- Introduced a new API route for committing changes after user review, allowing the agent to keep the sandbox alive for inspection before finalizing updates.
- Enhanced the existing applyConfigUpdate function to transition to an awaiting review state, storing the diff for user visibility.
- Added progress tracking and stage reporting for the config agent run, improving user feedback during the update process.
- Updated the dashboard to reflect the new review stages and provide a more interactive experience for managing configuration changes.
Co-Authored-By: mantra <mantra@stack-auth.com>
P0: Strip OAuth token from git origin after clone so LLM agent
never sees credentials (repo-agent.tsx)
P1: Replace raw error.message with safe hardcoded text in API
response and dashboard UI (apply/route.tsx, config-update.tsx)
P1: E2E spike script now requires explicit env vars instead of
falling back to pushing to main (spike-orchestrator-e2e.mts)
P2: Use urlSchema for commit_url (schema-fields.ts)
P2: Return commitSha directly instead of parsing from URL
(repo-agent.tsx, apply/route.tsx)
P2: Support LINK_BRANCH_ID env var (link-project-to-github.ts)
P2: Widen structural fallback regex (config-updater.ts)
P2: Log warning when cancel has no sandboxId (cancel/route.tsx)
P2: Reject arbitrary string config values (config-eval.ts)
Co-Authored-By: mantra <mantra@stack-auth.com>
- Added a script for building a shared config-agent base snapshot, optimizing the warm-boot process for configuration updates.
- Introduced a new script to link existing projects to GitHub repositories without re-seeding, improving workflow efficiency.
- Updated the workflow paths and configuration file names to align with the new Hexclave structure.
- Refactored existing scripts to ensure consistency in configuration paths and enhance overall integration with GitHub.
Co-Authored-By: mantra <mantra@stack-auth.com>
- Added a new script for seeding a local dashboard project linked to a GitHub repository, facilitating end-to-end testing of the config-agent flow.
- Introduced new API routes for preparing and applying configuration updates via the GitHub repo agent, improving the workflow for managing config changes.
- Updated the command hook in settings to provide clearer instructions on handling typecheck and lint failures.
- Refactored the config update logic to ensure seamless integration with the new agent routes.
Co-Authored-By: mantra <mantra@stack-auth.com>
- Introduced a new Config Update Repo Agent to manage GitHub configuration updates within a Vercel Sandbox.
- The agent allows for efficient cloning, dependency installation, and configuration updates while preserving the original file structure.
- Updated model selection to include "anthropic/claude-haiku-4.5" for enhanced AI capabilities.
- Refactored config update logic to ensure all writes are routed through the agent, maintaining authoring integrity.
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
Speed up the Usage page by aggregating metered usage across owned
projects/tenancies with fewer queries and new indexes. Adds E2E tests to
verify team-owned rollups and calendar‑month windows.
- **Performance**
- Added concurrent indexes for `EmailOutbox(tenancyId,
startedSendingAt)` and `SessionReplay(tenancyId, startedAt)`; updated
Prisma schema.
- Group tenancies by (DB client, schema) and run one SQL per group that
counts both emails and session replays; uses `mapWithConcurrency` from
`@hexclave/shared` (concurrency 4, aborts on first error).
- Added helpers `getOwnedProjectAndTenancyIdsForBillingTeam` and
`getNonAnonymousUserCountForTenancies`; made `mapWithConcurrency`
null‑safe with bounds checks.
- **Tests**
- Added E2E tests for the internal plan-usage endpoint covering
team-owned rollups, calendar‑month boundaries, and zero‑usage cases.
- Added unit tests for ownership scope resolution and non‑anonymous user
counting.
<sup>Written for commit 5d6098006c.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1650?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
* **Performance Improvements**
* Improved plan usage rollups by aggregating metered emails and session
replays together across an owned scope.
* Added database indexes to speed up time-window metering lookups for
email outbox and session replays.
* **Tests**
* Extended unit tests for billing-team entitlement aggregation and
non-anonymous user counting.
* Added end-to-end coverage for the internal plan-usage endpoint,
including seeded scenarios and period validation.
* **Refactor**
* Reworked entitlement and usage calculations to reuse shared logic for
more consistent results.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: armaan <armaan@stack-auth.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
### 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
Move Node.js-only functions (evalConfigFileContent, tryEvalConfigFileContent,
detectImportPackageFromDir) to new config-eval.ts. This prevents the dashboard
browser build from failing on fs/path/jiti imports.
Dashboard now uses parseStaticConfigLiteral (regex+JSON.parse) instead of
jiti eval for untrusted GitHub-fetched config content, avoiding RCE risk.
Remove type casts in favor of isRecord type guard.
Co-Authored-By: mantra <mantra@stack-auth.com>
Replace parseHexclaveConfigFileContent and evaluateStaticConfigExpression
with jiti-based evalConfigFileContent. Move renderConfigFileContent from
hexclave-config-file.ts to config-rendering.ts alongside the new eval
function.
Removed functions:
- parseHexclaveConfigFileContent (Babel AST walker)
- tryParseHexclaveConfigFileContent
- evaluateStaticConfigExpression
- unwrapStaticConfigExpression
Added jiti dep to @hexclave/shared since config-rendering.ts now uses
jiti.evalModule for runtime evaluation of config file content strings.
Co-Authored-By: mantra <mantra@stack-auth.com>
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
`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
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>