Downstream — session handoff (start here to continue)
Written 2026-07-15. Everything a fresh session needs to pick up Downstream (the "stress curves" app — the user's north-star "holy grail"). Full vision: DOWNSTREAM.md. This doc = current state + the exact deploy steps
- open threads.
What Downstream is (one paragraph)
A deferred/maintenance thing isn't a due date — it's a curve (how its cost + stress grow the longer you wait). Downstream triages your list by where each thing is on its curve, leads with relief ("let these drift; do this one"), runs a cost-vs-stress mirror (high-dread/low-cost → just do it; low-dread/ high-cost → the sneaky trap), and, when something crosses the knee, speaks a short nudge — in your own cloned voice — via in-app banner + web push. The maintenance half of the self (DreamPro = the aspiration half).
Build state (2026-07-15)
MERGED to main (#1266): curve model + inference + triage + mirror +
resolve/calibrate + snooze + the curve-timed nudge cron. Migrations 481
(downstream_items) + 482 (downstream_nudges) applied to prod.
OPEN — PR #1267 (branch feat/downstream-quiet-hours), NOT yet merged:
- Public landing page
/downstream+ Features catalog entry + footer link. - Quiet-hours (migration 483
downstream_prefs, browser-tz captured). - Traceability log ("what Downstream has told you") on the dashboard.
- Web push (migration 484
downstream_push_subs;web-pushdep; service workerapps/frontend/public/sw.js; "Turn on nudges" button). - Rename JQ Glasses → Downstream Glasses across the app + admin surfaces.
⚠️ To land #1267 (do these, in order)
- Apply migrations 483 + 484 to prod (
supabase/migrations/483_*.sql,484_*.sql). Both degrade gracefully if skipped, but apply them. - Set VAPID env vars (web push is a no-op until these exist):
- Backend (Railway):
VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY,VAPID_SUBJECT(e.g.mailto:hello@hivejournal.com). - Frontend (Vercel):
NEXT_PUBLIC_VAPID_PUBLIC_KEY(= the same public key). - Generate a pair with
cd apps/backend && npx web-push generate-vapid-keys. (A pair was generated for the user on 2026-07-15 and shared in chat — reuse or regenerate; never commit the private key.)
- Backend (Railway):
- Merge #1267 (
gh pr merge 1267 --squash --delete-branch). - Turn the cron on to feel it:
/dashboard/admin/crons→downstream_nudge(OFF by default). Add a couple of items at/dashboard/downstreamfirst.
File map
- Curve math (deterministic, tested 8/8):
apps/backend/src/services/downstream-curves.ts - Service (inference + CRUD + triage + prefs):
apps/backend/src/services/downstream.ts - Nudge cron (compose + voice + push):
apps/backend/src/services/downstream-nudge.ts - Web push:
apps/backend/src/services/downstream-push.ts+apps/frontend/public/sw.js - Routes:
apps/backend/src/routes/downstream.ts(/api/downstream/*) - App UI:
apps/frontend/src/app/dashboard/downstream/page.tsx - Landing:
apps/frontend/src/app/downstream/page.tsx - Migrations: 481 (items), 482 (nudges), 483 (prefs), 484 (push_subs)
Open threads / next steps
Downstream product:
- Glasses nudge delivery — the glasses app can already
GET /api/downstream/nudges; this is a glasses-app change, backend is ready. - Normative / open-science curves — aggregate the resolve-time calibration
(
resolution_feedback) into per-category crowd curves on the citizen-science infra ("the shape of human procrastination cost"). Personal-vs-normative overlay. - Household/shared curves (JQ Bridge / family).
- Quiet-hours UI — today tz is auto-captured but the quiet window (default 21:00–08:00) isn't user-editable in the UI yet (backend supports it).
- Inference tuning — deadlines for
rampitems are LLM-guessed (taxes came back Oct-15 not Apr-15); fine for v1 (user edits), but a calendar-anchored prior would sharpen it.
Downstream Glasses (the AR hardware layer):
- Rename sweep — DONE (task
75650d60): app surfaces (prior) + now the code and docs.apps/glassesclassJqGlassesApp→DownstreamGlassesApp+ header/session comments;INDEX.md"JQ on glasses" heading → "Downstream Glasses";JQ_SPEECH_MIRROR.mdproduct-name refs. Confirmed clean: the other glasses docs (DREAMPRO_GLASSES_VOICE_PROGRAM.md,AR_GLASSES_LIVING_VOICE.md,OWNED_HARDWARE_HORIZON.md) androutes/glasses.ts/jq-voice.tshad no product-name debt — every "JQ" in them is the assistant voice (/api/jq/voice/*, "JQ forward-voice nudges"), which stays by design. Rule: product name = "Downstream Glasses"; JQ = the assistant voice on them. - Gesture + scene check-ins (task
cee81c72): read the wearer's hand (thumbs/palm/finger-count) + background/setting from a glasses photo as a wordless check-in. - Traceability log (task
429a1306): thedownstream_nudgestable already seeds this ("what did it tell me"). Extend to all in-ear audio when the glasses app lands. - Sophia/Cicero local NLU (task
1bbcdca3): Matt (cicero.sh) — HA edition LIVE at https://nlu.to/ha/ (24MB, 160MB RAM, offline, ms latency). The on-device intent pre-filter for always-listening glasses. Warm WhatsApp intro offered — reach out.
Landmines / conventions (so you don't relearn them)
- ESM jest: run backend tests with
NODE_OPTIONS=--experimental-vm-modules npx jest <name>(thenpm testscript sets it; barenpx jestfalls into a broken CJS path).jest.config.jsignoresTS1378for the dynamic-import mock pattern. gh pr edit --bodyis BLOCKED on this repo (Projects-classic GraphQL error). Set the PR body atgh pr createtime; you can't edit it after.- Migration-gate merge flow: PRs that add tables aren't auto-applied — the
user applies the SQL to prod, then you
gh pr merge --squash --delete-branch. - Crons: every recurring task needs
isCronEnabledguard +recordHeartbeatCRON_REGISTRY(cron-toggles.ts) +CRON_LABELS(system-health.ts). New cost-bearing crons ship OFF by default.downstream_nudgefollows this.
- Frontend preflight:
cd apps/frontend && npm run checkbefore a PR; run the fullnpm run buildwhen adding a[param]dynamic route oruseSearchParams. - Supabase MCP ≠ prod (see CLAUDE.md). Trust the user on "migration applied."