Phase 1 of the design-contract conformance cleanup. Add a canonical
:root token block (single source of truth, mirroring
design/tokens.tokens.json) to public/index.html's stylesheet and migrate
the whole <style> block to var(--token); give public/auth.html its own
subset :root and migrate it too.
Fix all color + weight drift across every surface (stylesheet, 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 surface ladder
- #f5f9ff -> #f1f5f9, #312e81 -> #1e293b, weights 650->600 / 680->700
The meta theme-color stays a literal #0a0e1a. Verified: 144 tests pass,
both pages serve 200, all var() references resolve. Phase 2 (var-ifying
the long-tail inline styles, snapping off-scale font/radius) is in
ROADMAP.md.
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.
Multi-mode, off by default. Each new recap is synthesized into a 1-2
paragraph overview via the relay (operator-absorbed) and cached onto the
session JSON; a daily 08:00 scan emails opted-in users their fresh
recaps, deduped by a per-user watermark that never skips a failed or
over-cap recap. One-click tokenized unsubscribe; settings-modal toggle;
admin test trigger. Bumps to 0.2.158.
Four fixes in public/index.html, all reported against recaps.cc on mobile:
- Video minimize no longer shows a black frame on expand. toggleVideoMinimize()
used to call render(), rebuilding the YouTube iframe inside the display:none
minimized container, which wedged the IFrame API. Minimize now toggles the
.results-left.minimized class in place; a !videoMinimized guard on render()'s
needsMount plus a new ensureYtMounted() (called from the expand paths) keep the
player from ever being created in a hidden container.
- Background processing no longer interrupts podcast audio or resets the
transcript scroll. The ~60s relay-credit poll calls render(), which rebuilt the
<audio> element and chunks-scroll. render() now preserves the live <audio> node
across the innerHTML swap (replaceWith when the src matches) and restores
chunks-scroll scrollTop; initPodcastPlayer() is idempotent so the preserved node
doesn't get duplicate listeners.
- Removed the redundant centered "Processing..." box; the staged pizza-tracker
breadcrumb already covers that window.
- Added -webkit-overflow-scrolling/overscroll-behavior to .chunks-scroll for the
mobile can't-scroll-to-top report (best-effort, needs on-device verification).
Ships as 0.2.157. Reviewer pass clean; inline JS syntax checked with node --check.
iPad users hit a spurious "network error" on the first tap of
"Send sign-in link", with a second tap succeeding. Cause is iOS
Safari dispatching the POST onto a pooled keep-alive socket the
server/proxy already closed; unlike a GET it isn't transparently
re-sent, so it surfaces as a transport TypeError. The single 500ms
auto-retry was too quick and reused the same dead socket.
Both sign-in entry points (auth.html postWithRetry, index.html
fetchWithRetry) now retry 3x with growing backoff (0 -> +400ms ->
+1.6s) to outlast Safari evicting the socket. Frontend-only.
Ships as 0.2.156.
Refresh AGENTS.md conventions (client IP via req.ip; safeFilename exported),
rewrite Current state to a lean snapshot, move the P2 known-debt detail to
ROADMAP.md.
- Arbitrary file write (P0): validate import keys in /api/library/import via
a now-exported safeFilename(); a ../../ key is skipped, not written out of
the scope dir.
- SSRF (P0): guard downloadPodcastAudio — reject non-HTTP(S) schemes, block
IP-literal and DNS-resolved private/link-local/loopback/reserved/multicast
and embedded-IPv4 IPv6 targets (closes DNS rebinding), cap + resolve redirects.
- ESM require (P1): top-level import of randomBytes in license-purchase.js
(the inner require threw on the anon purchase-settle path).
- Concurrency lock (P1): skip the process-global free-tier slot in multi-mode
so it no longer serializes every cloud tenant onto one job.
- X-Forwarded-For bypass (P1): set Express trust proxy from
RECAP_TRUSTED_PROXY_HOPS (default 1); getClientIp now reads req.ip instead
of a client-spoofable XFF entry.
Tests added for safeFilename, the SSRF guard, and getClientIp (119 pass).
Registry blockers deferred (ROADMAP); leaked-key history purge queued.
Cross-repo git-hygiene audit remediation: surface ~/Projects/standards/INBOX.md items at session start, and switch .gitignore to the deny-by-default .claude/* block (shared wiring allow-listed) plus the canonical secrets/env lines — per standards/portability.md.
- Replace the Commands-table Lint/Type-check TODOs with the real, verified
commands: `npm run check` (tsc --noEmit over startos/) and `npm run prettier`.
There is no ESLint/linter; server/ JS is untooled.
- Move the client-side relay contract (env vars, /relay/* endpoints, X-Recap-*
headers, file map) out of AGENTS.md into docs/guides/relay-client.md with
paths: frontmatter, lazy-loaded via a .claude/rules symlink; AGENTS.md keeps
a one-line pointer.
- Un-ignore .claude/rules/ so the guide auto-attaches in any clone, while
.claude/ local state (worktrees, plans) stays ignored.
Add the /relay/* endpoints the app actually calls that were omitted (capabilities, policy, tts, jobs/:id, credits/*); fix the Files attribution (add relay-capabilities.js + credits-purchase.js; the /relay/policy proxy lives in index.js only).
- Add Client-side contract with the relay sub-section: env vars
(RECAP_RELAY_BASE_URL, RECAP_RELAY_OPERATOR_KEY ↔ relay_cloud_operator_key),
auth direction the client SENDS, the 12 /relay/* endpoints the consumer
actually calls (verified against providers/relay.js + billing-routes.js +
subscription-reminders.js).
- Drop two relay-internal references now canonical in ../recap-relay/AGENTS.md:
the extendUserTier function name and the Adjacent-repo bullet's
"Private; ships via make install only" sentence.