Changelog

Changelog

Cambios relevantes en CastorStack.

v1.36.0

CastorStack 1.36.0

  • Public blog `GET` endpoints have their own rate-limit policy (1000/min by default) instead of sharing the generic 100/min budget. A full marketing-site build fetches every post in every locale from one egress IP, which used to abort the build halfway.
  • The blog build is also resilient on its own: a transient API failure while prerendering a post is swallowed during `next build` only, so the page falls back instead of failing the build. At runtime the same failure still surfaces as an error page rather than a silent 404.
  • The marketing site uploads hidden sourcemaps to Sentry at build time and tags events with a release id, so a browser stack trace points at real source. Without `SENTRY_AUTH_TOKEN` nothing is generated, uploaded, or served — the build is unchanged.
  • The API no longer sends to Sentry: a client that aborts mid-upload (`BadHttpRequestException`), and the CRM tick's expected unique-index race. Both were already handled in code and only burned quota.

v1.35.0

CastorStack 1.35.0

  • Marketing site `[locale]` pages are prerendered and CDN-cacheable again. The locale cookie and `Accept-Language` re-detection are off, so a localized page is no longer served by a function on every request.
  • A plan checkout config that reuses an external price ID already linked to another plan of the same payment provider now returns a localized `409 Conflict` naming the other plan, instead of a `500`.
  • A manual admin purchase grant no longer requires the `manual` payment provider to be active, refuses a second grant of the same plan to the same buyer, and invalidates that buyer's session cache so the plan is released without a new sign-in.
  • Checkout no longer guesses the buyer's billing country from the browser language. The profile carries `billingCountryCode`, and the product access page uses it.

v1.34.0

CastorStack 1.34.0

  • The shared CI workflow honors the optional `AZURE_PRODUCTION_DEPLOY=false` repository variable for derived products. It skips only the production Azure API deployment; explicit development deployments remain available.
  • The pre-push hook now validates only the affected build surface. A first push compares the branch with its `origin/main` merge-base instead of treating every surface as changed, so buyer-package validation remains a release concern rather than a branch-publication delay.

v1.33.0

CastorStack 1.33.0

  • Manual segmented broadcast email. `CrmBroadcast` and `CrmBroadcastRecipient` freeze the resolved audience at creation time, from presets ("whole base", "non-paying" — never paid anything, churned included). `CrmSendingEngine` drains pending broadcasts on each tick, reusing the budget, send window, suppression and frequency cap the automations already had; a failed send returns to `Pending` and is retried on the next tick, matching automation semantics. `CrmEmailComposer` is extracted from `CrmSendingEngine` and `AdminCrmEndpoints` so the layout fallback is not written three times. Admin gets audience preview, draft creation, test send, launch, cancel and progress, under a new "Avulso" tab.
  • `azure-deploy.mjs` retries a modifying call that Azure rejects with `ContainerAppOperationInProgress`, or lets expire. Waiting on `provisioningState` before the call is not enough on its own: another change can land between the poll and the call. Three attempts, growing backoff, and a wait for the app to settle between them. **Only those two answers are retried** — any other error still fails on the first attempt, which is what separates a transient platform answer from a real defect. A derived product had already ported this retry into the shell of its own `ci.yml`, because it was hitting the failure in practice. Adopting the script without it would have been a regression.

v1.32.0

CastorStack 1.32.0

  • `RESEND_WEBHOOK_SECRET` now resolves through `IRuntimeSecretProvider` instead of `IConfiguration`. It was the only shared secret in the codebase read directly from configuration: its siblings of the same nature — `CRM_TICK_SECRET` and `BILLING_RECONCILE_SECRET`, also validated inside a handler on an open route — already went through `InternalCronSecretResolver`, which is fail-closed and caches for five minutes. With the secret provider switched to a vault, every secret of that class came from the vault except this one, which still required a loose environment variable on each host. `CrmResendWebhookEndpoints` now uses the same resolver.
  • `azure-deploy.mjs` takes `AZURE_API_IMAGE` (default `saas-core-api`). The registry, resource group and container app were already parameterised, but the image repository name was hardcoded, so a derived product calling this script would publish its image into a repository nothing else pulls from. That is why at least one derived product kept a hand-written shell deploy in its `ci.yml` and lost the two properties this script has: reading from the vault, and failing closed when a key is missing. The ACR build also publishes `:latest` alongside the commit sha, which matches what such a hand-written deploy typically already did.
  • `env:push` accepts `--from local|infisical` (default `local`). The push always read the `.env` of the machine running it, which makes every target a copy of one workstation — including the vault itself. Reading the vault instead lets Infisical be the source and Vercel, GitHub and Azure be replicas.
  • `scripts/env-push.mjs` gained the entrypoint guard the other scripts already had. `main()` ran on import, so the file could not be imported and therefore had no unit tests of its own; only `lib/env-push.mjs` was covered.

