State-driven accent HUD, stacked bands, CSS/JS split out of the inline HTML, polished presentational review recap. No behavior changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.1 KiB
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 <main> 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 withdata-state) - Fixed semantic colors:
--ok,--warn,--danger
Typography
- Timer: heavy weight,
font-variant-numeric: tabular-nums. - Evidence times:
ui-monospaceso 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 planningbutton (/planning). - Planning: an intent +
Sharpenband, 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 existingevidencefields). No new backend data is introduced; richer session reflection is M7's job. TheEndbutton (/end) remains.
4. Structure
Split the single inline file into three files under internal/web/static/:
index.html— markup shell only (<head>links the stylesheet and script).app.css— the full visual system (tokens, bands, per-state rules).app.js— the render logic, moved verbatim: samerender()function, same partial-update paths (updateActiveDrift,updatePlanningCoach), same element ids, sameEventSource('/events')and POST endpoints. The only additions are the band markup in the template strings and settingmain.dataset.stateper 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.cssGET /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.gostays 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.cssandGET /app.jseach return200with the correctContent-Type(text/css,text/javascript/application/javascript). Asserted against the router viahttptest, stdlibtestingonly. - Manual visual checklist across the six
data-statevalues: 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.