mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
fa4f25bcdd
625 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fa4f25bcdd | Rename port prefix envvar | ||
|
|
dc24d3770a | Update Next.js package name | ||
|
|
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
|
||
|
|
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 --> [](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 --> |
||
|
|
244e7e79f2
|
Add captureWarning for warnings (#1506) | ||
|
|
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> |
||
|
|
cbdcea2a78 |
Various small docs improvements
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
|
||
|
|
4854e551a8 | More small retry fixes | ||
|
|
7dd764324a | Update CLI to use unified setup prompt | ||
|
|
018ecd1107
|
Fix client interface 4xx retry handling (#1492) | ||
|
|
f702724d53 | Fix CI/CD | ||
|
|
e02ff07515 | Fix docs base URL | ||
|
|
2f3e5d0d5a | Update LLM documentation | ||
|
|
d30962bf66 | Fix GH tokens refresh & devtool tabs | ||
|
|
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>
|
||
|
|
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 --> [](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> |
||
|
|
f6ef49a3dc | Remove source-of-truth logic | ||
|
|
ca2fa8cd10 | Remove MCP setup prompt | ||
|
|
a443ec4a68 | Small Overview page docs fixes | ||
|
|
05e22e10a3
|
Use redirectToHandler in StackHandler and disallow string default URL target (#1472) | ||
|
|
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
|
||
|
|
c6d59d0288
|
Cross domain handoffs (#1458) | ||
|
|
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.

## 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](
|
||
|
|
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 -->
[](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 -->
|
||
|
|
954ebffc31
|
fix/kim john ung prompt (#1446)
- **Update generated prompts** - **Fix React error** - **Don't show alpha apps during onboarding** - **[Docs] First-class TanStack Start in AI setup prompts + clearer env-var guidance (#1438)** - **update docs** <!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/hexclave/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Simplified setup and environment-variable guidance across docs, onboarding prompts, and setup snippets, replacing long explanatory blocks with concise inline notes. * Condensed frontend/backend examples into shorter combined examples and streamlined cloud-project instructions. * Shortened custom auth page prompts by removing verbose redirect/URL override explanations and relying on shared reminder text. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1446?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 --> |
||
|
|
07af46944b
|
[Docs] First-class TanStack Start in AI setup prompts + clearer env-var guidance (#1438)
## Summary
Two related improvements to Stack Auth's AI setup story, both driven by
`packages/stack-shared/src/ai/prompts.ts`:
### 1. Clearer env-var guidance in the cloud-project flow (existing
commit)
The previous wording suggested `STACK_PROJECT_ID` should be prefixed via
a generic _"if available, prefix with your framework's convention"_
comment, and the backend section additionally listed
`NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY` — which the SDK does not
actually read in the cloud-project setup. Agents would dutifully
fabricate that third variable.
This is now spelled out:
- The exact prefix per framework (Next.js →
`NEXT_PUBLIC_STACK_PROJECT_ID`, Vite → `VITE_STACK_PROJECT_ID`, etc.) is
given inline.
- A note clarifies that on the client, **only** the project ID is read —
there is no separate publishable / client key.
- A note clarifies that the backend setup reads exactly two variables
(`STACK_PROJECT_ID` + `STACK_SECRET_SERVER_KEY`); a third slot in
`.env.local` is wrong.
### 2. First-class TanStack Start support (new commit)
Until now `mainType: "tanstack-start"` was silently routed through
`@stackframe/react` and inherited the React-only setup steps. Agents had
to guess at the TanStack-specific bits (where to mount `StackProvider`,
what to do with `routeTree.gen.ts`, how `useUser()` behaves under SSR,
where the handler route lives).
`prompts.ts` now:
- Recognizes TanStack Start as its own `mainType` and routes the install
to `@stackframe/tanstack-start`.
- Lists TanStack Start alongside Next.js / React in the
supported-frameworks list and the package table.
- Adds three TanStack-specific steps that don't apply to vanilla React:
1. Mount `StackProvider` / `StackTheme` inside the root route's
`component` (the inner React tree), keeping `shellComponent` as the
document shell.
2. Wrap `<Outlet />` in `Suspense` inside `RootComponent`.
3. Register the Stack handler splat at `src/routes/handler/\$.tsx` with
`ssr: false`.
- Surfaces the two notes that aren't obvious from the React docs:
`routeTree.gen.ts` is generated and shouldn't be hand-edited, and
`useUser()` resolves the SSR user from TanStack Start's request cookies
for free as long as `tokenStore: \"cookie\"` is set.
The auto-generated outputs
(`docs-mintlify/guides/getting-started/setup.mdx`,
`docs-mintlify/snippets/home-prompt-island.jsx`) are regenerated from
the prompt.
### 3. tanstack-start-demo SSR-vs-client examples
Two paired routes (`/ssr` and `/client`) render the same `AuthDemoCard`
so the SSR-vs-\`ssr: false\` tradeoff is observable side-by-side. The
new \`AuthDemoCard\` shows the resolved Stack Auth user (or sign-in/up
buttons) plus the snippet that produced it. The
\`ClientMountedUserButton\` workaround in the header is dropped now that
SSR cookie reading just works, and the empty \`Suspense
fallback={null}\` in \`__root.tsx\` is replaced with a
\`RouteLoadingState\` skeleton.
## Test plan
- [ ] \`pnpm typecheck\` and \`pnpm lint\` both pass on the touched
packages (\`stack-shared\`, \`tanstack-start-demo\`).
- [ ] \`docs-mintlify/guides/getting-started/setup.mdx\` and
\`docs-mintlify/snippets/home-prompt-island.jsx\` are byte-identical to
a fresh \`scripts/generate-setup-prompt-docs.ts\` run.
- [ ] In \`tanstack-start-demo\`, \`/ssr\` renders the user card during
the server response (no flash from signed-out → signed-in), and
\`/client\` renders the empty card on first paint, then resolves to the
user after hydration.
- [ ] \`/handler/sign-in\`, \`/handler/sign-up\`, OAuth callbacks, and
password reset all render correctly through the new splat route.
- [ ] Following the new TanStack Start prompt steps from scratch in an
empty \`npm create @tanstack/start@latest\` project produces a working
sign-in flow without any extra changes.
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added TanStack Start support, provider/theme wiring, SSR and
client-only demo pages, plus an Auth demo card component.
* **Documentation**
* Updated setup guides and snippets across frameworks; clarified env-var
guidance (client reads only project ID; secret is server-only) and
removed misleading publishable-key example.
* Clarified OAuth callback and hosted-domain behavior.
* **Improvements**
* Added loading skeleton UI, refined demo navigation, and tightened
setup wording.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1438?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>
|
||
|
|
29cea48beb
|
Remote dev envs (#1435) | ||
|
|
d68631ea4f | Update GitHub URL | ||
|
|
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 -->
[](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 -->
|
||
|
|
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 --> [](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 --> |
||
|
|
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). |
||
|
|
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 | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### User detail — new session-replays card + weekly metrics | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Session replays — moved out of `/analytics` | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Project permissions — new pagination | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Other migrated surfaces | Page | Light | Dark | | --- | --- | --- | | Project picker |  |  | | Overview / setup |  |  | | Teams list |  |  | | Team permissions |  |  | | API keys |  |  | ### Scroll behaviour — new data-grid on the users list | Light | Dark | | --- | --- | |  |  | ## 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 --> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
e0c1cc5376
|
Fix null-unsafe payments config validation for partial overrides (#1363)
## Summary - Make the `branchPaymentsSchema` custom validator tolerant of partial override objects - Avoid crashing when `payments.products` or `payments.productLines` are absent during validation - Add regression tests for partial configs plus the existing missing-line and customer-type mismatch cases ## Testing - Added Vitest coverage for partial payments configs and validation failures - Lint passed for the touched schema files - Typecheck passed for `packages/stack-shared` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved validation robustness with stricter type-safety checks for payment-related data configurations. * Enhanced error messages for clearer feedback on validation failures. * **Tests** * Added comprehensive test coverage for edge cases including missing configurations and type mismatches. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
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 --> |
||
|
|
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):  ### Dev launchpad now lists the MCP service New tile at port suffix `:42`, importance 2, alongside Backend / Dashboard / Demo app:  ## 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 --> |
||
|
|
7acbd8d56d | Improved StackAssertionError error logging | ||
|
|
d69773c9df | Retry OAuth refreshes | ||
|
|
765b0f4e29
|
New setup (#1413) | ||
|
|
2e41fde9c2 | _useSession now refreshes tokens more aggressively | ||
|
|
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 -->
|
||
|
|
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" /> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
9d1eee8ab8
|
Add useCliAuthConfirmation hook and customizable cliAuthConfirm URL target (#1388)
## Summary - Extract CLI auth confirmation into a `useCliAuthConfirmation()` hook (status / error / isLoading / authorize / retry) so custom pages don't have to reimplement the protocol; `CliAuthConfirmation` now consumes the hook. - Make `cliAuthConfirm` a first-class handler URL target — resolved via `resolveHandlerUrls`, customizable per project, and used by `promptCliLogin` through a new `buildCliAuthConfirmUrl()` helper. - Move `StackContext` to its own module so the hook can be unit-tested with a test double without tripping the client-version sentinel; register `cliAuthConfirm` in custom-page prompts and the dev-tool components tab; export the hook + types from `@stackframe/stack`. ## Test plan - [ ] `pnpm typecheck` - [ ] `pnpm lint` - [ ] `pnpm --filter @stackframe/stack test cli-auth-confirm url-targets` - [ ] Manually verify default `/handler/cli-auth-confirm` flow + a project with a custom `cliAuthConfirm` URL <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Adds a CLI authentication confirmation page with clear states (invalid, authorizing, redirecting, success, error), retry action, and flows for signed-in and anonymous users. * CLI login URL generation now derives from the configured handler target and app base, improving reliability. * CLI confirmation page exposed in the components/dev UI for previewing. * **Tests** * End-to-end and unit tests covering confirmation behaviors and URL generation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b3d0ab66cc
|
fix(stack-shared): make process.env access browser-safe (#1391)
## Summary - Bare `process.env.X` accesses in `stack-shared` throw `ReferenceError: process is not defined` when the package is bundled into a browser app without a `process` shim (e.g. a plain Vite app). The most reachable offenders are in `StackAssertionError`'s constructor and `schema-fields.ts`'s Neon Basic-auth validator, both of which can run on the client during normal sign-in flows with `@stackframe/react`. - Extracted a zero-dependency `getProcessEnv` helper at `packages/stack-shared/src/utils/process-env.tsx` and routed the bare references through it. Returns `undefined` when `process` is not defined; otherwise behaves like a normal `process.env[name]` read, so Next.js/webpack inlining is unchanged on the server. - Touched: `schema-fields.ts:884` (`STACK_INTEGRATION_CLIENTS_CONFIG`), `utils/errors.tsx:81` (`NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR`), `utils/promises.tsx` (`NODE_ENV` in `runAsynchronouslyWithAlert`), `utils/esbuild.tsx:16` (`NODE_ENV`, also reordered the `typeof process` guard so the env access is unreachable in browsers). ## Why a separate helper module `utils/env.tsx` already exists but its `getEnvVariable` explicitly throws in the browser, so it can't be reused here. The new module has zero imports so it can be safely consumed from low-level utilities like `errors.tsx` without creating a cycle (env.tsx ↔ errors.tsx). ## Test plan - [x] `pnpm lint` passes - [x] `pnpm typecheck` passes - [ ] Reproduced the original failure in a Vite + `@stackframe/react` app: sign-in flow logged `ReferenceError: process is not defined` from `StackAssertionError`, plus `clientSecret must not be empty` cascading from the same path - [ ] Verify the same flow in a Vite app no longer throws once `@stackframe/react` is rebuilt against this `stack-shared` change - [ ] Confirm Next.js consumer behavior is unchanged (env vars still inlined at build time for `NEXT_PUBLIC_*`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Refactor** * Improved environment variable handling across shared utilities for enhanced browser compatibility and safety. Introduced a new utility for dynamic, browser-safe environment variable access that prevents errors in non-Node.js environments. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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 | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Users — DataGrid with seeded rows | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Transactions — new DataGridToolbar + sticky chrome | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Teams | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Email Outbox | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### Payments — Customers | Light | Dark | | --- | --- | |  |  | Widescreen: | Light | Dark | | --- | --- | |  |  | ### 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 |  |  | | Teams |  |  | | Transactions |  |  | | Payments Customers |  |  | | Email Outbox |  |  | | Analytics Tables |  |  | ### Other migrated surfaces | Page | Light | Dark | | --- | --- | --- | | Analytics Tables |  |  | | Emails |  |  | | Email Sent |  |  | | Domains |  |  | | Webhooks |  |  | | External DB Sync |  |  | ## 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> |