v1.31.0

CastorStack 1.31.0

  • Coupons support a per-user validity window layered on top of the existing global `ExpiresAt`: `PerUserWindowAnchor` (`None` / `LastAccess` / `OfferSignal`) plus `PerUserWindowMinutes` on `Coupon`. `LastAccess` anchors at `Profile.LastAccessAt`; `OfferSignal` anchors at the first `POST /api/v1/me/coupons/{code}/start-offer` call a consuming product makes for that coupon+profile pair (idempotent — later calls just echo the first timestamp back). The new authenticated `POST /api/v1/me/coupons/validate` resolves the window and returns `PerUserWindowDeadlineAt` alongside the usual discount fields; `CouponValidator` rejects with `PerUserWindowNotStarted` or `PerUserWindowExpired` once the deadline has passed. The anonymous `PublicCouponEndpoints` path is unchanged and cannot resolve a per-user window — a coupon that needs one is only fully evaluable through the new `/me` endpoint. Admin configures the anchor and duration per coupon in `CouponFormPage`.
  • CRM email layout is now editable per locale instead of hardcoded. New `crm_email_layouts` table (`Locale` PK, `Html`, `UpdatedAt`) and `CrmTemplateRenderer.Compose`, applied before variable substitution in both places that render a CRM body: `CrmSendingEngine` and the admin test-send endpoint. A locale without a row sends the body unchanged (same as before 1.30.0's shared wrapper). New admin Layouts tab (`GET`/`PUT /api/v1/admin/crm/layouts`) edits the layout HTML per locale and previews the composed body through the same compose rule the API applies.
  • Users table (`GET /api/v1/admin/users`) sorts by column: `sortBy`/ `sortDirection` query params, closed whitelist (`name`/`email`/`status`/ `createdAt`/`lastAccessAt`), 400 on an invalid value, deterministic `Id` tie-break. Also fixes "Last accessed" to read `Profile.LastAccessAt` instead of `UpdatedAt`. Frontend adds sortable `th` buttons with `aria-sort`; demo mode sorts before pagination.
  • `shared/env-manifest.json` supports a per-surface default (`defaultsBySurface`) alongside the existing single `default`, so a key like `CORS_ALLOWED_ORIGINS` can keep its permissive local-dev default on `root` while the `api-container` surface gets a stricter one (production origins only, no `localhost`) without hand-editing the generated `.env.runtime`.

v1.30.0

CastorStack 1.30.0

  • New `CrmEmailLayoutRenderer` wraps the rendered body of every CRM email in a single shared layout (sender name header, consistent typography, footer with the unsubscribe link) before it reaches the Resend sender. Every automation and template picks up the same look without any per-template HTML edits — stored `CrmEmailTemplates.BodyHtml` rows are unchanged.
  • The admin `/templates/test` endpoint applies the same wrapper, so a test-send from the CRM admin screens now matches what a real recipient gets instead of previewing a bare fragment.
  • The unsubscribe link label is localized (pt/es/en) through the new `CrmEmailLayoutCopy` helper, following the same pattern as `AccessEmailCopy`.

v1.29.0

CastorStack 1.29.0

  • The API deploy injects the container's environment from Infisical. It used to update the image and leave the twelve environment variables to whatever had been set by hand in the Container App, which made every hosting change a manual re-registration. `node scripts/with-infisical-env.mjs node scripts/azure-deploy.mjs api` is now the whole contract, and a key the vault lacks fails the deploy with the list of missing names instead of falling through to the local `.env` and overwriting production with a development value.
  • New `api-container` surface in `shared/env-manifest.json`, generated to `infra/api-container.env.example`: the thirteen keys the production API container receives. The `root` surface could not play that role — it is the local `.env` template, so sourcing the deploy from it would have shipped fifteen `VITE_*` keys, `E2E_ADMIN_PASSWORD`, `E2E_LOGIN_PASSWORD` and the local Postgres password to production. Classification cannot separate them either: `POSTGRES_PASSWORD` and `SUPABASE_DB_CONNECTION` are both `secret` on `surfaces:["root"]`.
  • `validate:env-manifest` rejects a key the Infisical filter would drop. The filter in `scripts/with-infisical-env.mjs` keeps only `^[A-Z][A-Z0-9_]*$` and discards the rest **without an error**, so `Product__Name` would have been registered in the vault and never reached the container.
  • `Product__Name` is now `PRODUCT__NAME`. The .NET environment-variable provider is case-insensitive, so `configuration["Product:Name"]` resolves unchanged; `ProductNameConfigurationTests` proves it through `AddEnvironmentVariables()` rather than assuming.

v1.28.0

CastorStack 1.28.0

  • CRM inactivity triggers: `inactive_14_30_60d` and `sunset_90d` are now registered, so the `reengagement` and `sunset` automations enroll profiles instead of skipping with a warning. Their exit conditions ship with them — `accessed_again`, `clicked_or_accessed` and `responded`.
  • The candidate window no longer excludes the profiles an inactivity trigger is for. It used to be one 60-day bound on `Profile.CreatedAt` shared by every trigger, which made `sunset_90d` unreachable by construction: a profile dormant for more than 90 days had left the window before it could qualify. Signup and activation triggers keep that bound (now `SignupCandidateWindow`); the inactivity triggers range on `Profile.LastAccessAt` with a per-tick candidate cap instead, so the tick query stays bounded.
  • A CRM tick now reports an unregistered exit condition, not only an unregistered trigger. The engine used to validate the trigger first and skip the automation, so a missing exit condition never reached the warnings — five of them were invisible in production while the diagnosis chased the three triggers that did show up.
  • `reengagement` and `sunset` no longer chase the same dormant profile: `inactive_14_30_60d` is capped at the sunset threshold.

v1.27.0

CastorStack 1.27.0

  • The API now retries transient Postgres failures instead of answering 500. A managed pooler recycling a connection used to break any endpoint that touched the database, including public anonymous ones; database commands also get an explicit 60s timeout instead of the driver default of 30s.
  • Admin panel edge routing derives from one route manifest, so a route that exists in the app can no longer 404 at the edge because a second regex list was not updated. Product routes get their own manifest file to list them in.
  • New `trackEvent(name, params)` analytics helper in the web app and marketing site for funnel events, alongside the existing page-view tracking; signup completion and product access now emit events through it.
  • The worktree git hook mirrors `node_modules` for every subpackage that has its own, not only the repository root, so `npm --prefix <pkg>` no longer fails in a fresh worktree with an error that does not look like the cause.

v1.26.0

CastorStack 1.26.0

  • CRM engine admin tab: turn the lifecycle engine on/off, set max sends per tick, spacing between sends, and a sending window (local time), all backed by runtime settings.
  • `CRM_TICK_SECRET` and `BILLING_RECONCILE_SECRET` now resolve through the configured secret provider (environment variable or Infisical) instead of raw process env vars, and fail closed to 503 instead of a bare 500 if the provider is unreachable. Both are cached briefly so an anonymous request against the open cron endpoints cannot hammer a remote vault.
  • Manual OneTime plan grants: `POST /admin/purchases` lets an admin record a purchase without a real checkout.
  • CRM unsubscribe now requires the reader to confirm (no more one-click GET unsubscribe), and template test-sends fill every declared variable instead of leaving some blank.

v1.25.1

CastorStack 1.25.1

  • Worktree bootstrap now links `node_modules` for the todo-implement flow, avoiding a stale dependency error before the periodic sync job runs.

v1.25.0

CastorStack 1.25.0

  • CRM analytics. `GET /api/v1/admin/crm/analytics?days=7|30|90` reports, per automation, delivery health (bounce/complaint rate against attempted sends, with an ok/warning/critical threshold) and conversion up to the goal (a new `ExitReason` on each enrollment distinguishes "left because it converted" from "left because the steps ran out"), plus a per-step funnel of sends.
  • The CRM now has a public unsubscribe page (`/crm/unsubscribe?token=...`) in the web app, and delivery events from Resend (delivered/opened/clicked/ bounced/complained) reach it through a signed webhook that updates each email's status.
  • The marketing site can emit a Bing Webmaster Tools verification tag (`BING_SITE_VERIFICATION`) alongside the existing Google one.

v1.24.0

CastorStack 1.24.0

  • Billing reconciliation. `POST /api/v1/internal/billing/reconcile` asks every active provider what it settled in the last `windowHours` (24 by default) and answers with the settled payments that have no Completed purchase beside them. It is fail-closed behind `BILLING_RECONCILE_SECRET`, the same shape as the CRM tick, and an hourly workflow exits non-zero on any discrepancy so the scheduler's own failure notification is the alert. The new `ISettledPaymentSource` is optional like `ISubscriptionService`: Stripe implements it, Paddle and Polar are reported as skipped instead of breaking the run, and a provider that throws is skipped too so one unreachable provider cannot hide what the others can still report. The response carries no buyer email and no customer id. Found the hard way: an install ran nine days with a 404 webhook URL and 19 checkouts recorded zero purchases, with nothing anywhere raising a single alert.
  • The feedback widget no longer asks for an opinion on a first visit. `Profile` gains `AccessCount`/`LastAccessAt`, bumped once per session, and `FeedbackSettings` gains an admin-editable `MinimumAccessCount` (default 2) enforced before the existing opt-out and reopen-policy checks.
  • Node.js 20 reached end of life and Vercel stops building on it from October 1st. `engines.node`, `.nvmrc`, the four workflows that pin `setup-node` and the onboarding install requirements in all three languages move to 24.
  • The core line and the derived-product merge are now validated on every pull request that touches a core-owned path, instead of on the day of the cut. Two new PR-only jobs simulate the `core-v*` cut in an ephemeral clone and run `qa:fast` over the frozen tree, then merge that tree into a throwaway clone of a derived product and fail only on a core-owned conflict.

v1.23.0

CastorStack 1.23.0

  • Lifecycle email automations arrive in two phases. The first adds the schema (automations, steps, templates, send logs, suppressions), the seed for nine generic lifecycle automations in pt/en/es, and the `ICrmTrigger`/`ICrmTemplateVariableProvider`/`ICrmActivationSignal` contract a derived product implements to plug its own triggers in without editing core-owned files.
  • The second phase adds `CrmSendingEngine`: it evaluates every enabled automation and sends the step that is due, with a runtime kill switch (`crm_enabled`), exit conditions, suppression, a frequency ceiling (1/day, 3/week), quiet hours, and idempotency in two layers — a pointer to the last log plus a unique index on `(ProfileId, AutomationKey, StepIndex, Cycle)` as a backstop for concurrent ticks. `POST /api/v1/internal/crm/tick` is fail-closed behind `CRM_TICK_SECRET` and driven by a scheduled workflow; `GET`/`POST /api/v1/crm/unsubscribe` implements one-click unsubscribe (RFC 8058) with a signed token.
  • A Stripe checkout whose payment is still pending no longer grants access. Boleto, Pix and SEPA debit close the session before the money arrives, so `checkout.session.completed` fires with `payment_status: "unpaid"` — that event was creating a Completed purchase, which meant anyone could generate a boleto, never pay it, and get in. The unpaid event is now acknowledged and audited as `checkout_awaiting_payment` without creating a purchase, and `checkout.session.async_payment_succeeded` fulfils the checkout once the payment clears.
  • The feedback widget's AI affordance is gated on the `ai_credits` entitlement instead of being offered to everyone, and a failed generation surfaces the error rather than failing silently.

v1.22.0

CastorStack 1.22.0

  • A star-rating feedback widget collects ratings and comments from the customer dashboard, the support page and a modal, with placement, copy and reopen policy configured in the admin panel; submissions get their own admin list, and a profile can be opted out of ever seeing it. Dashboard and support placements are enabled by default; the modal is not.
  • End users can now trigger AI Gateway actions from the app through `POST /api/v1/me/ai-actions/{actionKey}`, metered and gated by the `ai_credits` entitlement introduced in 1.21.0, with a reusable `GenerateWithAiButton` component.
  • The global footer gained a product-owned signup CTA seam driven by `OWNER_FOOTER_CTA`, so a derived product adds its own conversion link without editing the core footer.
  • Authenticated requests in `web-app` are now bounded by a 20s timeout: a hung request rejects instead of leaving protected routes stuck on a loading state forever.

v1.21.0

CastorStack 1.21.0

  • On-demand revalidation (`revalidate/route.ts` + the API-side notifier) now accepts any caller-supplied paths instead of hardcoded blog routes, so a derived product can trigger revalidation from its own controller without a new secret.
  • User-triggered AI Gateway calls are now metered and can be gated against a numeric `ai_credits` plan feature, with a new admin read-only usage log.
  • Tracking-consent gating (GA4, Meta Pixel) is centralized in one shared helper instead of each provider re-implementing its own consent check.
  • A `fake` payment provider (test environments only) drives checkout and webhook flows end-to-end without a real Stripe/Paddle/Polar account, for deterministic E2E coverage.

v1.20.0

CastorStack 1.20.0

  • Admin → Settings now edits the MCP allowed origins, stored in a new `runtime_settings` table, so changing them no longer needs a redeploy; `MCP_ALLOWED_ORIGINS` and the built-in defaults remain the fallback.
  • The web-app sign-in forgets the remembered email as soon as "remember me" is unchecked, instead of only on submit.
  • Git worktrees link the main checkout's `node_modules` instead of crashing the pre-commit and pre-push hooks.
  • The repo pins Node 20 (`engines` and `.nvmrc`) to match CI and stop a resolver failure on newer local Node versions.

v1.19.1

CastorStack 1.19.1

  • The Meta Pixel now loads: `connect.facebook.net` was missing from `script-src` in both frontends' `vercel.json`, so the browser's Content Security Policy blocked `fbevents.js` on every deployment and no client-side event ever reached Meta.
  • Page traffic is now measured as `PageView` on each route instead of a single `ViewContent` on mount: the layout mounts once, so client-side navigation went uncounted and a whole visit reported one event.
  • The Meta Pixel now waits for the same cookie-banner acceptance Google Analytics already required, instead of loading as soon as an admin had the provider active.

v1.19.0

CastorStack 1.19.0

  • Admin → AI provider connections: global admins store an encrypted OpenAI or OpenRouter API key, pick catalog models per connection, and promote one connection as the default for text and one for image generation; backend code calls a single `IAiGateway` instead of talking to any provider directly.
  • Admin → Integrations → WhatsApp: a global support number and per-screen visibility now drive a dismissible `wa.me` widget shown to customers whose plan grants the `priority_support` feature.
  • Admin → Integrations → Meta Pixel: an opt-in Meta Pixel + Conversions API connection fires `ViewContent`/`InitiateCheckout`/`CompleteRegistration` client-side and a server-side `Purchase` event via the Conversions API, deduplicated against the existing webhook processing guard.
  • Signup now captures `fbclid`/`fbc`/`fbp` alongside the existing UTM/referrer attribution fields, fixing a bug where a later ad click's `fbclid` updated in memory but never persisted back to storage.

v1.18.0

CastorStack 1.18.0

  • Added remote MCP servers so ChatGPT and Claude can connect over authenticated HTTP using a client key created in Admin > Integrations > MCP, scoped and revocable without touching infra.
  • `mcp-actions` gained typed list/get/create/update/remove blog tools; create and update take `SaveBlogPostRequest` fields, and `status` published writes to the database, triggers revalidation, and appears on the public API (draft does not).
  • The remote MCP transport authenticates every request with the caller's own client key end to end; there is no separate server-only key to deploy or rotate.
  • Saving a blog post through the admin API now treats `status` as case-insensitive (`published` and `Published` both publish).

v1.17.0

CastorStack 1.17.0

  • Added remote MCP servers (`mcp.castorstack.com`, `mcp-actions.castorstack.com`) so ChatGPT and Claude can connect over authenticated HTTP using a client key created in Admin > Integrations > MCP, scoped and revocable without touching infra.
  • `mcp-actions` gained typed list/get/create/update/remove blog tools; create and update take `SaveBlogPostRequest` fields, and `status` published writes to the database, triggers revalidation, and appears on the public API (draft does not).
  • Saving a blog post through the admin API now treats `status` as case-insensitive (`published` and `Published` both publish).
  • The admin dashboard gate probes `GET /api/v1/admin/roles` (status-only) instead of listing payment providers, starts the profile fetch in parallel, and aborts the probe after 15s so a hung request shows retry instead of `ui.loading.stuck`.

v1.16.2

CastorStack 1.16.2

  • Fixed a bug where a Supabase storage folder's path was missing its trailing separator, breaking navigation into any folder more than one level deep in the admin Storage Explorer.

v1.16.1

CastorStack 1.16.1

  • The app switcher's "Web App" link now carries first-touch UTM attribution into the web app, so a signup started from the switcher (instead of a marketing page's signup button) is no longer misattributed as direct traffic.

v1.16.0

CastorStack 1.16.0

  • Signups capture their first-touch UTM parameters, referrer and landing page, shown per-user in the admin panel and aggregated on a new Analytics page (signups by day and source).
  • Product coupon discounts are created automatically on Stripe or Paddle from the admin panel instead of requiring a pasted provider discount id.
  • The dashboard home page gained a product extension slot, alongside the four existing account-page slots.
  • Unknown routes on the admin panel and web app now return an actual HTTP 404 status with a branded, localized page instead of a 200.

v1.15.0

CastorStack 1.15.0

  • The four core account pages (Settings, Product access, Features, Support) render a product-owned extension slot, so a product adds its own section without editing a core page.
  • The template's demo home page, marketing sections and example billing page moved out of the product seam, leaving it free for the product's own files.
  • Installing dependencies now registers the git merge driver that keeps product-owned and generated files from conflicting on every upstream update.
  • A product can be marked as accepting discount coupons directly on the admin product form.

v1.14.1

CastorStack 1.14.1

  • Fixed uploads failing on an install whose storage is configured by environment variables and has no saved connection yet.

v1.14.0

CastorStack 1.14.0

  • New Storage browser in the admin panel: navigate the bucket by folder, with image preview through short-lived signed URLs.
  • Every stored file now carries an owner and a purpose, so the panel shows who a file belongs to and what it is for.
  • The listing separates files that are registered, orphan, or missing, so a file deleted outside the panel stops being invisible.
  • Upload, replace, rename and delete files from the panel. Deleting a file that is still in use is refused and lists what depends on it.

v1.13.0

CastorStack 1.13.0

  • Hero section on the marketing home now plays a short intro video.
  • Preview section shows real product screenshots instead of placeholders.
  • Download page has clearer, more premium post-download instructions.
  • AppSwitcher links out to pricing so users can upgrade from any app.

v1.12.2

CastorStack 1.12.2

  • The license note component and its test no longer ship: 1.12.1 removed the note from the pages but left the files behind, and the test asserted against message keys that release had deleted.

v1.12.1

CastorStack 1.12.1

  • The license note below the pricing card is gone from the home page and from /pricing; the license page stays reachable from the footer.

v1.12.0

CastorStack 1.12.0

  • Plan features are edited per locale in the admin and served localized in the public catalog and pricing card.
  • The marketing purchase section is rebuilt around license, price, inclusions, and guarantee.
  • Home page anchors land on the right position, with reveal animations on scroll.
  • A failed download now shows a readable message instead of a raw error.

v1.11.0

CastorStack 1.11.0

  • Account page consolidates profile, password, and billing controls, with password re-authentication before sensitive changes.
  • Feature visibility and destinations are configurable per plan, with an upcoming-bills view on the dashboard.
  • Product webhook events, buyer ZIP release packaging, and shared app links across the frontends.
  • Marketing site gains a credibility/proof section, an about page, and refreshed home and pricing copy.

