## Summary
Adds a one-time informational modal that announces the **Stack Auth →
Hexclave** rebrand to existing logged-in dashboard users. Brand-new
users signing up after the cutoff already land on a fully
Hexclave-branded experience and don't see the modal — they have no
"Stack Auth" mental model to update.
Stacked on top of **PR #1481** (the visible-rebrand flip).

## What's in here
- **Component**:
[`apps/dashboard/src/components/hexclave-rebrand-modal.tsx`](https://github.com/hexclave/stack-auth/blob/cl/hexclave-rebrand-modal/apps/dashboard/src/components/hexclave-rebrand-modal.tsx)
— the modal itself plus the static `RebrandIllustration` (Stack Auth
mark → arrow → Hexclave benzene mark).
- **Mount**:
[`apps/dashboard/src/app/(main)/(protected)/layout-client.tsx`](https://github.com/hexclave/stack-auth/blob/cl/hexclave-rebrand-modal/apps/dashboard/src/app/(main)/(protected)/layout-client.tsx)
— single `<HexclaveRebrandModal />` inside `ConfigUpdateDialogProvider`
so it covers every authenticated dashboard route.
- **Asset**: `apps/dashboard/public/hexclave-icon.svg` — the
benzene-ring mark pulled from `https://hexclave.com/icon.svg` (the
canonical Hexclave brand mark). Stack Auth side uses the existing
`/logo.svg` + `/logo-bright.svg` (dark mode variant).
## Targeting
Three independent conditions must all hold for the modal to render:
1. **Logged-in user** — `useUser({ or: "return-null" })` opts out of the
sign-in redirect so guests are silently skipped.
2. **`user.signedUpAt < REBRAND_CUTOFF`** where `REBRAND_CUTOFF =
2026-05-27T00:00:00Z`. This is the cleanest "pre-rebrand user" signal —
more accurate than cookie inspection (the existing `stack-is-https` hint
cookie gets dual-written for new and returning users alike since
[`packages/template/src/lib/cookie.ts:201`](https://github.com/hexclave/stack-auth/blob/cl/hexclave-rebrand-modal/packages/template/src/lib/cookie.ts#L201),
so it can't distinguish), and it follows the same user across
browsers/devices.
3. **No `hexclave-rebrand-modal-dismissed=true` in localStorage**. Any
dismissal path (Got it button, X, overlay click, Escape) routes through
`onOpenChange` and writes this flag, so the modal never re-opens for
that browser.
## Behavior verification
End-to-end checked against a live dev dashboard:
- ✅ Modal opens once on first authenticated dashboard view for a
pre-cutoff user.
- ✅ `localStorage["hexclave-rebrand-modal-dismissed"]` flips to `"true"`
on any dismissal path.
- ✅ Reload after dismissal: zero `[role=dialog]` elements rendered,
localStorage flag persists.
- ✅ Not rendered for guests (no useUser → early `return null`, no
auth-redirect).
- ✅ Not rendered for users with `signedUpAt >= REBRAND_CUTOFF`.
- ✅ SSR-safe: hooks into `useEffect` to read storage post-hydration;
`try/catch` around storage access so private-mode / sandboxed iframes
degrade silently.
- ✅ `pnpm --filter @stackframe/dashboard lint` clean.
## Notes
- **`pnpm typecheck`** was not run as part of this change because the
dashboard typecheck depends on `codegen-prisma` against a live DB
(pre-existing infra debt noted in PR #1481). Lint covers the type-shape
of the touched files via the project's strict TS-aware ESLint rules.
- **Migration link** points to
[`docs.hexclave.com/migration`](https://docs.hexclave.com/migration),
the same URL referenced from
[`packages/template/src/internal/deprecation-warning.ts:36`](https://github.com/hexclave/stack-auth/blob/cl/hexclave-rebrand-modal/packages/template/src/internal/deprecation-warning.ts#L36)
— single source of truth for the migration story.
- **No animation** by design — the modal is static; entry/exit uses the
existing Radix Dialog fade.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a one-time modal in the dashboard to announce the Stack Auth →
Hexclave rebrand for pre-rebrand users. It shows once per account per
browser, inlines the import-rename step, links the migration guide, and
doesn’t show for new signups or in dev/preview environments.
- **New Features**
- Shows only for logged-in users with `signedUpAt <
'2026-05-27T00:00:00Z'`; guests, post-cutoff signups, and local
emulator/remote dev/preview never see it.
- Dismissal persists via
`localStorage["hexclave-rebrand-modal-dismissed:<user.id>"]="true"`; all
close paths set the flag.
- Mounted in the protected dashboard layout so it covers all
authenticated routes once per account per browser.
- Includes illustration (`/logo.svg` → arrow → `/hexclave-icon.svg`) and
links to app.hexclave.com and the migration guide; copy tells users to
rename `@stackframe/*` to `@hexclave/*` (`@stackframe/stack` →
`@hexclave/next`).
<sup>Written for commit 92c07f2bd8.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1493?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
Three favicon.ico files and the dashboard's open-graph-image.png are
regenerated from the canonical Hexclave benzene-mark SVG committed in
the previous logo-swap commit (apps/dashboard/public/logo.svg).
Favicons (all three: dashboard, dev-launchpad, docs-mintlify) bundle
16x16, 32x32, and 48x48 PNG-encoded variants — 32bpp RGBA for crisp
rendering on retina + dark-mode tab strips. dev-launchpad's old icon
was a 4bpp 16x16 single-frame; it now matches the other two.
OG image is 1200x630 with a centered 360px gradient benzene mark on a
dark canvas with a subtle radial glow. No wordmark — Jersey 10 isn't
available at rasterize time and the mark alone reads cleanly on the
standard OG card preview area in Slack / Twitter / LinkedIn.
The dashboard layout's openGraph.images metadata points at
${apiUrl}/open-graph-image.png — the backend deploy needs to serve
this file from the same path so social previews actually resolve.
Tracked separately as ops work.
PR 1481 carved out binary visual assets pending design work, but we now
have the Hexclave brand mark from PR 1493 (the neon-gradient benzene
ring) and a full wordmark variant. Swap them in everywhere the dashboard,
docs site, and example demo rendered the Stack Auth logo.
Icon-only variant (PR 1493's hexclave-icon.svg, neon-gradient benzene
mark) replaces:
- apps/dashboard/public/logo.svg + logo-bright.svg
- examples/demo/public/logo.svg + logo-bright.svg
Full wordmark variant (benzene mark + 'Hexclave' text in Jersey 10)
replaces:
- apps/dashboard/public/logo-full.svg (black text — light mode)
- apps/dashboard/public/logo-full-bright.svg (white text — dark mode)
- docs-mintlify/images/logo-light.svg (black text)
- docs-mintlify/images/logo-dark.svg (white text)
The benzene mark uses a colored gradient that reads on both light and
dark backgrounds, so logo.svg and logo-bright.svg now share identical
content — the dark-mode SmartImage swap in logo.tsx is effectively a
no-op but the file pair is preserved to avoid touching the component
API. Same applies to examples/demo.
Also drop the 'dark:invert' className on examples/demo's <Image> — the
new gradient mark should not be color-inverted on dark backgrounds (it
already reads correctly on both).
The MDX content was flipped to Hexclave in PR 1481, but the URL slug
itself (/guides/other/tutorials/build-a-saas-with-stack-auth) was still
public-facing. Rename the file and update the five internal links:
- docs.json navigation reference
- 3 in-MDX cross-links in build-a-team-based-app.mdx
- 2 in-MDX cross-links in ship-production-ready-auth.mdx
PR 1481's sweep missed four user-visible spots in the examples/ tree:
- examples/demo: page title 'Stack Demo' + description 'using Stack...'
- examples/docs-examples: page title 'Stack Demo' + description 'using Stack...'
- examples/tanstack-start-demo: 'Stack TanStack Demo' link text in header
- examples/middleware: description 'A demo of Stack\'s middleware capabilities.'
(title was already flipped; only the description was missed)
Result.or returns T | U synchronously, so awaiting it tripped
@typescript-eslint/await-thenable and @typescript-eslint/return-await
in the generated packages/stack and packages/react SDKs.
The legacy docs/ folder still referenced noreply@stackframe.co for the
shared email provider — flip to match the new sender domain set up
on Resend as the dedicated transactional-sender domain. Aligns with
the dashboard + docs-mintlify references that were already flipped.
The bare-name sweep was mutating the sentinel string
('js @stackframe/js@2.8.105') into 'js @hexclave/js@2.8.105' before
the sentinel-specific regex (built from oldName) had a chance to
match, silently leaving the version stuck at the old @stackframe
version on published @hexclave/* artifacts. Reorder so the sentinel
rewrite runs first; the name sweep that follows can't touch the
rewritten sentinel because it no longer contains any @stackframe/*
substrings.
Both failures are pre-existing on `dev` (confirmed by checking the most
recent dev run
[26434368271](https://github.com/hexclave/stack-auth/actions/runs/26434368271)
— same two annotations, same line numbers). Neither is caused by an open
PR.
## Failure 1 — \`apps/backend/src/lib/redirect-urls.test.tsx:75\`
\`\`\`
AssertionError: expected false to be true
\`\`\`
The \`withHostedHandlerEnv\` helper set/cleared only the
\`STACK_*\`-prefixed env vars. CI's
[e2e-custom-base-port-api-tests.yaml:21](.github/workflows/e2e-custom-base-port-api-tests.yaml#L21)
sets only the \`HEXCLAVE_*\`-prefixed sibling
(\`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67\`), and the dual-read shim in
[packages/stack-shared/src/utils/env.tsx#L53-L55](packages/stack-shared/src/utils/env.tsx#L53-L55)
prefers \`HEXCLAVE_*\` over \`STACK_*\`:
\`\`\`ts
const hexclaveName = getHexclaveEnvVarName(name);
let value = (hexclaveName ? process.env[hexclaveName] : undefined) ??
process.env[name];
\`\`\`
So \`getEnvVariable(\"NEXT_PUBLIC_STACK_PORT_PREFIX\", \"81\")\`
returned \`\"67\"\` instead of the test's \`\"92\"\`, the template
resolved to port \`6709\` instead of \`9209\`, and the assertion at line
75 failed.
**Fix:** mirror every \`STACK_*\` key managed by the helper to its
\`HEXCLAVE_*\` sibling. The dual-read then resolves to the
test-controlled value regardless of which key it checks first.
## Failure 2 —
\`apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts:58\`
\`\`\`
PostgresError: null value in column \"updatedAt\" of relation
\"OAuthAccessToken\" violates not-null constraint
\`\`\`
The migration test's raw INSERT omits \`\"updatedAt\"\`. The Prisma
model declares \`updatedAt DateTime @updatedAt\` with no
\`@default(now())\`, so the DB column is \`NOT NULL\` with no default —
Prisma populates it at the ORM layer on insert, but this test bypasses
Prisma via \`postgres.js\`.
**Fix:** add the \`\"updatedAt\"\` column to the INSERT, set to
\`NOW()\`, with a comment noting why raw SQL must set it explicitly.
## Verification
- **Failure 1, before fix:** ran \`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67
pnpm test run apps/backend/src/lib/redirect-urls.test.tsx\` locally →
reproduces the exact line-75 assertion failure from CI.
- **Failure 1, after fix:** same command → 33/33 pass.
- **Failure 2:** local reproduction requires the migration-test postgres
harness; the fix is one column matching how every other raw SQL insert
in this repo handles \`@updatedAt\` fields. CI on this branch will
confirm.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes two failing tests on dev CI by aligning env var handling in
redirect URL tests and by setting the missing updatedAt in a migration
test. Restores green CI with no runtime changes.
- **Bug Fixes**
- Redirect URL tests: `withHostedHandlerEnv` now mirrors `STACK_*`
values to their `HEXCLAVE_*` siblings and restores both, so
`getEnvVariable` reads the test-controlled values even when CI sets only
`HEXCLAVE_*` (e.g. `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX`).
- Migration test: the raw insert into `OAuthAccessToken` now sets
`"updatedAt" = NOW()` since `Prisma`’s `@updatedAt` isn’t applied when
using `postgres.js` and the column is NOT NULL.
<sup>Written for commit 75c8e4343e.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1488?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
The next.layout-provider check's import regex only matched the legacy
@stackframe/stack scope. After the PR-3 source rename, fixtures (and
real projects) import StackProvider from @hexclave/next, so the regex
returned false, the layout check failed, and the doctor exited 1.
Widen the regex to accept both scopes, mirroring the dual-scope detection
in packages/stack-shared/src/config-rendering.ts and apps/dashboard/src/lib/github-config-push.ts.
Fixes 9 Stack CLI - Doctor failures in apps/e2e/tests/general/cli.test.ts.
9 source comments pointed at the planning doc removed in the prior commit.
Drop the 'See RENAME-TO-HEXCLAVE.md' sentence/parenthetical from each;
keep the surrounding substantive context intact (CORS allowlist derivation,
JWT issuer dual-acceptance rationale, OIDC opaque-identifier carve-out
explanation, Hexclave/Stack alias rationale).
The rebrand-execution plan is now obsolete: PRs 1, 2, and 3 have shipped
the work it described. The doc was a one-shot working document and never
intended as long-lived reference material.
Note: 9 source-code comments still reference this file (idp.ts:172,
tokens.tsx:66, proxy.tsx in backend + dashboard, template/src/index.ts,
template/src/lib/stack-app/index.ts). They are intentionally left in
place as historical breadcrumbs; they don't affect runtime behavior and
can be scrubbed in a follow-up cleanup if desired.