### Context
Stripe recommends acking webhook events ASAP with a 200. Stripe also
recommends employing event idempotency on your end. By responding
quickly, you prevent stripe from thinking the webhook failed and
retrying the event. Retrying the event in the past used to be
responsible for people getting multiple payment receipt emails. Note
that even in the case where an event processing genuinely fails, we have
a new table to let us recover from it.
Currently, recovery will be manual, but since it will be logged to
sentry we will be notified.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Quick-ack Stripe webhooks with 200 and add atomic idempotency to stop
duplicate processing and emails. Events are persisted and processed in
the background with clear status and error tracking.
- **New Features**
- Persist each webhook in `StripeWebhookEvent` keyed by `event.id` with
full `payload` and `stripeAccountId` for recovery.
- Return 200 immediately; process in the background and track status as
`PENDING`, `PROCESSED`, or `FAILED`.
- Single-flight claim deduplicates redeliveries while `PENDING` and
after `PROCESSED`; only `FAILED` events reprocess on redelivery.
- Store `lastError` on failures; unknown webhook types ack with 200 and
are handled asynchronously.
- Webhook response includes `deduplicated: true` when a redelivery is
skipped.
- **Migration**
- Run Prisma migrations to create the `StripeWebhookEvent` table, enum,
and unique index on `stripeEventId`.
<sup>Written for commit 59456a36e8.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1664?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added persistent, idempotent Stripe webhook handling with event-level
deduplication keyed by the webhook event id.
* Webhooks are acknowledged immediately and processed asynchronously,
with automatic retry capability for failed events.
* **Bug Fixes**
* Reduced duplicate side effects from redeliveries (including preventing
repeated receipt emails) by ensuring only one successful processing per
event.
* **Tests**
* Updated and expanded integration and end-to-end coverage for
asynchronous processing, deduplication, and failure recovery behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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
Speeds up project onboarding by consolidating API calls and adding
prefetching:
- Single PATCH endpoint for saving onboarding status + state together
- Background config save on welcome step (with proper retry/error
handling)
- Prefetch email themes and Stripe info for upcoming steps
- Suspense-based skeleton loaders instead of full-page spinners
- Extracted shared types and lightweight step components
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Unified onboarding progress saving for the new project flow,
persisting status plus optional onboarding state in a single update
path.
* **Improvements**
* Onboarding wizard now derives status/state from owned project data and
removes extra internal fetching/loading gates.
* Added skeleton/Suspense loading for email theme and payments steps,
with more reliable “final config”/completion sequencing.
* Admin project data now includes onboarding state; Stripe account info
uses cached retrieval.
* Backend avoids source-of-truth override changes during metadata-only
updates.
* **Tests**
* Updated onboarding wizard tests and added end-to-end coverage for
updating onboarding status/state together.
* **Documentation**
* Added the “clickmaps” app icon to docs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: armaan <armaan@stack-auth.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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
## Summary
Completes the env-var side of the Hexclave rebrand: every
`STACK_*`-prefixed variable (including `NEXT_PUBLIC_STACK_*` and
`VITE_STACK_*`) is renamed to `HEXCLAVE_*` across all checked-in `.env`,
`.env.development`, and `.env.example` files (30 files, ~135 keys).
Legacy `STACK_*` names keep working everywhere via dual-read, so
**existing deployments, `.env.local` files, and self-hosted setups need
no immediate migration**.
## How legacy names keep working
- **Server code** already resolves `HEXCLAVE_*` first with `STACK_*`
fallback via `getEnvVariable`. Direct `process.env.STACK_X` readers fed
by the renamed files (prisma seed, e2e tests/helpers, internal-tool
scripts, examples, `prisma.config.ts`) now read `HEXCLAVE_X || STACK_X`.
- **Client code** (Next.js build-time inlining) uses literal dual-read
expressions; the dashboard's `_inlineEnvVars` already had them.
- **Docker/self-hosting**: `docker/server/entrypoint.sh` (shared by the
server and local-emulator images) gets a generic two-way
`HEXCLAVE_`↔`STACK_` env mirror — runs at startup and again before
sentinel replacement — replacing the previous URL-trio-only mirror.
Operators can use either prefix.
## The empty-placeholder trap (`||` vs `??`)
The checked-in templates define empty placeholders (`HEXCLAVE_X=#
comment` parses to `""` via dotenv). With `?? `-based fallbacks, that
empty string would silently shadow a real value under the legacy name —
including legacy vars set in Vercel/CI env at build time, since the
tracked `.env` is present during builds. All fallback chains therefore
treat empty-as-unset (`||`):
- `getEnvVariable` and `getProcessEnv` in `packages/shared`
- the dashboard/docs/example literal dual-reads
- the generated SDK env getters (via
`packages/template/scripts/generate-env.ts`; the generated
`src/generated/env.ts` files are gitignored and regenerate at build)
## Other notable changes
- Tests that override env now set the canonical `HEXCLAVE_*` name (it
wins over `STACK_*`): e2e `cross-domain-auth`, backend
`internal-feedback-emails` in-source test.
- e2e `helpers.ts` port-prefix expansion loop also matches the
`HEXCLAVE_` prefixes.
- `docker/local-emulator/generate-env-development.mjs` reads source keys
canonically (legacy fallback) and emits canonical keys; regenerated
output matches.
- `rotate-secrets.sh` falls back to
`HEXCLAVE_DATABASE_CONNECTION_STRING`.
- Docs code snippets (`docs/code-examples`) renamed outright to
canonical names, consistent with #1571.
- OAuth callback `console.warn` in `packages/template/src/lib/auth.ts`
now says Hexclave.
## Migration note for the team
Local `.env.local` files with legacy `STACK_*` overrides keep working
**unless** the override targets a var that `.env.development` now sets
to a real (non-empty) `HEXCLAVE_*` value — the canonical name wins over
file precedence. Rename those keys in your `.env.local` once.
## Verification
- `typecheck` + `lint` pass on every touched package (shared, backend,
dashboard, e2e, internal-tool, cli, docs, template). Pre-existing
failures on dev (`admin-app-impl.ts` typecheck, dashboard metrics-page
errors) are unchanged (identical error counts with/without this change).
- `getEnvVariable`/`getProcessEnv` fallback semantics smoke-tested
directly (empty-HEXCLAVE → legacy fallback, HEXCLAVE wins when set,
defaults intact).
- `internal-feedback-emails` in-source vitest passes; emulator env
generator `--check` passes; `bash -n` on touched shell scripts.
- Two independent review agents audited the diff for correctness bugs
and coverage gaps; all confirmed findings are fixed in the third commit.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Renamed all `STACK_*` env vars (including
`NEXT_PUBLIC_STACK_*`/`VITE_STACK_*`) to `HEXCLAVE_*` across env
templates and code, with dual‑read that treats empty as unset, detects
conflicts, ignores post‑build sentinels, and falls back to legacy names.
All GitHub Actions now use `HEXCLAVE_*`; local‑emulator e2e is fixed by
setting `NEXT_PUBLIC_HEXCLAVE_IS_LOCAL_EMULATOR` in CI.
- **Refactors**
- Added conflict‑aware dual‑read helpers (prefer `HEXCLAVE_*`,
empty‑as‑unset, ignore post‑build sentinels, preserve empty passthrough)
and used them across `packages/shared` (resolver + tests),
`apps/dashboard` inline/public envs (with tests), `apps/backend` Prisma
config/seed and vitest (accept both prefixes), `packages/cli`
(API/Dashboard URLs, project ID, `HEXCLAVE_EMULATOR_HOME`; tests),
Docker (`entrypoint.sh` mirroring + `rotate-secrets.sh` DB URL),
docs/components (`docs/src/lib/env.ts`), and examples; hosted/Vite apps
now error if both spellings differ.
- Port‑prefix expansion includes `HEXCLAVE_*`; backend tests use a new
helper to resolve DB connection strings; Prisma prefers
`HEXCLAVE_DATABASE_CONNECTION_STRING` with legacy fallback.
- Generated SDK env getters use plain `HEXCLAVE_*` || `STACK_*` (no
conflict throw); dashboard inline resolver preserves empty/sentinel
passthrough to avoid build failures; docs/examples include dual‑read
utilities.
- Tests now stub canonical `HEXCLAVE_*` flags (e.g., plan limits, bot
challenge, OAuth tokens, hosted handler) to avoid shadowing/conflict
with committed defaults.
- **Migration**
- No immediate action; legacy `STACK_*` names still work.
- If both names are set with different values, builds/scripts error. Set
only `HEXCLAVE_*` or make both equal.
- SDK consumers won’t see conflict throws; update env names to
`HEXCLAVE_*` over time.
<sup>Written for commit 7539fb9fbf.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1588?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Migrated environment variable names from the legacy `STACK_*` prefix
to the new `HEXCLAVE_*` prefix across backend, dashboard, tooling,
Docker, and examples.
* Updated environment/config resolution to prefer `HEXCLAVE_*`, treat
empty strings as unset, and detect conflicts when both `STACK_*` and
`HEXCLAVE_*` are set to different values.
* Updated local emulator, server startup, and env-generation workflows
to use the new names (with legacy fallback where applicable).
* **Documentation**
* Updated docs and code examples to reference `HEXCLAVE_*` variables.
* **Tests**
* Refreshed unit and e2e coverage to validate dual-read behavior,
conflict detection, and empty-value handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
`hexclave dev` re-execs itself through `npx <pkg>@latest`
(`maybeReexecToLatest`) so users always get the latest dashboard without
reinstalling. But when that npx run **fails**, it exits nonzero and we
did `process.exit(result.code)` — killing `hexclave dev` even though a
perfectly good CLI was already installed locally.
This surfaced on **Replit**, where a user running `hexclave dev` got:
```
npm notice Access denied: Your download has been blocked by the Socket Security Policy.
Reason: AI-detected potential malware.
npm error code ECOMPROMISED
npm error Lock compromised
```
Diagnosis: the user's `pnpm` is aliased to `sfw pnpm` (**Socket
Firewall**), and Replit enforces an org-level Socket Security Policy.
Socket **blocks the `@hexclave/cli@latest` download** (false-positive
"AI-detected malware", almost certainly the bundled minified dashboard).
The interrupted download breaks npx's reify while it holds its cache
lock, which npm reports as `Lock compromised`. The lock message is a
*downstream symptom*; the real failure is the blocked download.
The same class of failure (blocked download, npm error, lock contention,
offline) all share one shape: **npx exits nonzero before our CLI ever
runs**, and today that takes down `hexclave dev`.
## Fix
Use a **startup-marker handshake** to distinguish the two cases:
- The parent passes a temp marker path to the npx child via
`STACK_CLI_REEXEC_MARKER`.
- The re-exec'd child touches the marker the instant it starts
(`signalReexecStartedIfChild`).
- After the child exits (`decidePostReexec`):
- exited 0, or nonzero **with** the marker present → our CLI ran;
**propagate** the exit code (real command result).
- nonzero **without** the marker, or npx not spawnable → our CLI never
ran; **fall back** to the installed CLI instead of failing `hexclave
dev`.
The marker only needs file create/exists (robust on sandboxed/networked
filesystems). If the marker can't be created, we preserve the old
always-propagate behavior, so there's no spurious-fallback risk.
`decidePostReexec` and `signalReexecStartedIfChild` are pure and
unit-tested.
## Verification
- `pnpm test run src/lib/self-update.test.ts` → 23 passing (added cases
for the fallback decision and the marker handshake).
- `pnpm typecheck` / `pnpm lint` → clean.
- End-to-end: forced a real npx failure (unreachable registry → npx
exits nonzero before our CLI runs) and confirmed `hexclave dev` now logs
`Auto-update skipped: …; continuing with the installed CLI.` and
proceeds into the installed dev path instead of dying.
## Notes / follow-ups (not in this PR)
- Workaround for affected users today: `STACK_CLI_NO_AUTO_UPDATE=1` (or
`--no-auto-update`) skips the npx download entirely.
- Worth reporting the Socket false-positive to socket.dev to allowlist
`@hexclave/cli`, and reconsidering shipping a ~165 MB minified dashboard
inside the npm tarball (it's what trips AI-malware heuristics and slows
cold installs).
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Prevent `hexclave dev` from exiting when `npx @latest` auto-update fails
by falling back to the installed CLI. Signal-based aborts now propagate
as a nonzero exit (never NaN), so Ctrl-C doesn’t relaunch dev.
- **Bug Fixes**
- Fall back to the installed CLI when `npx <pkg>@latest` fails before
the CLI starts (firewalls, offline, npm lock errors). Do not fall back
when the child is killed by a signal; propagate 128+signum with a safe
nonzero fallback if the signum is unknown.
- Startup-marker handshake: parent sets `REEXEC_MARKER_ENV`
(`HEXCLAVE_CLI_REEXEC_MARKER`); child touches it on start;
`decidePostReexec` chooses propagate vs fallback. If the marker can’t be
created, keep the old always-propagate behavior.
- Scrub the marker env from user commands using the exported constant,
covering both Windows and POSIX spawn paths.
<sup>Written for commit cb2635f2ae.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1612?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Bug Fixes**
* Updated the `dev` command to prevent internal re-exec environment
details from being inherited by user commands.
* Improved self-update re-exec fallback logic to better detect whether
the CLI actually started, with more reliable exit-code and signal
handling.
* **Tests**
* Added deterministic test coverage for the post-re-exec decision path
and signal/exit propagation scenarios, including early `npx` failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
`envVarsForProject` in the dev-environment manager only emits the legacy
`STACK_*` env var names. The rest of the CLI was migrated to the
`HEXCLAVE_*` brand as canonical, keeping `STACK_*` only as a fallback:
- `init` scaffolds `.env` with `NEXT_PUBLIC_HEXCLAVE_PROJECT_ID`,
`NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY`,
`HEXCLAVE_SECRET_SERVER_KEY`
- `resolveProjectId` reads `HEXCLAVE_PROJECT_ID` first, then
`STACK_PROJECT_ID` (commented as "legacy")
- `doctor` lists the `HEXCLAVE_*` names first
So `hexclave dev` injects a different set of names than the CLI
otherwise expects/generates.
## Change
`envVarsForProject` now emits both brands (`HEXCLAVE_*` and `STACK_*`)
across the public framework prefixes (`NEXT_PUBLIC_`, `VITE_`,
`EXPO_PUBLIC_`), built from a single source list.
- All previously-emitted `STACK_*` keys are unchanged (no regression).
- The secret server key is still only emitted as
`HEXCLAVE_SECRET_SERVER_KEY` / `STACK_SECRET_SERVER_KEY` — never under a
public, client-readable prefix.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Updated the dev environment to emit both `HEXCLAVE_*` and legacy
`STACK_*` env vars so `hexclave dev` matches the rest of the CLI and
avoids name mismatches. Secret keys remain non-public.
- **Bug Fixes**
- Emit both brands for `PROJECT_ID`, `PUBLISHABLE_CLIENT_KEY`, and
`API_URL` under ``, `NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`.
- Only emit `HEXCLAVE_SECRET_SERVER_KEY` / `STACK_SECRET_SERVER_KEY`
(never with public prefixes).
- Keep existing `STACK_*` outputs unchanged for backward compatibility.
<sup>Written for commit 2aef5d4b1a.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1627?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Improved internal environment variable generation architecture for
enhanced maintainability and multi-framework support.
---
**Note:** This release contains internal code improvements with no
changes to user-facing functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
Adds a **Things to Know About Microsoft OAuth** section to the Microsoft
auth provider docs covering provider-specific behavior:
- **Emails are not marked as verified** — Microsoft doesn't attest
control of the returned email, so Hexclave treats Microsoft emails as
unverified. Links to Microsoft's [claims validation
guidance](https://learn.microsoft.com/en-us/entra/identity-platform/claims-validation#validate-the-subject).
- **Supported account types** (custom OAuth keys only) — explains how
the tenant type set in the dashboard/config maps to Microsoft's
`{tenant}` endpoint segment (`common` / `organizations` / `consumers` /
tenant ID), with the default being `consumers`. Links to Microsoft's
[endpoint
reference](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints).
Also fixes a leftover branding issue: the dev shared-keys `<Info>`
callout said "Stack" instead of "Hexclave".
## Why
The unverified-email behavior is surprising (sign-in succeeds but the
email isn't verified) and previously undocumented. The account-types
note helps developers using their own OAuth app pick the right tenant
setting.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a “Things to Know About Microsoft OAuth” section explaining that
Microsoft emails are treated as unverified and how account types map to
Microsoft tenant endpoints (custom keys only, default is `consumers`).
Also fixes the dev shared-keys callout to correctly use Hexclave
branding and behavior.
<sup>Written for commit 7ae78d6e52.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1623?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Updated Microsoft authentication provider guide to clarify that
Hexclave automatically creates shared development keys and displays its
logo on the OAuth sign-in page.
* Added section explaining Microsoft OAuth email verification behavior
and how account types map to Microsoft tenant settings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
Performance pass on the internal **platform-analytics** route. All 17
ClickHouse queries fire in a single `Promise.all` on the shared
`stackframe` admin user, which is subject to a **9 GB per-user** memory
cap — so the worst case is the *sum* of per-query peaks, not the max.
Benchmarked at 10k projects / 1M users / 50M events (power-law, top
project ≈100k users), the sum of peaks was ~6.7 GiB. This PR brings it
down to ~3.8 GiB.
## Changes
**ClickHouse — `sipHash64(user_id)` as the distinct key** (exact,
verified byte-identical):
| query | peak mem | Δ |
|---|---|---|
| `dauSeries` | 949 → 373 MiB | −61% |
| `mauProjects` | 715 → 313 MiB | −56% |
| `activeByProject` | 635 → 374 MiB | −41% |
| `sparkByProject` | 1165 → 809 MiB | −31% |
A 64-bit hash has negligible collision probability over 1M users; the
benchmark confirmed identical output. (Same trick already used in the
internal-metrics MAU query.)
**ClickHouse — sample the activity split**
(`new`/`retained`/`reactivated`):
The split was the single heaviest query (~1.3 GiB) — its cost is a
window function over ~25.8M `(user, day)` rows plus an all-history scan,
which `sipHash` alone barely helped (−7%). It now uses **consistent
1-in-4 user sampling** (same `cityHash64(user_id) % 4` bucket applied to
both subqueries so each sampled user's full activity sequence is
preserved; counts scaled ×4):
- **317 MiB (−78%)** peak memory, **~0.4% mean error** (max 1.4% on the
smallest day) vs the exact result.
This is an **approximation** — the dashboard "Growth quality" chart now
notes it (`subtitle: "… · sampled estimate (~0.4%)"`).
`ACTIVITY_SPLIT_SAMPLE` is a single constant in the route; set it to `1`
to go back to exact.
## What I tried that did NOT make the cut (documented in the harnesses)
- `country` — peak memory is dominated by the per-user `argMax(country,
event_at)` payload, not the key, so hashing does nothing. Left
exact/unchanged.
- PG `authMethods` / `email` — with the production composite PK indexes
the original plans are already best; correlated-subquery / anti-join
rewrites were far worse. No PG query changes in this PR.
## Benchmark harnesses (added)
- `apps/backend/scripts/benchmark-platform-analytics.ts` — full-route
baseline (per-query time/memory/rows).
- `apps/backend/scripts/optimize-platform-analytics.ts` — sipHash & PG
variant comparison with byte-equality checks.
- `apps/backend/scripts/optimize-split.ts` — exact vs sampled split
variants with accuracy measurement.
They seed isolated `bench_pa` databases (server-side, auto-cleaned) and
read `system.query_log` / `EXPLAIN (ANALYZE, BUFFERS)`. Run e.g.:
`pnpm --filter @hexclave/backend run with-env:dev tsx
scripts/optimize-split.ts`
## Testing
- Backend `typecheck` passes. (Dashboard has pre-existing typecheck
errors on the base branch in unrelated files — auth-methods,
team-analytics, user-emails, RDE config — not touched here.)
- All exact rewrites verified byte-identical to the originals by the
harnesses; the sampled split measured at ~0.4% mean error.
Numbers are local warm-cache (relative shape, not production latency).
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Cuts worst-case ClickHouse memory for the internal platform analytics
route by switching to hashed distinct keys and sampling the heaviest
query. On a 10k projects / 1M users / 50M events benchmark, the sum of
per-query peaks drops from ~6.7 GiB to ~3.8 GiB with exact results (or
~0.4% error on the sampled chart).
- **Performance**
- Use sipHash64(user_id) as the distinct key in uniqExact/uniqExactIf
for DAU series, MAU/projects, active-by-project, and sparkline. Exact
results (verified). Peak memory down 31–61% per query.
- Sample the new/retained/reactivated split at 1-in-4 users (consistent
`cityHash64` bucket across subqueries, counts ×4). Peak memory ~−78%
(~1.3 GiB → ~0.3 GiB) with ~0.4% mean error. Toggle via
`ACTIVITY_SPLIT_SAMPLE` (set to 4; set to 1 for exact). Dashboard
subtitle now notes “sampled estimate (~0.4%).”
- Added local harnesses to seed isolated data and measure
time/memory/equality:
`apps/backend/scripts/internal-analytics/benchmark-platform-analytics.ts`,
`optimize-platform-analytics.ts`, `optimize-split.ts`.
<sup>Written for commit 60ccf1a06f.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1632?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Updates
* **Improvements**
* Enhanced platform analytics calculations for more consistent and
efficient user counting across key performance indicators (DAU, MAU,
per-project metrics).
* Updated the Growth Quality chart to indicate that user counts
represent sampled estimates with approximately 0.4% margin of error for
improved performance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: mantrakp04 <mantrakp@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mantra <mantra@stack-auth.com>
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/hexclave/hexclave/blob/dev/CONTRIBUTING.md
-->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Add platform-wide analytics to the internal dashboard with a secure
backend route and a new page to visualize cross-project metrics. Only
available when viewing the `internal` project and gated by platform
admin access.
- **New Features**
- Backend: add `/api/latest/internal/platform-analytics` aggregating
metrics across all projects via ClickHouse; protected by
`ensurePlatformAdmin`.
- Dashboard: add `/projects/[projectId]/platform-analytics` page with
charts; sidebar entry appears only when `projectId === "internal"`.
- **Bug Fixes**
- Correctness: add `branch_id` filters to all event queries and project
aggregates; exclude the `internal` project from ClickHouse aggregates;
validate MRR quantity.
- Metrics/UI: feature adoption uses `total_projects` from the API and
clamps both chart and label to 0–100%; remove unreachable
`revenue_growth` sort key.
- Safety/Tests: use `Map` for country aggregation; add unit tests for
`ensurePlatformAdmin`/`isPlatformAdmin`; switch tests to inline
snapshots and document the `as-any` cast.
<sup>Written for commit 3c803a8915.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1626?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a Platform Analytics dashboard for internal projects with
interactive 7/30-day range charts, KPI tiles, and visual breakdowns
(growth, country, sign-in method, user mix), plus email health,
dead-click insights, a searchable project leaderboard, and feature
adoption.
* Introduced an internal analytics API providing rolling-window
comparisons and structured metrics for dashboard rendering.
* **Bug Fixes**
* Strengthened access control with platform-admin authorization for
analytics access.
* **Tests**
* Added coverage for platform-admin authorization behavior.
* **Chores**
* Updated Next.js to 16.2.9 across applications.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mantra <mantra@stack-auth.com>
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
## What
Fixes a TypeScript error in `@hexclave/template` that was breaking `pnpm
typecheck` repo-wide:
```
client-app-impl.cross-domain.test.ts(450,101): error TS2345:
Argument of type '(options: { url: string | URL; }) => Promise<void>'
is not assignable to parameter of type '(...args: unknown[]) => unknown'.
```
## Why
The `vi.spyOn(...).mockImplementation(...)` callback typed its parameter
directly as `{ url: string | URL }`, but `mockImplementation` expects
`(...args: unknown[]) => unknown`. `unknown` isn't assignable to `{ url
}`, so the build failed.
## How
Accept variadic `unknown[]` args and cast `args[0]` to the expected
shape — matching the spy's actual signature while preserving the test's
behavior.
`pnpm --filter=@hexclave/template typecheck` now passes.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fix TypeScript typecheck failure in `@hexclave/template` cross-domain
auth test by updating the `vi.spyOn(...)._redirectTo.mockImplementation`
to accept `(...args: unknown[])` and casting `args[0]` to `{ url: string
| URL }`. This aligns the mock with its expected signature and restores
`pnpm typecheck`.
<sup>Written for commit 4a1787ee66.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1628?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
## Summary
Two fixes:
**1. Impersonation no longer requires logout** — The generated JS
snippet now clears all auth cookie variants (`hexclave-refresh-{pid}*`,
`stack-refresh-{pid}*`, access tokens) and sets the token in the
structured `hexclave-refresh-{pid}--default` format the SDK reads first.
Previously the snippet only set the legacy cookie, which was ignored
when a structured cookie already existed.
**2. Fix OAuth + other deeply nested API routes returning 404 in dev** —
Moved the API 404 handler from file-based
`api/[...notFoundPath]/route.ts` into the middleware (`proxy.tsx`). The
catch-all at the `api/` level was shadowing dynamic routes 7+ segments
deep (e.g. `auth/oauth/authorize/[provider_id]`) in Turbopack dev mode
(Next.js 16.2.7). The middleware already has `routes` + `SmartRouter`,
so it checks for a match before rewriting and returns the custom 404
directly when nothing matches.
Link to Devin session:
https://app.devin.ai/sessions/d9dcb2d203aa4a6ea36c8cdd2a4a42c2
Requested by: @mantrakp04
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Updated the user impersonation dialog to explicitly state that the
pasted console snippet will replace your current session with the
impersonated user’s session.
* **Refactor**
* Standardized the impersonation console snippet generation to use a
shared token-based approach, including proper expiration handling.
* **Bug Fixes**
* Improved reliability of the impersonation flow by failing when the
required refresh token is unavailable, preventing incomplete snippet
generation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mantra <mantra@stack-auth.com>
## Summary
- Fetches the canonical Hexclave skill from `https://skill.hexclave.com`
when the backend AI route is invoked through MCP `ask_hexclave`
- Appends that skill content to the spawned docs agent's system context
before generation
- Adds focused tests for non-Ask-Hexclave no-op behavior, successful
skill embedding, and loud fetch failure
## Why
The public MCP server exposes the skill as a separate resource/prompt,
but the backend docs agent spawned by `ask_hexclave` only saw the user's
question. That meant clients had to correctly load the skill themselves,
and the server-side answer quality could miss the canonical
setup/context.
## Validation
- `pnpm -C apps/backend exec eslint
'src/app/api/latest/ai/query/[mode]/route.ts'
src/lib/ai/mcp-skill-context.ts src/lib/ai/mcp-skill-context.test.ts`
- `pnpm exec vitest run
apps/backend/src/lib/ai/mcp-skill-context.test.ts --config /dev/null
--environment node`
- `pnpm exec tsc --noEmit --target es2022 --module esnext
--moduleResolution bundler --lib es2022,dom --types vitest
apps/backend/src/lib/ai/mcp-skill-context.ts
apps/backend/src/lib/ai/mcp-skill-context.test.ts`
## Notes
- Normal backend Vitest/typecheck are blocked in this fresh worktree
because generated/built `@hexclave/shared/dist/*` files are missing, and
repo instructions say not to run package builds from the agent.
- Full backend lint also reports an unrelated pre-existing error in
`apps/backend/scripts/run-bulldozer-studio.ts`.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds full Hexclave docs to `ask_hexclave` requests by fetching
https://docs.hexclave.com/llms-full.txt and appending them to the docs
agent system prompt. Includes a 5‑minute cache and 5s timeout, and skips
docs tools when `ask_hexclave` is used.
- **New Features**
- Added `getMcpSkillContextPrompt` to fetch and inject docs for
`ask_hexclave`; no‑op otherwise.
- Integrated in `route.ts` to append context before tool selection and
pass `mcpToolName` to `getTools`.
- Reliability: 5‑minute TTL cache, 5s timeout, and error handling; tests
cover success, no‑op, errors, timeouts, null/undefined, and cache hits.
- **Refactors**
- Switched source to `https://docs.hexclave.com/llms-full.txt`.
- Removed `docs-mintlify/llms-full.txt` and related generator code.
- Cache TTL now uses `performance.now()` for accurate expiry.
<sup>Written for commit e0dc388c64.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1605?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* AI queries now dynamically fetch and include documentation context
during operation, with in-memory caching to minimize network requests
and redundant fetches.
* **Tests**
* Added comprehensive test suite validating documentation fetching
behavior, error handling for network failures and timeouts, and caching
mechanisms to ensure reliability.
* **Chores**
* Removed auto-generated documentation artifact from the codebase.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mantra <mantra@stack-auth.com>