- Added new environment variables for Vite in the dashboard V2 `.env.development` file. - Updated `pnpm-lock.yaml` to include new dependencies for TanStack Start and React Query. - Enhanced the routing setup in `router.tsx` to integrate React Query with TanStack Router. - Refactored the `routeTree.gen.ts` to include new routes for the dashboard V2. - Updated the design guide and documentation for dashboard V2 components and layout. - Added new SVG assets for branding and social previews in the public directory. - Adjusted ESLint configuration to relax rules for generated components. - Improved the `.gitignore` to include the new `tanstack-start` package directory.
16 KiB
AGENTS.md
This file provides guidance to coding agents when working with code in this repository.
Development Commands
Essential Commands
- Install dependencies:
pnpm install - Run tests:
pnpm test run(uses Vitest). You can filter withpnpm test run <file-filters>. Therunis important to not trigger watch mode - Lint code:
pnpm lint.pnpm lint --fixwill fix some of the linting errors, prefer that over fixing them manually. Usepnpm -C <package> lintto lint a specific package. - Type check:
pnpm typecheck
Extra commands
These commands are usually already called by the user, but you can remind them to run it for you if they forgot to.
- Build packages: NEVER DO THIS YOURSELF — ASK THE USER TO DO IT FOR YOU!
- Start dependencies:
pnpm restart-deps(resets & restarts Docker containers for DB, Inbucket, etc. Usually already called by the user) - Run development: Already called by the user in the background. You don't need to do this. This will also watch for changes and rebuild packages, codegen, etc. Do NOT call build:packages, dev, codegen, or anything like that yourself, as the dev is already running it.
- Run minimal dev:
pnpm dev:basic(only backend and dashboard for resource-limited systems)
Testing
You should ALWAYS add new E2E tests when you change the API or SDK interface. Generally, err on the side of creating too many tests; it is super important that our codebase is well-tested, due to the nature of the industry we're building in.
- Run all tests:
pnpm test run - Run some tests:
pnpm test run <file-filters>
Database Commands
- Generate migration:
pnpm db:migration-gen— NOTE: don't forget to create tests for the migrations! - Reset database (rarely used):
pnpm db:reset - Seed database (rarely used):
pnpm db:seed - Initialize database (rarely used):
pnpm db:init - Run migrations (rarely used):
pnpm db:migrate
Architecture Overview
Stack Auth is a monorepo using Turbo for build orchestration. The main components are:
Apps (/apps)
- backend (
/apps/backend): Next.js API backend running on port${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}02(defaults to 8102)- Main API routes in
/apps/backend/src/app/api/latest - Database models using Prisma
- Main API routes in
- dashboard (
/apps/dashboard): Admin dashboard on port${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}01(defaults to 8101) - dev-launchpad: Development portal on port
${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}00(defaults to 8100) - e2e: End-to-end tests
Packages (/packages)
- stack (
/packages/stack): Main Next.js SDK - stack-shared (
/packages/stack-shared): Shared utilities and types - stack-ui (
/packages/stack-ui): UI components - react (
/packages/react): React SDK - js (
/packages/js): JavaScript SDK
Key Technologies
- Framework: Next.js (with App Router)
- Database: PostgreSQL with Prisma ORM
- Testing: Vitest
- Package Manager: pnpm with workspaces
- Build Tool: Turbo
- TypeScript: Used throughout
- Styling: Tailwind CSS
API Structure
The API follows a RESTful design with routes organized by resource type:
- Auth endpoints:
/api/latest/auth/* - User management:
/api/latest/users/* - Team management:
/api/latest/teams/* - OAuth providers:
/api/latest/oauth-providers/*
Development Ports
To see all development ports, refer to the index.html of apps/dev-launchpad/public/index.html.
Important Notes
- NEVER UPDATE packages/stack OR packages/js. Instead, update packages/template, as the others are simply copies of that package.
- For blocking alerts and errors, never use
toast, as they are easily missed by the user. Instead, use alerts. - Environment variables are pre-configured in
.env.developmentfiles - Always run typecheck, lint, and test to make sure your changes are working as expected. You can save time by only linting and testing the files you've changed (and/or related E2E tests).
- The project uses a custom route handler system in the backend for consistent API responses
- When writing tests, prefer .toMatchInlineSnapshot over other selectors, if possible. You can check (and modify) the snapshot-serializer.ts file to see how the snapshots are formatted and how non-deterministic values are handled.
- Whenever you learn something new, or at the latest right before you call the
Stoptool, write whatever you learned into the ./claude/CLAUDE-KNOWLEDGE.md file, in the Q&A format in there. You will later be able to look up knowledge from there (based on the question you asked). - Animations: Keep hover/click transitions snappy and fast. Don't delay the action with a pre-transition (e.g. no fade-in when hovering a button) — it makes the UI feel sluggish. Instead, apply transitions after the action, like a smooth fade-out when the hover ends.
- Whenever you make changes in the dashboard, provide the user with a deep link to the dashboard page that you've just changed. Usually, this takes the form of
http://localhost:<whatever-is-in-$NEXT_PUBLIC_STACK_PORT_PREFIX>01/projects/-selector-/..., although sometimes it's different. If $NEXT_PUBLIC_STACK_PORT_PREFIX is set to 91, 92, or 93, usea.localhost,b.localhost, andc.localhostfor the domains, respectively. - To update the list of apps available, edit
apps-frontend.tsxandapps-config.ts. When you're tasked to implement a new app or a new page, always check existing apps for inspiration on how you could implement the new app or page. - NEVER use Next.js dynamic functions if you can avoid them. Instead, prefer using a client component to make sure the page remains static (eg. prefer
usePathnameinstead ofawait params). - Whenever you make backwards-incompatible changes to the config schema, you must update the migration functions in
packages/stack-shared/src/config/schema.ts! - NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error) (or similar). In most cases you don't actually need to be asynchronous, especially when UI is involved (instead, use a loading indicator! eg. our component already takes an async callback for onClick and sets its loading state accordingly — if whatever component doesn't do that, update the component instead). If you really do need things to be asynchronous, use
runAsynchronouslyorrunAsynchronouslyWithAlertinstead as it deals with error logging. - WHENEVER you create hover transitions, avoid hover-enter transitions, and just use hover-exit transitions. For example,
transition-colors hover:transition-none. - Any environment variables you create should be prefixed with
STACK_(or NEXT_PUBLIC_STACK_ if they are public). This ensures that their changes are picked up by Turborepo (and helps readability). - NEVER just silently use fallback values or whatever when you don't know how to fix type errors. If there is a state that should never happen because of higher-level logic, and the type system doesn't represent that, either update the types or throw an error. Stuff like
?? 0or?? ""is often code smell when?? throwErr("this should never happen because XYZ")would be better. - Code defensively. Prefer
?? throwErr(...)over non-null assertions, with good error messages explicitly stating the assumption that must've been violated for the error to be thrown. - Try to avoid the
anytype. Whenever you need to useany, leave a comment explaining why you're using it (optimally it explains why the type system fails here, and how you can be certain that any errors in that code path would still be flagged at compile-, test-, or runtime). - Don't use Date.now() for measuring elapsed (real) time, instead use
performance.now() - Use urlString`` or encodeURIComponent() instead of normal string interpolation for URLs, for consistency even if it's not strictly necessary.
- When making config updates, use path notation (
{ "path.to.field": my-value }) to avoid overwriting sibling properties - IMPORTANT: Any assumption you make should either be validated through type system (preferred), assertions, or tests. Optimally, two out of three.
- If there is an external browser tool connected, use it to test changes you make to the frontend when possible.
- Whenever you update an SDK implementation in
sdks/implementations, make sure to update the specs accordingly insdks/specssuch that if you reimplemented the entire SDK from the specs again, you would get the same implementation. (For example, if the specs are not precise enough to describe a change you made, make the specs more precise.) - When building internal tools for Stack Auth developers (eg. internal interfaces like the WAL info log etc.): Make the interfaces look very concise, assume the user is a pro-user. This only applies to internal tools that are used primarily by Stack Auth developers.
- The dev server already builds the packages in the background whenever you update a file. If you run into issues with typechecking or linting in a dependency after updating something in a package, just wait a few seconds, and then try again, and they will likely be resolved.
- When asked to review PR comments, you can use
gh pr statusto get the current pull request you're working on. - NEVER EVER AUTOMATICALLY COMMIT OR STAGE ANY CHANGES — DON'T MODIFY GIT WITHOUT USER CONSENT!
- When building frontend or React code for the legacy dashboard (
apps/dashboard), refer to DESIGN-GUIDE.md. - NEVER use italics in UI (no
italicclass, nofont-style: italic, noInstrument_Serifitalic display fonts). This applies to headings, body text, placeholders, "no value" hints — everywhere. Italic looks dated and breaks the V2 aesthetic. Use color/weight/mono variants for emphasis instead.
Dashboard V2 (apps/dashboardV2)
Dashboard V2 is a ground-up rewrite. Rules specific to V2 — these override the legacy guidance above when they conflict:
- Stack: TanStack Start + TanStack Router (file-based routes in
src/routes), shadcn/ui, Tailwind v4, Manrope font, Phosphor icons. Vite, not Next.js. Dev port:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}11(default 8111). - DO NOT use
DESIGN-GUIDE.mdfor V2. DO invoke thefrontend-designskill whenever you scaffold or restyle a V2 page/component. The legacy guide describes the old aesthetic that V2 is replacing. - The legacy
apps/dashboardis considered visually ugly and is being thrown out. DO NOT mimic its layout, components, copy, or visual language when building V2. You may read it for backend/API contracts (data shapes, endpoints) and for business logic reference (filtering rules, state machines, config-update shapes, validation, edge-case handling) — re-implement that logic against V2's own UI primitives. Never copy its UI/layout/styles/components. - You have full freedom to modify the backend (
apps/backend) to better fit V2's needs — add/rename endpoints, change response shapes, etc. Keep the legacy dashboard working unless explicitly told otherwise. - Authentication in V2 uses the SDK's
<StackHandler fullPage>drop-in, mounted via the splat route atroutes/handler.$.tsx. It serves all auth flows (sign-in, sign-up, forgot-password, email-verification, oauth-callback, …). Do not hand-roll auth pages — the SDK handler is the source of truth, and replacing it would fragment the auth surface across two codebases. - V2 uses TanStack Router, so deep links take the form
http://localhost:<port>/...(no/projects/-selector-/prefix unless explicitly added by V2 routing). - V2 uses React Query (
@tanstack/react-query) for all data fetching. The@tanstack/react-router-ssr-queryintegration is already wired up — keep using it for SSR-aware queries. - V2 env files:
.env(template with comments),.env.development,.env.local(gitignored, dev overrides),.env.production. - Env prefixes in V2 are different from the rest of the repo because V2 is Vite/TanStack Start, not Next.js:
VITE_STACK_*— client-safe public values (project ID, publishable key, API URL). Inlined into the browser bundle. Read viaimport.meta.env.STACK_*— server-only secrets (e.g.STACK_SECRET_SERVER_KEY). Read viaprocess.envinside TanStack Start server functions / loaders. Vite'senvPrefixis set to["VITE_"]only, soSTACK_*can never leak into client bundles.- Never read a
STACK_*value from a component or any module imported by client code. If a feature needs a server secret, expose it through a TanStack Start server function and call that function from the client. VITE_*is registered in the rootturbo.jsonglobalEnv, so cache invalidation still works.
- NEVER implement a hacky solution without EXPLICIT approval from the user. Always go the extra mile to make sure the solution is clean, maintainable, and robust.
- Fail early, fail loud. Fail fast with an error instead of silently continuing.
- Do NOT use
as/any/type casts or anything else like that to bypass the type system unless you specifically asked the user about it. Most of the time a place where you would use type casts is not one where you actually need them. Avoid wherever possible. - When writing database migration files, assume that we have >1,000,000 rows in every table (unless otherwise specified). This means you may have to use CONDITIONALLY_REPEAT_MIGRATION_SENTINEL to avoid running the migration and things like concurrent index builds; see the existing migrations for examples. One common pattern is to add a temporary index or extra boolean column marking whether the row has already been migrated (then deleting the column at the end).
- Each migration file runs in its own transaction with a relatively short timeout. Split long-running operations into separate migration files to avoid timeouts. For example, when adding CHECK constraints, use
NOT VALIDin one migration, thenVALIDATE CONSTRAINTin a separate migration file. - Note that each database migration file is executed in a single transaction. Even with the run-outside-transaction sentinel, the transaction will still continue during the entire migration file. If you want to split things up into multiple transactions, put it into their own migration files.
- When writing database migration files, ALWAYS ALWAYS add tests for all the potential edge cases! See the folder structure of the other migrations to see how that works.
- When building frontend code, always carefully deal with loading and error states. Be very explicit with these; some components make this easy, eg. the button onClick already takes an async callback for loading state, but make sure this is done everywhere, and make sure errors are NEVER just silently swallowed.
- Any design components you add or modify in the dashboard, update the Playground page accordingly to showcase the changes.
- Unless very clearly equivalent from types, prefer explicit null/undefinedness checks over boolean checks, eg.
foo == nullinstead of!foo. - Ensure aggressively that all code has low coupling and high cohesion. This is really important as it makes sure our code remains consistent and maintainable. Eagerly refactor things into better abstractions and look out for them actively.
- Always let me know about the tradeoffs and decisions you make while implementing a non-trivial change.
- Whenever you change the URL of a page in the docs (or remove one), add a redirect in the docs-mintlify/docs.json file to make sure we don't lose any SEO juice.
- When you made frontend (or docs, dashboard, demo, etc.) changes, and you have a browser MCP in your list of MCP tools, make sure to test the changes in the browser MCP.
Code-related
- Use ES6 maps instead of records wherever you can.