# M4 — "Look good" Design **Goal:** A real design pass on the web UI: a cockpit-style, state-aware HUD that reads at a glance, with CSS/JS split out of the inline HTML for maintainability, and a polished review recap. No behavior changes. **Status:** Design approved 2026-05-31. Supersedes the utilitarian inline UI shipped through M3.5. --- ## 1. Direction & Visual System The UI is an **instrument panel you glance at** — a cockpit, not a document. Dark, near-black cool-neutral base. State is carried by a **single state-driven accent**: a CSS custom property `--accent` switched by a `data-state` attribute on the `
` element. The accent colors the status band's top border and the state pill, so the frame itself communicates where you are without reading text. ### State → accent mapping | State | `data-state` | Accent | |--------------------|--------------|--------------------| | locked | `locked` | dim gray | | planning | `planning` | blue | | active · on-task | `active` | calm green / cyan | | active · nudge | `nudge` | amber | | active · drifting | `drift` | red | | review | `review` | violet-neutral | `data-state` is derived in the client render from `runtime_state` plus, when active, the drift sub-status (`drifting` → `drift`; a present `nudge` → `nudge`; otherwise `active`). This mirrors the precedence already used by the status-file renderer (drift outranks nudge). ### Design tokens (CSS custom properties) - Surfaces / text: `--bg`, `--panel`, `--line`, `--ink`, `--ink-dim` - State accent: `--accent` (the only variable that changes with `data-state`) - Fixed semantic colors: `--ok`, `--warn`, `--danger` ### Typography - Timer: heavy weight, `font-variant-numeric: tabular-nums`. - Evidence times: `ui-monospace` so the bucket columns align. - Band headers / pills: small, uppercase, letter-spaced (keeps the existing pill idiom from the current UI). - Prose: `system-ui`. ## 2. Layout — Stacked HUD Bands Every state composes the same **band primitive**: a row with a top divider and consistent horizontal/vertical padding. Stacking bands produces the layered HUD look. The active session follows the approved sketch: ``` ACTIVE · on task · 7 switches ← status band (accent border-top + pill) 24:18 write the spec section ← timer band done when: draft saved ← task band now code·spec ● | code 18:02 … ← evidence band [ Complete ] ← action band ``` Drift and nudge are **not** a separate floating box. When the session drifts or is nudged, the **status band itself** changes copy and `data-state` flips, so the whole frame goes amber/red. The same controls render inside that band: - Drift: `Back to task` (`/refocus`), `This is on task` (`/ontask`), `End session` (`/complete`). - Nudge: `Dismiss` (client-only, current behavior). - Pending: a quiet "checking focus…" line. ## 3. Per-State Treatment - **Locked:** one dim band, large `Start planning` button (`/planning`). - **Planning:** an intent + `Sharpen` band, then field bands — Next action, Success condition, Minutes, Allowed apps — with the blue accent. All existing input ids (`#intent`, `#na`, `#sc`, `#mins`, `#apps`, `#start`, `#coachStatus`) and the coach pre-fill behavior are untouched. - **Active:** the HUD described in §2. - **Review (polished, presentational only):** summary bands built from data the state already carries — `next_action`, `success_condition`, the context-switch count, and the per-window bucket recap (reusing the existing `evidence` fields). **No new backend data** is introduced; richer session reflection is M7's job. The `End` button (`/end`) remains. ## 4. Structure Split the single inline file into three files under `internal/web/static/`: - `index.html` — markup shell only (`` links the stylesheet and script). - `app.css` — the full visual system (tokens, bands, per-state rules). - `app.js` — the render logic, **moved verbatim**: same `render()` function, same partial-update paths (`updateActiveDrift`, `updatePlanningCoach`), same element ids, same `EventSource('/events')` and POST endpoints. The only additions are the band markup in the template strings and setting `main.dataset.state` per render. `web.go` currently serves only `/` via `c.FileFromFS`. Add routes so the two new assets are served from the embedded `staticFS`: - `GET /app.css` → `static/app.css` - `GET /app.js` → `static/app.js` No new Go dependencies, no JavaScript build step, no framework. The embedded static directory and the conciseness/token-efficiency ethos of the Go rewrite are preserved. ## 5. Behavior & Data Flow — Unchanged Same SSE stream, same partial-update `render()` logic, same element ids, same POST endpoints, same server-authoritative expiry timer. The redesign is markup + CSS + asset routing only. This is precisely what keeps the existing `web_test.go` (endpoint and state-JSON assertions, markup-agnostic) green. ## 6. Testing - **Existing `web_test.go` stays green.** It asserts on endpoint status codes and the state JSON, not on HTML markup, so the visual rework does not touch it. - **New Go test:** `GET /app.css` and `GET /app.js` each return `200` with the correct `Content-Type` (`text/css`, `text/javascript` / `application/javascript`). Asserted against the router via `httptest`, stdlib `testing` only. - **Manual visual checklist** across the six `data-state` values: locked, planning, active (on-task), active (nudge), active (drift), review. There is no JavaScript test harness in this Go project; the rendering is presentational and verified by eye, consistent with the existing approach. ## 7. Out of Scope - Micro-interactions / motion (timer easing, accent transitions, panel slide-in) — explicitly excluded for M4; can be a later pass. - Any new backend data or fields on the state payload. - M7 reflection content (real session summary, time-on-task analytics). The M4 review screen is presentational recap of already-available data only.