Commit Graph

3494 Commits

Author SHA1 Message Date
mantrakp04
ddec2a4ac4 fix(analytics): segment-derived span versions, off-path span writes, sign-out segment rotation
- span version is now the span's own end time (data-tied, never regresses)
- replay/segment span inserts moved off the response path (waitUntil)
- session_replay_segment_id rotates on sign-out to prevent cross-user correlation
- new SessionReplayChunk index narrowing per-segment bounds aggregation
2026-07-02 10:59:04 -07:00
mantrakp04
e637d72465 fix(docs): update email sending API description and parameters
Enhanced the description for the "/emails/send-email" endpoint to clarify the options for sending emails, including user IDs, all users, and arbitrary email addresses. Additionally, modified the parameters to remove the requirement for "user_id" and added an optional "email" field.
2026-07-01 17:33:28 -07:00
mantrakp04
8d124a4517 feat(analytics): spans telemetry surface + event parent_span_ids
Add a generic Spans telemetry surface alongside events, written directly to
ClickHouse (spans are siblings of events, not routed through ext-db-sync).

- analytics_internal.spans table (ReplacingMergeTree, versioned) + default.spans
  view, isolated by a row policy and granted to limited_user exactly like
  default.events.
- Span types: $session-replay and $session-replay-segment (per browser tab),
  written directly from the session-replays batch route and re-written per batch
  so their end advances; $refresh-token, derived in the view from the synced
  refresh_tokens dimension.
- events.parent_span_ids: additive column holding the root-first ancestor span-id
  list ([rti, sri, srsi]), stamped on each event insert. Typed-id prefixes live
  only on span ids / parent_span_ids; scalar id columns stay raw so existing
  customer SQL keeps working (additive, backwards compatible).
- SDK: EventTracker and SessionRecorder now share one per-tab
  session_replay_segment_id (each previously minted its own, so events could not
  be correlated to a replay recording).
- Dashboard: Spans tab on the analytics tables page.
- Tests: spans unit tests + e2e for session replays, analytics events batch, and
  the analytics query surface.
