This app mirrors in-app feedback/bug reports into the shared central Notion database "📥 App Feedback". This document is the canonical contract that both the app code and any triage agent must follow. Keep it in sync across all consuming apps.
- Central database:
📥 App Feedback - Database ID:
c153f1e7-b65d-49d2-a2d8-5e57d1f22b70 - Data source:
collection://5ccf696e-e7bf-42ba-ae23-5e12d4d35b2f - Env var:
NOTION_FEEDBACK_DB_ID(this ID in dev; prod repointed during supervised cutover)
| Property | Type | Notes |
|---|---|---|
| Title | title | {emoji} {title} — 🐛 Bug / 💬 Feedback / ✨ Request |
| App | select | SousIQ, HessFest, CareCover (add one option per new app) |
| Type | select | Bug, Feedback, Request |
| Status | select | New, Triaged, In progress, Done, Won't fix, Duplicate |
| Priority | select | Critical, High, Medium, Low |
| Environment | select | Production, Development |
| App Row ID | rich_text | Postgres UUID/cuid — idempotency key, write-once |
| Page URL | url | page the report was filed from |
| User Email | from auth, if present | |
| Browser | rich_text | userAgent, truncated |
| Screenshot | files | external URLs to the app's public screenshot endpoint |
| Fix Link | url | PR/commit — agent/human writable |
| Submitted | created_time | auto |
| Ticket ID | unique_id (FB) |
auto |
Page body: description paragraph + inline image blocks for screenshots.
- App-owned (never edited by agents): Title, App, App Row ID, Page URL, User Email, Browser, Screenshot, Environment, and the initial Type.
- Agent-writable: Status, Priority, Fix Link, and comments.
New → Triaged → In progress → Done
New | Triaged → Won't fix | Duplicate
Never move a row backwards out of In progress/Done during automated triage.
Same App + near-identical Title within ≤14 days → the newer row is set to Duplicate with a comment linking to the original. Never archive/delete the original.
- The app writes
notion_page_id(SousIQ) /notionPageId(HessFest) immediately after creating a Notion page; a NULL synced-at timestamp is the outbox flag. - The reconciler queries Notion by
App Row IDbefore creating a page, so a crash-after-create window never produces a duplicate. - Sync attempts are capped at 5; the last error is retained; persistent failure surfaces via Sentry/Railway logs — feedback is never silently lost.
App value: HessFest
Env vars (both required to enable the mirror; missing either keeps feedback DB-only —
see notionEnabled in lib/env.ts):
NOTION_API_KEY— Notion integration token (the integration must be shared with the central "📥 App Feedback" database).NOTION_FEEDBACK_DB_ID— the central database ID above (c153f1e7-…). Repointed on Railway during the supervised cutover; dev/staging may use a scratch DB.
Data model (prisma/schema.prisma, model Feedback):
typeenumFeedbackType { BUG, IDEA, OTHER }→ centralType: BUG→Bug, IDEA→Request, OTHER→Feedback.- Outbox columns:
notionPageId,notionSyncedAt(NULL = unsynced, the outbox flag),notionSyncAttempts(capped at 5),notionLastError. AlsonotionUrl(back-link).
Sync path:
- On submit,
lib/feedback/submit.tswrites the row then runs the Notion create out of the request path via Nextafter()(seelib/notion/feedback-sync.ts, which delegates the pure schema mapping tolib/notion/feedback-payload.ts). - Reconciler:
lib/notion/feedback-reconcile.ts, exposed at cron route/api/cron/reconcile-feedback(CRON_SECRET-guarded), invoked every 30 min by the always-on workerscripts/cron.mjs. It queries Notion byApp Row IDbefore creating.
Screenshot endpoint: GET /api/feedback/screenshots/{id}/{index} — public (so Notion's
servers can fetch the external file URLs), raster-only allowlist (image/png, image/jpeg,
image/webp — no SVG), nosniff + locked-down CSP. {index} is 0..2.