v1.10.0

CastorStack 1.10.0

  • Admin can edit runtime product links, auth email templates, and user or subscription records.
  • Marketing pricing localizes through Paddle PricePreview.
  • Env manifest drives `.env.example`, with `env:doctor` and `env:push`.
  • Tag push builds the buyer ZIP and syncs the public changelog.

v1.9.2

CastorStack 1.9.2

  • Blog index stays statically generated when the API is unreachable.
  • E2E pipeline retries transient MSBuild build failures.

v1.9.1

CastorStack 1.9.1

  • Parallel refresh no longer reuses a rotated refresh token.
  • Pricing and features JSON documented as bootstrap seed only.
  • CI retries transient MSBuild failures.

v1.9.0

CastorStack 1.9.0

  • Admin-granted profile feature overrides.
  • Extensible payment-provider seam for product modules.
  • Profile resolution middleware caching and closed PSP code registry.

v1.8.0

CastorStack 1.8.0

  • Google OAuth login with admin FAQ for non-config behaviors.
  • PSPs without an optional public key no longer appear pending.
  • Dev preview domains stay mapped on every main deploy.

v1.7.2

CastorStack 1.7.2

  • Polar added as a merchant-of-record payment provider.
  • PWA manifest generated from branding config.
  • SECURITY.md published; unknown web-app routes show 404.

