## Summary
- Forward Babel/JSX compile errors, runtime throws, and unhandled
rejections from the AI dashboard sandbox iframe to the parent composer
via `postMessage`, so users see actionable errors instead of a blank
preview
- Compile AI-generated dashboard source explicitly with
`Babel.transform` + try/catch (stored in `text/plain` to avoid Babel's
auto-handler swallowing parse errors) and add `crossorigin="anonymous"`
on the Babel script for readable cross-origin error messages
- Switch authenticated smart-tier model from
`moonshotai/kimi-k2.6:nitro` to `x-ai/grok-build-0.1`
## Test plan
- [ ] Generate a dashboard with valid AI code and confirm the preview
still renders
- [ ] Generate a dashboard with invalid JSX and confirm the composer
shows the compile error (not a blank iframe)
- [ ] Trigger a runtime error in generated dashboard code and confirm it
reaches the parent error boundary
- [ ] Verify authenticated smart-tier requests route to
`x-ai/grok-build-0.1`
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Embedded dashboards now show a clear “Dashboard failed to compile”
message on compilation errors instead of a blank iframe.
* Dashboard runtime errors and unhandled promise rejections are captured
earlier and forwarded to the parent for improved visibility.
* **Updates**
* The authenticated AI model used for the "smart" quality has been
changed, affecting model selection for authenticated requests.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1476?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Add an admin-only delete endpoint and SDK method to remove managed
email domains, with Resend/DNSimple cleanup and a guard against deleting
domains currently in use for sending.
- Add dashboard UI to remove unused managed domains (with confirmation)
and improve the DNS setup step with Cloudflare detection, zone file
download, and import instructions.
- Add E2E coverage for delete auth, success, in-use rejection,
post-switch deletion, and 404 cases.
## Test plan
- [ ] Run `pnpm test run managed-email-onboarding`
- [ ] In dashboard email settings, add a managed domain and verify
Cloudflare hint appears when NS records point to Cloudflare
- [ ] Remove an unused managed domain and confirm it disappears from the
list
- [ ] Verify active (in-use) managed domains cannot be deleted until
email provider is switched away
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Delete managed email domains from the dashboard with a confirmation
flow and success notification
* Cloudflare-aware domain setup: detection banner, quick links to
Cloudflare DNS, downloadable zone file, and import instructions
* Admin API and admin-app method to perform managed-domain deletion
* **Bug Fixes**
* Deletion blocked with a clear error when a domain is actively used for
sending
* **Tests**
* Added end-to-end coverage for managed-domain delete scenarios
(success, in-use conflict, auth rejection, and 404)
* **Style**
* Data grid layout adjusted to prevent unintended full-height stretching
across various tables
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1442?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
In preview-mode deployments (`NEXT_PUBLIC_STACK_IS_PREVIEW=true`) the
project overview dashboard reported **0 total users, 0 monthly active
users, and no live users** on the globe. The internal metrics endpoint
reads user/team totals from the ClickHouse `analytics_internal.*` tables
and "live users" from recent `$token-refresh` events — but those tables
are normally filled by the external-db-sync pipeline, which does not run
in preview deployments, so they were empty.
This makes the preview/demo dummy-data seeder populate ClickHouse
directly:
- **`seedDummyAnalyticsMirrorTables`** — mirrors the seeded users /
teams / contact channels into `analytics_internal.users` / `teams` /
`contact_channels` so the metrics endpoint reports real totals.
- **`seedDummyLiveTokenRefreshEvents`** — emits recent `$token-refresh`
events across distinct countries so the overview globe shows live users.
- **Timestamp clamping** — `bulkRandomTimestampOnDay` and the
page-view/click timestamps are clamped so seeded events are never dated
in the future (future-dated events permanently matched the unbounded
"live users" query).
- **`buildTokenRefreshClickhouseRow`** — shared helper for the
`$token-refresh` ClickHouse row shape.
- **`create-project`** — pre-warms the ClickHouse connection so the
seeding inserts don't pay the cold-start cost.
- **`projects-metrics`** — types the ClickHouse `.json()` results (fixes
a `tsc` error).
Also bundles a seeding performance optimization that skips redundant
idempotency lookups when seeding a brand-new project.
Notes:
- Seeded mirror rows use `sync_sequence_id = 0` so that if the
external-db-sync pipeline ever does run for the project, any real update
supersedes the seeded placeholder under `ReplacingMergeTree` + `FINAL`.
- "Live users" naturally decays out of the ~2-minute window a couple of
minutes after project creation; preview creates a fresh project per
visit, so the initial overview always shows them.
## Test plan
- [x] `pnpm --filter @stackframe/backend typecheck` passes
- [x] `pnpm --filter @stackframe/backend lint` passes
- [x] Created fresh preview projects; overview shows non-zero Total
Users / Monthly Active Users
- [x] `analytics_internal.users` / `teams` / `contact_channels`
populated for the seeded project
- [x] Globe shows 8 live users across 8 distinct countries (verified via
the metrics 2-minute query)
- [x] No future-dated `$token-refresh` events in
`analytics_internal.events`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Faster preview project creation by pre-warming the analytics database
and reusing the warmed connection.
* Reduced initialization delays and redundant checks when seeding
brand-new projects; creation paths now skip needless probes.
* More efficient, parallelized seeding of teams/users/events with
deterministic handling of token-refresh and session-replay data.
* Safer timestamp generation to avoid future-dated events and deferred
background processing for long-running tasks like payments.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1471?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
### Context
There was a small bug via dashboard checkout flow where it would fail on
trying to create a checkout flow for a free product subscription because
no client secret is generated for a 0 dollar subscription.
### Summary of Changes
The flow should be fine now. There's special carve out logic for it.
That being said, users attempting to mimic a free plan grant are
encouraged to follow the `ensureFreePlan` pattern.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Free subscription selections now bypass Stripe payment processing,
streamlining checkout for zero-cost offerings.
* Purchase return flow now properly recognizes and activates free
subscriptions without requiring payment confirmation.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1465?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Move `loadTotalUsers`, `loadAuthOverview`, and
`loadRecentlyActiveUsers` off direct Postgres queries to read from the
ClickHouse `analytics_internal` tables.
- Route the remaining `projectUser.findMany` reads in
`loadActiveUsersByCountry` and `loadRecentlyActiveUsers` through
`$replica()`.
- `loadRecentlyActiveUsers` falls back to an empty list on ClickHouse
query failure (captured via `captureError`) rather than failing the
whole metrics endpoint.
## Test plan
- [ ] Hit the internal metrics endpoint on a tenancy with users/teams
and confirm totals, daily series, and recently-active users match the
previous Postgres-backed numbers.
- [ ] Verify the 30-day daily-users series fills zero-activity days
correctly.
- [ ] Simulate a ClickHouse failure for the recently-active query and
confirm the endpoint still responds with the rest of the payload.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes & Improvements**
* Improved metrics aggregation for more consistent reporting.
* More accurate active-user and total-user time series with missing days
zero-filled.
* Authentication overview updated with clearer counts for verified,
unverified, and anonymous users.
* Performance improvements: recently-active and overview calculations
run more efficiently and in parallel.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1463?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
End-to-end flow for managing Stack Auth config via GitHub: link a repo
during onboarding, edit settings in the dashboard, and have the change
committed to your repo + synced back via a GitHub Actions workflow.

