Skip to content

feat: feedback + observability flow — PostHog dataset isolation, /support, Sentry correlation, real-ingestion e2e - #116

Open
BSalaeddin wants to merge 3 commits into
devfrom
feat/feedback-observability
Open

feat: feedback + observability flow — PostHog dataset isolation, /support, Sentry correlation, real-ingestion e2e#116
BSalaeddin wants to merge 3 commits into
devfrom
feat/feedback-observability

Conversation

@BSalaeddin

Copy link
Copy Markdown
Collaborator

Feedback + observability flow (/feedback-observability run, 2026-07-21)

Implements the standing feedback+observability prompt, self-scoped to what caramel actually has: Sentry + usesend email + Better Auth + sonner + withRoute + Playwright/Vitest, and wires PostHog (org self-hosted, posthog.devino.ca) — closing the "PostHog dark on dev" punch-list item from the 2026-07-16 compose cutover. Mastra / CopilotKit / RevenueCat / Turnstile sections don't apply (not in this app).

What landed (3 commits)

39f97f9 PostHog foundation

  • POSTHOG_DATASET=production|e2e|disabled runtime isolation (server env + NEXT_PUBLIC_POSTHOG_DATASET mirror, boot fail-fast on mismatch). The Playwright-only query READ key is banned from app env — env.ts fail-fasts if it appears in any mode.
  • posthog-js@1.405.3 + posthog-node@5.46.0 (exact-pinned). Browser provider in providers.tsx: pageviews, session replay (inputs masked, same selectors as Sentry replay), super props app_id:'caramel' / app_version / environment / platform:'web'; identify = Better Auth user id (never email), reset on logout. Server capture via captureServerEvent (posthog-node captureImmediate, never throws, returns checked boolean).
  • Sentry↔PostHog correlation: posthog_session_id/posthog_distinct_id in Sentry context (high-cardinality), app_id as tag (low-cardinality).
  • reportUserVisibleFailure(): exact per-call sentry_event_id (never global last-event) + user_visible_operation_failed PostHog event + per-fingerprint session rate limit.
  • Dockerfile ARGs + compose build args/runtime env for all new vars.
  • E2E hook: window.__CARAMEL_E2E__ handshake registers test_run_id/test_scenario super props + synthetic identity; opt_out_useragent_filter only in e2e dataset (prod bot filtering untouched).

ca1de2f Support flow

  • Public /support page (login problems reportable without auth) + POST /api/support via withRoute (mutation rate-limit, origin gate, zod, honeypot website field, length caps).
  • One feedback_id UUID per submission, client-generated, reused on retry, shared verbatim across the PostHog support_request_submitted event and the support email (subject + body). Logged-in users' email comes from the session (server-side override; never re-asked).
  • Email via existing sendEmail (extended with replyTo + real text): customer email is Reply-To, never sender; body carries feedback id, type, message, expected outcome, app/route/version/environment/platform, user id, posthog ids, sentry_event_id when present. Destination: SUPPORT_EMAIL_TO (default support@unotes.net, same inbox sites/suggest already mails).
  • Partial-failure honesty matrix (both destinations attempted independently): ok/ok · analytics-failed/email-ok · analytics-ok/email-failed (honest toast) · both-failed → 502, input preserved, retry with same feedback_id. Double-submit prevented.
  • Error-feedback prompt: non-blocking sonner toast ("Tell us what happened") → native <dialog> support form carrying the exact sentry_event_id; wired to the two genuine blocked-action sites (coupons load failure, suggestion submit failure); once per fingerprint per session.
  • No PostHog survey exists for this app → deliberate support_request_submitted event capture; POSTHOG_SUPPORT_SURVEY_ID noted in code as the future swap-in (no invented IDs).

10175be E2E + CI

  • e2e/support/posthog.ts: read-key HogQL query helper against shared project #Shared_Apps_E2E_Testing (id 25), bounded polling (no fixed sleeps), key never logged.
  • e2e/support-flow.spec.ts (4 tests): public render · anonymous submit verified by a real ingested PostHog row (feedback_id + app_id + environment:e2e + test_run_id matched — a 200 from /api/support is NOT accepted as proof) · browser $identify with synthetic e2e:caramel:<run>:<scenario> identity verified in project 25 · double-submit/retry feedback_id reuse (route-stubbed, ungated).
  • checks-app.yml e2e-pr: dataset=e2e env + 4 repo secrets (set + verified via gh secret list). The app process never sees the read key (blanked in playwright.config.ts webServer.env). e2e-push wiring deliberately deferred (TODO in workflow) until this merges + dev redeploys.

Verification evidence

  • Real PostHog ingestion: support + identify events queried back from project 25 via the read-only key (ingestion lag observed up to ~343s; poll ceilings sized accordingly). Full e2e run: 4/4 passed, twice.
  • Real email delivery: manual run with SUPPORT_EMAIL_TO=aladdin@devino.ca → route returned {ok:true, email:'ok'} → message confirmed in the Gmail inbox (subject [Caramel support] problem — 41c7c0d6-…, matching feedback_id, Reply-To=customer). No real customers emailed; e2e specs submit without wants_reply.
  • Gates (re-run on the combined branch by the coordinating session): tsc ✅ · vitest 438/438 ✅ · oxlint ✅ · prettier ✅ · knip ✅ (zero new ignores). Note: root pnpm lint (eslint) exits 1 on origin/dev too — 2 pre-existing no-console warnings in caramel-extension, unrelated to this PR.

Deploy/env state (dev only; prod untouched per gate)

  • Dokploy dev compose env staged 2026-07-21 with POSTHOG_DATASET=e2e + shared-project capture pair (pre-launch dataset rule: no real users yet → dev captures into the shared test project, not a prod project). Inert until this merges; first dev deploy after merge lights PostHog up.
  • Launch flip (prod, human-run): POSTHOG_DATASET=production + NEXT_PUBLIC_POSTHOG_HOST/KEY (prod capture token) + redeploy. Read key never goes into any deployment env.

Manual dashboard follow-ups (PostHog UI)

  • Optionally create a support survey → set POSTHOG_SUPPORT_SURVEY_ID (today the flow captures support_request_submitted).
  • Dashboards/insights for support_request_submitted, user_visible_operation_failed, thumbs-equivalents when an AI chat surface exists.

Known deliberate leftovers

  • e2e-push PostHog wiring: TODO in checks-app.yml (enable after merge+deploy).
  • In disabled dataset the matrix reports analytics:'failed' (cosmetic — capture is deliberately off there; prod/e2e datasets unaffected).
  • Extension surface (MV3 popup/content) intentionally out of scope this pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YKAWGPo8K34KZ1epqXE7WV

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant