mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary **Stacked on [#1475](https://github.com/hexclave/stack-auth/pull/1475)** (`cl/hexclave-pr1`, the invisible compatibility layer). Diff vs that base = the actual PR 2 code. This is **PR 2 of the Stack Auth → Hexclave rebrand: the visible flip**. Old wire identifiers (cookies, request/response headers, Bearer prefix, JWT issuers, MCP tool name) keep working indefinitely via PR 1's dual-accept. This PR flips every user-visible surface — package names taught in docs, SDK class names in code examples, dashboard setup snippets, page titles, error messages, email content, CLI binary, default base URLs, GitHub repo slug, contributor guidance — to the Hexclave brand. See [`RENAME-TO-HEXCLAVE.md`](./RENAME-TO-HEXCLAVE.md) → *"PR 2: Rebrand to Hexclave (visible)"* for the full per-work-area spec. ## What's implemented (per the plan's PR 2 scope) - **SDK base URLs** flipped: `defaultBaseUrl` and `defaultAnalyticsBaseUrl` in [common.ts](packages/template/src/lib/stack-app/apps/implementations/common.ts:127) → `https://api.hexclave.com` / `https://r.hexclave.com`. PR 1's [`getHardcodedFallbackUrls`](packages/stack-shared/src/utils/urls.tsx:199) table now keys on the Hexclave domain. - **Domain inventory sweep** (16 subdomains from the plan): every `api/app/docs/discord/demo/mcp/skill/feedback/test/preview/r/api2/api.staging/idp-jwk-audience/built-with.stack-auth.com` reference in production code, docs-mintlify, examples, READMEs, and contributor guidance flipped to `*.hexclave.com`. Carve-outs: PR 1's intentional JWT issuer dual-accept table in [tokens.tsx](apps/backend/src/lib/tokens.tsx), the legacy `./docs/` folder, the `unified-docs-widget` allowlist (deliberately accepts both during DNS transition), and `url-targets.ts` hosted-component default (baked into existing customer deploys). - **`@deprecated` JSDoc** on every `Stack*` public export ([packages/template/src/lib/stack-app/index.ts](packages/template/src/lib/stack-app/index.ts) + [packages/template/src/index.ts](packages/template/src/index.ts)) — `StackClientApp`, `StackServerApp`, `StackAdminApp` + every constructor/options/JSON type, `StackHandler`, `StackProvider`, `StackTheme`, `useStackApp`, `defineStackConfig`, `StackConfig`. Hexclave\* aliases are now canonical. - **Runtime `console.warn`** ([packages/template/src/internal/deprecation-warning.ts](packages/template/src/internal/deprecation-warning.ts)) — once-per-process when the SDK is loaded from a `@stackframe/*` artifact. Detection uses the existing `STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL` (rewritten at build time to e.g. `js @stackframe/stack@2.8.92` or `js @hexclave/next@1.0.0`); `@hexclave/*` mirror artifacts short-circuit the warning. - **Tier 3 data migration**: new idempotent SQL migration [`20260523000000_rename_internal_project_to_hexclave`](apps/backend/prisma/migrations/20260523000000_rename_internal_project_to_hexclave/migration.sql) — updates the internal Project `displayName` 'Stack Dashboard' → 'Hexclave Dashboard' and `description` only if both still hold the pre-rebrand defaults. Operator-renamed projects untouched, missing row no-ops, re-runs are no-ops. [`seed.ts`](apps/backend/prisma/seed.ts:87) default flipped. `getSharedEmailConfig("Stack Auth")` → `("Hexclave")`. - **Tier 4 brand strings** (mechanical sweep, ~340 files): - Page + OpenAPI titles (Hexclave API / Dashboard / REST API / Webhooks API / Documentation). OpenAPI `info.description` documents `X-Hexclave-*` headers as canonical with compat note on `X-Stack-*`. - `HexclaveAssertionError` message text ([errors.tsx:71](packages/stack-shared/src/utils/errors.tsx:71)) — "an error in Stack." → "an error in Hexclave." - Known-error message templates ([known-errors.tsx](packages/stack-shared/src/known-errors.tsx)) flipped to lead with `x-hexclave-*` + the new `docs.hexclave.com` URL; legacy `x-stack-*` mentioned as compat aliases. **25 e2e test files updated in lockstep**. - Email content: failed-emails-digest body, sendTestEmail recipient (now `sent-with-hexclave.com`), test-email-recipient default. - `CHANGELOG.md` title → "Hexclave Changelog". - `AGENTS.md` env var convention: new vars prefix `HEXCLAVE_` / `NEXT_PUBLIC_HEXCLAVE_` for Category A/B; legacy `STACK_*` explicitly noted as accepted via PR 1's dual-read. - **CLI / init wizard**: - Every dashboard setup snippet, init-stack template, and docs-mintlify page teaches `npx @hexclave/cli@latest init` (was `@stackframe/stack-cli`). [setup-page.tsx](apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx) + [link-existing-onboarding](apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsx). - [init-stack](packages/init-stack/src/index.ts:634) `STACK_*_INSTALL_PACKAGE_NAME_OVERRIDE` defaults flipped to `@hexclave/*`. - Generated `stack/client.ts` / `stack/server.ts` import from `@hexclave/next` and reference `HexclaveClientApp` / `HexclaveServerApp`. - Internal `StackAuthKeys` dashboard component renamed to `HexclaveKeys`. - **docs-mintlify rewrite** (legacy `./docs/` intentionally untouched per scoping decision): - **78 MDX files swept**. `@stackframe/{react,stack,js,tanstack-start,...}` → `@hexclave/{react,stack,js,...}` in install snippets and code blocks; `Stack*` SDK class names → `Hexclave*` in all code examples; 'Stack Auth' brand phrase → 'Hexclave'. - `openapi/{server,admin,client,webhooks}.json` titles → 'Hexclave REST API' / 'Hexclave Webhooks API'. - **Generators flipped before regeneration**: - [`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts), [`/ai/prompts.ts`](packages/stack-shared/src/ai/prompts.ts), [`apps/backend/src/lib/ai/prompts.ts`](apps/backend/src/lib/ai/prompts.ts), [`apps/backend/src/lib/ai/tools/create-email-{template,draft}.ts`](apps/backend/src/lib/ai/tools/create-email-template.ts), [`apps/skills/src/app/route.ts`](apps/skills/src/app/route.ts) (taught MCP tool → `ask_hexclave` with compat note; CLI binary teach → `hexclave`), [`docs-mintlify/snippets/home-prompt-island.jsx`](docs-mintlify/snippets/home-prompt-island.jsx), [`packages/template/README.md`](packages/template/README.md) + integrations/convex/component/README.md. - `generate-sdks` propagated changes to `packages/{react,stack,js}`. - **OpenAPI dual-documentation**: [`apps/backend/src/app/api/latest/route.ts`](apps/backend/src/app/api/latest/route.ts) now lists `X-Hexclave-*` headers as primary documented schemas with `X-Stack-*` duplicates marked `.optional()` (both accepted at runtime by PR 1's normalize-at-proxy shim). - **`@stackframe/emails` virtual module**: dual-aliased to `@hexclave/emails` at the bundler boundary ([email-rendering.tsx:89](apps/backend/src/lib/email-rendering.tsx:89)). Stored email templates continue to import from either name; new AI-generated templates and the system prompt teach `@hexclave/emails`. - **Tier 2 mirror-publish wiring** (new this PR, lays the groundwork for `@hexclave/*` first publish): - [`scripts/rewrite-packages-to-hexclave.ts`](scripts/rewrite-packages-to-hexclave.ts) — rewrites 9 publishable `@stackframe/*` → `@hexclave/*` `package.json` files (reads `HEXCLAVE_VERSION` env or `--version=` flag), pins cross-deps to the shared `@hexclave` version, registers `hexclave` bin alongside `stack` for `@hexclave/cli`. - [`.github/workflows/npm-publish.yaml`](.github/workflows/npm-publish.yaml) appended with rewrite-then-republish step. `pnpm publish` skips already-on-npm versions so reruns are safe. - **Sender email domain**: `noreply@stackframe.co` → `noreply@sent-with-hexclave.com` (the dedicated transactional-sender domain split per the plan, to isolate bulk deliverability from `hexclave.com` reputation); `security@` / `team@stack-auth.com` inbound mailboxes → `@hexclave.com`. - **Self-host docs**: docker network / container names in the bash examples flipped from `stack-auth` to `hexclave` (`hexclave-postgres`, `hexclave-clickhouse`, `hexclave.env`). The docker image tag `stackauth/server:latest` stays per the plan's locked decision. - **GitHub repo slug**: `hexclave/stack-auth` → `hexclave/hexclave` in every `package.json` `repository` field, README link, CHANGELOG raw-asset URL. ## Carve-outs (deliberately untouched) - **[`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)** JWT issuer dual-accept table — PR 1 intentional infrastructure, kept indefinitely. - **Legacy `./docs/` folder** — per scoping decision (only `docs-mintlify/` rewritten). - **`unified-docs-widget` hostname allowlist** — accepts both `.hexclave.com` (canonical) and `.stack-auth.com` (transition window) for DNS rollout. - **`url-targets.ts`** hosted-domain default `.built-with-stack-auth.com` — wire identifier baked into existing customer deploys; indefinite read-fallback. - **Binary visual assets** (logos, favicons, OG images, README screenshots) — out of scope for this PR. Need design work; tracked separately. ## Verification - **`pnpm typecheck`** on `packages/{template,stack-shared,react,stack,js}` + `apps/dashboard`: **all green**. The remaining backend / e-commerce-demo typecheck errors are pre-existing (Prisma codegen output + `./generated/api-versions.json` not present in fresh worktrees without `pnpm run codegen-prisma` + a live DB) and unrelated to this diff. - **`pnpm lint`** on the same 6 packages: all green. - **Final grep** for residual `Stack Auth` / `stack-auth.com` / `@stackframe/stack-cli@latest` references: zero outside the intentional carve-outs above. - **25 e2e test files updated in lockstep** with the known-error message changes (asserted strings flipped to match the new x-hexclave-* + compat-note messages). ## Deploy blockers (ops sequencing before this rebrand goes live) This PR is code-complete, but the rebrand's visible surfaces (SDK default URLs, dashboard links, npm READMEs, REST error messages, runtime deprecation warning) all point at `*.hexclave.com` / `@hexclave/*` resources that don't exist yet. None of these are fixable from a PR — they're ops/registrar/npm work that has to be sequenced before merging this to a release tag. Suggested ordering, hardest blockers first: ### Tier 1 — required before customer-facing deploy (everything below this line *will visibly break customers on day 1* if skipped) 1. **DNS + TLS for `api.hexclave.com` + `api1./api2.hexclave.com`** → must point at the same backend that serves `api.stack-auth.com` (or a backend that mirrors PR 1's dual-accept). The SDK's new `defaultBaseUrl` is `https://api.hexclave.com`; every customer that relied on the old default and upgrades to a post-PR2 SDK build sends API requests here. Until this resolves, every default-config customer's API call NXDOMAINs. 2. **DNS for `app.hexclave.com`** → the dashboard. Referenced in the SDK's default-error messages ("Please create a project on the Hexclave dashboard at https://app.hexclave.com"), the init-stack flow's `wizard-congrats` redirect, and the OAuth dashboard handoff. 3. **DNS for `docs.hexclave.com`** + Mintlify deploy → the SDK runtime deprecation warning (`https://docs.hexclave.com/migration`), every README, every "Learn more" link in the dashboard, and every REST API error body (`/api/overview#authentication`) points here. The MDX is in this PR; the docs build target needs DNS. 4. **DNS for `mcp.hexclave.com`** → the MCP server endpoint that every taught agent integration (`claude mcp add ...`, `cursor`, `codex`, `vscode`) registers. Until this resolves, every `npx @hexclave/cli@latest init` MCP-registration step fails. 5. **Reserve the `@hexclave` npm scope + set repo variable `HEXCLAVE_VERSION`** → the mirror-publish step in `.github/workflows/npm-publish.yaml` is gated on this variable. Without it, the entire taught onboarding command `npx @hexclave/cli@latest init` 404s from the npm registry, *and* every README that says "install `@hexclave/next`" leads to install failure. Pick the initial version intentionally (`1.0.0` or aligned to `@stackframe/stack`); don't accept a silent default. ### Tier 2 — required before announcing the rebrand publicly (lookalike or low-traffic surfaces, but visibly broken) 6. **DNS for `r.hexclave.com`** → the analytics beacon `defaultAnalyticsBaseUrl`. Silent failure if missing (analytics drops), but should land alongside Tier 1. 7. **Register `sent-with-hexclave.com` + full email auth (SPF / DKIM / DMARC)** → the new default sender domain for shared-sender transactional emails. Without it the dashboard "send test email" path emits bounces, and shared-sender flows (`getSharedEmailConfig("Hexclave")`) deliver to spam at best. 8. **MX + SPF / DMARC for `hexclave.com`** → `team@hexclave.com` and `security@hexclave.com` mailboxes. The security disclosure mailbox is referenced in [`.github/SECURITY.md`](.github/SECURITY.md); `team@hexclave.com` is the actual recipient of internal feedback emails sent at runtime by [`apps/backend/src/lib/internal-feedback-emails.tsx`](apps/backend/src/lib/internal-feedback-emails.tsx). Today, every runtime feedback email bounces. 9. **DNS for `skill.hexclave.com`** → the canonical AI-agent skill fetch URL (the agent bootstrap pivot). Without it, the entire "agent downloads `SKILL.md` from a known URL" flow taught in [`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts) fails. 10. **Create `github.com/hexclave/hexclave` as a public repo** (even as a redirect to `hexclave/stack-auth`) **OR** rewrite every `package.json` `"repository"` field + dashboard footer "view on GitHub" link to point at `hexclave/stack-auth` (which already exists). Currently every npm package page's "Repository" link is dead, and the dashboard's GitHub button + dev-tool repo link are dead. ### Tier 3 — broken but low-visibility / low-traffic 11. **DNS for `discord.hexclave.com`** → Discord invite redirect, used in every README's chip and the dashboard footer. 12. **DNS for `demo.hexclave.com`** → "✨ Demo" badge in every npm package README. Broken-image badge on the package page. 13. **DNS + TLS for `built-with-hexclave.com`** → optional hosted-handler domain (the default reverted to `.built-with-stack-auth.com` in this PR's carve-outs, so this only matters for projects that manually flip). ## Other follow-ups (not deploy-blocking) - **E2E snapshot regen across the full suite** for the dual-emitted `x-hexclave-*` response headers (PR 1 follow-up; `vitest -u` in CI absorbs). - **Binary visual assets** — logos, favicons, OG images, README screenshots; need design pass. - **Backend OpenAPI fumadocs regen** in CI flow — the JSON files in `docs-mintlify/openapi/` are committed but regen runs in CI. Verify the workflow that does this still works against the post-PR2 source. - **Backend typecheck infra debt** — needs `codegen-prisma` + `codegen-route-info` to clear; pre-existing, unaffected by this PR. ## Test plan - [ ] CI runs full e2e suite (with `vitest -u` to absorb residual snapshot deltas, then committed back). - [ ] Spot-check: new `@hexclave/cli init` (once published) generates `hexclave.config.ts` and works against a fresh project. - [ ] Spot-check: existing customer with `@stackframe/stack` import sees the once-per-process `console.warn` recommending `@hexclave/next` on SDK init. - [ ] Manual: dashboard setup page renders the `npx @hexclave/cli@latest init` snippet and the `x-hexclave-publishable-client-key` API header in the curl example. - [ ] Manual: a fresh `pnpm run prisma migrate` against a clean DB sets the internal project displayName to 'Hexclave Dashboard'. --------- Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
16 KiB
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
Hexclave 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
HEXCLAVE_(orNEXT_PUBLIC_HEXCLAVE_if they are public) for new customer-facing or framework-internal vars. This ensures that their changes are picked up by Turborepo (and helps readability). Self-host operator vars (e.g.STACK_DATABASE_CONNECTION_STRING) and build/dev/test vars (e.g.NEXT_PUBLIC_STACK_API_URLin dev fixtures) staySTACK_-prefixed for now — the dual-read shim accepts both. The Hexclave SDK reads both prefixes; new names take precedence when both are set. - 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 Hexclave 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 Hexclave 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! if its already staged and you didnt do it then dont unstage it.
- NEVER run destructive or working-tree-mutating git operations without EXPLICIT user permission in the current turn. This includes (non-exhaustive):
git stash/stash pop/stash drop/stash clear,git reset(any flag),git checkout -- <path>/git restore,git clean,git rebase,git revert,git commit --amend,git push --force/--force-with-lease,git branch -D,git tag -d,git filter-branch,git update-ref. Read-only inspection (git status,git diff,git log,git blame,git show,git show HEAD:path) is fine. If you think a destructive op is the right move, describe it and wait for a yes — do NOT run it to "verify" or "clean up". - When building frontend or React code for the dashboard, refer to DESIGN-GUIDE.md.
- 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.
- If you're using the browser to test the dashboard and need to sign in, use GitHub OAuth to sign in (by default it should redirect you to the mock OAuth provider page, where you can sign in with admin@example.com).
- NEVER INSTALL A NEW PACKAGE (or anything else) WITHOUT EXPLICIT APPROVAL FROM THE USER.
- A "development environment" is either an RDE (remote development environment; = local dashboard + prod backend) or a local emulator (local dashboard + local backend). When communicating to the user, we always say "development environment" instead of RDE or local emulator (the distinction to the user is minor, even though the implementation is quite different).
- NEVER EVER return a server error with an internal server error that may contain information that the user shouldn't see. For example, never return the error message on a public API from an upstream provider without properly filtering it first. Most of the time, for internal server errors, you should just use StackAssertionError (which won't pass the message to the user), not StatusError (you almost never want to instantiate a StatusError with status 5xx).
- When adding code to the
privatepart of the backend, put the actual implementation intoimplementation(if the submodule is checked out), and implement a simple fallback inimplementation-fallbackfor self-hosters.implementation.generated.tswill automatically be generated, which you can then import fromindex.ts. (See the existing code as an example.) If the submodule isn't checked out, but you need to add code to theprivatepart of the backend, let the user know. - Security-sensitive code on the backend that shouldn't be public should be in the
privatepart of the backend.
Code-related
- Use ES6 maps instead of records wherever you can.
- Read replicas for raw Prisma queries: When writing raw SQL queries (
$queryRaw,$queryRawUnsafe), always use$replica()for read-only queries (e.g.globalPrismaClient.$replica().$queryRaw\SELECT ...`). This routes reads to the database replica and reduces load on the primary. Do NOT use$replica()` for queries inside transactions or queries containing writes (INSERT/UPDATE/DELETE, even in CTEs).