diff --git a/.claude/CLAUDE-KNOWLEDGE.md b/.claude/CLAUDE-KNOWLEDGE.md index f01d96f85..a7e8287cd 100644 --- a/.claude/CLAUDE-KNOWLEDGE.md +++ b/.claude/CLAUDE-KNOWLEDGE.md @@ -12,7 +12,7 @@ A: `packages/stack-cli/src/lib/init-prompt.ts` re-exports `createInitPrompt` fro 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_STACK_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. +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. @@ -393,7 +393,7 @@ A: Use `getAuthorizationHeader()`, which returns `Bearer stackauth_ }`. 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_STACK_PORT_PREFIX`, because that points to the host dev environment ports (e.g. `92xx`) rather than the emulator host-forwarded ports. +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. diff --git a/AGENTS.md b/AGENTS.md index 750017f5b..d1aa98855 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,11 +34,11 @@ You should ALWAYS add new E2E tests when you change the API or SDK interface. Ge 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) +- **backend** (`/apps/backend`): Next.js API backend running on port `${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}02` (defaults to 8102) - Main API routes in `/apps/backend/src/app/api/latest` - Database models using Prisma -- **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) +- **dashboard** (`/apps/dashboard`): Admin dashboard on port `${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}01` (defaults to 8101) +- **dev-launchpad**: Development portal on port `${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}00` (defaults to 8100) - **e2e**: End-to-end tests ### Packages (`/packages`) @@ -76,7 +76,7 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub - 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:01/projects/-selector-/...`, although sometimes it's different. If $NEXT_PUBLIC_STACK_PORT_PREFIX is set to 91, 92, or 93, use `a.localhost`, `b.localhost`, and `c.localhost` for the domains, respectively. +- 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: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. - 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 `usePathname` instead of `await 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`! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65eedc4f9..68cd1e67b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,7 +36,7 @@ For any security-related concerns & bug bounties, please email us at [security@h NOTE: Every line of code should be reviewed by a human BEFORE you submit a PR. DO NOT waste our time by creating and submitting an AI-generated PR. -For vibecoding, it can help to have multiple parallel copies of the codebase open in different windows. For this, you can set the environment variable `NEXT_PUBLIC_STACK_PORT_PREFIX` to a different value (default 81). To do this consistently across all coding agents (Claude Code/Cursor Agent/Codex),we recommend you use `direnv` with a `.envrc` file: +For vibecoding, it can help to have multiple parallel copies of the codebase open in different windows. For this, you can set the environment variable `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX` to a different value (default 81). To do this consistently across all coding agents (Claude Code/Cursor Agent/Codex),we recommend you use `direnv` with a `.envrc` file: 1. Install `direnv` if you haven't already. On Mac, the easiest way is to install it with Homebrew: `brew install direnv`. 2. Update ALL your shell configs to append the following lines. On most Mac setups, this is `~/.bash_profile`, `~/.bashrc`, `~/.zprofile`, `~/.zshrc`, and `~/.zshenv`. @@ -51,13 +51,13 @@ For vibecoding, it can help to have multiple parallel copies of the codebase ope ```sh # .envrc # make sure to install direnv and add it to your shell rc file (e.g. ~/.bashrc or ~/.zshrc) - export NEXT_PUBLIC_STACK_PORT_PREFIX=181 + export NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=181 # with this many processes running, it can be useful to add a custom title to all Node.js processes # export NODE_OPTIONS="--require=/scripts/set-process-title.js $NODE_OPTIONS" ``` -When you do this, it is recommended that you give all workspaces a port prefix other than 81, to prevent accidental conflicts when you forgot to make a feature support the $NEXT_PUBLIC_STACK_PORT_PREFIX environment variable. (for example: first workspace at 181, second workspace at 182, etc.) +When you do this, it is recommended that you give all workspaces a port prefix other than 81, to prevent accidental conflicts when you forgot to make a feature support the $NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX environment variable. (for example: first workspace at 181, second workspace at 182, etc.) Also, the cookies on different ports may conflict with each other. To prevent this, open `a.localhost:18101` and `b.localhost:18201` instead or normal localhost, so the cookies are scoped differently. diff --git a/apps/backend/src/lib/redirect-urls.test.tsx b/apps/backend/src/lib/redirect-urls.test.tsx index 6d8ff6083..2e76d3ddf 100644 --- a/apps/backend/src/lib/redirect-urls.test.tsx +++ b/apps/backend/src/lib/redirect-urls.test.tsx @@ -20,7 +20,7 @@ describe('validateRedirectUrl', () => { const stackKeys = [ "NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE", "NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", - "NEXT_PUBLIC_STACK_PORT_PREFIX", + "NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", ] as const; const hexclaveOf = (name: string) => name.replace("STACK_", "HEXCLAVE_"); const allKeys = [...stackKeys, ...stackKeys.map(hexclaveOf)]; @@ -28,7 +28,7 @@ describe('validateRedirectUrl', () => { const newValues: Record = { NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE: values.hostedHandlerUrlTemplate, NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX: values.hostedHandlerDomainSuffix, - NEXT_PUBLIC_STACK_PORT_PREFIX: values.stackPortPrefix, + NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX: values.stackPortPrefix, }; for (const stackKey of stackKeys) { newValues[hexclaveOf(stackKey)] = newValues[stackKey]; @@ -69,7 +69,7 @@ describe('validateRedirectUrl', () => { describe('exact domain matching', () => { it('should implicitly validate hosted handler domains for the project', () => { withHostedHandlerEnv({ - hostedHandlerUrlTemplate: "http://{projectId}.localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09/{hostedPath}", + hostedHandlerUrlTemplate: "http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}", stackPortPrefix: "92", }, () => { const tenancy = createMockTenancy({ diff --git a/apps/backend/src/lib/redirect-urls.tsx b/apps/backend/src/lib/redirect-urls.tsx index 56a22c28e..dc5ee8ce9 100644 --- a/apps/backend/src/lib/redirect-urls.tsx +++ b/apps/backend/src/lib/redirect-urls.tsx @@ -9,7 +9,7 @@ export function getHostedHandlerTrustedDomain(projectId: string): string { projectId, hostedHandlerDomainSuffix: getProcessEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX"), hostedHandlerUrlTemplate: getProcessEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE"), - stackPortPrefix: getEnvVariable("NEXT_PUBLIC_STACK_PORT_PREFIX", "81"), + stackPortPrefix: getEnvVariable("NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", "81"), }); } diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index ddd9b3785..8d7653f46 100644 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -75,9 +75,9 @@ export NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL=${NEXT_PUBLIC_STACK_DASHBOARD_URL # is STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX, and the sentinel # substitution loop below derives the env var name from the sentinel — so this # MUST export NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX or the sentinel never resolves. -# Accept the legacy NEXT_PUBLIC_STACK_PORT_PREFIX as input for back-compat with +# Accept the legacy NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX as input for back-compat with # existing self-host configs. -export NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}} +export NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}} PORT_PREFIX=${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX} export NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL="http://localhost:${PORT_PREFIX}01" export NEXT_PUBLIC_BROWSER_STACK_API_URL=${NEXT_PUBLIC_STACK_API_URL} diff --git a/docs-mintlify/README.md b/docs-mintlify/README.md index 86701ae16..7ff1a830a 100644 --- a/docs-mintlify/README.md +++ b/docs-mintlify/README.md @@ -26,7 +26,7 @@ From the repository root: pnpm -C docs-mintlify run dev ``` -This starts Mintlify in `docs-mintlify` on `http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}04` (for example, `http://localhost:8104` with the default prefix). +This starts Mintlify in `docs-mintlify` on `http://localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}04` (for example, `http://localhost:8104` with the default prefix). From inside `docs-mintlify`, you can also run: diff --git a/docs/README.md b/docs/README.md index 2e2b6ee14..f4aeb02bc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ This is the documentation site for Stack Auth, built with [Next.js](https://next pnpm dev ``` -The docs server runs on port `8104` by default (or `${NEXT_PUBLIC_STACK_PORT_PREFIX}04`). +The docs server runs on port `8104` by default (or `${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX}04`). ## Project Structure diff --git a/packages/stack-shared/src/utils/redirect-urls.tsx b/packages/stack-shared/src/utils/redirect-urls.tsx index 29baf1557..0f32d631b 100644 --- a/packages/stack-shared/src/utils/redirect-urls.tsx +++ b/packages/stack-shared/src/utils/redirect-urls.tsx @@ -15,7 +15,7 @@ const hostedHandlerTemplateProjectIdB = "11111111-1111-4111-8111-111111111111"; function replaceStackPortPrefix(input: string | undefined, stackPortPrefix: string | undefined): string | undefined { if (input == null) return undefined; - return stackPortPrefix ? input.replace(/\$\{NEXT_PUBLIC_STACK_PORT_PREFIX:-81\}/g, stackPortPrefix) : input; + return stackPortPrefix ? input.replace(/\$\{NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81\}/g, stackPortPrefix) : input; } function getHostedHandlerUrlFromTemplate(template: string, projectId: string, hostedPath: string): string { @@ -232,7 +232,7 @@ import.meta.vitest?.test("validateRedirectUrl trusts implicit hosted handler dom allowLocalhost: false, trustedDomains: getImplicitlyTrustedDomainsForProject({ projectId, - hostedHandlerUrlTemplate: "http://{projectId}.localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09/{hostedPath}", + hostedHandlerUrlTemplate: "http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}", stackPortPrefix: "92", }), })).toBe(true); diff --git a/packages/template/src/lib/stack-app/url-targets.test.ts b/packages/template/src/lib/stack-app/url-targets.test.ts index fb98c22b8..8a116cfea 100644 --- a/packages/template/src/lib/stack-app/url-targets.test.ts +++ b/packages/template/src/lib/stack-app/url-targets.test.ts @@ -173,7 +173,7 @@ describe("handler URL targets", () => { }); it("uses the full hosted handler URL template when configured", () => { - vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE", "http://{projectId}.localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09/{hostedPath}"); + vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE", "http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}"); vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", "93"); const urls = resolveHandlerUrls({ diff --git a/sdks/implementations/swift/README.md b/sdks/implementations/swift/README.md index f46a483b1..ae7cf2e3e 100644 --- a/sdks/implementations/swift/README.md +++ b/sdks/implementations/swift/README.md @@ -188,7 +188,7 @@ Tests use Swift Testing framework against a running backend. swift test ``` -The tests connect to `http://localhost:8102` (or `${NEXT_PUBLIC_STACK_PORT_PREFIX}02`). +The tests connect to `http://localhost:8102` (or `${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX}02`). ## API Reference diff --git a/sdks/implementations/swift/Tests/StackAuthTests/TestConfig.swift b/sdks/implementations/swift/Tests/StackAuthTests/TestConfig.swift index 6730ea8e6..e01f1a2d5 100644 --- a/sdks/implementations/swift/Tests/StackAuthTests/TestConfig.swift +++ b/sdks/implementations/swift/Tests/StackAuthTests/TestConfig.swift @@ -6,10 +6,10 @@ import FoundationNetworking /// Shared test configuration /// Set environment variables to customize test behavior: -/// - NEXT_PUBLIC_STACK_PORT_PREFIX: Port prefix for backend (default: "81") +/// - NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX: Port prefix for backend (default: "81") /// - STACK_SKIP_E2E_TESTS: Set to "true" to skip E2E tests struct TestConfig { - static let portPrefix = ProcessInfo.processInfo.environment["NEXT_PUBLIC_STACK_PORT_PREFIX"] ?? "81" + static let portPrefix = ProcessInfo.processInfo.environment["NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX"] ?? "81" static let baseUrl = "http://localhost:\(portPrefix)02" static let skipE2E = ProcessInfo.processInfo.environment["STACK_SKIP_E2E_TESTS"] == "true"