Inventory the as-built recaps.cc look and distill it into a durable, vendor-neutral design contract: design/DESIGN.md (nine-section brand brief) + design/tokens.tokens.json (W3C DTCG tokens), plus brand icon and provenance notes. Canonical calls reconciled with the owner: indigo #818cf8 as the single interactive accent, purple #a855f7 for premium only, the #0a0e1a->#111827->#0f172a surface ladder, and a normalized type scale. Wire the AGENTS.md Design line and record the contract-vs-code drift as a cleanup backlog in ROADMAP.md.
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.
processItemInternallycurrently runtime-detects (relay-if-configured / gemini-fallback) because the user's choice lives only in the client'slocalStorage. 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
lintscript inserver/package.json; top-leveltsconfig.jsonexists but the server is pure.js. Decide: add ESLint, adopt JSDoc-driven TS checking, or remove the emptytsconfig.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 extendexpires_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 nokeysat_licensefor/api/account/license-keyto 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 setmax.monthly: 120on 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 inrecap-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 inpublic/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 is structurally
aligned (right surface ladder, accent system, premium-purple, components) but a set of
legacy values survived the reconciliation and now read as off-contract drift. None are
release-blocking; all are mechanical token migrations. design-checker found seven
categories (counts approximate, from grep) across the three styling surfaces — the main
public/index.html <style> block, its SHARE_PAGE_CSS string, and public/auth.html.
Edit all three in lockstep (the SHARE_PAGE_CSS string carries its own copies that a
main-stylesheet grep won't catch). Do NOT do this in the same pass as the contract itself.
- Legacy accent indigo →
#818cf8(~12 hard + ~16 tint).#6366f1/#4f46e5/#4338cafills/borders/hovers andrgba(99,102,241,…)tints →#818cf8/#a5b4fc+rgba(129,140,248,…). Biggest cluster is the activation screen (index.html:1213-1216,:1565,1576,1579) which uses all three demoted values in one component; also inline buttons:6496,7776,8183,.license-block:1593,1601. - Blue
#3b82f6as a primary interactive color → indigo (auth ×4 + index ×8).auth.htmlis the worst offender — its primary button + input focus are blue (auth.html:99,105-117); also wallet/sign-up/grant/password buttons (index.html:5228,5763,8151,8163,7609,12384). Blue stays only for info/status + speaker chips. - Legacy dark backgrounds → ladder (~10).
#0a0e17→#0a0e1a(:1156,1243,8662);#0b1120→#0a0e1a(:1191+ SHARE_PAGE_CSS:11111);#020617→#0f172a(:1562); and the auth sub-palette#121828→#111827,#1f2942→#1e293b(auth.html:47-48,83,index.html:400-401). - Stray heading near-white
#f5f9ff→#f1f5f9(×9; 5 inauth.html:59,63,87,94,96, 4 in index:418,660,6335,6343). - Off-scale font sizes → nearest step (~23).
15→16,24→22,9→10,12.5/11.5→12,10.5→10(e.g.index.html:84,899,907,auth.html:86,110). - Off-scale weights (×3):
650→600(:1008, SHARE_PAGE_CSS:11133),680→700(SHARE_PAGE_CSS:11113). - Off-scale radii → snap (~18).
3→4,5→6,7→6|8,9→8|10,11→10|12(scrollbars, history/queue action buttons, mobile submit/menu, buy inputs). - (stretch) Consolidate inline styles behind CSS custom properties. ~447 inline
style=attrs + duplicated values are why this drift accumulates; a:roottoken block (ordesign/brand/palette.cssgenerated from the tokens) would make the contract enforceable instead of advisory. Big diff — schedule deliberately.
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:3419detects these strings, then forwards them anyway.) - Credit over-spend TOCTOU on licensed installs — N parallel requests pass the
total>0check before any blinddebitOnelands. Make check+debit atomic (reserve up front, refund on failure).index.js:2497-2550vs: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/historyparses every full session file (transcript+summary, MB each) just to list ~8 metadata fields — cache them into_meta.jsonon 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/processgating,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-timesafeFilenameguards the load), but sanitize at write too;PUT /api/history/metaaccepts arbitrary JSON shapes with no schema;index.jsis 4351 lines mixing routing/pipeline/yt-dlp/SSE. - Doc drift (high-value): AGENTS.md credit-gate order omits the "paid cloud user" bypass state (
:77vsindex.js:2464-2472); operator-facingstartos-registry/.../INSTRUCTIONS.md+assets/ABOUT.mdare 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.downloadPodcastAudioalso needs a size/time cap — folds into the P0 SSRF fix. /api/credits/claiminvoice-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
USERinDockerfile) — 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 rootpackage.json(stillyoutube-summarizer-startos).cookies.txtis sensitive plaintext in the repo root and is expiring (/api/healthalready reportsfileExpiring: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; pointpackageRepo/upstreamRepoat a public source repo (currentlyhttps://ten31.xyz, a homepage); choose a source-available license for the wrapper (currentlyProprietary). Softer polish: emptymanifest.docsUrls; verify the multi-tenant cloud actions (enableMultiTenantModeet 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 affectsmake install. - Doc reconciliation (bulk). AGENTS.md directory layout omits ~25 server modules;
docs/guides/relay-client.md:17Authorization header is missing theBearerscheme;index.htmlis stated as~10klines but is 12.5k (AGENTS.md:51); thesafeFilename()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 arcdocs/core-decoupling-plan.md— separating the core summarize pipeline from billing / multi-tenant concernsdocs/per-tenant-subscriptions-plan.md— moving subscription state into the per-user scopedocs/self-serve-purchase-plan.md— buyer flow for Pro/Max and a la carte creditsdocs/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.