## What this adds
- **CLI** — `stack config push --source github --source-repo
--source-path --source-workflow-path`. Records the source on the config
row so the dashboard knows where the file lives. Reads `GITHUB_SHA` /
`GITHUB_REF_NAME` for commit + branch.
- **Onboarding "Link existing project"** — searchable repo/branch
comboboxes, auto-detects candidate `stack.config.{ts,js}` paths, writes
`STACK_AUTH_PROJECT_ID` + `STACK_AUTH_SECRET_SERVER_KEY` secrets, and
commits a generated workflow YAML that re-runs `stack config push` on
every change to the config file.
- **Dashboard "Push to GitHub" dialog** — replaces the prior TODO
buttons. Pre-flights `repo`+`workflow` scopes on the user's GitHub
connection; if missing, the button flips to "Reconnect with GitHub". On
push, commits the dashboard's edit straight to the linked repo/branch
via the Contents API (with `cache: "no-store"` to dodge GitHub's 60s GET
cache so consecutive pushes don't 409). Suspense boundary scoped to the
dialog body so opening it doesn't blank the dashboard.
- **Project settings** — surface the linked workflow file as a clickable
GitHub link when the source carries `workflow_path`.
## Test plan
- `pnpm lint` (29/29) ✓
- `pnpm typecheck` (29/29) ✓
- `pnpm --filter @stackframe/stack-cli test` (111/111) ✓
- Dashboard vitest on the three relevant files
(`link-existing-onboarding-workflow`, `github-api`,
`github-config-push`) — 37/37 ✓
- Live end-to-end: `BilalG1/lex-lookup` linked to a local dev project;
passkey toggled, push committed `0bb958bd`
([commit](0bb958bda3)).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Persist workflow file paths for GitHub-backed config sync
* Dashboard “Push” flow to commit config updates with trimmed/default
commit messages
* CLI options to declare GitHub source (repo/path/workflow) and persist
selectable package runner for manual pushes
* Show workflow-file link in project configuration when present
* **Improvements**
* Robust config-path normalization, existence checks, debounced
repo/branch search, and better GitHub rate-limit handling
* New GitHub API utilities for safe file read/commit and import-package
detection
* **Tests**
* Expanded tests covering GitHub API, config rendering/merge, and push
behaviors
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1450?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fixes Sentry
[STACK-BACKEND-16H](https://stackframe-pw.sentry.io/issues/STACK-BACKEND-16H)
— the `/api/v1/internal/metrics` endpoint was triggering the cluster's
10.8 GiB OvercommitTracker kill on tenants with months of
`$token-refresh` history.
## Root cause
Three queries in `loadAnalyticsOverview` plus `loadUsersByCountry` did
`GROUP BY user_id` over the events table with **no lower `event_at`
bound**, so their hash table working set scaled with
cumulative-distinct-users-ever-seen instead of the 30-day metrics
window.
## Changes
- Add 30-day `event_at` lower bound to `loadUsersByCountry` and to the
`analyticsUserJoin` inner subquery (used by `dailyEvents`,
`totalVisitors`, `topReferrers`).
- New `getClickhouseAdminClientForMetrics()` factory in
`lib/clickhouse.tsx` with connection-level safety net: per-query +
per-user memory caps, external GROUP BY spill, and `join_algorithm:
'grace_hash,parallel_hash,hash'` (grace_hash measured to give 48% memory
reduction at zero latency cost — see benchmark notes in the file).
- Inline comment + concrete next steps for the long-term fix (option C:
stamp `is_anonymous` at ingest on page-view/click events, then drop the
join entirely).
- Extend `scripts/benchmark-internal-metrics.ts` with the
historical-seed knob and three new modes (`BENCH_BACKFILL_COMPARE`,
`BENCH_JOIN_ALGO_COMPARE`, plus the existing `BENCH_ROUTE_QUERIES`
updated) used to validate the choices above.
## Benchmark — pre-PR vs post-PR
Synthetic seed: 300k users × 9 events spread over 365 days (~2.7M
events).
| | pre-PR | post-PR | delta |
|---|---:|---:|---:|
| Sum peak memory | 2.18 GiB | 515 MiB | **4.3× less** |
| Max query duration | 1293 ms | 101 ms | **12.8× faster** |
| Sum CPU duration | 5119 ms | 394 ms | 13× less work |
| Sum bytes read | 3.87 GiB | 929 MiB | 4.3× less I/O |
Per-query at 300k users:
- `analyticsOverview:dailyEvents` 561 → 44 MiB (12.8× less)
- `analyticsOverview:totalVisitors` 560 → 50 MiB (11.2× less)
- `analyticsOverview:topReferrers` 546 → 50 MiB (10.9× less)
- `loadUsersByCountry` 388 → 44 MiB (8.9× less)
## Caveats
- `loadDailyActiveSplitFromClickhouse` still scans all-history on its
`min(event_at)` subquery. It can't be naively bounded — `first_date` is
used to classify entities as new vs reactivated, and a 30d bound would
silently mislabel old-but-active entities as "new." The new SETTINGS
cap+spill it; the proper fix is option C (documented inline).
- A user with a page-view but no `$token-refresh` in the last 30 days
now falls through to `coalesce(NULL, 0)` and is classified
non-anonymous. Token-refresh fires every few minutes per active session,
so this is rare but not impossible (embedded SDKs that poll less
frequently, sessions straddling the 30d boundary).
- `max_memory_usage_for_user: 9 GB` trades "cluster-wide
OvercommitTracker kill of a random query" for "clean per-user memory
error attributed to the specific query." After our 30d bounds, no query
is anywhere near 9 GB.
## Test plan
- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal-metrics.test.ts` — 9/10
pass; the 1 failure (`risk_scores` snapshot drift) reproduces on clean
`dev` and is unrelated
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/analytics-{events,events-batch,query}.test.ts
apps/e2e/tests/backend/endpoints/api/v1/token-refresh-events.test.ts
apps/e2e/tests/backend/performance/metrics.test.ts` — all passing tests
pass; 10 pre-existing `PRODUCT_DOES_NOT_EXIST` setup failures reproduce
on clean `dev`
- [x] Benchmark `BENCH_ROUTE_QUERIES=1` at 300k users shows the deltas
above
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Improved internal metrics collection to use metrics-specific DB
settings for more reliable, safer analytical reads.
* Added guardrails to metrics queries to enforce time-window bounds and
avoid unbounded scans.
* Expanded benchmark modes (backfill and join-algo comparisons),
extended perf seeding, and improved logging/retry behavior to capture
more complete stats and reduce missing log rows.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1457?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->