2026-07-01 17:27:54 -07:00
Armaan Jain
db94ec2d94
More consistent dashboard (#1613)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/hexclave/hexclave/blob/dev/CONTRIBUTING.md

-->

## Summary

This PR tightens dashboard consistency across the project list, team
invite flow, hosted auth previews, and shared overlay surfaces. It also
includes smaller visual fixes for charts/tooltips, globe sizing, sticky
data-grid surfaces, and dashboard/hosted component light-mode parity.

**Base:** `dev@2c5b2d02c` -> **Head:**
`more-consistent-dashboard@b1eae206`
**Capture notes:** screenshots were captured locally at `1440x1000`
after signing in through GitHub -> `admin@example.com` on the mock OAuth
server. Each page/state was allowed to settle for 5 seconds immediately
before capture. Dark mode is used by default; light mode is included
only for the light-specific overlay/dialog surfaces changed in this PR.

## Visual Comparison

### Hosted Auth Preview - Dark Mode

| Before | After |
| --- | --- |
| ![Before hosted auth preview
dark](https://gist.githubusercontent.com/Developing-Gamer/7f29365532f54161bc5b4531f7a71b1b/raw/before-auth-methods-preview-dark.png)
| ![After hosted auth preview
dark](https://gist.githubusercontent.com/Developing-Gamer/7f29365532f54161bc5b4531f7a71b1b/raw/after-auth-methods-preview-dark.png)
|

### Team Invite Dialog - Light-Specific Surface

| Before | After |
| --- | --- |
| ![Before team invite dialog
light](https://gist.githubusercontent.com/Developing-Gamer/7f29365532f54161bc5b4531f7a71b1b/raw/before-projects-invite-dialog-light.png)
| ![After team invite dialog
light](https://gist.githubusercontent.com/Developing-Gamer/7f29365532f54161bc5b4531f7a71b1b/raw/after-projects-invite-dialog-light.png)
|

## What's Changed

- Reworks the team invite dialog with clearer loading/retry states, seat
information, capacity warnings, pending invites, revoke actions, and
refresh behavior.
- Adds a dashboard-local hosted auth preview used by Auth Methods and
onboarding-style flows, showing sign-in/sign-up methods with project
display names and disabled sign-up behavior.
- Standardizes overlay and popover surfaces across dashboard, hosted
components, and shared UI primitives for better light/dark consistency.
- Adjusts chart, tooltip, globe, sidebar drawer, data-grid, and
email-log presentation details to reduce clipping, bleed, and stacking
issues.

## Notes For Reviewers

- The visual comparison focuses on places where this PR has visible UI
differences: project cards/actions, the team invite modal, overlay menu
styling, and the hosted auth preview.
- Lower-level primitive background/ring changes are represented through
the menu/dialog screenshots rather than separate screenshots for every
consuming component.

## Test Plan

- Captured the before screenshots on `dev@2c5b2d02c` and the after
screenshots on `more-consistent-dashboard@b1eae206`.
- Waited 5 seconds on each page/state before capturing screenshots.
- Uploaded exactly 10 screenshots to a fresh gist and embedded them
above.
- Not run in this pass: code tests, because this pass only updates the
PR description/screenshots.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a live hosted authentication preview for project setup and auth
methods, including sign-in/sign-up states and multiple login options.

* **Bug Fixes**
* Improved invite dialog behavior so seat limits, plan upgrades, loading
states, retries, and invitation refreshes stay in sync.
* Fixed chart tooltip and globe display behavior so overlays, sizing,
and labels render more reliably.

* **Style**
* Refreshed dialogs, menus, tooltips, charts, and sidebars with improved
light/dark theme styling and spacing.

<!-- 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>
2026-07-01 09:24:08 -07:00
Konsti Wohlwend
244fc3ab7b
feat: add navigation funnel graph page (#1701) 2026-06-30 23:04:22 -07:00
github-actions[bot]
c9612539c7 chore: update package versions 2026-06-30 23:15:23 +00:00
BilalG1
526ce69ab9
feat(cli): fetch RDE dashboard from GitHub Releases instead of bundling (#1682)
## Summary

The RDE dashboard was bundled into the `@hexclave/cli` npm tarball
(~165MB). That made the `npx @latest` auto-update re-exec pull a huge
tarball, which tripped download firewalls (Socket Firewall
false-positives on Replit → `Lock compromised`).

This decouples the dashboard from the CLI: it's published as a **GitHub
Release artifact** (a zip + `manifest.json` on a floating
`dashboard-latest` release) and fetched at runtime, cached on disk. The
dashboard now rolls forward **independently of the CLI version**
(assuming newest dashboards stay compatible with older CLIs — no compat
gate yet, by design for now).

## How it works

- **Runtime** (`packages/cli/src/lib/dashboard-release.ts`): on
`hexclave dev`, fetch `manifest.json` (`{version, sha256, url}`) from a
stable, no-API GitHub URL → cache under
`<dirname(devEnvStatePath())>/dashboards/<version>/` → streamed
download, **sha256 verify**, extract via the existing `extract-zip` dep,
atomic publish. Offline → fall back to the newest cached build.
- **Wiring** (`dev.ts`): `startDashboardIfNeeded` resolves/launches the
cached release; the restart decision now compares the **dashboard
release** version (not the CLI version). `HEXCLAVE_DASHBOARD_DIR` runs a
local build with zero network; a custom dev dashboard command bypasses
releases entirely.
- **No more npx self-update**: the `npx @latest` re-exec in `hexclave
dev` is removed entirely (along with `self-update.ts`, the
`--no-auto-update` flag, and the binName plumbing that only built the
npx call). It existed to keep the bundled dashboard fresh; now the
dashboard self-updates from GitHub, so the re-exec only added an npx
download on every run — the firewall surface. Users update the CLI via
npm/npx themselves.
- **Publishing** (`scripts/package-dashboard-release.mjs` +
`.github/workflows/dashboard-release.yaml`): build standalone → zip →
write manifest → publish immutable `dashboard-v<version>` release +
clobber the `dashboard-latest` manifest. **Fails loudly** if an existing
tag's asset sha differs from a fresh build (dashboard changed without a
version bump → would otherwise advertise a hash the served zip doesn't
match).
- **Build**: CLI build is now `tsdown`-only (no bundled dashboard);
`turbo.json` drops the dashboard standalone build from the CLI build
graph.

## Testing

- **Unit**: 135 CLI tests pass (manifest parsing incl. path-safe version
validation, URL resolution, version picking, dir override).
- **End-to-end (no GitHub)**: a localhost HTTP server + fixture zip
exercised the full fetch path — download, sha256 verify, cache-hit,
roll-forward, **corrupt-sha rejection**, offline→cache fallback (9/9).
- **Real dashboard, no GitHub**: built the standalone, served the real
76MB zip over `python -m http.server`, and confirmed `hexclave dev`
downloads → verifies → extracts → **boots** the dashboard and registers
an RDE session against the hosted backend. Also validated
`HEXCLAVE_DASHBOARD_DIR` boot.

## Review

Three review agents (runtime correctness, packaging/workflow, code
quality) ran; valid findings fixed: path-traversal hardening on
`version`, a concurrent cache rm/rename race, the workflow
sha-mismatch/atomicity gap, plus `errorMessage` dedup and added tests.

## Notes / follow-ups

- No CLI↔dashboard compatibility gate yet (assumes always-compatible);
easy to add later via a min-CLI field in the manifest. This matters more
now that the CLI no longer auto-updates.
- The publish workflow has not run yet, so the first push to `main` will
create the `dashboard-latest` release.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added automated, immutable versioned releases for the standalone
dashboard plus a continuously updated “latest” manifest.
* CLI now fetches, verifies, caches, and supports offline fallback for
the dashboard runtime.
* **Bug Fixes**
* Improved dashboard restart behavior to update only when a strictly
newer published version is available.
* **Chores**
* Removed the CLI’s automatic update/re-launch behavior for more
predictable startup.
* **Tests**
* Added unit tests for dashboard manifest validation and latest-version
selection.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 16:12:13 -07:00
Konsti Wohlwend
6a979e8b3e
chore: remove unused dependencies from package.json files (#1698) 2026-06-30 15:45:25 -07:00
Konsti Wohlwend
d472283283
Use tsdown -W --watch (workspace mode) for dev builds (#1689) 2026-06-30 15:40:08 -07:00
github-actions[bot]
31b32735bf chore: update package versions 2026-06-30 20:44:13 +00:00
BilalG1
2f0d9bc6cb
fix(session-replay): gzip replay batches so large rrweb snapshots aren't dropped (#1675)
## Problem

Session replay batches are uploaded as **raw, uncompressed JSON**. A
single large rrweb full snapshot (>900 KB) is dropped client-side in
`SessionRecorder._flush`, and any batch near the server's 1 MB body
limit is rejected. Meanwhile the **analytics events** path right next
door already gzips on the wire (`encodeAnalyticsBody` + a server-side
gunzip with zip-bomb guards) — replays just never got the same
treatment.

Notably, `dev` was already **red** on the SDK test `sends a single
oversized event alone without dropping it` (the code dropped at 900 KB
while the test expected the event to be sent). This change makes that
test pass.

## What this does

Ports the proven analytics compression pattern to session replays:

- **`packages/shared` (client transport):** generalize
`encodeAnalyticsBody` → `encodeGzipJsonBody` and wire it into
`sendSessionReplayBatch`. Replays now gzip via native
`CompressionStream` (`application/octet-stream`), with the same
fallbacks: keepalive flushes send plain JSON (so the request survives
page teardown), and a missing/throwing `CompressionStream` (Safari <
16.4) degrades to plain JSON.
- **`packages/template` (SDK recorder):** replace the 900 KB hard-drop
with `MAX_SINGLE_EVENT_BYTES = 8 MB`, kept in sync with the server's
decompressed cap. The wire is gzipped downstream, so an oversized single
event is now sent compressed instead of discarded; only an event that
could never fit *decompressed* is dropped.
- **`apps/backend` (route):** add `maybeDecodeBinaryBody` (gunzip with a
1 MB compressed cap + 8 MB `maxOutputLength` zip-bomb guard) via a
`.transform` on the batch route body schema. Plain-JSON bodies pass
through untouched.

## Why gzip / CompressionStream

Benchmarked on a real rrweb capture (Puppeteer, rrweb 1.1.3): a **1.09
MiB full snapshot → 134 KiB gzip (~8×) in ~6.6 ms**. Native
`CompressionStream` adds **0 bundle bytes** and was faster than fflate.
This mirrors what PostHog (gzip, native `CompressionStream` + fflate
fallback) and Sentry (fflate deflate in a worker) both ship — both rely
on compression, not splitting, as the primary mechanism for large
events.

## Tests

- **Client SDK** (`client-interface.test.ts`): gzip+octet-stream on
non-keepalive (round-trip + size assert), plain JSON on keepalive, and
both `CompressionStream`-unavailable fallbacks.
- **SDK recorder** (`session-replay.test.ts`): existing oversized-event
test now passes; new test that a >8 MB event is dropped while a
following normal event still sends.
- **Backend e2e** (`session-replays.test.ts`): gzipped happy path
(`compressed < 1MB < raw`), invalid gzip → 400, oversized compressed →
413, zip-bomb → 400. Verified passing against a live local backend.

## Verification notes

- `@hexclave/shared` + `@hexclave/template` typecheck pass; touched
files lint clean; SDK unit tests pass.
- Two **pre-existing, unrelated** local failures (each confirmed by
stashing this change and re-running on clean `dev`): a backend typecheck
error in `oauth/ssrf-protection.test.ts` (`NODE_ENV` read-only), and 3
session-replay **quota** e2e tests failing in the payments
`setSessionReplayItemQuantity` setup helper.

## Follow-up (out of scope)

The keepalive/unload path still sends uncompressed (64 KB browser
keepalive cap), so a very large final batch on page exit can still be
lost — not addressed here.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Gzip session replay batches so large rrweb snapshots are sent instead of
dropped. Keepalive flushes cap single-event size to avoid 413s on page
unload, and limits now include a 1 KiB envelope margin to match server
caps.

- **Bug Fixes**
- Client (`@hexclave/shared`): generalized `encodeAnalyticsBody` →
`encodeGzipJsonBody`; gzip via native `CompressionStream` for
`/session-replays/batch`; keepalive and missing/throwing
`CompressionStream` fall back to JSON.
- SDK recorder (`@hexclave/template`): normal flushes allow a single
event up to `MAX_SINGLE_EVENT_BYTES = 8 MiB - 1 KiB`; keepalive flushes
cap single-event size at `MAX_BATCH_UNCOMPRESSED_BYTES = 900 KB`; send
oversized events alone and rely on gzip; drop only above those
thresholds; log a distinct 413 warning with the count of buffered events
dropped and stop the loop.
- Backend: gunzip `application/octet-stream` bodies with caps (`1 MB`
compressed, `8 MiB` decompressed); invalid gzip → 400; oversized
compressed → 413; plain JSON passes through.
- Tests: added client encoding tests for session replays; SDK tests for
>8 MiB drop, keepalive drop, and 413 warning; backend e2e for gzip,
invalid gzip, oversized compressed, and zip-bomb. The oversized-event
SDK test now passes.

- **Refactors**
  - Hoisted shared test helpers and trimmed comments.

<sup>Written for commit 855cdb052e.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1675?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**
* Session replay batch uploads can now be automatically gzipped (when
supported) to reduce request size; analytics batch uploads use the same
behavior.
* **Bug Fixes**
* Improved handling of gzipped upload bodies, with stricter validation
and safer size/decompression limits to reject invalid payloads and “zip
bomb” scenarios.
* Updated session replay buffering so oversized events are dropped only
when they exceed decompression constraints, while allowing later events
to upload.
* **Tests**
* Added client, server, and end-to-end coverage for gzip/keepalive
behavior and limit enforcement.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 13:13:11 -07:00
Konstantin Wohlwend
36c2b49e23 Fix slop description 2026-06-30 11:56:02 -07:00
Konstantin Wohlwend
7588445efa Update AI setup prompt 2026-06-30 11:36:13 -07:00
github-actions[bot]
9b52f76520 chore: update package versions 2026-06-30 18:27:55 +00:00
BilalG1
bdb511f6a9
Make analytics include_all_branches a no-op with TODO (#1678)
## What

The `/internal/analytics/query` route accepts an `include_all_branches`
flag (defaults to `false`). Previously, passing `true` threw a
`HexclaveAssertionError`. Since the flag is reachable from user input,
that assertion surfaced as a 500 / error-tracking noise rather than a
clean response.

This makes the flag a **no-op** for now and documents the intended
behavior with a TODO.

## Behavior

- Regardless of `include_all_branches`, queries remain scoped to the
current branch via the ClickHouse row policy that filters on
`SQL_branch_id` (set from `auth.tenancy.branchId`).
- Callers passing `include_all_branches=true` will still only receive
data for the **current branch** until cross-branch filtering is
implemented.

## Note / follow-up

Because the flag is silently ignored, a caller asking for "all branches"
gets single-branch results with no signal. The TODO tracks implementing
real cross-branch querying. Flagging in case we'd rather surface a
proper client-facing error in the interim instead of a silent no-op.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Made the `include_all_branches` flag on `/internal/analytics/query` a
no-op so `true` no longer throws and avoids 500s from user input.
Branching isn’t implemented yet, so queries always scope to the current
branch via the ClickHouse `SQL_branch_id` row policy; TODO added for
real cross-branch querying.

<sup>Written for commit a07e1237e9.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1678?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

* **Bug Fixes**
* Analytics requests now accept the “include all branches” option
without failing.
* Branch-scoped analytics queries continue to run as before, while the
option is treated as a no-op for now.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 11:16:37 -07:00
BilalG1
81433e5181
feat(emails): arbitrary-recipient send + Emailable on managed domains (#1681)
## Summary

Two related email changes:

### 1. Send to arbitrary email addresses
The `sendEmail` SDK function and `POST /api/v1/emails/send-email` now
accept a third recipient selector, **`emails: string[]`**, mutually
exclusive (XOR) with `user_ids` and `all_users`. These recipients don't
have to belong to a user — they're mapped to the existing internal
`custom-emails` recipient type (no associated user object, can't
unsubscribe, so intended for transactional mail).

- Response `results` items are now `{ user_id?, email? }`; the `emails`
path returns `{ email }`.
- Validation message updated to `Exactly one of user_ids, all_users, or
emails must be provided`.
- Recipient addresses are validated with the shared `emailSchema`.

### 2. Run Emailable on managed custom domains
`LowLevelEmailConfig.type` is widened `'shared' | 'standard'` →
`'shared' | 'managed' | 'standard'`, and `getEmailConfig` returns
`'managed'` for the managed-Resend branch. The Emailable deliverability
check now runs for **shared** and **managed**, and is skipped for
**`standard`** (a customer's own SMTP server or Resend API key).

**Why:** managed domains send through our single Resend *account*
(per-domain scoped keys, but shared account → account-level bounce
penalties are collateral across managed customers), so we own that
reputation. With a custom SMTP server or the customer's own Resend key,
the customer owns their deliverability, so we don't second-guess their
recipients or spend Emailable checks on their volume. The shared
dev-email wrapper remains `shared`-only.

## Tests
- `send-email.test.ts`: added arbitrary-recipient send
(single/multiple), empty-array, and validation cases (both-selectors,
no-selector, invalid email); updated the exactly-one-of snapshot.
**24/24 pass.**
- New `emails/deliverability-gating.test.ts`: shared & managed skip an
undeliverable address (`LIKELY_NOT_DELIVERABLE`), custom SMTP still
sends. **3/3 pass.**

> Note: `email-queue.test.ts` has 2 pre-existing snapshot failures (a
`margin:0rem`→`margin:0` CSS normalization in rendered email HTML) that
are unrelated to this change — confirmed by reproducing them on `dev`
with these changes stashed.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds support for sending emails to arbitrary addresses and enables
Emailable checks for managed domains to protect shared sending
reputation. The API/SDK now accept `emails: string[]` as a recipient
selector, and deliverability checks run on shared and managed sending.

- New Features
- API/SDK: `emails: string[]` recipient selector (XOR with
`user_ids`/`all_users`) in `sendEmail` and POST
/api/v1/emails/send-email. Results return `{ email }` for this path;
addresses validated via `emailSchema`. Recipients map to
transactional-only “custom-emails” (no user, no unsubscribe).
- Deliverability: email config adds `'managed'`; `getEmailConfig`
returns it for managed Resend domains. Emailable check runs for `shared`
and `managed`; skipped for `standard` (custom SMTP or customer Resend
key).

<sup>Written for commit e48cf2ddda.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1681?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**
* Emails can now be sent to arbitrary email addresses, not just existing
users.
* Email sending APIs and client options now accept an email list as a
recipient choice.

* **Bug Fixes**
* Improved recipient validation so exactly one target type must be
chosen.
* Fixed response results to consistently show either a user ID or an
email address.
* Corrected deliverability checks for managed email setups and updated
skip behavior for undeliverable addresses.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 11:16:25 -07:00
BilalG1
4cd766e406
fix(backend): use structured output for QA reviewer instead of JSON.parse (#1688)
## Problem

The QA reviewer (`apps/backend/src/lib/ai/qa-reviewer.ts`) extracted the
model's JSON verdict from free-form text with a greedy `/\{[\s\S]*\}/`
regex followed by `JSON.parse`. Whenever Haiku wrapped its JSON in prose
or tool-call text, the captured slice was malformed and `JSON.parse`
threw:

```
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
```

This is Sentry issue **STACK-BACKEND-18F** — **218 occurrences in the
last 30 days** (262 all-time), still ongoing. It's caught (background QA
task, 0 users impacted) but noisy, and on failure the review is silently
marked failed.

### Root cause
The brace-matching heuristic assumes the first `{` in the response
starts the JSON object and the last `}` ends it. Any stray brace in
surrounding prose (a preamble like `"Here's my review {note}: {...}"`,
unquoted/single-quoted keys, or `<function_calls>` tool text) breaks the
extraction. The reviewer output is non-deterministic, so a low steady
fraction of reviews fail.

## Fix

Switch to schema-enforced **structured output** via `generateText`'s
`output: Output.object({ schema })` (AI SDK v6), which keeps the
existing tool-calling loop while constraining the final result to a
validated object. The verdict is read directly off `result.output`,
removing the regex, `JSON.parse`, and manual `typeof` shape validation.

- Net **−12 lines**.
- Trimmed the now-redundant "respond with ONLY valid JSON … {inline
schema}" block from the system prompt (the schema enforces structure);
kept the semantic guidance (flag types, scoring, `needsHumanReview`
rule).
- Kept the existing `overallScore` clamp.

## Verification

- **Typecheck** (`tsc --noEmit`): clean.
- **Lint** (`eslint`): clean.
- **Live end-to-end**: ran the real Haiku-4.5 reviewer against the exact
prompt/input that failed **11/15** on the old path → **15/15** return a
validated object on the new path, zero parse failures.

## Follow-ups (out of scope, noted during investigation)
- These Sentry events leak a live access token + publishable key into
the request-context payload; the OpenRouter key sits in plaintext
(commented) in `apps/backend/.env.local`. Worth rotating/scrubbing.
- Consider capturing `result.text` on the failure path so any residual
structured-output failures are inspectable (currently discarded).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaced regex/JSON.parse extraction in the QA reviewer with
schema-enforced structured output using `ai`’s `generateText` +
`Output.object`. Also defaulted `improvementSuggestions` to an empty
string to avoid false failures when the model omits suggestions.

- **Bug Fixes**
  - Added a `zod` verdict schema and read result from `result.output`.
  - Removed greedy JSON extraction and manual shape checks.
- Defaulted `improvementSuggestions` to "" to tolerate omission and
prevent false `needsHumanReview` verdicts.
- Slimmed prompt by dropping “respond with ONLY JSON” block; kept review
guidance.
  - Kept `overallScore` clamp; tool-calling loop unchanged.
- Verification: typecheck/lint clean; 15/15 successful runs on a
previously failing case.

<sup>Written for commit 72ae71017f.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1688?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**
* QA reviews now use structured output, making review results more
reliable and consistently formatted.
* Review flags now include clearer details such as type, severity, and
explanation.

* **Bug Fixes**
* Improved handling of review output by removing fragile text parsing
and manual JSON extraction.
  * Added safer defaults for missing review suggestions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 11:15:46 -07:00
Konstantin Wohlwend
e60d880eae Update AGENTS.md 2026-06-30 11:06:32 -07:00
github-actions[bot]
ea2915faa8 chore: update package versions 2026-06-30 18:04:45 +00:00
Konstantin Wohlwend
86ef03cad9 Base64 self-hosted project key initialization 2026-06-30 10:58:40 -07:00
Konsti Wohlwend
09c8ef1946
Keep local emulator removal in a new migration, not edits to applied ones (#1694) 2026-06-30 10:43:10 -07:00
Mantra
09bb1a8974
chore: use glm-5.2:nitro for unauthenticated AI tiers; document CLI exec & config sync (#1693)
## Summary
- Standardize the unauthenticated `smart` and `smartest` model tiers in
`apps/backend/src/lib/ai/models.ts` on `z-ai/glm-5.2:nitro` (previously
a mix of `z-ai/glm-5.2` and `z-ai/glm-5:nitro`).
- Document the `npx @hexclave/cli exec <javascript>` command (runs JS
with a pre-configured `hexclaveServerApp`) in the agent reminders.
- Note that the config file is automatically synced when using the local
dashboard/dev environment via `npx @hexclave/cli dev --config-file
<path>`.

These reminder updates are reflected across `docs-mintlify/llms.txt`,
`docs-mintlify/snippets/hexclave-agent-reminders.jsx`,
`docs-mintlify/snippets/home-prompt-island.jsx`, and
`docs-mintlify/guides/getting-started/setup.mdx`.

## Test plan
- [ ] Verify unauthenticated AI requests resolve to `z-ai/glm-5.2:nitro`
for smart/smartest tiers.
- [ ] Confirm docs render the new agent-reminder bullets correctly.

Made with [Cursor](https://cursor.com)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Standardized unauthenticated `smart` and `smartest` tiers to
`z-ai/glm-5.2:nitro` for both slow and fast paths to ensure consistent
model selection. Updated docs to include `npx @hexclave/cli exec
<javascript>` (preloads `hexclaveServerApp`) and to note config
auto-sync when using `npx @hexclave/cli dev --config-file <path>`.

<sup>Written for commit 3363c8d9f3.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1693?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**
* Updated unauthenticated AI model selection to use a newer variant for
faster and more reliable responses in supported smart modes.

* **Documentation**
* Added clearer CLI guidance for running JavaScript with a
pre-configured app context.
* Expanded reminder text to better explain local config syncing during
development and dashboard runs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 10:39:27 -07:00
Konsti Wohlwend
d6d9d0d934
Remove local emulator support (#1692) 2026-06-30 10:27:01 -07:00
github-actions[bot]
d962e9843a chore: update package versions 2026-06-30 17:18:40 +00:00
Konstantin Wohlwend
d31d29c112 Clean up AI tools 2026-06-30 10:08:08 -07:00
Mantra
61698f6ecf
feat(mcp): switch ask_hexclave model from gemini to glm 5.2 (#1677)
## Summary

Switches the `ask_hexclave` MCP tool's model from Gemini 3.5 Flash to
GLM 5.2 by changing the speed parameter from `"fast"` to `"slow"` in the
query to the AI backend.

The model selection matrix maps `smart`/`slow`/`unauthenticated` →
`z-ai/glm-5.2` (previously `smart`/`fast`/`unauthenticated` →
`google/gemini-3.5-flash`).

Link to Devin session:
https://app.devin.ai/sessions/f710490916754099916f9899c2700a5c
Requested by: @mantrakp04

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Switched the `ask_hexclave` MCP tool from Gemini to GLM by updating the
model selection matrix. Unauthenticated smart/fast now routes to
`z-ai/glm-5:nitro`, unauthenticated smartest/fast to
`z-ai/glm-5.2:nitro`, and `dumb/slow/authenticated` uses
`z-ai/glm-4.5-air` (removed `:free`).

<sup>Written for commit 6a51ba6358.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1677?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

* **Bug Fixes**
* Adjusted AI request behavior to favor more deliberate responses, which
may improve result quality and consistency.

<!-- 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>
2026-06-30 10:00:58 -07:00
BilalG1
7f1d563753
fix(backend): use paid z-ai/glm-4.5-air slug instead of retired :free variant (#1685)
## Problem

Sentry is reporting an `AI_APICallError` on the AI proxy path:

> This model is unavailable for free. The paid version is available now
- use this slug instead: `z-ai/glm-4.5-air`

OpenRouter discontinued the **free** tier for GLM 4.5 Air. The model
selection matrix still pinned the `:free` variant for the **dumb / slow
/ authenticated** path, so every request routed there fails.

## Fix

Drop the `:free` suffix in `apps/backend/src/lib/ai/models.ts` so it
uses the paid slug OpenRouter recommends:

```diff
-      authenticated: { modelId: "z-ai/glm-4.5-air:free" },
+      authenticated: { modelId: "z-ai/glm-4.5-air" },
```

`ALLOWED_MODEL_IDS` is derived from the matrix, so the new slug is
picked up automatically.

## Notes

- This was the only `:free` slug in the matrix; nothing else is exposed
to the same break.
- This moves a previously-free path onto paid inference (cheap model,
but no longer $0).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Switch the dumb/slow/authenticated path to the paid `z-ai/glm-4.5-air`
slug, replacing the retired `:free` variant to stop “This model is
unavailable for free” errors. Restores successful requests after
OpenRouter ended the free tier.

- **Bug Fixes**
- Updated model matrix in apps/backend/src/lib/ai/models.ts to use
`z-ai/glm-4.5-air`; `ALLOWED_MODEL_IDS` picks up the new slug
automatically.

<sup>Written for commit c354b5fcf2.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1685?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

* **Bug Fixes**
* Updated the model selection for authenticated requests in one
quality/speed setting, so the app now uses the correct model ID and
accepts the updated option in allowed model lists.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 09:47:53 -07:00
Konstantin Wohlwend
b049306b6e Make hexclave exec support local dashboard
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
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
DB migration compat / No migration changes (skipped) (push) Has been cancelled
2026-06-29 18:45:11 -07:00
Konsti Wohlwend
287d96602a
feat: add Project ID column to platform analytics leaderboard (#1690) 2026-06-29 18:44:13 -07:00
github-actions[bot]
2c5b2d02cd chore: update package versions 2026-06-30 01:19:04 +00:00
Mantra
6bbc792fb3
refactor: migrate config parsing from Babel AST to jiti (#1661)
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
Publish npm packages / publish (push) Has been cancelled
Publish Swift SDK to prerelease repo / publish (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
## Summary

Replace `parseHexclaveConfigFileContent` /
`evaluateStaticConfigExpression` (Babel AST walker) with
`evalConfigFileContent` using `jiti.evalModule()`. Move
`renderConfigFileContent` from `hexclave-config-file.ts` →
`config-rendering.ts`.

Added `jiti` dep to `@hexclave/shared` (already used in shared-backend,
dashboard, backend, cli).

Link to Devin session:
https://app.devin.ai/sessions/cb098b1fb62b4dfeaf3324bc2e1377f1
Requested by: @mantrakp04

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Migrates trusted config evaluation to `jiti` and moves GitHub config
edits to a server‑side repo agent running in a Vercel Sandbox with an
apply → review → commit flow. Adds run tracking, safer defaults, and a
dashboard diff review with clear, user‑facing errors.

- **New Features**
- Two‑phase flow and endpoints: POST `/internal/config/github/apply`,
`.../commit`, `.../cancel`, plus GET `.../run`; each run tracked by
`run_id` in `ConfigAgentRun` (status, stage, progress, diff, base
commit, sandbox id). Run ids validated as UUIDs.
- Repo agent runs in a fresh sandboxed clone; warm‑boot via base
snapshot (`apps/backend/scripts/config-agent/build-image.ts`,
`HEXCLAVE_CONFIG_AGENT_BASE_SNAPSHOT_ID`). Captures a unified diff and
base commit, stops the sandbox at review, then rebuilds files from the
stored diff on commit. Returns `commitSha`, uses a safe conflict error,
and strips OAuth tokens from git remotes.
- Dashboard: non‑dismissible progress and diff preview using
`@pierre/diffs` with a cross‑tab run watcher; blocks conflicting edits
and supports cancel/commit review flow. Adds an RDE “apply” path with
progress UI.
- AI proxy defaults to `/api/latest/integrations/ai-proxy` (production
passthrough via `PRODUCTION_AI_PROXY_BASE_URL`); adds
`anthropic/claude-haiku-4.5`.

- **Refactors and Fixes**
- Trusted eval via `@hexclave/shared` `config-eval` using `jiti`;
browser‑safe parsing for untrusted GitHub content; rendering remains in
`config-rendering`. Clear separation of Node‑only code into
`config-eval`.
- Shared agent/updater logic moved to `@hexclave/shared-backend`;
removed deterministic fast path so all writes go through the agent to
preserve authoring. CLI and emulator updated to use `config-eval`.
- Defaults/renames: config file `hexclave.config.ts` (CLI `config pull`
defaults to this path), workflow `hexclave-config-sync.yml`; env
prefixes standardized to `HEXCLAVE_*`.
- Integrity and UX: commit advancement gated to the current linked
repo/branch; cancel clears any captured diff; elapsed timer handles late
starts and the not‑started sentinel; loader vs invalid config export
errors separated for accurate messaging.
- Onboarding and seeds: wizard now uses environment‑based OAuth provider
setup with updated tests; corrected GitHub owner in dummy project
seeding.

<sup>Written for commit 6cf0e899a0.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1661?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 configuration file parsing/validation by evaluating config
modules, supporting both string and object-based `config` exports and
ensuring the expected `config` export is present.
* Updated config rendering and import-package detection to consistently
generate the `config` export and handle legacy package entrypoints.
* Tightened handling of non-statically-resolvable forms during update
flows.
* **Tests**
* Updated and extended config parsing/validation tests to reflect the
new evaluation behavior and edge cases.
* **Chores**
  * Added a Jiti-based dependency to support runtime evaluation.
<!-- 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>
2026-06-29 10:25:11 -07:00
devin-ai-integration[bot]
c54ab2874d
Replace browser alert() with DesignDialog in user create/edit flow (#1680)
## Summary

In `UserDialog`, duplicate-email errors (`UserWithEmailAlreadyExists`,
`ContactChannelAlreadyUsedForAuthBySomeoneElse`) were shown via
`window.alert()`. Replaced with a controlled `DesignDialog` using
`WarningCircleIcon`, so the error appears as a styled modal instead of a
native browser popup.

Link to Devin session:
https://app.devin.ai/sessions/99d73091c47a4a58b33d8724df5a7ce8

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaced `window.alert()` in the user create/edit flow with a controlled
`DesignDialog` for duplicate-email errors (`UserWithEmailAlreadyExists`,
`ContactChannelAlreadyUsedForAuthBySomeoneElse`). Uses
`WarningCircleIcon` with an OK action, preserves form data via
`prevent-close-and-prevent-reset`, clears dialog state when the form
closes, and fixes a max-statements-per-line lint warning.

<sup>Written for commit 274fcb8e9b.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1680?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. -->

---------

Co-authored-by: vedanta.gawande <vedanta.gawande@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Armaan Jain <84474476+Developing-Gamer@users.noreply.github.com>
2026-06-29 10:18:16 -07:00
devin-ai-integration[bot]
4a0f2b1778
Default user export to filtered scope; note Anonymous in all-users label (#1679)
## Summary
Two changes to the user data export dialog on the project Users page:

1. The export scope now defaults to **"Export only filtered/searched
users"** instead of "all users".
2. The all-users option label is now **"Export all users in the project
(includes Anonymous)"**.

To keep this scoped to the Users table (the shared export dialog is
reused by other tables), the dialog's default scope is made configurable
rather than changed globally:

- `DataGridExportOptions` gains `defaultScope?: DataGridExportScope`
(defaults to `"all"`).
- The dialog initializes `useState(exportOptions?.defaultScope ??
"all")`.
- `user-table.tsx` passes `defaultScope: "filtered"` and the updated
`allScopeLabel`.

Other tables (teams, transactions, emails) are unaffected — they keep
the `"all"` default.

Link to Devin session:
https://app.devin.ai/sessions/4996678b2b944090b6eef2f64f0a62a1

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Default the Users export dialog to filtered scope and clarify that the
"all users" option includes Anonymous; scope resets to the per-table
default only when the dialog reopens, and other tables keep "all".

- **New Features**
- Added `defaultScope` to export options and initialized scope from it;
Users table sets `defaultScope: "filtered"` and updates the all-users
label.
- Reset scope to `defaultScope` only on a closed→open transition to
avoid changing it while the dialog is open.

- **Bug Fixes**
- Stubbed `NODE_ENV` via `vi.stubEnv` in
`apps/backend/src/oauth/ssrf-protection.test.ts` to fix lint and prevent
env mutation.

<sup>Written for commit 3aa670b6d2.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1679?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. -->

---------

Co-authored-by: vedanta.gawande <vedanta.gawande@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-06-29 09:31:06 -07:00
github-actions[bot]
092c27dd0e chore: update package versions 2026-06-27 22:00:12 +00:00
Konstantin Wohlwend
c6d162e5c8 Fix negative email queue step deltas 2026-06-27 14:54:24 -07:00
Konstantin Wohlwend
9c438fa604 Update RDE project keys 2026-06-26 18:48:33 -07:00
Konstantin Wohlwend
cf4f9b8918 Local dashboard header 2026-06-26 17:50:18 -07:00
Konsti Wohlwend
4a77dd98ca
Show config file paths instead of project cards on /projects for RDEs (#1676) 2026-06-26 17:30:14 -07:00
github-actions[bot]
e70e95a3af chore: update package versions 2026-06-27 00:28:49 +00:00
Konstantin Wohlwend
f49817655a Simpler post-onboarding configs 2026-06-26 16:09:06 -07:00
Konstantin Wohlwend
cf17fff37d stack dev -> hexclave dev 2026-06-26 16:05:34 -07:00
Konstantin Wohlwend
32e5a1ccbc Log config update agent stdout & stderr if needed 2026-06-26 16:05:34 -07:00
BilalG1
74471d8d30
feat(emails): allow custom emails on shared server with dev wrapper (#1673)
## What

Custom emails / templates / drafts sent through Hexclave's **shared
(development) email server** are no longer blocked with
`RequiresCustomEmailServer`. They are now allowed, but their **subject
and body are wrapped** at send time with a notice that this is a
development email from Hexclave, so unexpected recipients know they can
safely ignore it.

The wrapper only applies to **project-defined content addressed to the
project's own users**. Hexclave's own default-template emails
(verification, password reset, magic link, etc.) and system
notifications (credential-scanning alerts, internal feedback) are sent
**verbatim**.

## How

-
**[send-email/route.tsx](apps/backend/src/app/api/latest/emails/send-email/route.tsx)**
— removed the `RequiresCustomEmailServer` throw that blocked the shared
server.
- **[emails.tsx](apps/backend/src/lib/emails.tsx)** — added
`wrapSharedDevEmail()` (prefixes the subject with `[Hexclave dev email]`
and prepends a notice banner to HTML/text) and
`isCustomEmailForSharedServer(recipient, createdWith, templateId)`.
- **[email-queue-step.tsx](apps/backend/src/lib/email-queue-step.tsx)**
— applies the wrapper at send time, gated on `emailConfig.type ===
"shared"` **and** the email being project-defined custom content.
Applying it at send time reliably wraps both the subject (from
`overrideSubject` or the template's `<Subject>`) and the rendered HTML.

### What counts as "wrap-eligible"
`isCustomEmailForSharedServer` returns true only when **all** hold:
1. the email is addressed to one of the project's own users (recipient
type is not `custom-emails`), **and**
2. it is a draft, a custom template, or raw HTML — i.e. **not** one of
the built-in `DEFAULT_TEMPLATE_IDS`.

Condition (1) exempts Hexclave's own system senders (credential-scanning
revoke, internal feedback) which send raw HTML to bare addresses via
`custom-emails` and would otherwise be mis-classified as project
content. This was a bug caught in review — a leaked-API-key security
alert to a shared-server customer would have been prefixed `[Hexclave
dev email]` with a "you can safely ignore it" banner. The recipient type
is already persisted on the outbox row, so no schema change was needed.

## Tests

- **send-email.test.ts** — replaced the old "400 on shared config" test
with two new tests: (a) a custom email on the shared server is delivered
with the `[Hexclave dev email]` subject prefix + notice banner, and (b)
a **default template** (`sign_in_invitation`) on the shared server is
delivered **verbatim** (no prefix, no banner) — pinning the core safety
contract.
- **js/email.test.ts** — flipped the "throws RequiresCustomEmailServer"
test to assert the send now resolves.

Verified locally against a full stack:
-  `send-email.test.ts` — 18/18
-  `js/email.test.ts` — 12/12
-  `password/send-reset-code.test.ts` — passes (default templates on
shared server stay unwrapped)

## Known limitations (intentional scope)

- **Template CRUD still blocked on the shared server.**
`internal/email-templates` routes still throw
`RequiresCustomEmailServer`, so a shared-server project can send raw
HTML / a default template via the API but cannot create or edit a
*saved* custom template. Sending arbitrary HTML is unaffected; only the
saved-template editor remains gated.
- **A project can send a (project-edited) default template unwrapped**
by calling `send-email` with a `template_id` equal to a built-in
`DEFAULT_TEMPLATE_IDS` value. Low impact (requires a server key, limited
upside), noted for awareness.

## Note: freestyle-mock fix included


[freestyle-mock/Dockerfile](docker/dependencies/freestyle-mock/Dockerfile)
now also accepts `/execute/v3/script`. The `freestyle` SDK bump in #1654
moved to `/v3`, but the mock only served `/v1`+`/v2`, so **all** local
email rendering 404'd (pre-existing `dev` breakage, not from this
feature). The v3 request/response is identical to v2. Happy to split
this into its own PR if preferred.

Out of scope: `emails/email-queue.test.ts` has 2 pre-existing snapshot
failures (`margin:0` vs recorded `margin:0rem`, a
`@react-email/components` version drift in the mock) — those tests use a
custom email server, so this PR's shared-only code path never runs for
them.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Email sending can now proceed when using a shared email server.
* Development-style wrapping is applied to eligible shared-server custom
email content, including HTML notice injection.

* **Bug Fixes**
* Removed the previous blocking “requires custom email server” behavior
for shared-server configurations.
* Default-template emails over the shared server are no longer wrapped.

* **Tests**
* Updated end-to-end and JS email tests to validate both wrapped
custom-email behavior and unwrapped default-template behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 15:44:44 -07:00
github-actions[bot]
c868ec31bc chore: update package versions 2026-06-26 21:28:46 +00:00
Konstantin Wohlwend
a19f686c92 Update reminder with instructions on ask timeouts 2026-06-26 14:22:56 -07:00
github-actions[bot]
788e7cc87c chore: update package versions 2026-06-26 21:18:23 +00:00
Konstantin Wohlwend
73b8a0c27f Fix redirect URL on devtool indicator 2026-06-26 14:12:47 -07:00
Konstantin Wohlwend
912eea4f7f Improve local agent update logging 2026-06-26 14:08:03 -07:00
Konstantin Wohlwend
dff454f47f Document init script less 2026-06-26 13:50:48 -07:00
BilalG1
e3e4ac908d
Fix preview-mode logout when opening account settings (#1670)
## Problem

In **preview mode**, clicking the user profile → **Account settings**
logs the user out and strands them on a sign-in page they can't get
past.

Root cause is a combination of three things:

- Preview mode uses an **in-memory** token store (`tokenStore:
"memory"`), so any full page reload wipes the session.
- The account-settings menu item calls
`app.redirectToAccountSettings()`, whose `redirectTo*` helpers fall
through to `window.location.assign` on the client — a **hard reload**.
- The `/handler/account-settings` route lives **outside** the
`(protected)` layout that performs the preview auto-login, so nothing
re-mints the preview session after the reload.

Net effect: hard nav → in-memory session wiped → `useUser({ or:
'redirect' })` finds no user → redirect to sign-in, with no way back in
(preview credentials are a throwaway UUID).

In normal (cookie) mode this is harmless because the cookie survives the
reload — the bug is preview-specific.

## Fix

Soft-navigate from the menu item using `app.useNavigate()` (a
`router.push` wrapper under the `"nextjs"` redirect method) instead of
`redirectToAccountSettings()`. A soft client-side navigation does not
re-evaluate the JS bundle, so the module-singleton app instance and its
in-memory token store stay alive.

The fix is scoped to the single dashboard call site rather than the
shared SDK `redirectToAccountSettings()` method, which ships to all
customers and intentionally hard-navigates for cross-domain / sign-out
flows.

## Note / follow-up

This covers navigation **from within the app**, which is the only in-app
entry point to account settings. It does **not** cover a hard refresh or
a direct link to `/handler/account-settings` in preview mode — those
still wipe the in-memory session because the handler route has no
auto-login. A fuller root-cause fix would bring the preview auto-login
(or the `(protected)` layout) to the handler route.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes preview-mode logout when opening Account settings by switching the
dashboard menu to a soft client-side nav using `app.useNavigate()`
instead of a hard reload. This preserves the in-memory session.

- **Bug Fixes**
- Soft-navigate to `/handler/account-settings` via `app.useNavigate()`
to avoid wiping the preview token.
- Change scoped to the dashboard menu; `redirectToAccountSettings()` in
the SDK remains unchanged.

<sup>Written for commit 0e0c3a1bab.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1670?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

* **Bug Fixes**
* The “Account settings” menu now opens with smooth in-app navigation
instead of a full page reload, making the experience faster and more
seamless.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 13:43:48 -07:00