# ROADMAP — Proof of Work Longer-term backlog. Near-term state + next steps live in `AGENTS.md` → Current state. ## Known bugs - **Mobile-Safari first-login-tap fails ("An unexpected error occurred"); second tap works.** Reproduced on iPhone/iPad Safari against 1.2.0:5 (desktop Safari untested — user declined). The first Sign In tap fails, a second manual tap succeeds. **1.2.0:2's `retryOnTransportError` does NOT fix it.** Diagnosis so far: `LoginForm` only surfaces that error when *both* the initial action call and its in-tap retry throw, so the immediate retry isn't escaping the bad connection — only a fresh user-initiated tap does. Box app logs show no server-side error/500/reset around the attempt, so it's a transport-layer failure, not an app bug. - **Gating data (do this first):** capture the first failed request's error in Safari Web Inspector (iOS→Mac, Network/Console tab). The code picks the fix: - `-1005` "The network connection was lost" → client-side stale keep-alive socket. Fix = a *delayed* retry (let Safari tear down the dead socket before retrying), not the current instant one. - `502`/`503` → StartOS-proxy↔Node keep-alive mismatch (Node closing idle conns the proxy reuses). Fix = raise Node `keepAliveTimeout`/`headersTimeout` server-side; a client retry only masks it. - Files: `lib/retryAction.ts`, `app/auth/login/LoginForm.tsx`, `app/auth/signup/SignupForm.tsx`. ## AI quality - Tiered prompt formatting (also the immediate next step): JSON-Schema output enforcement via Ollama `format` and OpenAI `response_format`; pipe-separated library rows; XML-tagged prompt sections; Ollama-only few-shot example; stable prefix first for prompt-cache hits. - Keep `MODEL_MENU` / `PRICES` current as providers ship new models. ## Security & hardening (from 2026-06-13 full-eval; full detail + file:line in `EVALUATION.md`) - **Still open — verify on the box:** whether the StartOS proxy forwards real client IPs to the app. The rate limiter now keys on the rightmost (trusted-proxy) `X-Forwarded-For` entry; if the proxy instead makes every client look like one IP, the per-IP cap collapses to a single global bucket. Confirm with live headers. - P3 hardening batch (remaining): CSP `unsafe-eval` vs comment, `/api/health` info disclosure, rate-limit map leak, configurable/shorter sessions (currently 30-day), no text max-length. Also unify the 3rd JSON-parse pattern in `programs/[id]/days/[dayId]/start` (`try{json}catch{→{}}`). Done in 1.2.0:1–:3: Next 14→15 / React 18→19 bump (1.2.0:1, closed RSC DoS / WS-upgrade SSRF / App Router XSS + middleware-bypass CVEs); iOS-Safari login first-tap retry (1.2.0:2); login timing oracle closed + `exerciseId` ownership enforced on all workout-write & program routes (1.2.0:3). Done in 1.1.0:9 (P2 batch): input-validation 500s → 400 (`lib/http.ts readJsonBody` + explicit guards); `POST /api/auth` rate-limited; XFF anti-spoof; container drops root via su-exec. ## Packaging / distribution - Diagnose and fix the `publish.sh` Step-3 registry-register silent no-op. - Build for `arm` / additional arches once StartOS 0.4 supports them on the host. - Consider submission to the Start9 community registry (use the start9-spec-checker agent first). Blockers found 2026-06-13: non-SPDX `"Proprietary"` license, missing `instructions.md`, 404 `packageRepo`/`upstreamRepo` URLs, stale "0.3.5 data snapshot" install alert + long description; plus warnings (PNG vs SVG icon, migration-era README, no `.github/workflows`, generic `docsUrls`, Node 20 vs 22). ## Product - Adherence tracking: compare logged workouts against the planned `ProgramDay` (the `programDayId` link already exists). - Per-user export/import polish and scheduled backups. - CSV export↔import round-trip: export writes `setX`-prefixed headers (`setCalories`/`setWatts`/`setNotes`) the importer doesn't read (it expects `calories`/`watts`/`notes`), so the app's own CSV export silently drops those on re-import (calories long-standing; watts since 1.2.0:4). Fix by aligning export header names with the parser, or adding the prefixed names as `knownColumns` aliases. (JSON account export/import round-trips fine.) - Charts/progress views over history (the data and 1RM estimates already exist). ## Design Cleanup backlog from the 2026-06-19 design-contract extract (`design/DESIGN.md` + `tokens.tokens.json`). The contract documents the *intended* system; these are the spots where the code diverges. All cosmetic/mechanical — none ship-blocking. Found by the `design-checker` agent (re-run it for fresh file:line lists). - **Unify off-palette neutrals → zinc** (21 hits, 12 files): every `hover:bg-gray-100`/`active:bg-gray-200`/`hover:text-gray-200` on the white primary button → `zinc-200`/`zinc-300`. Canonical hover is `zinc-200` (`color.action.primary-hover-bg`). Worst: `app/main/workouts/page.tsx` (4), auth `LoginForm`/`SignupForm`, `dashboard/page.tsx`, `programs/page.tsx`. - **Unify success hue → emerald** (10 hits): `green-*` → `emerald-*`. Worst: `components/settings/SettingsForm.tsx` (6), `WorkoutForm.tsx:696`, `SetRow.tsx:477`, `ExerciseCard.tsx:12`. - **Unify warning hue → amber** (13 hits): `yellow-*` → `amber-*`. Worst: `app/main/import/page-csv.tsx` (6), `SettingsForm.tsx` (6), `ExerciseCard.tsx:14`. - **No solid red button** (1): `components/settings/DangerZone.tsx:101` uses `bg-red-700` — convert to the ghost/wash destructive treatment (`text-red-400 border-red-800 hover:bg-red-950/30`). (Red elsewhere — 65 wash/outline/text uses — already conforms.) - **Radius two-tier (4px control / 8px container)**: `rounded-md` (6px, 27 hits, mostly `SetRow.tsx` ×16 + `WorkoutForm.tsx` ×9) → `rounded`; `rounded-lg` on primary *buttons* (5 hits in `workouts/page.tsx`, `dashboard/page.tsx`) → `rounded`. (`rounded-full` on the FAB is fine.) - **Shadows are overlay-only**: drop `shadow-2xl` on the static login/signup cards (`app/auth/{login,signup}/page.tsx:23`) and `shadow-lg` on the FAB + desktop CTA (`workouts/page.tsx:163,174`) — depth = bg layering + border. - **Sub-scale font sizes** (10 hits): `text-[10px]`/`text-[11px]` (below the 12px `text-xs` floor) → `text-xs`. - **Extract a shared `