mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
feat/migrations
1789 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7bcc28df78 |
Add AuthDataMigrationJob model and related migration scripts
- Introduced the AuthDataMigrationJob model in the Prisma schema to manage authentication migration jobs. - Created SQL migration scripts to establish the AuthDataMigrationJob table with necessary constraints and indexes. - Implemented API routes for creating, listing, retrieving, and retrying auth migration jobs. - Added utility functions for handling encryption and decryption of migration credentials. - Developed tests to validate the functionality and constraints of the new migration job model. |
||
|
|
d2f2fb0e42
|
[codex] Fix preview dummy payments customer types (#1398)
## Summary Fixes preview dummy payments seed data so seeded products and items match their team-scoped product lines. ## Root Cause The preview seed configured `workspace` and `add_ons` product lines with `customerType: "team"`, but the products inside those lines (`starter`, `growth`, and `regression-addon`) were configured as `customerType: "user"`. Environment override writes validate against the rendered branch config, so unrelated environment updates could fail with a product/product-line customer type warning. ## Changes - Mark preview dummy payments products and included items as team-scoped. - Export the dummy payments setup helper for focused validation. - Add a regression test that validates the generated branch payments override has no config override errors or incomplete config warnings. ## Validation Passed in the original checkout with dependencies installed: - `STACK_SKIP_TEMPLATE_GENERATION=true pnpm exec vitest run --config vitest.config.ts src/lib/seed-dummy-data.test.ts --reporter=verbose --maxWorkers=1 --minWorkers=1` - `pnpm -C apps/backend lint src/lib/seed-dummy-data.ts src/lib/seed-dummy-data.test.ts` - `pnpm -C apps/backend typecheck` The temporary clean worktree used for this PR did not have `node_modules`, so dependency-backed commands were not rerun there. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Strengthened payment product configuration with tighter typing and validation * Normalized product customer types (switched relevant dummy data from user to team) for consistency * **Tests** * Added tests validating dummy payments configuration and branch/override validation * **Documentation** * Added Q&A documenting a configuration validation failure mode and required consistency for dummy payments data <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
e831972c4c
|
Move internal MCP server to backend, use Mintlify MCP for docs tools (#1389)
## Summary - Move the `/api/internal/[transport]` MCP route from the docs app to the backend, so the public `ask_stack_auth` MCP tool is served from the same origin as the AI query API it proxies to. - Replace the bespoke docs-tools HTTP client in `apps/backend/src/lib/ai/tools/docs.ts` with an `@ai-sdk/mcp` client that talks to Mintlify's generated MCP server. The backend AI agent now consumes Mintlify's lower-level search/fetch tools directly instead of going through the docs app. - Swap `STACK_DOCS_INTERNAL_BASE_URL` for `STACK_MINTLIFY_MCP_URL` (defaults to the Mintlify-hosted MCP URL). - Move the `@vercel/mcp-adapter` dependency from `docs` to `apps/backend`. ## Test plan - [ ] `pnpm typecheck` - [ ] `pnpm lint` - [ ] e2e: new `apps/e2e/tests/backend/endpoints/api/v1/internal/mcp.test.ts` covers `tools/list` and validation on `tools/call` - [ ] Manual: hit `POST /api/internal/mcp` on the backend and confirm `ask_stack_auth` is listed and callable - [ ] Manual: confirm backend AI agent docs tools resolve via the Mintlify MCP URL <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Backend docs tooling now uses a Mintlify MCP server for documentation tools and discovery. * **Chores** * Development environment variables updated to point to the Mintlify MCP endpoint. * Backend dependency added to support MCP integration; docs package dependency removed. * **Tests** * Added end-to-end tests for the internal MCP endpoint and tool validation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
ed8961069c
|
fix(dashboard): UI bug fixes (#1377)
## Summary Rolling PR for dashboard UI bug fixes. Each fix is appended to the **Fix log** below with before/after screenshots. This PR stays open until we batch-merge or split. --- ## Fix log ### 1. Hide Alpha/Beta stage badges in onboarding "Select apps" tooltip **Bug:** On the new-project onboarding, hovering an app card showed an "Alpha" or "Beta" stage badge next to the app name in the tooltip. These shouldn't be surfaced on the onboarding step. **Fix:** Removed the stage badge from the onboarding app-card tooltip only. The "Required" badge is preserved, and stage badges on other surfaces (app management, app store, command palette) are unchanged. #### Before / After — Beta (Payments) | Before | After | | --- | --- | |  |  | #### Before / After — Alpha (Onboarding) | Before | After | | --- | --- | |  |  | --- ### 2. Eliminate full-page flash when advancing onboarding steps **Bug:** Moving between onboarding steps (e.g. Configure authentication → Select email theme) briefly blanked out the entire page — only the navbar remained visible for roughly two seconds — before the next step rendered. It felt like a complete browser reload. **Fix:** Contained the suspension inside the wizard. A local Suspense boundary around the onboarding page means that when any data cache refresh fires during the step advance, the suspension no longer bubbles up to the site-wide loading indicator. The step-advance state update is also marked as a React transition, so the current step stays rendered until the next step is ready to commit. Net effect: the previous step is visible throughout the save, then the next step swaps in without a blank frame. #### Before — full blank flash mid-transition | Auth step (start) | Mid-transition (blank) | Email theme step (end) | | --- | --- | --- | |  |  |  | #### After — previous step stays visible, no blank frame | Auth step (start) | Mid-transition (auth stays visible) | Email theme step (end) | | --- | --- | --- | |  |  |  | --- ### 3. Add a subtle back arrow to the onboarding timeline **Bug:** The only way to return to a previous step in the new-project onboarding was to click one of the tiny completed-step dots at the bottom of the page — not discoverable, and easy to miss. **Fix:** Added a small muted left-arrow next to the timeline dots. Clicking it advances back one step. It's absolute-positioned so the dots stay perfectly centered, and it hides itself on the first step (where there's nothing to go back to). #### Before / After — Select apps step | Before — dots only | After — back arrow next to the dots | | --- | --- | |  |  | ### 4. Unify onboarding step styling — cards everywhere, no glassmorphism **Bug:** Step-to-step styling in the onboarding was inconsistent. The Config and Email-theme steps used a glassmorphic surround (`backdrop-blur`, translucent whites) while the other steps used solid cards. Advancing from auth to email made it look like the visual language had changed mid-flow. **Fix:** Dropped the glassmorphic variants from the onboarding wizard. The config-choice option cards, the email-theme container, and the `ModeNotImplementedCard` surround all now use the same solid card treatment (`bg-white/90` light, `bg-white/[0.06]` dark, with subtle ring). One consistent surface across every step. #### Before / After — Config choice step | Before — glassmorphic | After — solid card | | --- | --- | |  |  | #### Before / After — Email theme step | Before — glassmorphic | After — solid card | | --- | --- | |  |  | ### 5. Add "Copy prompt" button on the project setup page **Bug:** The post-project-creation setup page surfaces a terminal command for every framework (Next.js, React, JS, Python), but there was no one-click handoff for users who drive their setup through an AI agent. Users had to manually copy the command, figure out whether the Stack Auth MCP server got registered, and add it themselves if not. **Fix:** Added a compact **✦ Copy prompt** button at the top-right above the steps list. Clicking it copies a framework-aware prompt to the clipboard — the prompt tells the user's AI agent to run the install command for the currently-selected framework, then verify the Stack Auth MCP server (`stack-auth`, transport `http`, `https://mcp.stack-auth.com/`) is registered in its client config and add it manually if the install didn't. #### Before / After — Project setup page | Before — no AI handoff | After — "Copy prompt" at the top-right | | --- | --- | |  |  | ### 6. Disable email theme cards while the onboarding step is saving **Bug:** On the "Select an email theme" step, the theme cards stayed clickable after clicking Continue. Because we keep the previous step visible during the step-advance transition (fix #2), users could click through to a different theme mid-save — the server would then commit whatever selection was active at click time, not the one on screen when Continue was pressed. **Fix:** Added `disabled={saving}` to the email theme buttons, matching the same pattern the config-choice, apps-selection, and auth-setup steps already follow. Added `disabled:cursor-not-allowed disabled:opacity-60` so users get a clear visual signal that the cards are locked while the save is in flight. --- <!-- Append new fixes above this line. Template: ### N. <title> **Bug:** … **Fix:** … #### Before / After | Before | After | | --- | --- | |  |  | --> ## Test plan - [ ] Load the new-project onboarding "Select apps" step and hover every app card — no Alpha/Beta badge appears. - [ ] Hover a required app — "Required" badge still appears. - [ ] Confirm app management tooltips, app store detail page, and command palette still show stage badges (out of scope for this PR). - [ ] Drive the onboarding from Configure authentication to Select email theme — the auth panel stays rendered throughout the save phase and the email panel swaps in without the site-wide loading indicator or a blank content area. - [ ] Repeat for other step transitions (Config → Apps, Apps → Auth, Email → Domain, Domain → Payments) — same seamless behavior. - [ ] From any step after Config, the back arrow appears to the left of the dots. Clicking it goes back one step. On the first step, the arrow is not rendered. - [ ] Walk through every onboarding step. Container surface is visually consistent across steps — no glassmorphic/card mismatch between Config, Apps, Auth, Email Theme, Payments. - [ ] On the project setup page, the "Copy prompt" button appears above the steps (top-right). Clicking it copies the prompt for the currently-selected framework (Next.js / React / JS / Python) and shows a success toast. - [ ] On the "Select an email theme" step, click Continue — the three theme cards become visibly dimmed (`opacity-60`, `cursor-not-allowed`) for the duration of the save and don't respond to clicks. Once the next step renders they stop being visible anyway. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added back navigation to onboarding wizard steps. * Added "Copy prompt" button for framework-aware terminal commands with MCP verification. * Added loading indicator during asynchronous operations. * **UI/UX Improvements** * Updated card styling for unselected options. * Disabled email theme selection during save operations. * Removed stage badges (Alpha/Beta) from app cards. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
e2dc5f5ee0
|
[codex] fix OAuth redirect contract (#1393)
## Summary - Route browser OAuth redirects through the configured `redirectMethod` instead of hardcoded `window.location` calls. - Keep OAuth redirect APIs pending after navigation starts, including custom redirect methods. - Add `cliAuthConfirm` handler URL metadata and custom-page prompt coverage. - Update SDK spec text for browser OAuth callback and `returnTo` behavior. ## Root Cause OAuth helpers previously combined URL construction with direct browser navigation. That bypassed configured redirect methods and made it too easy for public redirect APIs to resolve after navigation started. ## Impact Browser SDK consumers get consistent redirect behavior across built-in and custom navigation methods. `returnTo` is handled as the post-callback destination while the OAuth callback URL remains fixed to the configured handler route. ## Validation - `pnpm test run packages/template/src/lib/auth.test.ts` - `pnpm test run apps/e2e/tests/js/oauth.test.ts` - `pnpm -C packages/template lint` - `pnpm -C apps/e2e lint` - `pnpm -C packages/template typecheck` - `pnpm -C apps/e2e typecheck` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added CLI authorization confirmation page/flow for terminal-based auth. * Added optional returnTo parameter for OAuth to control post-auth redirects. * Exposed configurable redirect behavior so apps follow the chosen redirect method. * **Bug Fixes** * OAuth callback now uses app navigation/queued redirects and shows a fallback link instead of forcing location.assign. * **Tests** * Added unit and e2e tests covering OAuth URL generation, scope handling, and CLI auth confirmation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
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 --> |
||
|
|
0207721f68
|
fix(dashboard): improve analytics replay replayer lifecycle (#1349)
## Summary Improves reliability of the session replay viewer on the project analytics replays page by tracking replayer staleness, coordinating pause/restart with effects, and cleaning up instances to avoid leaks. ## Changes - Add `isReplayerStale` and wire replayer lifecycle into `executeEffects` so playback and pause stay in sync with the replayer state. - Pause/restart and teardown when the replayer becomes stale or unmounts. ## Test plan - [ ] Open a project’s **Analytics → Replays**, load a replay, scrub timeline, pause/resume, and switch replays; confirm no stuck playback or console errors. - [ ] `pnpm lint` / `pnpm typecheck` on touched packages if CI does not cover. ## Notes Small `CLAUDE.md` tweak included in the same commit. Made with [Cursor](https://cursor.com) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Disabled automatic session recording in the dashboard. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
a82097db62
|
refactor(dashboard): use getEnabledAppIds on metrics page (#1394)
## Summary Uses the shared `getEnabledAppIds` helper from `@/lib/apps-utils` instead of manually filtering installed apps with `typedEntries` on the project metrics page. ## Why Keeps enabled-app logic consistent with other dashboard code paths and slightly reduces duplication. ## Test plan - [ ] Smoke: open project metrics / overview and confirm installed app-dependent UI (e.g. analytics) still behaves as before. Made with [Cursor](https://cursor.com) |
||
|
|
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> |
||
|
|
04d57d91ed
|
fix(emulator): move mock OAuth off 8114 to avoid pnpm dev conflict (#1385)
## Summary
- The emulator's mock OAuth server bound to `${PORT_PREFIX}14` (8114)
inside the VM and the host forwarded the same port, colliding with `pnpm
dev`'s mock-oauth-server on 8114.
- Moves the emulator's mock OAuth to `EMULATOR_MOCK_OAUTH_PORT` (default
`26704`, joining the existing `267xx` host port block) and binds the
VM-internal mock to the same port. Same port on both sides keeps the
OIDC issuer URL (`http://localhost:26704`) resolvable identically from
the browser and from the backend inside the VM.
- Plumbed via `runtime-config.iso` as
`STACK_EMULATOR_MOCK_OAUTH_HOST_PORT`, read by cloud-init into
`STACK_OAUTH_MOCK_URL` + new `STACK_OAUTH_MOCK_PORT`;
`mock-oauth-server` now prefers `STACK_OAUTH_MOCK_PORT` so `pnpm dev`
(which doesn't set it) stays on 8114.
## Files
- `docker/local-emulator/qemu/run-emulator.sh` — new
`EMULATOR_MOCK_OAUTH_PORT`, hostfwd/ensure_ports_free/runtime.env
updates
- `docker/local-emulator/qemu/cloud-init/emulator/user-data` — reads the
host port, sets `STACK_OAUTH_MOCK_URL` + `STACK_OAUTH_MOCK_PORT`
- `apps/mock-oauth-server/src/index.ts` — honors `STACK_OAUTH_MOCK_PORT`
- `packages/stack-cli/src/commands/emulator.ts` — default + runtime.env
entry
## Test plan
- [ ] `pnpm emulator:build` succeeds and new snapshot boots
- [ ] `stack emulator start` with `pnpm dev` running on 8114 — no port
collision
- [ ] OAuth sign-in via mock provider completes end-to-end in the
emulator
- [ ] `pnpm dev` mock OAuth unchanged (still 8114)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* The mock OAuth server port is now configurable in the local emulator
with a sensible default, allowing custom port assignments via
environment variable.
* **Improvements**
* Updated port forwarding and environment variable handling to ensure
consistent mock OAuth endpoint configuration across host and guest
systems in the emulator.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
2f719903b1
|
Redesign Email Server settings + managed domain flow (#1373)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary Rewrites the **Email Server** section of the project email settings page and the managed-domain setup flow. Replaces the dropdown + conditional-fields layout with a visual four-card picker, a clearer unsaved-state model, a stepper dialog for managed-domain onboarding, and a consistent tracked-domains list. Also fixes two data-correctness bugs in the managed-domain backend. ## Walkthrough (2×, dead-frames trimmed)  ## Before The saved state was a minimal dropdown, but choosing Custom SMTP / Resend revealed a long conditional form with a hidden gear toggle for server config, no clear "what is saved" signal, and a separate dialog pattern for managed domains. | Saved (Managed) | Custom SMTP selected | |---|---| |  |  | ## After — Provider cards Four visual cards (Stack Shared, Managed Domain, Resend, Custom SMTP) with updated copy. The saved provider shows a green **Current** pill; the card the user is previewing shows an amber dashed **Draft** pill. An amber unsaved-changes banner appears between the picker and the form when state diverges from saved, so it is unambiguous that a click is not yet committed. | Saved state | Previewing a different provider | |---|---| |  |  | Copy changes: - **Stack Shared** — "Only default emails — no custom templates, themes, or sender identity." (was: "Shared (noreply@stackframe.co)") - **Managed Domain** — "Bring your own domain. You add DNS records; we handle signing & delivery." (was: "Managed (via managed domain setup)") - **Resend** uses the official Resend brand mark (light/dark variants in `apps/dashboard/public/assets/`) ## After — Managed domain list + stepper dialog Selecting **Managed Domain** immediately shows the tracked-domain list with an **Add domain** button. Each row reflects real status (Active / Verified / Waiting for DNS / Verifying / Failed). Exactly one domain can be **Active** — the one matching the saved email config; every other verified/applied domain shows a **Use this domain** button so switching is always possible. Adding a domain opens a 3-stage dialog with a horizontal stepper (Verify is right-aligned for the final step). Stage 2 replaces the old bare NS-list with a proper **Type / Name / Content** DNS records table with per-row copy buttons. | Tracked domains list | DNS records table | |---|---| |  |  | ## Bug fixes - **Backend: applying a managed domain did not demote previously-applied ones.** Multiple rows could end up with status `APPLIED` even though only one could be in the saved config. New helper `demoteOtherAppliedManagedEmailDomains({ tenancyId, keepId })` runs inside `applyManagedEmailProvider` to demote all other applied rows in the tenancy back to `VERIFIED` before marking the new one. - **Frontend: "Use this domain" only appeared for `status === verified`.** A domain that had been applied then replaced could never be re-applied from the UI. Button now appears for any `verified` or `applied` row that is not currently in use; the **Active** label is derived from config match instead of DB status. - **Dev mock onboarding now mirrors production timing.** `shouldUseMockManagedEmailOnboarding()` used to insert domains as `verified` synchronously. Now the domain is created as `pending_verification`, and a fire-and-forget `runAsynchronously(() => wait(1000))` updates it to `verified` — mirroring the real Resend webhook flow so the UI states (pending → verifying → verified) are exercised in local dev. ## Test plan - [ ] Cards: clicking each card shows `Draft` pill + amber banner; Discard restores; Save commits and flips `Current` to the new card - [ ] Managed: Add domain → stage 1 input → stage 2 DNS table + copy → Check verification flips to stage 3 → Use this domain sets it Active and demotes the previously-active domain in the list - [ ] Managed: clicking **Use this domain** on a non-active verified row makes it Active and the previously-active row back to Verified - [ ] Shared / Resend / SMTP: existing save + test-email flows still work (logic preserved verbatim) - [ ] `pnpm typecheck` (dashboard + backend) and `pnpm lint` pass <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Redesigned email domain setup flow with multi-step verification dialog * Added copy-to-clipboard for DNS records * Enhanced provider selection interface with improved visual presentation * Onboarding now shows initial "pending verification" state and completes verification asynchronously * **Bug Fixes** * Ensures only one managed domain becomes active when applying a domain * Improved error handling for email configuration saves * **Tests** * Updated end-to-end tests to reflect async verification timing <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
4a2595d9f7
|
Classify ClickHouse NO_COMMON_TYPE (386) as unsafe (#1380)
## Summary - Add ClickHouse error code `386` (`NO_COMMON_TYPE`) to `UNSAFE_CLICKHOUSE_ERROR_CODES` in `apps/backend/src/lib/clickhouse-errors.ts`. This stops the Sentry `StackAssertionError` (`Unknown Clickhouse error: code 386 not in safe or unsafe codes`) that was firing whenever an admin wrote a query like `SELECT [1, 'a']` or `SELECT if(1, 'a', 1)`, while keeping the raw error message out of prod responses. - Add two e2e regression tests: one against the cross-project `analytics_internal.users` table, and one against `system.query_log`, to pin that 386 is wrapped with the generic `Error during execution of this query.` message in prod (full detail only surfaces in dev/test). ## Why unsafe, not safe Both callers of `getSafeClickhouseErrorMessage` (`apps/backend/src/app/api/latest/internal/analytics/query/route.ts:59` and `apps/backend/src/lib/ai/tools/sql-query.ts:80`) execute caller-authored SQL under `readonly: "1"` with `SQL_project_id`/`SQL_branch_id` scoping. The ClickHouse client runs under a `limited_user` whose grants restrict most tables — but ClickHouse resolves types **before** enforcing ACL. That means a query like `SELECT if(1, query, 1) FROM system.query_log` surfaces code 386 with a message like `There is no supertype for types String, UInt8 ...`, leaking that `system.query_log.query` is a `String` — schema info from a table the caller can't actually read. This is the same type-before-ACL class as code 43 (`ILLEGAL_TYPE_OF_ARGUMENT`), which is already classified unsafe. Classifying 386 as unsafe keeps the defense-in-depth consistent: if per-customer tables are ever introduced and grants don't block reference-resolution in time, 386 won't leak their schema. Cost: in prod, an admin writing a malformed type-mismatch query sees only `Error during execution of this query.` instead of the supertype hint. Dev and test environments still show the full error via the existing `getNodeEnvironment()` branch, so local iteration is unaffected. ## Test plan - [x] `pnpm test run apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts` — all 64 tests pass, including the two 386 regression tests. - [ ] Monitor Sentry after deploy to confirm the `unknown-clickhouse-error-for-query` events for code 386 stop firing. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved handling of a ClickHouse type-mismatch error to prevent exposure of sensitive data and ensure sanitized error responses. * **Tests** * Added regression tests that verify error responses are sanitized, return consistent error codes, and include expected headers without leaking internal details. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
cbd945e3a6
|
[codex] Fix Neon malformed Basic auth validation (#1381)
## What changed This fixes Sentry issue [STACK-BACKEND-1A3](https://stackframe-pw.sentry.io/issues/7436639623/?project=4507442898272256&query=is%3Aunresolved&referrer=issue-stream&seerDrawer=true). A request with this malformed header: ```http Authorization: Basic ``` used to crash the Neon auth validator with a `StackAssertionError`, which turned a bad client request into a 500. The fix makes `neonAuthorizationHeaderSchema` only validate Neon client credentials after the Basic auth header successfully decodes. If decoding fails, the Neon-specific validator returns `true` and lets `basicAuthorizationHeaderSchema` produce the intended 400 schema error: `Authorization header must be in the format "Basic <base64>"`. ## Reviewer walkthrough There are two checks chained together: 1. `basicAuthorizationHeaderSchema` checks that the header is structurally valid Basic auth. 2. `neonAuthorizationHeaderSchema` checks that the decoded `client_id:client_secret` matches a configured Neon client. Yup may still run the second check after the first one has failed, because route validation collects errors with `abortEarly: false`. The old code assumed the first check had already passed and called `throwErr(...)` when decoding returned `null`. This PR changes that path to return `true`, because the format error is already owned by the first check. ## Tests - `pnpm -C packages/stack-shared exec vitest run --maxWorkers=1 --minWorkers=1 src/schema-fields.ts` - `pnpm -C apps/e2e exec vitest run --maxWorkers=1 --minWorkers=1 tests/backend/endpoints/api/v1/integrations/neon/projects/transfer.test.ts -t "malformed"` - `pnpm -C packages/stack-shared lint` - `pnpm -C packages/stack-shared typecheck` - `pnpm -C apps/e2e lint` - `pnpm -C apps/e2e typecheck` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Enhanced authorization header validation in API endpoints with improved error handling, ensuring malformed credentials return clear, specific validation error messages. * **Tests** * Added comprehensive end-to-end test coverage for API request validation, including edge cases for authorization headers. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
a132dd23f9
|
fix: refresh-token P2025 race with concurrent sign-out (#1372)
## Summary - Fixes Sentry [STACK-BACKEND-146](https://stackframe-pw.sentry.io/issues/7377768662/): `PrismaClientKnownRequestError` P2025 on `projectUserRefreshToken.update()` during token refresh. - Root cause: `generateAccessTokenFromRefreshTokenIfValid` (`apps/backend/src/lib/tokens.tsx`) reads the refresh-token row upstream, then issues `.update(...)` on it (and on `projectUser`) inside a `Promise.all`. If a concurrent sign-out (`DELETE /auth/sessions/current`), session revoke, password change, or user deletion removes the row between the read and the update, Prisma throws P2025 and the refresh endpoint 500s. ## Changes - `apps/backend/src/lib/tokens.tsx` — swap the two `.update(...)`s for `.updateMany(...)` so a missing row is a no-op, then re-check the refresh token still exists; return `null` if it doesn't. The refresh route already maps `null` -> `KnownErrors.RefreshTokenNotFoundOrExpired` (401), which is the correct user-facing behavior for a just-revoked session. - `apps/backend/src/oauth/model.tsx` — in `generateAccessToken`, replace the "ultra-rare race condition" `throwErr` fallback with `throw new KnownErrors.RefreshTokenNotFoundOrExpired()` so concurrent sign-out during an OAuth `refresh_token` grant returns a clean 401 instead of 500. - `apps/e2e/tests/backend/endpoints/api/v1/auth/sessions/current/refresh-race.test.ts` — new regression test that fires `POST /auth/sessions/current/refresh` and `DELETE /auth/sessions/current` concurrently with the same refresh token. Before the fix it 500s on the first iteration; after, it passes in ~12s. ## Test plan - [x] New regression test passes locally. - [x] Existing `auth/sessions/**` + `auth/oauth/token.test.ts` still pass (27 tests, 3 todo, 0 failed). - [ ] CI green. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Refresh flows now detect a revoked or removed refresh token during concurrent operations and stop cleanly, preventing issuance of an access token from stale data. * A specific refresh-token-not-found/expired error is returned instead of a generic failure when refresh cannot proceed. * **Tests** * Added E2E tests exercising concurrent refresh vs sign-out to prevent race-condition crashes and validate safe handling of competing requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
982b8fb2d9
|
Simplify sign-up rules tester dialog (#1369)
## Summary
The sign-up rules tester dialog was dense and hard to parse: a
two-column layout crammed 8 input fields against 4 stacked result panels
(Outcome, Triggered rules, Evaluation trace, Normalized context), and
used technical jargon ("Turnstile override", "Normalized context",
"Evaluation trace") without much hierarchy. This PR reworks it around
the user's actual question — *"will this sign-up be allowed?"* — and
moves the entrypoint somewhere more discoverable.
## What changed
### 1. Dialog UI — essentials-first layout
- Only **Email** and **Sign-up method** are shown upfront.
- Everything else (OAuth provider, Country, Bot / free-trial-abuse
scores, Turnstile) is hidden behind a single **Advanced options**
collapsible panel. The label previews what's inside, so users know when
they need to expand it.
- Results are outcome-first: a large green/red hero card with a check/X
icon and a plain-English decision ("Sign-up would be allowed"). Matched
rules and resolved context are tucked into `<details>` sections below.
- Removed the "Fill out the form above…" placeholder — it added clutter
without adding info.
### 2. Loading → result transition
- The outcome card now mounts **immediately** when Run test is clicked.
While the request is in flight it shows a neutral gray card with a
spinning `CircleNotchIcon` and "Running test…".
- When the result arrives, the card's border/background transitions over
500ms to green or red, the spinner fades out, and the check/X fades in.
Matched rules and resolved context slide down underneath via a
`grid-rows-[0fr→1fr]` animation.
### 3. Entry-point moved to the page header
- "Open tester" now sits **next to Add rule** in the header (secondary
variant, same size).
- Removed the dedicated "Test rules" card at the bottom of the page — it
was using real estate for something a button can do.
### 4. Code cleanup
- Dropped three exploratory variants (wizard, inspector, the original
complex card) that were temporarily in the file during design
exploration.
- Extracted `useTestRulesState()` to encapsulate state + API call, so
the card is purely presentational.
## Why
The tester is an admin-only debugging tool, so it lives or dies by how
fast someone can glance at it and answer *"would this sign-up go
through?"*. The old dialog asked readers to visually parse two columns
and seven fields just to find the outcome. The new layout answers that
question in the first card.
## Walkthrough

21s demo (2x speed): page → open tester → type email → Run test →
loading spinner transitions into the green decision card.
[Download
MP4](https://gist.githubusercontent.com/BilalG1/67639d1590ac172880dc705a027560d3/raw/tester-flow.mp4)
· [Gist with all
media](https://gist.github.com/BilalG1/67639d1590ac172880dc705a027560d3)
## Before / After
### Original tester

### New header layout
"Open tester" next to "Add rule"; no more bottom card.

### New tester dialog — initial
Just Email + Sign-up method. Advanced options collapsed.

### New tester dialog — mid-run (loading)
Outcome card mounts with a spinner while the request is in-flight.

### New tester dialog — result
Outcome hero transitions to green; matched rules + resolved context
collapsibles underneath.

## Test plan
- [x] `pnpm typecheck` (dashboard) passes
- [x] `pnpm lint` (dashboard) passes
- [x] Manually exercised the tester against a configured rule
(`emailDomain.endsWith("tempmail.com")`) with Advanced options both open
and closed
- [x] Verified the loading → green/red transition under artificial
latency (1.2s)
- [x] Verified the "Open tester" button sits next to "Add rule" and the
bottom card is gone
## Scope notes
- No backend, schema, or API changes. Only touches
`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx`.
- The existing analytics / trigger-history / rule-editor code is
untouched.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Advanced testing options now available in a collapsible panel
* Enhanced test results visualization with detailed rule evaluation
display
* **UI/UX Improvements**
* Test trigger button relocated to main action area
* Larger, repositioned "Run test" button
* Reorganized results display with collapsible sections for rules and
context details
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Bilal Godil <bilal@stack-auth.com>
|
||
|
|
7957de4182
|
fix(email-queue): recover stuck sending without duplicate retry (#1356)
## Summary Email outbox rows can get stuck in `SENDING` if a worker dies after setting `startedSendingAt` but before finishing or unclaiming. This change adds `recoverEmailsStuckInSending`, which runs each email queue step and marks rows past the stuck timeout as **terminal server errors** with delivery status unknown, **without** scheduling an automatic retry (to avoid duplicate sends if the provider already accepted the message). ## Changes - **`recoverEmailsStuckInSending`**: updates stuck rows with `finishedSendingAt`, `canHaveDeliveryInfo: false`, and server error fields; emits Sentry via `captureError` when any rows are recovered. - **Tests**: `email-queue-step.test.tsx` covers recovery of old `startedSendingAt`, no-op for recent sends, and idempotency (second pass does not re-queue). ## Test plan - [ ] `pnpm` / vitest for `apps/backend/src/lib/email-queue-step.test.tsx` (requires dev DB like other integration tests in this package) Made with [Cursor](https://cursor.com) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Email reliability: messages that remained stuck in sending are now automatically marked as terminal failures, assigned standardized error details, cleared from retry scheduling, prevented from receiving delivery info, and recovery emits an alert only when actual work occurs. Recovery is safe to run repeatedly (idempotent). * **Tests** * Added integration tests validating recovery behavior, proper field updates, and idempotency. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
94541c4a94
|
fix(dashboard): Restricted row styling + Replays empty state (#1366)
## Summary Two small UI polish fixes in `apps/dashboard`: 1. **User detail page** — the **Restricted** field now visually matches its sibling fields (`User ID`, `Display name`, `Primary email`, etc.) by reusing the same input-box appearance (`rounded-xl` border, ring, shadow, `h-8`). Previously it rendered as a bare button with `rounded-md` hover styling, which looked out of place in the user details grid. 2. **Analytics → Replays page** — the empty state previously read just *"No session replays yet"* with no guidance. It now shows a short description of what session replays are, and links out to the docs (`https://docs.stack-auth.com/docs/apps/analytics`) so new users can discover more. ## Files changed - [`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx`](https://github.com/stack-auth/stack-auth/blob/fix/ui-bugs-users-analytics/apps/dashboard/src/app/%28main%29/%28protected%29/projects/%5BprojectId%5D/users/%5BuserId%5D/page-client.tsx) — `RestrictedStatusRow` button now styled to mirror the read-only `EditableInput` look. - [`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/replays/page-client.tsx`](https://github.com/stack-auth/stack-auth/blob/fix/ui-bugs-users-analytics/apps/dashboard/src/app/%28main%29/%28protected%29/projects/%5BprojectId%5D/analytics/replays/page-client.tsx) — empty state now includes a description and a `StyledLink` to the docs. --- ## Bug 1 — Restricted row no longer visually orphaned Before, the *Restricted* row's value (`No`) was just plain text inside the grid; every other row (User ID, Display name, Primary email, Password, 2-factor auth, Signed up at, Risk scores, Sign-up country code) was rendered inside a styled input box. After the fix, *Restricted* uses the same boxed style — the row is still clickable and still opens the existing restriction dialog. ### Before / after toggle (full page)  ### Cropped view of the changed region (clearer)  ### Wipe transition  ### Fade transition  ### Pixel diff (only the Restricted cell changes)  --- ## Bug 2 — Replays empty state explains itself Before, an empty replays workspace showed only *"No session replays yet"*. Users had no signal that there is anything they need to do, or where to look. After the fix, the empty state explains what session replays are, hints that replays will appear once captured, and links to the relevant docs page. > Session replays let you watch how users interact with your app. Replays will appear here once your project starts capturing them. > > [Learn more in the docs](https://docs.stack-auth.com/docs/apps/analytics) ### Before / after toggle (full page)  ### Cropped view of the empty state  ### Wipe transition  ### Fade transition  ### Pixel diff  --- ## Test plan - [x] `pnpm --filter @stackframe/dashboard run lint` passes - [x] `pnpm --filter @stackframe/dashboard run typecheck` passes - [x] Manual verification on `localhost:8101`: - [x] User detail page renders Restricted with the same input-box style as siblings - [x] Clicking Restricted still opens the existing restriction dialog - [x] Replays empty state shows description + working docs link - [x] Light mode visually verified (dark mode untouched, classes are dark-mode-aware) ## Notes for reviewers - No change to `RestrictionDialog`, `getRestrictionReasonText`, or any restriction logic — this is purely visual. - The replays empty-state copy keeps the existing `MonitorPlayIcon` and centered layout; only added the description paragraph and the `StyledLink` (which is already imported in this file). - Comparison assets (toggles / fades / wipes / pixel diffs) are hosted in [this gist](https://gist.github.com/BilalG1/eb9ca0eeec88357728127fd4d759fa17) for reference. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Style** * Improved analytics empty state: centered, constrained layout; clearer primary text, added muted secondary explanatory copy and an external documentation link that opens in a new tab. * Restyled restricted-user control: refreshed appearance and spacing, truncation for long values, and stronger hover/focus feedback while preserving existing behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
0532a18c36
|
fix(dashboard): wrap "Block new purchases" toggle in a Card (#1364)
## Summary The **Block new purchases** toggle on the Payments → Settings page was visually out of place: it rendered as a bare `SettingSwitch` outside the `max-w-3xl` settings column, while every neighboring setting (Stripe Connection, Test Mode, Payment Methods, Platform-Managed Methods) was a full-width `Card`. This PR wraps it in a `Card` that matches the existing `TestModeToggle` pattern so it inherits the same width constraint, border, padding, title/description structure, and state-colored icon badge. **File changed:** [`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/settings/page-client.tsx`](https://github.com/stack-auth/stack-auth/blob/fix/payments-block-new-purchases-card/apps/dashboard/src/app/(main)/(protected)/projects/%5BprojectId%5D/payments/settings/page-client.tsx) ## What was wrong Two concrete mismatches with the rest of the page: 1. **Wrong container.** The `SettingSwitch` was a direct child of `<PageLayout>` rather than the `<div className="space-y-6 max-w-3xl">` column that wraps the other settings — so it stretched to the full page width instead of the 3xl column and broke the vertical rhythm (no consistent `space-y-6` gap from the card above). 2. **Wrong style primitive.** It used the bare `SettingSwitch` row component instead of a `Card` + `CardHeader`/`CardTitle`/`CardDescription`/`CardContent` structure — so there was no border, no heading hierarchy, and no state-colored icon badge, which every other setting on the page has. ## Fix - Moved the block inside the `space-y-6 max-w-3xl` column so it's constrained and spaced like its siblings. - Replaced the `SettingSwitch` with a `Card` mirroring `TestModeToggle`: - `CardHeader` with `CardTitle` (\"Block New Purchases\") and `CardDescription` (\"Stops new checkouts while keeping existing subscriptions active.\"). - `CardContent` with an icon badge (`ProhibitIcon`) that turns red when blocking is active, plus a short \"Block new purchases\" label and the `Switch`. - Copy is intentionally minimal: one title, one sentence of description, one label next to the switch. No two-state narration. ## Visual comparison ### Pixel diff (changed pixels tinted red over the after image) 4.7% of pixels changed, all concentrated in the bottom of the settings column — everything else is pixel-identical, confirming the fix is scoped.  ### Cropped before/after toggle (zoomed to the changed region) Full-viewport comparisons are noisy when the delta is a single component at the bottom. This one is cropped to the changed bbox so the card fix is the whole frame — 1s before, 1s after, looped.  ### Wipe reveal (before on the left, after swept in from the left) A vertical red sweeps across the full page, revealing the after state over the before state. Useful for spotting any unintended drift elsewhere on the page (there is none).  ## Test plan - [ ] Open `/projects/<id>/payments/settings` in the dashboard. - [ ] Verify \"Block New Purchases\" renders as a `Card` with the same width as Stripe Connection / Test Mode / Payment Methods. - [ ] Toggle the switch on — icon badge turns red, config write fires (`payments.blockNewPurchases = true`, `pushable: true`). - [ ] Toggle off — icon returns to muted gray, config write fires with `false`. - [ ] Reload the page and confirm the persisted state matches the toggle. - [ ] `pnpm lint` and `pnpm typecheck` pass. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Redesigned the "Block New Purchases" toggle in payment settings with a new card-based interface and visual prohibit indicator for improved clarity and user experience. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
4f198bd55b
|
Fix dashboard UI bugs: webhook detail crash and http domain silent https upgrade (#1362)
## Summary
Fixes two dashboard UI bugs surfaced while auditing the project area for
large user-visible issues:
1. **Webhook detail page completely broken** — the page shows a blank
screen because the SvixProvider token was being set to the string
`"[object Object]"`.
2. **Editing a trusted domain with an `http://` base URL silently
upgrades it to `https://`** — saving the edit dialog without changing
anything changes the protocol, breaking callbacks to the original host.
Both are corrected with minimal, targeted changes in the dashboard app.
No API, schema, or shared package changes are required.
---
## Bug 1 — Webhook detail page crashes because `svixToken + ''` yields
`"[object Object]"`
### Where
`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsx`
### Root cause
`stackAdminApp.useSvixToken()` returns an object of shape `{ token:
string, url: string | null }` (see
`packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts`).
The page was doing:
```ts
const svixToken = stackAdminApp.useSvixToken();
const [updateCounter, setUpdateCounter] = useState(0);
// This is a hack to make sure svix hooks update when content changes
const svixTokenUpdated = useMemo(() => {
return svixToken + '';
}, [svixToken, updateCounter]);
// …
<SvixProvider token={svixTokenUpdated} …>
```
`svixToken + ''` coerces the object to the string `"[object Object]"`,
which is then passed to `<SvixProvider>` as the auth token. Every nested
Svix hook (`useEndpoint`, `useEndpointSecret`,
`useEndpointMessageAttempts`) authenticates with that bogus token, gets
a `401 {"code":"authentication_failed","detail":"Invalid token"}` from
Svix, and `getSvixResult`
(`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/utils.tsx`)
throws, crashing the page.
Additional notes while in there:
- `setUpdateCounter` was declared but never called anywhere, so the
surrounding `useMemo`/`useState` was dead weight as well as broken.
Removing it removes the dead code too.
- The neighbouring list page (`webhooks/page-client.tsx`) already uses
the correct shape (`svixToken.token`, `svixToken.url`), which is why the
list page rendered correctly while the detail page didn't.
### Fix
Pass `svixToken.token` directly to `<SvixProvider>` and drop the unused
counter/memo.
```ts
export default function PageClient(props: { endpointId: string }) {
const stackAdminApp = useAdminApp();
const svixToken = stackAdminApp.useSvixToken();
return (
<AppEnabledGuard appId="webhooks">
<SvixProvider
token={svixToken.token}
appId={stackAdminApp.projectId}
options={{ serverUrl: getPublicEnvVar('NEXT_PUBLIC_STACK_SVIX_SERVER_URL') }}
>
<PageInner endpointId={props.endpointId} />
</SvixProvider>
</AppEnabledGuard>
);
}
```
### Reproduction (before fix)
1. Enable the Webhooks app on a project.
2. Create an endpoint with any URL.
3. Open the row's action menu and click **View Details**.
4. The page renders blank (Svix hooks throw 401 Invalid token; the error
boundary unmounts the detail tree). URL, Description, Verification
Secret, and Events History never appear.
### Before / After
| Before | After |
| --- | --- |
| 
| 
|
---
## Bug 2 — Editing an `http://` trusted domain silently upgrades it to
`https://`
### Where
`apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx`
### Root cause
In `EditDialog`, the form's `defaultValues` always set `insecureHttp:
false`, regardless of the protocol of the domain being edited:
```ts
defaultValues={{
addWww: props.type === 'create',
domain: props.type === 'update' ? props.defaultDomain.replace(/^https?:\/\//, "") : undefined,
handlerPath: props.type === 'update' ? props.defaultHandlerPath : "/handler",
insecureHttp: false, // ← ignores the existing protocol
}}
```
The `domain` field strips `http(s)://` for display but the protocol
itself is only tracked through the `insecureHttp` switch, which lives
inside the collapsed-by-default **Advanced** accordion. On submit:
```ts
const protocol = values.insecureHttp ? 'http://' : 'https://';
const baseUrl = protocol + values.domain;
```
So an `http://myapp.test` entry reopens with `insecureHttp: false`, the
Advanced section stays collapsed, the user sees nothing wrong, and
hitting **Save** (even with zero visible changes) writes
`https://myapp.test` back to config. Existing redirects from SSO / email
verification flows that depend on the original `http://` host stop
working.
### Fix
Derive `insecureHttp` from the existing `defaultDomain` when editing:
```ts
insecureHttp: props.type === 'update' ? props.defaultDomain.startsWith('http://') : false,
```
This makes the switch in the Advanced panel pre-check itself correctly
and the submit path emits the preserved protocol.
### Reproduction (before fix)
1. Go to **Project Settings → Trusted Domains**.
2. Add a new domain, expand **Advanced**, toggle **Use HTTP instead of
HTTPS** on, enter `myapp.test`, click **Create**. The list now shows
`http://myapp.test`.
3. Click the row's **⋯ → Edit**, then **Save** without changing
anything.
4. Observe the list now shows `https://myapp.test`.
### Before / After
**Domain list after an edit+save:**
| Before (http silently became https) | After (http preserved) |
| --- | --- |
| 
| 
|
In the "before" screenshot, `http://myapp.test` was edited with no
changes and silently became `https://myapp.test`.
`http://www.myapp.test` (not edited) stayed `http://`, confirming the
bug is triggered only through the edit-save path.
**Edit dialog (Advanced expanded):**
| Before (HTTP switch always off) | After (reflects stored protocol) |
| --- | --- |
| 
| 
|
The "after" dialog also shows the protocol prefix label flip from
`https://` to `http://` next to the input — a second visual cue that the
user is editing an HTTP domain.
---
## Scope / out of scope
In scope here:
- The two fixes above, plus a small amount of dead-code cleanup adjacent
to the first fix (the unused `updateCounter` / `useMemo` hack).
Intentionally **not** included (tracked separately from the same audit —
see internal notes):
- Cursor pagination cache wipe across Users/Teams/Transactions tables
(`data-table/common/cursor-pagination.tsx`)
- Email Outbox "Scheduled At" input being reset on every keystroke and
rendered in the wrong timezone (`email-outbox/page-client.tsx`)
- Latent empty-group handling in the sign-up rule builder (validator +
CEL emitter), which is real in code but not currently reachable through
the editor UI
These are broader and deserve their own PRs.
## Test plan
- [ ] **Bug 1 (webhook detail):** Enable Webhooks on a project, create
an endpoint, open **View Details**. Confirm URL, Description,
Verification Secret, and Events History render (no 401s in the console,
no blank page). Confirm the Copy button on the verification secret still
copies the key.
- [ ] **Bug 2 (domain edit preserves http):** Add an `http://` trusted
domain. Edit it and save with no changes — list should still show
`http://`. Edit again, flip the Advanced switch to HTTPS, save — list
should show `https://`. Repeat with the inverse direction (start https,
flip to http).
- [ ] **Regression sweep:** Webhooks list page, create/delete endpoint,
copy signing secret; Trusted Domains add/delete; auth-methods callbacks
against an `http://localhost` domain continue to work.
- [ ] `pnpm typecheck` passes locally. (`pnpm lint` was also run against
the dashboard app and is clean.)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Domain editing now correctly initializes and preserves the protocol
type (HTTP or HTTPS) based on the existing domain configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
000634607a
|
fix(internal-tool): continue dev startup when spacetime publish fails (#1371)
## Summary - pre-dev.mjs now warns and exits 0 when the local SpacetimeDB publish fails, instead of aborting `next dev` - Lets contributors without a running local SpacetimeDB server still start the internal-tool dev server - Updates the header comment to reflect the new behavior ## Test plan - [ ] Run `pnpm dev` in `apps/internal-tool` with no SpacetimeDB server running — dev server should still start, with a warning - [ ] Run with SpacetimeDB server running and `spacetime` CLI installed — publish still runs and dev proceeds - [ ] Run without `spacetime` CLI installed — existing warn-and-continue path still works <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated local publishing configuration to derive server settings from environment variables for improved flexibility and easier customization. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
f89b97bc54
|
fix connected accounts tokens (#1358)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * OAuth flows now consistently block extra scopes and access tokens for shared OAuth keys, enforcing restrictions earlier in the request processing and across all environments. * **Tests** * Added end-to-end regression tests to verify requests with extra scopes against shared OAuth providers return a 400 response indicating extra scopes/access tokens are not allowed. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
3ea8052d35 | chore: update package versions | ||
|
|
d9492ac5f1 | Update submodules | ||
|
|
6f1df1a0c7 | Update submodules | ||
|
|
37ee5ec320
|
Fast-start local emulator via RAM snapshot + live secret rotation (#1340)
## Summary
`stack emulator start` now resumes a fully-warm VM snapshot instead of
cold-booting, bringing startup from 30–120s down to ~5–8s with
per-install secret rotation, or ~2.5s with rotation opt-out. The
snapshot is captured **locally on first `stack emulator pull`**, not
shipped from CI — QEMU migration state isn't portable across
accelerators (KVM/HVF/TCG) or `-cpu max` feature sets, so a CI-captured
snapshot couldn't resume reliably on arbitrary user hardware.
Also bundles a pile of CLI QoL fixes (progress bars, PR/run artifact
pulls, PR-build download, native-TS ISO writer replacing
`hdiutil`/`mkisofs`/`genisoimage` host dep, unit tests).
| Scenario | Before | After |
|---|---|---|
| Cold boot (no snapshot) | 30–120s | same, works as fallback |
| `stack emulator pull` (one-time, includes local snapshot capture) |
~30s download | ~30s download + ~1–3 min cold-boot capture |
| Snapshot resume, normal start | — | **~5–8s** |
| Snapshot resume, `EMULATOR_NO_ROTATION=1` | — | **~2.5s** |
Backend (`/health?db=1`) and dashboard (`/handler/sign-in`) return 200
on all paths. Two successive snapshot resumes produce different rotated
PCK/SSK/SAK/CRON_SECRET values per install.
## How it works
**Build (CI)** — `docker/local-emulator/qemu/build-image.sh`:
1. Cloud-init provisioning runs to completion (migrations, seed,
slim-image) producing `stack-emulator-<arch>.qcow2`.
2. Image is built with a topology compatible with later snapshot capture
(pinned SMP=4, phantom seed/bundle ISOs, STACKCFG runtime ISO mounted at
build time, qemu-guest-agent running, placeholder hex secrets baked in
under `STACK_EMULATOR_BUILD_SNAPSHOT=1`).
3. CI publishes **only the qcow2** — no `.savevm.zst` ships.
**Pull (user's machine)** —
`packages/stack-cli/src/commands/emulator.ts` + `run-emulator.sh
capture`:
1. `stack emulator pull` downloads the qcow2 with a progress bar (or
from a PR / workflow run via `--pr` / `--run`).
2. CLI invokes `run-emulator.sh capture`: cold-boots the qcow2 with a
matching device layout (phantom ISOs, fsdev, pcie-root-port, virtfs
detached — migration-incompatible), waits for backend+dashboard health,
then drives QMP: `stop` → set `mapped-ram` + `multifd` caps → `migrate
file:state.raw` → poll `query-migrate` → `quit`. Raw mapped-ram file is
zstd-compressed to `stack-emulator-<arch>.savevm.zst` in the images dir.
3. `--skip-snapshot` opts out (first `start` will then cold-boot).
**Runtime** — `run-emulator.sh start`:
1. Launch QEMU with `-incoming defer` when a `.savevm.zst` is present;
decompress on first use, keep the `.raw` cached for subsequent starts.
2. QMP: same `mapped-ram` + `multifd` caps → `migrate-incoming
file:<.raw>` → poll for `paused` → `cont`.
3. Generate fresh per-install secrets on the host; pipe them
base64-encoded through QGA `guest-exec input-data` →
`trigger-fast-rotate` in the guest → `docker exec -e … rotate-secrets`.
4. `rotate-secrets` in the container: validate keys (hex-only), targeted
`sed` on the placeholder PCK across built JS, `UPDATE ApiKeySet`,
`supervisorctl restart stack-app cron-jobs` (with
`stopasgroup`/`killasgroup` so the Node children actually die and
release their ports).
5. Poll backend+dashboard health; if anything fails, clean up and fall
back to cold boot transparently.
**Security model**: placeholder hex values are baked into the snapshot
(`00…ff` PCK, `00…ee` SSK, `00…dd` SAK, `00…cc` CRON_SECRET). They are
non-secret by construction. Real per-install secrets are generated at
each `emulator start` and never leave the host.
## CLI changes (`packages/stack-cli`)
- **`src/lib/iso.ts`** (new): native TypeScript ISO 9660 + Joliet
writer, replacing the host-side `hdiutil`/`mkisofs`/`genisoimage`
dependency for generating the STACKCFG runtime config disk. Unit tests
in `src/lib/iso.test.ts`.
- **`src/commands/emulator.ts`**:
- `pull`: streamed downloads with progress bar + ETA; `--pr <number>`
and `--run <id>` to pull from a PR build's CI artifacts (uses
`extract-zip` for the nested zip); `--skip-snapshot` to opt out of the
one-time local capture.
- `start` (existing, extended): auto-pulls AND auto-captures when no
image exists, so first-ever `start` is self-bootstrapping; emits
`STACK_EMULATOR_CLI_WROTE_ISO=1` so the shell helper skips its own ISO
regen (avoids the genisoimage host dep).
- `capture` (new, invoked by `pull` and the auto-pull path of `start`):
drives the local snapshot capture via `run-emulator.sh`.
- `status`, `stop`, `reset`, `list-releases`: preflight +
path-resolution tightening (`STACK_EMULATOR_HOME` → images/run dirs).
- Unit tests in `src/commands/emulator.test.ts`.
- **`EMULATOR_NO_ROTATION=1`** env var skips the post-resume rotation
(intended for tests/CI where the placeholder secrets are fine — comes
with a loud warning).
## CI (`.github/workflows/qemu-emulator-build.yaml`)
- Builds **QEMU 10.2.2 from source** (cached), because
`mapped-ram`/`multifd` migration capabilities aren't available in the
distro's QEMU. Enables KVM on ubicloud runners so amd64 boots at
hardware speed.
- amd64 + arm64 both build on the same amd64 matrix
(`ubicloud-standard-8`); arm64 runs under cross-arch TCG (provisioning
only — boot/verify smoke test is amd64-only).
- Verification now runs through the CLI: `emulator start` → `emulator
status` → `emulator stop` against the freshly-built qcow2 (via
`STACK_EMULATOR_HOME` pointing at the workspace, so the CLI doesn't
silently auto-pull a prior release).
- Packages **only** the qcow2. No `.savevm.zst` upload / publish.
- Release notes updated.
## Key files
**Shell / guest:**
- `docker/local-emulator/qemu/build-image.sh` — snapshot-compatible
device topology + STACKCFG runtime ISO at build time
- `docker/local-emulator/qemu/run-emulator.sh` — `start`, `capture`,
`stop`, `reset`, `status`; `-incoming defer`, `.raw` cache, QGA-driven
rotation, cold-boot fallback
- `docker/local-emulator/qemu/common.sh` (new) — shared `qmp_session` +
`capture_vm_state` (factored out so build-image.sh and run-emulator.sh
share the capture path)
- `docker/local-emulator/qemu/cloud-init/emulator/user-data` —
placeholder secrets in snapshot mode, `wait-for-stack-ready`,
`trigger-fast-rotate`, qemu-guest-agent enabled
- `docker/local-emulator/rotate-secrets.sh` (new) — in-container
rotation (sed + UPDATE + supervisorctl)
- `docker/local-emulator/supervisord.conf` — `stopasgroup`/`killasgroup`
on `stack-app` and `cron-jobs`
- `docker/local-emulator/entrypoint.sh` — only mint CRON_SECRET if unset
(placeholder supplied in snapshot mode via --env-file)
- `docker/local-emulator/Dockerfile` — ships `rotate-secrets` to
`/usr/local/bin`
- `docker/server/entrypoint.sh` — source
`/run/stack-auth/rotated-secrets.env`; skip full-tree sentinel scan on
warm restarts via marker
**CLI:**
- `packages/stack-cli/src/lib/iso.ts` (new) + `iso.test.ts` (new)
- `packages/stack-cli/src/commands/emulator.ts` + `emulator.test.ts`
(new)
- `packages/stack-cli/vitest.config.ts` (new)
**CI:**
- `.github/workflows/qemu-emulator-build.yaml`
## Test plan
- [x] `docker/local-emulator/qemu/build-image.sh {amd64,arm64}` produces
`stack-emulator-<arch>.qcow2` with snapshot-compatible topology
- [x] `stack emulator pull` downloads qcow2 with progress, then captures
locally (~1–3 min) and writes `stack-emulator-<arch>.savevm.zst` in the
images dir
- [x] `stack emulator pull --skip-snapshot` stops after download
- [x] `stack emulator pull --pr <n>` / `--run <id>` pull from PR /
workflow run artifacts
- [x] `stack emulator start` on a fresh dir auto-pulls **and**
auto-captures, then starts; subsequent starts fast-resume in ~5–8s;
backend + dashboard return 200
- [x] `EMULATOR_NO_ROTATION=1 stack emulator start` completes in ~2.5s;
backend + dashboard return 200 with warning printed
- [x] Two consecutive `emulator start` invocations produce different PCK
values in the internal `ApiKeySet` row
- [x] `stack emulator status` / `stop` / `reset` resolve paths from
`STACK_EMULATOR_HOME`
- [x] Verified end-to-end on arm64 macOS under HVF (capture ~50s,
fast-resume ~6.5s)
- [x] `pnpm lint` and `pnpm typecheck` pass; stack-cli unit tests (iso +
emulator) pass
- [ ] CI green on this PR (qemu-emulator-build matrix, smoke test)
- [ ] `gh release download emulator-<branch>-latest` contains only
`stack-emulator-<arch>.qcow2` once this PR merges and publish runs
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Snapshot fast-start/resume with optional warm-snapshot assets, runtime
ISO generation, and a cached QEMU build to speed emulator setup.
* CLI: streamed artifact downloads with progress, improved release/asset
handling, stronger preflight checks, and start/status/stop emulator
commands.
* Automated secret rotation and ability to apply rotated secrets at
container startup; supervisor control socket enabled.
* **Bug Fixes**
* More robust start/stop/resume flows with automatic fallback to cold
boot and improved process-group shutdown behavior.
* **Tests**
* New tests for CLI utilities and ISO image generation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
6bc1836e66
|
fix(dashboard): resolve UI issues across email-* pages (#1345)
## Summary
Six UI issues found across the email-* dashboard pages, ranked by
impact, fixed here:
1. **email-sent layout** — the email log table and domain reputation
card were forced side-by-side at all widths. A fixed-width sidebar plus
a flex-1 table meant that on tablet the table got crushed, and on mobile
the row overflowed horizontally. Fix: stack vertically below `lg`, and
let the reputation card span full width on narrow viewports.
2. **Domain status enum leaks to the UI** — `<span>Status:
{domain.status}</span>` rendered raw values like `pending_dns` /
`pending_verification`. Added a `MANAGED_DOMAIN_STATUS_LABELS` map and
route through it before rendering.
3. **email-themes dialog grid cramped on mobile** — the Change Theme
dialog hardcoded `grid-cols-2`, so at 375px each theme card had ~150px
and the preview images were illegible. Changed to `grid-cols-1
sm:grid-cols-2`.
4. **Template name row overflow** — long template names pushed the Edit
Template button off the right edge of the card because the flex row had
no `min-w-0` / `truncate`. Fixed both, and made the action column
`shrink-0`.
5. **Boosted-capacity label was color-only** — during an active boost
the label used a red strikethrough for the base value and a blue number
for the boosted value with no non-color cue. Added an explicit `→` arrow
between the two numbers, `title` tooltips on each, and a visible
\"(boosted)\" marker after `/h max`.
6. **Draft progress bar overflowed at mobile width** — the 4-step
progress bar used fixed 80px connectors, giving a minimum width of
~400px that clipped off both ends at 375px. Changed connectors to `w-8
sm:w-20` (32px on mobile, 80px otherwise) so all four steps and their
labels fit below 640px.
## Before / after
Each GIF below loops \"before\" (1s) → \"after\" (1s) with a red pill in
the top-right indicating which frame is which. Full-size stills (before
+ after + extra viewports) are listed under **All screenshots** at the
bottom.
### 1. email-sent — two-column layout collapses on narrow viewports
Mobile (375px):

Tablet (900px):

### 2. email-settings — managed-domain status label

### 3. email-themes — Change Theme dialog on mobile

### 4. email-templates — long name overflow

### 5. email-sent — boosted capacity label

### 7. email-drafts — draft progress bar on mobile

## Test plan
- [x] \`pnpm --filter @stackframe/dashboard lint\` — clean
- [x] \`pnpm --filter @stackframe/dashboard typecheck\` — clean
- [x] Manual verification in a browser at 375px / 900px / 1440px, light
+ dark mode, for each fixed page
- [ ] Reviewer sanity check of the remaining email-* pages
(email-outbox, email-viewer) for similar responsive regressions
## Notes
- The initial review flagged a \"white-on-white capacity boost timer\" —
on closer look the label sits on a deliberately dark `bg-zinc-900/0.82`
overlay inside the boost card, so it reads fine in light and dark mode.
Not fixing; that part of the review was a false positive.
- The initial review also flagged a missing empty state on
email-templates. Because Stack seeds built-in templates, the empty
branch is unreachable in practice — skipping that fix to avoid dead
code.
## All screenshots
Gist with all the individual before/after PNGs and the GIFs themselves:
https://gist.github.com/BilalG1/edb04740a19c3f2d048da6e602209d45
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added human-readable status labels for managed domains in domain
settings
* **Improvements**
* Enhanced responsive layouts across dashboard pages for improved mobile
experience
* Improved email capacity display with visual indicators and tooltips
for boost status
* Refined template and theme selection layouts with better text handling
and spacing
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
85ae4b1c9e
|
Fix ClickHouse OOM in MAU query + optimize /internal/metrics route (#1344)
## Summary Fixes the Sentry `StackAssertionError: Failed to load monthly active users for internal metrics` crash (ClickHouse OOM at the 7.2 GiB per-query cap) and applies two related optimizations to other queries in the same route while here. Adds a local benchmark harness that validates correctness and measures peak memory / duration before & after. ## Root cause (the original Sentry error) `loadMonthlyActiveUsers` was written as `SELECT user_id … GROUP BY user_id` and then counting in Node via a `Set`. On a large project that ships back millions of user_ids. Two failure modes stacked: 1. **Result materialization** — every distinct user_id had to be buffered in the server before streaming to Node (~20 MiB of result for 450k users; much more at real scale). 2. **`JSONExtract(toJSONString(data), 'is_anonymous', 'UInt8')`** — the `toJSONString(data)` per-row re-serialization of the entire nested JSON column, billions of times, just to pull one boolean. Dominates bytes-read. Combined, on a single partition read from S3-backed MergeTree, this can exceed ClickHouse's 7.2 GiB per-query memory cap. That's exactly what the Sentry trace showed. ## Changes ### 1. Fix MAU query (`loadMonthlyActiveUsers`) Moved counting to the server with `uniqExact(sipHash64(normalized_user_id))` and pulled the JS-side normalization (`lower`, `trim`, `isUuid`) into SQL. Picked `sipHash64` after benchmarking 7 variants — it's exact (at <<2³² users) and halves the uniqExact hash-state vs. raw string keys. ### 2. Fix 1 — `JSONExtract(toJSONString(data), …)` → direct `CAST(data.is_anonymous, …)` Applied everywhere the pattern appeared in the metrics route: - `loadDailyActiveUsers` - the `analyticsUserJoin` subquery - the `nonAnonymousAnalyticsUserFilter` - `analyticsOverview:topRegion` - `analyticsOverview:online` Semantics preserved (`coalesce(CAST(data.is_anonymous, 'Nullable(UInt8)'), 0)` matches `JSONExtract(…, 'UInt8')` behavior when the field is missing). ### 3. Fix 3 — server-aggregate the split queries `loadDailyActiveUsersSplit` and `loadDailyActiveTeamsSplit` used to ship 1.2M+ `(day, user_id)` rows back to Node just so the JS could bucket them into new / retained / reactivated. Rewrote both as one CTE-style query that returns 31 rows (one per day in the 30-day window) with the counts precomputed. **Minor semantic shift** (documented inline in `route.tsx`): \"new\" is now based on the user's first-ever `\$token-refresh` event rather than their Postgres `signedUpAt`. Agrees for users who log in immediately after sign-up (the common case). Disagrees for the rare edge case of an account that existed pre-window but never generated a `\$token-refresh` until now — old code classified as \"reactivated,\" new code classifies as \"new.\" Judged acceptable; can be revisited. Postgres round-trips for `ProjectUser.signedUpAt` / `Team.createdAt` are no longer needed for the split, and the 76 MiB-ish wire ship is gone. ### 4. Benchmark harness (`apps/backend/scripts/benchmark-internal-metrics.ts`) Local-only tool. Three modes: - **MAU equivalence matrix** — 13 edge cases (empty, dedup, anonymous filter, window boundary, null user_id, non-UUID user_id, case variation, project isolation, missing/null `is_anonymous`, wrong event_type). Asserts OLD pipeline and NEW query return the **same set** of users, not just the same count. - **MAU perf** — OLD vs NEW plus 6 other candidate variants (inline regex, UUID keys, sipHash64, HLL sketches), reads `memory_usage` / `read_rows` / `result_bytes` from `system.query_log` for each, prints a ranked table. - **Full-route benchmark** (`BENCH_ROUTE_QUERIES=1`) — runs every ClickHouse query in `/internal/metrics` in three stages (BEFORE, AFTER, candidate OPTIMIZED) against the same seed and prints per-query deltas plus endpoint-level totals. Seeds under a synthetic `project_id` so real data is never touched; cleans up on exit via `ALTER TABLE … DELETE`. ## Benchmark results ### MAU query alone Ran at two scales; set-equality verified (new query identifies the same individual users, not just the same count). | seed | MAU | peak memory (old → new) | bytes read | duration | |---|---|---|---|---| | 500k events | 89,939 | 158.7 MiB → 46.7 MiB (**3.4×**, −70%) | 175.7 MiB → 63.0 MiB (2.8×) | 483 ms → 76 ms (**6.4×**) | | 2.5M events | 449,990 | 439.2 MiB → 281.4 MiB (1.56×, −36%) | 865.0 MiB → 310.9 MiB (2.8×) | 783 ms → 126 ms (**6.2×**) | MAU variant bake-off at 2.5M events (all exact, all set-equal to OLD): | variant | memory | duration | notes | |---|---|---|---| | v0_old (baseline) | 440 MiB | 567 ms | — | | v1_uniqExact_string | 284 MiB | 110 ms | naive fix | | v3_uniqExact_toUUID | 244 MiB | 153 ms | UUID keys, slower per-row | | **v4_uniqExact_sipHash64** | **125 MiB** | **95 ms** | **shipped** | | v5_uniq (HLL) ~approx | 30 MiB | 86 ms | −0.25% error | | v6_uniqCombined ~approx | 31 MiB | 67 ms | −0.15% error | ### Full `/internal/metrics` route (2.7M events, 300k users + page-views + clicks + teams) Ranked by BEFORE peak memory: | query | mem BEFORE | mem AFTER | Δ mem | dur BEFORE | dur AFTER | Δ dur | |---|---|---|---|---|---|---| | analyticsOverview:topReferrers | 588.1 MiB | 411.1 MiB | 1.43× | 1833 ms | 110 ms | **16.66×** | | analyticsOverview:totalVisitors | 584.3 MiB | 403.5 MiB | 1.45× | 1829 ms | 121 ms | 15.12× | | analyticsOverview:dailyEvents | 584.1 MiB | 403.7 MiB | 1.45× | 1897 ms | 140 ms | 13.55× | | loadUsersByCountry | 393.1 MiB | 385.4 MiB | ≈same | 74 ms | 80 ms | ≈same | | loadDailyActiveUsersSplit | 363.4 MiB | 396.8 MiB | *+9%* | 1966 ms | 356 ms | 5.52× | | analyticsOverview:topRegion | 269.9 MiB | 106.4 MiB | 2.54× | 1602 ms | 65 ms | 24.65× | | loadDailyActiveUsers | 268.3 MiB | 84.0 MiB | 3.19× | 1111 ms | 44 ms | 25.25× | | loadDailyActiveTeamsSplit | 59.6 MiB | 78.1 MiB | *+31%* | 70 ms | 123 ms | *+76%* | | loadMonthlyActiveUsers | 54.9 MiB | 54.9 MiB | ≈same | 68 ms | 56 ms | ≈same | | analyticsOverview:online | 18.4 MiB | 5.8 MiB | 3.17× | 58 ms | 4 ms | 14.50× | **Endpoint-level totals** | metric | BEFORE | AFTER | Δ | |---|---|---|---| | Sum peak ClickHouse memory | 3.11 GiB | 2.28 GiB | **−27%** | | **Max query duration** (endpoint wall-clock floor) | **1966 ms** | **356 ms** | **−82%** (5.5×) | | Sum query duration (total CPU) | 10508 ms | 1099 ms | **−90%** (9.6×) | | Bytes read | 10.70 GiB | 4.55 GiB | −57% | | Bytes shipped to Node | 94.8 MiB | 44.2 KiB | **−99.95%** | Both split queries show a small memory *regression* at this seed size (the new server-side window-function + self-join has its own state cost that's near break-even with \"materialize + ship\" at 300k users); at prod scale the 76 MiB-ship saving dominates. Duration is unambiguously better. ## Why we don't need to drop the `analyticsUserJoin` in this PR The benchmark includes an OPTIMIZED stage that drops the LEFT JOIN and trusts `e.data.is_anonymous` directly, which would shave another **1.2 GiB / 1.9× duration** off the endpoint. **But we can't ship that here** — an audit of the client tracker (`packages/js/src/lib/stack-app/apps/implementations/event-tracker.ts`) confirmed `is_anonymous` is never set on client-emitted `$page-view` / `$click` events. The JOIN is currently load-bearing. A follow-up PR will enrich `is_anonymous` at the batch ingest endpoint using `auth.user.is_anonymous`; after one metrics-window cycle (~30 days) the JOIN can be dropped. ## Follow-up work (out of scope for this PR) - **Batch-endpoint enrichment** + drop the analytics-overview LEFT JOIN (est. further −53% endpoint memory, −46% duration per the benchmark). - **Teams-split hash-variant count mismatch** — `sipHash64(team_id)` variant of the teams split shows a count discrepancy vs. the string-keyed version in the benchmark. Not blocking since teams-split is only #8 by memory; needs a root-cause pass before shipping that particular optimization. - **`loadUsersByCountry` window bound** — currently scans every `$token-refresh` event ever for the tenancy (no time filter). Bounding to 30 days would bound memory growth with project age, but changes semantics (\"country of latest login ever\" → \"in last 30 days\"). Deferred because it's product-facing. ## Snapshot changes in `internal-metrics.test.ts.snap` The `should return metrics data with users` test signs in 10 users today, then deletes one of them mid-test. Two small snapshot values change on today's date; both are just a reclassification of that single deleted user — the total (10 active users) is unchanged. - **`daily_active_users_split.new[today]`: 9 → 10** All 10 users really did sign in for the first time today. The old code only counted 9 because the deleted user's Postgres row was gone by the time the metrics query ran, so the old classifier couldn't see they were created today. The new query looks at ClickHouse events directly, sees the deleted user's first event was today, and counts them as new like everyone else. - **`daily_active_users_split.reactivated[today]`: 1 → 0** No user was "reactivated" today — nobody was active on an earlier day and came back. The old "1" was the deleted user falling into this bucket by default (the old classifier had no other rule that fit them). The new code correctly reports zero. Totals match either way (9 + 1 = 10 + 0). We're moving one deleted user out of the "returning visitor" bucket and into the "brand-new user" bucket, which is what they actually were. ## Test plan - [x] `pnpm typecheck` and `pnpm lint` pass on the backend package - [x] MAU equivalence matrix: 13/13 cases return the same set of users (not just the same count) between OLD and NEW pipelines - [x] Set-equality verified at 500k-MAU perf scale - [x] Full-route benchmark confirms the expected memory / duration improvements - [ ] Sanity-check the dashboard rendering after deploy (split charts, MAU counter, analytics overview) - [ ] Monitor Sentry for the assertion error — should drop to zero <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Performance Improvements** * Monthly and daily active metrics are now computed entirely server-side for faster queries and reduced client-side processing. * **Bug Fixes** * More consistent handling of anonymous/missing IDs and stricter ID filtering to improve accuracy across edge cases. * **Tests** * Added a comprehensive benchmark and validation harness to measure query performance and verify result equivalence across variants. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
0621ad2032
|
ai proxy fix (#1343)
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Request sanitization now includes an extra proxy-specific preprocessing step for safer AI proxying. * **New Features** * Initialization prompts centralized into a shared helper, with a web-specific prompt variant. * Authenticated requests can optionally route via a provided external API key to access alternate models. * **Chores** * Added and exposed a preprocessing hook with a default no-op implementation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
f0bbdb1c34 | Make access token warning just a log | ||
|
|
82c923e03c | waitUntil Sentry flush is complete | ||
|
|
560ee4c16e | Fix memory leak | ||
|
|
d568ad5149 | Increase Clickhouse request timeout | ||
|
|
cf67d37611 | Don't override 5xx errors | ||
|
|
ac9707b89e |
Update metrics endpoint to no longer trigger global error boundary on failure
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
|
||
|
|
ee68908111 | Skip Swift tests temporarily | ||
|
|
1594ed94d5 | Speed up seed script by a lot | ||
|
|
f85b4f3997 | Make Bulldozer SQL statements deterministic | ||
|
|
8046a7dd8f | Fix dashboard sidebar hover states | ||
|
|
2e247dd06d | Improve dashboard sidebar styling | ||
|
|
fd68701097 | Fix bigint serialization error on tracing | ||
|
|
91fbf63f7f | chore: update package versions | ||
|
|
847d14df70
|
[Fix]: Assortment of Bugs with Timefold Table and Payments (#1348) | ||
|
|
f4ca6cb4c7 | More tracing for replication-related functions | ||
|
|
665870a144
|
[Fix] Bulldozer Studio and SpaceTime DB port conflict (#1346) | ||
|
|
22ae47fe73 | Replace Cmd with Ctrl on Windows computers | ||
|
|
1de8a17183
|
Payments bulldozer txn rework (#1315)
### Object of this PR This PR is NOT a monolithic series of fixes for the payments suite + a complete rework. Its aims were a) introducing and robustly testing the bulldozer db system b) reworking the payments underlying architecture to use bulldozer for correctness and scalability c) Achieving parity with the old payments system excepting a few changes like ensuring correctness of the ledger algo There may still be some work to do with handling refunds, decoupling the concepts of purchases from that of products, and some other things. ### Ledger Algorithm This has been tuned and fixed. Item removals i.e negative item quantity changes will apply to the soonest expiring item grant i.e positive item quantity change. This is what is best for the user. Item grants can also expire, and when they expire we obviate whatever is left of their original capacity (meaning after all the removals that were applied to it). Our ledger algo is applied via Bulldozer, so automatic re-computation is handled when a new grant/ removal is inserted in the middle of the existing ones. ### Things we got rid of * No more automatic support for default products. You can use $0 plan provisions to accomplish the same effect but it's manual * Negative item quantity changes (i.e item removals) no longer can have expiries <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Enhanced payment processing pipeline with improved data consistency and state management. * Advanced refund handling with comprehensive transaction tracking. * Better tracking and management of customer item quantities and owned products. * Improved subscription lifecycle management including period-end handling. * **Bug Fixes** * Fixed payment data integrity verification. * Improved handling of edge cases in refund scenarios. * **Chores** * Updated cSpell configuration with additional words. * Expanded developer documentation for linting workflows. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com> Co-authored-by: Aadesh Kheria <kheriaaadesh@gmail.com> Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com> |
||
|
|
8af48c1e94
|
fix(dashboard): correct keyboard shortcut display and HTML entity rendering (#1342)
## Summary Two small UI bugs found while auditing `apps/dashboard` for visible defects. ### 1. Dashboards empty state hardcoded `Cmd+K` `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/page-client.tsx:80` The empty state copy referenced the command palette as `Cmd+K`. The rest of the dashboard renders the shortcut as the `⌘ K` keycap (see `cmdk-search.tsx:1062`), so this one string was inconsistent. Replaced with `⌘ K` to match the convention. **Before/after flicker:**  **Pixel diff** — 3,500 diff pixels (0.270%). Changed regions: the "No dashboards yet" description line (the Cmd+K text) and the "DEV" badge in the bottom-right.  | Before | After | |---|---| |  |  | ### 2. Vercel page rendered `'` as raw text `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx:168`, `:169`, `:414` Three string literals contained `'`: ```tsx ? "You'll receive a publishable client key and a secret server key for this project." : "You'll receive a secret server key for this project." … subtitle="See Vercel's documentation on environment variables for more details." ``` These are JS strings passed into props, not JSX text nodes — React only decodes HTML entities in JSX text, so the literal characters `'` ended up in the DOM. Verified via `document.querySelector` — actual text content was `You'll receive a secret server key for this project.`. Replaced with a plain ASCII apostrophe. **Before/after flicker:**  **Pixel diff** — 1,252 diff pixels (0.163%). Changed region: the `You'll` → `You'll` line.  | Before | After | |---|---| |  |  | ## Test plan - [x] Visited `/projects/<id>/dashboards` with no dashboards — empty state now reads `(⌘ K)` - [x] Visited `/projects/<id>/vercel` — both the "API keys generated" subtitle and the "Need more detail?" subtitle render `'` as a real apostrophe - [x] `eslint` clean on both touched files |
||
|
|
b5273f7326 | Clicking a dashboard category now opens its first page | ||
|
|
5341371782
|
LLM MCP Flow (#1321)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
Mirror main branch to main-mirror-for-wdb / lint_and_build (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
Sync Main to Dev / sync-commits (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Automated AI QA review pipeline and human-verified knowledge base consulted first * Internal MCP review tool: call log viewer, conversation replay, add/edit/publish Q&A, knowledge editor, and analytics * Docs search now preserves follow-up conversation context * **Documentation** * Added “Ask DeepWiki” badge to README * **Chores** * Added local SpacetimeDB background service and internal-tool app scaffolding <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: mantrakp04 <mantrakp@gmail.com> Co-authored-by: Mantra <87142457+mantrakp04@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com> |
||
|
|
94dd22c1c5
|
Overview revamp (#1238) |