feat(sentry): staging-only "Trigger Sentry Test" button (#1072)#1183
Conversation
…#1072) Adds a staging-gated diagnostic button at the top of Settings → Developer Options that fires a tagged exception (`SentryStagingTestError`, `tags.test=manual-staging`) so QA can validate the React → Sentry pipeline end-to-end without depending on real user errors. The new `triggerSentryTestEvent()` helper bypasses the analytics-consent gate in `beforeSend` for events tagged `manual-staging` only, mirroring the existing smoke-test bypass — consent is not required to confirm the pipeline is alive on a fresh install. Visibility is gated by `APP_ENVIRONMENT === 'staging'` so production and dev builds never render the row. To remove after verification, drop the `SentryTestRow` block in `DeveloperOptionsPanel.tsx` and the helper + manual-staging branch in `analytics.ts`.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a staging-only “Trigger Sentry Test” UI row and local component, a new exported helper to emit a tagged manual-staging Sentry error, updates Sentry init's beforeSend to allow/sanitize manual-staging events, and adds service and UI tests covering gating, success, and failure modes. ChangesSentry Staging Test Event
Sequence DiagramsequenceDiagram
participant User as User
participant UI as SentryTestRow
participant Service as triggerSentryTestEvent()
participant Sentry as `@sentry/react` client
participant Gate as initSentry beforeSend
User->>UI: Click "Send test event"
UI->>UI: state = 'sending'
UI->>Service: call triggerSentryTestEvent()
Service->>Sentry: getClient()
alt client exists
Service->>Sentry: captureException(Error with tags test:manual-staging, source:developer-options-button)
Sentry->>Gate: beforeSend(event)
Gate->>Gate: detect manual-staging → sanitize & allow
Sentry-->>Service: return eventId
Service->>Sentry: flush(2000)
Sentry-->>Service: flush complete
Service-->>UI: return eventId
UI->>UI: state = 'sent', display eventId
else no client
Service-->>UI: return undefined
UI->>UI: state = 'sent', display "no id / Sentry disabled"
end
alt flush fails
Service-->>UI: throw/reject error
UI->>UI: state = 'error', display failure message
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Review rate limit: 4/5 reviews remaining, refill in 12 minutes. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/components/settings/panels/DeveloperOptionsPanel.tsx`:
- Around line 162-191: The status messages in DeveloperOptionsPanel.tsx (the
conditional UI driven by status.kind and displayed below the send button) are
only visual; wrap the status output (the blocks rendered when status.kind ===
'sent' or 'error') in a single container element with an accessible live region
(e.g., aria-live="polite" and optionally role="status") so screen readers
announce the result when onClick triggers the send flow and status is updated;
ensure the same live region shows the sent message (including status.eventId or
the no-id text) and the error message (status.message) so assistive tech
receives updates.
In `@app/src/services/analytics.ts`:
- Around line 159-160: Remove the variable timestamp from the exception message
so the Sentry event signature stays stable: change the Error construction to a
constant message like "Manual Sentry test from staging UI" (replace the current
`const error = new Error(\`Manual Sentry test from staging UI @ ${stamp}\`)`),
and instead attach the `stamp` value as metadata (e.g., via Sentry.setExtra /
captureException extras or a tag) so timing info is recorded but does not affect
grouping; refer to the `stamp` and `error` constants when making the change.
- Around line 168-170: Sentry.flush(2000) returns Promise<boolean> and the
current code ignores it; update the code around the console.info/await
Sentry.flush(2000) block to capture the boolean result (from Sentry.flush) and
if it is false, throw an error or otherwise fail the test before returning
eventId so callers know the flush timed out; reference Sentry.flush and the
eventId variable to locate where to check the return value and fail fast when
flush returns false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e1e92532-dd36-4259-a54b-3b6509f1ef02
📒 Files selected for processing (3)
app/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/services/__tests__/analytics.test.tsapp/src/services/analytics.ts
Brings tinyhumansai#1183's diff coverage above the 80% gate by exercising the two files that previously sat at 0% / 85.7% on changed lines: - `services/__tests__/analytics.test.ts` — adds three `initSentry` beforeSend tests: * drops untagged events when consent is off * lets `tags.test === 'manual-staging'` events through (and verifies breadcrumbs / request / extra are stripped, contexts narrowed, surface tag added) * preserves the existing smoke-test bypass - `components/settings/panels/__tests__/DeveloperOptionsPanel.test.tsx` — new file: covers the production-build hide path, the staging-only render, the success state machine (idle → sending → "Event sent. id: …"), the missing-id branch, and the failure branch. Both suites mock `@sentry/react`, the core-state snapshot, and the config module so consent / DSN / IS_DEV gates are controlled in-test.
… surface flush timeouts Three fixes from CodeRabbit-style review on tinyhumansai#1183: 1. **Accessible live region for status updates** (DeveloperOptionsPanel) The `sent` / `error` status blocks rendered as separate `<div>`s with no ARIA hooks, so screen readers stayed silent on click. Wrapped both branches in a single `<div role="status" aria-live="polite" aria-atomic="true">` so assistive tech announces the result whenever `status.kind` changes. Added a test asserting the attributes. 2. **Stable Sentry grouping** (analytics.ts) The exception message previously embedded an ISO timestamp (`Manual Sentry test from staging UI @ <stamp>`), so Sentry's default fingerprint hashed each click into a fresh issue. Made the message constant and moved the timestamp to `extra.triggered_at` — every QA click now collapses into one issue with N events while per-click timing remains visible on each event. Used `extra`, not `tag`, since timestamps are high-cardinality and would explode tag indexes. 3. **Fail loud when `Sentry.flush()` times out** (analytics.ts) `await Sentry.flush(2000)` discarded the boolean. A `false` return means the queue did not drain within 2s and the network round-trip is unconfirmed — for a diagnostic tool, returning a successful-looking `eventId` in that case is a lie. Now captures the boolean and throws on `false` so the existing UI error path surfaces the timeout to QA. Added a test for the new failure path. Local diff-cover still at 100% (27/27 lines on changed files).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/services/analytics.ts`:
- Around line 63-68: Scope the manual-staging consent bypass to staging by
requiring APP_ENVIRONMENT === 'staging' wherever isManualTest is computed (e.g.
replace the current isManualTest = event.tags?.test === 'manual-staging' with a
check that also verifies process.env.APP_ENVIRONMENT === 'staging' or equivalent
environment accessor), and add a fail-fast guard in triggerSentryTestEvent() to
return/throw immediately when APP_ENVIRONMENT !== 'staging'; apply the same
change to the other occurrence around lines 152-157 so no manual-staging tag can
bypass consent outside staging.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 17f61eb2-3317-4056-9c0e-53741bb43eea
📒 Files selected for processing (4)
app/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/__tests__/DeveloperOptionsPanel.test.tsxapp/src/services/__tests__/analytics.test.tsapp/src/services/analytics.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- app/src/services/tests/analytics.test.ts
- app/src/components/settings/panels/DeveloperOptionsPanel.tsx
Defense in depth for the manual-staging consent bypass. The previous implementation matched purely on `tags.test === 'manual-staging'`, so any production code path that emitted an event with that tag (accidental, malicious, or from a future refactor) would have skipped the analytics consent gate. Two layers, both gated on APP_ENVIRONMENT now: 1. `analytics.ts::beforeSend` — `isManualTest` requires `APP_ENVIRONMENT === 'staging' && event.tags?.test === 'manual-staging'`. In production, the tag has no special meaning and the consent gate applies normally. 2. `analytics.ts::triggerSentryTestEvent` — early-return with a console warning if `APP_ENVIRONMENT !== 'staging'`. The UI button is already gated by the same check, but the helper is also defensive against programmatic callers (stray imports, future refactors). New tests: - `triggerSentryTestEvent` refuses to fire and never calls captureException when APP_ENVIRONMENT !== 'staging'. - `beforeSend` drops a `manual-staging` tagged event when APP_ENVIRONMENT === 'production' even though the tag is present. The test mock for `../../utils/config` is now a getter on a hoisted var so APP_ENVIRONMENT can flip per-test. Diff coverage stays comfortably above the 80% gate (90% on changed lines).
* feat(remotion): Ghosty character library with transparent MOV variants (tinyhumansai#1059) Co-authored-by: WOZCODE <contact@withwoz.com> * feat(composio/gmail): sync into memory tree (Slack-parity) (tinyhumansai#1056) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(scheduler-gate): throttle background AI on battery / busy CPU (tinyhumansai#1062) * fix(core,cef): run core in-process and stop orphaning CEF helpers on Cmd+Q (tinyhumansai#1061) * ci: add dedicated staging release workflow (tinyhumansai#1066) * fix(sentry): Rust source context + per-release deploy marker (tinyhumansai#405) (tinyhumansai#1067) * fix(welcome): re-enable OAuth buttons with focus/timeout recovery (tinyhumansai#1049) (tinyhumansai#1069) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(dependencies): update pnpm-lock.yaml and Cargo.lock for package… (tinyhumansai#1082) * fix(onboarding): personalize welcome agent greeting with user identity (tinyhumansai#1078) * fix(chat): make agent message bubbles fit content width (tinyhumansai#1083) * Feat/dmg checks (tinyhumansai#1084) * fix(linux): Add X11 platform flags to .deb package launcher (tinyhumansai#1087) Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> * fix(sentry): auto-send React events; collapse core→tauri for desktop (tinyhumansai#1086) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(cef): run blank reload guard on the CEF UI thread (tinyhumansai#1092) * fix(app): reload webview instead of restart_app in dev mode (tinyhumansai#1068) (tinyhumansai#1071) * fix(linux): deliver X11 ozone flags via custom .desktop template (tinyhumansai#1091) * fix(webview-accounts): retry data-dir purge so CEF handle race doesn't leak cookies (tinyhumansai#1076) (tinyhumansai#1081) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(webview/slack): media perms + deep-link isolation (tinyhumansai#1074) (tinyhumansai#1080) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * ci(release): split staging vs production workflows; promote staging tags (tinyhumansai#1094) * Update release-staging.yml (tinyhumansai#1097) * chore(staging): v0.53.5 * chore(staging): v0.53.6 * ci(staging): cut staging from main; add act local-debug helper (tinyhumansai#1099) * chore(staging): v0.53.7 * fix(ci): correct sentry-cli download URL and trap scope (tinyhumansai#1100) * chore(staging): v0.53.8 * feat(chat): forward thread_id to backend for KV cache locality (tinyhumansai#1095) * fix(ci): bump pinned sentry-cli to 3.4.1 (2.34.2 was never published) (tinyhumansai#1102) * chore(staging): v0.53.9 * fix(ci): drop bash trap in upload_sentry_symbols.sh; inline cleanup (tinyhumansai#1103) * chore(staging): v0.53.10 * refactor(session): flatten session_raw/, switch md to YYYY_MM_DD (tinyhumansai#1098) * Add full Composio managed-auth toolkit catalog (tinyhumansai#1093) * ci: add diff-aware 80% coverage gate (Vitest + cargo-llvm-cov) (tinyhumansai#1104) * feat(scripts): pnpm work + pnpm debug for agent-driven workflows (tinyhumansai#1105) * ci: pull pnpm into CI image, drop redundant setup steps (tinyhumansai#1107) * docs: add Cursor Cloud specific instructions to AGENTS.md (tinyhumansai#1106) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * chore(staging): v0.53.11 * docs: surface 80% coverage gate and scripts/debug runners (tinyhumansai#1108) * feat(app): show Composio integrations as sorted icon grid on Skills (tinyhumansai#1109) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat(composio): client-side trigger enable/disable toggles (tinyhumansai#1110) * feat(skills): channels grid + integrations card polish; tolerant Composio trigger decode (tinyhumansai#1112) * chore(staging): v0.53.12 * feat(home): early-bird banner + assistant→agent terminology (tinyhumansai#1113) * feat(updater): in-app auto-update with auto-download + restart prompt (tinyhumansai#677) (tinyhumansai#1114) * chore(claude): add ship-and-babysit slash command (tinyhumansai#1115) * feat(home): EarlyBirdyBanner + agent terminology + LinkedIn enrichment model pin (tinyhumansai#1118) * fix(chat): single onboarding thread in sidebar after wizard (tinyhumansai#1116) Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> * fix: filter out global namespace from citation chips (tinyhumansai#1124) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> * feat(nav): enable Memory tab in BottomTabBar (tinyhumansai#1125) * feat(memory): singleton ingestion + status RPC + UI pill (tinyhumansai#1126) * feat(human): mascot tab with viseme-driven lipsync (staging only) (tinyhumansai#1127) * Fix CEF zombie processes on full app close and restart (tinyhumansai#1128) Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * Update issue templates for GitHub issue types (tinyhumansai#1146) * feat(human): expand mascot expressions and tighten reply-speech state machine (tinyhumansai#1147) * feat(memory): ingestion pipeline + tree-architecture docs + ops/schemas split (tinyhumansai#1142) * feat(threads): surface live subagent work in parent thread (tinyhumansai#1122) (tinyhumansai#1159) * fix(human): keep mascot mouth animating when TTS ships no viseme data (tinyhumansai#1160) * feat(composio): consume backend markdownFormatted for LLM output (tinyhumansai#1165) * fix(subagent): lazy-register toolkit actions filtered out of fuzzy top-K (tinyhumansai#1162) * feat(memory): user-facing long-term memory window preset (tinyhumansai#1137) (tinyhumansai#1161) * fix(tauri-shell): proactively kill stale openhuman RPC on startup (tinyhumansai#1166) * chore(staging): v0.53.13 * fix(composio): per-action tool consumes backend markdownFormatted (tinyhumansai#1167) * fix(threads): persist selectedThreadId across reloads (tinyhumansai#1168) * feat(memory_tree): switch embed model to bge-m3 (1024-dim, 8K context) (tinyhumansai#1174) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): drop redundant [Memory context] recall injection (tinyhumansai#1173) * chore(memory_tree): drop body-read timeouts on Ollama HTTP calls (tinyhumansai#1171) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(transcript): emit thread_id + fix orchestrator missing cost (tinyhumansai#1169) * fix(composio/gmail): phase out html2md, prefer text/plain MIME part (tinyhumansai#1170) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): markdown output for internal tool results (tinyhumansai#1172) * feat(security): enforce prompt-injection guard before model and tool execution (tinyhumansai#1175) * fix(cef): popup paint dies after first frame — skip blank-page guard for popups (tinyhumansai#1079) (tinyhumansai#1182) Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> * chore(sentry): rename OPENHUMAN_SENTRY_DSN → OPENHUMAN_CORE_SENTRY_DSN (tinyhumansai#1186) * feat(remotion): add yellow mascot character with all animation variants (tinyhumansai#1193) Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(composio): hide raw connection ID, derive friendly label (tinyhumansai#1153) (tinyhumansai#1185) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(windows): align install.ps1 MSI with per-machine scope (tinyhumansai#913) (tinyhumansai#1187) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(tauri): deterministic CEF teardown on full app close (tinyhumansai#1120) (tinyhumansai#1189) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(composio): cap Gmail HTML body before strip (crash mitigation) (tinyhumansai#1191) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(auth): stop stale chat threads after signup (tinyhumansai#1192) Co-authored-by: Cursor <cursoragent@cursor.com> * feat(sentry): staging-only "Trigger Sentry Test" button (tinyhumansai#1072) (tinyhumansai#1183) * chore(staging): v0.53.14 * chore(staging): v0.53.15 * feat(composio): format trigger slugs into human-readable labels (tinyhumansai#1129) (tinyhumansai#1179) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(ui): hide unsupported permission UI on non-macOS for Screen Intelligence (tinyhumansai#1194) Co-authored-by: Cursor <cursoragent@cursor.com> * chore(tauri-shell): retire embedded Gmail webview-account flow (tinyhumansai#1181) * feat(onboarding): replace welcome-agent bot with react-joyride walkthrough (tinyhumansai#1180) * chore(release): v0.53.16 * fix(threads): preserve selectedThreadId on cold-boot identity hydration (tinyhumansai#1196) * feat(core): version/shutdown/update RPCs + mid-thread integration refresh (tinyhumansai#1195) * fix(mascot): swap to yellow mascot via @remotion/player (tinyhumansai#1200) * feat(memory_tree): cloud-default LLM, queue priority, entity filter, Memory tab UI (tinyhumansai#1198) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Persist turn state + restore conversation history on cold-boot (tinyhumansai#1202) * feat(mascot): floating desktop mascot via native NSPanel + WKWebView (macOS) (tinyhumansai#1203) * fix(memory/tree): emit summary children as Obsidian wikilinks (tinyhumansai#1210) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): coding-harness baseline primitives (tinyhumansai#1205) (tinyhumansai#1208) * docs: add Codex PR checklist for remote agents --------- Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Co-authored-by: WOZCODE <contact@withwoz.com> Co-authored-by: sanil-23 <sanil@vezures.xyz> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Co-authored-by: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com> Co-authored-by: oxoxDev <164490987+oxoxDev@users.noreply.github.com> Co-authored-by: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Co-authored-by: Gaurang Patel <ptelgm.yt@gmail.com> Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> Co-authored-by: Steven Enamakel's Droid <enamakel.agent@tinyhumans.ai> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Co-authored-by: Neil <neil@maha.xyz> Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: obchain <167975049+obchain@users.noreply.github.com> Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
Summary
triggerSentryTestEvent()helper inanalytics.tsemits aSentryStagingTestErrorwithtags.test=manual-staging,tags.source=developer-options-button, thenflush(2000).manual-stagingtag is allow-listed in the existingbeforeSendconsent gate (mirroring the smoke-test bypass), so the event lands regardless of analytics opt-in state.APP_ENVIRONMENT === 'staging'— production and dev builds never render the row.Problem
Per #1072: the staging dashboard had not received any React events for several days, and there was no in-app trigger to distinguish "pipeline broken" from "no errors actually occurring." A QA-controllable button that bypasses the consent gate gives an unambiguous, on-demand signal.
Solution
app/src/services/analytics.ts— newtriggerSentryTestEvent()that captures a deterministic exception, applies QA-friendly tags, and flushes synchronously.beforeSendadds anisManualTestallow-list arm parallel to the existing smoke-test bypass.app/src/components/settings/panels/DeveloperOptionsPanel.tsx—SentryTestRowcomponent with idle/sending/sent/error states; rendered only whenAPP_ENVIRONMENT === 'staging'.app/src/services/__tests__/analytics.test.ts— Vitest coveringtriggerSentryTestEvent(no-client / happy paths) plus threeinitSentrybeforeSendcases (consent-off drop,manual-stagingallow + PII strip, smoke-test pass-through).app/src/components/settings/panels/__tests__/DeveloperOptionsPanel.test.tsx— Vitest covering the production-build hide path, staging render, click → "Event sent. id: …", missing-id branch, and helper-throws → error message.Verified locally with
pnpm macos:build:debug(debug.appbuilt withVITE_OPENHUMAN_APP_ENV=staging): button visible at the top of Developer Options, click fires the event, Sentry SDK accepts it (network POST to*.sentry.io).While verifying, also fired the existing core + shell triggers from the same bundle so all three surfaces could be cross-checked:
OPENHUMAN_APP_ENV=staging openhuman-core sentry-test --message "..."→ event id returned, projectopenhuman-core.OPENHUMAN_TAURI_SENTRY_TEST=message→ smoke message captured + flushed at startup, projectopenhuman-tauri.Submission Checklist
docs/TESTING-STRATEGY.md— bothtriggerSentryTestEventpaths covered (no-client + tagged capture);beforeSendconsent gate + manual-staging bypass covered;SentryTestRowidle/sending/sent (with + without id) and error state covered.diff-cover app/coverage/lcov.info --compare-branch=origin/main --fail-under=80reports 100% on changed lines (29/29) for bothanalytics.tsandDeveloperOptionsPanel.tsx.## Related— see Related section (no matrix row, see N/A above).docs/TESTING-STRATEGY.md).docs/RELEASE-MANUAL-SMOKE.md).Closes #NNNin the## Relatedsection.Impact
APP_ENVIRONMENT === 'staging'is false), so no production user impact and no production Sentry events from this path.manual-stagingallow-list arm only fires when an explicit button click is made by a user inside the staging app —beforeSendstill strips frames, breadcrumbs, and PII before the event leaves the renderer. No new data classes are sent.SentryTestRowblock inDeveloperOptionsPanel.tsx, the helper + manual-staging branch inanalytics.ts, and the corresponding tests.Related
Closes #1072
Test plan
pnpm compile(tsc --noEmit) passes.pnpm lint src/services/analytics.ts src/components/settings/panels/DeveloperOptionsPanel.tsxclean.pnpm test:unit src/services/__tests__/analytics.test.ts src/components/settings/panels/__tests__/DeveloperOptionsPanel.test.tsxpasses (9 tests across the two suites).pnpm macos:build:debugsucceeds withVITE_OPENHUMAN_APP_ENV=staging; button is visible in the bundled UI.openhuman-reactfiltered bytag:test:manual-stagingwithenvironment:staging. (Verification deferred to post-merge staging build, per the issue's acceptance criteria.)Summary by CodeRabbit
New Features
Tests