diff --git a/AGENTS.md b/AGENTS.md index 280a14b..1956d10 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -136,19 +136,13 @@ unsure whether a change is contract-affecting, assume it is and check. ## Current state -**Live on the operator's StartOS box** — app **0.2.161** + relay **0.2.126**. Tests: `cd server && npm test` → **144 pass**. +**Live on the operator's StartOS box** — app **0.2.162** + relay **0.2.126**. Tests: `cd server && npm test` → **158 pass**. -**Done & live:** self-serve Pro/Max purchase (Bitcoin inline-Lightning + Zaprite card, prepaid, relay owns tier/expiry), core-decoupling, per-tenant subscriptions, expiry-reminder emails (`POST /api/admin/reminders/run {test_email}`), **opt-in Daily Digest** (0.2.158, `b4fa5d7`): off-by-default daily email of a user's last ~24h of library recaps, each synthesized via `/relay/analyze` (operator-absorbed); `daily-digest.js` scan at `SEND_HOUR=8`, per-user watermark dedup, public tokenized unsubscribe, admin trigger `POST /api/admin/digest/run`; **YouTube `/live/` + `/shorts/` URL support** (0.2.159, `cb961cd`): `extractVideoId` now accepts those forms (was rejecting them as "Invalid YouTube URL"); and **self-contained shareable HTML export** (0.2.160, `621af7c`; first installed in 0.2.161): the Export menu offers a standalone `.html` with the embedded video + expandable timestamped summaries baked in, no account needed (native share sheet on mobile, download on desktop). Plans in `docs/*-plan.md`. +**⚠ Active incident (2026-06-20) — YouTube processing is down.** A relay Gemini analyze call black-holed and permanently jammed the relay's single in-memory hardware FIFO slot, blocking ALL manual + background-subscription jobs (operator reports ~a week of no subscription processing). Root cause is **relay-side** (no `AbortSignal` on the relay's analyze/transcribe `generateContent` calls + no dead-holder release on its queue) — full diagnosis + 2-part fix captured to the standards inbox as `(recap-relay) [bug][P1]`; the fix lands in a separate `../recap-relay` session via `/triage`. Operator is restarting recap-relay to unblock (clears the in-memory jam; **recurs until the fix ships**). +- **Recap-side open question:** confirm whether the week-long *subscription* silence is fully explained by that jam, or whether the background sub-check's Keysat-license entitlement gate (`server/index.js:1400`) is also skipping silently post-core-decoupling — check `https://recaps.cc/api/sub-check-log` (needs sign-in). -**Design system — DONE & live (0.2.161).** The `design/` contract + both conformance-cleanup phases are installed: Phase 1 (canonical `:root` token block; stylesheet + `auth.html` on `var()`; drift fixed) and Phase 2 (var-ified the inline `style=` hexes — 346 + 7 `#475569` — and snapped off-scale fonts/radii to the scale). Verified: 144 tests, both pages serve 200, every `var()` resolves, no off-scale residue. The var-ify scoping rule + the `SHARE_PAGE_*` literal-hex exception now live in **Conventions**; only the Style-Dictionary `palette.css` stretch goal remains (`ROADMAP.md`). +**Just shipped (0.2.162, `890d671`):** relay-client dedup — `providers/relay.js` collapsed to `handleRelayResponse` (pingBalance left separate, different error-recording contract) + `operatorPost`/`operatorGet` (write-throws/read-null split preserved); first fetch-mock harness `server/test/relay.test.js` (+14). Internal-only. -**Only loose end:** the Daily Digest's relay-synthesis + SMTP path can't be exercised off-box, so it's installed but **not yet smoke-tested** — that's operator action #5 below. Everything else (schema/upgrade, scheduler boot, unsubscribe flow) is verified. +**Prior live features** (self-serve Pro/Max purchase, core-decoupling, per-tenant subs, expiry reminders, opt-in Daily Digest, `/live`+`/shorts` URLs, shareable HTML export, design-system token pass) are in the git log + `docs/*-plan.md`. Daily Digest is installed but **not yet smoke-tested off-box** (operator action, `ROADMAP.md`). -**Pending operator actions:** -1. **Verify the mobile can't-scroll-to-top fix on the iPad** — UNVERIFIED in 0.2.157 (iOS-layout-specific, not reproducible off-device); send a screen recording if it persists. Inbox item kept open + annotated. -2. (optional) Rotate the still-live Gemini key in AI Studio, then `rm /Users/macpro/Projects/recap-keyleak-purge-backup.bundle`. -3. Real-world cloud tests: first Bitcoin purchase; enable Zaprite cards (relay "Set Zaprite Connection" + webhook); eyeball a reminder email. -4. If recaps.cc ever gains a CDN/LB hop, set `RECAP_TRUSTED_PROXY_HOPS` or the trial-cap bypass reopens. -5. **Smoke-test the Daily Digest (0.2.158):** (a) `POST /api/admin/digest/run {test_email}` to eyeball the sample render; (b) toggle it on in Settings, add a recap, then `POST /api/admin/digest/run` (no body) to force a real scan — confirms relay synthesis + SMTP send + the unsubscribe link end-to-end. Needs System-SMTP configured. - -**Backlog** in `ROADMAP.md`: eval **P2** known-debt (SSE error-string scrub, credit-debit TOCTOU, multi-tenant gemini-key bypass, `GET /api/history` perf, dependency CVEs, integration tests, doc drift) + **P3** cleanup, and standing decisions (Zaprite recurring, "take Recaps home" broken for relay-tier users, cloud paid-only, no CI lint/type-check). +**Next steps, in order:** (1) operator restart recap-relay → restore service; (2) the recap-relay analyze-hang fix (separate session, `/triage` the inbox item); (3) answer the sub-check question above; (4) deferred refactor-survey items + pending operator actions + P2/P3 backlog all live in `ROADMAP.md`. diff --git a/ROADMAP.md b/ROADMAP.md index e4abdde..4734db5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -13,6 +13,16 @@ Longer-term backlog for Recaps. Near-term in-flight work and known issues live i - **Decide the Max tier-quota default.** The relay code default is `max.monthly: null` (unlimited) → cards render "Unlimited" on a fresh install. The operator set `max.monthly: 120` on their box via the Adjust-Tier-Quotas action (so cards show 120 there). Decide whether a metered number (e.g. 120) should be the shipped default in `recap-relay/server/config.js` — note it also enforces the ceiling, not just the card label. - **Add Gemini 3.5 to model selection.** First have a research agent confirm which stable Gemini model versions are actually available and the correct model id/name before wiring anything. The model list is duplicated server + client (provider config under `server/providers/` + the model picker in `public/index.html`) — add the option in lockstep, like the URL-parser convention. Coordinate with the matching relay-side capture (the relay routes Gemini, so its model list must agree). — captured 2026-06-16 +## Pending operator actions + +Operator to-dos (verification + real-world tests on the live box), not coding work. + +1. **Verify the mobile can't-scroll-to-top fix on the iPad** — UNVERIFIED in 0.2.157 (iOS-layout-specific, not reproducible off-device); send a screen recording if it persists. +2. (optional) Rotate the still-live Gemini key in AI Studio, then `rm /Users/macpro/Projects/recap-keyleak-purge-backup.bundle`. +3. Real-world cloud tests: first Bitcoin purchase; enable Zaprite cards (relay "Set Zaprite Connection" + webhook); eyeball a reminder email. +4. If recaps.cc ever gains a CDN/LB hop, set `RECAP_TRUSTED_PROXY_HOPS` or the trial-cap bypass reopens. +5. **Smoke-test the Daily Digest (0.2.158)** — installed but not yet exercised off-box: (a) `POST /api/admin/digest/run {test_email}` to eyeball the sample render; (b) toggle it on in Settings, add a recap, then `POST /api/admin/digest/run` (no body) to force a real scan — confirms relay synthesis + SMTP send + the unsubscribe link end-to-end. Needs System-SMTP configured. + ## Design-contract conformance cleanup (from the 2026-06-16 `/design` extract) The `design/` contract (`design/DESIGN.md` + `design/tokens.tokens.json`) was extracted diff --git a/startos/versions/index.ts b/startos/versions/index.ts index 76c5fef..c270851 100644 --- a/startos/versions/index.ts +++ b/startos/versions/index.ts @@ -180,8 +180,9 @@ import { v_0_2_158 } from './v0.2.158' import { v_0_2_159 } from './v0.2.159' import { v_0_2_160 } from './v0.2.160' import { v_0_2_161 } from './v0.2.161' +import { v_0_2_162 } from './v0.2.162' export const versionGraph = VersionGraph.of({ - current: v_0_2_161, - other: [v_0_2_160, v_0_2_159, v_0_2_158, v_0_2_157, v_0_2_156, v_0_2_155, v_0_2_154, v_0_2_153, v_0_2_152, v_0_2_151, v_0_2_150, v_0_2_149, v_0_2_148, v_0_2_147, v_0_2_146, v_0_2_145, v_0_2_144, v_0_2_143, v_0_2_142, v_0_2_141, v_0_2_140, v_0_2_139, v_0_2_138, v_0_2_137, v_0_2_136, v_0_2_135, v_0_2_134, v_0_2_133, v_0_2_132, v_0_2_131, v_0_2_130, v_0_2_129, v_0_2_128, v_0_2_127, v_0_2_126, v_0_2_125, v_0_2_124, v_0_2_123, v_0_2_122, v_0_2_121, v_0_2_120, v_0_2_119, v_0_2_118, v_0_2_117, v_0_2_116, v_0_2_115, v_0_2_114, v_0_2_113, v_0_2_112, v_0_2_111, v_0_2_110, v_0_2_109, v_0_2_108, v_0_2_107, v_0_2_106, v_0_2_105, v_0_2_104, v_0_2_103, v_0_2_102, v_0_2_101, v_0_2_100, v_0_2_99, v_0_2_98, v_0_2_97, v_0_2_96, v_0_2_95, v_0_2_94, v_0_2_93, v_0_2_92, v_0_2_91, v_0_2_90, v_0_2_89, v_0_2_88, v_0_2_87, v_0_2_86, v_0_2_85, v_0_2_84, v_0_2_83, v_0_2_82, v_0_2_81, v_0_2_80, v_0_2_79, v_0_2_78, v_0_2_77, v_0_2_76, v_0_2_75, v_0_2_74, v_0_2_73, v_0_2_72, v_0_2_71, v_0_2_70, v_0_2_69, v_0_2_68, v_0_2_67, v_0_2_66, v_0_2_65, v_0_2_64, v_0_2_63, v_0_2_62, v_0_2_61, v_0_2_60, v_0_2_59, v_0_2_58, v_0_2_57, v_0_2_56, v_0_2_55, v_0_2_54, v_0_2_53, v_0_2_52, v_0_2_51, v_0_2_50, v_0_2_49, v_0_2_48, v_0_2_47, v_0_2_46, v_0_2_45, v_0_2_44, v_0_2_43, v_0_2_42, v_0_2_41, v_0_2_40, v_0_2_39, v_0_2_38, v_0_2_37, v_0_2_36, v_0_2_35, v_0_2_34, v_0_2_33, v_0_2_32, v_0_2_31, v_0_2_30, v_0_2_29, v_0_2_28, v_0_2_27, v_0_2_26, v_0_2_25, v_0_2_24, v_0_2_23, v_0_2_22, v_0_2_21, v_0_2_20, v_0_2_19, v_0_2_18, v_0_2_17, v_0_2_16, v_0_2_15, v_0_2_14, v_0_2_13, v_0_2_12, v_0_2_11, v_0_2_10, v_0_2_9, v_0_2_8, v_0_2_7, v_0_2_6, v_0_2_5, v_0_2_4, v_0_2_3, v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_18, v_0_1_17, v_0_1_16, v_0_1_15, v_0_1_14, v_0_1_13, v_0_1_12, v_0_1_11, v_0_1_10, v_0_1_9, v_0_1_8, v_0_1_7, v_0_1_6, v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0], + current: v_0_2_162, + other: [v_0_2_161, v_0_2_160, v_0_2_159, v_0_2_158, v_0_2_157, v_0_2_156, v_0_2_155, v_0_2_154, v_0_2_153, v_0_2_152, v_0_2_151, v_0_2_150, v_0_2_149, v_0_2_148, v_0_2_147, v_0_2_146, v_0_2_145, v_0_2_144, v_0_2_143, v_0_2_142, v_0_2_141, v_0_2_140, v_0_2_139, v_0_2_138, v_0_2_137, v_0_2_136, v_0_2_135, v_0_2_134, v_0_2_133, v_0_2_132, v_0_2_131, v_0_2_130, v_0_2_129, v_0_2_128, v_0_2_127, v_0_2_126, v_0_2_125, v_0_2_124, v_0_2_123, v_0_2_122, v_0_2_121, v_0_2_120, v_0_2_119, v_0_2_118, v_0_2_117, v_0_2_116, v_0_2_115, v_0_2_114, v_0_2_113, v_0_2_112, v_0_2_111, v_0_2_110, v_0_2_109, v_0_2_108, v_0_2_107, v_0_2_106, v_0_2_105, v_0_2_104, v_0_2_103, v_0_2_102, v_0_2_101, v_0_2_100, v_0_2_99, v_0_2_98, v_0_2_97, v_0_2_96, v_0_2_95, v_0_2_94, v_0_2_93, v_0_2_92, v_0_2_91, v_0_2_90, v_0_2_89, v_0_2_88, v_0_2_87, v_0_2_86, v_0_2_85, v_0_2_84, v_0_2_83, v_0_2_82, v_0_2_81, v_0_2_80, v_0_2_79, v_0_2_78, v_0_2_77, v_0_2_76, v_0_2_75, v_0_2_74, v_0_2_73, v_0_2_72, v_0_2_71, v_0_2_70, v_0_2_69, v_0_2_68, v_0_2_67, v_0_2_66, v_0_2_65, v_0_2_64, v_0_2_63, v_0_2_62, v_0_2_61, v_0_2_60, v_0_2_59, v_0_2_58, v_0_2_57, v_0_2_56, v_0_2_55, v_0_2_54, v_0_2_53, v_0_2_52, v_0_2_51, v_0_2_50, v_0_2_49, v_0_2_48, v_0_2_47, v_0_2_46, v_0_2_45, v_0_2_44, v_0_2_43, v_0_2_42, v_0_2_41, v_0_2_40, v_0_2_39, v_0_2_38, v_0_2_37, v_0_2_36, v_0_2_35, v_0_2_34, v_0_2_33, v_0_2_32, v_0_2_31, v_0_2_30, v_0_2_29, v_0_2_28, v_0_2_27, v_0_2_26, v_0_2_25, v_0_2_24, v_0_2_23, v_0_2_22, v_0_2_21, v_0_2_20, v_0_2_19, v_0_2_18, v_0_2_17, v_0_2_16, v_0_2_15, v_0_2_14, v_0_2_13, v_0_2_12, v_0_2_11, v_0_2_10, v_0_2_9, v_0_2_8, v_0_2_7, v_0_2_6, v_0_2_5, v_0_2_4, v_0_2_3, v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_18, v_0_1_17, v_0_1_16, v_0_1_15, v_0_1_14, v_0_1_13, v_0_1_12, v_0_1_11, v_0_1_10, v_0_1_9, v_0_1_8, v_0_1_7, v_0_1_6, v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0], }) diff --git a/startos/versions/v0.2.162.ts b/startos/versions/v0.2.162.ts new file mode 100644 index 0000000..9322db2 --- /dev/null +++ b/startos/versions/v0.2.162.ts @@ -0,0 +1,13 @@ +import { VersionInfo } from '@start9labs/start-sdk' + +export const v_0_2_162 = VersionInfo.of({ + version: '0.2.162:0', + releaseNotes: { + en_US: + 'Internal maintenance: relay-client code cleanup (deduplicated response handling and operator calls) plus new automated tests. No user-facing changes.', + }, + migrations: { + up: async ({ effects }) => {}, + down: async ({ effects }) => {}, + }, +})