Files
recap/ROADMAP.md
T
Keysat 82e544af47 Var-ify inline styles and snap off-scale type/radii (design Phase 2)
Phase 2 of the design-contract cleanup:
- 346 inline-style hexes (+7 #475569, mapped by property) -> var(--token),
  scoped to CSS-value position so JS-logic/quoted hex, the meta theme-color,
  SVG attrs, and the no-:root share-export region stay literal; #fff and
  no-token hexes left as-is.
- Snap off-scale font sizes (9/10.5->10, 11.5/12.5->12, 15->16, 24->22) and
  radii (3->4, 5->6, 7->6, 11->12, 9->8|10) to the scale.
- Bump to 0.2.161, which also ships the previously-uninstalled 0.2.160
  share-page HTML export.
2026-06-17 08:22:48 -05:00

13 KiB

ROADMAP

Longer-term backlog for Recaps. Near-term in-flight work and known issues live in AGENTS.md under Current state.

Near-term backlog

  • Persist provider preference server-side. processItemInternally currently runtime-detects (relay-if-configured / gemini-fallback) because the user's choice lives only in the client's localStorage. Persist it so a fresh-container rebuild or any non-browser caller (cron, background processor) picks the right path. Probably a single key in the StartOS config blob + a small migration to seed it from the first authenticated client.
  • Apply Export ▾ menu to the clip-collection panel. The main view and history rows already have it; the clip collection still has the single legacy "Export PDF" button. Reuse the existing menu component.
  • CI lint + type-check. No lint script in server/package.json; top-level tsconfig.json exists but the server is pure .js. Decide: add ESLint, adopt JSDoc-driven TS checking, or remove the empty tsconfig.json.
  • Surface failed auto-queue items in the dashboard. Currently hidden by default behind a "Show all" toggle. Worth a small banner / count chip when failures exist so operators notice without hunting.
  • Zaprite recurring card billing (BLOCKED on Zaprite). Grant wants card payments to DEFAULT to recurring (buyer can opt out at checkout). Zaprite's public API (api.zaprite.com/openapi.json) only creates one-time /v1/orders — recurring is a hosted/dashboard feature with no per-buyer metadata, no renewal webhook, and no billing-portal URL via API. The shipped card rail is one-time prepaid. UNBLOCK by confirming with Zaprite support whether the account can: (a) attach a per-buyer reference/metadata to a recurring checkout (so a payment maps to a Recaps user), (b) fire a webhook on each renewal charge (so we extend the tier each period), (c) expose a customer/billing-portal URL (for the chosen "link to Zaprite portal" cancel path). Decisions already made: no reminder emails for auto-renewing cards; a failed charge = lapse at period end (the relay's expiry-enforcement already does this — a missed renewal just doesn't extend expires_at).
  • Close the architecture-simplification gaps (docs/architecture-simplification-plan.md). After core-decoupling + self-serve, these steps remain OPEN: (8) "Take Recaps home" — mint a fresh Keysat token on demand at click time; likely BROKEN today because relay-tier cloud users have no keysat_license for /api/account/license-key to return. (10) cloud paid-only — the free signed-in tier + signup-grant credits are still live; the plan wanted cloud to be paid-only with self-hosted as the free path (product call — confirm intent before building). (5, partial) anon signup→Pro still routes through /api/license/purchase + pending_signups (Keysat license) instead of the relay tier like the signed-in flow does. (6, partial) tokenized renew — the reminder email's renew link is ?renew=1 (requires sign-in); the plan wanted a one-time-token /renew?token=… for friction-free renewal. NOTE: the doc's Zaprite-recurring / cancel-button / Recaps-DB-owns-expiry parts were intentionally SUPERSEDED by the prepaid + relay-owns-tier model — don't build those.
  • 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

Design-contract conformance cleanup (from the 2026-06-16 /design extract)

The design/ contract (design/DESIGN.md + design/tokens.tokens.json) was extracted from the as-built UI and reconciled with Grant on 2026-06-16. The code was structurally aligned but a set of legacy values had survived as off-contract drift.

Phase 1 — DONE 2026-06-16 (not yet deployed to the box). Introduced a canonical :root token block (the single source of truth, mirroring tokens.tokens.json) at the top of the public/index.html <style> block and migrated the whole stylesheet to var(--token); public/auth.html got its own subset :root and was migrated too. Fixed all color + weight drift across every surface (stylesheet, ~447 inline styles, JS handlers, the SHARE_PAGE_CSS export): legacy indigos #6366f1/#4f46e5/#4338ca + rgba(99,102,241,…)#818cf8/#a5b4fc/rgba(129,140,248,…); blue #3b82f6 interactive buttons (incl. the whole auth screen) → indigo; legacy darks #0a0e17/#0b1120/#020617/#121828/#1f2942 → the ladder; #f5f9ff#f1f5f9; #312e81#1e293b; weights 650→600, 680→700. Verified: 144 tests pass, both pages serve 200, all 426+27 var() references resolve, no undefined vars. (SHARE_PAGE_CSS and auth.html are standalone documents that each carry their own copy — kept in sync; the meta theme-color stays a literal #0a0e1a.)

Phase 2 — DONE 2026-06-17 (shipped in app 0.2.161).

  • Var-ified the long-tail inline style= attributes — 346 inline-style hexes (+7 #475569, mapped by property to --text-faint/--border-strong) → var(--token). Scoped to CSS-value position (hex preceded by :/space/,, never a quote), which cleanly dodged the non-var() spots: the <meta theme-color>, SVG fill/stroke attrs, and hex held in JS logic (quoted ternary branches like ${cond ? "#1e293b" : ...}, const colour = …). Left as literals on purpose: #fff (its uses split between on-accent button text and functional white — --on-accent doesn't cleanly cover both, zero visual gain), no-token hexes (#e0e7ff/#c7d2fe/#a78bfa/#04210f/etc.), and the entire SHARE_PAGE_* export region (a standalone doc with no :rootvar() wouldn't resolve there).
  • Snapped off-scale font sizes (21 occ): 9/10.5→10, 11.5/12.5→12, 15→16, 24→22. Left 40px/56px display glyphs (success numeral, buy spinner) — off-scale by design.
  • Snapped off-scale radii (18 occ): 3→4, 5→6, 7→6, 11→12; 9→10 for the two 18px capsules (.menu-badge, .rc-spk — radius clamps at 9 on an 18px box, so on-scale and visually identical) and 9→8 for .icon-btn/.buy-select-btn/.buy-discount-input. Left the 1px hamburger-bar radius. Verified: 144 tests pass, both pages serve 200, every introduced var() resolves against :root, no off-scale residue.
  • (stretch, NOT done) Generate design/brand/palette.css from the tokens (Style Dictionary) and @import/inline it, so the :root block isn't hand-maintained in three places. Still open.

Known debt (P2, from the 2026-06-14 full-eval — EVALUATION.md)

Real but not release-blocking for self-host. The P0/P1 findings from the same eval were fixed 2026-06-15 (see git log + EVALUATION.md).

  • Operator-internal strings leak to cloud users at the SSE error boundary (Parakeet/Gemma/CUDA/LAN IPs) — no scrub exists, violating the scrub convention in AGENTS.md. server/index.js:3432,3003,4246 + providers/relay.js:135-144. (Sharp edge: index.js:3419 detects these strings, then forwards them anyway.)
  • Credit over-spend TOCTOU on licensed installs — N parallel requests pass the total>0 check before any blind debitOne lands. Make check+debit atomic (reserve up front, refund on failure). index.js:2497-2550 vs :3158,:4197.
  • Multi-mode tenant can spend the operator's server Gemini key via transcriptionProvider:"gemini" + empty key (bypasses relay metering) — providers/index.js:104-114. Refuse the operator-key fallback for non-admin tenants.
  • GET /api/history parses every full session file (transcript+summary, MB each) just to list ~8 metadata fields — cache them into _meta.json on save. history.js:418-437.
  • Dependency CVEs — nodemailer 6.10.1 (high; low practical reach here), ws/qs/express/protobufjs (moderate). npm audit fix (nodemailer is a major bump).
  • No tests on the riskiest files (/api/process gating, relay.js, tenant-auth.js, billing) — the real summarize→save→debit path can't run end-to-end without a key/credits. Add an integration test as the regression net.
  • Smaller hardening: unsanitized IDs persisted to _meta.json (array-form library import + PUT /api/history/move) — no file-path escape (read-time safeFilename guards the load), but sanitize at write too; PUT /api/history/meta accepts arbitrary JSON shapes with no schema; index.js is 4351 lines mixing routing/pipeline/yt-dlp/SSE.
  • Doc drift (high-value): AGENTS.md credit-gate order omits the "paid cloud user" bypass state (:77 vs index.js:2464-2472); operator-facing startos-registry/.../INSTRUCTIONS.md + assets/ABOUT.md are stale Gemini-first (relay is the default provider).

Deferred hardening & cleanup (P3, from the 2026-06-14 full-eval — EVALUATION.md)

Low-severity; batch when convenient. None block release. (P0/P1 work queue and P2 known debt live in AGENTS.md → Current state.)

  • Request-size / fetch caps. express.json({limit:"100mb"}) on every route (server/index.js:203) is a cheap memory-exhaustion lever; tighten it. downloadPodcastAudio also needs a size/time cap — folds into the P0 SSRF fix.
  • /api/credits/claim invoice-ID hijack. A leaked anon BTCPay invoice ID is claimable by any signed-in account (server/credits-purchase.js:342); bind claims to the buyer's email. Random IDs keep this low-risk.
  • Container runs as root (no USER in Dockerfile) — acceptable under StartOS isolation; add a non-root user for the cloud image.
  • In-memory auth rate-limit buckets reset on restart (server/auth-routes.js:106) — fine for self-host single-operator; note for cloud HA.
  • Repo hygiene. Delete the stale youtube-summarizer_x86_64.s9pk (~223MB, old package ID) and rename the root package.json (still youtube-summarizer-startos). cookies.txt is sensitive plaintext in the repo root and is expiring (/api/health already reports fileExpiring:true) — gitignored, but rotate/move it.
  • StartOS community-registry submission — deferred (decision 2026-06-15: self-host + cloud only for now). Hard blockers if/when we submit: add a root instructions.md; point packageRepo/upstreamRepo at a public source repo (currently https://ten31.xyz, a homepage); choose a source-available license for the wrapper (currently Proprietary). Softer polish: empty manifest.docsUrls; verify the multi-tenant cloud actions (enableMultiTenantMode et al., startos/actions/index.ts) run cleanly — not stack-trace — in single mode; 172 empty version-file migration stubs are a growing maintenance surface. None of this affects make install.
  • Doc reconciliation (bulk). AGENTS.md directory layout omits ~25 server modules; docs/guides/relay-client.md:17 Authorization header is missing the Bearer scheme; index.html is stated as ~10k lines but is 12.5k (AGENTS.md:51); the safeFilename() convention (AGENTS.md:79) becomes accurate once the function is exported (the P0 fix).

Larger plans (already drafted in docs/)

  • docs/architecture-simplification-plan.md — broader simplification arc
  • docs/core-decoupling-plan.md — separating the core summarize pipeline from billing / multi-tenant concerns
  • docs/per-tenant-subscriptions-plan.md — moving subscription state into the per-user scope
  • docs/self-serve-purchase-plan.md — buyer flow for Pro/Max and a la carte credits
  • docs/path-2b-and-path-1-interweave.md — sequencing for the multi-tenant cloud meetings work (depends on the relay's Path 2A)

Treat the docs/ plans as the source of truth for those items; cross-reference rather than restating here.

Adjacent (lives in ../recap-relay)

The relay now has its own AGENTS.md + ROADMAP.md — track relay work there; this is just what the client surfaces or waits on.

  • Speaker MERGE + re-run detection + re-polish — SHIPPED relay-side (operator dashboard, live on the box at relay 0.2.124, 2026-06-13). Merge folds two clusters into one; re-run re-clusters at a new strictness to split over-merged speakers; re-polish rewrites topic summaries to corrected names. App-side UI for these is now unblocked if wanted. (The relay tree is at 0.2.124 but uncommitted to git — see ../recap-relay/ROADMAP.md.)
  • Cross-call speaker fingerprint memory (recognize the same voice across meetings) — not yet shipped.
  • Phase 3 of Path 2A: multiple operator-editable meeting prompt sets (1on1 / all-hands / customer-interview / standup) selectable per upload — not yet shipped.

Avoid building app-side UI for the unshipped items until the relay-side pieces land.