Remove CLAUDE-KNOWLEDGE file

This commit is contained in:
Konstantin Wohlwend 2026-06-01 15:06:23 -07:00
parent d1d96fdcc4
commit 6fb99c4340
2 changed files with 0 additions and 598 deletions

View File

@ -1,597 +0,0 @@
# CLAUDE-KNOWLEDGE.md
This file contains knowledge learned while working on the codebase in Q&A format.
## Q: What are the local development ports for the MCP and Skills apps?
A: The MCP app runs on port suffix `44` from `apps/mcp/package.json`, so with `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=91` it is at `http://localhost:9144/mcp`. The Skills app runs on suffix `45` from `apps/skills/package.json`, so with the same prefix it is at `http://localhost:9145`. The dev launchpad app list in `apps/dev-launchpad/public/index.html` should use these suffixes.
## Q: Where does the Stack CLI init agent prompt come from?
A: `packages/stack-cli/src/lib/init-prompt.ts` re-exports `createInitPrompt` from `packages/stack-shared/src/helpers/init-prompt.ts`. The CLI calls it from `packages/stack-cli/src/commands/init.ts` after project creation/linking, then sends the result to Claude. The shared helper embeds `aiSetupPrompt` from `packages/stack-shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts`, with CLI-specific context that project/env setup has already happened. The CLI wrapper tells the agent to apply only relevant setup sections so optional Convex/Supabase/CLI-app sections are not forced onto every project.
## Q: How are connected-account OAuth tokens stored and refreshed?
A: Connected accounts live in `ProjectUserOAuthAccount`. Stored refresh tokens are in `OAuthToken` (`oauthAccountId`, `scopes`, `isValid`), and cached access tokens are in `OAuthAccessToken` (`expiresAt`, `scopes`, `isValid`). A null `OAuthAccessToken.expiresAt` means the OAuth provider did not supply an access-token expiry; `retrieveOrRefreshAccessToken` treats null-expiry tokens as candidates and still calls the provider-specific validity check before returning them. If no usable access token exists, it looks for valid refresh tokens with matching scopes and invalidates only those that the provider explicitly rejects.
## Q: Which Hexclave rename compatibility layers should be avoided in PR #1475 follow-ups?
A: Do not keep backwards compatibility for the MCP tool name, cross-domain auth query parameter names, `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX`, or a parallel `hexclaveAppInternalsSymbol`. For refresh/access cookies, read both legacy Stack and new Hexclave cookie names, but only write the canonical Hexclave cookies.
## Q: How should GitHub Contents API request-body assertions be written in Stack Auth tests?
A: Prefer inline snapshots over individual field selectors. For request bodies that contain base64 file content, parse the JSON body, assert it is an object, decode the `content` field back to UTF-8, and snapshot the normalized call object so the test verifies the path, method, headers, branch, message, sha, and rendered file content together.
## Q: How should Stack CLI GitHub source paths be stored?
A: Explicit `stack config push --source github` paths should be normalized as repo-relative paths before storing source metadata. Trim whitespace and strip leading `./`, repeated `./`, and leading `/` segments, matching the dashboard workflow generator's normalization for `STACK_AUTH_CONFIG_PATH` and workflow paths.
## Q: How should Stack CLI code handle flags proven present by nearby validation?
A: Avoid non-null assertions even when an earlier missing-flags check proves presence. Use `flags.foo ?? throwErr("Expected ...; this should have been caught by ...")` so the type system receives a definite value and future refactors fail loudly with the violated assumption.
## Q: How do anonymous users work in Stack Auth?
A: Anonymous users are a special type of user that can be created without any authentication. They have `isAnonymous: true` in the database and use different JWT signing keys with a `role: 'anon'` claim. Anonymous JWTs use a prefixed secret ("anon-" + audience) for signing and verification.
## Q: How are anonymous user JWTs different from regular user JWTs?
A: Anonymous JWTs have:
1. Different kid (key ID) - prefixed with "anon-" in the generation
2. Different signing secret - uses `getPerAudienceSecret` with `isAnonymous: true`
3. Contains `role: 'anon'` in the payload
4. Must pass `isAnonymous` flag to both `getPrivateJwk` and `getPublicJwkSet` functions for proper verification
## Q: What is the X-Stack-Allow-Anonymous-User header?
A: This header controls whether anonymous users are allowed to access an endpoint. When set to "true" (which is the default for client SDK calls), anonymous JWTs are accepted. When false or missing, anonymous users get an `AnonymousAuthenticationNotAllowed` error.
## Q: How do you upgrade an anonymous user to a regular user?
A: When an anonymous user (identified by `is_anonymous: true`) signs up or signs in through any auth method (password, OTP, OAuth), instead of creating a new user, the system upgrades the existing anonymous user by:
1. Setting `is_anonymous: false`
2. Adding the authentication method (email, password, OAuth provider, etc.)
3. Keeping the same user ID so old JWTs remain valid
## Q: How do you access the current user in smart route handlers?
A: In smart route handlers, the user is accessed through `fullReq.auth?.user` not through the destructured `auth` parameter. The auth parameter only guarantees `tenancy`, while `user` is optional and needs to be accessed from the full request.
## Q: How do user CRUD handlers work with parameters?
A: The `adminUpdate` and similar methods take parameters directly, not wrapped in a `params` object:
- Correct: `adminUpdate({ tenancy, user_id: "...", data: {...} })`
- Wrong: `adminUpdate({ tenancy, params: { user_id: "..." }, data: {...} })`
## Q: What query parameter filters anonymous users in user endpoints?
A: The `include_anonymous` query parameter controls whether anonymous users are included in results:
- Without parameter or `include_anonymous=false`: Anonymous users are filtered out
- With `include_anonymous=true`: Anonymous users are included in results
This applies to user list, get by ID, search, and team member endpoints.
## Q: How does the JWKS endpoint handle anonymous keys?
A: The JWKS (JSON Web Key Set) endpoint at `/.well-known/jwks.json`:
- By default: Returns only regular user signing keys
- With `?include_anonymous=true`: Returns both regular and anonymous user signing keys
This allows systems that need to verify anonymous JWTs to fetch the appropriate public keys.
## Q: What is the typical test command flow for Stack Auth?
A:
1. `pnpm typecheck` - Check TypeScript compilation
2. `pnpm lint --fix` - Fix linting issues
3. `pnpm test run <path>` - Run specific tests (the `run` is important to avoid watch mode)
4. Use `-t "test name"` to run specific tests by name
## Q: How do E2E tests handle authentication in Stack Auth?
A: E2E tests use `niceBackendFetch` which automatically:
- Sets `x-stack-allow-anonymous-user: "true"` for client access type
- Includes project keys and tokens from `backendContext.value`
- Handles auth tokens through the context rather than manual header setting
## Q: What is the signature of a verification code handler?
A: The handler function in `createVerificationCodeHandler` receives 5 parameters:
```typescript
async handler(tenancy, validatedMethod, validatedData, requestBody, currentUser)
```
Where:
- `tenancy` - The tenancy object
- `validatedMethod` - The validated method data (e.g., `{ email: "..." }`)
- `validatedData` - The validated data object
- `requestBody` - The raw request body
- `currentUser` - The current authenticated user (if any)
## Q: How does JWT key derivation work for anonymous users?
A: The JWT signing/verification uses a multi-step key derivation process:
1. **Secret Derivation**: `getPerAudienceSecret()` creates a derived secret from:
- Base secret (STACK_SERVER_SECRET)
- Audience (usually project ID)
- Optional "anon-" prefix for anonymous users
2. **Kid Generation**: `getKid()` creates a key ID from:
- Base secret (STACK_SERVER_SECRET)
- "kid" string with optional "anon-" prefix
- Takes only first 12 characters of hash
3. **Key Generation**: Private/public keys are generated from the derived secret
## Q: What is the JWT signing and verification flow?
A:
**Signing (signJWT)**:
1. Derive secret: `getPerAudienceSecret(audience, STACK_SERVER_SECRET, isAnonymous)`
2. Generate kid: `getKid(STACK_SERVER_SECRET, isAnonymous)`
3. Create private key from derived secret
4. Sign JWT with kid in header and role in payload
**Verification (verifyJWT)**:
1. Decode JWT without verification to read the role
2. Check if role === 'anon' to determine if it's anonymous
3. Derive secret with same parameters as signing
4. Generate kid with same parameters as signing
5. Create public key set and verify JWT
## Q: What makes anonymous JWTs different from regular JWTs?
A: Anonymous JWTs have:
1. **Different derived secret**: Uses "anon-" prefix in secret derivation
2. **Different kid**: Uses "anon-" prefix resulting in different key ID
3. **Role field**: Contains `role: 'anon'` in the payload
4. **Verification requirements**: Requires `allowAnonymous: true` flag to be verified
## Q: How do you debug JWT verification issues?
A: Common debugging steps:
1. Check that the `X-Stack-Allow-Anonymous-User` header is set to "true"
2. Verify the JWT has `role: 'anon'` in its payload
3. Ensure the same secret derivation parameters are used for signing and verification
4. Check that the kid in the JWT header matches the expected kid
5. Verify that `allowAnonymous` flag is passed through the entire call chain
## Q: What is the difference between getPrivateJwk and getPrivateJwkFromDerivedSecret?
A:
- `getPrivateJwk(secret, isAnonymous)`: Takes a base secret, may derive it internally, generates kid
- `getPrivateJwkFromDerivedSecret(derivedSecret, kid)`: Takes an already-derived secret and pre-calculated kid
The second is used internally for the actual JWT signing flow, while the first is for backward compatibility and special cases like IDP.
## Q: How does the JWT verification process work with jose?
A: The `jose.jwtVerify` function:
1. Extracts the kid from the JWT header
2. Looks for a key with matching kid in the provided JWK set
3. Uses that key to verify the JWT signature
4. If no matching kid is found, verification fails with an error
## Q: What causes UNPARSABLE_ACCESS_TOKEN errors?
A: This error occurs when JWT verification fails in `decodeAccessToken`. Common causes:
1. Kid mismatch - the kid in the JWT header doesn't match any key in the JWK set
2. Wrong secret derivation - using different parameters for signing vs verification
3. JOSEError thrown during `jose.jwtVerify` due to invalid signature or key mismatch
## OAuth Flow and Validation
### Q: Where does OAuth redirect URL validation happen in the flow?
A: The validation happens in the callback endpoint (`/api/v1/auth/oauth/callback/[provider_id]/route.tsx`), not in the authorize endpoint. The authorize endpoint just stores the redirect URL and redirects to the OAuth provider. The actual validation occurs when the OAuth provider calls back, and the oauth2-server library validates the redirect URL.
### Q: How do you test OAuth flows that should fail?
A: Use `Auth.OAuth.getMaybeFailingAuthorizationCode()` instead of `Auth.OAuth.getAuthorizationCode()`. The latter expects success (status 303), while the former allows you to test failure cases. The failure happens at the callback stage with a 400 status and specific error message.
### Q: What error is thrown for invalid redirect URLs in OAuth?
A: The callback endpoint returns a 400 status with the message: "Invalid redirect URI. The URL you are trying to redirect to is not trusted. If it should be, add it to the list of trusted domains in the Stack Auth dashboard."
## Wildcard Pattern Implementation
### Q: How do you handle ** vs * precedence in regex patterns?
A: Use a placeholder approach to prevent ** from being corrupted when replacing *:
```typescript
const doubleWildcardPlaceholder = '\x00DOUBLE_WILDCARD\x00';
regexPattern = regexPattern.replace(/\*\*/g, doubleWildcardPlaceholder);
regexPattern = regexPattern.replace(/\*/g, '[^.]*');
regexPattern = regexPattern.replace(new RegExp(doubleWildcardPlaceholder, 'g'), '.*');
```
### Q: Why can't you use `new URL()` with wildcard domains?
A: Wildcard characters (* and **) are not valid in URLs and will cause parsing errors. For wildcard domains, you need to manually parse the URL components instead of using the URL constructor.
### Q: How do you validate URLs with wildcards?
A: Extract the hostname pattern manually and use `matchHostnamePattern()`:
```typescript
const protocolEnd = domain.baseUrl.indexOf('://');
const protocol = domain.baseUrl.substring(0, protocolEnd + 3);
const afterProtocol = domain.baseUrl.substring(protocolEnd + 3);
const pathStart = afterProtocol.indexOf('/');
const hostnamePattern = pathStart === -1 ? afterProtocol : afterProtocol.substring(0, pathStart);
```
## Testing Best Practices
### Q: How should you run multiple independent test commands?
A: Use parallel execution by batching tool calls together:
```typescript
// Good - runs in parallel
const [result1, result2] = await Promise.all([
niceBackendFetch("/endpoint1"),
niceBackendFetch("/endpoint2")
]);
// In E2E tests, the framework handles this automatically when you
// batch multiple tool calls in a single response
```
### Q: What's the correct way to update project configuration in E2E tests?
A: Use the `/api/v1/internal/config/override/environment` endpoint with PATCH method and admin access token:
```typescript
await niceBackendFetch("/api/v1/internal/config/override/environment", {
method: "PATCH",
accessType: "admin",
headers: {
'x-stack-admin-access-token': adminAccessToken,
},
body: {
config_override_string: JSON.stringify({
'domains.trustedDomains.name': { baseUrl: '...', handlerPath: '...' }
}),
},
});
```
## Code Organization
### Q: Where does domain validation logic belong?
A: Core validation functions (`isValidHostnameWithWildcards`, `matchHostnamePattern`) belong in the shared utils package (`packages/stack-shared/src/utils/urls.tsx`) so they can be used by both frontend and backend.
### Q: How do you simplify validation logic with wildcards?
A: Replace wildcards with valid placeholders before validation:
```typescript
const normalizedDomain = domain.replace(/\*+/g, 'wildcard-placeholder');
url = new URL(normalizedDomain); // Now this won't throw
```
## Debugging E2E Tests
### Q: What does "ECONNREFUSED" mean in E2E tests?
A: The backend server isn't running. Make sure to start the backend with `pnpm dev` before running E2E tests.
### Q: How do you debug which stage of OAuth flow is failing?
A: Check the error location:
- Authorize endpoint (307 redirect) - Initial request succeeded
- Callback endpoint (400 error) - Validation failed during callback
- Token endpoint (400 error) - Validation failed during token exchange
### Q: How should connected-account OAuth access-token refresh errors be classified?
A: In `apps/backend/src/oauth/providers/base.tsx`, invalid/revoked refresh-token provider errors such as `invalid_grant` return `Result.error({ type: "invalid-refresh-token", ... })` so `access-token-helpers.tsx` can invalidate that stored refresh token and try another. Transient provider/network failures such as openid-client `RPError: outgoing request timed out after 3500ms` return `Result.error({ type: "temporarily-unavailable", cause })`; the connected-account helper converts that to `OAuthProviderTemporarilyUnavailable` without invalidating the refresh token. Expected refresh outcomes should be represented in the provider return type instead of thrown as known/status errors. Refresh requests use a 6s openid-client HTTP timeout and retry transient failures once. If a retry sees `invalid_grant` after an ambiguous transient failure, keep treating it as temporarily unavailable rather than invalidating the refresh token, because the first request may have reached the provider and rotated the token before our client timed out. Sentry should capture non-revocation refresh issues (temporary provider failure, invalid client, unexpected) with provider id/class, attempts, retry count, ambiguity state, final cause, and all provider errors seen during attempts; normal revoked/expired refresh tokens should not be reported.
## Git and Development Workflow
### Q: How should you format git commit messages in this project?
A: Use a HEREDOC to ensure proper formatting:
```bash
git commit -m "$(cat <<'EOF'
Commit message here.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"
```
### Q: What commands should you run before considering a task complete?
A: Always run:
1. `pnpm test run <relevant-test-files>` - Run tests
2. `pnpm lint` - Check for linting errors
3. `pnpm typecheck` - Check for TypeScript errors
## Common Pitfalls
### Q: Why might imports get removed after running lint --fix?
A: ESLint may remove "unused" imports. Always verify your changes after auto-fixing, especially if you're using imports in a way ESLint doesn't recognize (like in test expectations).
### Q: What's a common linting error in test files?
A: Missing newline at end of file. ESLint requires files to end with a newline character.
### Q: How do you handle TypeScript errors about missing exports?
A: Double-check that you're only importing what's actually exported from a module. The error "Module declares 'X' locally, but it is not exported" means you're trying to import something that isn't exported.
## Project Transfer Implementation
### Q: How do I add a new API endpoint to the internal project?
A: Create a new route file in `/apps/backend/src/app/api/latest/internal/` using the `createSmartRouteHandler` pattern. Internal endpoints should check `auth.project.id === "internal"` and throw `KnownErrors.ExpectedInternalProject()` if not.
### Q: How do team permissions work in Stack Auth?
A: Team permissions are defined in `/apps/backend/src/lib/permissions.tsx`. The permission `team_admin` (not `$team_admin`) is a normal permission that happens to be defined by default on the internal project. Use `ensureUserTeamPermissionExists` to check if a user has a specific permission.
### Q: How do I check team permissions in the backend?
A: Use `ensureUserTeamPermissionExists` from `/apps/backend/src/lib/request-checks.tsx`. Example:
```typescript
await ensureUserTeamPermissionExists(prisma, {
tenancy: internalTenancy,
teamId: teamId,
userId: userId,
permissionId: "team_admin",
errorType: "required",
recursive: true,
});
```
### Q: How do I add new functionality to the admin interface?
A: Don't use server actions. Instead, implement the endpoint functions on the admin-app and admin-interface. Add methods to the AdminProject class in the SDK packages that call the backend API endpoints.
### Q: How do I use TeamSwitcher component in the dashboard?
A: Import `TeamSwitcher` from `@stackframe/stack` and use it like:
```typescript
<TeamSwitcher
triggerClassName="w-full"
teamId={selectedTeamId}
onChange={async (team) => {
setSelectedTeamId(team.id);
}}
/>
```
### Q: How do I write E2E tests for backend endpoints?
A: Import `it` from helpers (not vitest), and set up the project context inside each test:
```typescript
import { describe } from "vitest";
import { it } from "../../../../../../helpers";
import { Auth, Project, backendContext, niceBackendFetch, InternalProjectKeys } from "../../../../../backend-helpers";
it("test name", async ({ expect }) => {
backendContext.set({ projectKeys: InternalProjectKeys });
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
// test logic
});
```
### Q: Where is project ownership stored in the database?
A: Projects have an `ownerTeamId` field in the Project model (see `/apps/backend/prisma/schema.prisma`). This links to a team in the internal project.
### Q: What's the difference between ensureTeamMembershipExists and ensureUserTeamPermissionExists?
A: `ensureTeamMembershipExists` only checks if a user is a member of a team. `ensureUserTeamPermissionExists` checks if a user has a specific permission (like `team_admin`) within that team. The latter also calls `ensureTeamMembershipExists` internally.
### Q: How do I handle errors in the backend API?
A: Use `KnownErrors` from `@stackframe/stack-shared` for standard errors (e.g., `KnownErrors.ProjectNotFound()`). For custom errors, use `StatusError` from `@stackframe/stack-shared/dist/utils/errors` with an HTTP status code and message.
### Q: What's the pattern for TypeScript schema validation in API routes?
A: Use yup schemas from `@stackframe/stack-shared/dist/schema-fields`. Don't use regular yup imports. Example:
```typescript
import { yupObject, yupString, yupNumber } from "@stackframe/stack-shared/dist/schema-fields";
```
### Q: How are teams and projects related in Stack Auth?
A: Projects belong to teams via the `ownerTeamId` field. Teams exist within the internal project. Users can be members of multiple teams and have different permissions in each team.
### Q: How do I properly escape quotes in React components to avoid lint errors?
A: Use template literals with backticks instead of quotes in JSX text content:
```typescript
<Typography>{`Text with "quotes" inside`}</Typography>
```
### Q: What auth headers are needed for internal API calls?
A: Internal API calls need:
- `X-Stack-Access-Type: 'server'`
- `X-Stack-Project-Id: 'internal'`
- `X-Stack-Secret-Server-Key: <server key>`
- Either `X-Stack-Auth: Bearer <token>` or a session cookie
### Q: How do I reload the page after a successful action in the dashboard?
A: Use `window.location.reload()` after the action completes. This ensures the UI reflects the latest state from the server.
### Q: What's the file structure for API routes in the backend?
A: Routes follow Next.js App Router conventions in `/apps/backend/src/app/api/latest/`. Each route has a `route.tsx` file that exports HTTP method handlers (GET, POST, etc.).
### Q: How do I get all teams a user is a member of in the dashboard?
A: Use `user.useTeams()` where `user` is from `useUser({ or: 'redirect', projectIdMustMatch: "internal" })`.
### Q: What's the difference between client and server access types?
A: Client access type is for frontend applications and has limited permissions. Server access type is for backend operations and requires a secret key. Admin access type is for dashboard operations with full permissions.
### Q: How to avoid TypeScript "unnecessary conditional" errors when checking auth.user?
A: If the schema defines `auth.user` as `.defined()`, TypeScript knows it can't be null, so checking `if (!auth.user)` causes a lint error. Remove the check or adjust the schema if the field can be undefined.
### Q: What to do when TypeScript can't find module '@stackframe/stack' declarations?
A: This happens when packages haven't been built yet. Run these commands in order:
```bash
pnpm clean && pnpm i && pnpm codegen && pnpm build:packages
```
Then restart the dev server. This rebuilds all packages and generates the necessary TypeScript declarations.
## Q: How is backwards compatibility for the offer→product rename handled in the payments purchase APIs?
A: API v1 requests are routed through the `v2beta1` migration. The migration wraps the latest handlers, accepts legacy `offer_id`/`offer_inline` request fields, translates product-related errors back to the old offer error codes/messages, and augments responses (like `validate-code`) with `offer`/`conflicting_group_offers` aliases alongside the new `product` fields. Newer API versions keep the product-only contract.
## Q: How does the Stack Auth template dev tool decide whether to iframe the dashboard?
A: The dev tool always iframes the Dashboard tab and provides an "Open in New Tab" escape hatch for auth or framing issues.
### Q: What's the reliable way to run targeted tests across backend, dashboard, stack-shared, and e2e at once?
A: Run from the monorepo root with explicit file paths: `pnpm test run "<path1>" "<path2>" ...`. This works even when individual packages do not define a local `test` script. Also avoid passing an extra `run` argument to package-level `test` scripts that already execute `vitest run`.
### Q: What's the new Authorization header format for Stack token forwarding?
A: Use `getAuthorizationHeader()`, which returns `Bearer stackauth_<base64(getAuthJson())>`. The payload encodes both `accessToken` and `refreshToken`, and request-like token stores should parse this format first, with legacy `x-stack-auth` remaining as a backward-compatible fallback.
### Q: What RequestLike header shapes are supported by tokenStore overrides?
A: `RequestLike` accepts both `{ headers: { get(name): string | null } }` and `{ headers: Record<string, string | null> }`. Header lookup is case-insensitive for record-style headers, and supports `authorization`, `x-stack-auth`, and `cookie`.
### Q: Which env var should emulator onboarding URLs use for dashboard port?
A: Use `EMULATOR_DASHBOARD_PORT` (default `26700`) or explicit `STACK_LOCAL_EMULATOR_DASHBOARD_URL`. Do not derive emulator URLs from `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX`, because that points to the host dev environment ports (e.g. `92xx`) rather than the emulator host-forwarded ports.
### Q: Why does `PATCH /api/v1/internal/projects/current` fail in local emulator when updating only `onboarding_state`?
A: `createOrUpdateProjectWithLegacyConfig` always called `overrideEnvironmentConfigOverride`, even when there were zero config override keys to apply. In local emulator mode, environment config overrides are intentionally blocked, so this threw `Environment configuration overrides cannot be changed in the local emulator` and returned 500. The fix is to skip `overrideEnvironmentConfigOverride` unless `configOverrideOverride` has at least one key.
### Q: Why might local emulator UI changes in `apps/dashboard` not appear immediately at `localhost:26700`?
A: The QEMU local emulator serves the dashboard from the Docker image bundled inside the VM, not from the host repo's live source tree. Source edits in `apps/dashboard` are reflected in lint/typecheck/tests immediately, but you need an updated emulator image/runtime to see the visual change on `26700`.
### Q: Why can local emulator onboarding break with `ParseError` on non-`.ts` config files (e.g. `test-config.untracked`)?
A: The emulator writes TypeScript-style config source (`import type ...` and `config: StackConfig`) and later evaluates it with Jiti. If the filename has a non-TS extension, Jiti may parse it as plain JS and fail. Fix by evaluating unknown extensions as TypeScript (use a `.ts` eval filename fallback) and add regression coverage for non-`.ts` config paths.
### Q: How should docs fetch the canonical AI setup prompt text?
A: Expose an unauthenticated backend endpoint at `/api/v1/setup-prompt` that returns `getSdkSetupPrompt("ai-prompt", { tanstackQuery: false })` as plain text and sets `Cache-Control: public, max-age=60`. Mintlify docs should fetch `https://api.stack-auth.com/api/v1/setup-prompt` directly when docs and API are on different origins.
### Q: Can Mintlify snippets import other snippets?
A: No. Keep snippet logic inline within each snippet file; avoid snippet-to-snippet imports. For setup prompt fetching, point directly to `https://api.stack-auth.com/api/v1/setup-prompt` when docs run on a different origin/port than the API.
## Q: How does `/api/v1/ai/query/generate` reject invalid AI tool names?
A: Invalid `tools` entries are rejected by `requestBodySchema` in `apps/backend/src/lib/ai/schema.ts` via `yupString().oneOf(TOOL_NAMES)`, so the endpoint returns a structured `SCHEMA_ERROR` object mentioning `body.tools[n]` rather than a custom `"Invalid tool names"` string from handler logic.
## Q: Why did the internal metrics E2E snapshots need to change in April 2026?
A: The `/api/v1/internal/metrics` response now intentionally includes `analytics_overview.daily_anonymous_visitors_fallback`, `analytics_overview.anonymous_visitors_fallback`, and `active_users_by_country`. Those additions are reflected in `packages/stack-shared/src/interface/admin-metrics.ts` and the backend route, so the E2E snapshots must include them instead of treating them as regressions.
## Q: Why can environment config override writes fail with a product/product-line customer type warning after creating a preview project?
A: The environment override endpoint validates the new environment override against the rendered branch config. Preview dummy payments data must therefore be internally coherent: products assigned to a product line need the same `customerType` as that product line, otherwise unrelated environment patches can fail with warnings like `Product "growth" has customer type "user" but its product line "workspace" has customer type "team"`.
## Q: How do you keep the Stack Auth dev tool from reopening automatically after navigation or reload?
A: Treat `isOpen` as mount-local state in `packages/template/src/dev-tool/dev-tool-core.ts`: load persisted preferences with `isOpen: false`, and save state back to localStorage with `isOpen: false` so tab/size preferences persist without reopening the panel on the next mount.
## Q: How should the Stack Auth dev tool indicator avoid being hidden by other dev indicators?
A: Do not dynamically reflow around framework indicators; that makes pointer interaction brittle. Keep the trigger anchored to its saved corner and give `.sdt-trigger` a max practical z-index (`2147483647`) so the Stack indicator renders above Next/Turbo overlays.
## Q: How should Stack Auth dev tool trigger movement feel?
A: Dragging should remain instant/direct, but programmatic moves like snap-to-corner after drag, resize reposition, and post-measurement correction should use a short snappy left/top transition. In `dev-tool-core.ts`, toggle a dedicated animation class only for those programmatic updates and remove it shortly after.
## Q: How do you prevent duplicate Stack Auth dev tool indicators from multiple package/module instances?
A: `createDevTool` in `packages/template/src/dev-tool/dev-tool-core.ts` should register a browser-wide singleton instance on `window` with an idempotent cleanup function, call any previous global cleanup before mounting, and remove leftover `#__stack-dev-tool-root` nodes as a fallback for older instances that did not register cleanup.
## Q: How should the Stack Auth dev tool handle Dashboard tab sizing?
A: Keep the user's default panel width/height in state for normal tabs, but apply a transient fullscreen class while the active tab is `dashboard`. The fullscreen class should override fixed dimensions and hide resize handles, then remove itself and restore the saved default dimensions when any other tab is selected.
## Q: How should the Stack Auth dev tool animate Dashboard fullscreen transitions?
A: Add a short-lived geometry animation class only around tab-driven switches into or out of Dashboard fullscreen. Animate `width`, `height`, `right`, `bottom`, and radius for the mode change, then remove the class so manual dragging/resizing remains direct and does not lag.
## Q: Should the Stack Auth dev tool Dashboard tab be gated on local emulator mode?
A: No. The Dashboard tab should always render the dashboard URL in an iframe inside the dev tool, and should also show an "Open in New Tab" link so users can escape iframe/auth/framing issues without losing the embedded view.
## Q: How should the Dashboard iframe use space in the Stack Auth dev tool?
A: In Dashboard fullscreen mode, the panel should cover the full viewport with no inset or rounded frame, and the iframe should fill all available content space. Put auxiliary actions like "Open in New Tab" in a top-edge overlay below the tab bar so they do not reserve layout height from the iframe.
## Q: How do you maximize iframe tabs inside the Stack Auth dev tool panel?
A: Mark Docs/Dashboard panes with an iframe-specific class, remove the normal 16px tab-pane padding, hide pane overflow, and give the iframe container explicit `width: 100%` and `height: 100%`. Keep toolbar actions as absolute overlays so they do not reduce iframe layout space.
## Q: How should docs access work in the Stack Auth dev tool?
A: Docs should not be an iframe-backed tab. Keep docs as a top-bar external link to `https://docs.stack-auth.com` with an up-right arrow icon, and migrate any persisted `activeTab: "docs"` value back to `overview`.
## Q: How should the Stack Auth dev tool Console tab handle large log volumes?
A: Keep the full log history in the shared log store, but render only the newest 100 entries initially. When the log scroll area nears the bottom, increase the visible count by another 100 and rerender. The Console tab should be a single logs view with Copy, Export, and Clear buttons in the header rather than nested Logs/Config subtabs.
## Q: How should the Stack Auth dev tool Customize page detail show page metadata?
A: Avoid repeated page-type badges like `Handler` in page tiles or detail headers. Keep actionable badges such as `Outdated`, show the compact route path next to the page title, use `Open` for the page action, and phrase the customization prompt as "Want to customize this page? Paste this prompt into your coding agent." with a `Copy prompt` button below it.
## Q: How should the Stack Auth dev tool Customize page Open action behave?
A: The page detail `Open` action should be a taller button matching the redirect code chip height, include a small up-right arrow icon, and always open the selected page in a new tab.
## Q: How should the Stack Auth dev tool Support tab be structured?
A: Do not keep top-level Feedback/Feature Request subtabs unless the backend supports them. The Support tab should mount the feedback form directly, show Discord/Email/GitHub links at the top, keep only Feedback and Bug Report choices in the form, and use a Submit button with a right-arrow icon after the text.
## Q: Which tab should the Stack Auth dev tool show when opened?
A: Opening the dev tool should always reset `activeTab` to `overview` before creating the panel. Other preferences like size can persist, but each fresh open should start on Overview instead of the last-used tab.
## Q: How should Stack Auth dev tool PR review comments around Overview and trigger behavior be handled?
A: Keep trigger corner resolution on-screen even in tiny viewports by snapping to bounded edge positions, remove unused trigger-position helpers, treat browser fetch `TypeError`s such as Safari's `Load failed` as best-effort Overview hydration errors, replace auth-method skeletons with a fallback on every load failure, and compute the `Auth method active` checklist row from loaded project config instead of hard-coding it as passing.
## Q: Why can `pnpm run dev` fail with `ERR_MODULE_NOT_FOUND` for `@stackframe/stack/dist/esm/index.js` during OpenAPI docs generation?
A: Root `dev` starts the OpenAPI docs watcher at the same time as package `dev` watchers. If a package `dev` script removes `dist` before `tsdown --watch` recreates it, the docs generator can import `apps/backend/src/stack.tsx` while `@stackframe/stack`'s ESM entrypoint is temporarily missing. Package watch scripts should update `dist` in place, and eager generators should wait for package imports to resolve before running.
## Q: How do SDK source tests replace the compile-time client version sentinel?
A: `packages/template/vitest.config.ts` installs a Vite transform plugin for Vitest that replaces `STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL` with `js <package-name>@<version>` from the local package.json. Keep the plugin in `packages/template` so `pnpm pre`/`scripts/generate-sdks.ts` propagates it to `packages/js`, `packages/react`, and `packages/stack`; otherwise tests importing `common.ts` throw `Client version was not replaced` before test collection.
## Q: How does the Mintlify apps sidebar filter stay in sync with theme changes?
A: `docs-mintlify/apps-sidebar-filter.js` injects the Apps filter with inline styles, so the MutationObserver must reapply `applySidebarAppsFilterTheme` when an existing input is found. Theme detection should handle both `html.dark` and `data-theme="dark"` signals.
## Q: How should `StackAssertionError` preserve an underlying thrown error?
A: Pass the underlying error as the `cause` property in the second argument. The `StackAssertionError` constructor only forwards `cause` into `ErrorOptions`, so storing a caught error under an `error` property captures it as ordinary metadata instead of preserving the error cause chain.
## Q: How does the local QEMU emulator expose host-side control channels?
A: `docker/local-emulator/qemu/run-emulator.sh` daemonizes QEMU with a QMP monitor socket at `$EMULATOR_RUN_DIR/vm/monitor.sock`, a QEMU guest agent socket at `$EMULATOR_RUN_DIR/vm/qga.sock`, and serial output redirected to `$EMULATOR_RUN_DIR/vm/serial.log`. The default user networking forwards only Stack-facing service ports, not SSH.
## Q: Where should remote development environment local state live?
A: Use `~/.stack/dev-envs.json` on macOS/Linux and `%LOCALAPPDATA%\Stack Auth\dev-envs.json` on Windows for local remote-development-environment state. The CLI and local dashboard both read this file; it stores the local dashboard bearer secret, anonymous refresh token, and config-path-to-project credential mappings with owner-only permissions.
## Q: How should `stack dev` run the local dashboard in a published CLI?
A: The CLI cannot depend on `apps/dashboard` source being present or run `next dev`. Package a Next.js standalone dashboard build into `packages/stack-cli/dist/dashboard`, copy it to a writable runtime directory next to the RDE state file, replace dashboard `STACK_ENV_VAR_SENTINEL_*` values for that launch, and run the standalone `apps/dashboard/server.js` with `node`, `HOSTNAME=127.0.0.1`, `PORT=26700`, and `NEXT_PUBLIC_STACK_IS_REMOTE_DEVELOPMENT_ENVIRONMENT=true`.
## Q: Where should the RDE local dashboard self-shutdown lifecycle start?
A: Start the RDE lifecycle from the dashboard server startup path (`apps/dashboard/src/instrumentation.ts`) when `NEXT_PUBLIC_STACK_IS_REMOTE_DEVELOPMENT_ENVIRONMENT=true`, not lazily after session registration. Keep the shutdown timer idempotent and use a short initial empty-session grace period so failed session registration still exits instead of leaving an orphaned standalone dashboard process. Once a CLI session has been explicitly closed, the dashboard can skip the startup grace and exit on the next shutdown tick if no sessions or operations remain.
## Q: How should the dashboard Stack app be split for the local remote development environment?
A: Keep `apps/dashboard/src/stack/client.tsx` as the root `StackProvider` app and handler app so the local RDE dashboard can boot without `STACK_SECRET_SERVER_KEY`. Put `StackServerApp` in `apps/dashboard/src/stack/server.tsx`, inherit from the client app, and only import it from server-only routes that are not needed in local RDE.
## Q: How should `stack dev` handle a local RDE dashboard outage while the child command is still running?
A: Keep it in the existing heartbeat path. If the heartbeat cannot reach the local dashboard and the dashboard session has passed the 5-second stability window, restart the standalone dashboard and re-register the RDE session; otherwise throw to avoid restart loops.
## Q: How should the local RDE dashboard authenticate in the browser without exposing refresh tokens?
A: The browser should fetch only a short-lived access token from the local RDE auth endpoint, install it into the memory token store with an empty refresh token, and refresh it by calling the local endpoint before expiry. Shared session logic must allow access-token-only sessions to read a still-valid access token; otherwise the SDK treats the session as absent and may redirect or create a separate anonymous user.
## Q: Why can `stack dev` fail to register an RDE session with `ECONNREFUSED` against `localhost`?
A: The RDE dashboard does server-side SDK calls from Node. If the backend is configured as `http://localhost:<port>`, Node may resolve or probe loopback differently than the browser; normalize exact `localhost` API base URLs to `127.0.0.1` in the CLI. If the backend process is actually down, the dashboard log will still show `ECONNREFUSED 127.0.0.1:<port>` and the dev server needs to be restarted.
## Q: How should Stack CLI `--config-file` options interpret paths?
A: `--config-file` should point directly to a regular config file. Do not treat an existing directory as a shortcut for `stack.config.ts` inside it; reject directories with a clear error instead. `stack config pull` may default to `./stack.config.ts` when the flag is omitted, but an explicitly provided directory is still invalid.
## Q: How should RDE PR-review fixes handle the local dashboard and CLI lifecycle?
A: Use the shared RDE browser security helper for browser-local endpoints, mark bearer-token responses `Cache-Control: private, no-store`, and return 400 for malformed local endpoint JSON. `stack dev` should fail loudly if a bundled dashboard sentinel has no environment value, validate session response shapes at runtime before using `env`, recover from HTTP heartbeat failures the same way as network heartbeat failures, and make heartbeat shutdown interruptible so child-process exit is not delayed by the full heartbeat interval.
## Q: How should local RDE endpoints trust browser origins and API base URLs?
A: Browser-only RDE endpoints should accept only the exact local dashboard origin derived from the dashboard env/state, such as `http://127.0.0.1:26700`, and reject arbitrary localhost subdomains like `evil.localhost`. CLI bearer endpoints should require the bearer secret and a loopback host but should not use broad localhost origins as trust signals. RDE session registration should accept only `https://api.stack-auth.com`, the exact API base URL passed into the local dashboard by the CLI, or exact custom URLs from a `STACK_`-prefixed allowlist.
## Q: How should the Stack CLI depend on the dashboard RDE standalone build in CI?
A: Do not invoke a nested `turbo run build:rde-standalone` from `packages/stack-cli`'s `build` script. When the outer CI is already running `turbo run build`, that nested Turbo process can start `apps/dashboard`'s Next build while the outer graph is also building it, causing `.next/lock` failures. Model the dependency in `turbo.json` instead with `@stackframe/stack-cli#build` depending on `@stackframe/dashboard#build:rde-standalone`, and let the CLI script only run `tsdown` plus runtime asset copying.
## Q: How should local RDE/browser health endpoints handle active state and origins?
A: Browser-only RDE endpoints should require RDE to be enabled, a local dashboard state entry with a non-empty secret, loopback host, exact dashboard origin, and same-origin/none fetch metadata. Development-environment health checks should not trust broad localhost origins; reject origins like `evil.localhost` and only allow the exact expected dashboard origins (or no Origin header for same-origin polling).
## Q: How should development-environment project creation seed environment config?
A: Seed the normal initial environment config before marking the project as `isDevelopmentEnvironment=true`. Existing development-environment projects should continue to reject environment config override writes, but creation needs to populate defaults like RBAC permissions, password sign-in, and installed apps first; otherwise the write guard throws during setup/restart-deps.
## Q: What can cause React error #185 immediately on dashboard load?
A: React error #185 is a maximum update depth error. In the dashboard root, `useSyncExternalStore` snapshot getters must return cached referentially stable values. Returning a fresh object such as `{ status: "healthy" }` from `getSnapshot` on every call can make React think the external store changed on every render and loop immediately. Use module-level constants for stable snapshots.
## Q: How should client-side OAuth callback and nested cross-domain auth avoid racing session consumers?
A: Track startup auth transitions as pending client-app promises and make `_getSession`/react-like `_useSession` wait for them when using the default persistent token store. Auth-transition code that needs to inspect the current session should explicitly call `_getSession(..., { awaitPendingAuthResolutions: false })` instead of relying on a global reentrancy flag.
## Q: When should hosted OAuth callback handling auto-start on a client app page?
A: Only auto-start hosted OAuth callback handling when the current URL has `code` and `state` and the matching `stack-oauth-outer-${state}` verifier cookie exists. Generic `code/state` or `errorCode/message` query parameters are not Stack-owned enough to run callback processing automatically on every hosted app page.
## Q: Should built-with hosted handler domains be manually configured as trusted domains?
A: No. Treat the hosted handler origin for the project, such as `https://<project-id>.built-with-stack-auth.com` or the origin derived from `NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE`, as an implicit trusted redirect domain on both client and backend validation paths. The hosted template must put `{projectId}` in the hostname so every project has its own origin; path-based templates like `https://host/{projectId}/{hostedPath}` are not safe for implicit origin trust.
## Q: How should post-auth redirects mint cross-domain auth codes right after sign-in?
A: When a sign-in/sign-up/OAuth callback immediately redirects through the cross-domain authorize endpoint, pass the freshly returned `{ accessToken, refreshToken }` as an override token store into the redirect path. Do not rely on browser cookies having already flushed; otherwise the request can pair a new access token with an old refresh token and fail the backend refresh-token/session consistency check.
## Q: How should mixed-token cross-domain auth regressions be tested?
A: Add a source-level template test that creates a client app with a stale persistent token store, calls `_createCrossDomainAuthRedirectUrl` with `overrideTokenStoreInit`, and patches only `sendClientRequest` on the real client interface so session creation remains intact. The assertion should inspect the session passed to the interface and require the fresh refresh token, proving the cross-domain authorize request does not use stale persisted tokens.
## Q: When should cross-domain auth call `captureError`?
A: Do not call `captureError` for normal cross-domain auth failures such as stale/deleted cookies, untrusted redirect URLs, invalid or mismatched refresh tokens, missing handoff params, or interrupted auth flows. Reserve `captureError` for states that definitely imply a Stack developer mistake; ordinary auth failures should just return or throw their normal user-facing errors.
## Q: How should the npm publish workflow create the post-publish dev version bump?
A: The workflow needs a full checkout using the fine-grained `NPM_PUBLISH_VERSION_UPDATE_PR_PAT` secret. It then fetches `origin/dev`, checks out `dev`, creates a non-interactive patch changeset, runs `pnpm changeset version`, copies the generated `packages/template/package.json` version line back into `packages/template/package-template.json`, and commit/pushes `chore: update package versions`. Because direct pushes to `dev` are blocked by repository rules requiring PRs and the `all-good` status check, the PAT's owning user or bot account must be added to the ruleset bypass list with "Always allow" rather than "For pull requests only".
## Q: How should the Mintlify docs homepage reuse the generated setup prompt?
A: Import `generatedSetupPromptText` from `docs-mintlify/snippets/home-prompt-island.jsx` in `docs-mintlify/index.mdx`, render it directly in a `<pre><code>{generatedSetupPromptText}</code></pre>` block, and keep the home copy button wired to that imported value. Clipboard failures can happen when the browser document is not focused, so the button should surface the actual error text instead of only saying "Copy failed".
## Q: Where should Mintlify docs for restricted users live?
A: Put restricted-user docs at `docs-mintlify/guides/apps/authentication/restricted-users.mdx` and register the page in the Authentication group in `docs-mintlify/docs.json`. The page should cover `includeRestricted: true`, `user.isRestricted`, `user.restrictedReason`, anonymous users being restricted by definition, and JWKS `include_restricted=true` for services that intentionally accept restricted-user tokens.
## Q: How should e2e tests switch to a newly created project?
A: `Project.createAndSwitch` should leave `backendContext.projectKeys` set to real project API keys, not only `{ projectId, adminAccessToken }`. Internal admin access tokens are regular short-lived access tokens; keeping one in the default project context makes later server/admin requests fail with `ADMIN_ACCESS_TOKEN_EXPIRED` or validate the token against the wrong project.
## Q: How should backend SMTP SSRF checks be rolled out?
A: Keep the real outbound SMTP policy in `apps/backend/src/private/implementation/smtp-egress-policy.ts`, export it through `apps/backend/src/private/index.ts`, and provide a simple `implementation-fallback` function for self-hosters. It should allow only SMTP ports 25, 465, 587, 2465, 2587, and 2525, reject internal IP literals or DNS resolutions, and initially run report-only from `emails-low-level.tsx` via `captureError("smtp-egress-policy-report-only", ...)` before enforcing hard failures.
## Q: What project-level `sourceOfTruth` config is supported?
A: Project config overrides only support the hosted `sourceOfTruth` shape. Legacy external source-of-truth overrides such as Postgres or Neon are removed by `migrateConfigOverride("project", ...)`, while raw schema validation should reject them.
## Q: How should managed email onboarding e2e tests wait for mock verification?
A: Do not rely on a fixed `wait(1500)` after setup. The mock onboarding path flips the domain to `verified` asynchronously through `runAsynchronously`, so tests should poll the managed-onboarding check endpoint until the expected status appears.
## Q: How should Microsoft OAuth callback token exchange include scopes?
A: Microsoft Entra ID's v2 token endpoint can reject authorization-code exchanges with `AADSTS70011` if the token request omits `scope`. Keep scope emission opt-in at the provider layer (`includeScopeInCallbackTokenExchange`) and pass the same merged base/provider scopes to `openid-client` via the callback `extras.exchangeBody.scope` parameter. The callback route must forward stored `providerScope` from the outer OAuth info so custom Microsoft provider scopes are included in the token exchange.
## Q: How should the development-environment dashboard load local config files?
A: Use `jiti` to import the user's config module, matching `stack config push`, so helper functions such as `defineStackConfig(...)` or `makeConfig()` work. Disable `moduleCache` for this reader because the development-environment file watcher may import the same config path repeatedly after edits, and cached modules would otherwise hide changes.
## Q: What is needed to put the legacy Fumadocs `docs/` app back into the Hexclave pnpm workspace?
A: Add `docs` to `pnpm-workspace.yaml`, keep the package named `@hexclave/docs`, use `workspace:*` for `@hexclave/next` and `@hexclave/shared`, and update actual app imports from `@stackframe/stack`/`@stackframe/stack-shared` to `@hexclave/next`/`@hexclave/shared`. Docs app runtime env reads should prefer `NEXT_PUBLIC_HEXCLAVE_*`/`HEXCLAVE_*` while retaining legacy `STACK_*` fallbacks.
## Q: How should template React contexts avoid duplicate client-bundle context identities?
A: Define exported provider contexts such as `StackContext` and `TranslationContext` through `createGlobal` from `@stackframe/stack-shared/dist/utils/globals`, not direct `React.createContext(...)` exports. That helper stores the React context under `globalThis[Symbol.for("__hexclave-globals")]`, so duplicated SDK bundles still share the same provider/consumer context object.
## Q: Why should RDE project creation avoid passing `config` to `createProject`?
A: `createProject({ config })` writes those values as environment config overrides, which have higher precedence than the branch/local config that the remote development environment later syncs from the user's config file. Seeding values such as `signUpEnabled: true` there masks dashboard edits like `"auth.allowSignUp": false`; RDE projects should be created without config and then configured through onboarding/config-file sync.
## Q: How can multiple remote development-environment dashboards run without port conflicts?
A: Let `NEXT_PUBLIC_HEXCLAVE_LOCAL_DASHBOARD_PORT` select the local dashboard port, defaulting to `26700`. The CLI should key dashboard secrets/state, runtime directories, and log files by port so one dashboard does not overwrite another process's browser-origin checks or bundled runtime.
## Q: How should the RDE CLI decide that the local dashboard is ready?
A: Probe `/api/development-environment/health`, not a generic page route, and treat a structurally valid health response as dashboard readiness even when the HTTP status is `503`. Before any CLI session is registered, the RDE health route intentionally reports unhealthy; waiting for a 2xx response prevents session registration and lets the dashboard shut itself down after the empty-session grace period.
## Q: Why should the dashboard avoid a global `/api/[...any]` App Router catch-all?
A: The dashboard has real local API endpoints such as `/api/remote-development-environment/*`. In standalone Next builds, a broad `/api/[...any]` route can shadow more specific local API routes, sending RDE session requests down the wrong path. Prefer no catch-all so unknown dashboard API URLs 404 naturally instead of interfering with real endpoints.
## Q: How should RDE session registration report an unreachable backend?
A: Do not let SDK network errors escape the local dashboard route as an empty Next.js 500. Classify expected API connection failures such as `ECONNREFUSED`/`fetch failed`, return a sanitized `503` JSON error naming the configured API base URL, and have the CLI parse JSON error bodies so users see the actual cause instead of `Failed to register ... (500):`.
## Q: Which dashboard files control the Hexclave logo in light and dark mode?
A: `apps/dashboard/src/components/logo.tsx` chooses `apps/dashboard/public/logo.svg` and `logo-full.svg` for light mode, and `logo-bright.svg` and `logo-full-bright.svg` for dark mode. The light-mode assets can be changed independently without affecting the dark-mode neon logo.
## Q: Why did the user/team detail page "Install apps" action sit at the far right of the tab bar?
A: `DesignCategoryTabs` in `packages/dashboard-ui-components/src/components/tabs.tsx` rendered tab buttons in a `flex-1` scroll container, so the trailing slot was pushed to the far right of the full row. Removing `flex-1` keeps trailing actions directly after the visible tabs while preserving horizontal scrolling through `min-w-0 overflow-x-auto`.

View File

@ -74,7 +74,6 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub
- 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 `Stop` tool, 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_HEXCLAVE_PORT_PREFIX>01/projects/-selector-/...`, although sometimes it's different. If $NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX is set to 91, 92, or 93, use `a.localhost`, `b.localhost`, and `c.localhost` for the domains, respectively.
- To update the list of apps available, edit `apps-frontend.tsx` and `apps-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.