Commit Graph

2186 Commits

Author SHA1 Message Date
github-actions[bot]
1999ad8be3 chore: update package versions 2026-06-11 17:19:24 +00:00
Mantra
59daf1321c
[codex] Add analytics overview filters (#1496)
## Summary

Adds richer analytics overview metrics and filterable dashboard
breakdowns.

- adds hourly overview series for the 1-day range
- adds country, referrer, browser, OS, and device filters to internal
metrics
- adds bounce rate, session duration, top countries, top browsers, top
operating systems, and device breakdowns
- updates the overview dashboard with filter chips, top-list cards,
animated metric states, and 1-day hourly chart support
- captures user agent on page-view analytics events, with a server-side
fallback for older clients

## Validation

Attempted targeted tests:

`pnpm test run
apps/backend/src/app/api/latest/internal/metrics/route.test.ts
'apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/analytics-chart-mode.test.ts'`

This did not reach Vitest in the temporary split worktree because
`node_modules` is not installed there and the repo pre-step failed at
`pnpm exec tsx ./scripts/generate-sdks.ts`.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds analytics overview filters with optional date‑range bounds and
1‑day hourly charts, plus smoother, accessible animations across charts
and top lists. Improves correctness and stability with deterministic
caching, normalized inputs, client‑only user‑agent capture, and
globe/layout fixes.

- **New Features**
- Filterable analytics overview (country, referrer, browser, OS, device)
with normalized inputs and optional `since`/`until`; API/admin/dashboard
accept `AnalyticsOverviewFilters` with deterministic cache keys.
- 1‑day hourly charts (page views, visitors) and a metric mode toggle
(DAU, Visitors, Revenue); animated top‑lists and sparklines powered by
`motion` with reduced‑motion support.
- UI: filter chips/menu, clearer tooltips (incl. user metric cards),
optional interactive globe with dynamic camera distance; exported
`TooltipPortal` from `@hexclave/ui`.

- **Refactors & Bug Fixes**
- Event ingest: client sends `user_agent`; removed server‑side fallback;
added user‑agent filter‑fragment builder and tests.
- Metrics correctness: aligned hourly bounds to start of UTC hour;
derived 1‑day revenue total from daily series; resilient chart x‑axis
formatting; country filter options use analytics `top_regions`;
fixed‑'en' locale for top‑lists; added date‑range parsing/validation for
filters.
- UI/runtime: smoother pill/tab slider animations with guards for
missing Web APIs; added `containedHeight` to `PageLayout` and wired into
sidebar/session replays; globe disables zoom when non‑interactive.
- Misc: instrumentation runs only in Node (`process.env.NEXT_RUNTIME ===
"nodejs"`); analytics/overview page redirects with URL‑encoded
`projectId`; Docker: include `@hexclave/template` in `turbo prune` to
fix CI builds.

<sup>Written for commit 7fcd3558a5.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1496?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Analytics filters (country, referrer, browser, OS, device); hourly
signup and active-user series; expanded hourly/daily analytics payloads
and top-lists UI.
* Chart metric modes (DAU, Visitors, Revenue), optional page-views
series, interactive globe support, animated Top Lists, and sparkline
animations.

* **Improvements**
* Better user-agent capture/normalization for batched events and
page-view tracking; reduced-motion aware animations; enhanced tooltips
and UI slider/tab indicators.
  * Added motion library dependency.

* **Tests**
  * New unit tests for analytics filters and chart metric mode behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mantra <mantra@stack-auth.com>
2026-06-10 17:50:35 -07:00
Armaan Jain
4479758a68
Ask mcp endpoint on skill (#1570)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/hexclave/hexclave/blob/dev/CONTRIBUTING.md

-->

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Expose MCP tools on the skills site as HTTP endpoints. Adds an MCP
wrapper that maps query params to JSON-RPC with CORS and stricter
validation; HEAD now validates routes and matches GET behavior.

- New Features
  - Dynamic GET/HEAD/OPTIONS route at `/[toolName]` in the skills app.
- MCP wrapper: resolves tool aliases, builds/validates args from query,
proxies to the MCP endpoint with timeout, sets CORS + no-store headers,
maps MCP/HTTP errors, and rejects malformed routes/params; utilities for
endpoint URL resolution from env or sibling host and listing available
route names.
- Tests/config: Vitest setup for `apps/skills`; coverage for HEAD
short-circuit and 404s, route resolution, argument coercion, and invalid
query cases.

- Bug Fixes
- HEAD delegates to the shared handler and returns 404 for unknown tool
routes.
  - IPv6 localhost detection accepts bracketed `[::1]` in URL hostnames.

<sup>Written for commit 4ad3c135fd.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1570?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added dynamic HTTP endpoints for MCP tools with GET/HEAD/OPTIONS
handling, tool discovery, route resolution, argument mapping/coercion,
and standardized text/error responses.

* **Tests**
* Added/expanded unit tests for route resolution, argument coercion,
endpoint derivation, response handling, HEAD behavior, and route-listing
snapshots.

* **Documentation**
* Clarified that hexclave dev injects required environment variables and
added guidance on updating auth SDK URLs to avoid redirect issues.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: armaan <armaan@stack-auth.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-06-11 04:21:50 +05:30
Madison
49442cab4b
[Fix] remove support button from user page (#1568)
Remove the support button from user profile pages. 

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Removed the Support button from user profile pages to simplify actions
and avoid navigating to conversations from this view. Also removed
unused code: the `urlString` import and `useRouter`.

<sup>Written for commit 807112ceec.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1568?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Removed the "Support" action button from user management actions in
the user details page. The remaining user actions such as impersonate,
restrict, and delete remain available.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-10 17:14:35 -05:00
Konsti Wohlwend
e15ea932f1
Bump submodules (#1577) 2026-06-10 13:53:13 -07:00
Aman Ganapathy
35e144e25c
feat(emails): disable unavailable email server options for local (#1576)
### Context
When working with local install, we can't send emails with resend keys/
managed domain/ custom smtp as of yet.
So, we disable those buttons and indicate to the user what's going on.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Disables unsupported email server options when running the dashboard
locally. Only the `shared` provider is selectable; others are disabled
with a clear tooltip.

- **New Features**
- In local development, disables Resend, Managed Domain, and Custom
SMTP; only `shared` remains enabled.
- Shows tooltip: "These email server options are not supported when
running dashboard locally." and applies disabled, non-interactive styles
without changing the card layout.

<sup>Written for commit 49828fd687.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1576?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Email provider selection UI now disables non-shared providers when the
dashboard is running in a local development/emulator environment, and
shows explanatory tooltips on the disabled provider cards. A
shared-environment tooltip string was added to clarify which option is
supported locally, improving clarity when configuring email settings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-10 13:22:01 -07:00
BilalG1
7f99f15b42
fix(rde): graceful config load errors + lightweight /config import path (#1557)
## Problem

A user hit `Failed to register development environment session (500)`
when running the RDE (`hexclave dev` / `stack dev`). Removing
`defineStackConfig` from their `stack.config.ts` made it go away.

**Root cause:** the local dashboard evaluates the project's config file
in a plain Node context via `jiti`
([config-file.ts](apps/dashboard/src/lib/remote-development-environment/config-file.ts)).
When the config imports a *value* (e.g. `defineStackConfig`) from a
framework package like `@stackframe/stack` / `@hexclave/next`, jiti
executes the entire SDK — React, `server-only`, Next internals — which
throws in that context. The exception propagated as a bare 500. Dropping
`defineStackConfig` removed the value import, so jiti no longer loaded
the framework.

## Changes

**1. Graceful error (Fix 3)**
`readConfigFile` now wraps the `jiti.import` in try/catch and rethrows a
message pointing at the lightweight import path, instead of a raw 500.

**2. Lightweight `/config` subpath (Fix 1)**
Added a side-effect-free `./config` entrypoint to the framework packages
— `@hexclave/{js,next,react,tanstack-start}/config` — that re-exports
`defineHexclaveConfig` / `defineStackConfig` + the `HexclaveConfig` type
from `@hexclave/shared/config`, with **no framework runtime**. Source of
truth:
[`packages/template/src/config.ts`](packages/template/src/config.ts) +
the export in
[`package-template.json`](packages/template/package-template.json),
propagated to the generated packages via `generate-sdks`.

> Why per-package and not `@hexclave/shared/config`: `@hexclave/shared`
is only a *transitive* dependency from a user's perspective, so
importing from it fails under pnpm strict mode. Users depend on the
framework package directly, so `@hexclave/next/config` always resolves.
This was confirmed empirically — the previous tests that imported
`@hexclave/shared/config` were red.

**3. Docs / prompts / renderer aligned to the new path**
-
[`ai-setup-prompt.ts`](packages/shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts)
+ regenerated `docs-mintlify` (setup.mdx, llms-full.txt, snippets).
- Hand-written
[`hexclave-config.mdx`](docs-mintlify/guides/going-further/hexclave-config.mdx)
and
[`local-vs-cloud-dashboard.mdx`](docs-mintlify/guides/going-further/local-vs-cloud-dashboard.mdx).
(`docs/**` left untouched — legacy.)
- `renderConfigFileContent` (the config file the dashboard/CLI
auto-writes) now emits `import type { HexclaveConfig } from
"<pkg>/config"`. Legacy `@stackframe/*` packages predate the subpath, so
they keep their root import (guarded).

## Behavioral note

Existing config files that import from a package root get their import
line upgraded to `/config` on their next dashboard/CLI sync — a
one-time, harmless rewrite that migrates them onto the safe path. The
github-config-push idempotence test was updated to use the current
`/config` format so it still genuinely verifies "no spurious commit."

## Testing

- 43 unit tests pass across `config-file`, `github-config-push`,
`config-rendering`, `config-authoring`, `local-emulator`. The two
previously-red RDE `define*` tests now pass through jiti via
`@hexclave/next/config` (the real code path), and were made
resolution-stable by rooting their temp dir at the test file instead of
`process.cwd()`.
- Typecheck green on all source-changed packages (shared, cli, js, next,
react, tanstack-start). Lint clean.
- ⚠️ The two e2e suites (`cli.test.ts`, `config-local-emulator.test.ts`)
need backend+DB infra; their snapshot updates are mechanical and
**confirmable only in CI**.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Prevents 500s when loading `hexclave.config.ts` by adding a lightweight
`<pkg>/config` entrypoint and showing a clear, actionable error without
leaking framework stacks. Import detection, rendering, CLI, tests, and
docs now default to `/config` (including `@hexclave/tanstack-start`) so
configs load in plain Node contexts.

- **New Features**
- Added `/config` subpaths in `@hexclave/js`, `@hexclave/next`,
`@hexclave/react`, `@hexclave/tanstack-start` (and template)
re-exporting `defineHexclaveConfig`, `defineStackConfig`, and
`HexclaveConfig` with no framework runtime.
- Renderer, CLI, and docs import `HexclaveConfig` from `<pkg>/config`;
legacy `@stackframe/*` keep root imports. Existing config files
auto-upgrade on next dashboard/CLI sync.

- **Bug Fixes**
- Wrapped `jiti` config load with try/catch; capture raw error for
diagnostics and show a concise message pointing to `<pkg>/config` (no
nested framework stack traces).
- Import detection accepts optional `/config` suffix; renderer always
appends `/config` for Hexclave packages and recognizes
`@hexclave/tanstack-start`.
- Tests stabilized by scoping temp dirs to the test file; CLI error
example now references `HexclaveConfig` from `<pkg>/config` for Hexclave
packages.

<sup>Written for commit dfe7d5fee4.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1557?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added lightweight "/config" subpath exports across SDK packages to
enable side-effect-free config authoring in plain Node contexts.

* **Documentation**
* Updated guides and snippets to recommend importing config types and
helpers from the "/config" entrypoint and added example usage.

* **Bug Fixes**
* Improved error messaging when dynamic config imports fail, with
guidance to use the "/config" entrypoint.

* **Tests**
* Adjusted tests and snapshots to expect normalized "/config" import
paths.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-10 11:40:19 -07:00
Armaan Jain
88ef2ce85f
Hosted Components Accounts Navigation bug (#1565)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/hexclave/hexclave/blob/dev/CONTRIBUTING.md

-->

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes and hardens hosted components navigation. Restores Accounts tab
behavior without full-page reloads by using a validated, async navigate
adapter and migrating to `Hexclave*` components.

- **Bug Fixes**
- Replaced `redirectMethod` with `useHostedComponentsNavigate` to route
`#...` via `hash` and others via `href`, allowing only trusted targets
via `isRelative`/`validateRedirectUrl` with `trustedDomains:
[window.location.origin]`.
- Runs redirects with `runAsynchronously` from `@hexclave/shared` to
avoid race conditions.

- **Refactors**
- Migrated from `StackClientApp`/`StackProvider`/`StackTheme` to
`HexclaveClientApp`/`HexclaveProvider`/`HexclaveTheme` in
`@hexclave/react`.
- Regenerated Mintlify docs/snippets to use Hexclave naming and clarify
the Auth SDK `urls` option and `hexclave dev` env var injection.

<sup>Written for commit 41ba5d0b15.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1565?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved client-side navigation handling: allow hash navigation and
enforce/trust-check redirect targets to prevent unsafe external
navigation.

* **Refactor**
* Switched UI runtime to Hexclave-specific app, provider, and theme
components for consistent theming and behavior.

* **Documentation**
* Clarified setup: `hexclave dev` now auto-injects required environment
variables.
* Updated guidance for configuring auth/redirect URLs to ensure correct
post-auth navigation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-10 23:44:38 +05:30
github-actions[bot]
3132de1cae chore: update package versions 2026-06-10 18:10:23 +00:00
github-actions[bot]
96273a9d65 chore: update package versions 2026-06-08 18:18:54 +00:00
github-actions[bot]
4fdd2b3831 chore: update package versions 2026-06-08 17:36:50 +00:00
github-actions[bot]
468a221379 chore: update package versions 2026-06-06 00:07:24 +00:00
github-actions[bot]
461b9acd68 chore: update package versions 2026-06-05 23:54:49 +00:00
Armaan Jain
b246e4ab65
Consistency and design changes light mode (#1500)
# PR #1500 Visual Writeup


Visual assets hosted in [this
gist](https://gist.github.com/b0c1d3d072a71e30b65380f8b2cf53a1).

- Base: `dev`
- Head: `Consistency-and-design-changes-light-mode`
- Dashboard dev server: `http://localhost:8101`
- Viewport: `1920x1200`
- Screenshots: `74` referenced (`18` surfaces x `2` themes x
`before/after`, plus the attached Conversations create-dialog pair)
- Red outlines appear only on **after** screenshots to mark the changed
surface.

## Summary

This PR refreshes light-mode consistency across dashboard surfaces and
adds a dashboard-only Account Settings implementation. The screenshots
below compare the base branch against the PR branch for every changed
dashboard route/surface included in the scope.

## Screenshot Matrix

### Account Settings - Profile

Dashboard-only profile page redesign against the previous Stack handler
page.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-profile-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-profile-before-light.png)
|
![account-profile-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-profile-after-light.png)
|
| Dark |
![account-profile-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-profile-before-dark.png)
|
![account-profile-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-profile-after-dark.png)
|

### Account Settings - Emails & Auth

Email, password, passkey, OTP, and MFA settings shell.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-emails-auth-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-emails-auth-before-light.png)
|
![account-emails-auth-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-emails-auth-after-light.png)
|
| Dark |
![account-emails-auth-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-emails-auth-before-dark.png)
|
![account-emails-auth-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-emails-auth-after-dark.png)
|

### Account Settings - Notifications

Notification preferences styling.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-notifications-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-notifications-before-light.png)
|
![account-notifications-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-notifications-after-light.png)
|
| Dark |
![account-notifications-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-notifications-before-dark.png)
|
![account-notifications-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-notifications-after-dark.png)
|

### Account Settings - Active Sessions

Active session table and action styling.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-sessions-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-sessions-before-light.png)
|
![account-sessions-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-sessions-after-light.png)
|
| Dark |
![account-sessions-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-sessions-before-dark.png)
|
![account-sessions-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-sessions-after-dark.png)
|

### Account Settings - Payments

Billing surface and account/team billing selector.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-payments-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-payments-before-light.png)
|
![account-payments-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-payments-after-light.png)
|
| Dark |
![account-payments-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-payments-before-dark.png)
|
![account-payments-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-payments-after-dark.png)
|

### Account Settings - Settings

Sign out and account deletion settings.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-settings-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-settings-before-light.png)
|
![account-settings-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-settings-after-light.png)
|
| Dark |
![account-settings-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-settings-before-dark.png)
|
![account-settings-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-settings-after-dark.png)
|

### Account Settings - Team

Team profile, members, and leave-team sections.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-team-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-before-light.png)
|
![account-team-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-after-light.png)
|
| Dark |
![account-team-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-before-dark.png)
|
![account-team-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-after-dark.png)
|

### Account Settings - Create Team

Team creation form.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![account-team-create-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-create-before-light.png)
|
![account-team-create-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-create-after-light.png)
|
| Dark |
![account-team-create-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-create-before-dark.png)
|
![account-team-create-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/account-team-create-after-dark.png)
|

### Analytics Queries

Touched analytics query page surface.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![analytics-queries-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-queries-before-light.png)
|
![analytics-queries-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-queries-after-light.png)
|
| Dark |
![analytics-queries-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-queries-before-dark.png)
|
![analytics-queries-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-queries-after-dark.png)
|

### Analytics Tables

Analytics table/query controls and data-grid surface.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![analytics-tables-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-tables-before-light.png)
|
![analytics-tables-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-tables-after-light.png)
|
| Dark |
![analytics-tables-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-tables-before-dark.png)
|
![analytics-tables-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/analytics-tables-after-dark.png)
|

### Auth Methods

Authentication method configuration surface.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![auth-methods-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/auth-methods-before-light.png)
|
![auth-methods-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/auth-methods-after-light.png)
|
| Dark |
![auth-methods-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/auth-methods-before-dark.png)
|
![auth-methods-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/auth-methods-after-dark.png)
|

### Conversations

Support conversation UI surface.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![conversations-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/conversations-before-light.png)
|
![conversations-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/conversations-after-light.png)
|
| Dark |
![conversations-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/conversations-before-dark.png)
|
![conversations-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/conversations-after-dark.png)
|

#### Conversations - Create Dialog

Attached screenshots for the create conversation dialog.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![conversations-dialog-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/conversations-dialog-before-light.png)
|
![conversations-dialog-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/conversations-dialog-after-light.png)
|

### Domains

Trusted domains alert/card styling.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![domains-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/domains-before-light.png)
|
![domains-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/domains-after-light.png)
|
| Dark |
![domains-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/domains-before-dark.png)
|
![domains-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/domains-after-dark.png)
|

### Email Sent

Sent email and reputation card styling.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![email-sent-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/email-sent-before-light.png)
|
![email-sent-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/email-sent-after-light.png)
|
| Dark |
![email-sent-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/email-sent-before-dark.png)
|
![email-sent-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/email-sent-after-dark.png)
|

### Launch Checklist

Launch checklist page surface.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![launch-checklist-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/launch-checklist-before-light.png)
|
![launch-checklist-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/launch-checklist-after-light.png)
|
| Dark |
![launch-checklist-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/launch-checklist-before-dark.png)
|
![launch-checklist-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/launch-checklist-after-dark.png)
|

### Payment Products

Products/items payment page surface.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![payments-products-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/payments-products-before-light.png)
|
![payments-products-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/payments-products-after-light.png)
|
| Dark |
![payments-products-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/payments-products-before-dark.png)
|
![payments-products-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/payments-products-after-dark.png)
|

### Session Replays

Session replay page layout.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![session-replays-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/session-replays-before-light.png)
|
![session-replays-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/session-replays-after-light.png)
|
| Dark |
![session-replays-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/session-replays-before-dark.png)
|
![session-replays-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/session-replays-after-dark.png)
|

### Sign-up Rules

Sign-up rules page surface.

| Theme | Before | After |
| --- | --- | --- |
| Light |
![sign-up-rules-before-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/sign-up-rules-before-light.png)
|
![sign-up-rules-after-light.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/sign-up-rules-after-light.png)
|
| Dark |
![sign-up-rules-before-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/sign-up-rules-before-dark.png)
|
![sign-up-rules-after-dark.png](https://gist.githubusercontent.com/Developing-Gamer/b0c1d3d072a71e30b65380f8b2cf53a1/raw/sign-up-rules-after-dark.png)
|


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added comprehensive account settings dashboard with profile
management, email and authentication settings, active sessions
monitoring, API key generation and management, payment methods,
notification preferences, and team creation/management.
* Introduced user profile image editing with circular cropping and
compression.
  * Added multi-factor authentication (MFA) setup via TOTP QR codes.
  * Enabled team-based API key management and team member invitations.

* **Design Improvements**
* Refined UI styling across analytics, forms, and dialogs for better
visual hierarchy.
  * Enhanced dark mode support throughout dashboard components.
  * Improved responsive layouts and spacing on dashboard pages.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 10:57:13 -07:00
Armaan Jain
b0cfeb5fb9
Payments checkout page redesign (#1536)
## Summary

This PR redesigns the payments checkout experience around a cleaner
split-panel purchase flow, clearer pricing selection, and more focused
terminal states for successful and invalid purchase links.

The new UI reduces visual noise, makes the selected plan and total
amount easier to scan, and keeps test-mode bypass behavior explicit
without overwhelming the checkout form.

## Visual Review

### Checkout Flow

| Before | After |
| --- | --- |
| ![Checkout before, dark
yearly](cc77e112e8/checkout-after-dark-yearly.png)
| ![Checkout after, dark
yearly](73d059fec2/checkout-before-dark-yearly.png)
|
| ![Checkout before, dark
one-time](ddb9530961/checkout-after-dark-onetime.png)
| ![Checkout after, dark
one-time](1628aee6dc/checkout-before-dark-onetime.png)
|
| ![Checkout before,
light](47ed5f6c35/checkout-after-light.png)
| ![Checkout after,
light](3b2956ef85/checkout-before-light.png)
|

### Purchase Result States

| Before | After |
| --- | --- |
| ![Success
before](a1a7a9123f/success-after-dark.png)
| ![Success
after](7cc59b1031/success-before-dark.png)
|
| ![Invalid code
before](0bb0854811/invalid-code-after-dark.png)
| ![Invalid code
after](442c33fecf/invalid-code-before-dark.png)
|

## What's Changed

- Introduces a more focused checkout layout with clearer plan selection,
pricing, quantity controls, and total amount display.
- Improves test-mode purchase handling with a dedicated bypass action
that is visually separate from the pricing controls.
- Polishes purchase result screens so success and invalid-code states
feel intentional and consistent with the checkout surface.
- Improves supporting dashboard flows for checkout URL creation and
product included-item handling.

## Notes For Reviewers

- The screenshots above are the supplied before/after captures for the
main changed surfaces.
- The checkout screenshots cover dark mode, light mode, recurring
pricing, and one-time pricing.
- The result-state screenshots cover both successful test purchases and
invalid/expired purchase codes.

## Test Plan

- Verified checkout UI in dark and light mode.
- Verified yearly and one-time price selection display.
- Verified test-mode purchase completion state.
- Verified invalid purchase-code state.
- Ran dashboard lint locally.



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Redesigned purchase flow with dedicated price options and quantity
selector
  * New payment UI cards and a Payments Not Enabled card
* Create checkout URL dialog and inline "Create Item" action for
products

* **Improvements**
* Clearer subscription interval and price labeling; improved free-price
detection
  * Return and checkout pages restyled to design system components
* Select dropdown positioning/height and trigger accessibility improved

* **Bug Fixes**
* Safer item display and validation/error messaging during purchase
flows
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 10:08:33 -07:00
Konstantin Wohlwend
1d8babb0e1 Better UX around hosted component errors 2026-06-04 17:27:04 -07:00
github-actions[bot]
5cb50721bd chore: update package versions 2026-06-05 00:16:17 +00:00
Konsti Wohlwend
bc45117777
Support local dashboard in remote SSH and GH Codespaces (#1538) 2026-06-04 16:36:17 -07:00
Konstantin Wohlwend
c3b043ef1e Log project ID with every request 2026-06-04 15:57:55 -07:00
Konstantin Wohlwend
3c19e173e0 Fix tooltip error 2026-06-04 11:01:56 -07:00
Aman Ganapathy
45f8c7f5c4
[Fix] [Docs]: Exclude Unavailable Routes from API Reference (#1550)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
### Summary of Changes
Some routes were made visible that aren't actually accessible.
We fix that


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Hide internal `/internal/*` routes from the generated API reference so
docs only show endpoints that are actually accessible. Aligns the docs
with the requirement to hide internal API routes.

- **Bug Fixes**
- Added an explicit filter in `parseOpenAPI` to exclude `/internal`
paths for all audiences.
- Regenerated `docs-mintlify/openapi/{admin,client,server}.json` to
remove internal endpoints.
  - No runtime/API changes; docs only.

<sup>Written for commit c7b356a9b1.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1550?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added OAuth authentication endpoints for provider authorization and
token exchange.
* Expanded OAuth provider management with updated schema and additional
configuration options.

* **Bug Fixes**
  * Internal endpoints no longer appear in public API documentation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: aman <aman@stack-auth.com>
2026-06-03 19:37:06 -07:00
BilalG1
c14a9dd3d0
feat(hexclave): PR 5 — internal symbol/path/package renames + brand strings (#1547)
## Stack Auth → Hexclave rename — PR 5 (internal symbols, paths,
packages, brand strings)

PR 5 finishes the **internal / non-wire** half of the Stack→Hexclave
rename. It only touches things where nothing outside the repo depends on
the exact name: internal symbols, file/dir names, the
`@stackframe/template` package, and residual brand strings. Plan +
progress are in `HEXCLAVE-RENAME-PR5-PLAN.md`.

Every step was verified green (`pnpm typecheck` + `pnpm lint`, 28/28)
and committed as its own checkpoint, then a fan-out of review agents
audited all commits and the findings were fixed.

### What changed
- **Internal symbols** (`@hexclave/shared`, `packages/template`, apps):
`stack*`/`Stack*` → `hexclave*`/`Hexclave*` — incl.
`stackGlobalsSymbol`, the `_Stack*AppImpl` classes,
`stackAppInternalsSymbol`, `StackContext`, `getStackStripe`, etc. The
`stack*App` local-variable convention
(`stackServerApp`/`stackClientApp`/…) was renamed across 175
source/example/doc files.
- **File renames**: `hexclave-handler/provider/context.tsx`,
`backend/hexclave.tsx`, `internal-tool/hexclave.ts`,
`hexclave-app-internals.ts`.
- **Directory renames**: `lib/hexclave-app`, `hexclave-companion`,
`[...hexclave]` route segment, `skills/hexclave`,
`dashboard/src/hexclave`, and the package dirs
**`packages/{next,shared,ui,sc,cli}`** (dropping the `stack-` prefix to
match the `@hexclave/*` npm names).
- **Packages**: `@stackframe/template` → `@hexclave/template`; **deleted
`packages/init-stack`** (onboarding lives in `@hexclave/cli init`; the
published npm package is untouched).
- **Brand strings**: reworded `Stack Auth`/`Stack dashboard` prose in
code + docs-mintlify, renamed `hexclave-app.mdx`/`use-hexclave-app.mdx`
with redirects, regenerated OpenAPI, updated coupled e2e assertions;
`doctor`/`init` now prefer `hexclave.config.ts`.

### Intentionally kept (verified, not oversights)
Wire/compat identifiers (`x-stack-*` headers, `stack-*` cookies,
`STACK_*` env names, `*.stack-auth.com`, `stackauth_`, `ask_stack_auth`,
query params), public `Stack*` SDK aliases, crypto/JWT/vault
domain-separation tags, `*-brand-sentinel`s, the
`Symbol.for("StackAuth--…")` string, `_stack_sync_metadata`, Postgres
`stackframe` / docker image names, the `stack-auth-logo*.svg` (used by
the rebrand modal), and `migration.mdx` / "formerly known as Stack Auth"
notes. False positives (Phosphor `StackIcon`/`StackSimple`, `TanStack`,
`OrbStack`, `stackable`/`Stacked` charts) left alone.

### Review pass
Six review agents audited all commits. Found + fixed one real bug — a
build script (`bundle-type-definitions.ts`) hardcoded the old
`lib/stack-app` glob path (not an import, so typecheck/lint were blind),
silently emptying the dashboard AI type bundle — plus stale comments, a
dead CI env var, and stale `.gitignore`/`.dockerignore` entries.
Cross-cutting audit confirmed **zero wire-compat identifiers were
accidentally renamed**.

### ⚠️ Verification note
`typecheck` + `lint` are fully green locally. The **e2e suite was not
run** (needs a live backend+DB), so the brand-string assertion +
OpenAPI-regen changes are verified by grep/codegen only — please let CI
exercise e2e to confirm.

### Base-branch note
This branch was forked from the local-only `cl/friendly-lewin-72293f`
(not on origin, no separate PR), so this PR against `dev` also carries
that branch's ~11 preceding Hexclave-rename commits (config-file rename,
env-var dual-read, AI setup-prompt rebrand). If those should land
separately, re-parent before merge.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Finishes the internal Stack Auth → Hexclave rename and cleans up
remaining stragglers, including dev-tool and prompt copy. All changes
are internal-only; public/wire APIs remain unchanged. Re-merged `dev`
and resolved the payments create-purchase-url conflict.

- **Refactors**
- Internal symbols: stack*/Stack* → hexclave*/Hexclave* (e.g.,
`getHexclaveServerApp` via `@/hexclave`, `getHexclaveStripe`,
`hexclaveAppInternalsSymbol`, `hexclaveSchemaInfo`, Prisma
`__hexclave_*`, `data-hexclave-handler-page`, Stripe mock
`hexclavePortPrefix`).
- Files/dirs: moved to `lib/hexclave-app`; handler route
`[...hexclave]`; backend entry `src/hexclave.tsx`; dashboard internals
`hexclave-app-internals`; companion `hexclave-companion`; dropped
`stack-` prefix across package dirs
(`packages/{shared,ui,sc,cli,next}`); workflows/emulator paths now
`packages/cli`; Quetzal codegen env at `packages/next/.env.local`.
- Packages/docs: `@stackframe/template` → `@hexclave/template`; removed
`packages/init-stack`; regenerated OpenAPI and updated docs
slugs/redirects for hexclave-app/use-hexclave-app.
- Brand strings/prompts: reworded remaining “Stack” dashboard strings to
Hexclave; updated dev-tool copy and prompts; `doctor/init` now prefer
`hexclave.config.ts`. Kept all wire-compat identifiers and public
aliases (`x-stack-*`, `stack-*` cookies, `STACK_*` env,
`*.stack-auth.com`, `Stack*` SDK names).
- Rebased/merged onto latest `dev`: retained `@hexclave/template`, kept
`src` in published files, refreshed setup-prompt imports and docs JSON,
adopted 1.0.5 version bumps, and re-merged `dev` again (resolved
`create-purchase-url` with `getHexclaveStripe`).

- **Bug Fixes**
- Restored dashboard AI type bundle by pointing the glob to
`packages/template/src/lib/hexclave-app`.
- Addressed rename leftovers: updated lingering `@/stack` imports and
CSS selector, fixed schema/meta and port-prefix expansions, and aligned
emulator commands to `packages/cli`.
- CI/build: removed a dead env var and stale ignore entries; fixed
Docker by renaming `STACK_SKIP_TEMPLATE_GENERATION` →
`HEXCLAVE_SKIP_TEMPLATE_GENERATION`.

<sup>Written for commit 3c1af3bff3.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1547?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-06-03 18:57:09 -07:00
devin-ai-integration[bot]
65362f3efb
fix(dashboard): prevent blank email preview when switching themes (#1514)
## Problem

When switching themes in the email drafts/template editor, the preview
occasionally shows the loading spinner and then goes completely blank.

## Cause

`EmailPreview` remounts its preview subtree (and thus the `<iframe>`)
via a `key` on `EmailPreviewErrorBoundary` whenever the inputs that
affect the rendered output change. That key was built from the
template/theme TSX source + edit mode, but **omitted `themeId`**.

Switching only the selected theme changes `themeId` without changing the
source, so the key stayed the same. React then reused the existing
`<iframe>` element and merely mutated its `srcDoc` attribute in place.
Replacing `srcDoc` on a live sandboxed iframe is intermittently not
repainted by the browser, leaving a blank preview after the loading
state.

The old key also concatenated values without a separator, which could
collide.

## Fix

Include `themeId` (and `templateId` / `editableSource`) in the remount
key and build it with a collision-safe serialization. A theme switch now
remounts the preview subtree, so a fresh iframe is mounted and loads its
`srcDoc` reliably instead of mutating it in place. This also resets
stale error-boundary state when moving from a broken theme to a working
one.

Single-line, behavior-preserving change.


Link to Devin session:
https://app.devin.ai/sessions/b269b97b40db4015a8770f77a13c10ea

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes a blank email preview when switching themes in the dashboard
editor by remounting the preview iframe. The preview now reliably
updates on theme changes and resets stale error state.

- **Bug Fixes**
- Updated `EmailPreviewErrorBoundary` key to include `themeId`,
`templateId`, `editableSource`, `editMode`, and debounced theme/template
TSX sources, serialized with JSON and joined with a null delimiter.
- Remounts the iframe on theme switch instead of mutating `srcDoc`,
preventing intermittent blank renders.

<sup>Written for commit 331a0fa071.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1514?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
Co-authored-by: Armaan Jain <84474476+Developing-Gamer@users.noreply.github.com>
2026-06-03 18:31:58 -07:00
Aman Ganapathy
7c5034fae6
[Fix]: Can now switch subscriptions/ create checkout urls even when stripeAccountId is null with test mode (#1519)
### Context
Remote/Local emulator allow one to set up payments configs without being
provisioned stripe connected accounts. Previously, even if the user had
test mode toggled, they couldn't create checkout urls or switch
subscriptions. Now, they can thanks to the refactor.

### Out of scope
The switch route still needs to be reworked for when we want to consider
stripe-test mode or test mode - stripe switches. For now we should be
good
2026-06-03 18:05:12 -07:00
github-actions[bot]
760cc4a826 chore: update package versions 2026-06-04 00:19:36 +00:00
Konsti Wohlwend
6940aa603f
Reduce @hexclave/cli bundle size by ~154 MB (#1544) 2026-06-03 17:17:19 -07:00
Konstantin Wohlwend
5835db6b54 Update missing docs redirects 2026-06-03 17:14:22 -07:00
Konstantin Wohlwend
cc636c50dc Add hexclave.config.ts and Local vs. Cloud docs pages 2026-06-03 14:04:49 -07:00
BilalG1
501ae9fe61
PR 4: Rename Stack -> Hexclave: examples config module, app-internal symbols, crypto docs (#1534)
## What

Continues the **Stack Auth → Hexclave** rename for a set of safe,
internal-only surfaces. This intentionally avoids public-contract names.

### Changes
- **Examples** — renamed the user-facing config module
`stack.ts`/`stack.tsx` (and the `convex` / `lovable` `stack/`
directories) to `hexclave`, and updated every importer across
`.ts`/`.tsx`/`.jsx`. The public `app/handler/[...stack]/` route segment
is left unchanged.
- **apps/{dashboard,backend,internal-tool}** — renamed app-local
SDK-init symbols `stackClientApp → hexclaveClientApp` and
`getStackServerApp → getHexclaveServerApp`, and the dashboard
`StackCompanion` component → `HexclaveCompanion` (incl.
`useStackCompanion`, context types). The public
`StackClientApp`/`StackServerApp` SDK classes are **unchanged**.
- **packages/stack-shared** — added comments to the crypto / JWT / vault
`stack-*` literals documenting that they must **not** be renamed (key
derivation / JWKS / KMS-alias stability). The literals are
byte-identical.

### Deliberately excluded
- **`STACK_*` → `HEXCLAVE_*` env-var rename** — `HEXCLAVE_*` already
resolves via the dual-read layers (SDK env, dashboard `_inlineEnvVars`,
`getEnvVariable`). The remaining holdout is the docker post-build
sentinel path, which the codebase authors explicitly deferred and which
is tightly coupled to `entrypoint.sh` + untestable here. A blind rename
there risks silently breaking self-host/emulator bootstrap for ~zero
functional gain.
- **All public-contract names** — SDK class names, env vars, HTTP
headers (`x-stack-*`), and the `/handler` route convention.

## Verification
- `pnpm lint` — **29/29 passing**.
- `pnpm typecheck` — **28/29 passing**; the only failure is
`@hexclave/docs` (pre-existing missing fumadocs `.source` codegen,
untouched by this PR).
- Two rounds of adversarial multi-agent review; findings fixed:
string-literal collateral from the symbol sweep (CLI test fixtures + an
AI-prompt template) reverted, and a missed `.jsx` importer in
`examples/cjs-test` corrected.

## Notes
- Based on a `dev` snapshot from when the branch was cut (a couple
commits behind tip); the diff contains only the changes above.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Complete the internal “Stack” → “Hexclave” rename across examples,
app-local code, config tooling, and setup docs, and standardize env
output to HEXCLAVE_* with correct default API URL handling. Public SDK
classes, handler routes, and legacy env names keep working.

- **Refactors**
- Examples/config: `stack.*` files and `stack/` dirs →
`hexclave.*`/`hexclave/`; imports updated; keep `app/handler/[...stack]`
route.
- Apps: backend/dashboard/internal-tool now use `getHexclaveServerApp`
and `hexclaveClientApp`; dashboard `StackCompanion` →
`HexclaveCompanion`. Public `StackClientApp`/`StackServerApp` unchanged.
- Env/setup: Next.js and CLI generators write HEXCLAVE_* and omit API
URL when using https://api.stack-auth.com; CLI `doctor` and auth
resolution prefer HEXCLAVE_* (e.g. `HEXCLAVE_SECRET_SERVER_KEY`,
`HEXCLAVE_PROJECT_ID`) with `STACK_*` fallback.
- Config tooling: `stack-config-file` → `hexclave-config-file`, emitting
`HexclaveConfig`; imports updated across backend/dashboard/tooling.
- Shared/docs: added “do not rename” notes for crypto/JWT/vault
`stack-*` literals; regenerated setup prompt/docs to use
`hexclave.config.ts`, `hexclave dev`, and `src/hexclave/`.
- Tests: updated snapshots/assertions to expect `HexclaveConfig` and
HEXCLAVE_* env names.

- **Migration**
  - No action required. SDK and CLI read both HEXCLAVE_* and STACK_*.

<sup>Written for commit 8a891b4f6c.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1534?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Renamed internal app/client/server instances and companion/provider
components to the new product name across backend, dashboard, examples,
and tooling; imports updated accordingly.
* Updated generated environment variable names and CLI init/doctor
outputs to prefer the new product prefix.

* **Documentation**
* Added clarifying notes about vault/encryption and JWT/key labels to
avoid breaking existing encrypted data.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-03 12:09:20 -07:00
github-actions[bot]
a21ba6b2f5 chore: update package versions 2026-06-03 18:09:27 +00:00
Konstantin Wohlwend
22718843f2 Fix unnecessary console.error in hosted components page 2026-06-03 11:08:33 -07:00
github-actions[bot]
fc7174d110 chore: update package versions 2026-06-03 01:10:42 +00:00
Konstantin Wohlwend
b946c6fa47 Fix nested cross domain auth 2026-06-02 17:58:12 -07:00
BilalG1
7d5adeb06e
fix(email): DNSimple zone teardown, managed-domain apply flow (#1527)
## Summary

Three related fixes, surfaced while investigating a production error on
managed email onboarding.

### 1. DNSimple managed-email zone deletion (the reported production
error)
`deleteDnsimpleZoneByName` issued `DELETE /v2/{account}/zones/{zone}`,
but **DNSimple's v2 API has no DELETE endpoint for zones**. Zones are
created here by creating a *domain* (`POST /domains`), so the symmetric
teardown is `DELETE /domains/{name}`, which also removes the hosted
zone. The old call returned a non-OK status, throwing:

> `HexclaveAssertionError: DNSimple returned non-OK status when deleting
managed email zone`

on every managed-domain deletion (the Resend domain was already deleted
by then, so the request 500s and the DNSimple zone leaks). Now deletes
the owning domain.

### 2. `applyManagedEmailProvider` left configs in an invalid state
The apply early-returned on `domain.status === "applied"` **without
rewriting config**. If the config had since drifted (e.g. the user
switched to a shared/other provider and back), re-applying never
restored `password`/`senderName`, so the *rendered* config was invalid
and **every `GET /internal/projects` 500'd** with `Result admin
validation failed in CRUD handler` (dashboard then loops/refreshes). Now
it only short-circuits when the config actually uses the domain
(`isManagedEmailDomainInUseForTenancy`); otherwise it re-provisions and
writes the full valid config.

### 3. Email-settings dashboard: managed domain now uses the staged save
flow
Clicking "Use this domain" fired an immediate API call and only
refreshed the domains *list* — never the project config the UI derives
"active" from — so nothing visibly changed and it appeared not to
persist. Applying a managed domain now matches the other providers:
selecting a domain stages a draft and shows the standard **"Unsaved
changes → Save"** card; **Save** calls `applyManagedEmailProvider`
(which owns the full config write) and then refreshes the reactive
config cache so the UI flips to **Active**.

### 4. Build unblock: `@stackframe/stack-shared` → `@hexclave/shared`
The `@hexclave/*` rename (#1482) missed `createGlobal`'s import in two
template providers, and PR 3 deleted the compat alias — so `pnpm
build:packages` couldn't resolve
`@stackframe/stack-shared/dist/utils/globals` when building the
dashboard. Fixed in the template (generated SDKs follow). *(Independent
of the email fixes, but required to build the branch.)*

## Files
- `apps/backend/src/lib/managed-email-onboarding.tsx` — DNSimple domain
delete + apply re-provision-on-drift
- `apps/dashboard/.../email-settings/domain-settings.tsx` — staged
managed-domain apply
-
`packages/template/src/providers/{stack-context,translation-provider-client}.tsx`
— globals import rename

## Testing
- `tsc --noEmit` and `eslint` clean on `@hexclave/backend` and
`@hexclave/dashboard`.
- `pnpm build:packages` + `pnpm codegen` succeed; dashboard
email-settings route compiles and serves `200`.
- Reproduced the 500 loop locally, root-caused it to a partial managed
config override, and reset the affected project's `emails.server`
override to recover.

## Notes
- Applying a managed domain still calls the API once (to mint the scoped
Resend sending key) — that call now happens on **Save** rather than on
click.
- The older `apps/dashboard/.../emails/page-client.tsx` has the same
immediate-apply pattern; left untouched pending confirmation that screen
is still in use.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes managed email onboarding: deletes DNSimple domains correctly,
restores a valid managed config on re-apply, and moves managed-domain
apply to a staged Save that waits for the domain list and shows
selection state. Also updates imports to `@hexclave/shared` to unblock
builds.

- **Bug Fixes**
- DNSimple teardown: use DELETE `/domains/{name}` (zones have no
DELETE). Stops errors and zone leaks when removing managed domains.
- `applyManagedEmailProvider`: only short-circuits when the rendered
config already uses the domain; otherwise re-provisions and writes the
full managed config to prevent invalid renders and 500s.
- Dashboard: managed domain selection now stages a draft; Save calls
`applyManagedEmailProvider`, refreshes the config cache, and the UI
updates. Shows “Selected — save to apply,” supports deselecting,
requires a selection, and disables Save until the domains list finishes
loading.

- **Dependencies**
- Renamed globals import from `@stackframe/stack-shared` to
`@hexclave/shared` to restore `pnpm build:packages`.

<sup>Written for commit 6ecb6a1ff8.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1527?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved detection and recovery for managed email configuration drift
scenarios
* Fixed teardown behavior to avoid leaving inconsistent managed domain
state

* **New Features**
* Managed domain selection now requires explicit save confirmation
before applying
* Added status labels showing current vs staged domain and pending
changes
* Added ability to deselect staged managed domains; deleting a domain
clears any draft
* Save now shows applied sender identity via toast after applying
managed domain
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-02 16:52:51 -07:00
Konsti Wohlwend
bfd15f07d1
Improve development environment loopback error message (#1526) 2026-06-02 15:40:45 -07:00
Konsti Wohlwend
9fa3a19920
Fix Docker builds for pnpm v11 (#1532) 2026-06-02 14:41:18 -07:00
Konstantin Wohlwend
1af0071233 Metrics page improvement 2026-06-01 18:55:29 -07:00
Konstantin Wohlwend
8a6e13fa38 Fix dev launchpad link 2026-06-01 18:48:15 -07:00
Konstantin Wohlwend
2e1bfecb5f Fix types 2026-06-01 16:25:49 -07:00
Konstantin Wohlwend
f9d081da09 Upgrade pnpm to v11.5.0 2026-06-01 15:33:25 -07:00
Konstantin Wohlwend
d1d96fdcc4 Update light mode logo 2026-06-01 15:05:44 -07:00
Konstantin Wohlwend
97bb65cc33 Fix config overrides for RDEs 2026-06-01 14:51:47 -07:00
Konstantin Wohlwend
64dbc70b8f Update out-of-date error message 2026-06-01 14:46:43 -07:00
github-actions[bot]
64a38d0758 chore: update package versions 2026-06-01 21:41:58 +00:00
BilalG1
011d7d751d
Fix stale e2e CLI + RDE config tests (emulator removal & hexclave rename) (#1528)
## What

Fixes the 9 failing tests in the E2E (Local Emulator) job — 2 test
files, both stale after recent refactors that renamed source but missed
the tests.

### `apps/e2e/tests/general/cli.test.ts` (6 failures)
The `stack emulator` command was removed in #1522 (which also reworded
its auth errors from "local emulator" → "development environment"), but
the e2e tests were never updated:
- Updated two error-string assertions:
- `"Local emulator publishable client key not found"` → `"Development
environment publishable client key not found"`
- `"Cannot reach local emulator"` → `"Cannot reach development
environment"`
- Removed the dead `"Stack CLI — Emulator"` describe block (4 tests for
the deleted `emulator pull/start/stop/list-releases` subcommands) and
the now-unused `CLI_SRC_BIN` const.

###
`apps/dashboard/src/lib/remote-development-environment/config-file.test.ts`
(3 failures)
Leftover `@stackframe/*` names from the `@hexclave` rename:
- Fixture imports `@stackframe/stack-shared/config` →
`@hexclave/shared/config` (the `defineStackConfig` /
`defineHexclaveConfig` cases were failing with `Cannot find module`).
- Rendered-config snapshot type import `@stackframe/js` →
`@hexclave/js`.

## Verification
- `config-file.test.ts`: **7 passed**.
- `cli.test.ts` against a live local backend: **69 passed / 4 skipped /
0 failed** (the 4 skips are environment-gated `it.runIf`/`it.skip`, not
failures).
- Reproduced both CLI error paths against the built CLI binary to
confirm the new strings match exactly.
- `lint` ✓ and `typecheck` ✓ for `@hexclave/e2e-tests` and
`@hexclave/dashboard`.

Only test files are changed — no production code.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes 9 failing E2E tests by updating CLI error messages and RDE config
test imports after the emulator command removal and the `@hexclave`
rename. Removes obsolete emulator CLI tests; no production code changed.

- **Bug Fixes**
- CLI: updated two error-string assertions to use “Development
environment …” wording.
- CLI: removed the obsolete “Stack CLI — Emulator” test block and the
unused `CLI_SRC_BIN`.
- Dashboard RDE config tests: replaced `@stackframe/stack-shared/config`
with `@hexclave/shared/config`.
  - Snapshot import: `@stackframe/js` → `@hexclave/js`.

<sup>Written for commit a887d7209b.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1528?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Updated error messages to use "development environment" terminology
instead of "emulator" references.

* **Tests**
* Adjusted test assertions and imports for consistency with updated
terminology.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-01 14:31:01 -07:00
Konstantin Wohlwend
66242c9e63 Mark @stackframe packages as deprecated 2026-06-01 14:30:19 -07:00
Konstantin Wohlwend
3961a9703e Bump package versions 2026-06-01 14:22:56 -07:00
BilalG1
609579abab
feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
2026-05-29 15:21:59 -07:00
Konstantin Wohlwend
1ba69eb1fb More tests for jiti RDE 2026-05-29 13:09:53 -07:00
Konstantin Wohlwend
70486596f0 Use jiti for RDE config parsing 2026-05-29 12:59:59 -07:00
Konsti Wohlwend
bd61184bdc
Add LLM metadata endpoints (#1499) 2026-05-28 18:07:28 -07:00
Konsti Wohlwend
7aafa8fd87
Fix Hexclave logo SVGs: convert wordmark text to outlined paths (#1517) 2026-05-28 17:53:56 -07:00
Konstantin Wohlwend
8b2bcbca69 Update repository URL back to github.com/hexclave/stack-auth
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
2026-05-28 14:27:12 -07:00
BilalG1
fa2baa829d
feat(oauth): per-provider customCallbackUrl for redirect_uri (#1512)
## Summary

Replaces the request-host-header-derived OAuth `redirect_uri` with a
config-driven `customCallbackUrl` field on each environment-level OAuth
provider.

Resolution of the `redirect_uri` we send to providers (and that
customers register in their provider app config):

- **Shared providers** → always the stack-auth-branded callback, so
Stack's shared OAuth apps keep working. `customCallbackUrl` is
schema-forbidden when `isShared` is true.
- **Custom + `customCallbackUrl` set** → the configured URL verbatim.
- **Custom without it (legacy)** → the stack-auth-branded callback, so
providers registered before this field are unaffected.
- **New custom providers set up in the dashboard** → the env-aware
hexclave-branded callback (prod → `api.hexclave.com`, dev/staging →
siblings, self-host/localhost → `NEXT_PUBLIC_STACK_API_URL` unchanged).

## Details

- **Schema** (`schema.ts`, `schema-fields.ts`): optional
`customCallbackUrl` after `clientSecret`, with a `.when('isShared')`
rule rejecting any value for shared providers; added to the provider
default factory.
- **Shared host helper** (`utils/cloud-hosts.tsx`, new):
`CLOUD_HOST_PAIRS` moved into stack-shared with `getCloudApiUrlSiblings`
/ `getStackAuthApiBaseUrl` / `getHexclaveApiBaseUrl`;
`request-api-url.ts` re-exports it so the JWT `iss` logic is untouched.
- **Runtime** (`oauth/index.tsx` + all 13 provider `create()`s):
`getProvider` resolves the full `redirect_uri` from config instead of
the request host; providers now take `redirectUri` instead of `apiUrl`.
The JWT `iss` path still uses the request host.
- **Dashboard** (`page-client.tsx`, `providers.tsx`,
`oauth-callback-url.ts` new): brand-new custom providers get the
hexclave callback; existing providers keep whatever they had (edits
never silently move a registered redirect URL); the displayed Redirect
URL mirrors backend resolution.
- **Docs** (`migration.mdx`): existing `api.stack-auth.com` callbacks
keep working; only recreated providers use the hexclave URL.

## Notes / scope decisions

- **Dashboard-only injection**: SDK/CLI/legacy-config-created custom
providers fall back to the stack-auth callback (they don't auto-get the
hexclave URL).
- **shared → standard** conversions keep the stack-auth fallback rather
than flipping to hexclave (the safe path that never breaks a registered
redirect).

## Test plan

- [x] `typecheck` + `lint` green across stack-shared, backend,
dashboard, e2e
- [x] cloud-hosts unit tests, schema tests, schema fuzzer pass
- [x] e2e: shared-provider `customCallbackUrl` rejected (400);
standard-provider `customCallbackUrl` accepted and round-trips
- [ ] e2e OAuth authorize/callback flow (needs running stack) — reasoned
unaffected since localhost isn't a cloud host, so the redirect base
stays localhost as before

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a per-provider `customCallbackUrl` for OAuth `redirect_uri`,
removing the request-host dependency and making redirects predictable.
Shared providers always use the Stack-branded callback; new or converted
custom providers default to the Hexclave-branded callback. Existing
callbacks keep working; no changes needed unless you recreate or convert
a provider.

- **New Features**
- Added `customCallbackUrl` on provider configs (URL-validated;
forbidden when `isShared` is true).
- `getProvider` now resolves a config-driven `redirectUri`; providers
take `redirectUri` instead of `apiUrl` (pure resolver with in-source +
e2e tests to lock legacy behavior).
- Introduced `@stackframe/stack-shared` `utils/cloud-hosts.tsx` and
dashboard helpers to show the resolved Redirect URL and set the Hexclave
callback for new providers and when converting shared → standard.

- **Bug Fixes**
- OAuth callback now handles legitimate cross-host flows by recording
the authorize host and skipping the host-scoped CSRF cookie when
authorize and callback hosts differ, relying on server-side state and
PKCE.

<sup>Written for commit 32d95fcdcb.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1512?utm_source=github">Review
in cubic</a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Preserve and display custom OAuth callback/redirect URLs in the
dashboard; provider creation/edit flows respect existing custom URLs.
* Added cloud-host mapping and redirect-uri helpers to resolve branded
API callback bases.

* **Bug Fixes**
* Improved cross-host OAuth callback handling and CSRF validation for
reliable cross-host flows.

* **Tests**
* Added E2E and unit tests covering callback URL behavior and host
mapping.

* **Documentation**
* Updated migration guidance for callback URL changes and recreation
scenarios.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1512?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 -->
2026-05-28 12:28:38 -07:00
aadesh18
5b9ae9c443
fix(account-settings): decode URL-encoded city in active sessions (#1503)
## Summary

The **Active Sessions** table in account settings showed locations like
`San%20Francisco` instead of `San Francisco`.

Vercel percent-encodes its geolocation headers (e.g.
`x-vercel-ip-city`), so a multi-word city arrives URL-encoded. The city
name was being stored verbatim, so the raw `%20` leaked into the UI.

The fix decodes the city name where the Vercel geo header is read, so
recorded sessions store the human-readable name. This also benefits any
other consumer of the location data. It falls back to the raw value if
it isn't valid percent-encoding, so a stray `%` can't break things.

## Test plan

- [ ] Unit tests (in-source, `apps/backend/src/lib/end-users.tsx`):
simulating Vercel headers with `x-vercel-ip-city: San%20Francisco` now
yields `cityName: "San Francisco"`; an invalid-encoding value (`100%
Real City`) passes through unchanged instead of throwing. All 8 tests in
the file pass.
- [ ] In a Vercel-deployed environment, sign in and open Account
Settings → Active Sessions; confirm the Location column shows a plain
city name (e.g. `San Francisco`) with no `%20`.

> Note: this can't be reproduced on localhost because there's no Vercel
proxy supplying geo headers (the location shows `Unknown`). The behavior
is covered by the unit tests, which feed the exact headers Vercel sends.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Corrected handling of city name data from hosting-provided location
headers so multi-word city names display correctly and invalid
percent-encoding no longer causes errors.

* **Tests**
* Added tests to verify URL-decoded city names from location headers and
to ensure malformed encodings are safely preserved.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1503?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 -->
2026-05-28 11:50:00 -07:00
Konstantin Wohlwend
fa4f25bcdd Rename port prefix envvar 2026-05-27 18:09:52 -07:00
Konstantin Wohlwend
5a8e0e5112 Small fixes 2026-05-27 18:01:20 -07:00
Konstantin Wohlwend
eeac70e48b Fix fallback URL JWT issuer 2026-05-27 17:33:31 -07:00
Konstantin Wohlwend
4f3fa24dfe Include the scope parameter in certain Microsoft OAuth operations 2026-05-27 17:31:51 -07:00
BilalG1
6b07c43fe9
feat(backend): derive JWT issuer and OAuth redirect_uri from request host (#1498)
## Summary

When the backend serves both `api.stack-auth.com` and `api.hexclave.com`
from the same deployment, signed JWT `iss` claims and OAuth
`redirect_uri` values need to match the host the customer's SDK actually
talks to — otherwise customers with hardcoded issuer checks or
registered OAuth callback URLs break when their SDK upgrades. This PR
makes both follow the request host.

Closes the "Interpretation B" plan from our earlier discussion.

## Changes

### Backend — request-host-derived `iss` and `redirect_uri`

- **New helper**
[`apps/backend/src/lib/request-api-url.ts`](apps/backend/src/lib/request-api-url.ts)
exports `getApiUrlForRequest(req)` and `getApiUrlForHost(host)`. A
consolidated `CLOUD_HOST_PAIRS` constant is the single source of truth
for the stack-auth ↔ hexclave host pairs (prod, dev, staging). Both the
allowlist here and the validator alias map in `tokens.tsx` derive from
it, so they can never drift again.
- **JWT issuer per request**
([`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)) —
`getIssuer` now takes `apiUrl`.
`generateAccessTokenFromRefreshTokenIfValid` and `createAuthTokens`
accept an `apiUrl` parameter that flows into the `iss` claim.
`getAllowedIssuers` stays env-driven with the bidirectional alias map,
so tokens cross-validate across hosts.
- **OAuth `redirect_uri` per request** — all 12 providers +
`MockProvider` now take `apiUrl` and use it to build `redirect_uri =
apiUrl + "/api/v1/auth/oauth/callback/<provider>"`. `getProvider()`
accepts an `{ apiUrl }` option and forwards it.
- **OAuth2Server factory** — the module-level `oauthServer` singleton
became a per-request `createOAuthServer({ apiUrl })` factory so
`OAuthModel.generateAccessToken` mints tokens with the right `iss`. Used
in the callback route, the token route, and the cross-domain-authorize
helper.
- **Token-minting call sites updated** — all 10 `createAuthTokens`
invocations (password sign-up/sign-in, sessions create,
sessions/current/refresh, anonymous sign-up, apple-native callback,
MFA/OTP/passkey sign-in, OAuth model token exchange), plus the
CLI-complete `generateAccessTokenFromRefreshTokenIfValid` direct call,
now pass `getApiUrlForRequest(fullReq)`.
- **`createVerificationCodeHandler` refactored** to pass `apiUrl` as a
6th positional arg to the user's handler, so MFA/OTP/passkey sign-in
flows get the same per-request `iss` as the rest. The other 8 callers
(password-reset, contact-channels-verify, etc.) don't need changes —
they accept fewer args and TS function-arity compatibility makes that
fine.

### SDK — freeze `@stackframe/*` defaults at stack-auth.com

-
[`packages/template/src/lib/stack-app/apps/implementations/common.ts`](packages/template/src/lib/stack-app/apps/implementations/common.ts)
reverts `defaultBaseUrl` and `defaultAnalyticsBaseUrl` to
`https://api.stack-auth.com` / `https://r.stack-auth.com`. A customer
who upgrades their `@stackframe/*` package to the latest version without
explicitly migrating to `@hexclave/*` keeps hitting `api.stack-auth.com`
and never sees their JWT `iss` or OAuth `redirect_uri` change.
- The `@hexclave/*` mirror packages are unaffected because they were
published from source when `defaultBaseUrl =
"https://api.hexclave.com"`; v1.0.0 already targets the hexclave host on
npm. Extending `scripts/rewrite-packages-to-hexclave.ts` to substitute
these literals during future republishes is a separate follow-up.

### Docs — migration guide rewrite

- [`docs-mintlify/migration.mdx`](docs-mintlify/migration.mdx) rewritten
concisely. Spells out the two host-visible changes that require
pre-deploy action when migrating to `@hexclave/*`: updating manual JWT
verifier code (with the array-of-issuers pattern) and updating OAuth
callback URLs at each provider (with the GitHub-OAuth-Apps single-URL
caveat explicitly called out).

## What was deliberately left out

- **Rewriter pipeline extension** for `@hexclave/*` republishes —
separate follow-up.
- **Cross-SDK defaults** (Swift, stack-cli, init-stack still default to
`api.hexclave.com`) — out of scope per discussion.
- **Dashboard launch-checklist host-awareness** — out of scope per
discussion (callback URLs stay hexclave-branded in the dashboard UI).

## Verification

- `pnpm typecheck` — 29/29 packages pass.
- `pnpm lint` — 29/29 packages pass.
- Five parallel review agents (JWT, OAuth, helper, migration guide, SDK
defaults) + an external review of the commit caught four real issues —
all resolved in the same commit before push:
- Missing `api.staging.*` entries in `issuerHostAliases` (would have
broken cross-host token validation on staging).
  - Stale comment in `apps/backend/src/stack.tsx`.
  - Misleading "backward-compat" comment in `getHardcodedFallbackUrls`.
- MFA/OTP/passkey using env-var fallback for `iss` instead of the
request host.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Makes JWT issuers and OAuth redirect_uri values follow the incoming
request’s host so tokens and redirects always match `api.stack-auth.com`
or `api.hexclave.com`. Also freezes `@stackframe/*` SDK defaults to
Stack Auth and tightens the migration guide to focus on OAuth callbacks.

- **New Features**
- Added `request-api-url` helper with an allowlist of cloud hosts;
unknown hosts fall back to the deployment’s API URL.
- JWT `iss` now uses the per-request API URL; validation accepts both
brands via aliases derived from one `CLOUD_HOST_PAIRS` source.
- All OAuth providers build `redirect_uri` from the request host;
`getProvider()` now takes `{ apiUrl }`.
- Replaced the OAuth2Server singleton with per-request
`createOAuthServer({ apiUrl })` so token exchange mints with the right
issuer.
- Updated token-minting and OAuth routes to pass the API URL;
verification-code flows receive it; connected-accounts refresh paths
safely pin the deployment default.
- SDK: `@stackframe/*` defaults point to `https://api.stack-auth.com`;
`@hexclave/*` mirrors remain hexclave-branded.
- Shared: updated fallback API host lists to include both stack-auth and
hexclave hosts.

- **Migration**
- Update each provider’s OAuth callback URL to
`https://api.hexclave.com/api/v1/auth/oauth/callback/<provider>` when
migrating to `@hexclave/*`.
- If you verify JWTs manually, update the expected issuer to the
hexclave host (including anonymous/restricted variants).

<sup>Written for commit e42dfaf05b.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1498?utm_source=github">Review
in cubic</a></sup>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Per-request API host awareness so tokens and OAuth flows reflect the
incoming request’s canonical API URL.

* **Updates**
* OAuth providers and servers now derive callback/redirect URLs from
request context rather than a static URL.
* Token issuance/validation now embeds and accepts host-specific issuer
URLs and paired host aliases for rebrand compatibility.

* **Documentation**
* Migration guide updated for branding, JWT issuer expectations, and
OAuth callback guidance.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1498?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 -->
2026-05-27 17:06:15 -07:00
BilalG1
c0fefd3b7a
feat(backend): dual-accept hexclave-mobile-oauth-url:// alongside legacy scheme (#1501)
## What

1. **Backend dual-accept**: `isAcceptedNativeAppUrl()` accepts both
`stack-auth-mobile-oauth-url://` (legacy) and
`hexclave-mobile-oauth-url://` (canonical).
2. **Swift SDK switches to the canonical scheme**: `StackAuth` Swift SDK
now emits and intercepts `hexclave-mobile-oauth-url://` for native-app
OAuth callbacks.

Before this PR, `hexclave-mobile-oauth-url` existed only inside
`RENAME-TO-HEXCLAVE.md` — not in any code.

## Why the Swift SDK change is safe

The Swift SDK uses
`ASWebAuthenticationSession(url:callbackURLScheme:completion:)`
([StackClientApp.swift:197-199](sdks/implementations/swift/Sources/StackAuth/StackClientApp.swift#L197)).
With this API, iOS intercepts the callback scheme **ephemerally** — no
`Info.plist` registration is required. The Swift SDK source has no
`Info.plist`, and the example apps' `pbxproj` registers no
`CFBundleURLSchemes`. So:

- New customer builds against the updated SDK → emit new scheme →
backend accepts → `ASWebAuthenticationSession` intercepts on new scheme
→ works.
- Already-shipped customer App Store binaries on older SDK versions →
emit old scheme → backend still accepts → works.
- **No customer ever has to update an `Info.plist`.**

The only real backward-compat constraint is that the backend can never
drop the old scheme (already-shipped customer binaries have the constant
baked into them). Hence the dual-accept.

(Note: `RENAME-TO-HEXCLAVE.md` line 88 incorrectly attributes the
constraint to `Info.plist` registration. That's not how the SDK works —
the scheme is baked into the SDK binary, not the customer's plist. The
fix described in that doc is essentially the right shape; only the
mechanism description is wrong.)

## Changes

| File | Change |
|---|---|
| `packages/stack-shared/src/utils/redirect-urls.tsx` |
`isAcceptedNativeAppUrl()` accepts either protocol. |
| `apps/backend/src/lib/redirect-urls.test.tsx` | Adds positive
assertions for the new scheme in `isAcceptedNativeAppUrl`; parity
negative assertions in `validateRedirectUrl`. |
| `sdks/implementations/swift/Sources/StackAuth/StackClientApp.swift` |
`callbackScheme` → `"hexclave-mobile-oauth-url"`; fatalError example
strings updated. |
| `sdks/implementations/swift/Tests/StackAuthTests/OAuthTests.swift` |
Test fixture URLs updated (no assertions depend on the scheme literal).
|
|
`sdks/implementations/swift/Examples/StackAuthiOS/.../StackAuthiOSApp.swift`
| Default values in the example UI. |
|
`sdks/implementations/swift/Examples/StackAuthMacOS/.../StackAuthMacOSApp.swift`
| Default values in the example UI. |
| `sdks/implementations/swift/README.md` | Documents the new canonical
scheme; compat note for the legacy one. |
| `sdks/spec/src/apps/client-app.spec.md` | New scheme is canonical;
legacy is "accepted indefinitely for already-shipped customer app
binaries built against older SDK versions." |

## Verification

- `pnpm test run apps/backend/src/lib/redirect-urls.test.tsx` — 34/34
passing (was 33; one new `it` block plus parity assertions).
- `pnpm --filter @stackframe/stack-shared --filter @stackframe/backend
run lint` — clean.
- `pnpm --filter @stackframe/stack-shared --filter @stackframe/backend
run typecheck` — clean.
- Swift assertions in `OAuthTests.swift` do not check the scheme literal
— they only check `oauth/authorize/<provider>`, state/verifier
non-emptiness, and that `redirectUrl` round-trips. The fixture-value
change is mechanical.

## Risk

Low. Backend behavior strictly widens (every URL accepted before is
still accepted). Swift SDK change is internal to OAuth callback
handling, requires no customer migration, and is paired with the backend
dual-accept landing in the same PR.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Adopted the canonical OAuth callback scheme
"hexclave-mobile-oauth-url://" for native apps while continuing to
accept the legacy "stack-auth-mobile-oauth-url://".

* **Documentation**
* Updated SDK docs, examples, and spec guidance to reference the
canonical callback scheme and clarify legacy acceptance.

* **Tests & Samples**
* Updated tests and example apps to use and validate the canonical
scheme.

* **Style**
* Rebranded the dev-tool trigger icon to the new Hexclave monochrome
logo.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1501?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 -->
2026-05-27 15:44:06 -07:00
tembo[bot]
244e7e79f2
Add captureWarning for warnings (#1506) 2026-05-27 15:35:10 -07:00
BilalG1
c753ab2831
fix(dashboard): show Stack Auth logo on left of rebrand modal (#1495)
## Summary

The Stack Auth → Hexclave rebrand modal added in
[#1493](https://github.com/hexclave/stack-auth/pull/1493) rendered the
**Hexclave** logo on both sides of its illustration instead of *Stack
Auth → Hexclave*.

## Root cause


[\`hexclave-rebrand-modal.tsx\`](https://github.com/hexclave/stack-auth/blob/dev/apps/dashboard/src/components/hexclave-rebrand-modal.tsx)
referenced \`/logo.svg\` and \`/logo-bright.svg\` for the left-hand
"Stack Auth" mark. But the visible-rebrand flip in
[#1481](https://github.com/hexclave/stack-auth/pull/1481) replaced those
files in \`apps/dashboard/public/\` with the Hexclave benzene mark — so
post-merge, both \`<Image>\` slots resolved to the Hexclave logo.

## Fix

- Restored the **pre-rebrand Stack Auth SVGs** (lifted from
\`57ff5d3ce~1\`, the commit before the brand-flip) under dedicated
filenames so they can't get clobbered the next time \`/logo.svg\` is
updated:
- \`apps/dashboard/public/stack-auth-logo.svg\` — black mark, light
theme
- \`apps/dashboard/public/stack-auth-logo-bright.svg\` — white mark,
dark theme
- Pointed the modal's left-hand \`<Image>\` pair at the new paths and
left a comment explaining why we don't share \`/logo.svg\` (so the next
person doesn't try to consolidate it back).

The originals on \`origin/dev\` (\`/logo.svg\`, \`/logo-bright.svg\` =
Hexclave) are untouched.

## Verification

- \`pnpm --filter @stackframe/dashboard lint\` — clean.
- Diff of
[hexclave-rebrand-modal.tsx](https://github.com/hexclave/stack-auth/blob/fix-stack-logo-in-modal/apps/dashboard/src/components/hexclave-rebrand-modal.tsx)
is two \`src=\` swaps plus a comment; the dismissal logic, gates, and
storage key are untouched.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes the rebrand modal to show the Stack Auth logo on the left instead
of rendering Hexclave on both sides. Restores pre-rebrand assets as
`stack-auth-logo.svg` and `stack-auth-logo-bright.svg` and updates the
modal to use them for light/dark themes.

<sup>Written for commit f4ed26266c.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1495?utm_source=github">Review
in cubic</a></sup>

<!-- End of auto-generated description by cubic. -->



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Style**
* Updated the rebrand modal illustration assets to display Stack Auth
branding correctly.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1495?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 -->
2026-05-27 10:09:16 -07:00
BilalG1
4f621f5cbe
Rebrand skills route page to Hexclave (#1494)
Updates [apps/skills/src/app/route.ts](apps/skills/src/app/route.ts) —
the HTML rendered at the skills site root — so it matches the Hexclave
branding used everywhere else in the app.

## Changes

- **Install command**: `npx @stackframe/stack-cli@latest init` → `npx
@hexclave/cli@latest init` (the package is published as `@hexclave/cli`
per `skill-site-prompt-parts/ai-setup-prompt.ts`, not
`@hexclave/stack-cli`)
- **Page chrome**: `<title>`, meta description, header brand, h1, and
lede all switch from *Stack Auth* → *Hexclave*
- **Inline CLI references**: `stack-cli` → `hexclave-cli`
- **Docs links**: `docs.stack-auth.com` → `docs.hexclave.com` (header
link + AI Integration + CLI Reference cards)
- **MCP card**: `mcp.stack-auth.com` → `mcp.hexclave.com`
- **Footer**: *© Stack Auth* → *© Hexclave*; GitHub link
`hexclave/stack-auth` → `hexclave/hexclave` (matches
`apps/skills/package.json`'s `repository` field)

The markdown body served to non-browser clients comes from
`skillSitePrompt` and was already on-brand — no change there.

## Verification

`pnpm --filter @stackframe/skills run typecheck` passes.
2026-05-27 09:59:18 -07:00
BilalG1
57ff5d3ce9
feat(hexclave): PR 2 — visible rebrand (Hexclave brand goes public) (#1481)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary

**Stacked on [#1475](https://github.com/hexclave/stack-auth/pull/1475)**
(`cl/hexclave-pr1`, the invisible compatibility layer). Diff vs that
base = the actual PR 2 code.

This is **PR 2 of the Stack Auth → Hexclave rebrand: the visible flip**.
Old wire identifiers (cookies, request/response headers, Bearer prefix,
JWT issuers, MCP tool name) keep working indefinitely via PR 1's
dual-accept. This PR flips every user-visible surface — package names
taught in docs, SDK class names in code examples, dashboard setup
snippets, page titles, error messages, email content, CLI binary,
default base URLs, GitHub repo slug, contributor guidance — to the
Hexclave brand.

See [`RENAME-TO-HEXCLAVE.md`](./RENAME-TO-HEXCLAVE.md) → *"PR 2: Rebrand
to Hexclave (visible)"* for the full per-work-area spec.

## What's implemented (per the plan's PR 2 scope)

- **SDK base URLs** flipped: `defaultBaseUrl` and
`defaultAnalyticsBaseUrl` in
[common.ts](packages/template/src/lib/stack-app/apps/implementations/common.ts:127)
→ `https://api.hexclave.com` / `https://r.hexclave.com`. PR 1's
[`getHardcodedFallbackUrls`](packages/stack-shared/src/utils/urls.tsx:199)
table now keys on the Hexclave domain.

- **Domain inventory sweep** (16 subdomains from the plan): every
`api/app/docs/discord/demo/mcp/skill/feedback/test/preview/r/api2/api.staging/idp-jwk-audience/built-with.stack-auth.com`
reference in production code, docs-mintlify, examples, READMEs, and
contributor guidance flipped to `*.hexclave.com`. Carve-outs: PR 1's
intentional JWT issuer dual-accept table in
[tokens.tsx](apps/backend/src/lib/tokens.tsx), the legacy `./docs/`
folder, the `unified-docs-widget` allowlist (deliberately accepts both
during DNS transition), and `url-targets.ts` hosted-component default
(baked into existing customer deploys).

- **`@deprecated` JSDoc** on every `Stack*` public export
([packages/template/src/lib/stack-app/index.ts](packages/template/src/lib/stack-app/index.ts)
+ [packages/template/src/index.ts](packages/template/src/index.ts)) —
`StackClientApp`, `StackServerApp`, `StackAdminApp` + every
constructor/options/JSON type, `StackHandler`, `StackProvider`,
`StackTheme`, `useStackApp`, `defineStackConfig`, `StackConfig`.
Hexclave\* aliases are now canonical.

- **Runtime `console.warn`**
([packages/template/src/internal/deprecation-warning.ts](packages/template/src/internal/deprecation-warning.ts))
— once-per-process when the SDK is loaded from a `@stackframe/*`
artifact. Detection uses the existing
`STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL` (rewritten at build
time to e.g. `js @stackframe/stack@2.8.92` or `js
@hexclave/next@1.0.0`); `@hexclave/*` mirror artifacts short-circuit the
warning.

- **Tier 3 data migration**: new idempotent SQL migration
[`20260523000000_rename_internal_project_to_hexclave`](apps/backend/prisma/migrations/20260523000000_rename_internal_project_to_hexclave/migration.sql)
— updates the internal Project `displayName` 'Stack Dashboard' →
'Hexclave Dashboard' and `description` only if both still hold the
pre-rebrand defaults. Operator-renamed projects untouched, missing row
no-ops, re-runs are no-ops. [`seed.ts`](apps/backend/prisma/seed.ts:87)
default flipped. `getSharedEmailConfig("Stack Auth")` → `("Hexclave")`.

- **Tier 4 brand strings** (mechanical sweep, ~340 files):
- Page + OpenAPI titles (Hexclave API / Dashboard / REST API / Webhooks
API / Documentation). OpenAPI `info.description` documents
`X-Hexclave-*` headers as canonical with compat note on `X-Stack-*`.
- `HexclaveAssertionError` message text
([errors.tsx:71](packages/stack-shared/src/utils/errors.tsx:71)) — "an
error in Stack." → "an error in Hexclave."
- Known-error message templates
([known-errors.tsx](packages/stack-shared/src/known-errors.tsx)) flipped
to lead with `x-hexclave-*` + the new `docs.hexclave.com` URL; legacy
`x-stack-*` mentioned as compat aliases. **25 e2e test files updated in
lockstep**.
- Email content: failed-emails-digest body, sendTestEmail recipient (now
`sent-with-hexclave.com`), test-email-recipient default.
  - `CHANGELOG.md` title → "Hexclave Changelog".
- `AGENTS.md` env var convention: new vars prefix `HEXCLAVE_` /
`NEXT_PUBLIC_HEXCLAVE_` for Category A/B; legacy `STACK_*` explicitly
noted as accepted via PR 1's dual-read.

- **CLI / init wizard**:
- Every dashboard setup snippet, init-stack template, and docs-mintlify
page teaches `npx @hexclave/cli@latest init` (was
`@stackframe/stack-cli`).
[setup-page.tsx](apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx)
+
[link-existing-onboarding](apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsx).
- [init-stack](packages/init-stack/src/index.ts:634)
`STACK_*_INSTALL_PACKAGE_NAME_OVERRIDE` defaults flipped to
`@hexclave/*`.
- Generated `stack/client.ts` / `stack/server.ts` import from
`@hexclave/next` and reference `HexclaveClientApp` /
`HexclaveServerApp`.
- Internal `StackAuthKeys` dashboard component renamed to
`HexclaveKeys`.

- **docs-mintlify rewrite** (legacy `./docs/` intentionally untouched
per scoping decision):
- **78 MDX files swept**.
`@stackframe/{react,stack,js,tanstack-start,...}` →
`@hexclave/{react,stack,js,...}` in install snippets and code blocks;
`Stack*` SDK class names → `Hexclave*` in all code examples; 'Stack
Auth' brand phrase → 'Hexclave'.
- `openapi/{server,admin,client,webhooks}.json` titles → 'Hexclave REST
API' / 'Hexclave Webhooks API'.

- **Generators flipped before regeneration**:
-
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts),
[`/ai/prompts.ts`](packages/stack-shared/src/ai/prompts.ts),
[`apps/backend/src/lib/ai/prompts.ts`](apps/backend/src/lib/ai/prompts.ts),
[`apps/backend/src/lib/ai/tools/create-email-{template,draft}.ts`](apps/backend/src/lib/ai/tools/create-email-template.ts),
[`apps/skills/src/app/route.ts`](apps/skills/src/app/route.ts) (taught
MCP tool → `ask_hexclave` with compat note; CLI binary teach →
`hexclave`),
[`docs-mintlify/snippets/home-prompt-island.jsx`](docs-mintlify/snippets/home-prompt-island.jsx),
[`packages/template/README.md`](packages/template/README.md) +
integrations/convex/component/README.md.
  - `generate-sdks` propagated changes to `packages/{react,stack,js}`.

- **OpenAPI dual-documentation**:
[`apps/backend/src/app/api/latest/route.ts`](apps/backend/src/app/api/latest/route.ts)
now lists `X-Hexclave-*` headers as primary documented schemas with
`X-Stack-*` duplicates marked `.optional()` (both accepted at runtime by
PR 1's normalize-at-proxy shim).

- **`@stackframe/emails` virtual module**: dual-aliased to
`@hexclave/emails` at the bundler boundary
([email-rendering.tsx:89](apps/backend/src/lib/email-rendering.tsx:89)).
Stored email templates continue to import from either name; new
AI-generated templates and the system prompt teach `@hexclave/emails`.

- **Tier 2 mirror-publish wiring** (new this PR, lays the groundwork for
`@hexclave/*` first publish):
-
[`scripts/rewrite-packages-to-hexclave.ts`](scripts/rewrite-packages-to-hexclave.ts)
— rewrites 9 publishable `@stackframe/*` → `@hexclave/*` `package.json`
files (reads `HEXCLAVE_VERSION` env or `--version=` flag), pins
cross-deps to the shared `@hexclave` version, registers `hexclave` bin
alongside `stack` for `@hexclave/cli`.
-
[`.github/workflows/npm-publish.yaml`](.github/workflows/npm-publish.yaml)
appended with rewrite-then-republish step. `pnpm publish` skips
already-on-npm versions so reruns are safe.

- **Sender email domain**: `noreply@stackframe.co` →
`noreply@sent-with-hexclave.com` (the dedicated transactional-sender
domain split per the plan, to isolate bulk deliverability from
`hexclave.com` reputation); `security@` / `team@stack-auth.com` inbound
mailboxes → `@hexclave.com`.

- **Self-host docs**: docker network / container names in the bash
examples flipped from `stack-auth` to `hexclave` (`hexclave-postgres`,
`hexclave-clickhouse`, `hexclave.env`). The docker image tag
`stackauth/server:latest` stays per the plan's locked decision.

- **GitHub repo slug**: `hexclave/stack-auth` → `hexclave/hexclave` in
every `package.json` `repository` field, README link, CHANGELOG
raw-asset URL.

## Carve-outs (deliberately untouched)

-
**[`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)**
JWT issuer dual-accept table — PR 1 intentional infrastructure, kept
indefinitely.
- **Legacy `./docs/` folder** — per scoping decision (only
`docs-mintlify/` rewritten).
- **`unified-docs-widget` hostname allowlist** — accepts both
`.hexclave.com` (canonical) and `.stack-auth.com` (transition window)
for DNS rollout.
- **`url-targets.ts`** hosted-domain default
`.built-with-stack-auth.com` — wire identifier baked into existing
customer deploys; indefinite read-fallback.
- **Binary visual assets** (logos, favicons, OG images, README
screenshots) — out of scope for this PR. Need design work; tracked
separately.

## Verification

- **`pnpm typecheck`** on
`packages/{template,stack-shared,react,stack,js}` + `apps/dashboard`:
**all green**. The remaining backend / e-commerce-demo typecheck errors
are pre-existing (Prisma codegen output +
`./generated/api-versions.json` not present in fresh worktrees without
`pnpm run codegen-prisma` + a live DB) and unrelated to this diff.
- **`pnpm lint`** on the same 6 packages: all green.
- **Final grep** for residual `Stack Auth` / `stack-auth.com` /
`@stackframe/stack-cli@latest` references: zero outside the intentional
carve-outs above.
- **25 e2e test files updated in lockstep** with the known-error message
changes (asserted strings flipped to match the new x-hexclave-* +
compat-note messages).

## Deploy blockers (ops sequencing before this rebrand goes live)

This PR is code-complete, but the rebrand's visible surfaces (SDK
default URLs, dashboard links, npm READMEs, REST error messages, runtime
deprecation warning) all point at `*.hexclave.com` / `@hexclave/*`
resources that don't exist yet. None of these are fixable from a PR —
they're ops/registrar/npm work that has to be sequenced before merging
this to a release tag.

Suggested ordering, hardest blockers first:

### Tier 1 — required before customer-facing deploy (everything below
this line *will visibly break customers on day 1* if skipped)

1. **DNS + TLS for `api.hexclave.com` + `api1./api2.hexclave.com`** →
must point at the same backend that serves `api.stack-auth.com` (or a
backend that mirrors PR 1's dual-accept). The SDK's new `defaultBaseUrl`
is `https://api.hexclave.com`; every customer that relied on the old
default and upgrades to a post-PR2 SDK build sends API requests here.
Until this resolves, every default-config customer's API call NXDOMAINs.
2. **DNS for `app.hexclave.com`** → the dashboard. Referenced in the
SDK's default-error messages ("Please create a project on the Hexclave
dashboard at https://app.hexclave.com"), the init-stack flow's
`wizard-congrats` redirect, and the OAuth dashboard handoff.
3. **DNS for `docs.hexclave.com`** + Mintlify deploy → the SDK runtime
deprecation warning (`https://docs.hexclave.com/migration`), every
README, every "Learn more" link in the dashboard, and every REST API
error body (`/api/overview#authentication`) points here. The MDX is in
this PR; the docs build target needs DNS.
4. **DNS for `mcp.hexclave.com`** → the MCP server endpoint that every
taught agent integration (`claude mcp add ...`, `cursor`, `codex`,
`vscode`) registers. Until this resolves, every `npx
@hexclave/cli@latest init` MCP-registration step fails.
5. **Reserve the `@hexclave` npm scope + set repo variable
`HEXCLAVE_VERSION`** → the mirror-publish step in
`.github/workflows/npm-publish.yaml` is gated on this variable. Without
it, the entire taught onboarding command `npx @hexclave/cli@latest init`
404s from the npm registry, *and* every README that says "install
`@hexclave/next`" leads to install failure. Pick the initial version
intentionally (`1.0.0` or aligned to `@stackframe/stack`); don't accept
a silent default.

### Tier 2 — required before announcing the rebrand publicly (lookalike
or low-traffic surfaces, but visibly broken)

6. **DNS for `r.hexclave.com`** → the analytics beacon
`defaultAnalyticsBaseUrl`. Silent failure if missing (analytics drops),
but should land alongside Tier 1.
7. **Register `sent-with-hexclave.com` + full email auth (SPF / DKIM /
DMARC)** → the new default sender domain for shared-sender transactional
emails. Without it the dashboard "send test email" path emits bounces,
and shared-sender flows (`getSharedEmailConfig("Hexclave")`) deliver to
spam at best.
8. **MX + SPF / DMARC for `hexclave.com`** → `team@hexclave.com` and
`security@hexclave.com` mailboxes. The security disclosure mailbox is
referenced in [`.github/SECURITY.md`](.github/SECURITY.md);
`team@hexclave.com` is the actual recipient of internal feedback emails
sent at runtime by
[`apps/backend/src/lib/internal-feedback-emails.tsx`](apps/backend/src/lib/internal-feedback-emails.tsx).
Today, every runtime feedback email bounces.
9. **DNS for `skill.hexclave.com`** → the canonical AI-agent skill fetch
URL (the agent bootstrap pivot). Without it, the entire "agent downloads
`SKILL.md` from a known URL" flow taught in
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts)
fails.
10. **Create `github.com/hexclave/hexclave` as a public repo** (even as
a redirect to `hexclave/stack-auth`) **OR** rewrite every `package.json`
`"repository"` field + dashboard footer "view on GitHub" link to point
at `hexclave/stack-auth` (which already exists). Currently every npm
package page's "Repository" link is dead, and the dashboard's GitHub
button + dev-tool repo link are dead.

### Tier 3 — broken but low-visibility / low-traffic

11. **DNS for `discord.hexclave.com`** → Discord invite redirect, used
in every README's chip and the dashboard footer.
12. **DNS for `demo.hexclave.com`** → " Demo" badge in every npm
package README. Broken-image badge on the package page.
13. **DNS + TLS for `built-with-hexclave.com`** → optional
hosted-handler domain (the default reverted to
`.built-with-stack-auth.com` in this PR's carve-outs, so this only
matters for projects that manually flip).

## Other follow-ups (not deploy-blocking)

- **E2E snapshot regen across the full suite** for the dual-emitted
`x-hexclave-*` response headers (PR 1 follow-up; `vitest -u` in CI
absorbs).
- **Binary visual assets** — logos, favicons, OG images, README
screenshots; need design pass.
- **Backend OpenAPI fumadocs regen** in CI flow — the JSON files in
`docs-mintlify/openapi/` are committed but regen runs in CI. Verify the
workflow that does this still works against the post-PR2 source.
- **Backend typecheck infra debt** — needs `codegen-prisma` +
`codegen-route-info` to clear; pre-existing, unaffected by this PR.

## Test plan

- [ ] CI runs full e2e suite (with `vitest -u` to absorb residual
snapshot deltas, then committed back).
- [ ] Spot-check: new `@hexclave/cli init` (once published) generates
`hexclave.config.ts` and works against a fresh project.
- [ ] Spot-check: existing customer with `@stackframe/stack` import sees
the once-per-process `console.warn` recommending `@hexclave/next` on SDK
init.
- [ ] Manual: dashboard setup page renders the `npx @hexclave/cli@latest
init` snippet and the `x-hexclave-publishable-client-key` API header in
the curl example.
- [ ] Manual: a fresh `pnpm run prisma migrate` against a clean DB sets
the internal project displayName to 'Hexclave Dashboard'.

---------

Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
2026-05-26 19:18:20 -07:00
Armaan Jain
a27e4d9bb5
Small onboarding fixes (#1489)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/hexclave/stack-auth/blob/dev/CONTRIBUTING.md

-->

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Improves onboarding and analytics visuals. Adds clear Stripe setup
actions (Connect or Do Later) with safe redirects and precise
loading/disabled states, and fixes a stuck dashboard reload after
linking an existing project.

- **New Features**
- Payments onboarding: separate Connect/Do Later actions with per-button
loading and mutual disabling.
- US-only: “Do Later” creates a deferred Stripe account before finishing
onboarding.
- Secure redirect: enforce HTTPS on Connect and navigate via
window.location.href.
- Refresh Stripe account cache after `setupPayments()` to avoid stale
data on return.
- Analytics: smoother pie hover transitions, fading center label,
optional `showDateRange`; dashboard donut hides date range and adjusts
radii; revenue hover chart uses split bars for rounded tops and an avg
line.
- Tests cover deferred/unsupported payments setup and button loading
isolation.

- **Bug Fixes**
- After linking an existing config, use a full page navigation to the
project to prevent the dashboard from getting stuck on initial load.

<sup>Written for commit c80034ad1f.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1489?utm_source=github">Review
in cubic</a></sup>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Option to defer payments setup during project onboarding;
connect/defer actions show targeted loading states.

* **Improvements**
* Linking existing projects now performs full navigation when
applicable.
* Charts: refined donut/pie sizing, smoother center fade animation,
optional date-range display, and improved color/stack rendering.
  * Payments setup now refreshes account info after setup.

* **Tests**
* Added tests covering payments deferral, connect flows, and UI/loading
behavior.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1489?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 -->
2026-05-26 17:45:50 -07:00
github-actions[bot]
6a01c1bcc7 chore: update package versions 2026-05-26 22:26:55 +00:00
github-actions[bot]
18df895842 chore: update package versions 2026-05-26 21:59:38 +00:00
github-actions[bot]
5f3dc6d9ee chore: update package versions 2026-05-26 20:37:35 +00:00
Konsti Wohlwend
a06c9331f9
Fix skills app CI typecheck (#1490) 2026-05-26 13:32:09 -07:00
Konstantin Wohlwend
c13853702c Remove GitHub OAuth provider error noise 2026-05-26 13:31:16 -07:00
github-actions[bot]
ad197de7fe chore: update package versions 2026-05-26 20:28:07 +00:00
Konstantin Wohlwend
7a2561dd7e Fix OAuth sign-in 2026-05-26 13:14:15 -07:00
BilalG1
a4ae7edecd
fix(ci): repair two pre-existing test failures on dev (#1488)
Both failures are pre-existing on `dev` (confirmed by checking the most
recent dev run
[26434368271](https://github.com/hexclave/stack-auth/actions/runs/26434368271)
— same two annotations, same line numbers). Neither is caused by an open
PR.

## Failure 1 — \`apps/backend/src/lib/redirect-urls.test.tsx:75\`

\`\`\`
AssertionError: expected false to be true
\`\`\`

The \`withHostedHandlerEnv\` helper set/cleared only the
\`STACK_*\`-prefixed env vars. CI's
[e2e-custom-base-port-api-tests.yaml:21](.github/workflows/e2e-custom-base-port-api-tests.yaml#L21)
sets only the \`HEXCLAVE_*\`-prefixed sibling
(\`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67\`), and the dual-read shim in
[packages/stack-shared/src/utils/env.tsx#L53-L55](packages/stack-shared/src/utils/env.tsx#L53-L55)
prefers \`HEXCLAVE_*\` over \`STACK_*\`:

\`\`\`ts
const hexclaveName = getHexclaveEnvVarName(name);
let value = (hexclaveName ? process.env[hexclaveName] : undefined) ??
process.env[name];
\`\`\`

So \`getEnvVariable(\"NEXT_PUBLIC_STACK_PORT_PREFIX\", \"81\")\`
returned \`\"67\"\` instead of the test's \`\"92\"\`, the template
resolved to port \`6709\` instead of \`9209\`, and the assertion at line
75 failed.

**Fix:** mirror every \`STACK_*\` key managed by the helper to its
\`HEXCLAVE_*\` sibling. The dual-read then resolves to the
test-controlled value regardless of which key it checks first.

## Failure 2 —
\`apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts:58\`

\`\`\`
PostgresError: null value in column \"updatedAt\" of relation
\"OAuthAccessToken\" violates not-null constraint
\`\`\`

The migration test's raw INSERT omits \`\"updatedAt\"\`. The Prisma
model declares \`updatedAt DateTime @updatedAt\` with no
\`@default(now())\`, so the DB column is \`NOT NULL\` with no default —
Prisma populates it at the ORM layer on insert, but this test bypasses
Prisma via \`postgres.js\`.

**Fix:** add the \`\"updatedAt\"\` column to the INSERT, set to
\`NOW()\`, with a comment noting why raw SQL must set it explicitly.

## Verification

- **Failure 1, before fix:** ran \`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67
pnpm test run apps/backend/src/lib/redirect-urls.test.tsx\` locally →
reproduces the exact line-75 assertion failure from CI.
- **Failure 1, after fix:** same command → 33/33 pass.
- **Failure 2:** local reproduction requires the migration-test postgres
harness; the fix is one column matching how every other raw SQL insert
in this repo handles \`@updatedAt\` fields. CI on this branch will
confirm.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes two failing tests on dev CI by aligning env var handling in
redirect URL tests and by setting the missing updatedAt in a migration
test. Restores green CI with no runtime changes.

- **Bug Fixes**
- Redirect URL tests: `withHostedHandlerEnv` now mirrors `STACK_*`
values to their `HEXCLAVE_*` siblings and restores both, so
`getEnvVariable` reads the test-controlled values even when CI sets only
`HEXCLAVE_*` (e.g. `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX`).
- Migration test: the raw insert into `OAuthAccessToken` now sets
`"updatedAt" = NOW()` since `Prisma`’s `@updatedAt` isn’t applied when
using `postgres.js` and the column is NOT NULL.

<sup>Written for commit 75c8e4343e.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1488?utm_source=github">Review
in cubic</a></sup>

<!-- End of auto-generated description by cubic. -->
2026-05-26 12:59:44 -07:00
Konstantin Wohlwend
c94bf484c8 Fix CI/CD 2026-05-26 12:54:55 -07:00
Konstantin Wohlwend
2f3e5d0d5a Update LLM documentation 2026-05-26 12:42:00 -07:00
Konstantin Wohlwend
fae8d2dfab Longer refresh token expiries for OAuth providers that don't return one 2026-05-25 22:35:22 -07:00
github-actions[bot]
bef9452c95 chore: update package versions 2026-05-26 03:44:50 +00:00
github-actions[bot]
ed54832671 chore: update package versions 2026-05-26 00:59:48 +00:00
Konstantin Wohlwend
d30962bf66 Fix GH tokens refresh & devtool tabs 2026-05-25 17:50:09 -07:00
BilalG1
f7e389809e
feat(hexclave): PR 1 — wire compatibility layer (invisible) (#1475)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary

**Stacked on #1468** (`docs/hexclave-rename-plan` — the plan doc). Diff
vs that base = the actual PR 1 code.

This is **PR 1 of the Hexclave rebrand: the invisible compatibility
layer**. Everything is additive. Old SDKs, old wire identifiers, and old
env var names keep working unchanged. The backend dual-accepts and
dual-emits; new SDK code emits `x-hexclave-*` headers and the
`hexclave_` Bearer prefix; cookies dual-write; env vars dual-read across
every category. **No user-visible rebranding lands here** — that's PR 2.

See [`RENAME-TO-HEXCLAVE.md`](./RENAME-TO-HEXCLAVE.md) → *"PR 1
implementation guide"* for the full per-work-area spec, file pointers,
and chosen approach.

## What's implemented (all 14 PR-1 work-areas)

- **SDK export aliases** — `Hexclave*` aliases for the user-facing
`Stack*` exports added in `packages/template`; codegen propagates them
to `@stackframe/{js,stack,react,tanstack-start}`. React-only aliases
correctly excluded from `@stackframe/js`. (`e60550a2`)
- **JWT issuer dual-accept** — `decodeAccessToken` accepts both
`api.stack-auth.com` and `api.hexclave.com` issuers. Signing unchanged.
(`fc781def`)
- **Request-header dual-accept** — backend + dashboard proxies normalize
`x-hexclave-*` → `x-stack-*` at the existing empty proxy hook (so
`smart-request.tsx` and every route schema keep working unchanged); CORS
allowlists extended via a derive-once helper. (`2a056eac`)
- **MCP `ask_hexclave`** — registered alongside `ask_stack_auth` via a
shared helper; `ask_stack_auth` behavior byte-identical. (`30ffd604`)
- **Dev-tool** — DOM ids + header emit switched.
`window.HexclaveDevTool` exposed alongside `window.StackDevTool`.
(`32131ea7`)
- **The big consolidated commit** (`7fed864a`):
- **Env vars** — central `getEnvVariable` prefix-transform (HEXCLAVE
first, STACK fallback); dashboard + template client env files dual-read;
`turbo.json` globalEnv; `NEXT_PUBLIC_STACK_PORT_PREFIX` renamed outright
across ~82 files including docker.
- **Cookies** — dual-write/dual-read auth (`stack-access`/`-refresh-*`
and custom-domain variants), OAuth-state
(`stack-oauth-{inner,outer}-*`), and low-risk cookies (`stack-is-https`,
`stack-last-seen-changelog-version`). Bypass sites patched (backend
OAuth callback, dashboard remote-dev auth route, impersonation snippets,
snapshot serializer).
- **Bearer prefix** — SDK token parser accepts both `stackauth_` and
`hexclave_`; emits `hexclave_`. Discovery correction: this is purely
SDK-internal — the backend never parses it.
- **Response headers** — backend dual-emits
`x-hexclave-{request-id,actual-status,known-error}`; SDKs dual-read (new
first, stack fallback).
- **SDK request-header emit switch** —
`client/server/admin-interface.ts` + dashboard `api-headers.ts` +
`internal-project-headers.ts` + `feedback-form.tsx` switched to
`x-hexclave-*`. Plus `stack_response_mode` query param.
- **Storage keys** — dev-tool / cli-auth / oauth-button / docs keys
renamed (straight); `stack:session-replay:v1` dual-read so in-progress
recordings survive SDK upgrades; `stack_mfa_attempt_code` dual-read.
- **Query params** — cross-domain params dual-emit/dual-accept via
shared helpers; backend `oauth/authorize` accepts
`hexclave_response_mode` and `stack_response_mode`; `stack-init-id`
renamed.
- **`Symbol.for`** — app-internals symbol gets a parallel
`Symbol.for("Hexclave--app-internals")` getter on each attach site (no
read-site churn — old symbol still attached). 3 file-private symbols
renamed outright.
- **Config discovery** — prefer `hexclave.config.ts`, fall back to
`stack.config.ts` at every discovery site (CLI / dashboard / backend /
local-emulator); `init` writes the new filename; CLI credentials path
migrates.
- **Internal renames** — `StackAssertionError`,
`StackClient/Server/AdminInterface` renamed outright (no alias, per the
"internal-only → rename" rule). ~264 files touched.
- **Review-pass fixes** (`21217fbe`) — three real bugs found by parallel
review agents and fixed:
- `snapshot-serializer.ts` was interpolating the whole
`keyedCookieNamePrefixes` array (`${arr}`) — adding a second prefix
would have corrupted **every** OAuth-cookie snapshot, not just new ones.
- **Docker port-prefix producer/consumer mismatch** —
`entrypoint.sh`/`run-emulator.sh`/cloud-init `user-data` were still
producing `NEXT_PUBLIC_STACK_PORT_PREFIX` while the dashboard sentinel +
consumers had been renamed; silent self-host regression (custom port
prefix would be ignored).
- **Missing `hexclave-oauth-inner-*` dual-write** in the OAuth authorize
route — callback's fallback masked it but the dual-write was specified
by the plan.
- Plus: `mcp.test.ts` tool-list assertions updated to include
`ask_hexclave`; two dashboard header-emit sites switched to
`x-hexclave-*` for consistency.
- **E2E snapshot serializer follow-up** (`4b16cc5d`) —
`x-hexclave-request-id` added to the hidden-headers list (mirroring
`x-stack-request-id` treatment), and 2 sample inline snapshots
regenerated in `projects.test.ts` to include the new dual-emitted
headers.

## Verification

- **`pnpm typecheck`** — clean (the fresh-worktree `@/.source` / Prisma
codegen gap in `stack-docs` is pre-existing and unrelated).
- **`pnpm lint`** — 29/29 packages green.
- **`pnpm exec turbo run build --filter=./packages/*`** — 13/13 packages
build (including `@stackframe/stack-cli` once the dashboard standalone
is present).
- **Live E2E** against a running backend on `cl/hexclave-pr1`:
- `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal/mcp.test.ts` — **6/6
pass** (verifies the new `ask_hexclave` tool — the hand-written inline
snapshot matched actual MCP server output).
- `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts` —
**11/11 pass** (verifies wire dual-accept + dual-emit end-to-end; the
snapshot serializer fix was found and applied during this check).

A four-agent parallel **review pass** also audited the full diff for
logic/runtime bugs across the work-areas (wire headers + JWT, cookies +
bearer + symbols, env vars, query params + config + MCP + aliases). All
in-slice review verdicts were ✓ except the three bugs listed above,
which are now fixed.

## Known follow-ups (out of scope for this PR)

- **E2E snapshots across the rest of the suite** — backend now
dual-emits `x-hexclave-{known-error,actual-status}` alongside
`x-stack-*`, which legitimately appears in inline snapshots throughout
`apps/e2e`. Two were regenerated here as a sample; the rest should regen
with `vitest -u` in CI.
- **Docker shell env vars beyond `PORT_PREFIX`** — `entrypoint.sh` still
reads `STACK_*` env vars directly (the JS-side `getEnvVariable`
transform doesn't help the shell). JS consumers dual-read so it works in
practice; full shell-level dual-read is a deeper self-host follow-up.
- **`@stackframe/stack-cli` build ordering** — pre-existing; needs
`build:rde-standalone` first. Not affected by this PR.

## Test plan

- [ ] CI runs full e2e suite (with `vitest -u` to absorb dual-emit
snapshot deltas, then committed back)
- [ ] Spot-check: an old SDK build (emitting only `x-stack-*`) still
authenticates against the new backend
- [ ] Spot-check: a new SDK (emitting `x-hexclave-*` / `Bearer
hexclave_*`) still authenticates against an old backend during deploy
ordering
- [ ] Manual: `npx @stackframe/stack-cli@latest init` (new onboarding
entrypoint) generates `hexclave.config.ts`
- [ ] Manual: existing `stack.config.ts`-only project still resolves (no
migration required)

---------

Co-authored-by: bilal <bilal@stack-auth.com>
2026-05-23 17:24:55 -07:00
Konstantin Wohlwend
1044144091 More test fixes 2026-05-23 13:55:15 -07:00
Mantra
10df9b2b7b
Fix dashboard sandbox compile errors and switch smart model to Grok (#1476)
## 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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 -->
2026-05-23 12:34:37 -07:00
Konstantin Wohlwend
725f2da886 Fix tests 2026-05-23 12:29:20 -07:00
Konstantin Wohlwend
281701e99d Fix CI 2026-05-23 11:17:51 -07:00
github-actions[bot]
957a33a651 chore: update package versions 2026-05-23 18:13:12 +00:00
Konstantin Wohlwend
866e618a20 Fix CI/CD
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
2026-05-23 10:48:41 -07:00
Konstantin Wohlwend
d4663fbe7d Capture more errors on failures 2026-05-23 10:34:55 -07:00
github-actions[bot]
6a0ded1340 chore: update package versions 2026-05-23 16:45:36 +00:00
Konstantin Wohlwend
760b866fea Fix CI/CD 2026-05-23 09:36:23 -07:00
Mantra
9b1851dd54
Managed email domain deletion and Cloudflare DNS import UX (#1442)
## 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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>
2026-05-23 09:22:29 -07:00
Konstantin Wohlwend
f6ef49a3dc Remove source-of-truth logic 2026-05-23 01:06:42 -07:00
Konstantin Wohlwend
884f8dfcea Better logs for unknown email errors 2026-05-22 21:08:31 -07:00
github-actions[bot]
01948e2313 chore: update package versions 2026-05-23 03:30:16 +00:00
github-actions[bot]
62aa8616d5 chore: update package versions 2026-05-23 03:25:52 +00:00
Konsti Wohlwend
6a8b45e6c5
fix: CI failures — Node 22, test timeouts, QEMU transaction timeout (#1479) 2026-05-22 20:24:13 -07:00
Konsti Wohlwend
b48902fcf2
docs: restart changelog with weekly updates (#1478) 2026-05-22 20:18:05 -07:00
github-actions[bot]
70999df64e chore: update package versions 2026-05-23 01:02:03 +00:00
Konsti Wohlwend
7e70b11d80
Fix onboarding shared OAuth provider persistence (#1477) 2026-05-22 17:54:11 -07:00
Konsti Wohlwend
1f0c27b644
fix: fix failing CI/CD on dev branch (#1473) 2026-05-22 16:25:57 -07:00
github-actions[bot]
9355c8665c chore: update package versions 2026-05-22 23:02:49 +00:00
github-actions[bot]
cd29811456 chore: update package versions 2026-05-22 22:58:20 +00:00
BilalG1
fb9f77843e
Populate ClickHouse analytics tables when seeding preview projects (#1471)
## 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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>
2026-05-22 15:54:24 -07:00
Konstantin Wohlwend
1effedbc42 Fix various cross-domain auth bugs 2026-05-22 13:40:39 -07:00
Konsti Wohlwend
197ad09eea
Route read-only raw Prisma queries to read replica (#1467) 2026-05-22 11:16:11 -07:00
github-actions[bot]
0c6e135c30 chore: update package versions
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
2026-05-22 01:35:39 +00:00
Konstantin Wohlwend
99f07e9516 Trust hosted domains
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
2026-05-21 18:23:23 -07:00
Konsti Wohlwend
9fc7332ead
Restore sign-up setting descriptions (#1469) 2026-05-21 17:58:24 -07:00
github-actions[bot]
d12968eb3d chore: update package versions 2026-05-22 00:46:34 +00:00
Konsti Wohlwend
f993e92b68
fix: flaky E2E tests and backend build TypeScript error (#1462) 2026-05-21 17:28:35 -07:00
Konsti Wohlwend
c6d59d0288
Cross domain handoffs (#1458) 2026-05-21 17:15:12 -07:00
Konsti Wohlwend
600a0d6fcf
fix: handle race condition in recordExternalDbSyncDeletion (#1466) 2026-05-21 17:02:20 -07:00
github-actions[bot]
03e7b61308 chore: update package versions 2026-05-21 23:29:36 +00:00
Konstantin Wohlwend
bf8d0ece28 chore: update package versions 2026-05-21 16:23:12 -07:00
Konstantin Wohlwend
35f3e699dd Fix types 2026-05-21 15:56:17 -07:00
Konstantin Wohlwend
4ff24dea9b chore: update package versions 2026-05-21 14:54:23 -07:00
Konstantin Wohlwend
a6762b00fa Update projects-metrics to use more Clickhouse 2026-05-21 14:52:25 -07:00
Armaan Jain
e42ec65c88
Payments app design fixes (#1375)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->

## Summary

This PR brings the Payments dashboard surfaces in line with the shared
design system: product creation, product-line / included-item dialogs,
auth-method toggles, payments empty states, and related layout polish.
Dialogs migrate from raw shadcn `Dialog` to `DesignDialog` with
consistent headers, footers, inputs, and selector dropdowns.

**Base:** `dev` → **Head:** `Payments-app-design-fixes`  
**Scope:** 31 files, ~+1.4k / −1.3k lines  
**Captured on:** local dev server (`internal` project), signed in as
`admin@example.com`

## Screenshots

Captured from `http://localhost:8101` (viewport: **1920×1200** standard,
**2560×1440** widescreen). Assets hosted in [this
gist](https://gist.github.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf).

> Red outlines on the **after** shots mark the new or changed UI
introduced by this PR.

### Create Product — payments form redesign

| | Before | After |
| --- | --- | --- |
| Light |
![payments-products-new-before-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-before-light.png)
|
![payments-products-new-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-after-light.png)
|
| Dark |
![payments-products-new-before-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-before-dark.png)
|
![payments-products-new-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-after-dark.png)
|

Widescreen:

| | Before | After |
| --- | --- | --- |
| Light |
![payments-products-new-before-light-wide](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-before-light-wide.png)
|
![payments-products-new-after-light-wide](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-after-light-wide.png)
|
| Dark |
![payments-products-new-before-dark-wide](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-before-dark-wide.png)
|
![payments-products-new-after-dark-wide](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-products-new-after-dark-wide.png)
|

### Product Lines onboarding — vertical centering fix

| | Before | After |
| --- | --- | --- |
| Light |
![payments-product-lines-before-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-product-lines-before-light.png)
|
![payments-product-lines-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-product-lines-after-light.png)
|
| Dark |
![payments-product-lines-before-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-product-lines-before-dark.png)
|
![payments-product-lines-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-product-lines-after-dark.png)
|

### Create Product Line dialog — `DesignDialog` migration

| | Before | After |
| --- | --- | --- |
| Light | *(legacy shadcn dialog on `dev` — open via Product Line →
Create new)* |
![dialog-create-product-line-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/dialog-create-product-line-after-light.png)
|
| Dark | |
![dialog-create-product-line-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/dialog-create-product-line-after-dark.png)
|

### Auth Methods — toggle row accessibility

| | Before | After |
| --- | --- | --- |
| Light |
![auth-methods-before-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/auth-methods-before-light.png)
|
![auth-methods-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/auth-methods-after-light.png)
|
| Dark |
![auth-methods-before-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/auth-methods-before-dark.png)
|
![auth-methods-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/auth-methods-after-dark.png)
|

### Other migrated surfaces (after only)

| Page | Light | Dark |
| --- | --- | --- |
| Payments settings |
![payments-settings-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-settings-after-light.png)
|
![payments-settings-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/payments-settings-after-dark.png)
|
| Sign-up rules |
![sign-up-rules-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/sign-up-rules-after-light.png)
|
![sign-up-rules-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/sign-up-rules-after-dark.png)
|
| Projects list (Create Project button) |
![projects-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/projects-after-light.png)
|
![projects-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/projects-after-dark.png)
|
| Playground / DesignDialog |
![playground-dialog-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/playground-dialog-after-light.png)
|
![playground-dialog-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/playground-dialog-after-dark.png)
|
| Included Item dialog |
![dialog-included-item-after-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/dialog-included-item-after-light.png)
|
![dialog-included-item-after-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/dialog-included-item-after-dark.png)
|

### Scroll behaviour — Sign-up Rules

| | Light | Dark |
| --- | --- | --- |
| Scroll |
![sign-up-rules-scroll-light](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/sign-up-rules-scroll-light.gif)
|
![sign-up-rules-scroll-dark](https://gist.githubusercontent.com/mantrakp04/ca3483d2b66b8e28f0872488df573ccf/raw/sign-up-rules-scroll-dark.gif)
|

## What's new

- **`DesignDialog`** extended with `customHeader`, `noBodyPadding`, and
section `className` hooks; Playground updated to showcase them.
- **Payments dialogs** (`CreateProductLineDialog`, `IncludedItemDialog`,
price edit, item dialog) migrated to design-system components.
- **Create Product** page uses `DesignButton`, `DesignInput`,
`DesignSelectorDropdown`, and refreshed header actions.
- **Auth Methods** toggle rows use semantic `<Label htmlFor>` instead of
click-capture divs.
- **Payments layout** empty-state card centers correctly; product-lines
onboarding slideshow vertically centers.
- **Backend** seed invariant for Growth product price; removed unused
import in product switch route.

## Notes for reviewers

- Dialog migrations preserve validation + async error handling
(`runAsynchronouslyWithAlert` where applicable).
- Included-item dialog uses a sentinel value for “Create new item” to
avoid colliding with real item IDs.
- `packages/stack` / `packages/js` are untouched; template +
dashboard-ui-components carry SDK-facing dialog changes.

## Test plan

- [x] Visual capture on `internal` project (`admin@example.com`) —
light/dark, standard + widescreen
- [ ] Create product flow: customer type → product line dropdown →
create line dialog
- [ ] Add included item dialog from create/edit product
- [ ] Auth Methods toggles (label click + switch)
- [ ] Payments product-lines onboarding slideshow at varied viewport
heights
- [ ] `pnpm typecheck` / `pnpm lint` / targeted E2E if API surface
changed

---------

Co-authored-by: nams1570 <amanganapathy@gmail.com>
Co-authored-by: mantrakp04 <mantrakp@gmail.com>
Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com>
2026-05-21 14:48:56 -07:00
Aman Ganapathy
be2ad595ad
fix: checkout flow for 0 dollar subscription (#1465)
### 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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 -->
2026-05-21 14:33:55 -07:00
Mantra
e3bd5a6638
Move internal metrics queries to ClickHouse replica (#1463)
## 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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 -->
2026-05-21 14:31:58 -07:00
Konstantin Wohlwend
5edccc322c Increase replication timeout to 2s 2026-05-21 14:18:39 -07:00
Konsti Wohlwend
0df594abe7
Handle payout.created and payout.reconciliation_completed Stripe webhook events (#1461) 2026-05-21 14:11:36 -07:00
Konsti Wohlwend
3b2e991c78
Fix browser compatibility: guard requestIdleCallback and startViewTransition (#1464) 2026-05-21 14:10:51 -07:00
BilalG1
b8fc04bdbd
feat: link Stack Auth projects to GitHub and push config from the dashboard (#1450)
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.


![demo](https://gist.githubusercontent.com/BilalG1/29d1188fc581e87d1311baec6e2ae770/raw/demo-2x.gif)

## 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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 -->
2026-05-21 13:47:46 -07:00
BilalG1
91b8e4caa4
Fix /internal/metrics ClickHouse OOM (#1457)
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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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 -->
2026-05-21 13:47:32 -07:00
Konsti Wohlwend
002692e519
Compress oversized images client-side in AI chat (#1456) 2026-05-21 12:57:20 -07:00
Aman Ganapathy
0e85b05c3d
[Fix]: Payments App Sundry Fixes (#1455)
### Summary of Changes
You can now edit items on a product view.
The "Make free" button is less obtuse, and it clearly tells you what
it's going to do.
Additionally, we found out while working on this PR that you cannot
create a `paymentIntent` on stripe that is < 0.5$. So, you can't create
an OTP for a "free" product. We add safeguards to protect against that.
Also, 0 dollar subscriptions don't create a subscription invoice.
Additionally, the old code relied on being able to fetch the stripe
client secret, which would be null for a 0 dollar subscription so we
create a carve out.



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Better free-product checkout handling: $0 subscriptions return an
empty success response without a payment client secret; non-free
subscriptions include client secret when needed.
* UI: “Make free” flow, “Free · {amount}” with price ID, per-price
checkout error indicators/tooltips, and an alert for products with
invalid prices.
  * Client- and server-side Stripe one-time minimum checks.

* **Bug Fixes**
* Included-item dialog now resets form state when opened to avoid stale
values.

* **Documentation**
* OpenAPI: clarified client_secret may be omitted when no customer
confirmation is required.

* **Tests**
  * Added end-to-end tests covering $0 purchase-session flows.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1455?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 -->
2026-05-20 19:33:14 -07:00
BilalG1
01aacd2dd4
fix(dashboard): repair and polish the GitHub link-existing project flow (#1441)
Rework of the **new-project → Link Existing Config** flow on the
dashboard, plus the published `stack-cli` it depends on.

The starting point on `dev` had the link-existing flow effectively
broken end-to-end (the generated GitHub workflow could never
authenticate, and the GitHub-account selection UI dead-ended in several
states). This PR fixes the blockers, polishes the local-CLI path, and
adds a searchable repo/branch picker.

---

## What was broken

| Severity | Issue | Fixed in |
|---|---|---|
| 🔴 | Generated workflow omitted the required `--cloud-project-id` flag
→ every run failed at Commander before the action ran. | `d0e6ad15f`,
`55ff7e319` |
| 🔴 | Workflow exported `STACK_PROJECT_ID` env var the CLI never read. |
`55ff7e319` (CLI now reads it; workflow drops the explicit flag) |
| 🔴 | `pnpx` isn't on `ubuntu-latest` → step failed with `command not
found`. | `65789a1ac` |
| 🔴 | "No connected GitHub account found" alert with **no Connect
button**. | `d0e6ad15f` |
| 🟠 | "Connect new" used `getOrLinkConnectedAccount` (get-or-link) →
silently returned the existing account instead of starting a fresh OAuth
flow. | `d0e6ad15f` |
| 🟠 | `workflow_dispatch` 404s on non-default branches; threw before
advancing to the logs step even though the push-triggered run worked. |
`d0e6ad15f` |
| 🟠 | Config-path suggestions prepended `./`, which breaks GitHub's
`on.push.paths` filter — ongoing config edits never re-triggered the
workflow. | `d0e6ad15f` |
| 🟡 | Account selector briefly showed the numeric `providerAccountId`
before the GitHub `/user` fetch populated the username. | `de9ec1923` |
| 🟡 | Repository / branch dropdowns capped at 100 entries with no
search. | `7550eaacb` |

## What changed

### Dashboard — Link Existing Config flow

- **Local CLI step rebuild** (`ed25eabf9`, `ebb090e5b`): split into
separate "Sign in" and "Push config" code blocks using the shared
`CodeBlock` component (copy button built-in), added a `npx / pnpx /
bunx` runner pill toggle (default `npx`), moved `--config-file <path>`
to the end of the push command so users can copy everything up to the
placeholder, trimmed redundant helper text.
- **GitHub OAuth states** (`d0e6ad15f`, `de9ec1923`): empty-state
"Connect GitHub account" button; "Connect new" now uses
`linkConnectedAccount` so it actually starts OAuth; loading row instead
of `providerAccountId` flash.
- **Searchable repo + branch combobox** (`7550eaacb`, `5ce1b6bd9`): new
`RemoteSearchCombobox` (Popover + cmdk, same pattern as
`data-table/faceted-filter`), debounced GitHub `/search/repositories`
and `/git/matching-refs/heads/{prefix}` calls so users with > 100
repos/branches can find any of them. Branch "Refresh" button removed —
branches auto-load on repo select.
- **Workflow generator** (`d0e6ad15f`, `65789a1ac`): config paths
normalised (strip leading `./`); workflow uses `actions/setup-node@v4` +
`npx --yes`; `workflow_dispatch` failure is now best-effort (the
workflow-file commit's push event triggers the run on any branch).

### Stack CLI

- `STACK_PROJECT_ID` env-var fallback for `--cloud-project-id`
(`55ff7e319`). Both `config push` and `config pull` are affected;
explicit flag still wins. New `resolveProjectId` helper in `lib/auth.ts`
with 5 unit tests (`auth.test.ts`).

### Misc

- `2faffb662` drops an unused `useTransition` wrapper around a
`setProjectStatuses` Map insert in the new-project flow.

---

## Release ordering note

The generated workflow's `run:` line **no longer passes
`--cloud-project-id`** — the CLI reads `STACK_PROJECT_ID` from env
instead. This means a workflow generated by this branch only works
against a `@stackframe/stack-cli` published with the env-var fallback
from `55ff7e319`. The CLI and dashboard ship from the same monorepo so
this should be a non-issue in the normal release cadence, but worth
confirming the CLI publishes alongside the dashboard deploy.

Existing workflows already committed in user repos still have the
explicit flag and continue to work unchanged.

## Validation

- `pnpm --filter @stackframe/dashboard run typecheck` 
- `pnpm --filter @stackframe/dashboard run lint` 
- `pnpm --filter @stackframe/stack-cli run typecheck` 
- `pnpm --filter @stackframe/stack-cli run lint` 
- `pnpm --filter @stackframe/stack-cli test`  (14 tests; 5 new for
`resolveProjectId`)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Searchable repository and branch selection UI for GitHub onboarding
  * New remote search combobox component for selecting repos/branches
* Selectable CLI package runner and dynamic command display during
onboarding

* **Improvements**
  * CLI accepts STACK_PROJECT_ID env var; cloud project flag is optional
* Workflow generation normalizes/validates config paths, sets up Node.js
v20, and uses npx; onboarding dispatch is non-fatal
  * Hardened repository loading to avoid stale async updates

* **Tests**
  * Added tests covering project ID resolution logic

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1441?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 -->
2026-05-20 16:39:40 -07:00
devin-ai-integration[bot]
20b7921b93
Fix theme toggle in browsers without View Transitions (#1453)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/hexclave/stack-auth/blob/dev/CONTRIBUTING.md

-->

Fixes the dashboard theme toggle in browsers that do not support
`document.startViewTransition` by falling back to an immediate theme
change.

Link to Devin session:
https://app.devin.ai/sessions/c1f1deed2f1c4d42979df2ee949cf74d
Requested by: @madster456

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-20 18:32:00 -05:00
Konstantin Wohlwend
104f347cbf Update AI chat models 2026-05-20 13:43:28 -07:00
Mantra
a84c7814de
Use Accept header for skills HTML/markdown negotiation (#1454)
## Summary

Follow-up to #1452. `Sec-Fetch-Mode` / `Sec-Fetch-Dest` didn't reliably
split HTML vs. markdown at the CDN edge, so curl could still get the
HTML landing page. Switch to the `Accept` header:

- Browsers send `Accept: text/html,...` on top-level navigations.
- `curl`, `fetch()`, and agent fetchers send `*/*` or omit `Accept`.
- Serve HTML only when `text/html` is explicitly listed; everything else
gets `SKILL.md`.
- `Vary` updated to `Accept` to match.

## Test plan

- [ ] Deploy preview
- [ ] `curl -sSL https://skill.stack-auth.com/ | head -3` returns
markdown frontmatter
- [ ] Browser load of `https://skill.stack-auth.com/` still shows the
HTML landing page
- [ ] Purge Vercel cache if stale variants persist

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved content format negotiation for skill resources to correctly
serve HTML or markdown based on client requests.

* **Chores**
* Optimized caching behavior for edge and CDN services to enhance global
content distribution efficiency.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1454?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 -->
2026-05-20 13:15:13 -07:00
Armaan Jain
055304d3fd
Onboarding app redesign (#1370)
# Onboarding app redesign

Rolls out a unified dashboard visual language centered on `DesignCard`
groupings, a new canonical `DesignDialog`, and an inline live-preview
pattern. Touches the project listing, project overview, auth methods,
design language, onboarding, and sign-up rules surfaces. Reusable
primitives (`DesignCard`, `DesignDialog`, `MethodToggleRow`) replace
one-off layouts, and the project card now leads with **total users +
30-day signups** instead of a weekly-users tile.

**Base:** `dev` → **Head:** `onboarding-app-redesign`

> Red outlines on the "after" shots highlight the UI that changed in
this PR. Empty outlines = layout/chrome change with no data delta.

---

## Flagship: Project listing (`/projects`)

Project cards swap the weekly-users widget for a `ProjectUsersMetric`
(total user count + 30-day signups sparkline). Hover lifts the card; the
metrics row is now part of the card body instead of a footer strip.

|        | Light | Dark |
|--------|-------|------|
| Before | ![before
light](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/projects-before-light.png)
| ![before
dark](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/projects-before-dark.png)
|
| After | ![after
light](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/projects-after-light.png)
| ![after
dark](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/projects-after-dark.png)
|

## Flagship: Auth methods (`/projects/[id]/auth-methods`)

Full restructure: the horizontal `SettingCard` strips are replaced by
stacked `DesignCard` sections (Sign-in methods · Sign-up policies · User
deletion), with a sticky **live sign-in preview** column on the right.
Provider rows become `MethodToggleRow`s with inline configure actions.

|        | Light | Dark |
|--------|-------|------|
| Before | ![before
light](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/auth-methods-before-light.png)
| ![before
dark](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/auth-methods-before-dark.png)
|
| After | ![after
light](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/auth-methods-after-light.png)
| ![after
dark](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/auth-methods-after-dark.png)
|

## Flagship: Project overview (`/projects/[id]`)

Line + donut charts migrate to the shared `AnalyticsChart` component.
Referrers list gains a max-height + scroll affordance so it no longer
pushes neighbouring tiles off-screen.

|        | Light | Dark |
|--------|-------|------|
| Before | ![before
light](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/overview-before-light.png)
| ![before
dark](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/overview-before-dark.png)
|
| After | ![after
light](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/overview-after-light.png)
| ![after
dark](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/overview-after-dark.png)
|

## Other migrated surfaces

| Surface | Before (dark) | After (dark) | What changed |
|---------|---------------|--------------|--------------|
| `/projects/[id]/onboarding` |
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/onboarding-before-dark.png)
|
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/onboarding-after-dark.png)
| Email-verification toggle adopts the new `MethodToggleRow` +
confirmation `DesignDialog` variant |
| `/projects/[id]/sign-up-rules` |
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/sign-up-rules-before-dark.png)
|
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/sign-up-rules-after-dark.png)
| Rule builder rewrapped in `DesignCard`/`DesignAlert`/`DesignButton`
primitives |
| `/projects/[id]/design-language` |
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/design-language-before-dark.png)
|
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/design-language-after-dark.png)
| Adds a `DesignDialog` showcase section so consumers can see the
canonical modal styling |
| `/playground` |
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/playground-before-dark.png)
|
![](https://gist.githubusercontent.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7/raw/playground-after-dark.png)
| New `dialog` playground entry exercising the size/variant/icon-chip
permutations |

Light-mode counterparts for the long-tail surfaces are in the [companion
gist](https://gist.github.com/mantrakp04/ff6b32969cb08510860e94be7d67dbf7).

---

## What's new

- **`DesignDialog`**
(`packages/dashboard-ui-components/src/components/dialog.tsx`) —
canonical modal with configurable size/variant, optional icon chip, and
split header/body/footer regions. Replaces ad-hoc `Dialog` +
`DialogContent` usage across the dashboard.
- **`MethodToggleRow`** — shared row primitive used by auth-methods and
onboarding for "thing with a toggle and an inline configure CTA".
- **`ProjectUsersMetric`** — total users + 30-day signups sparkline;
powers the new project card metric and reuses the
`projects-weekly-users` backend route renamed to `projects-metrics`.
- **`action-dialog`** gains `keepOpenOnOutsideInteraction` and
`contentClassName` props so variant chrome can ride along through the
existing helper.
- Backend: new internal `projects-metrics` route + test;
`seed-dummy-data.ts` updated to populate the new metric.

## Notes for reviewers

- Reusable primitives (`DesignCard`, `DesignDialog`, `MethodToggleRow`)
live in `packages/dashboard-ui-components` — please flag any inline
duplications you spot.
- The auth-methods live-preview only renders at `lg+`. Below that
breakpoint the page falls back to the stacked card layout.
- The OAuth provider config dialogs adopt the new pill toggle for
**Shared keys / Custom OAuth credentials**; the underlying form fields
are unchanged.

## Test plan

- [ ] `/projects` — verify the metric tile renders both empty-state and
populated (Demo Project has 584 users seeded)
- [ ] `/projects/[id]/auth-methods` — toggle each method on/off, confirm
live preview updates in real time
- [ ] `/projects/[id]/auth-methods` — open a provider dialog, switch
between Shared / Custom keys, verify form state preserved
- [ ] `/projects/[id]/onboarding` — toggle email verification, confirm
the confirmation dialog variant
- [ ] `/projects/[id]/sign-up-rules` — verify rule builder still saves
correctly under the new chrome
- [ ] Mobile/`md` breakpoint — auth-methods falls back to stacked
layout, no overflow
- [ ] Dark mode parity on every flagship surface

<sub>Visuals captured via local dev server (`localhost:8101`) on
`admin@example.com` seeded account. Red outlines mark new/changed UI on
the "after" pass.</sub>

---------

Co-authored-by: mantrakp04 <mantrakp@gmail.com>
Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com>
2026-05-20 13:10:22 -07:00
Mantra
f170e3f32e
Fix skill.stack-auth.com CDN serving HTML to curl (#1452)
## Summary

- Adds `Vary: Sec-Fetch-Mode, Sec-Fetch-Dest` on the skills app root
route so the CDN caches markdown and HTML responses separately.
- Fixes production behavior where `curl https://skill.stack-auth.com/`
could return the browser HTML landing page (cached from a
`Sec-Fetch-Mode: navigate` request) instead of the canonical `SKILL.md`
body.

## Context

The route already content-negotiates: browsers with `Sec-Fetch-Mode:
navigate` get HTML; `curl` and agents get markdown. Without `Vary`,
Vercel served a single cached variant to all clients.

## Test plan

- [ ] Deploy or preview the skills app
- [ ] `curl -sSL https://skill.stack-auth.com/ | head -3` returns
markdown (`---` frontmatter), not `<!doctype html>`
- [ ] Open `https://skill.stack-auth.com/` in a browser — still shows
the HTML landing page
- [ ] Purge Vercel cache if stale HTML persists after deploy


Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved CDN caching configuration to optimize content delivery and
response handling across different content formats.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1452?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>
2026-05-20 12:27:07 -07:00
Konstantin Wohlwend
20b029fd81 Fix build 2026-05-20 12:05:53 -07:00
Konstantin Wohlwend
90421431ee chore: update package versions 2026-05-20 11:58:44 -07:00
Konstantin Wohlwend
6a35289aa7 Revert upgrades 2026-05-20 11:58:27 -07:00
Konstantin Wohlwend
e6d8613055 Disable top-level awaits 2026-05-20 11:51:43 -07:00
Konstantin Wohlwend
3df4ca524e Remove "type": "module" from backend package.json 2026-05-20 11:44:00 -07:00
Konsti Wohlwend
2c620aa208
Show enabled alpha apps in sidebar and app store (#1449) 2026-05-20 11:07:49 -07:00
BilalG1
512099ed23
Speed up dummy-project seeding (preview create-project ~15s → ~1.3s) (#1437)
## Summary

The internal `preview/create-project` endpoint was taking ~15s because
`seedDummyProject` created its dummy users one at a time through the
full `usersCrudHandlers.adminCreate` CRUD pipeline (one DB transaction +
config render per user, ~86 users). This reworks the seeding path to use
bulk inserts.

End-to-end, the endpoint's server-side handler time drops from
**~15,100ms → ~1,300ms** (~11× faster).

## Seeding changes (`seed-dummy-data.ts`)

- **`seedDummyUsers` — bulk insert.** Build every row (`ProjectUser`,
`ContactChannel`, `AuthMethod`, `ProjectUserOAuthAccount`,
`OAuthAuthMethod`, default permissions) up front with pre-generated
UUIDs, then insert via one `createMany` per table inside a single
transaction — replacing ~86 sequential `adminCreate` transactions.
Named-user team memberships are bulk-inserted the same way (`TeamMember`
+ `TeamMemberDirectPermission`). Idempotency is preserved with a single
up-front email lookup, so re-runs against an existing project still skip
existing users.
- **Native `randomUUID`.** The seed paths now use `node:crypto`'s
`randomUUID()` instead of stack-shared's `generateUuid()`. The
browser-safe polyfill calls `crypto.getRandomValues` ~31× per UUID (once
per template char, each with a fresh `Uint8Array(1)`); generating
thousands of seed UUIDs made that ~800ms of pure CPU in the
activity-event build alone.
- **`seedBulkSignupsAndActivity`.** Skip the redundant back-date
`UPDATE` for freshly-inserted users (`createMany` already writes correct
`createdAt`/`signedUpAt`), and flush ClickHouse events in larger,
parallel batches.
- **`seedDummyProject`.** Run `seedBulkSignupsAndActivity` concurrently
with the lighter remaining steps, and fold `seedDummyTransactions` into
the emails/activity/replays `Promise.all`.
- Removed the now-unused `syncSeedUserOauthProviders` helper.

The bulk path produces the same rows as the CRUD-handler path (verified
row-count equality during development). Webhooks / soft-limit checks are
intentionally not fired for seed data, consistent with the rest of the
seed.

## Also in this PR — preview-mode 404 fix
(`preview-project-redirect.tsx`)

While testing the above, the dashboard 404'd right after a preview
project was created. In preview mode the `/projects` page renders
`PreviewProjectRedirect`, which `POST`s
`/internal/preview/create-project` and then `router.push()`es to
`/projects/<new-id>` — but it never refreshed the client-side
owned-projects cache, so the `[projectId]` route's `useAdminApp()` read
a stale list, failed to find the just-created project, and called
`notFound()`.

Fixed by refreshing the owned-projects cache before navigating, matching
what the normal create-project flow in `page-client.tsx` already does.
(Pre-existing bug, not caused by the seeding change — but it surfaces
the seeding path, so it's bundled here.)

## Testing

`pnpm typecheck` and `pnpm lint` pass for both backend and dashboard.
The preview endpoint was exercised repeatedly during development (HTTP
200, projects created and populated correctly).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Performance**
* Much faster bulk user and event seeding via larger, parallelized
batches and optimized backfilling.

* **Refactor**
* Dummy data seeding redesigned to be idempotent, deterministic, and
bulk-oriented; seeding tasks now overlap where safe.

* **Bug Fixes**
* Preview project flow validates client capabilities and refreshes the
local project list to avoid stale navigation.
  * Auto-login guarded to run only once to prevent duplicate sign-ins.

* **UI/UX**
* Walkthrough steps and sidebar behavior improved; walkthrough labels
and search keywords updated.

* **Chore**
* CLI identity command now resolves session authentication more
reliably.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1437?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 -->
2026-05-20 10:10:22 -07:00
Konsti Wohlwend
ffbd09dc57
Fix flaky tests and preexisting CI failures (#1443) 2026-05-20 10:00:11 -07:00
Konsti Wohlwend
97f86a116b
Fix globe drag not ending when pointer released outside element (#1447) 2026-05-19 23:09:45 -07:00
Armaan Jain
5dbfb1ebab
Auth app redesign (#1367)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a reusable DesignDialog modal system (sizes, variants,
header/footer/headerContent, trigger/close controls).
* Added a documented "roids" skill and pinned it in the skills registry.

* **Documentation**
* Expanded design guide with comprehensive dialog usage patterns,
examples, and props.

* **Improvements**
  * Playground now previews and generates dialog code interactively.
* Auth methods and sign-up rules UIs migrated to the new design system.
* Action dialogs can opt to ignore outside interactions and accept
custom content classes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---

## Summary

Two things bundled together:

1. **New `DesignDialog` primitive** in
`@stackframe/dashboard-ui-components` — the canonical glassmorphic
dashboard modal shell. Exposes configurable sizes (`sm`→`7xl`/`full`),
`glassmorphic` vs `plain` variant, optional icon / title / description /
footer / custom header slots, and a `DesignDialogClose` companion.
Replaces the ad-hoc dialog wrappers scattered across the dashboard.
2. **Auth-app pages migrated onto the design-components system** —
`auth-methods` and `sign-up-rules` are rebuilt on `DesignCard` /
`DesignAlert` / `DesignButton` / `DesignBadge` / `DesignInput` /
`DesignMenu` / `DesignSelectorDropdown` / `DesignDialog`. Live
OAuth-page preview frame, glassmorphic confirmation dialogs, and a
redesigned rule-builder all live behind these new shells.

The design-language catalog page and the `/playground` component
explorer were both extended with full dialog showcases so the new
primitive has a single discoverable home.

**Base:** `dev` → **Head:** `auth-app-redesign`
**Scope:** 11 files changed · +2553 / −1151 lines

---

## Screenshots — before and after

> Captured locally against `http://localhost:8101` at 1440×900 with a
fresh project (`Demo Project`) created via the sign-up + new-project
flow. Dev-only overlays (outdated-version banner, console toasts) are
hidden via injected CSS for clarity.

### Auth methods — `/projects/<id>/auth-methods`

The big page-client rewrite. Before was a flat list of toggleable rows
with a live preview pinned to the right. After is a sectioned layout —
`SIGN-IN METHODS` and `SSO PROVIDERS` get uppercase subheaders, each
method gets a `DesignBadge` icon + description ("Classic email +
password credentials.", "One-time codes delivered by email.",
"Phishing-resistant device-bound credentials."), and empty states (e.g.
SSO with no providers configured) become real call-outs instead of plain
rows.

| Before (`dev`) | After (this PR) |
| --- | --- |
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/before-auth-methods__light.png)
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-auth-methods__light.png)
|
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/before-auth-methods__dark.png)
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-auth-methods__dark.png)
|

### Sign-up rules — `/projects/<id>/sign-up-rules`

Full rule-builder rewrite (CEL ↔ visual tree round-trip kept intact,
just dressed in the new design system). Before's empty state was a flat
alert + plain "Default action" row. After uses `DesignCard` variants —
`NO RULES YET` with an inline "Add your first rule" CTA, an "If no rules
match → Allow sign-up" surface, and a dedicated `TEST RULES` card
linking the simulator.

| Before (`dev`) | After (this PR) |
| --- | --- |
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/before-sign-up-rules__light.png)
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-sign-up-rules__light.png)
|
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/before-sign-up-rules__dark.png)
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-sign-up-rules__dark.png)
|

### Component playground — `/playground`

A new **Dialog** entry was added to the component selector. The before
shots show `dev` — the selector only listed Button (and a handful of
other primitives) and had no Dialog playground at all. The after shots
show the new entry: a props panel for `shape` / `size` / `variant` /
`title` / `description` / `headerIcon` / `footer` / `topRightClose`,
plus an "Open confirmation" button that mounts the live `DesignDialog`.

#### Closed (props panel + code preview)

| Before (`dev` — no Dialog entry) | After (this PR) |
| --- | --- |
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/before-playground-dialog__light.png)
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-playground-dialog__light.png)
|
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/before-playground-dialog__dark.png)
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-playground-dialog__dark.png)
|

> The "before" shots default to the Button playground because the Dialog
entry doesn't exist on `dev` — that's the change.

#### Open (glassmorphic surface in action)

The dialog itself — only available after this PR, so no `dev`
equivalent.

| Light | Dark |
| --- | --- |
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-playground-dialog-open__light.png)
|
![](https://gist.githubusercontent.com/aadesh18/7789d1cf8622693a85f61cd7db91f5c2/raw/after-playground-dialog-open__dark.png)
|
## What changed

- **New** `packages/dashboard-ui-components/src/components/dialog.tsx` —
the `DesignDialog` primitive. Props shape: `size` × `variant` × optional
`icon` / `title` / `description` / `headerContent` / `customHeader` /
`footer` slots, plus `trigger`, `noBodyPadding`, `hideTopCloseButton`,
and per-section `*ClassName` escape hatches. Exports `DesignDialog`,
`DesignDialogClose`, plus the `DesignDialogSize` / `DesignDialogVariant`
/ `DesignDialogProps` types.
- **Exports** wired through
`packages/dashboard-ui-components/src/index.ts` so consumers import from
`@stackframe/dashboard-ui-components` or, by extension, the dashboard's
local `@/components/design-components` barrel.
- **Auth methods page**
(`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx`)
— full migration. Sign-in methods, OAuth provider list, dot-menu
actions, "Add disabled providers" search dialog, two confirmation
dialogs, sign-up policy block, user-deletion block. Old `Card` / `Input`
/ `Button` / `SettingCard` imports replaced with their design-component
counterparts. `providers.tsx` follows the same migration for the
per-provider config dialogs.
- **Sign-up rules page**
(`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx`)
— the big 1830-line rewrite. Rule builder, empty state,
conditional-group editor, and tester sheet all rebuilt on the new
primitives. CEL ↔ visual-tree conversion (`parseCelToVisualTree` /
`visualTreeToCel`) is unchanged.
- **Design-language catalog**
(`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/design-language/page-client.tsx`)
— adds the Dialog section so the catalog reflects the new primitive.
- **Playground**
(`apps/dashboard/src/app/(main)/(outside-dashboard)/playground/page-client.tsx`)
— adds the Dialog entry with `Shape` / `Size` / `Variant` / `Title` /
`Description` / `Header Icon` / `Footer` / `Top-right close` controls
and live JSX generation.
- **Design guide** (`apps/dashboard/DESIGN-GUIDE.md`) — new "Dialogs"
section documenting when to reach for `DesignDialog` (default),
`DesignDrawer`, `ActionDialog`, or the raw `<Dialog>` primitives, plus
the canonical usage snippet.
- **Action dialog shim**
(`apps/dashboard/src/components/ui/action-dialog.tsx`) — small follow-on
edits so existing `ActionDialog` callers stay consistent with the new
look.

## Notes for reviewers

- **Start with**
`packages/dashboard-ui-components/src/components/dialog.tsx` — it's the
load-bearing piece. The two state machines worth eyeballing are the
`dialogSurfaceClasses` map (glassmorphic vs plain shells, including the
dark-mode ring/backdrop tweaks) and the header/body/footer composition
inside the main `DesignDialog` function.
- **Then** `sign-up-rules/page-client.tsx`. 1830 lines, but the diff is
mostly mechanical (Card→DesignCard, Button→DesignButton, etc.). The
interesting bits are the rule-row layout, the conditional-group editor,
and the simulator drawer — those received structural tweaks, not just
visual ones. The CEL serialization (`parseCelToVisualTree` /
`visualTreeToCel`) was deliberately left alone.
- **OAuth provider migration to non-pushable config** — a `// OAuth
client ID/secret are environment-level (not pushable)` comment was
removed from a couple of call-sites. Behaviour-equivalent (the call
already passes `pushable: false`), just trimmed because the new code is
cleaner. Flag if you want it kept.
- **Catalog routes are dashboard-internal**
(`/projects/<id>/design-language`, `/playground`) — exposed only in
dev/staging, not customer-facing. They exist so design changes have a
discoverable demo surface.
- **Live-preview frame on `auth-methods`** uses a real `<AuthPage>`
inside `BrowserFrame`, fed by the in-progress config. Verify your
changes still render correctly there if you touch `<AuthPage>` props.

## Test plan

- [ ] `/projects/<id>/auth-methods` — toggle each sign-in method; live
preview reflects the change; "Save changes" inline action works; "Add
SSO providers" dialog filters via the search input
- [ ] OAuth provider dot-menu — open the provider config dialog (now
`DesignDialog` glassmorphic), confirm the per-provider switches/inputs
save through the `useUpdateConfig` hook
- [ ] Sign-up confirmation dialogs — toggling "Allow new user sign-ups"
off and back on shows the new warning `DesignAlert`s inside the dialog
- [ ] `/projects/<id>/sign-up-rules` — add a rule, add a condition
group, run the tester sheet; CEL output unchanged vs `dev`
- [ ] `/projects/<id>/design-language` — Dialog showcase renders all
sizes/variants without overflow
- [ ] `/playground` → select **Dialog** — all prop combinations render;
generated code snippet matches the rendered component; "Open
confirmation" launches the glassmorphic shell
- [ ] Light + dark mode visual sanity across all four pages (screenshots
above are the canonical reference)

---------

Co-authored-by: Aadesh Kheria <kheriaaadesh@gmail.com>
2026-05-19 23:03:46 -07:00
Konsti Wohlwend
2aa4affa54
Fix build and lint failures on dev (#1445) 2026-05-19 19:05:26 -07:00
Konsti Wohlwend
6e769c3be3
Upgrade Next.js 2026-05-19 19:01:00 -07:00
Konstantin Wohlwend
a62702354b Don't show alpha apps during onboarding 2026-05-19 17:35:27 -07:00
Konstantin Wohlwend
bb901068cb Fix React error 2026-05-19 16:48:35 -07:00
Konstantin Wohlwend
48acb8c640 chore: update package versions 2026-05-19 16:22:10 -07:00
Konstantin Wohlwend
0848a1aaed Add schema to migration that was missing it 2026-05-19 16:14:28 -07:00
Konsti Wohlwend
29cea48beb
Remote dev envs (#1435) 2026-05-19 15:54:18 -07:00
devin-ai-integration[bot]
deff6c3cc4
[DEVIN: Konsti] Fix failing E2E tests: CLI error string and MCP prompt name (#1439) 2026-05-19 14:20:18 -07:00
Konstantin Wohlwend
d68631ea4f Update GitHub URL 2026-05-19 10:27:53 -07:00
BilalG1
d0202eeef9
payments: rework refund flow to three-knob API (#1429)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary
- Replaces per-entry refund schema with a flat `{ amount_usd,
revoke_product, end_subscription? }` shape; refund state is now derived
from bulldozer ledger rows (`refund:<sourceTxnId>:<uuid>`) instead of
the legacy `refundedAt` column, enabling multiple partial refunds up to
the remaining cap.
- Adds `invoice_id` for refunding any subscription invoice (start or
renewal), Stripe idempotency keys derived from `(tenancyId, sourceTxnId,
amount, prior_refunded)` so retries dedupe but intentional partials
don't collide, and a legacy backstop that rejects pre-rework
`refundedAt` purchases.
- Dashboard refund dialog rebuilt around the three toggles (revoke→end
coupling cascades into the UI); refund rows surface in the listing as
`type: "refund"` with `adjusted_by` linkage handling both new and legacy
formats.

## Implements
[STA2-52 — Build in refund logic for
payments](https://linear.app/stack-auth/issue/STA2-52/build-in-refund-logic-for-payments)

## Documented limitations (planned follow-up work)
These are called out in code comments and intentionally deferred to a
follow-up PR:
- **Cap-check race under concurrent refunds.** Bulldozer's embedded
`BEGIN/COMMIT` prevents an outer Prisma tx from scoping the writes, so
two concurrent refunds can both pass the cap check. Needs a
bulldozer-aware mutex or pending-refund-intent pattern. In practice
refunds are admin-only and rare, so the race window is small.
- **Stripe + DB non-atomicity on the DB-success → response-loss path.**
The Stripe idempotency key is keyed on `(tenancyId, sourceTxnId, amount,
priorRefunded)`, so a retry after Stripe-success → DB-fail self-heals
(Stripe dedupes; the next attempt writes the bulldozer row). The hole is
the reverse direction: if the bulldozer row commits but the response is
lost, a retry sees a higher `priorRefunded` and generates a fresh key —
Stripe would issue a second real refund. No out-of-band reconciliation
today.
- **Dashboard can't reach the `invoice_id` path.** Refund actions are
only enabled on `purchase` rows and the submit call never passes
`invoice_id`, so admins refunding a renewal must use the API directly.
Follow-up: enable the action on `subscription-renewal` rows and thread
`invoice_id` through.

## Architectural note
`active-subscription-end` and `item-quantity-expire` entries are **not**
emitted on the refund row itself. They're produced by the derived
sub-end transaction (`transactions.ts:158-228`) once Prisma
`subscription.endedAt` is updated, keeping the `expiresWhen` /
`when-repeated` semantics in one place. This is the main structural
divergence from the ticket's literal entry recipe.

## Review follow-ups addressed in this PR

**First-pass review:**
- **KnownError back-compat preserved**: `SubscriptionAlreadyRefunded` /
`OneTimePurchaseAlreadyRefunded` are once again thrown by the
legacy-`refundedAt` backstop, and `TestModePurchaseNonRefundable` is
thrown when an admin sends `amount_usd > 0` against a test-mode
purchase. Callers catching by error code keep working through the
rework.
- **Idempotency-key comment corrected**: now accurately describes the
`(tenancyId, sourceTxnId, amount, priorRefunded)` key and its
self-healing behaviour on the Stripe-success → DB-fail retry path (see
Documented limitations above for the remaining hole).
- **Renewal-invoice e2e coverage added**: new test sets up a live-mode
subscription via Stripe webhooks (`subscription_create` +
`subscription_cycle` invoices), refunds the renewal invoice via
`invoice_id`, and asserts the resulting `refund_transaction_id` starts
with `refund:sub-renewal:` and is linked back via `adjusted_by` on the
*renewal* row (not the start row). Plus negative cases:
cross-subscription `invoice_id` → 404, `invoice_id` on a one-time
purchase → SchemaError.

**Second-pass review:**
- **Idempotent sub-cancel error-code string fix**: the Stripe code for
re-cancelling an already-canceled sub is
`subscription_already_canceled`, not `subscription_canceled` — the
previous catch would have re-thrown.
- **End-only sub refund replay rejected**: when `amount=0, revoke=false,
end=true` and the sub is already `cancelAtPeriodEnd` or `endedAt`, throw
SchemaError. Otherwise `readPriorRefundSummary` doesn't see end-only
events and the call would be a forever-no-op accumulating empty refund
rows.
- **`revoke_product=true` with renewal `invoice_id` rejected**: the
product grant lives on the sub-start txn, not on renewal txns — a
renewal-scoped revocation would write a back-reference to a non-existent
entry. Forces admin to revoke against the start invoice (or the default
no-`invoice_id` call).
- **Refund row `id` matches the linkage**: the listing route now returns
the full refund txnId as `id` for `type: "refund"` rows so it matches
`adjusted_by.transaction_id` — the dashboard can join source rows to
their refund rows.
- **+2 e2e tests** for the above (end-only replay rejection,
revoke+renewal rejection).

**Third-pass review:**
- **Dashboard refund dialog seeds state on open**: previously the reset
block lived in `ActionDialog`'s `onOpenChange`, which doesn't fire on
the open transition for a controlled dialog. As a result the dialog
opened with the initial `useState` defaults (`amountUsd = '0'`), and an
admin submitting unchanged on a paid purchase would revoke/end at $0
instead of refunding the charged amount. The seed now runs in the menu
`onClick` before `setIsDialogOpen(true)`.
- **`SUBSCRIPTION_START_PRODUCT_GRANT_ENTRY_INDEX` corrected from 1 →
0**: the constant is persisted as `adjustedEntryIndex` on
product-revocation entries and copied through verbatim by
`mapLedgerEntry`. That mapper drops the hidden
`active-subscription-start` entry, so the public-API layout puts the
product grant at index 0. The prior value of `1` pointed at the
money-transfer entry (or out of range on test-mode subs) through the
public listing.
- **`amountTotal` cap gated behind a USD pre-flight**:
`SubscriptionInvoice` doesn't persist invoice currency, and the previous
code took `invoice.amountTotal` as USD cents directly. Now
`getTotalUsdStripeUnits` (which throws on non-USD pricing) is always
called first; `amountTotal` is only preferred as the actual cap after
that pre-flight succeeds.

## Test plan
- [x] `pnpm typecheck` — 28/28 pass
- [x] `pnpm lint` — 28/28 pass
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal/transactions-refund.test.ts`
— **19/19 pass** (was 14/14 on the original PR; +3 for `invoice_id`
path: renewal refund happy path, unrelated `invoice_id` rejection,
`invoice_id` on OTP rejection; +2 for second-pass: end-only replay
rejection, revoke+renewal rejection)
- [x] curl smoke against
`/api/latest/internal/payments/transactions/refund` — unknown purchase →
404, no-op → 400, negative → 400, sub-revoke-without-end → 400
- [x] **Dashboard UI end-to-end re-run pending** — the original
agent-browser pass ran before the third-pass dialog-seed fix, so any
"money + revoke" submissions may have actually sent `amount_usd = "0"`.
Re-test before un-drafting: open the refund dialog from the menu,
confirm the amount field pre-fills with the charged amount, exercise
validation (negative / exceeds-cap / no-op), and submit both an
end-subscription-only sub refund and a money+revoke OTP refund; verify
bulldozer rows and Prisma `cancelAtPeriodEnd` updates.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Ledger-driven refund flow with stable refund IDs, invoice-aware
refunds, OTP/product-revocation support, tri-state end_action (now /
at-period-end / none), and API responses that include
refund_transaction_id.

* **Bug Fixes / Improvements**
* Deterministic Stripe idempotency, stronger replay protection,
refundable-amount caps, test-mode constraints, and transactions listing
updated to surface refunds.

* **Tests**
* Expanded unit and E2E coverage for new request shape, invoice paths,
money-unit conversion, and edge cases.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1429)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-15 19:29:21 -07:00
Armaan Jain
b526e3b367
Project transfer page redesign (#1309)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Reusable transfer confirmation UI with clear loading, success, and
error states.
* Neon-specific transfer flow added, guiding sign-in, account switching,
or accepting transfers.
* Custom integration transfer flow with streamlined confirm/check
behavior.
* Improved transfer sign-up redirect so users return to the correct page
after auth.

* **Bug Fixes**
  * Consistent messaging for missing/invalid/expired transfer codes.
  * Safer widget “Reload” handling when reset may be unavailable.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---

## Summary

Redesigns the **custom integration** project-transfer confirmation page
(`/integrations/custom/projects/transfer/confirm`) onto the new
design-components system (`DesignCard` + `DesignAlert` + `DesignButton`
+ `DesignInput`). The presentational shell is extracted into a reusable
`ProjectTransferConfirmView` so the route file only handles state + API
calls. The legacy Neon transfer page is split out unchanged into its own
client component to keep the existing Neon × Stack co-branded UI intact.

---

## Screenshots — before and after

> Captured against `http://localhost:8101` at 1280×900. Dev-only
overlays (outdated-version banner, console toast, DEV badge) are hidden
via injected CSS for clarity.

### Custom integration — missing transfer code

Visiting `/integrations/custom/projects/transfer/confirm` with no
`?code=…` query param.

| Before (`dev`) | After (this PR) |
| --- | --- |
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/before-custom-missing__light.png)
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/after-custom-missing__light.png)
|
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/before-custom-missing__dark.png)
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/after-custom-missing__dark.png)
|

Before was a raw `"Error: No transfer code provided."` line. After is a
dedicated `DesignAlert` with an explanation and recovery instructions.

### Custom integration — invalid / expired code (check endpoint fails)

| Before (`dev`) | After (this PR) |
| --- | --- |
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/before-custom-error__light.png)
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/after-custom-error__light.png)
|
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/before-custom-error__dark.png)
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/after-custom-error__dark.png)
|

Before showed the raw backend error string (`Request validation failed
on POST …`). After uses a `DesignCard` with the `ArrowsLeftRightIcon`, a
friendlier "This transfer can't continue" copy in an inline
`DesignAlert`, the Stack Auth logomark in the actions slot, and an
explicit **Close** button to dismiss.

### Neon integration — legacy UI preserved

The Neon page (`/integrations/neon/projects/transfer/confirm`) was
deliberately **not** redesigned — it still uses the Neon × Stack
co-branded card so partner-facing copy/branding stay identical. It's now
its own client component (`neon-transfer-confirm-page.tsx`) instead of
sharing the redesigned one.

| Before (`dev`) | After (this PR) |
| --- | --- |
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/before-neon-error__light.png)
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/after-neon-error__light.png)
|
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/before-neon-error__dark.png)
|
![](https://gist.githubusercontent.com/aadesh18/85342c8cb1890b11e7b44c9baff34283/raw/after-neon-error__dark.png)
|

Same shell on both sides — copy was tightened slightly ("Return to your
Neon dashboard and start the transfer again") and the raw API error
string is gone.

---

## What changed

- **New**
`apps/dashboard/src/components/project-transfer-confirm-view.tsx` —
purely presentational `ProjectTransferConfirmView`. Owns the
design-components shell, the loading spinner, the signed-in vs
signed-out branches of the success state (with `DesignInput` + "Use a
different account" button), and the error / missing-code alerts.
- **New**
`apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx`
— extraction of the legacy Neon UI (Neon logo, Stack logo, "Project
transfer" header, Card / CardContent / CardFooter). Behaviour and copy
match the previous `transfer-confirm-page` exactly when `type ===
"neon"`.
- **Rewritten**
`apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx` —
now hard-coded to the `custom` integration (no more `type` prop), defers
UI to `ProjectTransferConfirmView`, and exports a
`TransferConfirmMissingCodeView` used by the route when `code` is absent
from the URL.
- **Route plumbing**
- `app/(main)/integrations/custom/projects/transfer/confirm/page.tsx` —
renders the redesigned flow, falls back to
`TransferConfirmMissingCodeView` when `code` is missing.
- `app/(main)/integrations/neon/projects/transfer/confirm/page.tsx` —
points at the new dedicated Neon client component.
- **New** `apps/dashboard/src/lib/stack-app-internals.ts` — consolidates
the symbol-keyed `getStackAppInternals(app)` helper (and
`stackAppInternalsSymbol`) into one module with a JSDoc explainer +
runtime type guard, replacing scattered `as any` casts.
- **New** `apps/dashboard/src/lib/transfer-utils.ts` —
`buildTransferSignUpUrl()` helper so the route file + the view stay in
sync on the `/handler/signup?after_auth_return_to=…` query construction.

---

## Bot review follow-ups addressed in this PR

- **Fail-loud assertions for unset handlers** in the success state of
`ProjectTransferConfirmView` (`StackAssertionError` instead of silent
no-op).
- **SSR safety:** moved every `window.location` read into client-only
handlers / `useEffect`s — the page was previously evaluating it at
module load.
- **Friendly error fallback** when the backend `/check` endpoint throws
— replaces the raw `KnownError<…>` message with "This transfer link is
invalid, has expired, or has already been used. Open the original link
from the partner or integrations dashboard, or start the transfer
again."
- **`runAsynchronouslyWithAlert`** around every async `onClick`
(Transfer, Sign in, Switch account, Close) so unhandled rejections
surface to the user.
- **JSX entity bug fix:** `&apos;` was a string-attribute literal, not a
JSX expression — converted to a JSX expression so it renders as `'`.
- **`window.close()` removal** in error state — replaced with a Close
button that resets local state, so users on a fresh tab (no opener)
aren't stuck.
- **`getStackAppInternals` consolidated** — previously three independent
copies (here + two in `projects/page-client.tsx`). Now one helper with a
runtime type guard instead of `as any`, plus a comment explaining the
symbol-keyed SDK escape hatch.
- **Widget-playground reset:** the original change here turned out to
duplicate a deliberate prior fix on `dev` (N2D4, `e68015909d "Fix
lint"`). Reverted in `fe92689eb` so we don't fight that fix.

---

## Notes for reviewers

- **Start with** `components/project-transfer-confirm-view.tsx`.
Everything reviewer-interesting is in the props shape
(`ProjectTransferConfirmUiState` union, `onPrimary` / `onCancel` /
`onSwitchAccount` callbacks). The route file just wires those to the
`getStackAppInternals(app).sendRequest(...)` calls.
- **The Neon page was intentionally not migrated.** Partner-facing
co-branding (Neon logo × Stack logo, "Neon would like to transfer…"
copy) is unchanged — flag it if you think it should be brought onto
design-components too, but the goal of this PR was only the custom flow.
- **API surface is unchanged** — same
`/integrations/custom/projects/transfer/confirm/check` and
`/integrations/custom/projects/transfer/confirm` endpoints, same request
bodies, same redirect to `/projects/{project_id}` on success.
- **Success state isn't in the screenshots** because reproducing it
locally needs a real transfer code (the `/check` endpoint validates the
code against the DB). It uses the same `DesignCard` shell with either a
`DesignInput` showing the receiving account + a "Use a different
account" outline button (signed-in branch), or a `DesignAlert
variant="info"` prompting sign-in (signed-out branch). Worth manually
testing on a real transfer before merging.

## Test plan

- [ ] Visit `/integrations/custom/projects/transfer/confirm` with no
`code` → renders the "transfer link is incomplete" alert (screenshots
above)
- [ ] Visit
`/integrations/custom/projects/transfer/confirm?code=invalid` → renders
the redesigned card with the friendly error inside a `DesignAlert
variant="error"` and a working Close button
- [ ] Trigger a real custom-integration transfer end to end → loading
spinner, success state, "Accept transfer" works while signed in, "Sign
in" deep-links to `/handler/signup?after_auth_return_to=…` while signed
out
- [ ] Visit `/integrations/neon/projects/transfer/confirm?code=…` →
unchanged legacy Neon × Stack co-branded card
- [ ] Light + dark mode visual sanity (screenshots above are the
canonical reference)

---------

Co-authored-by: Aadesh Kheria <kheriaaadesh@gmail.com>
Co-authored-by: aadesh18 <110230993+aadesh18@users.noreply.github.com>
2026-05-15 16:59:51 -07:00
Mantra
9102b3db75
[Feat] Hexclave AI integration: skill, MCP SKILL.md route, docs (#1434)
## Summary
- Adds a `hexclave` SKILL.md pointer skill that fetches the live skill
body on every invocation
- Adds an `/SKILL.md` route on the MCP app that renders the full skill
(CLI usage + docs sidebar generated from `docs.json`)
- Expands `docs-mintlify/guides/getting-started/ai-integration.mdx` with
three install paths (CLI, Skill, MCP) and per-agent config snippets
- Updates `packages/stack-shared/src/helpers/init-prompt.ts` to install
both the MCP server and skill file, with per-project vs global scope
detection

## Test plan
- [ ] `pnpm typecheck`
- [ ] `pnpm lint`
- [ ] Hit the MCP app's `/SKILL.md` endpoint locally and verify it
returns valid markdown with the full docs sidebar
- [ ] Render the updated `ai-integration.mdx` in Mintlify preview and
confirm tabs/cards render

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Rewrote the AI integration guide with complete, user-facing
instructions for connecting Stack Auth to coding agents; removed the
separate MCP setup page and updated site navigation.
* Added the canonical Stack Auth skill content and guidance that clients
should fetch the latest skill at runtime.

* **New Features**
* MCP now serves the canonical Stack Auth skill dynamically and provides
interactive skill responses.
* Init prompts now include full MCP + skill install workflows and scope
guidance.
  * Added a health-check endpoint.

* **Chores**
* Added scaffold and configs for a new skills app (build, dev, lint, and
type settings).

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1434?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 -->
2026-05-15 14:30:23 -07:00
Mantra
5cb9240bc3
refactor(dashboard): unify AI chat surfaces on assistant-ui Thread (#1427)
## Summary
- Replace the bespoke `ai-chat-shared` chat UI (used by ask-ai, the
stack companion widget, vibe coding chat, and the create-dashboard
preview) with the shared `assistant-ui` `Thread` component.
- Extract streaming request/format helpers into a new
`components/assistant-ui/chat-stream.ts` module so each surface only
owns its `ChatModelAdapter`.
- Add a reusable `ToolFallback` for tool-call rendering and delete the
now-unused `ai-chat-shared.tsx` (-1386 / +747 lines net).

Stacked on top of `refactor/data-grid-and-dashboard-surfaces`.

Base: `refactor/data-grid-and-dashboard-surfaces` → Head:
`refactor/assistant-ui-chat-surfaces` · 18 files changed

> Red outlines on the **after** shots mark the unified `assistant-ui`
`Thread` surface in each location.

## Screenshots

### Analytics → Tables — AI Query dialog

| | Before | After |
|---|---|---|
| **Light** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-before-light.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-after-light.png"
width="480" /> |
| **Dark** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-before-dark.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/analytics-tables-ai-after-dark.png"
width="480" /> |

### Stack Companion — chat widget

| | Before | After |
|---|---|---|
| **Light** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-before-light.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-after-light.png"
width="480" /> |
| **Dark** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-before-dark.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/stack-companion-after-dark.png"
width="480" /> |

### Ask-AI command palette (⌘K → Ask AI)

| | Before | After |
|---|---|---|
| **Light** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-before-light.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-after-light.png"
width="480" /> |
| **Dark** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-before-dark.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/ask-ai-cmdk-after-dark.png"
width="480" /> |

### Email editor — embedded chat panel

| | Before | After |
|---|---|---|
| **Light** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-before-light.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-after-light.png"
width="480" /> |
| **Dark** | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-before-dark.png"
width="480" /> | <img
src="https://gist.githubusercontent.com/mantrakp04/323851437f41145aab12a27fb6c392b4/raw/email-editor-chat-after-dark.png"
width="480" /> |

## Notes for reviewers

The four surfaces above all previously shared
`components/commands/ai-chat-shared.tsx` (516 lines, deleted). After
this PR they each own a thin `ChatModelAdapter` and render through
`components/assistant-ui/thread.tsx` + the new `chat-stream.ts` helpers.
Visual differences between **before** and **after** are intentional —
the `assistant-ui` `Thread` brings its own message bubbles,
scroll-to-bottom behaviour, composer, and `ToolFallback` rendering. The
email editor's chat panel is the surface where the behaviour change is
most visible (tool-call rendering now consistent with the rest of the
app).

Heaviest changes (lines):
- `components/stack-companion/ai-chat-widget.tsx` (571)
- `components/commands/ai-chat-shared.tsx` (516, deleted)
- `analytics/tables/ai-query-dialog.tsx` (429)
- `components/vibe-coding/chat-adapters.ts` (400)
- `components/assistant-ui/chat-stream.ts` (284, new)
- `components/commands/ask-ai.tsx` (274)
- `components/assistant-ui/thread.tsx` (115)
- `components/assistant-ui/tool-fallback.tsx` (113)

## Test plan
- [ ] `pnpm lint`
- [ ] `pnpm typecheck`
- [ ] Manually exercise each affected surface: command-center Ask AI,
stack-companion widget, vibe-coding chat, analytics tables AI query,
create-dashboard preview, email editor chat.
- [ ] Verify tool-call chips render consistently across all four
surfaces (uses the new `ToolFallback`).
- [ ] Verify streaming + cancel works on each adapter (`chat-stream.ts`
is shared).
2026-05-15 14:21:00 -07:00
Mantra
c808e23b7d
Data-grid overhaul + session-replays / team-payments dashboard surfaces (#1424)
## Summary

Refactors the dashboard data-grid into a smaller, URL-state-aware
primitive and lands several new dashboard surfaces around it: per-user
session replays, team-level analytics and payments, and pagination for
permission definitions. Also moves session replays out from under
`/analytics` to a top-level surface and adds a
`project_user.last_active_at` index that the new weekly-active metrics
depend on.

**Base:** `dev` → **Head:** `refactor/data-grid-and-dashboard-surfaces`
**Scope:** 91 files, +5,644 / −1,858. Assets in [this
gist](https://gist.github.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7).

## Screenshots

Captured from a local dev server (dashboard at `:8101`, dummy project
seeded with 26 users). Standard viewport **1920×1200**, widescreen
**2560×1440**.

### Users list — data-grid overhaul in context

| Light | Dark |
| --- | --- |
|
![users-list-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/users-list-light.png)
|
![users-list-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/users-list-dark.png)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![users-list-light-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/users-list-light-wide.png)
|
![users-list-dark-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/users-list-dark-wide.png)
|

### User detail — new session-replays card + weekly metrics

| Light | Dark |
| --- | --- |
|
![user-detail-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/user-detail-light.png)
|
![user-detail-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/user-detail-dark.png)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![user-detail-light-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/user-detail-light-wide.png)
|
![user-detail-dark-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/user-detail-dark-wide.png)
|

### Session replays — moved out of `/analytics`

| Light | Dark |
| --- | --- |
|
![session-replays-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/session-replays-light.png)
|
![session-replays-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/session-replays-dark.png)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![session-replays-light-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/session-replays-light-wide.png)
|
![session-replays-dark-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/session-replays-dark-wide.png)
|

### Project permissions — new pagination

| Light | Dark |
| --- | --- |
|
![project-permissions-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/project-permissions-light.png)
|
![project-permissions-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/project-permissions-dark.png)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![project-permissions-light-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/project-permissions-light-wide.png)
|
![project-permissions-dark-wide](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/project-permissions-dark-wide.png)
|

### Other migrated surfaces

| Page | Light | Dark |
| --- | --- | --- |
| Project picker |
![projects-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/projects-light.png)
|
![projects-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/projects-dark.png)
|
| Overview / setup |
![overview-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/overview-light.png)
|
![overview-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/overview-dark.png)
|
| Teams list |
![teams-list-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/teams-list-light.png)
|
![teams-list-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/teams-list-dark.png)
|
| Team permissions |
![team-permissions-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/team-permissions-light.png)
|
![team-permissions-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/team-permissions-dark.png)
|
| API keys |
![api-keys-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/api-keys-light.png)
|
![api-keys-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/api-keys-dark.png)
|

### Scroll behaviour — new data-grid on the users list

| Light | Dark |
| --- | --- |
|
![users-list-scroll-light](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/users-list-scroll-light.gif)
|
![users-list-scroll-dark](https://gist.githubusercontent.com/mantrakp04/01bf8db4c71ec7a119b73d6ee60717a7/raw/users-list-scroll-dark.gif)
|

## What's new

- **`packages/dashboard-ui-components/src/components/data-grid`** —
rewritten. Trimmed `data-grid.tsx` from ~1.7k LOC, split sizing logic
into `data-grid-sizing.ts`, added `use-url-state.ts` for URL-synced
state, and added `data-grid.test.tsx`.
- **Session replays** moved from `…/analytics/replays` to
`…/session-replays` (top-level surface). New `user-session-replays.tsx`
card on the user detail page; new internal `route.tsx` to feed it.
- **Teams** detail page gains `team-analytics.tsx` and
`team-payments.tsx`.
- **Permissions** — new shared `permission-definitions-pagination.ts`
consumed by both project and team permission CRUD routes.
- **Backend** — Prisma migration `add_project_user_last_active_at_idx` +
a `lastActiveAt` index that backs the new weekly-active metrics.
- **Polish** — `editable-input`, `inline-save-discard`, `settings.tsx`,
walkthrough steps, and several data-table components touched in line
with the data-grid rewrite.

## Notes for reviewers

- The data-grid rewrite changes the *shape* of state (now URL-synced),
not just internals. Consumers in
`apps/dashboard/src/components/data-table/*` were updated to match —
please scan those for any missed knobs.
- The `analytics/replays` → `session-replays` rename is git-tracked as
renames; diffs should be small in those files.
- New SDK surface in
`packages/template/src/lib/stack-app/session-replays/index.ts` and
additions in `admin-app-impl.ts` / `server-app-impl.ts` mean OpenAPI
specs (`docs-mintlify/openapi/{admin,client}.json`) regenerate; the diff
is mostly mechanical.

## Test plan

- [ ] `pnpm typecheck` clean
- [ ] `pnpm lint` clean
- [ ] Data-grid unit tests pass (`packages/dashboard-ui-components`)
- [ ] Manual: users list — column resize, sort, filter, paginate; URL
state reflects each change and survives reload
- [ ] Manual: user detail — session-replays card lists replays;
weekly-metrics card renders without `lastActiveAt` index migration
applied (i.e. on a fresh DB) and after applying it
- [ ] Manual: project + team permissions — pagination cursor advances
and stays consistent under search
- [ ] Manual: session-replays top-level page loads; old
`/analytics/replays/...` URL path is no longer expected to be linked
anywhere


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Session Replays app (embedded mode, search, sorting, share links)
  * Tabbed Team pages with Team Analytics and Team Payments dashboards
* Server-backed cursor pagination, debounced search, and infinite-scroll
for teams/users/permissions

* **UX**
* Permission and member tables refresh after edits; permission creation
triggers table refresh
  * Users list supports sorting by last-active

* **Performance**
  * Index added to speed ProjectUser last-active queries

* **Documentation**
  * API/SDK docs updated for pagination and new query params
* Contributor guidance: explicit git-safety rules added (no destructive
git ops without consent)

* **Tests**
  * Added e2e tests for pagination and filtering on list endpoints
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-15 14:16:47 -07:00
Aman Ganapathy
a9623d976a
[Refactor] [Fix] Remove default prod creation (#1350)
With the new bulldozer rework we dont support default products anymore.
Users are encouraged to currently manually handle granting products to
their end users.

We block api requests and new product creations that attempt to set no
price, and we remove any options to set include-by-default. We also
migrate users' existing product snapshots in `Subscriptions`,
`OneTimePurchases`, and `ProductVersions` to have no price set if it's
an include-by-default product. This will make it so that next time a
user goes onto their products page, they will be informed that the
pricing is invalid and it is no longer delivered by default.

Note, however, that these products will still be providing items and the
like to the users who have them.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Migrated legacy product snapshots so missing included-items no longer
break readers.
* Removed deprecated "include-by-default" pricing sentinel; pricing now
requires explicit price entries and write validation rejects the old
sentinel.

* **Chores**
* Simplified dashboard pricing flows: create/edit/save now use explicit
prices and surface an alert when a formerly implicit free plan needs an
explicit $0 price.
* Config overrides and stored data are auto-normalized to explicit price
objects.

* **Tests**
* Updated and added tests covering migration, validation, and switching
behavior for explicit prices.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mantrakp04 <mantrakp@gmail.com>
Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com>
2026-05-15 10:38:33 -07:00
BilalG1
15faf709f3
stack-cli: explicit --cloud-project-id / --config-file across exec, config, project (#1422)
## Summary

Reworks the `stack` CLI surface so the cloud-vs-local choice is
**explicit at every invocation**, removing the global `--project-id` /
`STACK_PROJECT_ID` env var and the local-default `exec` behavior
introduced earlier in this branch.

### `stack exec`
- Removes `--cloud`, `STACK_EXEC_DEFAULT_TARGET`, and the implicit local
default. The CLI now requires **exactly one** of:
  - `--cloud-project-id <id>` — run against the Stack Auth cloud API
- `--config-file <path>` — run against the local emulator project mapped
to that absolute config-file path
- The `--config-file` branch resolves the project id by calling the
existing `GET /api/latest/internal/local-emulator/project` endpoint and
matching `absolute_file_path` client-side. No new backend endpoint
introduced.

### `stack config pull` / `stack config push`
- Both now take `--cloud-project-id <id>` per-command instead of the
global flag / `STACK_PROJECT_ID` env.
- `config pull --config-file` is **optional**: when omitted, the CLI
uses `./stack.config.ts` from the current directory. If neither flag nor
cwd file is present, it exits with a clear hint to pass `--config-file`
or `cd` into a directory containing `stack.config.ts`.

### `stack project list`
- Default (no flags) lists both **cloud and local emulator** projects.
Each entry carries a `target: "cloud" | "dev"` field (text format:
`<id>\t<displayName>\t[<target>]`).
- `--cloud` / `--dev` filter to a single source (mutually exclusive —
passing both errors).
- On the default code path, an unreachable local emulator emits a single
stderr warning (`warning: skipping dev projects — local emulator not
reachable …`) and the command still succeeds with cloud results. With
`--dev` explicit, the unreachable case hard-errors.

### `stack project create`
- Now requires `--cloud` to make the cloud-vs-local choice explicit.
There is no local alternative today; the flag exists to surface the
decision so a future local-project create doesn't silently change
behavior.

### Backend
- Bumps the `LIMIT` on `GET /api/latest/internal/local-emulator/project`
from 20 → 100 so `project list --dev` doesn't silently truncate.

### Refactors (from earlier in this branch, unchanged here)
- Local-emulator paths/ports/PCK polling live in
`packages/stack-cli/src/lib/emulator-paths.ts`.
- Shared local-emulator admin credentials live in
`packages/stack-shared/src/local-emulator.ts`.
- `resolveAuth` / `resolveLocalEmulatorAuth` take an explicit
`projectId: string` (no more `Flags` parameter).
- New `packages/stack-cli/src/lib/local-emulator-client.ts` encapsulates
the GET-and-match flow used by both `exec --config-file` and `project
list --dev`.

## Breaking changes

**Scripts that relied on any of the following must be updated:**

| Removed | Replacement |
| --- | --- |
| Global `--project-id <id>` flag | Per-command `--cloud-project-id
<id>` |
| `STACK_PROJECT_ID` env var | Per-command `--cloud-project-id <id>` |
| `stack exec --cloud` | `stack exec --cloud-project-id <id>` |
| `STACK_EXEC_DEFAULT_TARGET=cloud\|local` | `--cloud-project-id <id>`
or `--config-file <path>` |
| `stack exec` defaulting to local emulator | Explicit `--config-file
<path>` required |
| `stack project create` without a flag | `stack project create --cloud
…` required |

## Test plan
- [x] `pnpm lint` (stack-cli, backend, e2e) — clean
- [x] `pnpm --filter @stackframe/stack-cli typecheck` — clean
- [x] `pnpm --filter @stackframe/stack-cli exec vitest run` — **72/72
passing** (new unit tests: `parseExecTarget`,
`resolveConfigFilePathForPull`, `resolveProjectListSources`,
`formatProjectList`)
- [x] `pnpm test run apps/e2e/tests/general/cli.test.ts` — **73 passing,
4 skipped, 0 failing**. New e2e cases cover:
  - `exec` with neither flag → errors with "Specify a target"
  - `exec` with both flags → errors with "not both"
- `exec --config-file` with missing file / missing PCK / unreachable API
- `exec --config-file` happy path against a real local-emulator backend
(gated on `NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true`)
  - `config pull` cwd fallback to `./stack.config.ts`
- `config pull` with no `--config-file` and no cwd `stack.config.ts` →
errors with `Pass --config-file …`
  - `project list --cloud --dev` together → errors
- `project list` default with unreachable emulator → cloud results +
single stderr warning
  - `project create` without `--cloud` → errors
  - All previously-`--cloud` exec cases ported to `--cloud-project-id`
- [x] Manual smoke: `stack exec --help`, `stack project list --cloud
--dev`, `stack project create` all emit the expected friendly errors /
help text.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* CLI `exec`, `config`, and `project` commands now require explicit
targeting via `--cloud-project-id` (cloud) or `--config-file` (local
emulator).
* `project list` now supports `--cloud` and `--dev` flags to display
projects from both sources with target indicators.
* Enhanced environment variable validation for emulator service ports
with proper fallback handling.

* **Bug Fixes**
* `project list` now gracefully handles unreachable emulator with
warning fallback instead of failure.

* **Tests**
* Expanded test coverage for project targeting, config file resolution,
and emulator connectivity scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-14 17:20:40 -07:00
BilalG1
024da3cacb
[Fix] freestyle-mock honors $PORT, drop server.listen string-patch (#1432)
## Summary

The multi-worker freestyle-mock rewrite
([#1430](https://github.com/hexclave/stack-auth/pull/1430)) hardcoded
`server.listen(8080)`, which collides with qstash inside the
local-emulator container. Supervisord sets `PORT=8180` for
freestyle-mock specifically to avoid this clash, but the new source
ignores `process.env.PORT`.

The local-emulator Dockerfile previously bridged this with a
`server.replace('server.listen(8080)', ...)` string-patch on the
embedded source. The new code is `server.listen(8080, () => { ... })` —
the literal `'server.listen(8080)'` substring no longer matches, so the
replace silently no-ops and freestyle-mock binds 8080. qstash then can't
start (`address already in use: 127.0.0.1:8080` → FATAL), the backend
(which depends on qstash) never comes up, and the emulator smoke test
times out.

Observed in [this
run](https://github.com/hexclave/stack-auth/actions/runs/25832479377):

```
smoke-test: FTL address already in use: 127.0.0.1:8080
smoke-test: WARN exited: qstash (exit status 1; not expected)
smoke-test: INFO gave up: qstash entered FATAL state, too many start retries too quickly
[603s] SMOKE TEST FAILED: backend /health?db=1 did not return 200 within 300s
```

## Changes

- `docker/dependencies/freestyle-mock/Dockerfile`: `server.listen(PORT)`
where `PORT = process.env.PORT || 8080`, plus the startup log reflects
the actual port.
- `docker/local-emulator/Dockerfile`: drop the now-redundant
string-replace for the listen call. The two remaining replaces
(`fs/promises` import + node_modules symlink) are unrelated and kept.

## Test plan

- [ ] QEMU emulator build workflow passes on this branch (smoke test
reaches healthy backend).
- [ ] Verify locally that supervisord's `PORT=8180` is honored by
freestyle-mock and qstash binds 8080 cleanly.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Server listening port is now configurable via PORT (default 8080).
* Local emulator startup adjusted to better handle dependencies and
create a node_modules symlink for smoother local runs.
  * Seed/process transaction timeout increased to 90s for reliability.
* Local database statement timeout changed to 0 (no statement timeout).
* **CI**
  * Added step to enable and validate KVM access during emulator builds.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1432)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-14 15:31:16 -07:00
BilalG1
988505249b
[Revert] team invitation accept email-match check (#1431)
## Summary

Reverts the team-invitation accept email-match check added in #1365 in
response to user friction. The check required the signed-in user to own
the invited email as a *verified* contact channel before accepting,
which rejected legitimate flows where the recipient hadn't verified the
invited email on their account.

- Drops the pre-claim `validate` hook in
`accept/verification-code-handler.tsx` that compared the accepting
user's verified channels to the invited email.
- Drops the `normalizeEmail(body.email)` in `send-code/route.tsx` (only
existed to make the now-removed compare case-insensitive).
- Removes the four e2e tests that asserted the check (mismatch,
does-not-burn, case-insensitive, happy-path).
- Reverts `items.test.ts` invitee sign-up back to bare
`Auth.fastSignUp()`.

## What's preserved

- **`TeamInvitationEmailMismatch`** in
`packages/stack-shared/src/known-errors.tsx` and its plumbing in
`client-interface.ts` / `client-app-impl.ts` / `client-app.ts` —
intentionally kept so the check can be reinstated in a focused follow-up
without re-plumbing the SDK return types.
- **The TOCTOU fix** from the same PR (atomic `updateMany` claim in
`route-handlers/verification-code-handler.tsx` and its
5-parallel-redemption test) is unrelated and untouched.



## Test plan

- [x] `pnpm lint` — clean (28/28)
- [x] `pnpm --filter @stackframe/backend --filter @stackframe/e2e-tests
typecheck` — clean
- [ ] Pre-existing dashboard typecheck failure on
`transaction-table.tsx:347` (`refundEntries`) reproduces on `origin/dev`
— not caused by this PR
- [ ] e2e team-invitations + items + otp sign-in suites

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Simplified team invitation acceptance process by removing strict email
matching requirements, allowing users to accept invitations more
flexibly.

* **Tests**
  * Updated team invitation tests to reflect simplified acceptance flow.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1431)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-13 17:15:11 -07:00
Aman Ganapathy
c0871e64b2
[Feat(tests)] multi-worker freestyle mock (#1430)
### Context
Lots of flakiness comes from email polling leading to timeouts. This
usually happens when freestyle mock cannot service requests in time. Old
mock was single threaded and so clogged up by a lot of requests.

### Summary of Changes
A multiworker system should be better.
2026-05-13 16:32:00 -07:00
Madison
2cf0f6f981
[Apps] Adding support app alpha and dogfooding (#1368)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Support app: inbox UI to create, view, reply, and manage conversations
(status, priority, assignee, tags, internal notes).
* Dashboard pages: Conversations and Support Settings; feedback can
create managed conversations.
* Public/internal APIs for listing, creating, updating, and fetching
conversation details; client-side helpers.

* **SLA**
* Configurable first/next response targets, urgency classification, and
timing logic.

* **Data**
* New conversation persistence (conversations, entry points, messages)
and migration tests; preserves conversations on user/team deletion and
anonymizes sender data.

* **Tests**
  * Unit, migration, and end-to-end tests added.

* **Documentation**
  * Updated docs describing conversation model and workflow rules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 11:36:11 -05:00
Aman Ganapathy
3385d6e2b0
[Fix] recover stale external db requests (#1428)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
Failures between claiming and the deletion of outgoing requests from the
handler can leave requests stale and never clean them up. Some of these
requests may also have duplicates that are fresh in the outgoing queue.
These requests need to be deleted or retried.

It's important to still log the stale requests to sentry so the root
cause can be investigated.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved detection and recovery of stale outgoing requests; telemetry
now records precise reset/deleted counts and includes sampled affected
IDs.
* Added an early fast path to skip unnecessary external calls when there
are no pending requests.

* **Refactor**
* Consolidated stale-request handling into a dedicated helper and
optimized recovery logic; poller telemetry now includes claim-limit
attributes.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1428)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-12 17:55:44 -07:00
Aman Ganapathy
4648fc1899
[Feat] new scripts on migrate/seed/init run for internal (#1421)
### Context

One script grants free plan to any team which is a customer of the
internal project who doesnt have it already.
We also want to migrate our users (internal) to the latest version of
their products.

Needed because some subs on dev right now dont have a plan. And internal
isnt using latest version of its own growth plan.

### Describing the Paths we want to Account for

1. Users on production who currently don't have a plan should get free
plans, since this script is run with every migrate
2. Users on production should get the latest version of each plan of
ours. So a forced migration to latest version of internal project plans
3. No other project's products/product lines should be affected. They
will continue to have product versioning
4. 2 should apply to test mode subscriptions as well, on top of stripe
subscriptions. All of them should be refreshed
5. Internal project itself should get latest version of its own growth
plan
6. If the bulldozer write fails, we should be able to recover on next
migration (this should already be handled by init bulldozer script,
because it checks if prisma db and bulldozer db are out of sync)
7. if the regenerate or backfill fail, we should be able to recover just
by rerunning the script
8. Product version table should not balloon. No table should really
balloon



### What I've tested on local
1. Put in 1000 db subscription rows, made them all stale and then ran
the regen script. It took about 6 minutes to update all of them, and it
was idempotent so rerunning it again did nothing.
2. With proper stripe keys I switched off of test mode on the internal
app, granted a product to a new team and updated the product's item
list. At this point I checked and the new team had the outdated version
of the product. Then I ran the regen script and the new team was moved
to latest product version.
3. Tried the above with the internal team's growth plan too and it
worked as well.
4. Backfill actually grants free plan


### Deployment strategy in prod
Run the backfill and the regen scripts once each after your migrations
on the prod db.
`pnpm db:backfill-internal-free-plans` will make sure every team has a
free plan at least if they dont have an existing plan (and it is
idempotent).
After that, run `pnpm db:regen-internal-subscriptions-to-latest` which
will migrate every user to the latest version of their plan (i.e latest
snapshot). This should also be idempotent.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Automated backfill to grant internal free plans to qualifying billing
teams.
* Regeneration tool to refresh internal subscription snapshots to the
latest product versions.

* **Chores**
* Added CLI commands and package scripts to run backfill and regen jobs.
  * Database init now runs payment initialization before backfill/regen.

* **Tests**
* Integration and unit tests added/updated to validate backfill,
regeneration, and free-plan idempotency.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1421)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-12 16:05:45 -07:00
Konstantin Wohlwend
d2030e826b Unhandled promise rejections no longer kill the whole server if not in development 2026-05-12 13:14:12 -07:00
aadesh18
76023af9d6
Custom Dashboards Versioning fix (#1418)
This PR fixes the versioning error that we ran into for custom
dashboards. Now if the latest version of the packages does not work, we
fall back to the version that is one patch below the latest version. We
log this into sentry. If the fall back doesn't work either, we log that
into sentry as well and show the user an error message.

Apart from that, I also made changes to ensure dashboards with older
versions of the dashboard-ui-component package would still work. Each
dashboard now stores the version it was created with, as a comment at
the top of its source code, and we use that version when loading the
dashboard. When a dashboard gets edited via the AI chat, we re-stamp it
with the latest version of the package so it stays up to date.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved error handling and reporting for dashboard load failures;
host surfaces structured dependency errors for faster diagnostics.
* Added automatic fallback loading for missing resources to reduce load
failures.
* Fixed page height calculation so pages align correctly with the
viewport.

* **New Features**
* Generated and editor-provided dashboard code is now stamped with the
app version for clearer provenance.

* **UI/UX Improvements**
* Clearer, more informative error messages when custom dashboard loading
encounters issues.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1418)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-12 12:35:13 -07:00
Konstantin Wohlwend
efa2153d47 Improve project overview weekly users 2026-05-12 11:45:22 -07:00
Konstantin Wohlwend
e61c70d3c1 Update implementation 2026-05-12 11:14:30 -07:00
Mantra
e50358710a
fix(tests): use sql.json in onboarding migration test and refresh metrics snapshot (#1420)
## Summary

Two small test-maintenance fixes that came up while running the suite:

- **Onboarding migration test**
(`apps/backend/prisma/migrations/20260420000000_add_project_onboarding_state/tests/default-and-updates.ts`):
switch the JSON insert from `\${JSON.stringify(onboardingState)}::jsonb`
to `\${sql.json(onboardingState)}`. This matches the pattern used by
every other migration test in the repo (see
`20260214000000_fix_trusted_domains_config/tests/*`) and lets the
`postgres` driver handle serialization and parameter binding
consistently rather than relying on a manual `::jsonb` cast.
- **Internal metrics snapshot**
(`apps/e2e/tests/backend/endpoints/api/v1/__snapshots__/internal-metrics.test.ts.snap`):
update `active_users_by_country.AQ` to list `mailbox-2` before
`mailbox-1`. The `should return metrics data with users` test signs in
`mailbox-1` (mailboxes[0]) into AQ first, then later signs `mailbox-2`
(mailboxes[1]) into AQ, so sorted by `last_active_at_millis desc`
`mailbox-2` should come first. The snapshot now matches that ordering.

No production code is touched — both changes are limited to test
fixtures.

## Test plan

- [ ] `pnpm -C apps/backend test run` (migration tests)
- [ ] `pnpm -C apps/e2e test run internal-metrics` (snapshot test)
- [ ] `pnpm lint`
- [ ] `pnpm typecheck`


Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* No user-facing behavior changed; test flows made more robust and less
flaky (migration validation, metrics ingestion polling, CLI expiry
checks, failed-emails digest expectations).
* **API / Documentation**
* CLI auth default expiration reduced from 2 hours to 2 minutes (updated
OpenAPI defaults and related test expectations).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 10:06:29 -07:00
Konstantin Wohlwend
9ff2c13f8d Add functionality to restrict or unrestrict users 2026-05-11 18:58:33 -07:00
Konstantin Wohlwend
80a26ca15d chore: update package versions 2026-05-11 10:10:47 -07:00
Mantra
227dac6567
feat(dashboard): add weekly users metrics for projects (#1412)
- Introduced a new API endpoint to fetch weekly and daily user metrics
for managed projects.
- Updated the dashboard to utilize this new endpoint, replacing the
previous daily active users data.
- Created a new component to visualize weekly users metrics in the
project cards.
- Refactored existing components to accommodate the new data structure
and ensure proper rendering of user activity charts.

This change enhances the analytics capabilities of the dashboard,
providing better insights into user engagement over time.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* New internal endpoint providing per-project weekly user totals and
7-day daily activity series.

* **Updates**
* Dashboard and project cards switched from DAU to weekly user metrics;
main metric shows weekly users and label reads "users/wk".
* Charts now display weekly-user-aware sparklines alongside daily
activity.

* **Tests**
* Added unit tests covering weekly aggregation and daily-series merging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-08 11:00:50 -07:00
Mantra
261d8923d4
stack-cli: support self-hosted URLs and tighten CLI auth polling (#1419)
## Summary
- **Self-hosted CLI**: read `STACK_API_URL` / `STACK_DASHBOARD_URL` from
env in `stack-cli` so the published CLI can talk to self-hosted Stack
Auth installs without a custom build. The existing
`STACK_CLI_PUBLISHABLE_CLIENT_KEY` override is kept as-is.
- **Docker example**: surface the three CLI-relevant vars in
`docker/server/.env.example` so self-host operators see them.
- **Tighter polling-code TTL**: default `2h -> 2min`, max `24h -> 15min`
for the CLI auth polling code. The code is only valid while a user is
actively waiting in `stack login`, so a tight window limits the blast
radius of a leaked code.
- **Raw-SQL poll handler**: convert
`apps/backend/src/app/api/latest/auth/cli/poll/route.tsx` from
`prisma.cliAuthAttempt.*` to raw SQL targeted at the tenancy
source-of-truth schema, matching the pattern already used by the
initiate handler in
`apps/backend/src/app/api/latest/auth/cli/route.tsx`.

## Test plan
- [ ] `pnpm typecheck`
- [ ] `pnpm lint`
- [ ] `pnpm test run` (focus on CLI-auth tests if any)
- [ ] Manual: `stack login` against a local backend
  - polling code now expires after ~2 minutes by default
- `waiting` / `success` / `used` / `expired` branches still return
correct status codes and bodies
- [ ] Manual: published `stack-cli` against a self-hosted backend with
`STACK_API_URL` / `STACK_DASHBOARD_URL` set, end-to-end login


Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* More robust CLI authentication polling with atomic database updates to
prevent races; returns explicit statuses (waiting/expired/used/success)
and provides the refresh token on success.

* **Changes**
* Default CLI auth token TTL reduced to 2 minutes and capped at 15
minutes.
* Anonymous refresh token is considered present only when not null; null
expiry is treated as not-expired.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 11:00:03 -07:00
Mantra
68ae6d1f1c
[codex] Add TanStack Start SDK integration (#1399)
## Summary

- Adds the generated `@stackframe/tanstack-start` workspace package
registration.
- Adds TanStack Start platform macros/dependencies to the SDK template
and generator.
- Adds TanStack Start cookie/token-store support plus the handler SSR
guard needed by Start.

## Scope

This intentionally excludes Dashboard V2 routes, hooks, components, app
shell logic, and dashboard API type additions. Those stay in the
existing dashboard PR/branch.

## Validation

- `pnpm install --lockfile-only --ignore-scripts`
- `pnpm install --ignore-scripts`
- `pnpm -C packages/template lint
src/components-page/stack-handler-client.tsx src/lib/cookie.ts
src/lib/stack-app/apps/implementations/client-app-impl.ts`

Package typecheck was attempted with `pnpm -C packages/template
typecheck`, but the clean worktree lacks generated package declaration
outputs for workspace dependencies such as `@stackframe/stack-shared`
and `@stackframe/stack-ui`. Per repo instructions, package
builds/codegen are not run by agents.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* TanStack Start integration: published SDK package, example demo app,
dashboard onboarding flow, framework-aware CTAs/docs, and a
TanStack-specific provider for client-only auth routes.
* Improved client/server auth: safer runtime guards and consistent
cookie/token-store behavior across SSR and client.

* **Documentation**
* New Integrations guide and expanded getting-started/setup docs with
TanStack Start examples and env/key guidance.

* **Chores**
* Template, build, tooling, and demo config updates to support the new
platform.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-08 10:59:16 -07:00
aadesh18
acc646cb0b
stack-cli: cloud/local init flow, auto-create on empty projects, post-setup next-steps (#1383)
### Summary

Reworks `stack init` UX, adds Sentry error reporting to the CLI,
polishes the emulator start flow, and overhauls the local-emulator
dashboard's "Open config file" dialog.

#### `stack init` flow
- **New top-level flow.** Drops the old "link existing vs. create new
local" fork. `init` now asks *where* to create the project — "Stack Auth
Cloud" or "Local". Adds a new `create-cloud` mode that logs the user in,
creates a cloud project, mints keys, and writes `.env` — no round-trip
through the dashboard.
- **Conditional emulator-install warning.** The "Local" choice label
only shows "(requires local emulator installation, ~1.3gb storage
required)" when the QEMU image isn't already on disk; otherwise it shows
"(emulator already installed)". Driven by a new
`isEmulatorImageInstalled()` helper in `commands/emulator.ts`.
- **Auto-create on zero-projects.** When the link-from-cloud path hits
an empty project list, the CLI now prompts *"You don't have any Stack
Auth projects yet. Would you like to create one?"* and, on yes, runs the
same flow as `stack project create`. Skips the pointless "select a
project" prompt when we just created one.
- **MCP-server notice.** Before invoking the coding agent, the CLI
announces that it's also registering the Stack Auth MCP server
(`mcp.stack-auth.com`) so the agent can answer Stack-specific questions
going forward.
- **Local-emulator env header.** When `writeProjectKeysToEnv` runs in
`local` mode it writes a 3-line comment header above the keys explaining
they're emulator-only and only valid while the emulator is running.
- **"What's next" footer.** After setup finishes, prints a short
orientation block: where the sign-up/sign-in routes live
(`/handler/sign-up`, `/handler/sign-in`), how to start the local
emulator (for `create` mode), a dashboard deep link for cloud projects
(respects `STACK_DASHBOARD_URL`), and a docs link.

#### Sentry error reporting (`lib/sentry.ts`, `index.ts`,
`tsdown.config.ts`)
- New `lib/sentry.ts` initializes `@sentry/node` with PII scrubbing
(Stack key prefixes, JWTs, home-dir paths, sensitive field names like
`token`/`secret`/`password`/`dsn`).
- DSN is baked at build time via a tsdown `define` sentinel
(`__STACK_CLI_SENTRY_DSN__`) — no DSN in source, no runtime env-var
dependency for installed users. CI sets `STACK_CLI_SENTRY_DSN_BUILD`
before `pnpm build`.
- Disabled when `NODE_ENV=development` or `CI`. No user opt-out.
- Wired into `main()`'s catch (only for unexpected errors —
`CliError`/`AuthError` still print and exit cleanly) plus
`uncaughtException` and `unhandledRejection` handlers via a
`handleFatal` helper.

#### `stack emulator start` welcome
- After a fresh start (not when reusing a running VM, not when
`--config-file` keeps stdout JSON-only), prints a short "Emulator is up"
block with service URLs (dashboard / backend / inbucket) and common
commands (`status`, `stop`, `reset`, `run`).

#### Local-emulator dashboard "Open config file" dialog
The dialog at `http://localhost:26700` (when no project is loaded) used
to be a single text input asking for an absolute path, with no
explanation of where that path comes from.

**Backend**
(`apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`):
- POST is now tolerant of directory paths or paths that don't end in
`.ts`/`.js`/`.mjs` — it appends `stack.config.ts` and creates the file
if missing (`writeConfigToFile` mkdir's parents). Lets users paste a
project folder instead of hunting for the config file.
- New GET endpoint returns up to 20 most-recent `LocalEmulatorProject`
rows joined with their display names, sorted by `updatedAt` desc. Same
`isLocalEmulatorEnabled()` + client-auth gating as POST.

**Dashboard**
(`apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx`):
- Title changed to "Open your Stack Auth project". Description now
explicitly ties the file to `stack init`: *"Point the local dashboard at
the `stack.config.ts` in your project. If you just ran `stack init`, it
was created at the root of that project."*
- Added: *"Don't have one yet? Paste your project folder path instead
and we'll create stack.config.ts for you."*
- Recent-projects list (clickable rows that prefill the input) fetched
from the new GET endpoint when the dialog opens.
- OS-specific copy-path tip below the input (macOS ⌥-Copy as Pathname,
Windows Shift+RC Copy as path, Linux `realpath`).
- "Open project" button is disabled when the input is empty.
- All error paths (empty input, non-absolute path, server errors,
exceptions) surface via destructive toasts instead of throwing.

Why no native file picker: browsers do not expose absolute filesystem
paths from `<input type="file">`, drag-and-drop, or the File System
Access API. The backend requires an absolute path, so a Finder-style
picker isn't possible from a web page. The recent list + OS tips are the
workaround.

### Goal

The previous `init` flow dead-ended new users: if you had no project you
got an error telling you to go create one in the dashboard and come
back. The happy path also forced a choice between "link existing" and
"create local emulator" — not the question most users are trying to
answer. The emulator dashboard's open-project dialog had similar
friction: an unexplained path field with no recall of previously-opened
projects. And the CLI silently swallowed unexpected errors with no
telemetry. This branch makes the first-run path work end-to-end from the
terminal, gives the emulator dashboard a usable open-project surface,
and turns CLI crashes into actionable bug reports.

### How to review

- Start with `packages/stack-cli/src/commands/init.ts` — the whole
user-facing flow lives in `runInit`. Mode dispatch at the top,
`handleCreateCloud` is the new cloud branch, `printNextSteps` is the
footer, the MCP notice prints right before `runClaudeAgent`.
- `packages/stack-cli/src/lib/sentry.ts` is small and self-contained;
the sentinel-replacement contract is in `tsdown.config.ts`'s `define`
block. Confirm `dist/index.js` contains zero `__STACK_CLI_SENTRY_DSN__`
occurrences after a build with the env var unset, and the actual DSN
host after a build with it set.
- `packages/stack-cli/src/commands/emulator.ts` —
`printEmulatorWelcome()` is the welcome block;
`isEmulatorImageInstalled()` is the new exported helper used by
`init.ts`.
-
`apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`
— the directory-tolerance branch is in the POST handler around the
`looksLikeConfigFile` check; the GET handler is appended at the bottom.
-
`apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx`
— dialog markup, recent-list fetch effect, `pathCopyTip` memo, and the
toast-based error handling in `handleOpenConfigFile`.
- Non-interactive (CI) paths stay strict: empty-project list still
errors with a pointer to `stack project create --display-name`. No
surprise project creation in CI.
- No tests. The CLI has no harness for the interactive flow;
verification is manual.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Recent local emulator projects listed in the config dialog for quick
selection.
* New CLI create-cloud mode and --display-name flag; interactive cloud
project creation and clearer next steps.
* Emulator start shows a welcome banner with service URLs when a new
instance starts.

* **Improvements**
* Config dialog UX, validation, error-toasting, and platform-aware copy
refined; “Open project” disabled for empty/invalid paths.
* CLI: centralized interactive project creation and improved fatal error
handling.

* **Chores**
  * Sentry added and initialized for CLI error reporting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Bilal Godil <bg2002@gmail.com>
2026-05-08 10:47:49 -07:00
aadesh18
6eaf49237f
Add fix command registration and update agent UI label handling (#1387)
Adds a fix command to the stack cli 


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a CLI "fix" command to submit Stack Auth errors (flag, stdin, or
interactive), confirm before applying changes, show a customizable
progress label, and produce a final markdown report with Error, Files
changed, and Solution.
* Added a CLI "doctor" command to analyze projects (framework override,
output directory, JSON output), run framework-specific checks, validate
env and config, and exit non-zero on failures.
* **Tests**
  * Added comprehensive end-to-end tests for the doctor command.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-07 18:33:43 -07:00
Mantra
647883c7ac
Move MCP server into a standalone apps/mcp app (#1405)
## Summary

Splits the Stack Auth MCP server out of `apps/backend` and into a
dedicated Next.js app at `apps/mcp/`, served on port `:42` (suffixed via
`NEXT_PUBLIC_STACK_PORT_PREFIX`) and exposed in production at
`https://mcp.stack-auth.com/mcp`. The backend no longer carries the MCP
transport route; clients now point at the new host.

Base: `dev` → Head: `chore/move-mcp-to-a-sep-app`
Scope: 34 files, +1425 / −353

## What changed

- **New app** `apps/mcp/` — standalone Next.js + `@vercel/mcp-adapter`,
with:
- `src/app/api/internal/[transport]/route.ts` — MCP transport handler
(moved from backend)
- `src/app/mcp/route.ts`, `src/app/route.ts` — public landing + setup
page
  - `src/app/health/route.ts` — health check
  - `src/mcp-handler.ts`, `src/setup-page.ts`, `src/analytics.ts`
- **Backend** drops
`apps/backend/src/app/api/internal/[transport]/route.ts` (−105) — MCP
code is gone from the backend image.
- **Dashboard** install hint updated to point at
`https://mcp.stack-auth.com/mcp` (was `/`).
- **Dev launchpad** gets an MCP tile so the new service shows up
alongside the rest of the local stack.
- **CI** workflows (`db-migration-backwards-compatibility`,
`e2e-api-tests*`) start the MCP service in the background before running
tests.
- **Docs** (`docs-mintlify`, `docs/`) and `init-stack` / `init-prompt`
updated to reference the new URL.
- **E2E** `apps/e2e/tests/backend/endpoints/api/v1/internal/mcp.test.ts`
reworked to hit the new host; `helpers.ts` and env files gain an MCP
base-URL var.

## Visuals

### New `apps/mcp` setup page (`https://mcp.stack-auth.com/`)

The standalone app's root now serves a self-contained MCP setup guide
with per-client instructions (Cursor, VS Code, Codex, Claude Code,
Claude Desktop, Windsurf, ChatGPT, Gemini CLI):

![MCP setup
page](https://gist.githubusercontent.com/mantrakp04/892b45cb1b4e0d65d6c73a0c8771fe7d/raw/mcp-setup-page.png)

### Dev launchpad now lists the MCP service

New tile at port suffix `:42`, importance 2, alongside Backend /
Dashboard / Demo app:

![Dev launchpad with MCP
tile](https://gist.githubusercontent.com/mantrakp04/892b45cb1b4e0d65d6c73a0c8771fe7d/raw/launchpad-light-full.png)

## Notes for reviewers

- The MCP transport endpoint moved path: it was mounted under
`/api/internal/[transport]` in the backend; in the new app it's at the
same path but on the dedicated host. The public-facing URL is
`https://mcp.stack-auth.com/mcp`.
- `apps/mcp` ships its own PostHog analytics client (`src/analytics.ts`)
so the backend doesn't have to proxy events for it anymore.
- Port allocation: `${PORT_PREFIX}42` (default `8142` in dev). Picked to
fit the existing dev-launchpad importance-2 row.
- No DB migrations.

## Test plan

- [x] `apps/mcp` builds and `pnpm dev` serves on `:8142`
- [x] Dev launchpad renders the new MCP tile (screenshot above)
- [x] MCP setup page renders client tabs (screenshot above)
- [x] E2E `mcp.test.ts` updated to hit the new host
- [ ] CI green on `e2e-api-tests*` and
`db-migration-backwards-compatibility` workflows (they were touched to
start the MCP service)
- [ ] `init-stack` / `mcp.ts` install flow lands users on the new URL


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Standalone MCP app added with a public /mcp endpoint and health check.
  * MCP appears in the dev-launchpad apps list.

* **Documentation**
* MCP endpoint updated to https://mcp.stack-auth.com/mcp in all setup
guides and installer snippets.
* Setup page enhanced with detailed client install tabs and
instructions.

* **Chores**
  * MCP service integrated into CI/e2e workflows and local env configs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-07 15:22:44 -07:00
Konstantin Wohlwend
7acbd8d56d Improved StackAssertionError error logging 2026-05-07 13:29:01 -07:00
Konstantin Wohlwend
d69773c9df Retry OAuth refreshes 2026-05-06 16:52:40 -07:00
aadesh18
616d805443
layout fix (#1408)
This PR fixes a layout bug 


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Updated dashboard top-panel sizing to use viewport-aware height for a
more consistent fit across screen sizes.
* Improved dark-mode spacing to prevent clipping and ensure content
remains fully visible without extra scrolling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-06 16:21:20 -07:00
Konstantin Wohlwend
7f35ae7d54 Fix migration tests
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
2026-05-06 15:18:48 -07:00
Konstantin Wohlwend
5ccd8dfd38 Update GitHub URL 2026-05-06 15:17:01 -07:00
Aman Ganapathy
bc6347e3c3
[Feat]: set flag to disable billing (#1417)
### Context
There are some kinks to work out with deploying plan limits onto prod,
so we'd like to disable it temporarily.

### Summary of Changes
We update all call sites of the item quantity things with a flag based
check. Idea is when flag is set to true, it should function as if there
are no limits.
2026-05-06 14:58:06 -07:00
Konsti Wohlwend
765b0f4e29
New setup (#1413) 2026-05-06 12:03:06 -07:00
Konstantin Wohlwend
440c18c894 chore: update package versions 2026-05-06 11:43:03 -07:00
Konstantin Wohlwend
2e41fde9c2 _useSession now refreshes tokens more aggressively 2026-05-06 11:36:10 -07:00
BilalG1
8901a93b55
Rename STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY to STACK_INTERNAL_PROJECT_SECRET_SERVER_KEY (#1415)
## Summary
- Renames the env var `STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY` to
`STACK_INTERNAL_PROJECT_SECRET_SERVER_KEY` everywhere it is used (20
occurrences across 8 files), covering backend env files, the Prisma seed
script, runtime config, and the docker entrypoint/local-emulator
scripts.
- Mirrors the prior publishable-client-key rename in #1411.

## Test plan
- [x] `pnpm lint`
- [x] `pnpm typecheck`
- [ ] Verify local emulator still boots with the renamed variable
- [ ] Verify any deploy/CI configs that set the old name are updated
alongside this change

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated internal environment variable naming for API key management
and server configuration consistency across backend systems, Docker
deployment, and local development setup.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-06 10:35:08 -07:00
BilalG1
24245ae54e
Rename STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY to STACK_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY (#1411)
## Summary
- Renames the env var
`STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY` to
`STACK_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY` everywhere it is used
(24 occurrences across 9 files), covering backend env files, the Prisma
seed script, runtime config, and the docker entrypoint/local-emulator
scripts.

## Test plan
- [x] `pnpm lint`
- [x] `pnpm typecheck`
- [ ] Verify local emulator still boots with the renamed variable
- [ ] Verify any deploy/CI configs that set the old name are updated
alongside this change

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated internal environment variable naming for consistency across
backend configuration files and deployment scripts.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-06 09:10:45 -07:00
BilalG1
b0812c8808
feat(analytics): gzip event batch body to bypass adblockers (#1407)
## Summary

The `POST /api/latest/analytics/events/batch` endpoint was being dropped
by content-blocking browser extensions (adblockers) because the JSON
request body literally contains the substring `$click`. Many filter
lists pattern-match on tokens like that and silently kill the request —
analytics events from anyone with an adblocker enabled never reached our
backend.

This PR encodes the request body so keyword-matching filters can't see
those tokens, while keeping the URL path unchanged (only the body was
being matched here) and keeping older SDK clients working.

## Approach

- **Client**: gzip the JSON payload via the browser-native
`CompressionStream("gzip")` API and POST it as
`application/octet-stream`. Falls back to plain JSON if
`CompressionStream` isn't available (very old browsers / non-browser
runtimes).
- **Server**: a yup `.transform()` on the body schema detects an
`ArrayBuffer`/`Uint8Array` input, gunzips it, and `JSON.parse`s before
normal schema validation runs. The existing JSON path is untouched, so
requests from older SDK versions in the wild continue to work without
changes — and all existing schema-error snapshot tests still pass
verbatim.
- **Safety**: hard caps on compressed (1 MB) and decompressed (8 MB)
sizes guard against zip-bomb shaped abuse. `node:zlib`'s
`maxOutputLength` enforces the latter at the C++ layer.

Bonus: gzip also gives a meaningful bandwidth win — click/page-view
events compress very well — and keepalive bodies (which have a 64 KB cap
in browsers) get more headroom.

## Files

- `apps/backend/src/app/api/latest/analytics/events/batch/route.tsx` —
body schema gains `.transform()` that gunzips binary inputs; size limits
added; everything else unchanged.
- `packages/stack-shared/src/interface/client-interface.ts` —
`sendAnalyticsEventBatch` now routes through a new module-level
`encodeAnalyticsBody` helper that gzips and switches Content-Type. Same
outer signature; encoding is internal.
- `apps/e2e/tests/backend/backend-helpers.ts` — `niceBackendFetch` gains
optional `rawBody`/`rawContentType` params so tests can send non-JSON
payloads. Existing JSON callers unaffected.
-
`apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts`
— adds two tests:
  - happy path: gzipped binary body returns `inserted: 1`
  - sad path: garbage bytes return 400

## Out of scope (intentional)

- **URL path renaming**: not all adblockers match on `/analytics/`, but
some do. We're shipping the body fix first and will revisit if requests
still get blocked after deployment.
- **Encryption**: gzip is enough to defeat keyword filters. Encryption
adds key-management cost with no real adversary.
- **SDK regen**: only `client-interface.ts` (in `stack-shared`) was
touched; `event-tracker.ts` (the caller) is unchanged because it already
passes a JSON string. No `pnpm -w run generate-sdks` needed.

## Test plan

- [x] `pnpm typecheck` — green
- [x] `pnpm lint` — green
- [ ] Manually verify in dev: enable adblocker, click around with
analytics enabled, confirm batch requests now go through
- [ ] Spot-check ClickHouse `analytics_internal.events` shows the
expected rows
- [ ] Run the new e2e tests (`pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/analytics-events-batch.test.ts`)
and confirm both new cases plus all preexisting snapshots pass
- [ ] Confirm the JSON back-compat path still works by hitting the route
with the existing JSON-body curl/test payloads

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Analytics batch uploads now accept gzipped binary payloads; clients
can send compressed bytes and the server will detect and decompress.
* Client sender can gzip event batches (falls back to JSON) and uses
keepalive to choose JSON vs compressed bytes.

* **Bug Fixes**
* Malformed, non-gzip, or overly-large compressed payloads now return a
clear 400 response.

* **Tests**
* Added E2E and unit tests plus test-helper support for raw/gzipped
request bodies and encoding behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-05 16:38:39 -07:00
Madison
185bddec9e
[Dashboard] Redefine the user page with tabs and updated UI (#1351)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Tabbed user profile with Activity (30-day analytics, KPIs, daily
chart, top lists, recent events), Payments (transactions, subscriptions,
product/item balances) and an activity heatmap sidebar.
* New internal user-activity API and admin-facing activity hook; admin
API client can fetch per-user activity.

* **UI/UX Improvements**
* Unified menus, cards and tables; inline editable user details with
accept/revert; metadata editor validates JSON; country-code input has
draft editing; tabs support optional icons.

* **API**
* Transactions endpoint and admin transaction queries now support
optional customer-scoped filtering.

* **Tests**
  * End-to-end coverage for the user-activity endpoint.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<img width="1326" height="752" alt="image"
src="https://github.com/user-attachments/assets/97c04dca-db59-4357-98b1-8eae5a7a3673"
/>

<img width="1142" height="251" alt="image"
src="https://github.com/user-attachments/assets/e1aa44fc-0d7e-436d-90a5-c7cb15155e24"
/>

<img width="1170" height="1125" alt="image"
src="https://github.com/user-attachments/assets/bf6659fd-a9b5-4ae6-a13d-dab9956ad650"
/>
2026-05-05 17:09:09 -05:00
Konstantin Wohlwend
7a54e82865 Revert globe coloring to old algorithm 2026-05-05 09:28:48 -07:00
Mantra
3a5153f4db
feat(payments): collect 0.9% platform fee on every stripe money movement (#1378)
## Summary

Charges the platform 0.9% on both legs of each transaction on
non-internal projects.

- **Charge leg** — rides along via Stripe's native
\`application_fee_amount\` / \`application_fee_percent\` params on the
PaymentIntent / Subscription.
- **Refund leg** — Stripe's default reverses our charge-leg fee on
refund, netting us zero. We disable that with \`refund_application_fee:
false\`

## Refs
-
https://docs.stripe.com/api/subscriptions/create#create_subscription-application_fee_percent
-
https://docs.stripe.com/api/payment_intents/object#payment_intent_object-application_fee_amount

---------

Co-authored-by: nams1570 <amanganapathy@gmail.com>
2026-05-05 09:22:53 -07:00
Aman Ganapathy
c01c052ac9
[Refactor][Feat] Implement Plan Limits for Hard-and-Soft Item Caps (#1215)
### Suggested Review Areas
Please see `plans.ts` and `seed.ts` to verify whether the item caps are
where they should be. Outside of that, each commit should be atomic so
stepping through the commits should give you an idea of how I
implemented each limit.

### Discussion
Something to discuss: when a user cancels team/growth we regrant free
fine, but any extra-seats they had just keeps billing. So they end up
paying ~$29/mo per extra-seat on top of free's 1 seat, which is strictly
worse than just staying on team. This surfaced while manually testing
this PR, we only enforce the add-on base requirement at purchase time,
nothing cascades on cancel. Should we cascade cancel add ons?

### Context
Now that we have a stable suite of products for stack-auth, we want to
limit the items under each product a customer has access to based on
their plan. So for example, a free plan user has a certain amount of
emails they can send out each month, and so on. We try to implement
limits in this PR.

### Summary of Changes
Implemented hard limits for dashboard admins, analytics per-query
timeouts, sent email monthly capacity, events, and session replays.
Implemented a soft cap for auth users (where if there's a signup beyond
the limit, we log it to sentry so we can manually choose to email that
user/team).

For auth users, we do not block new user sign ups once plan limit has
been hit. We also don't degrade or impact the customer experience. It
logs to sentry and it is up to us to take manual action to email the
user to upgrade the plan. Also, implementation wise, we count all the
users across all the projects for this team and compare it to their plan
item limit, rather than debiting items like we do for other approaches.
As a soft cap, this should be fine plus this is a better source of
truth.

For email capacity, we operate a monthly limit of emails. Once this is
hit, no more emails can be sent until the next month/ a plan upgrade.
These emails will be treated as a send error, so they can be manually
resent once the capacity is reset. With respect to the `email-queue`
state engine, they go from `SENDING`->`SERVER_ERROR`, hooking into the
existing state engine flow, with an external error that shows it's
because of the rate limit. This is cleaner than inventing a new state
that is identical for all intents and purposes to `SERVER_ERROR`. We
check in processSingleEmail since that maps to the sending state.

For analytics query timeouts, the backend route accepts a timeout
parameter with the request. The way we implement the timeout for each
query is by taking the `min(request_timeout,plan_timeout)` and using
that. This determines how long a query can run for.

For analytics events, there are server-side events (like refresh token
refreshes or sign up rule triggers) and client side events (like page
views or clicks). When these events occur, they are written to the
events table in clickhouse. We choose to implement a hard cap for the
total events, not just server side or client side. Once the cap is hit,
we stop storing the events and display a banner on the analytics page. A
different banner renders when we are at >=80% of total plan capacity.

For session replays, we stop creating new session replays when the limit
is hit. Old replays can still have chunks appended to them. The source
of truth here is the session replay table- a new replay corresponds to a
new row in the table. We have similar banners as to the events.

Dashboard admins should be 4 for both team and unlimited.

#### Implementation Caveats

For debiting items across these limits, we now use `tryDecreaseQuantity`
at the beginning. This means we debit first if possible before
conducting the action (like writing events to clickhouse). In practice,
this means that if clickhouse fails, then the user is debited for
something that doesn't happen. However trying to build a refund
workaround would be very clunky, and also, clickhouse is reliable. For
debits that are very small in the order of things (say, 200 items on a
100k plan), it doesn't mean much.

For emails, we don't debit items if it's a retry. This prevents the user
for being charged multiple times for effectively one email.


### UI Changes
The only UI changes in this PR are having certain banners render in
analytics when a customer is approaching/ is at their monthly limit of
session replays or events.


### Out of Scope for this PR
We do not have metered pricing yet, so events/session replays/ email use
beyond the limits cannot be charged yet. This is why for this
implementation, we rely on hard and soft caps.
We do not implement payment per-transaction pricing yet. That is
deferred to a followup PR.
The UI for the onboarding call will be set up as part of the overall
onboarding flow which doesn't exist yet, so it has been deferred.
Since the UI for the dashboard home page and project/account settings is
currently being reworked, finding a better spot for plan upgrades is not
handled in this PR.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Session replays added as a monthly included entitlement; onboarding
calls added to Team/Growth plans. Dashboard banners warn about
analytics-event and session-replay limits. Projects page adds extra-seat
flow and improved invitation error handling.

* **Behavior Changes**
* Monthly renewal semantics for emails-per-month and analytics-events;
analytics query timeouts now respect plan limits and are clamped. Email
sends, analytics events, and new session creation are blocked when
quotas are exhausted. Growth plan seats set to 4.

* **Tests**
* E2E and unit tests added to verify quota enforcement and free-plan
regranting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com>
2026-05-04 18:25:13 -07:00
BilalG1
c69a27017b
fix team invitation email check + verification code TOCTOU (#1365)
## Summary

Two authorization fixes in the backend. Both are pre-existing in `dev`
and were found during a security audit of `apps/backend/src`.

### 1. Team invitation accept — email not validated


[`team-invitations/accept/verification-code-handler.tsx`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/app/api/latest/team-invitations/accept/verification-code-handler.tsx)
destructured the invited email as `{}` and only used `data.team_id` +
the accepting `user`. Any signed-in user in the tenancy who possessed
the 45-char code could join the team as themselves — the invitation was
not actually bound to the email it was addressed to.

**Attack scenarios that work without this fix**
- Forwarded invitation email (shared inbox, assistant inbox,
auto-forward rules).
- Screenshot of the invitation link pasted into Slack / Notion.
- Insider with server-access reading the email outbox (`GET
/api/latest/emails/outbox` returns rendered `html` +
`variables.teamInvitationLink`).
- Stale invite still sitting in spam after the invitee forwarded it
elsewhere.

**Fix.** The accept handler now requires that the accepting user owns
the invited email as a *verified* contact channel on their account.
Matches the invariant already used by the "list invitations for me"
endpoint
([`team-invitations/crud.tsx:41-66`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/app/api/latest/team-invitations/crud.tsx#L41-L66)).
Rejections return a new `TEAM_INVITATION_EMAIL_MISMATCH` (403) error.

### 2. Verification-code handler TOCTOU


[`route-handlers/verification-code-handler.tsx`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/route-handlers/verification-code-handler.tsx)
had a classic read-then-write TOCTOU:

```ts
const verificationCode = await prisma.verificationCode.findUnique(...);
if (verificationCode.usedAt) throw new KnownErrors.VerificationCodeAlreadyUsed();
// ... validation ...
await prisma.verificationCode.update({ data: { usedAt: new Date() } });  // unconditional
return await options.handler(...);
```

Five concurrent requests with the same code all pass the `if (usedAt)`
gate, all mark the code used, all run the post-handler. For OTP sign-in
the handler calls `createAuthTokens` which writes a fresh
`projectUserRefreshToken` row per call — so **one OTP → N refresh
tokens**. `auth/sessions/current` only revokes by `id: refreshTokenId`
and there is no bulk-revoke for passwordless users (only password change
in
[`users/crud.tsx:1210`](https://github.com/stack-auth/stack-auth/blob/dev/apps/backend/src/app/api/latest/users/crud.tsx#L1210)
does `deleteMany`). A phished OTP therefore becomes a
session-persistence primitive.

**Fix.** Replace the unconditional `update` with a conditional
`updateMany({ where: { …, usedAt: null } })` executed before
`options.handler`; if `count === 0` the race was already lost and we
throw `VERIFICATION_CODE_ALREADY_USED` (409). This also benefits MFA
sign-in and passkey sign-in, which share the same handler.

## Changes

| File | Change |
|---|---|
| `team-invitations/accept/verification-code-handler.tsx` | Require
verified contact channel matching `method.email` |
| `route-handlers/verification-code-handler.tsx` | Atomic `updateMany`
claim gated on `usedAt: null` |
| `stack-shared/src/known-errors.tsx` | New
`TeamInvitationEmailMismatch` (403) |
| `e2e/.../team-invitations.test.ts` | Two new tests (mismatch + happy
path) |
| `e2e/.../auth/otp/sign-in.test.ts` | One new test: 5 parallel
redemptions of one OTP → 1× 200 + 4× 409 |

## Test plan

- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts` —
27/27 pass
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/auth/otp/sign-in.test.ts` —
12/12 (+ 4 pre-existing `it.todo`)
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/auth/password` — 33/33 (+ 7
pre-existing todos)
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/contact-channels` — 24/24
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/auth/passkey
apps/e2e/tests/backend/endpoints/api/v1/auth/mfa` — 16/16
- [x] `pnpm --filter @stackframe/backend typecheck` — clean
- [x] `pnpm --filter @stackframe/backend lint` + `pnpm --filter
@stackframe/stack-shared lint` — clean

## Notes

- The broader "plaintext credentials in DB + Sentry logs every header"
finding from the same audit is **not** in this PR — a scrubber for
`Sentry.setContext` request headers + unit tests is prepared on a local
stash and will go out as a separate PR.
- The team-invitation fix does not require any config change; fresh
signups via the OTP / password flows that set `primary_email_verified:
true` during creation already land the user with a verified channel
matching the invited email, so the happy path is unaffected.

### Follow-up review (Codex)

Addressed in follow-up commit `954cddb`:
- **Finding 1 (High)**: mismatched invite acceptance was consuming the
invitation before rejecting. Moved the email-ownership check into the
pre-claim `options.validate` hook so a wrong-email attempt leaves
`usedAt` untouched and the real recipient can still redeem. New test
asserts this end-to-end.
- **Finding 3 (Medium)**: invitation stored `body.email` raw but contact
channels are stored via `normalizeEmail`, so case-varied invites (e.g.
`Alice@Example.com`) wouldn't match a `alice@example.com` channel.
`send-code` now normalizes on storage and `accept` normalizes on compare
for back-compat with already-issued invites. New test covers the
mixed-case path.
- **Finding 2 (partial)**: added `expiresAt > now` to the atomic claim
predicate for the boundary case where a code expires between the read
and the claim. The reviewer's broader point about the `attemptCount`
rate-limit check being non-atomic with its own increment **pre-dates
this PR** (it reads the in-memory `verificationCode.attemptCount` from
line 150, not a fresh read) and exists independently of the `usedAt`
TOCTOU I'm fixing here. Tracking that as a separate follow-up so this PR
stays scoped to the two originally-flagged issues.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Invite acceptance now requires the invitee’s verified, normalized
(case‑insensitive) email; mismatches return HTTP 403
(TEAM_INVITATION_EMAIL_MISMATCH).
* Client APIs now surface the new email-mismatch error alongside
verification errors.

* **Bug Fixes**
* OTP verification codes are now guarded against parallel double‑redeem
so only one request succeeds.

* **Tests**
* Added E2E tests for invitation email validation, non‑consuming
rejection, case‑insensitive matching, and OTP concurrency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-04 17:13:03 -07:00
BilalG1
9f79bfbe5c
fix(dashboard): collapsed email editor height; sandbox email-preview iframes (#1406)
## Summary

Two small dashboard fixes bundled together.

### 1. Email editor renders with zero height

The email template/theme/draft pages render `VibeCodeLayout`, whose
mobile and desktop root wrappers used `h-full`. The dashboard shell's
`<main>` (`sidebar-layout.tsx:750`) has no explicit height — its flex
parent uses `items-start`, so `<main>` shrinks to its content rather
than stretching. With no definite height up the chain, every `h-full`
along the way (sidebar-layout's inner div, the `data-full-bleed`
wrapper, `VibeCodeLayout`'s own root) resolves to `auto`, and since the
editor's content lives inside absolutely-positioned `ResizablePanel`s,
the wrapper collapses to ~0.

**Fix:** anchor `VibeCodeLayout`'s root wrappers to
viewport-minus-header instead of `h-full`. The values match what
`sidebar-layout.tsx:738` already uses for the sticky sidebar (`3.5rem`
light / `6rem` dark for the floating header card). With a definite
height at the top, the existing `flex-1` chains inside `VibeCodeLayout`
resolve correctly without any layout/architecture refactor in the
surrounding dashboard shell.

```diff
-<div className="flex flex-col h-full w-full overflow-hidden md:hidden">
+<div className="flex flex-col h-[calc(100dvh-3.5rem)] w-full overflow-hidden md:hidden">

-<div className="hidden md:flex flex-col h-full w-full overflow-hidden">
+<div className="hidden md:flex flex-col h-[calc(100vh-3.5rem)] dark:h-[calc(100vh-6rem)] w-full overflow-hidden">
```

Trade-off: the editor knows the dashboard header is `3.5rem` (`6rem`
dark). The same numbers are already hardcoded in `sidebar-layout.tsx`,
so this isn't a new coupling.

### 2. Sandbox the email-preview iframes

`EmailPreviewContent` and `EmailPreviewEditableContent` rendered
user-authored template HTML in iframes with no `sandbox` at all. With
`srcDoc`-rendered iframes treated as same-origin by default, that meant
any `<script>` (or `onerror=`, `javascript:` URL, etc.) inside a
template could read the dashboard's cookies/localStorage and call the
API as the viewing admin.

Set `sandbox="allow-scripts"` on both iframes:
- Iframe is forced into a unique opaque origin → no access to parent
cookies, `localStorage`, `sessionStorage`, or DOM.
- No `allow-same-origin`, so credentialed fetches to the dashboard API
don't carry the user's session (cookies aren't sent to a third-party
opaque origin under default `SameSite=Lax`; cross-origin responses also
unreadable due to CORS).
- No `allow-top-navigation` / `allow-forms` / `allow-popups` → template
can't redirect the parent tab, submit forms, or open windows.
- `allow-scripts` is required so the inline scripts we inject
(link-click prevention; the WYSIWYG editor that drives the `postMessage`
flow at `email-preview.tsx:413-435` and `:625-672`) can actually run.
Without it, the editor itself was broken and links navigated freely.

Note: `allow-scripts allow-same-origin` together would be equivalent to
no sandbox at all (the iframe could rewrite its own `sandbox` attribute
and escape), so we deliberately omit `allow-same-origin`.

**Residual risk (not addressed in this PR):** a malicious template
script can still `postMessage` a fake `stack_edit_commit` to the parent
— the parent's `e.source === iframeWindow` check passes because the
script *is* running in that iframe. The viewing admin would silently
apply attacker-chosen source-code edits on save. That's a cross-admin
UI-redress concern, not token exfiltration, and is best fixed with a CSP
nonce on the injected script (so user template `<script>` tags can't run
at all). Tracking as a follow-up.

## Test plan

- [ ] Open an email template editor — verify the preview, code panel,
and chat panel are all visible at full height (light + dark mode).
- [ ] Same for an email theme editor and an email draft editor in the
`draft` stage.
- [ ] Resize the window vertically — editor should fill the viewport
below the header without overflowing past the bottom.
- [ ] Click a link inside the rendered preview — should not navigate
(link-click prevention script works under `allow-scripts`).
- [ ] In edit mode, hover an editable text region, click to edit, type a
change, hit ✓ — change should round-trip through `postMessage` and
update the source.
- [ ] Sanity check: paste `<script>document.title='pwned'</script>` (or
`<img onerror=...>`) into a template, render preview — parent tab
title/cookies/etc. should be untouched (script runs in opaque origin,
can't reach parent).
2026-05-04 15:37:33 -07:00
Konstantin Wohlwend
0ab2654051 chore: update package versions 2026-05-04 15:33:33 -07:00
Mantra
9ec32cef60
re-enable posthog recordings (#1404) 2026-05-03 23:38:46 -07:00
Mantra
d2f2fb0e42
[codex] Fix preview dummy payments customer types (#1398)
## Summary

Fixes preview dummy payments seed data so seeded products and items
match their team-scoped product lines.

## Root Cause

The preview seed configured `workspace` and `add_ons` product lines with
`customerType: "team"`, but the products inside those lines (`starter`,
`growth`, and `regression-addon`) were configured as `customerType:
"user"`. Environment override writes validate against the rendered
branch config, so unrelated environment updates could fail with a
product/product-line customer type warning.

## Changes

- Mark preview dummy payments products and included items as
team-scoped.
- Export the dummy payments setup helper for focused validation.
- Add a regression test that validates the generated branch payments
override has no config override errors or incomplete config warnings.

## Validation

Passed in the original checkout with dependencies installed:

- `STACK_SKIP_TEMPLATE_GENERATION=true pnpm exec vitest run --config
vitest.config.ts src/lib/seed-dummy-data.test.ts --reporter=verbose
--maxWorkers=1 --minWorkers=1`
- `pnpm -C apps/backend lint src/lib/seed-dummy-data.ts
src/lib/seed-dummy-data.test.ts`
- `pnpm -C apps/backend typecheck`

The temporary clean worktree used for this PR did not have
`node_modules`, so dependency-backed commands were not rerun there.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Strengthened payment product configuration with tighter typing and
validation
* Normalized product customer types (switched relevant dummy data from
user to team) for consistency

* **Tests**
* Added tests validating dummy payments configuration and
branch/override validation

* **Documentation**
* Added Q&A documenting a configuration validation failure mode and
required consistency for dummy payments data
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-01 09:44:30 -07:00
Mantra
e831972c4c
Move internal MCP server to backend, use Mintlify MCP for docs tools (#1389)
## Summary
- Move the `/api/internal/[transport]` MCP route from the docs app to
the backend, so the public `ask_stack_auth` MCP tool is served from the
same origin as the AI query API it proxies to.
- Replace the bespoke docs-tools HTTP client in
`apps/backend/src/lib/ai/tools/docs.ts` with an `@ai-sdk/mcp` client
that talks to Mintlify's generated MCP server. The backend AI agent now
consumes Mintlify's lower-level search/fetch tools directly instead of
going through the docs app.
- Swap `STACK_DOCS_INTERNAL_BASE_URL` for `STACK_MINTLIFY_MCP_URL`
(defaults to the Mintlify-hosted MCP URL).
- Move the `@vercel/mcp-adapter` dependency from `docs` to
`apps/backend`.

## Test plan
- [ ] `pnpm typecheck`
- [ ] `pnpm lint`
- [ ] e2e: new
`apps/e2e/tests/backend/endpoints/api/v1/internal/mcp.test.ts` covers
`tools/list` and validation on `tools/call`
- [ ] Manual: hit `POST /api/internal/mcp` on the backend and confirm
`ask_stack_auth` is listed and callable
- [ ] Manual: confirm backend AI agent docs tools resolve via the
Mintlify MCP URL

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Backend docs tooling now uses a Mintlify MCP server for documentation
tools and discovery.

* **Chores**
* Development environment variables updated to point to the Mintlify MCP
endpoint.
* Backend dependency added to support MCP integration; docs package
dependency removed.

* **Tests**
* Added end-to-end tests for the internal MCP endpoint and tool
validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-29 09:45:52 -07:00
aadesh18
ed8961069c
fix(dashboard): UI bug fixes (#1377)
## Summary

Rolling PR for dashboard UI bug fixes. Each fix is appended to the **Fix
log** below with before/after screenshots. This PR stays open until we
batch-merge or split.

---

## Fix log

### 1. Hide Alpha/Beta stage badges in onboarding "Select apps" tooltip

**Bug:** On the new-project onboarding, hovering an app card showed an
"Alpha" or "Beta" stage badge next to the app name in the tooltip. These
shouldn't be surfaced on the onboarding step.

**Fix:** Removed the stage badge from the onboarding app-card tooltip
only. The "Required" badge is preserved, and stage badges on other
surfaces (app management, app store, command palette) are unchanged.

#### Before / After — Beta (Payments)

| Before | After |
| --- | --- |
|
![before-payments](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-hover-beta-payments.png)
|
![after-payments](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-hover-beta-payments.png)
|

#### Before / After — Alpha (Onboarding)

| Before | After |
| --- | --- |
|
![before-onboarding](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-hover-alpha-onboarding.png)
|
![after-onboarding](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-hover-alpha-onboarding.png)
|

---

### 2. Eliminate full-page flash when advancing onboarding steps

**Bug:** Moving between onboarding steps (e.g. Configure authentication
→ Select email theme) briefly blanked out the entire page — only the
navbar remained visible for roughly two seconds — before the next step
rendered. It felt like a complete browser reload.

**Fix:** Contained the suspension inside the wizard. A local Suspense
boundary around the onboarding page means that when any data cache
refresh fires during the step advance, the suspension no longer bubbles
up to the site-wide loading indicator. The step-advance state update is
also marked as a React transition, so the current step stays rendered
until the next step is ready to commit. Net effect: the previous step is
visible throughout the save, then the next step swaps in without a blank
frame.

#### Before — full blank flash mid-transition

| Auth step (start) | Mid-transition (blank) | Email theme step (end) |
| --- | --- | --- |
|
![before-auth](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-01-auth-step.png)
|
![before-flash](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-02-suspense-flash.png)
|
![before-email](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-03-email-theme-step.png)
|

#### After — previous step stays visible, no blank frame

| Auth step (start) | Mid-transition (auth stays visible) | Email theme
step (end) |
| --- | --- | --- |
|
![after-auth](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-suspense-01-auth-step.png)
|
![after-mid](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-suspense-02-mid-transition.png)
|
![after-email](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-suspense-03-email-step.png)
|

---

### 3. Add a subtle back arrow to the onboarding timeline

**Bug:** The only way to return to a previous step in the new-project
onboarding was to click one of the tiny completed-step dots at the
bottom of the page — not discoverable, and easy to miss.

**Fix:** Added a small muted left-arrow next to the timeline dots.
Clicking it advances back one step. It's absolute-positioned so the dots
stay perfectly centered, and it hides itself on the first step (where
there's nothing to go back to).

#### Before / After — Select apps step

| Before — dots only | After — back arrow next to the dots |
| --- | --- |
|
![before-back-arrow](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-back-arrow-apps.png)
|
![after-back-arrow](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-back-arrow-apps.png)
|

### 4. Unify onboarding step styling — cards everywhere, no
glassmorphism

**Bug:** Step-to-step styling in the onboarding was inconsistent. The
Config and Email-theme steps used a glassmorphic surround
(`backdrop-blur`, translucent whites) while the other steps used solid
cards. Advancing from auth to email made it look like the visual
language had changed mid-flow.

**Fix:** Dropped the glassmorphic variants from the onboarding wizard.
The config-choice option cards, the email-theme container, and the
`ModeNotImplementedCard` surround all now use the same solid card
treatment (`bg-white/90` light, `bg-white/[0.06]` dark, with subtle
ring). One consistent surface across every step.

#### Before / After — Config choice step

| Before — glassmorphic | After — solid card |
| --- | --- |
|
![before-glass-config](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-glass-config-choice.png)
|
![after-glass-config](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-glass-config-choice.png?v=2)
|

#### Before / After — Email theme step

| Before — glassmorphic | After — solid card |
| --- | --- |
|
![before-glass-email](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-glass-email-theme.png)
|
![after-glass-email](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-glass-email-theme.png)
|

### 5. Add "Copy prompt" button on the project setup page

**Bug:** The post-project-creation setup page surfaces a terminal
command for every framework (Next.js, React, JS, Python), but there was
no one-click handoff for users who drive their setup through an AI
agent. Users had to manually copy the command, figure out whether the
Stack Auth MCP server got registered, and add it themselves if not.

**Fix:** Added a compact **✦ Copy prompt** button at the top-right above
the steps list. Clicking it copies a framework-aware prompt to the
clipboard — the prompt tells the user's AI agent to run the install
command for the currently-selected framework, then verify the Stack Auth
MCP server (`stack-auth`, transport `http`,
`https://mcp.stack-auth.com/`) is registered in its client config and
add it manually if the install didn't.

#### Before / After — Project setup page

| Before — no AI handoff | After — "Copy prompt" at the top-right |
| --- | --- |
|
![before-copy-prompt](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/before-copy-prompt-setup.png)
|
![after-copy-prompt](https://gist.githubusercontent.com/aadesh18/948fc31499e8bca4943199173cbe0e00/raw/after-copy-prompt-setup.png)
|

### 6. Disable email theme cards while the onboarding step is saving

**Bug:** On the "Select an email theme" step, the theme cards stayed
clickable after clicking Continue. Because we keep the previous step
visible during the step-advance transition (fix #2), users could click
through to a different theme mid-save — the server would then commit
whatever selection was active at click time, not the one on screen when
Continue was pressed.

**Fix:** Added `disabled={saving}` to the email theme buttons, matching
the same pattern the config-choice, apps-selection, and auth-setup steps
already follow. Added `disabled:cursor-not-allowed disabled:opacity-60`
so users get a clear visual signal that the cards are locked while the
save is in flight.

---

<!-- Append new fixes above this line. Template:
### N. <title>
**Bug:** …
**Fix:** …
#### Before / After
| Before | After |
| --- | --- |
| ![before](…) | ![after](…) |
-->

## Test plan

- [ ] Load the new-project onboarding "Select apps" step and hover every
app card — no Alpha/Beta badge appears.
- [ ] Hover a required app — "Required" badge still appears.
- [ ] Confirm app management tooltips, app store detail page, and
command palette still show stage badges (out of scope for this PR).
- [ ] Drive the onboarding from Configure authentication to Select email
theme — the auth panel stays rendered throughout the save phase and the
email panel swaps in without the site-wide loading indicator or a blank
content area.
- [ ] Repeat for other step transitions (Config → Apps, Apps → Auth,
Email → Domain, Domain → Payments) — same seamless behavior.
- [ ] From any step after Config, the back arrow appears to the left of
the dots. Clicking it goes back one step. On the first step, the arrow
is not rendered.
- [ ] Walk through every onboarding step. Container surface is visually
consistent across steps — no glassmorphic/card mismatch between Config,
Apps, Auth, Email Theme, Payments.
- [ ] On the project setup page, the "Copy prompt" button appears above
the steps (top-right). Clicking it copies the prompt for the
currently-selected framework (Next.js / React / JS / Python) and shows a
success toast.
- [ ] On the "Select an email theme" step, click Continue — the three
theme cards become visibly dimmed (`opacity-60`, `cursor-not-allowed`)
for the duration of the save and don't respond to clicks. Once the next
step renders they stop being visible anyway.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added back navigation to onboarding wizard steps.
* Added "Copy prompt" button for framework-aware terminal commands with
MCP verification.
  * Added loading indicator during asynchronous operations.

* **UI/UX Improvements**
  * Updated card styling for unselected options.
  * Disabled email theme selection during save operations.
  * Removed stage badges (Alpha/Beta) from app cards.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 18:49:28 -07:00
Mantra
e2dc5f5ee0
[codex] fix OAuth redirect contract (#1393)
## Summary

- Route browser OAuth redirects through the configured `redirectMethod`
instead of hardcoded `window.location` calls.
- Keep OAuth redirect APIs pending after navigation starts, including
custom redirect methods.
- Add `cliAuthConfirm` handler URL metadata and custom-page prompt
coverage.
- Update SDK spec text for browser OAuth callback and `returnTo`
behavior.

## Root Cause

OAuth helpers previously combined URL construction with direct browser
navigation. That bypassed configured redirect methods and made it too
easy for public redirect APIs to resolve after navigation started.

## Impact

Browser SDK consumers get consistent redirect behavior across built-in
and custom navigation methods. `returnTo` is handled as the
post-callback destination while the OAuth callback URL remains fixed to
the configured handler route.

## Validation

- `pnpm test run packages/template/src/lib/auth.test.ts`
- `pnpm test run apps/e2e/tests/js/oauth.test.ts`
- `pnpm -C packages/template lint`
- `pnpm -C apps/e2e lint`
- `pnpm -C packages/template typecheck`
- `pnpm -C apps/e2e typecheck`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added CLI authorization confirmation page/flow for terminal-based
auth.
* Added optional returnTo parameter for OAuth to control post-auth
redirects.
* Exposed configurable redirect behavior so apps follow the chosen
redirect method.

* **Bug Fixes**
* OAuth callback now uses app navigation/queued redirects and shows a
fallback link instead of forcing location.assign.

* **Tests**
* Added unit and e2e tests covering OAuth URL generation, scope
handling, and CLI auth confirmation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 16:33:59 -07:00
Madison
5e5cfdec4f
[Dashboard][Backend][SDK] - Adds sharable session replay ids. (#1294)
# Shareable Session Replay Links
Adds the ability to share individual session replays via unique, direct
URLs.

https://www.loom.com/share/1e3298a19b114fc38af4bc43dcd5ec48

## What changed
- New admin endpoint — GET /api/v1/internal/session-replays/:id
- Fetches a single session replay by ID with user metadata (display
name, primary email) and chunk/event counts
- Returns 404 if the replay doesn't exist
- Admin-only access, consistent with the existing list endpoint
## New standalone replay page —
/projects/:projectId/analytics/replays/:replayId
- Thin server page wrapper that passes the replay ID to the existing
PageClient
- PageClient detects standalone mode via initialReplayId prop and
fetches replay metadata directly instead of loading the full session
list
- Sidebar is hidden; the replay viewer takes the full width
- "Back to all replays" link shown under the page title
## Copy link button
- Moved from per-session sidebar items to the replay viewer header (next
to the settings gear)
- Copies a direct URL to the currently selected replay
## SDK plumbing
- AdminGetSessionReplayResponse type in stack-shared
- getSessionReplay() on StackAdminInterface, StackAdminApp interface,
and _StackAdminAppImplIncomplete
## Tests
- Happy path: fetch single replay by ID with inline snapshot
- 404 for nonexistent replay ID
- 401 for non-admin access (client and server)
## Test plan
- [ ] Open /analytics/replays, select a replay, click the link icon in
the header — verify URL is copied to clipboard
- [ ] Paste that URL in a new tab — verify the standalone replay page
loads and plays the correct replay
- [ ] Verify "Back to all replays" link navigates back to the list page
- [ ] Verify the original /analytics/replays list page still works as
before (selecting, filtering, pagination)
- [ ] Run pnpm test run session-replays


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Backend: internal endpoint to fetch a single session replay with user
info, millisecond timestamps, and chunk/event counts.
* Admin SDK/App: added response type and admin method to retrieve a
single session replay; admin app maps response into the app model.
* Dashboard: standalone session-replay page, UI adjustments for
standalone mode, and a “copy replay link” button.

* **Tests**
* Added end-to-end tests for retrieval, not-found, and access-control
scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 17:57:07 -05:00
Mantra
0207721f68
fix(dashboard): improve analytics replay replayer lifecycle (#1349)
## Summary

Improves reliability of the session replay viewer on the project
analytics replays page by tracking replayer staleness, coordinating
pause/restart with effects, and cleaning up instances to avoid leaks.

## Changes

- Add `isReplayerStale` and wire replayer lifecycle into
`executeEffects` so playback and pause stay in sync with the replayer
state.
- Pause/restart and teardown when the replayer becomes stale or
unmounts.

## Test plan

- [ ] Open a project’s **Analytics → Replays**, load a replay, scrub
timeline, pause/resume, and switch replays; confirm no stuck playback or
console errors.
- [ ] `pnpm lint` / `pnpm typecheck` on touched packages if CI does not
cover.

## Notes

Small `CLAUDE.md` tweak included in the same commit.

Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Disabled automatic session recording in the dashboard.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 15:29:54 -07:00
Mantra
a82097db62
refactor(dashboard): use getEnabledAppIds on metrics page (#1394)
## Summary
Uses the shared `getEnabledAppIds` helper from `@/lib/apps-utils`
instead of manually filtering installed apps with `typedEntries` on the
project metrics page.

## Why
Keeps enabled-app logic consistent with other dashboard code paths and
slightly reduces duplication.

## Test plan
- [ ] Smoke: open project metrics / overview and confirm installed
app-dependent UI (e.g. analytics) still behaves as before.

Made with [Cursor](https://cursor.com)
2026-04-28 13:16:33 -07:00
Mantra
65d87a4836
Dashboard: DataGrid refactor + layout (stacked on overview-revamp) (#1338)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary

Stacked on `overview-revamp` (now rebased against `dev`). Introduces a
first-class `DataGrid` component in
`@stackframe/dashboard-ui-components`, migrates every dashboard table
off the legacy `DesignDataTable` / hand-rolled `<Table>` pattern to it,
and ships a matching dashboard design guide.

Since the last writeup the `DataGrid` runtime has been substantially
rewritten: the virtualizer now supports `rowHeight="auto"` with
`estimatedRowHeight`, every column can opt into `cellOverflow: "wrap"`,
the toolbar + header stick under a configurable `stickyTop`, and the
seeded dummy data has been fleshed out so the migrated surfaces render
with realistic density. The AI-analytics prompt was also extended with
full schema docs for the auth / team / email / payments tables so
natural-language queries produce better SQL.

**Base:** `dev` → **Head:** `ui-fixes-minor`
**Scope:** 39 files, ~+6.5k / -2.4k

## Screenshots

Captured against the seeded Demo Project on the local dashboard
(`admin@example.com` via mock GitHub OAuth). Viewport: **1920×1200**
(standard) and **2560×1440** (widescreen). Assets hosted in [this
gist](https://gist.github.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9).

### Overview — revamped metrics + line chart

| Light | Dark |
| --- | --- |
|
![overview-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/overview-light.jpg)
|
![overview-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/overview-dark.jpg)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![overview-wide-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/overview-wide-light.jpg)
|
![overview-wide-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/overview-wide-dark.jpg)
|

### Users — DataGrid with seeded rows

| Light | Dark |
| --- | --- |
|
![users-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/users-light.jpg)
|
![users-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/users-dark.jpg)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![users-wide-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/users-wide-light.jpg)
|
![users-wide-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/users-wide-dark.jpg)
|

### Transactions — new DataGridToolbar + sticky chrome

| Light | Dark |
| --- | --- |
|
![transactions-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/transactions-light.jpg)
|
![transactions-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/transactions-dark.jpg)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![transactions-wide-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/transactions-wide-light.jpg)
|
![transactions-wide-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/transactions-wide-dark.jpg)
|

### Teams

| Light | Dark |
| --- | --- |
|
![teams-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/teams-light.jpg)
|
![teams-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/teams-dark.jpg)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![teams-wide-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/teams-wide-light.jpg)
|
![teams-wide-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/teams-wide-dark.jpg)
|

### Email Outbox

| Light | Dark |
| --- | --- |
|
![email-outbox-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-outbox-light.jpg)
|
![email-outbox-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-outbox-dark.jpg)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![email-outbox-wide-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-outbox-wide-light.jpg)
|
![email-outbox-wide-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-outbox-wide-dark.jpg)
|

### Payments — Customers

| Light | Dark |
| --- | --- |
|
![payments-customers-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/payments-customers-light.jpg)
|
![payments-customers-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/payments-customers-dark.jpg)
|

Widescreen:

| Light | Dark |
| --- | --- |
|
![payments-customers-wide-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/payments-customers-wide-light.jpg)
|
![payments-customers-wide-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/payments-customers-wide-dark.jpg)
|

### Sticky behaviour — scrolled views

Grids scrolled down ~600px. The page header is still pinned, and the
`DataGrid` toolbar + column header row stay put under it (backdrop-blur
+ `stickyTop` offset) while the virtualized body rows scroll past.
Compare the scrolled view against the top-of-page view above.

| Page | Light | Dark |
| --- | --- | --- |
| Users |
![users-light-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/users-light-scrolled.jpg)
|
![users-dark-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/users-dark-scrolled.jpg)
|
| Teams |
![teams-light-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/teams-light-scrolled.jpg)
|
![teams-dark-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/teams-dark-scrolled.jpg)
|
| Transactions |
![transactions-light-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/transactions-light-scrolled.jpg)
|
![transactions-dark-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/transactions-dark-scrolled.jpg)
|
| Payments Customers |
![payments-customers-light-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/payments-customers-light-scrolled.jpg)
|
![payments-customers-dark-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/payments-customers-dark-scrolled.jpg)
|
| Email Outbox |
![email-outbox-light-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-outbox-light-scrolled.jpg)
|
![email-outbox-dark-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-outbox-dark-scrolled.jpg)
|
| Analytics Tables |
![analytics-tables-light-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/analytics-tables-light-scrolled.jpg)
|
![analytics-tables-dark-scrolled](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/analytics-tables-dark-scrolled.jpg)
|

### Other migrated surfaces

| Page | Light | Dark |
| --- | --- | --- |
| Analytics Tables |
![analytics-tables-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/analytics-tables-light.jpg)
|
![analytics-tables-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/analytics-tables-dark.jpg)
|
| Emails |
![emails-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/emails-light.jpg)
|
![emails-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/emails-dark.jpg)
|
| Email Sent |
![email-sent-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-sent-light.jpg)
|
![email-sent-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/email-sent-dark.jpg)
|
| Domains |
![domains-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/domains-light.jpg)
|
![domains-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/domains-dark.jpg)
|
| Webhooks |
![webhooks-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/webhooks-light.jpg)
|
![webhooks-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/webhooks-dark.jpg)
|
| External DB Sync |
![external-db-sync-light](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/external-db-sync-light.jpg)
|
![external-db-sync-dark](https://gist.githubusercontent.com/mantrakp04/2fe05ddbb2d2d7cd2d237027c909c1b9/raw/external-db-sync-dark.jpg)
|

## What's new

### `DataGrid` in `@stackframe/dashboard-ui-components`

A new, fully-typed, fully-controlled grid component under
`packages/dashboard-ui-components/src/components/data-grid/`. Single
source of truth for tabular UI across the dashboard.

Package files:
- `data-grid.tsx` — main grid renderer (virtualized rows, sticky toolbar
+ header)
- `data-grid-toolbar.tsx` — built-in toolbar (search, columns, density,
export)
- `data-grid-sizing.ts` — column width / flex / min-width resolution
- `state.ts` — state helpers (`createDefaultDataGridState`, sort /
select / paginate utilities, `exportToCsv`, date formatters)
- `strings.ts` — i18n string table + `resolveDataGridStrings`
- `types.ts` — public types (`DataGridColumnDef`, `DataGridProps`,
`DataGridState`, `DataGridDataSource`, etc.)
- `use-data-source.ts` — `useDataSource` hook with `client` / `server` /
`infinite` modes
- `index.ts` — package entrypoint

Features:
- Controlled state (`state` + `onChange`) covering sorting, pagination,
column visibility, column widths, column pinning, selection,
date-display mode, and quick search.
- Column definitions with `string` / `number` / `date` / `dateTime` /
`boolean` / `singleSelect` / `custom` types, custom `renderCell`, custom
sort comparators, per-column `parseValue` / `dateFormat`, pinning,
align, flex / min / max width.
- **Cell overflow control** — new `cellOverflow: "truncate" | "wrap"`
per column. `"wrap"` + `rowHeight="auto"` lets rows grow to fit
multi-line content.
- **Dynamic row heights** — `rowHeight` now accepts `"auto"` with an
`estimatedRowHeight` hint for the virtualizer, eliminating
scroll-position jank while rows are still being measured.
- **Sticky chrome with `stickyTop`** — the toolbar and header stick
under a caller-provided offset (matching the page header height) with a
proper blur backdrop. See the _Sticky behaviour — scrolled views_
section above for the visual.
- Client-side sort + quick-search + pagination via `useDataSource` —
consumer never pre-sorts / paginates.
- Server-side and async-generator data sources for streaming / cursor
pagination.
- Paginated and infinite-scroll UI modes.
- CSV export + clipboard copy.
- Row single / multi selection with shift-range anchor.
- Row + cell click / double-click callbacks.
- Pluggable toolbar / footer / empty / loading states and i18n strings.

### Dashboard design guide

New `apps/dashboard/DESIGN-GUIDE.md`: prescriptive, AI-readable source
of truth for dashboard UI. Documents when to use each
`design-components` primitive, the `DataGrid` canonical pattern, color /
typography / spacing / motion rules, route-specific guidance, and the
migration priority. Now also documents the new `cellOverflow` and
dynamic-`rowHeight` patterns, and marks `DesignDataTable` as deprecated
in favor of `DataGrid` + `useDataSource` + `createDefaultDataGridState`.

### Overview page revamp


`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx`
— line chart rewritten on top of the shared `AnalyticsChart` /
`DonutChartDisplay` primitives, feeding the revamped Overview.

### Data-table migrations

Every shared table under `apps/dashboard/src/components/data-table/` has
been rewritten on top of `DataGrid`:

- `api-key-table.tsx`
- `payment-product-table.tsx`
- `permission-table.tsx`
- `team-member-search-table.tsx`
- `team-member-table.tsx`
- `team-search-table.tsx`
- `team-table.tsx`
- `transaction-table.tsx` — now also wires in `DataGridToolbar` with
search / column visibility
- `user-search-picker.tsx`
- `user-table.tsx` — extracted `USER_TABLE_COLUMNS` for readability /
reuse

### Page adoption

Page-level tables migrated to `DataGrid` (or the new `useDataSource` +
`createDefaultDataGridState` pattern):

- `(overview)/line-chart.tsx`
- `analytics/tables/query-data-grid.tsx` (now with sticky header)
- `domains/page-client.tsx`
- `email-drafts/[draftId]/page-client.tsx`
- `email-outbox/page-client.tsx` (with `DataGridToolbar`)
- `email-sent/page-client.tsx`, `grouped-email-table.tsx`,
`sent-emails-view.tsx`
- `emails/page-client.tsx`
- `external-db-sync/page-client.tsx`
- `payments/layout.tsx`, `payments/customers/page-client.tsx`,
`payments/products/[productId]/page-client.tsx`
- `users/[userId]/page-client.tsx`
- `webhooks/page-client.tsx`, `webhooks/[endpointId]/page-client.tsx`
- `design-language/page-client.tsx`,
`design-language/realistic-demo/page-client.tsx`
- `playground/page-client.tsx`

### Backend & supporting changes

- `apps/backend/src/lib/ai/prompts.ts` — extends the AI-analytics prompt
with detailed schema docs for `contact_channels`, `teams`,
`team_member_profiles`, `team_permissions`, `team_invitations`,
`email_outboxes`, `project_permissions`, `notification_preferences`,
`refresh_tokens`, and `connected_accounts`, so natural-language queries
have richer context to compile against.
- `apps/backend/src/lib/seed-dummy-data.ts` — additional OAuth providers
on seed users, improving dummy-data coverage for the migrated tables
(visible on the Users grid).
- `apps/dashboard/src/app/globals.css` — adds `--data-grid-sticky-top`
token used to derive the grid's sticky offset under the page header.
- `packages/template/src/dev-tool/dev-tool-core.ts` — persist the
"closed" state when the user closes the dev-tool panel so it doesn't
reopen on next load.

## Notes for reviewers

- Rebased onto latest `dev`; conflict in `api-key-table.tsx` resolved by
keeping the `DataGrid` implementation (consistent with the other
migrated tables).
- `DesignDataTable` is still in the codebase but marked deprecated in
the design guide — new code must use `DataGrid`.
- `DataGrid` is fully controlled: consumers must pass state + onChange,
must feed `rows` from `useDataSource` (never raw arrays), and must
define columns outside the component or via `useMemo`. The guide's §4.12
spells this out.
- `rowHeight="auto"` is opt-in; the default fixed-height virtualization
path is unchanged and remains the fast path for dense, single-line grids
(users, transactions, etc.).
- Screenshots are JPEG this round — the local capture tooling's PNG path
was producing blank frames, so the new set is `.jpg` end-to-end. Same
viewports, same seeded project.

## Test plan

- [ ] `pnpm lint` passes
- [ ] `pnpm typecheck` passes
- [ ] Load the dashboard and verify every migrated surface renders,
sorts, searches, paginates, and handles row-click navigation:
  - [ ] Overview (line chart + donut metrics)
- [ ] Users list + user detail (teams, sessions, permissions, API keys)
  - [ ] Teams list + team detail (members, permissions)
  - [ ] Domains
  - [ ] Emails, email-sent, email-outbox, email-drafts
  - [ ] Webhooks list + endpoint detail
  - [ ] Payments customers, product detail, transactions (new toolbar)
  - [ ] External DB sync
  - [ ] Analytics query table (sticky header)
- [ ] Verify infinite-scroll surfaces (domains, etc.) load additional
rows on scroll
- [ ] Verify sticky header stays below the page header in light and dark
themes
- [ ] Verify CSV export produces correct output on a representative
table
- [ ] Verify column resize, visibility toggle, and sort work across
themes
- [ ] Verify `cellOverflow: "wrap"` rows grow to fit when
`rowHeight="auto"` and clip when `rowHeight` is numeric
- [ ] Spot-check AI analytics queries against the new schema context
(contact_channels, teams, email_outboxes, …)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Unified table components across dashboard with improved infinite
pagination and quick search.

* **Improvements**
* Enhanced table performance with sticky headers and better row height
handling.
* Improved sorting, filtering, and data loading with consistent state
management.
  * Better visual consistency across all data grids and table layouts.

* **UI/Styling**
* Refined table styling for better text truncation and content wrapping.
  * Optimized layout spacing and alignment across dashboard tables.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Developing-Gamer <maxcodes11110@gmail.com>
Co-authored-by: Armaan Jain <84474476+Developing-Gamer@users.noreply.github.com>
Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
2026-04-27 13:50:24 -07:00
BilalG1
04d57d91ed
fix(emulator): move mock OAuth off 8114 to avoid pnpm dev conflict (#1385)
## Summary
- The emulator's mock OAuth server bound to `${PORT_PREFIX}14` (8114)
inside the VM and the host forwarded the same port, colliding with `pnpm
dev`'s mock-oauth-server on 8114.
- Moves the emulator's mock OAuth to `EMULATOR_MOCK_OAUTH_PORT` (default
`26704`, joining the existing `267xx` host port block) and binds the
VM-internal mock to the same port. Same port on both sides keeps the
OIDC issuer URL (`http://localhost:26704`) resolvable identically from
the browser and from the backend inside the VM.
- Plumbed via `runtime-config.iso` as
`STACK_EMULATOR_MOCK_OAUTH_HOST_PORT`, read by cloud-init into
`STACK_OAUTH_MOCK_URL` + new `STACK_OAUTH_MOCK_PORT`;
`mock-oauth-server` now prefers `STACK_OAUTH_MOCK_PORT` so `pnpm dev`
(which doesn't set it) stays on 8114.

## Files
- `docker/local-emulator/qemu/run-emulator.sh` — new
`EMULATOR_MOCK_OAUTH_PORT`, hostfwd/ensure_ports_free/runtime.env
updates
- `docker/local-emulator/qemu/cloud-init/emulator/user-data` — reads the
host port, sets `STACK_OAUTH_MOCK_URL` + `STACK_OAUTH_MOCK_PORT`
- `apps/mock-oauth-server/src/index.ts` — honors `STACK_OAUTH_MOCK_PORT`
- `packages/stack-cli/src/commands/emulator.ts` — default + runtime.env
entry

## Test plan
- [ ] `pnpm emulator:build` succeeds and new snapshot boots
- [ ] `stack emulator start` with `pnpm dev` running on 8114 — no port
collision
- [ ] OAuth sign-in via mock provider completes end-to-end in the
emulator
- [ ] `pnpm dev` mock OAuth unchanged (still 8114)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* The mock OAuth server port is now configurable in the local emulator
with a sensible default, allowing custom port assignments via
environment variable.

* **Improvements**
* Updated port forwarding and environment variable handling to ensure
consistent mock OAuth endpoint configuration across host and guest
systems in the emulator.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-27 09:39:34 -07:00
BilalG1
2f719903b1
Redesign Email Server settings + managed domain flow (#1373)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary

Rewrites the **Email Server** section of the project email settings page
and the managed-domain setup flow. Replaces the dropdown +
conditional-fields layout with a visual four-card picker, a clearer
unsaved-state model, a stepper dialog for managed-domain onboarding, and
a consistent tracked-domains list. Also fixes two data-correctness bugs
in the managed-domain backend.

## Walkthrough (2×, dead-frames trimmed)


![walkthrough](https://raw.githubusercontent.com/stack-auth/stack-auth/pr-assets-email-ui/pr-assets-walkthrough.gif)

## Before

The saved state was a minimal dropdown, but choosing Custom SMTP /
Resend revealed a long conditional form with a hidden gear toggle for
server config, no clear "what is saved" signal, and a separate dialog
pattern for managed domains.

| Saved (Managed) | Custom SMTP selected |
|---|---|
|
![before-managed](https://raw.githubusercontent.com/stack-auth/stack-auth/pr-assets-email-ui/pr-assets-01-before-shared.png)
|
![before-smtp](https://raw.githubusercontent.com/stack-auth/stack-auth/pr-assets-email-ui/pr-assets-02-before-smtp.png)
|

## After — Provider cards

Four visual cards (Stack Shared, Managed Domain, Resend, Custom SMTP)
with updated copy. The saved provider shows a green **Current** pill;
the card the user is previewing shows an amber dashed **Draft** pill. An
amber unsaved-changes banner appears between the picker and the form
when state diverges from saved, so it is unambiguous that a click is not
yet committed.

| Saved state | Previewing a different provider |
|---|---|
|
![after-saved](https://raw.githubusercontent.com/stack-auth/stack-auth/pr-assets-email-ui/pr-assets-03-after-saved.png)
|
![after-draft](https://raw.githubusercontent.com/stack-auth/stack-auth/pr-assets-email-ui/pr-assets-04-after-draft.png)
|

Copy changes:
- **Stack Shared** — "Only default emails — no custom templates, themes,
or sender identity." (was: "Shared (noreply@stackframe.co)")
- **Managed Domain** — "Bring your own domain. You add DNS records; we
handle signing & delivery." (was: "Managed (via managed domain setup)")
- **Resend** uses the official Resend brand mark (light/dark variants in
`apps/dashboard/public/assets/`)

## After — Managed domain list + stepper dialog

Selecting **Managed Domain** immediately shows the tracked-domain list
with an **Add domain** button. Each row reflects real status (Active /
Verified / Waiting for DNS / Verifying / Failed). Exactly one domain can
be **Active** — the one matching the saved email config; every other
verified/applied domain shows a **Use this domain** button so switching
is always possible.

Adding a domain opens a 3-stage dialog with a horizontal stepper (Verify
is right-aligned for the final step). Stage 2 replaces the old bare
NS-list with a proper **Type / Name / Content** DNS records table with
per-row copy buttons.

| Tracked domains list | DNS records table |
|---|---|
|
![after-list](https://raw.githubusercontent.com/stack-auth/stack-auth/pr-assets-email-ui/pr-assets-05-after-managed-list.png)
|
![after-dns-table](https://raw.githubusercontent.com/stack-auth/stack-auth/pr-assets-email-ui/pr-assets-06-after-dns-table.png)
|

## Bug fixes

- **Backend: applying a managed domain did not demote previously-applied
ones.** Multiple rows could end up with status `APPLIED` even though
only one could be in the saved config. New helper
`demoteOtherAppliedManagedEmailDomains({ tenancyId, keepId })` runs
inside `applyManagedEmailProvider` to demote all other applied rows in
the tenancy back to `VERIFIED` before marking the new one.
- **Frontend: "Use this domain" only appeared for `status ===
verified`.** A domain that had been applied then replaced could never be
re-applied from the UI. Button now appears for any `verified` or
`applied` row that is not currently in use; the **Active** label is
derived from config match instead of DB status.
- **Dev mock onboarding now mirrors production timing.**
`shouldUseMockManagedEmailOnboarding()` used to insert domains as
`verified` synchronously. Now the domain is created as
`pending_verification`, and a fire-and-forget `runAsynchronously(() =>
wait(1000))` updates it to `verified` — mirroring the real Resend
webhook flow so the UI states (pending → verifying → verified) are
exercised in local dev.

## Test plan
- [ ] Cards: clicking each card shows `Draft` pill + amber banner;
Discard restores; Save commits and flips `Current` to the new card
- [ ] Managed: Add domain → stage 1 input → stage 2 DNS table + copy →
Check verification flips to stage 3 → Use this domain sets it Active and
demotes the previously-active domain in the list
- [ ] Managed: clicking **Use this domain** on a non-active verified row
makes it Active and the previously-active row back to Verified
- [ ] Shared / Resend / SMTP: existing save + test-email flows still
work (logic preserved verbatim)
- [ ] `pnpm typecheck` (dashboard + backend) and `pnpm lint` pass

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Redesigned email domain setup flow with multi-step verification dialog
  * Added copy-to-clipboard for DNS records
* Enhanced provider selection interface with improved visual
presentation
* Onboarding now shows initial "pending verification" state and
completes verification asynchronously

* **Bug Fixes**
* Ensures only one managed domain becomes active when applying a domain
  * Improved error handling for email configuration saves

* **Tests**
  * Updated end-to-end tests to reflect async verification timing
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-24 13:35:03 -07:00
BilalG1
4a2595d9f7
Classify ClickHouse NO_COMMON_TYPE (386) as unsafe (#1380)
## Summary
- Add ClickHouse error code `386` (`NO_COMMON_TYPE`) to
`UNSAFE_CLICKHOUSE_ERROR_CODES` in
`apps/backend/src/lib/clickhouse-errors.ts`. This stops the Sentry
`StackAssertionError` (`Unknown Clickhouse error: code 386 not in safe
or unsafe codes`) that was firing whenever an admin wrote a query like
`SELECT [1, 'a']` or `SELECT if(1, 'a', 1)`, while keeping the raw error
message out of prod responses.
- Add two e2e regression tests: one against the cross-project
`analytics_internal.users` table, and one against `system.query_log`, to
pin that 386 is wrapped with the generic `Error during execution of this
query.` message in prod (full detail only surfaces in dev/test).

## Why unsafe, not safe
Both callers of `getSafeClickhouseErrorMessage`
(`apps/backend/src/app/api/latest/internal/analytics/query/route.ts:59`
and `apps/backend/src/lib/ai/tools/sql-query.ts:80`) execute
caller-authored SQL under `readonly: "1"` with
`SQL_project_id`/`SQL_branch_id` scoping. The ClickHouse client runs
under a `limited_user` whose grants restrict most tables — but
ClickHouse resolves types **before** enforcing ACL. That means a query
like `SELECT if(1, query, 1) FROM system.query_log` surfaces code 386
with a message like `There is no supertype for types String, UInt8 ...`,
leaking that `system.query_log.query` is a `String` — schema info from a
table the caller can't actually read.

This is the same type-before-ACL class as code 43
(`ILLEGAL_TYPE_OF_ARGUMENT`), which is already classified unsafe.
Classifying 386 as unsafe keeps the defense-in-depth consistent: if
per-customer tables are ever introduced and grants don't block
reference-resolution in time, 386 won't leak their schema.

Cost: in prod, an admin writing a malformed type-mismatch query sees
only `Error during execution of this query.` instead of the supertype
hint. Dev and test environments still show the full error via the
existing `getNodeEnvironment()` branch, so local iteration is
unaffected.

## Test plan
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts` — all
64 tests pass, including the two 386 regression tests.
- [ ] Monitor Sentry after deploy to confirm the
`unknown-clickhouse-error-for-query` events for code 386 stop firing.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved handling of a ClickHouse type-mismatch error to prevent
exposure of sensitive data and ensure sanitized error responses.

* **Tests**
* Added regression tests that verify error responses are sanitized,
return consistent error codes, and include expected headers without
leaking internal details.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-24 12:07:16 -07:00
Mantra
cbd945e3a6
[codex] Fix Neon malformed Basic auth validation (#1381)
## What changed

This fixes Sentry issue
[STACK-BACKEND-1A3](https://stackframe-pw.sentry.io/issues/7436639623/?project=4507442898272256&query=is%3Aunresolved&referrer=issue-stream&seerDrawer=true).

A request with this malformed header:

```http
Authorization: Basic
```

used to crash the Neon auth validator with a `StackAssertionError`,
which turned a bad client request into a 500.

The fix makes `neonAuthorizationHeaderSchema` only validate Neon client
credentials after the Basic auth header successfully decodes. If
decoding fails, the Neon-specific validator returns `true` and lets
`basicAuthorizationHeaderSchema` produce the intended 400 schema error:
`Authorization header must be in the format "Basic <base64>"`.

## Reviewer walkthrough

There are two checks chained together:

1. `basicAuthorizationHeaderSchema` checks that the header is
structurally valid Basic auth.
2. `neonAuthorizationHeaderSchema` checks that the decoded
`client_id:client_secret` matches a configured Neon client.

Yup may still run the second check after the first one has failed,
because route validation collects errors with `abortEarly: false`. The
old code assumed the first check had already passed and called
`throwErr(...)` when decoding returned `null`. This PR changes that path
to return `true`, because the format error is already owned by the first
check.

## Tests

- `pnpm -C packages/stack-shared exec vitest run --maxWorkers=1
--minWorkers=1 src/schema-fields.ts`
- `pnpm -C apps/e2e exec vitest run --maxWorkers=1 --minWorkers=1
tests/backend/endpoints/api/v1/integrations/neon/projects/transfer.test.ts
-t "malformed"`
- `pnpm -C packages/stack-shared lint`
- `pnpm -C packages/stack-shared typecheck`
- `pnpm -C apps/e2e lint`
- `pnpm -C apps/e2e typecheck`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Enhanced authorization header validation in API endpoints with
improved error handling, ensuring malformed credentials return clear,
specific validation error messages.

* **Tests**
* Added comprehensive end-to-end test coverage for API request
validation, including edge cases for authorization headers.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-24 11:59:18 -07:00
Mantra
a132dd23f9
fix: refresh-token P2025 race with concurrent sign-out (#1372)
## Summary
- Fixes Sentry
[STACK-BACKEND-146](https://stackframe-pw.sentry.io/issues/7377768662/):
`PrismaClientKnownRequestError` P2025 on
`projectUserRefreshToken.update()` during token refresh.
- Root cause: `generateAccessTokenFromRefreshTokenIfValid`
(`apps/backend/src/lib/tokens.tsx`) reads the refresh-token row
upstream, then issues `.update(...)` on it (and on `projectUser`) inside
a `Promise.all`. If a concurrent sign-out (`DELETE
/auth/sessions/current`), session revoke, password change, or user
deletion removes the row between the read and the update, Prisma throws
P2025 and the refresh endpoint 500s.

## Changes
- `apps/backend/src/lib/tokens.tsx` — swap the two `.update(...)`s for
`.updateMany(...)` so a missing row is a no-op, then re-check the
refresh token still exists; return `null` if it doesn't. The refresh
route already maps `null` -> `KnownErrors.RefreshTokenNotFoundOrExpired`
(401), which is the correct user-facing behavior for a just-revoked
session.
- `apps/backend/src/oauth/model.tsx` — in `generateAccessToken`, replace
the "ultra-rare race condition" `throwErr` fallback with `throw new
KnownErrors.RefreshTokenNotFoundOrExpired()` so concurrent sign-out
during an OAuth `refresh_token` grant returns a clean 401 instead of
500.
-
`apps/e2e/tests/backend/endpoints/api/v1/auth/sessions/current/refresh-race.test.ts`
— new regression test that fires `POST /auth/sessions/current/refresh`
and `DELETE /auth/sessions/current` concurrently with the same refresh
token. Before the fix it 500s on the first iteration; after, it passes
in ~12s.

## Test plan
- [x] New regression test passes locally.
- [x] Existing `auth/sessions/**` + `auth/oauth/token.test.ts` still
pass (27 tests, 3 todo, 0 failed).
- [ ] CI green.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Refresh flows now detect a revoked or removed refresh token during
concurrent operations and stop cleanly, preventing issuance of an access
token from stale data.
* A specific refresh-token-not-found/expired error is returned instead
of a generic failure when refresh cannot proceed.

* **Tests**
* Added E2E tests exercising concurrent refresh vs sign-out to prevent
race-condition crashes and validate safe handling of competing requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-24 18:44:39 +00:00
BilalG1
982b8fb2d9
Simplify sign-up rules tester dialog (#1369)
## Summary

The sign-up rules tester dialog was dense and hard to parse: a
two-column layout crammed 8 input fields against 4 stacked result panels
(Outcome, Triggered rules, Evaluation trace, Normalized context), and
used technical jargon ("Turnstile override", "Normalized context",
"Evaluation trace") without much hierarchy. This PR reworks it around
the user's actual question — *"will this sign-up be allowed?"* — and
moves the entrypoint somewhere more discoverable.

## What changed

### 1. Dialog UI — essentials-first layout
- Only **Email** and **Sign-up method** are shown upfront.
- Everything else (OAuth provider, Country, Bot / free-trial-abuse
scores, Turnstile) is hidden behind a single **Advanced options**
collapsible panel. The label previews what's inside, so users know when
they need to expand it.
- Results are outcome-first: a large green/red hero card with a check/X
icon and a plain-English decision ("Sign-up would be allowed"). Matched
rules and resolved context are tucked into `<details>` sections below.
- Removed the "Fill out the form above…" placeholder — it added clutter
without adding info.

### 2. Loading → result transition
- The outcome card now mounts **immediately** when Run test is clicked.
While the request is in flight it shows a neutral gray card with a
spinning `CircleNotchIcon` and "Running test…".
- When the result arrives, the card's border/background transitions over
500ms to green or red, the spinner fades out, and the check/X fades in.
Matched rules and resolved context slide down underneath via a
`grid-rows-[0fr→1fr]` animation.

### 3. Entry-point moved to the page header
- "Open tester" now sits **next to Add rule** in the header (secondary
variant, same size).
- Removed the dedicated "Test rules" card at the bottom of the page — it
was using real estate for something a button can do.

### 4. Code cleanup
- Dropped three exploratory variants (wizard, inspector, the original
complex card) that were temporarily in the file during design
exploration.
- Extracted `useTestRulesState()` to encapsulate state + API call, so
the card is purely presentational.

## Why

The tester is an admin-only debugging tool, so it lives or dies by how
fast someone can glance at it and answer *"would this sign-up go
through?"*. The old dialog asked readers to visually parse two columns
and seven fields just to find the outcome. The new layout answers that
question in the first card.

## Walkthrough


![walkthrough](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/tester-flow.gif)

21s demo (2x speed): page → open tester → type email → Run test →
loading spinner transitions into the green decision card.
[Download
MP4](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/tester-flow.mp4)
· [Gist with all
media](https://gist.github.com/BilalG1/67639d1590ac172880dc705a027560d3)

## Before / After

### Original tester

![before](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/before-original.png)

### New header layout
"Open tester" next to "Add rule"; no more bottom card.
![after
header](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/after-header-buttons.png)

### New tester dialog — initial
Just Email + Sign-up method. Advanced options collapsed.
![after
initial](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/after-dialog-initial.png)

### New tester dialog — mid-run (loading)
Outcome card mounts with a spinner while the request is in-flight.
![after
loading](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/after-dialog-loading.png)

### New tester dialog — result
Outcome hero transitions to green; matched rules + resolved context
collapsibles underneath.
![after
results](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/after-dialog-results.png)

## Test plan

- [x] `pnpm typecheck` (dashboard) passes
- [x] `pnpm lint` (dashboard) passes
- [x] Manually exercised the tester against a configured rule
(`emailDomain.endsWith("tempmail.com")`) with Advanced options both open
and closed
- [x] Verified the loading → green/red transition under artificial
latency (1.2s)
- [x] Verified the "Open tester" button sits next to "Add rule" and the
bottom card is gone

## Scope notes

- No backend, schema, or API changes. Only touches
`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx`.
- The existing analytics / trigger-history / rule-editor code is
untouched.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Advanced testing options now available in a collapsible panel
* Enhanced test results visualization with detailed rule evaluation
display

* **UI/UX Improvements**
  * Test trigger button relocated to main action area
  * Larger, repositioned "Run test" button
* Reorganized results display with collapsible sections for rules and
context details

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Bilal Godil <bilal@stack-auth.com>
2026-04-24 11:35:47 -07:00
Mantra
7957de4182
fix(email-queue): recover stuck sending without duplicate retry (#1356)
## Summary

Email outbox rows can get stuck in `SENDING` if a worker dies after
setting `startedSendingAt` but before finishing or unclaiming. This
change adds `recoverEmailsStuckInSending`, which runs each email queue
step and marks rows past the stuck timeout as **terminal server errors**
with delivery status unknown, **without** scheduling an automatic retry
(to avoid duplicate sends if the provider already accepted the message).

## Changes

- **`recoverEmailsStuckInSending`**: updates stuck rows with
`finishedSendingAt`, `canHaveDeliveryInfo: false`, and server error
fields; emits Sentry via `captureError` when any rows are recovered.
- **Tests**: `email-queue-step.test.tsx` covers recovery of old
`startedSendingAt`, no-op for recent sends, and idempotency (second pass
does not re-queue).

## Test plan

- [ ] `pnpm` / vitest for
`apps/backend/src/lib/email-queue-step.test.tsx` (requires dev DB like
other integration tests in this package)

Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Email reliability: messages that remained stuck in sending are now
automatically marked as terminal failures, assigned standardized error
details, cleared from retry scheduling, prevented from receiving
delivery info, and recovery emits an alert only when actual work occurs.
Recovery is safe to run repeatedly (idempotent).

* **Tests**
* Added integration tests validating recovery behavior, proper field
updates, and idempotency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-24 11:00:46 -07:00
BilalG1
94541c4a94
fix(dashboard): Restricted row styling + Replays empty state (#1366)
## Summary

Two small UI polish fixes in `apps/dashboard`:

1. **User detail page** — the **Restricted** field now visually matches
its sibling fields (`User ID`, `Display name`, `Primary email`, etc.) by
reusing the same input-box appearance (`rounded-xl` border, ring,
shadow, `h-8`). Previously it rendered as a bare button with
`rounded-md` hover styling, which looked out of place in the user
details grid.
2. **Analytics → Replays page** — the empty state previously read just
*"No session replays yet"* with no guidance. It now shows a short
description of what session replays are, and links out to the docs
(`https://docs.stack-auth.com/docs/apps/analytics`) so new users can
discover more.

## Files changed

-
[`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx`](https://github.com/stack-auth/stack-auth/blob/fix/ui-bugs-users-analytics/apps/dashboard/src/app/%28main%29/%28protected%29/projects/%5BprojectId%5D/users/%5BuserId%5D/page-client.tsx)
— `RestrictedStatusRow` button now styled to mirror the read-only
`EditableInput` look.
-
[`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/replays/page-client.tsx`](https://github.com/stack-auth/stack-auth/blob/fix/ui-bugs-users-analytics/apps/dashboard/src/app/%28main%29/%28protected%29/projects/%5BprojectId%5D/analytics/replays/page-client.tsx)
— empty state now includes a description and a `StyledLink` to the docs.

---

## Bug 1 — Restricted row no longer visually orphaned

Before, the *Restricted* row's value (`No`) was just plain text inside
the grid; every other row (User ID, Display name, Primary email,
Password, 2-factor auth, Signed up at, Risk scores, Sign-up country
code) was rendered inside a styled input box. After the fix,
*Restricted* uses the same boxed style — the row is still clickable and
still opens the existing restriction dialog.

### Before / after toggle (full page)

![user-detail
toggle](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/user_detail_toggle.gif)

### Cropped view of the changed region (clearer)

![user-detail crop
toggle](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/user_detail_crop_toggle.gif)

### Wipe transition

![user-detail
wipe](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/user_detail_wipe.gif)

### Fade transition

![user-detail
fade](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/user_detail_fade.gif)

### Pixel diff (only the Restricted cell changes)

![user-detail pixel
diff](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/user_detail_pixel_diff.png)

---

## Bug 2 — Replays empty state explains itself

Before, an empty replays workspace showed only *"No session replays
yet"*. Users had no signal that there is anything they need to do, or
where to look. After the fix, the empty state explains what session
replays are, hints that replays will appear once captured, and links to
the relevant docs page.

> Session replays let you watch how users interact with your app.
Replays will appear here once your project starts capturing them.
>
> [Learn more in the
docs](https://docs.stack-auth.com/docs/apps/analytics)

### Before / after toggle (full page)

![replays
toggle](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/replays_toggle.gif?v=2)

### Cropped view of the empty state

![replays crop
toggle](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/replays_crop_toggle.gif?v=2)

### Wipe transition

![replays
wipe](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/replays_wipe.gif?v=2)

### Fade transition

![replays
fade](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/replays_fade.gif?v=2)

### Pixel diff

![replays pixel
diff](https://gist.githubusercontent.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17/raw/replays_pixel_diff.png?v=2)

---

## Test plan

- [x] `pnpm --filter @stackframe/dashboard run lint` passes
- [x] `pnpm --filter @stackframe/dashboard run typecheck` passes
- [x] Manual verification on `localhost:8101`:
- [x] User detail page renders Restricted with the same input-box style
as siblings
  - [x] Clicking Restricted still opens the existing restriction dialog
  - [x] Replays empty state shows description + working docs link
- [x] Light mode visually verified (dark mode untouched, classes are
dark-mode-aware)

## Notes for reviewers

- No change to `RestrictionDialog`, `getRestrictionReasonText`, or any
restriction logic — this is purely visual.
- The replays empty-state copy keeps the existing `MonitorPlayIcon` and
centered layout; only added the description paragraph and the
`StyledLink` (which is already imported in this file).
- Comparison assets (toggles / fades / wipes / pixel diffs) are hosted
in [this
gist](https://gist.github.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17)
for reference.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Style**
* Improved analytics empty state: centered, constrained layout; clearer
primary text, added muted secondary explanatory copy and an external
documentation link that opens in a new tab.
* Restyled restricted-user control: refreshed appearance and spacing,
truncation for long values, and stronger hover/focus feedback while
preserving existing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-22 17:42:39 -07:00
BilalG1
0532a18c36
fix(dashboard): wrap "Block new purchases" toggle in a Card (#1364)
## Summary

The **Block new purchases** toggle on the Payments → Settings page was
visually out of place: it rendered as a bare `SettingSwitch` outside the
`max-w-3xl` settings column, while every neighboring setting (Stripe
Connection, Test Mode, Payment Methods, Platform-Managed Methods) was a
full-width `Card`.

This PR wraps it in a `Card` that matches the existing `TestModeToggle`
pattern so it inherits the same width constraint, border, padding,
title/description structure, and state-colored icon badge.

**File changed:**
[`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/settings/page-client.tsx`](https://github.com/stack-auth/stack-auth/blob/fix/payments-block-new-purchases-card/apps/dashboard/src/app/(main)/(protected)/projects/%5BprojectId%5D/payments/settings/page-client.tsx)

## What was wrong

Two concrete mismatches with the rest of the page:

1. **Wrong container.** The `SettingSwitch` was a direct child of
`<PageLayout>` rather than the `<div className="space-y-6 max-w-3xl">`
column that wraps the other settings — so it stretched to the full page
width instead of the 3xl column and broke the vertical rhythm (no
consistent `space-y-6` gap from the card above).
2. **Wrong style primitive.** It used the bare `SettingSwitch` row
component instead of a `Card` +
`CardHeader`/`CardTitle`/`CardDescription`/`CardContent` structure — so
there was no border, no heading hierarchy, and no state-colored icon
badge, which every other setting on the page has.

## Fix

- Moved the block inside the `space-y-6 max-w-3xl` column so it's
constrained and spaced like its siblings.
- Replaced the `SettingSwitch` with a `Card` mirroring `TestModeToggle`:
- `CardHeader` with `CardTitle` (\"Block New Purchases\") and
`CardDescription` (\"Stops new checkouts while keeping existing
subscriptions active.\").
- `CardContent` with an icon badge (`ProhibitIcon`) that turns red when
blocking is active, plus a short \"Block new purchases\" label and the
`Switch`.
- Copy is intentionally minimal: one title, one sentence of description,
one label next to the switch. No two-state narration.

## Visual comparison

### Pixel diff (changed pixels tinted red over the after image)
4.7% of pixels changed, all concentrated in the bottom of the settings
column — everything else is pixel-identical, confirming the fix is
scoped.

![pixel
diff](https://gist.githubusercontent.com/BilalG1/faacb21aea28bc6acae0f527f232c38c/raw/compare_pixel_diff.png)

### Cropped before/after toggle (zoomed to the changed region)
Full-viewport comparisons are noisy when the delta is a single component
at the bottom. This one is cropped to the changed bbox so the card fix
is the whole frame — 1s before, 1s after, looped.

![crop
toggle](https://gist.githubusercontent.com/BilalG1/faacb21aea28bc6acae0f527f232c38c/raw/compare_crop_toggle.gif)

### Wipe reveal (before on the left, after swept in from the left)
A vertical red sweeps across the full page, revealing the after state
over the before state. Useful for spotting any unintended drift
elsewhere on the page (there is none).


![wipe](https://gist.githubusercontent.com/BilalG1/faacb21aea28bc6acae0f527f232c38c/raw/compare_wipe.gif)

## Test plan

- [ ] Open `/projects/<id>/payments/settings` in the dashboard.
- [ ] Verify \"Block New Purchases\" renders as a `Card` with the same
width as Stripe Connection / Test Mode / Payment Methods.
- [ ] Toggle the switch on — icon badge turns red, config write fires
(`payments.blockNewPurchases = true`, `pushable: true`).
- [ ] Toggle off — icon returns to muted gray, config write fires with
`false`.
- [ ] Reload the page and confirm the persisted state matches the
toggle.
- [ ] `pnpm lint` and `pnpm typecheck` pass.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Improvements**
* Redesigned the "Block New Purchases" toggle in payment settings with a
new card-based interface and visual prohibit indicator for improved
clarity and user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-22 17:28:09 -07:00
BilalG1
4f198bd55b
Fix dashboard UI bugs: webhook detail crash and http domain silent https upgrade (#1362)
## Summary

Fixes two dashboard UI bugs surfaced while auditing the project area for
large user-visible issues:

1. **Webhook detail page completely broken** — the page shows a blank
screen because the SvixProvider token was being set to the string
`"[object Object]"`.
2. **Editing a trusted domain with an `http://` base URL silently
upgrades it to `https://`** — saving the edit dialog without changing
anything changes the protocol, breaking callbacks to the original host.

Both are corrected with minimal, targeted changes in the dashboard app.
No API, schema, or shared package changes are required.

---

## Bug 1 — Webhook detail page crashes because `svixToken + ''` yields
`"[object Object]"`

### Where


`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsx`

### Root cause

`stackAdminApp.useSvixToken()` returns an object of shape `{ token:
string, url: string | null }` (see
`packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts`).
The page was doing:

```ts
const svixToken = stackAdminApp.useSvixToken();
const [updateCounter, setUpdateCounter] = useState(0);

// This is a hack to make sure svix hooks update when content changes
const svixTokenUpdated = useMemo(() => {
  return svixToken + '';
}, [svixToken, updateCounter]);

// …
<SvixProvider token={svixTokenUpdated} …>
```

`svixToken + ''` coerces the object to the string `"[object Object]"`,
which is then passed to `<SvixProvider>` as the auth token. Every nested
Svix hook (`useEndpoint`, `useEndpointSecret`,
`useEndpointMessageAttempts`) authenticates with that bogus token, gets
a `401 {"code":"authentication_failed","detail":"Invalid token"}` from
Svix, and `getSvixResult`
(`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/utils.tsx`)
throws, crashing the page.

Additional notes while in there:
- `setUpdateCounter` was declared but never called anywhere, so the
surrounding `useMemo`/`useState` was dead weight as well as broken.
Removing it removes the dead code too.
- The neighbouring list page (`webhooks/page-client.tsx`) already uses
the correct shape (`svixToken.token`, `svixToken.url`), which is why the
list page rendered correctly while the detail page didn't.

### Fix

Pass `svixToken.token` directly to `<SvixProvider>` and drop the unused
counter/memo.

```ts
export default function PageClient(props: { endpointId: string }) {
  const stackAdminApp = useAdminApp();
  const svixToken = stackAdminApp.useSvixToken();

  return (
    <AppEnabledGuard appId="webhooks">
      <SvixProvider
        token={svixToken.token}
        appId={stackAdminApp.projectId}
        options={{ serverUrl: getPublicEnvVar('NEXT_PUBLIC_STACK_SVIX_SERVER_URL') }}
      >
        <PageInner endpointId={props.endpointId} />
      </SvixProvider>
    </AppEnabledGuard>
  );
}
```

### Reproduction (before fix)

1. Enable the Webhooks app on a project.
2. Create an endpoint with any URL.
3. Open the row's action menu and click **View Details**.
4. The page renders blank (Svix hooks throw 401 Invalid token; the error
boundary unmounts the detail tree). URL, Description, Verification
Secret, and Events History never appear.

### Before / After

| Before | After |
| --- | --- |
| ![Webhook detail blank before
fix](https://gist.githubusercontent.com/BilalG1/f31b7631cb914ea8fd0113b97d26319e/raw/bug1-webhook-detail-before.png)
| ![Webhook detail renders after
fix](https://gist.githubusercontent.com/BilalG1/f31b7631cb914ea8fd0113b97d26319e/raw/bug1-webhook-detail-after.png)
|

---

## Bug 2 — Editing an `http://` trusted domain silently upgrades it to
`https://`

### Where


`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx`

### Root cause

In `EditDialog`, the form's `defaultValues` always set `insecureHttp:
false`, regardless of the protocol of the domain being edited:

```ts
defaultValues={{
  addWww: props.type === 'create',
  domain: props.type === 'update' ? props.defaultDomain.replace(/^https?:\/\//, "") : undefined,
  handlerPath: props.type === 'update' ? props.defaultHandlerPath : "/handler",
  insecureHttp: false, // ← ignores the existing protocol
}}
```

The `domain` field strips `http(s)://` for display but the protocol
itself is only tracked through the `insecureHttp` switch, which lives
inside the collapsed-by-default **Advanced** accordion. On submit:

```ts
const protocol = values.insecureHttp ? 'http://' : 'https://';
const baseUrl = protocol + values.domain;
```

So an `http://myapp.test` entry reopens with `insecureHttp: false`, the
Advanced section stays collapsed, the user sees nothing wrong, and
hitting **Save** (even with zero visible changes) writes
`https://myapp.test` back to config. Existing redirects from SSO / email
verification flows that depend on the original `http://` host stop
working.

### Fix

Derive `insecureHttp` from the existing `defaultDomain` when editing:

```ts
insecureHttp: props.type === 'update' ? props.defaultDomain.startsWith('http://') : false,
```

This makes the switch in the Advanced panel pre-check itself correctly
and the submit path emits the preserved protocol.

### Reproduction (before fix)

1. Go to **Project Settings → Trusted Domains**.
2. Add a new domain, expand **Advanced**, toggle **Use HTTP instead of
HTTPS** on, enter `myapp.test`, click **Create**. The list now shows
`http://myapp.test`.
3. Click the row's **⋯ → Edit**, then **Save** without changing
anything.
4. Observe the list now shows `https://myapp.test`.

### Before / After

**Domain list after an edit+save:**

| Before (http silently became https) | After (http preserved) |
| --- | --- |
| ![Domain list
before](https://gist.githubusercontent.com/BilalG1/f31b7631cb914ea8fd0113b97d26319e/raw/bug4-domain-list-before.png)
| ![Domain list
after](https://gist.githubusercontent.com/BilalG1/f31b7631cb914ea8fd0113b97d26319e/raw/bug4-domain-list-after.png)
|

In the "before" screenshot, `http://myapp.test` was edited with no
changes and silently became `https://myapp.test`.
`http://www.myapp.test` (not edited) stayed `http://`, confirming the
bug is triggered only through the edit-save path.

**Edit dialog (Advanced expanded):**

| Before (HTTP switch always off) | After (reflects stored protocol) |
| --- | --- |
| ![Edit dialog
before](https://gist.githubusercontent.com/BilalG1/f31b7631cb914ea8fd0113b97d26319e/raw/bug4-edit-dialog-before.png)
| ![Edit dialog
after](https://gist.githubusercontent.com/BilalG1/f31b7631cb914ea8fd0113b97d26319e/raw/bug4-edit-dialog-after.png)
|

The "after" dialog also shows the protocol prefix label flip from
`https://` to `http://` next to the input — a second visual cue that the
user is editing an HTTP domain.

---

## Scope / out of scope

In scope here:
- The two fixes above, plus a small amount of dead-code cleanup adjacent
to the first fix (the unused `updateCounter` / `useMemo` hack).

Intentionally **not** included (tracked separately from the same audit —
see internal notes):
- Cursor pagination cache wipe across Users/Teams/Transactions tables
(`data-table/common/cursor-pagination.tsx`)
- Email Outbox "Scheduled At" input being reset on every keystroke and
rendered in the wrong timezone (`email-outbox/page-client.tsx`)
- Latent empty-group handling in the sign-up rule builder (validator +
CEL emitter), which is real in code but not currently reachable through
the editor UI

These are broader and deserve their own PRs.

## Test plan

- [ ] **Bug 1 (webhook detail):** Enable Webhooks on a project, create
an endpoint, open **View Details**. Confirm URL, Description,
Verification Secret, and Events History render (no 401s in the console,
no blank page). Confirm the Copy button on the verification secret still
copies the key.
- [ ] **Bug 2 (domain edit preserves http):** Add an `http://` trusted
domain. Edit it and save with no changes — list should still show
`http://`. Edit again, flip the Advanced switch to HTTPS, save — list
should show `https://`. Repeat with the inverse direction (start https,
flip to http).
- [ ] **Regression sweep:** Webhooks list page, create/delete endpoint,
copy signing secret; Trusted Domains add/delete; auth-methods callbacks
against an `http://localhost` domain continue to work.
- [ ] `pnpm typecheck` passes locally. (`pnpm lint` was also run against
the dashboard app and is clean.)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Domain editing now correctly initializes and preserves the protocol
type (HTTP or HTTPS) based on the existing domain configuration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-22 17:27:37 -07:00
Mantra
000634607a
fix(internal-tool): continue dev startup when spacetime publish fails (#1371)
## Summary
- pre-dev.mjs now warns and exits 0 when the local SpacetimeDB publish
fails, instead of aborting `next dev`
- Lets contributors without a running local SpacetimeDB server still
start the internal-tool dev server
- Updates the header comment to reflect the new behavior

## Test plan
- [ ] Run `pnpm dev` in `apps/internal-tool` with no SpacetimeDB server
running — dev server should still start, with a warning
- [ ] Run with SpacetimeDB server running and `spacetime` CLI installed
— publish still runs and dev proceeds
- [ ] Run without `spacetime` CLI installed — existing warn-and-continue
path still works

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Updated local publishing configuration to derive server settings from
environment variables for improved flexibility and easier customization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-22 22:35:27 +00:00
BilalG1
f89b97bc54
fix connected accounts tokens (#1358)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* OAuth flows now consistently block extra scopes and access tokens for
shared OAuth keys, enforcing restrictions earlier in the request
processing and across all environments.
* **Tests**
* Added end-to-end regression tests to verify requests with extra scopes
against shared OAuth providers return a 400 response indicating extra
scopes/access tokens are not allowed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-20 19:33:47 -07:00
Konstantin Wohlwend
3ea8052d35 chore: update package versions 2026-04-20 19:06:56 -07:00
Konstantin Wohlwend
d9492ac5f1 Update submodules 2026-04-20 19:01:16 -07:00
Konstantin Wohlwend
6f1df1a0c7 Update submodules 2026-04-20 18:53:36 -07:00
BilalG1
37ee5ec320
Fast-start local emulator via RAM snapshot + live secret rotation (#1340)
## Summary

`stack emulator start` now resumes a fully-warm VM snapshot instead of
cold-booting, bringing startup from 30–120s down to ~5–8s with
per-install secret rotation, or ~2.5s with rotation opt-out. The
snapshot is captured **locally on first `stack emulator pull`**, not
shipped from CI — QEMU migration state isn't portable across
accelerators (KVM/HVF/TCG) or `-cpu max` feature sets, so a CI-captured
snapshot couldn't resume reliably on arbitrary user hardware.

Also bundles a pile of CLI QoL fixes (progress bars, PR/run artifact
pulls, PR-build download, native-TS ISO writer replacing
`hdiutil`/`mkisofs`/`genisoimage` host dep, unit tests).

| Scenario | Before | After |
|---|---|---|
| Cold boot (no snapshot) | 30–120s | same, works as fallback |
| `stack emulator pull` (one-time, includes local snapshot capture) |
~30s download | ~30s download + ~1–3 min cold-boot capture |
| Snapshot resume, normal start | — | **~5–8s** |
| Snapshot resume, `EMULATOR_NO_ROTATION=1` | — | **~2.5s** |

Backend (`/health?db=1`) and dashboard (`/handler/sign-in`) return 200
on all paths. Two successive snapshot resumes produce different rotated
PCK/SSK/SAK/CRON_SECRET values per install.

## How it works

**Build (CI)** — `docker/local-emulator/qemu/build-image.sh`:

1. Cloud-init provisioning runs to completion (migrations, seed,
slim-image) producing `stack-emulator-<arch>.qcow2`.
2. Image is built with a topology compatible with later snapshot capture
(pinned SMP=4, phantom seed/bundle ISOs, STACKCFG runtime ISO mounted at
build time, qemu-guest-agent running, placeholder hex secrets baked in
under `STACK_EMULATOR_BUILD_SNAPSHOT=1`).
3. CI publishes **only the qcow2** — no `.savevm.zst` ships.

**Pull (user's machine)** —
`packages/stack-cli/src/commands/emulator.ts` + `run-emulator.sh
capture`:

1. `stack emulator pull` downloads the qcow2 with a progress bar (or
from a PR / workflow run via `--pr` / `--run`).
2. CLI invokes `run-emulator.sh capture`: cold-boots the qcow2 with a
matching device layout (phantom ISOs, fsdev, pcie-root-port, virtfs
detached — migration-incompatible), waits for backend+dashboard health,
then drives QMP: `stop` → set `mapped-ram` + `multifd` caps → `migrate
file:state.raw` → poll `query-migrate` → `quit`. Raw mapped-ram file is
zstd-compressed to `stack-emulator-<arch>.savevm.zst` in the images dir.
3. `--skip-snapshot` opts out (first `start` will then cold-boot).

**Runtime** — `run-emulator.sh start`:

1. Launch QEMU with `-incoming defer` when a `.savevm.zst` is present;
decompress on first use, keep the `.raw` cached for subsequent starts.
2. QMP: same `mapped-ram` + `multifd` caps → `migrate-incoming
file:<.raw>` → poll for `paused` → `cont`.
3. Generate fresh per-install secrets on the host; pipe them
base64-encoded through QGA `guest-exec input-data` →
`trigger-fast-rotate` in the guest → `docker exec -e … rotate-secrets`.
4. `rotate-secrets` in the container: validate keys (hex-only), targeted
`sed` on the placeholder PCK across built JS, `UPDATE ApiKeySet`,
`supervisorctl restart stack-app cron-jobs` (with
`stopasgroup`/`killasgroup` so the Node children actually die and
release their ports).
5. Poll backend+dashboard health; if anything fails, clean up and fall
back to cold boot transparently.

**Security model**: placeholder hex values are baked into the snapshot
(`00…ff` PCK, `00…ee` SSK, `00…dd` SAK, `00…cc` CRON_SECRET). They are
non-secret by construction. Real per-install secrets are generated at
each `emulator start` and never leave the host.

## CLI changes (`packages/stack-cli`)

- **`src/lib/iso.ts`** (new): native TypeScript ISO 9660 + Joliet
writer, replacing the host-side `hdiutil`/`mkisofs`/`genisoimage`
dependency for generating the STACKCFG runtime config disk. Unit tests
in `src/lib/iso.test.ts`.
- **`src/commands/emulator.ts`**:
- `pull`: streamed downloads with progress bar + ETA; `--pr <number>`
and `--run <id>` to pull from a PR build's CI artifacts (uses
`extract-zip` for the nested zip); `--skip-snapshot` to opt out of the
one-time local capture.
- `start` (existing, extended): auto-pulls AND auto-captures when no
image exists, so first-ever `start` is self-bootstrapping; emits
`STACK_EMULATOR_CLI_WROTE_ISO=1` so the shell helper skips its own ISO
regen (avoids the genisoimage host dep).
- `capture` (new, invoked by `pull` and the auto-pull path of `start`):
drives the local snapshot capture via `run-emulator.sh`.
- `status`, `stop`, `reset`, `list-releases`: preflight +
path-resolution tightening (`STACK_EMULATOR_HOME` → images/run dirs).
  - Unit tests in `src/commands/emulator.test.ts`.
- **`EMULATOR_NO_ROTATION=1`** env var skips the post-resume rotation
(intended for tests/CI where the placeholder secrets are fine — comes
with a loud warning).

## CI (`.github/workflows/qemu-emulator-build.yaml`)

- Builds **QEMU 10.2.2 from source** (cached), because
`mapped-ram`/`multifd` migration capabilities aren't available in the
distro's QEMU. Enables KVM on ubicloud runners so amd64 boots at
hardware speed.
- amd64 + arm64 both build on the same amd64 matrix
(`ubicloud-standard-8`); arm64 runs under cross-arch TCG (provisioning
only — boot/verify smoke test is amd64-only).
- Verification now runs through the CLI: `emulator start` → `emulator
status` → `emulator stop` against the freshly-built qcow2 (via
`STACK_EMULATOR_HOME` pointing at the workspace, so the CLI doesn't
silently auto-pull a prior release).
- Packages **only** the qcow2. No `.savevm.zst` upload / publish.
- Release notes updated.

## Key files

**Shell / guest:**
- `docker/local-emulator/qemu/build-image.sh` — snapshot-compatible
device topology + STACKCFG runtime ISO at build time
- `docker/local-emulator/qemu/run-emulator.sh` — `start`, `capture`,
`stop`, `reset`, `status`; `-incoming defer`, `.raw` cache, QGA-driven
rotation, cold-boot fallback
- `docker/local-emulator/qemu/common.sh` (new) — shared `qmp_session` +
`capture_vm_state` (factored out so build-image.sh and run-emulator.sh
share the capture path)
- `docker/local-emulator/qemu/cloud-init/emulator/user-data` —
placeholder secrets in snapshot mode, `wait-for-stack-ready`,
`trigger-fast-rotate`, qemu-guest-agent enabled
- `docker/local-emulator/rotate-secrets.sh` (new) — in-container
rotation (sed + UPDATE + supervisorctl)
- `docker/local-emulator/supervisord.conf` — `stopasgroup`/`killasgroup`
on `stack-app` and `cron-jobs`
- `docker/local-emulator/entrypoint.sh` — only mint CRON_SECRET if unset
(placeholder supplied in snapshot mode via --env-file)
- `docker/local-emulator/Dockerfile` — ships `rotate-secrets` to
`/usr/local/bin`
- `docker/server/entrypoint.sh` — source
`/run/stack-auth/rotated-secrets.env`; skip full-tree sentinel scan on
warm restarts via marker

**CLI:**
- `packages/stack-cli/src/lib/iso.ts` (new) + `iso.test.ts` (new)
- `packages/stack-cli/src/commands/emulator.ts` + `emulator.test.ts`
(new)
- `packages/stack-cli/vitest.config.ts` (new)

**CI:**
- `.github/workflows/qemu-emulator-build.yaml`

## Test plan

- [x] `docker/local-emulator/qemu/build-image.sh {amd64,arm64}` produces
`stack-emulator-<arch>.qcow2` with snapshot-compatible topology
- [x] `stack emulator pull` downloads qcow2 with progress, then captures
locally (~1–3 min) and writes `stack-emulator-<arch>.savevm.zst` in the
images dir
- [x] `stack emulator pull --skip-snapshot` stops after download
- [x] `stack emulator pull --pr <n>` / `--run <id>` pull from PR /
workflow run artifacts
- [x] `stack emulator start` on a fresh dir auto-pulls **and**
auto-captures, then starts; subsequent starts fast-resume in ~5–8s;
backend + dashboard return 200
- [x] `EMULATOR_NO_ROTATION=1 stack emulator start` completes in ~2.5s;
backend + dashboard return 200 with warning printed
- [x] Two consecutive `emulator start` invocations produce different PCK
values in the internal `ApiKeySet` row
- [x] `stack emulator status` / `stop` / `reset` resolve paths from
`STACK_EMULATOR_HOME`
- [x] Verified end-to-end on arm64 macOS under HVF (capture ~50s,
fast-resume ~6.5s)
- [x] `pnpm lint` and `pnpm typecheck` pass; stack-cli unit tests (iso +
emulator) pass
- [ ] CI green on this PR (qemu-emulator-build matrix, smoke test)
- [ ] `gh release download emulator-<branch>-latest` contains only
`stack-emulator-<arch>.qcow2` once this PR merges and publish runs

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Snapshot fast-start/resume with optional warm-snapshot assets, runtime
ISO generation, and a cached QEMU build to speed emulator setup.
* CLI: streamed artifact downloads with progress, improved release/asset
handling, stronger preflight checks, and start/status/stop emulator
commands.
* Automated secret rotation and ability to apply rotated secrets at
container startup; supervisor control socket enabled.

* **Bug Fixes**
* More robust start/stop/resume flows with automatic fallback to cold
boot and improved process-group shutdown behavior.

* **Tests**
  * New tests for CLI utilities and ISO image generation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-20 14:24:49 -07:00
BilalG1
6bc1836e66
fix(dashboard): resolve UI issues across email-* pages (#1345)
## Summary

Six UI issues found across the email-* dashboard pages, ranked by
impact, fixed here:

1. **email-sent layout** — the email log table and domain reputation
card were forced side-by-side at all widths. A fixed-width sidebar plus
a flex-1 table meant that on tablet the table got crushed, and on mobile
the row overflowed horizontally. Fix: stack vertically below `lg`, and
let the reputation card span full width on narrow viewports.
2. **Domain status enum leaks to the UI** — `<span>Status:
{domain.status}</span>` rendered raw values like `pending_dns` /
`pending_verification`. Added a `MANAGED_DOMAIN_STATUS_LABELS` map and
route through it before rendering.
3. **email-themes dialog grid cramped on mobile** — the Change Theme
dialog hardcoded `grid-cols-2`, so at 375px each theme card had ~150px
and the preview images were illegible. Changed to `grid-cols-1
sm:grid-cols-2`.
4. **Template name row overflow** — long template names pushed the Edit
Template button off the right edge of the card because the flex row had
no `min-w-0` / `truncate`. Fixed both, and made the action column
`shrink-0`.
5. **Boosted-capacity label was color-only** — during an active boost
the label used a red strikethrough for the base value and a blue number
for the boosted value with no non-color cue. Added an explicit `→` arrow
between the two numbers, `title` tooltips on each, and a visible
\"(boosted)\" marker after `/h max`.
6. **Draft progress bar overflowed at mobile width** — the 4-step
progress bar used fixed 80px connectors, giving a minimum width of
~400px that clipped off both ends at 375px. Changed connectors to `w-8
sm:w-20` (32px on mobile, 80px otherwise) so all four steps and their
labels fit below 640px.

## Before / after

Each GIF below loops \"before\" (1s) → \"after\" (1s) with a red pill in
the top-right indicating which frame is which. Full-size stills (before
+ after + extra viewports) are listed under **All screenshots** at the
bottom.

### 1. email-sent — two-column layout collapses on narrow viewports

Mobile (375px):

![email-sent
mobile](https://gist.githubusercontent.com/BilalG1/edb04740a19c3f2d048da6e602209d45/raw/gif-01-email-sent-mobile.gif)

Tablet (900px):

![email-sent
tablet](https://gist.githubusercontent.com/BilalG1/edb04740a19c3f2d048da6e602209d45/raw/gif-01-email-sent-tablet.gif)

### 2. email-settings — managed-domain status label

![domain
status](https://gist.githubusercontent.com/BilalG1/edb04740a19c3f2d048da6e602209d45/raw/gif-02-domain-status.gif)

### 3. email-themes — Change Theme dialog on mobile

![themes
mobile](https://gist.githubusercontent.com/BilalG1/edb04740a19c3f2d048da6e602209d45/raw/gif-03-themes-mobile.gif)

### 4. email-templates — long name overflow

![templates
overflow](https://gist.githubusercontent.com/BilalG1/edb04740a19c3f2d048da6e602209d45/raw/gif-04-templates-overflow.gif)

### 5. email-sent — boosted capacity label

![capacity
label](https://gist.githubusercontent.com/BilalG1/edb04740a19c3f2d048da6e602209d45/raw/gif-05-capacity-label.gif)

### 7. email-drafts — draft progress bar on mobile

![draft progress
bar](https://gist.githubusercontent.com/BilalG1/edb04740a19c3f2d048da6e602209d45/raw/gif-07-draft-progress-mobile.gif)

## Test plan

- [x] \`pnpm --filter @stackframe/dashboard lint\` — clean
- [x] \`pnpm --filter @stackframe/dashboard typecheck\` — clean
- [x] Manual verification in a browser at 375px / 900px / 1440px, light
+ dark mode, for each fixed page
- [ ] Reviewer sanity check of the remaining email-* pages
(email-outbox, email-viewer) for similar responsive regressions

## Notes

- The initial review flagged a \"white-on-white capacity boost timer\" —
on closer look the label sits on a deliberately dark `bg-zinc-900/0.82`
overlay inside the boost card, so it reads fine in light and dark mode.
Not fixing; that part of the review was a false positive.
- The initial review also flagged a missing empty state on
email-templates. Because Stack seeds built-in templates, the empty
branch is unreachable in practice — skipping that fix to avoid dead
code.

## All screenshots

Gist with all the individual before/after PNGs and the GIFs themselves:
https://gist.github.com/BilalG1/edb04740a19c3f2d048da6e602209d45

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added human-readable status labels for managed domains in domain
settings

* **Improvements**
* Enhanced responsive layouts across dashboard pages for improved mobile
experience
* Improved email capacity display with visual indicators and tooltips
for boost status
* Refined template and theme selection layouts with better text handling
and spacing

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-19 22:58:07 -07:00
BilalG1
85ae4b1c9e
Fix ClickHouse OOM in MAU query + optimize /internal/metrics route (#1344)
## Summary

Fixes the Sentry `StackAssertionError: Failed to load monthly active
users for internal metrics` crash (ClickHouse OOM at the 7.2 GiB
per-query cap) and applies two related optimizations to other queries in
the same route while here. Adds a local benchmark harness that validates
correctness and measures peak memory / duration before & after.

## Root cause (the original Sentry error)

`loadMonthlyActiveUsers` was written as `SELECT user_id … GROUP BY
user_id` and then counting in Node via a `Set`. On a large project that
ships back millions of user_ids. Two failure modes stacked:

1. **Result materialization** — every distinct user_id had to be
buffered in the server before streaming to Node (~20 MiB of result for
450k users; much more at real scale).
2. **`JSONExtract(toJSONString(data), 'is_anonymous', 'UInt8')`** — the
`toJSONString(data)` per-row re-serialization of the entire nested JSON
column, billions of times, just to pull one boolean. Dominates
bytes-read.

Combined, on a single partition read from S3-backed MergeTree, this can
exceed ClickHouse's 7.2 GiB per-query memory cap. That's exactly what
the Sentry trace showed.

## Changes

### 1. Fix MAU query (`loadMonthlyActiveUsers`)

Moved counting to the server with
`uniqExact(sipHash64(normalized_user_id))` and pulled the JS-side
normalization (`lower`, `trim`, `isUuid`) into SQL. Picked `sipHash64`
after benchmarking 7 variants — it's exact (at <<2³² users) and halves
the uniqExact hash-state vs. raw string keys.

### 2. Fix 1 — `JSONExtract(toJSONString(data), …)` → direct
`CAST(data.is_anonymous, …)`

Applied everywhere the pattern appeared in the metrics route:
- `loadDailyActiveUsers`
- the `analyticsUserJoin` subquery
- the `nonAnonymousAnalyticsUserFilter`
- `analyticsOverview:topRegion`
- `analyticsOverview:online`

Semantics preserved (`coalesce(CAST(data.is_anonymous,
'Nullable(UInt8)'), 0)` matches `JSONExtract(…, 'UInt8')` behavior when
the field is missing).

### 3. Fix 3 — server-aggregate the split queries

`loadDailyActiveUsersSplit` and `loadDailyActiveTeamsSplit` used to ship
1.2M+ `(day, user_id)` rows back to Node just so the JS could bucket
them into new / retained / reactivated. Rewrote both as one CTE-style
query that returns 31 rows (one per day in the 30-day window) with the
counts precomputed.

**Minor semantic shift** (documented inline in `route.tsx`): \"new\" is
now based on the user's first-ever `\$token-refresh` event rather than
their Postgres `signedUpAt`. Agrees for users who log in immediately
after sign-up (the common case). Disagrees for the rare edge case of an
account that existed pre-window but never generated a `\$token-refresh`
until now — old code classified as \"reactivated,\" new code classifies
as \"new.\" Judged acceptable; can be revisited.

Postgres round-trips for `ProjectUser.signedUpAt` / `Team.createdAt` are
no longer needed for the split, and the 76 MiB-ish wire ship is gone.

### 4. Benchmark harness
(`apps/backend/scripts/benchmark-internal-metrics.ts`)

Local-only tool. Three modes:
- **MAU equivalence matrix** — 13 edge cases (empty, dedup, anonymous
filter, window boundary, null user_id, non-UUID user_id, case variation,
project isolation, missing/null `is_anonymous`, wrong event_type).
Asserts OLD pipeline and NEW query return the **same set** of users, not
just the same count.
- **MAU perf** — OLD vs NEW plus 6 other candidate variants (inline
regex, UUID keys, sipHash64, HLL sketches), reads `memory_usage` /
`read_rows` / `result_bytes` from `system.query_log` for each, prints a
ranked table.
- **Full-route benchmark** (`BENCH_ROUTE_QUERIES=1`) — runs every
ClickHouse query in `/internal/metrics` in three stages (BEFORE, AFTER,
candidate OPTIMIZED) against the same seed and prints per-query deltas
plus endpoint-level totals.

Seeds under a synthetic `project_id` so real data is never touched;
cleans up on exit via `ALTER TABLE … DELETE`.

## Benchmark results

### MAU query alone

Ran at two scales; set-equality verified (new query identifies the same
individual users, not just the same count).

| seed | MAU | peak memory (old → new) | bytes read | duration |
|---|---|---|---|---|
| 500k events | 89,939 | 158.7 MiB → 46.7 MiB (**3.4×**, −70%) | 175.7
MiB → 63.0 MiB (2.8×) | 483 ms → 76 ms (**6.4×**) |
| 2.5M events | 449,990 | 439.2 MiB → 281.4 MiB (1.56×, −36%) | 865.0
MiB → 310.9 MiB (2.8×) | 783 ms → 126 ms (**6.2×**) |

MAU variant bake-off at 2.5M events (all exact, all set-equal to OLD):

| variant | memory | duration | notes |
|---|---|---|---|
| v0_old (baseline) | 440 MiB | 567 ms | — |
| v1_uniqExact_string | 284 MiB | 110 ms | naive fix |
| v3_uniqExact_toUUID | 244 MiB | 153 ms | UUID keys, slower per-row |
| **v4_uniqExact_sipHash64** | **125 MiB** | **95 ms** | **shipped** |
| v5_uniq (HLL) ~approx | 30 MiB | 86 ms | −0.25% error |
| v6_uniqCombined ~approx | 31 MiB | 67 ms | −0.15% error |

### Full `/internal/metrics` route (2.7M events, 300k users + page-views
+ clicks + teams)

Ranked by BEFORE peak memory:

| query | mem BEFORE | mem AFTER | Δ mem | dur BEFORE | dur AFTER | Δ
dur |
|---|---|---|---|---|---|---|
| analyticsOverview:topReferrers | 588.1 MiB | 411.1 MiB | 1.43× | 1833
ms | 110 ms | **16.66×** |
| analyticsOverview:totalVisitors | 584.3 MiB | 403.5 MiB | 1.45× | 1829
ms | 121 ms | 15.12× |
| analyticsOverview:dailyEvents | 584.1 MiB | 403.7 MiB | 1.45× | 1897
ms | 140 ms | 13.55× |
| loadUsersByCountry | 393.1 MiB | 385.4 MiB | ≈same | 74 ms | 80 ms |
≈same |
| loadDailyActiveUsersSplit | 363.4 MiB | 396.8 MiB | *+9%* | 1966 ms |
356 ms | 5.52× |
| analyticsOverview:topRegion | 269.9 MiB | 106.4 MiB | 2.54× | 1602 ms
| 65 ms | 24.65× |
| loadDailyActiveUsers | 268.3 MiB | 84.0 MiB | 3.19× | 1111 ms | 44 ms
| 25.25× |
| loadDailyActiveTeamsSplit | 59.6 MiB | 78.1 MiB | *+31%* | 70 ms | 123
ms | *+76%* |
| loadMonthlyActiveUsers | 54.9 MiB | 54.9 MiB | ≈same | 68 ms | 56 ms |
≈same |
| analyticsOverview:online | 18.4 MiB | 5.8 MiB | 3.17× | 58 ms | 4 ms |
14.50× |

**Endpoint-level totals**

| metric | BEFORE | AFTER | Δ |
|---|---|---|---|
| Sum peak ClickHouse memory | 3.11 GiB | 2.28 GiB | **−27%** |
| **Max query duration** (endpoint wall-clock floor) | **1966 ms** |
**356 ms** | **−82%** (5.5×) |
| Sum query duration (total CPU) | 10508 ms | 1099 ms | **−90%** (9.6×)
|
| Bytes read | 10.70 GiB | 4.55 GiB | −57% |
| Bytes shipped to Node | 94.8 MiB | 44.2 KiB | **−99.95%** |

Both split queries show a small memory *regression* at this seed size
(the new server-side window-function + self-join has its own state cost
that's near break-even with \"materialize + ship\" at 300k users); at
prod scale the 76 MiB-ship saving dominates. Duration is unambiguously
better.

## Why we don't need to drop the `analyticsUserJoin` in this PR

The benchmark includes an OPTIMIZED stage that drops the LEFT JOIN and
trusts `e.data.is_anonymous` directly, which would shave another **1.2
GiB / 1.9× duration** off the endpoint. **But we can't ship that here**
— an audit of the client tracker
(`packages/js/src/lib/stack-app/apps/implementations/event-tracker.ts`)
confirmed `is_anonymous` is never set on client-emitted `$page-view` /
`$click` events. The JOIN is currently load-bearing. A follow-up PR will
enrich `is_anonymous` at the batch ingest endpoint using
`auth.user.is_anonymous`; after one metrics-window cycle (~30 days) the
JOIN can be dropped.

## Follow-up work (out of scope for this PR)

- **Batch-endpoint enrichment** + drop the analytics-overview LEFT JOIN
(est. further −53% endpoint memory, −46% duration per the benchmark).
- **Teams-split hash-variant count mismatch** — `sipHash64(team_id)`
variant of the teams split shows a count discrepancy vs. the
string-keyed version in the benchmark. Not blocking since teams-split is
only #8 by memory; needs a root-cause pass before shipping that
particular optimization.
- **`loadUsersByCountry` window bound** — currently scans every
`$token-refresh` event ever for the tenancy (no time filter). Bounding
to 30 days would bound memory growth with project age, but changes
semantics (\"country of latest login ever\" → \"in last 30 days\").
Deferred because it's product-facing.

## Snapshot changes in `internal-metrics.test.ts.snap`

The `should return metrics data with users` test signs in 10 users
today, then deletes one of them mid-test. Two small snapshot values
change on today's date; both are just a reclassification of that single
deleted user — the total (10 active users) is unchanged.

- **`daily_active_users_split.new[today]`: 9 → 10**
All 10 users really did sign in for the first time today. The old code
only counted 9 because the deleted user's Postgres row was gone by the
time the metrics query ran, so the old classifier couldn't see they were
created today. The new query looks at ClickHouse events directly, sees
the deleted user's first event was today, and counts them as new like
everyone else.

- **`daily_active_users_split.reactivated[today]`: 1 → 0**
No user was "reactivated" today — nobody was active on an earlier day
and came back. The old "1" was the deleted user falling into this bucket
by default (the old classifier had no other rule that fit them). The new
code correctly reports zero.

Totals match either way (9 + 1 = 10 + 0). We're moving one deleted user
out of the "returning visitor" bucket and into the "brand-new user"
bucket, which is what they actually were.

## Test plan

- [x] `pnpm typecheck` and `pnpm lint` pass on the backend package
- [x] MAU equivalence matrix: 13/13 cases return the same set of users
(not just the same count) between OLD and NEW pipelines
- [x] Set-equality verified at 500k-MAU perf scale
- [x] Full-route benchmark confirms the expected memory / duration
improvements
- [ ] Sanity-check the dashboard rendering after deploy (split charts,
MAU counter, analytics overview)
- [ ] Monitor Sentry for the assertion error — should drop to zero

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Performance Improvements**
* Monthly and daily active metrics are now computed entirely server-side
for faster queries and reduced client-side processing.

* **Bug Fixes**
* More consistent handling of anonymous/missing IDs and stricter ID
filtering to improve accuracy across edge cases.

* **Tests**
* Added a comprehensive benchmark and validation harness to measure
query performance and verify result equivalence across variants.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-19 22:57:46 -07:00
BilalG1
0621ad2032
ai proxy fix (#1343)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Request sanitization now includes an extra proxy-specific
preprocessing step for safer AI proxying.
* **New Features**
* Initialization prompts centralized into a shared helper, with a
web-specific prompt variant.
* Authenticated requests can optionally route via a provided external
API key to access alternate models.
* **Chores**
* Added and exposed a preprocessing hook with a default no-op
implementation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-19 22:57:38 -07:00
Konstantin Wohlwend
f0bbdb1c34 Make access token warning just a log 2026-04-18 23:14:27 -07:00
Konstantin Wohlwend
82c923e03c waitUntil Sentry flush is complete 2026-04-18 22:28:02 -07:00
Konstantin Wohlwend
560ee4c16e Fix memory leak 2026-04-18 22:21:05 -07:00
Konstantin Wohlwend
d568ad5149 Increase Clickhouse request timeout 2026-04-18 21:46:10 -07:00
Konstantin Wohlwend
cf67d37611 Don't override 5xx errors 2026-04-18 19:31:13 -07:00
Konstantin Wohlwend
ac9707b89e Update metrics endpoint to no longer trigger global error boundary on failure
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
2026-04-18 19:08:52 -07:00
Konstantin Wohlwend
ee68908111 Skip Swift tests temporarily 2026-04-18 17:32:41 -07:00
Konstantin Wohlwend
1594ed94d5 Speed up seed script by a lot 2026-04-18 17:29:21 -07:00
Konstantin Wohlwend
f85b4f3997 Make Bulldozer SQL statements deterministic 2026-04-18 16:43:26 -07:00
Konstantin Wohlwend
8046a7dd8f Fix dashboard sidebar hover states 2026-04-18 16:18:55 -07:00
Konstantin Wohlwend
2e247dd06d Improve dashboard sidebar styling 2026-04-18 14:54:40 -07:00
Konstantin Wohlwend
fd68701097 Fix bigint serialization error on tracing 2026-04-18 14:46:03 -07:00
Konstantin Wohlwend
91fbf63f7f chore: update package versions 2026-04-18 14:20:39 -07:00
Aman Ganapathy
847d14df70
[Fix]: Assortment of Bugs with Timefold Table and Payments (#1348) 2026-04-18 14:17:24 -07:00
Konstantin Wohlwend
f4ca6cb4c7 More tracing for replication-related functions 2026-04-17 17:57:34 -07:00
Aman Ganapathy
665870a144
[Fix] Bulldozer Studio and SpaceTime DB port conflict (#1346) 2026-04-17 17:56:11 -07:00
Konstantin Wohlwend
22ae47fe73 Replace Cmd with Ctrl on Windows computers 2026-04-17 17:04:30 -07:00
Aman Ganapathy
1de8a17183
Payments bulldozer txn rework (#1315)
### Object of this PR
This PR is NOT a monolithic series of fixes for the payments suite + a
complete rework. Its aims were
a) introducing and robustly testing the bulldozer db system 
b) reworking the payments underlying architecture to use bulldozer for
correctness and scalability
c) Achieving parity with the old payments system excepting a few changes
like ensuring correctness of the ledger algo
There may still be some work to do with handling refunds, decoupling the
concepts of purchases from that of products, and some other things.

### Ledger Algorithm
This has been tuned and fixed. Item removals i.e negative item quantity
changes will apply to the soonest expiring item grant i.e positive item
quantity change. This is what is best for the user. Item grants can also
expire, and when they expire we obviate whatever is left of their
original capacity (meaning after all the removals that were applied to
it). Our ledger algo is applied via Bulldozer, so automatic
re-computation is handled when a new grant/ removal is inserted in the
middle of the existing ones.

### Things we got rid of 
* No more automatic support for default products. You can use $0 plan
provisions to accomplish the same effect but it's manual
* Negative item quantity changes (i.e item removals) no longer can have
expiries



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Enhanced payment processing pipeline with improved data consistency
and state management.
  * Advanced refund handling with comprehensive transaction tracking.
* Better tracking and management of customer item quantities and owned
products.
* Improved subscription lifecycle management including period-end
handling.

* **Bug Fixes**
  * Fixed payment data integrity verification.
  * Improved handling of edge cases in refund scenarios.

* **Chores**
  * Updated cSpell configuration with additional words.
  * Expanded developer documentation for linting workflows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
Co-authored-by: Aadesh Kheria <kheriaaadesh@gmail.com>
Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com>
2026-04-17 22:11:21 +00:00
BilalG1
8af48c1e94
fix(dashboard): correct keyboard shortcut display and HTML entity rendering (#1342)
## Summary

Two small UI bugs found while auditing `apps/dashboard` for visible
defects.

### 1. Dashboards empty state hardcoded `Cmd+K`


`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/page-client.tsx:80`

The empty state copy referenced the command palette as `Cmd+K`. The rest
of the dashboard renders the shortcut as the `⌘ K` keycap (see
`cmdk-search.tsx:1062`), so this one string was inconsistent. Replaced
with `⌘ K` to match the convention.

**Before/after flicker:**

![bug 5
flicker](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/bug5-flicker.gif)

**Pixel diff** — 3,500 diff pixels (0.270%). Changed regions: the "No
dashboards yet" description line (the Cmd+K text) and the "DEV" badge in
the bottom-right.

![bug 5 pixel
diff](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/bug5-pixeldiff.png)

| Before | After |
|---|---|
|
![before](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/before-bug5-cmdk.png)
|
![after](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/after-bug5-cmdk.png)
|

### 2. Vercel page rendered `&apos;` as raw text


`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx:168`,
`:169`, `:414`

Three string literals contained `&apos;`:

```tsx
? "You&apos;ll receive a publishable client key and a secret server key for this project."
: "You&apos;ll receive a secret server key for this project."
…
subtitle="See Vercel&apos;s documentation on environment variables for more details."
```

These are JS strings passed into props, not JSX text nodes — React only
decodes HTML entities in JSX text, so the literal characters `&apos;`
ended up in the DOM. Verified via `document.querySelector` — actual text
content was `You&apos;ll receive a secret server key for this project.`.
Replaced with a plain ASCII apostrophe.

**Before/after flicker:**

![bug 7
flicker](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/bug7-flicker.gif)

**Pixel diff** — 1,252 diff pixels (0.163%). Changed region: the
`You&apos;ll` → `You'll` line.

![bug 7 pixel
diff](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/bug7-pixeldiff.png)

| Before | After |
|---|---|
|
![before](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/before-bug7-apos.png)
|
![after](https://gist.githubusercontent.com/BilalG1/adf11369b93cf280e1af49428a5b4f89/raw/after-bug7-apos.png)
|

## Test plan

- [x] Visited `/projects/<id>/dashboards` with no dashboards — empty
state now reads `(⌘ K)`
- [x] Visited `/projects/<id>/vercel` — both the "API keys generated"
subtitle and the "Need more detail?" subtitle render `'` as a real
apostrophe
- [x] `eslint` clean on both touched files
2026-04-17 14:13:49 -07:00
Konstantin Wohlwend
b5273f7326 Clicking a dashboard category now opens its first page 2026-04-17 12:15:29 -07:00
aadesh18
5341371782
LLM MCP Flow (#1321)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Automated AI QA review pipeline and human-verified knowledge base
consulted first
* Internal MCP review tool: call log viewer, conversation replay,
add/edit/publish Q&A, knowledge editor, and analytics
  * Docs search now preserves follow-up conversation context

* **Documentation**
  * Added “Ask DeepWiki” badge to README

* **Chores**
* Added local SpacetimeDB background service and internal-tool app
scaffolding
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mantrakp04 <mantrakp@gmail.com>
Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
2026-04-15 17:57:08 +00:00
Armaan Jain
94dd22c1c5
Overview revamp (#1238) 2026-04-15 09:36:00 -07:00
Armaan Jain
654c97c56e
Onboarding redo (#1308) 2026-04-15 09:35:48 -07:00
Mantra
74f2df9c79
fix(ai): Accept header for docs-tools MCP endpoint (#1334) 2026-04-14 21:36:31 -07:00
BilalG1
c66bdfb5ae
Fix five dashboard UI issues (#1337)
## Summary

Fixes five independent UI bugs in the dashboard. Each is a narrow,
localized fix — no changes to shared table / card primitives.

### 1. Auth methods preview didn't update until save
Toggling Email/password, Magic link, or Passkey updated the switch UI
but the right-hand sign-in preview kept rendering the pre-save config
until "Save changes" was clicked. The preview was reading
`project.config` instead of the local pending state.

**Fix:** pass the computed local state (`passwordEnabled`, `otpEnabled`,
`passkeyEnabled`) into `AuthPage`'s `mockProject.config` so the preview
reflects toggles immediately.

| Before | After |
|---|---|
|
![before](b6d4f39f66/01-auth-methods-before.gif)
|
![after](b6d4f39f66/01-auth-methods-after.gif)
|

---

### 2. Email-drafts "New Draft" dropdown items stacked on two rows
Icon rendered above text in the dropdown because the icon was a child of
a non-flex inner wrapper inside `DropdownMenuItem` and phosphor icons
default to `display: block`.

**Fix:** use `DropdownMenuItem`'s built-in `icon` prop (which
absolute-positions the icon) instead of passing it as a child.

| Before | After |
|---|---|
|
![before](b6d4f39f66/02-email-drafts-before.png)
|
![after](b6d4f39f66/02-email-drafts-after.png)
|

---

### 3. Project-keys status filter: clicking options did nothing visible
`DesignDataTable` renders the toolbar outside the card when
`glassmorphic && !insideDesignCard`. The table instance was captured
once via `onTableReady`; filter clicks updated the table's internal
state (rows actually filtered to "No results") but the toolbar's parent
never re-rendered, so checkboxes, chip count, and button label stayed
frozen.

**Fix:** wrap `InternalApiKeyTable` in `DesignCard` so
`useInsideDesignCard()` returns true, `needsOwnCard` becomes false, and
the toolbar renders inside the `DataTable` where it re-renders normally.
No changes to the shared `DesignDataTable` component.

| Before | After |
|---|---|
|
![before](b6d4f39f66/03-project-keys-before.gif)
|
![after](b6d4f39f66/03-project-keys-after.gif)
|

---

### 4. Analytics "Tables" page only listed Events
`AVAILABLE_TABLES` was hardcoded to a single entry.

**Fix:** registered all 12 ClickHouse views that exist in the `default`
schema (events, users, contact_channels, teams, team_member_profiles,
team_permissions, team_invitations, email_outboxes, project_permissions,
notification_preferences, refresh_tokens, connected_accounts) with
sensible default sort columns. Widened `TableId` to `string`.

| Before | After |
|---|---|
|
![before](b6d4f39f66/04-analytics-tables-before.png)
|
![after](b6d4f39f66/04-analytics-tables-after.png)
|

---

### 5. Price input `$` prefix overlapped the number on prod
The Input composed `h-9 px-3 ... pl-7`. In production's CSS bundle order
`.px-3` declared after `.pl-7`, so `padding-left` resolved to 12px —
same as the prefix's `left-3` position — making `$` overlap the first
digit. The emulator's bundle happened to order them the other way, which
is why it only reproduced in prod. Verified with a devtools injection
that mimics the prod CSS ordering.

**Fix:** change `pl-7` → `!pl-7` in `repeating-input.tsx` so the prefix
padding wins regardless of CSS order.

| Before (prod CSS ordering) | After (same ordering) |
|---|---|
|
![before](b6d4f39f66/05-price-overlap-before.png)
|
![after](b6d4f39f66/05-price-overlap-after.png)
|

---

## Test plan

- [x] `pnpm --filter @stackframe/dashboard typecheck`
- [x] `pnpm --filter @stackframe/dashboard lint`
- [x] Manual verification of each issue against the local dev dashboard
at localhost:8101
- [ ] Reviewer: confirm no visual regressions on other `DesignDataTable`
usages (api-key-table is the only one wrapped here)
- [ ] Reviewer: confirm analytics queries on added tables work with the
signed-in user's permissions


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added 12 new analytics tables to the dashboard for enhanced data
visibility and tracking.

* **Bug Fixes**
  * Fixed input styling issue with prefix alignment.

* **Style**
* Improved visual presentation of data tables with enhanced card
styling.
  * Refined dropdown menu icon display for better UI consistency.
* Enhanced authentication preview settings to reflect current
configuration state.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-14 19:38:52 -07:00