v1.7.1

CastorStack 1.7.1

  • Bootstrap tests copy shared config schema into fixtures.
  • Webhooks return 200 for events not targeting this installation.

v1.7.0

CastorStack 1.7.0

  • One-time (lifetime) plan billing support.
  • Transactional auth emails sent via the API and GoTrue Send Email Hook.
  • Google OAuth login and CastorStack README/agent docs rebrand.

v1.6.2

CastorStack 1.6.2

  • Marketing locale tests derive routing expectations from config.

v1.6.1

CastorStack 1.6.1

  • No functional changes; corrects a mistagged 1.6.0 core cut.

v1.6.0

CastorStack 1.6.0

  • Auth email template preview per locale in the admin panel.
  • Template seed data uses neutral identity; locales follow product config.
  • Core release re-mirrors `.env.example` after freezing product config.

v1.5.2

CastorStack 1.5.2

  • Deploy checks validate the domain the deployment actually serves.

v1.5.1

CastorStack 1.5.1

  • Admin assets page tests use fixtures instead of template branding.

v1.5.0

CastorStack 1.5.0

  • Admin Project Assets library for versioned brand files.
  • API HTTP edge hardening: rate limiting, Swagger, and health checks.
  • Additional admin MCP tools for previously uncovered writes.

v1.4.0

CastorStack 1.4.0

  • Supabase storage write path with shared endpoint resolution.
  • Core release merge no longer blocked by the comment guard.

v1.3.0

CastorStack 1.3.0

  • Theme, typography, and navigation customizable via config files.
  • Admin MCP covers write operations across the admin surface.
  • Shared loading screen and ref-aware CI deploy targets.

v1.2.0

CastorStack 1.2.0

  • Self-hosted Supabase path with GoTrue and S3-compatible storage.
  • Admin MCP setup page with API key generation.
  • Security headers standardized across the three frontends.

v1.0.0

CastorStack 1.0.0

  • Profile-scoped authentication, authorization, subscriptions, entitlements, API keys, and audit logs.
  • Explicit reusable-core and product-owned boundaries across the API and all three frontends.
  • Independent `AppDbContext` and `ProductDbContext` migration histories.
  • The removable Upcoming Bills instructional product example with profile-owned persistence, RLS, API endpoints, localized UI, and tests.

v0.3.0

Internationalized frontends

  • Locale routing and translation files across marketing site, web app and admin panel.

v0.2.0

Multi-provider billing

  • Stripe and Paddle integrated end to end: country-to-provider routing, signed webhooks and entitlements, configured from the admin panel.

v0.1.0

Initial foundation

  • Monorepo with three frontends, .NET API, auth, tests and CI/CD structure.