Skip to content

0022 — "Pro Dark" studio redesign (Stage 2 interface)

  • Status: DRAFT (2026-07-13) — awaiting review of the slice plan (§6) and the open questions (§8). The design is signed off (the wireframe, §2); this spec turns it into a token system + a component-by-component rework sequence.
  • Depends on: 0014 (the Svelte componentisation — this reworks onto it), the shipped studio (0003), 0012 (global settings — new surface to restyle), 0011 (auth/route shell). Rides the existing e2e/Playwright net from 0014.
  • Realises: 0019 §9.1 Stage 2 — interface redesign ("the high-fidelity wireframe → restyle/rebuild the componentised UI onto it"); the 0018 "Pro Dark" direction (its successor). Folds in the layout/skin UX items the anti-rework rule (§4) parked for the redesign: grid pinch-reflow (0017 #6), shortcut HUD (#10).
  • Design source of truth: docs/development/mockups/prodark/index.html (operable wireframe; grid.png / loupe.png reference renders).
  • Anchors: new R-REDESIGN-* requirements (§7), to fold into 0002.

1. Why

The studio works but looks like scaffolding: a light, utilitarian grey shell (app.css --bg:#f5f4f6) that was never designed, only assembled. 0014 paid down the structural debt (a 1,498-line Review.svelte monolith → 16 components with a perf net); this spec pays down the visual debt on top of it, giving Hailey a tool that reads as considered, professional, and calm to sit inside for a 4,000-frame day.

The direction is settled: "Pro Dark" — aubergine chrome wrapping a cream cull canvas, muted gold as the sole accent, verdict semantics kept separate from that accent. Dark surround cuts eye-strain and colour-contaminates less over long sessions; the warm cream grid keeps the photographs the brightest thing on screen.

2. What we're building onto

The wireframe (§ design source) is the spatial + chromatic target for the two primary surfaces (justified-rows grid + full-aubergine loupe). This spec does not re-open those design decisions; it sequences the code to reach them and extends the same system to the remaining surfaces (Library, Survey, Settings, GlobalSettings, Export, FolderBrowser, Wiper, CompareBurst).

3. The rework surface (measured 2026-07-13)

  • 16 components, ~3,864 lines, post-0014. Heaviest: Loupe (713), GlobalSettings (519), Review (382), Settings (320), Library (297), Toolbar (260).
  • No theme system today. app.css is light-only — no prefers-color-scheme, no data-theme. "Lights-out" is a chrome fade, not a theme. So Pro Dark is a new dark ground, not a toggle of an existing one.
  • Token debt is the lever. Components already route ~130 colours through tokens (--plum ×20, --muted ×25, --line ×29, --gold ×6, verdict tokens, --panel) — redefining those tokens to Pro Dark values reskins a large fraction for free. Against that sit ~90 hardcoded literals (#fff ×35, #eac ×15, #7d3b3b ×6, …) that must be migrated to tokens before the dark flip, or they'll strand as light islands on a dark ground. This migration is the bulk of the manual work.

4. Guardrails

  • Anti-rework rule (0019, agreed 2026-07-10) holds. The Stage-1 behavioural UX already shipped (lights-out, bulk rating, throughput HUD, survey, wiper, before/after, explainable-cull overlays, justified-rows default) — the redesign restyles, never rebuilds these. No behaviour change lands in this spec.
  • Justified-rows stays the default (decision 2026-07-12); masonry stays in-engine, unsurfaced. The redesign owns pinch-reflow (#6) and the final skin only.
  • The perf net is load-bearing. Every slice runs green against the 0014 Playwright bounded-DOM / jank-budget trace over 4,000 frames and the existing e2e suite. Virtualisation is not to regress under any restyle.
  • Non-destructive + WASM guarantees untouched — this is presentation only; no engine, no pkg/ change, no pixels written.
  • Accessibility: every text/ground pair meets WCAG AA (4.5:1 body, 3:1 large); gold-on-aubergine and verdict-on-dark are contrast-checked; keyboard focus keeps a visible ring on the dark ground; prefers-reduced-motion respected.

5. The design system (to codify in app.css + a tokens module)

Semantic, two-layer tokens so the sibling Editorial Light theme (§8-Q1) is a token redefinition, not a component rewrite. Primitive ramp → semantic aliases; components consume only semantic aliases.

  • Colour — primitives: aubergine 900 #241823 · 800 #2E2130 · 700 #392A3B · 650 #412B44 · 600 #4A2F4C · 500 #5C3E5E; cream 50 #F7F1E6 · 100 #F3ECDF · 200 #EADFC9 · 300 #DED0B4; gold 300 #E4C877 · 400 #D3B057 · 500 #C6A24A · 600 #A8842F; verdicts keep #5AA06E / maybe #C6923A / reject #C25C5C.
  • Colour — semantic aliases (what components use): --ground (chrome), --canvas (the cull grid surface — cream), --surface / --surface-raised, --text / --text-dim / --text-faint (aubergine-biased warm greys, chosen not default grey), --accent (= gold, the only accent), --line, plus the existing --keep/--maybe/--reject(+ -soft) kept separate from --accent.
  • Type: display/brand = serif stack ("Iowan Old Style",Palatino,Georgia,ui-serif) for the wordmark + view titles; UI = the current system sans; data = tabular-nums on all scores/counts. (System stacks — no webfont; the studio bundle stays offline.)
  • Space/radius/elevation: an 8-pt spacing scale, --rad 8–11px, two elevation shadows for raised panels on the dark ground.

6. Slice plan

Each slice is independently shippable, green against the perf + e2e nets, and leaves the app coherent (no half-dark states shipped).

  1. Tokens + literal migration (foundation). Add the two-layer token system to app.css; migrate the ~90 hardcoded literals in components to semantic aliases. Still light at this slice — pure refactor, zero visual change — so it lands behind the net with no risk. This is the enabler for everything below.
  2. The dark flip — app shell & chrome. Redefine semantic tokens to Pro Dark; restyle App shell, top chrome (serif wordmark, view switch, avatar), and the left library rail. The frame goes dark; the grid canvas comes next.
  3. The cull canvas — grid. Grid/Tile/Review: cream canvas, score chip + star pips, verdict stripe, selection ring, burst marker, justified spacing to match the wireframe. The heart of the tool. (IMPLEMENTED 2026-07-16.) Landed: cream-canvas semantic tokens (literals promoted out of Review.svelte); tiles on dark contact-sheet grounds with a left-edge verdict stripe, a rating-pip chip, a burst marker, and gold selection (crisp ring = current frame, ring + wash = multi-select batch); 9-px gaps/radius. Green against the 0014 perf + e2e nets.
  4. Deferred to a follow-up "tile score" slice (needs data, not presentation). The wireframe's numeric 0–100 score chip and the best-of-burst crown both need a per-frame field the frames API does not carry: frameView exposes verdict/rating/ reasons/cluster/dims but no aggregate score, and shoot.FrameVerdict persists none; item.cluster marks burst membership, not the keeper. Surfacing either touches pkg/shoot + pkg/studio (schema + handler + tests), so it sits outside slice 3's presentation-only scope (R-REDESIGN-6). Until then the chip shows rating pips only (hidden when unrated) and the burst marker means "part of a burst". The follow-up must first decide the score's source — persisted cull score vs transient aesthetic (0..1) vs the opt-in 0009 AI-review aggregate — then add Score/best to FrameVerdict+frameView and light up the chip number + crown.
  5. The inspector rail. ReviewPanel/FacePanels: metadata, AI cull scores rail (mini-bars, tabular), verdict/reasons block, keyboard-key affordances. (IMPLEMENTED 2026-07-16.) Landed: the inspector (Loupe .lp) is now a stack of eyebrow-headed .sect sections — Verdict & reasons (verdict buttons with key chips, rating, located reasons; active-verdict text moved to the -ink tokens for AA), Tools (faces, focus-peaking, before/after, burst, object-removal), and AI cull scores. The scores rail is wired to the real 0009 AI-review dimensions (per-dimension mini-bars, score/10 + label, viability headline) rather than a stub — the coaching prose is preserved under each bar (no feature regression), and the on-demand trigger stays (privacy/cost gate, R-PRIV-2). Nets green.
  6. Deferred (data): the CAPTURE / EXIF block. The wireframe's capture rail (ISO · shutter · ƒ · focal · faces) is entirely mock — no EXIF is extracted or exposed (frameView has no capture metadata). Surfacing it needs an EXIF read in pkg/studio/pkg/shoot + frameView fields + tests, so it joins the same data follow-up as the tile score. Omitted for now rather than shown empty.
  7. The loupe. Loupe (the 713-line surface): full-aubergine ground, adjustments rail, colour-coded filmstrip, focus-peaking + face-box overlays restyled onto the system. Largest single slice — may sub-split (chrome → adjustments → filmstrip). (PARTIAL 2026-07-16.) Scope reality: the wireframe loupe's headline — the adjustments rail (Exposure/Contrast/Highlights/Shadows/Temp/Tint) and the colour-coded filmstrip — is interactive tonal develop, a Phase-2 feature not built in the studio (develop is CLI-only today), so it can't be restyled yet; it lands with Phase 2. What shipped now: the loupe's enlarged preview and every overlay laid over it (reason pins, face boxes, focus-peaking) now take the frame's real aspect (current.width/height) instead of a hardcoded 3:2 — so portraits show whole and the pins align, matching the grid/survey portrait fix; and the Lightbox literals moved to --scrim / --glass tokens. The immersive full-screen loupe + adjustments + filmstrip are deferred to Phase 2.
  8. The finish bar + throughput HUD. Verdict tally, cull progress, Export/Write-XMP actions; fold the HUD into the system. (IMPLEMENTED 2026-07-16.) The finish bar now carries a verdict tally (keep/maybe/reject swatches + counts), a stacked keep/maybe/reject composition bar, and a "% culled" reject-rate readout, with the throughput HUD and the Export/Write-XMP actions folded in. Adapted from the wireframe's "% culled = share decided": every frame carries a cull verdict here, so the three counts already sum to the whole shoot — the bar shows composition and the readout the reject rate. Existing session data only; the Export tier select is kept.
  9. Secondary surfaces. Library, Survey, Settings, GlobalSettings, FolderBrowser, CompareBurst, Wiper, Lightbox — restyle onto the system. (IMPLEMENTED 2026-07-16.) Most already cascaded dark from the slice-2 flip; this pass closed the gaps: the modal backdrop literal (×4 dialogs) → --scrim-modal, dead light-theme var(--x, #hex) fallbacks dropped, and — the visible one — the native range sliders + checkboxes are themed for the dark ground (dark --line track, gold thumb, gold checkbox accent) so the cull-profile panels no longer strand light native controls. (Library/Survey/Lightbox were done in earlier slices.)
  10. Redesign-owned UX. Pinch-reflow (0017 #6) and the shortcut HUD (#10), which the anti-rework rule parked for the redesign. (IMPLEMENTED 2026-07-16.) A grid size control (a toolbar slider, persisted like the layout mode) scales the layout's nominal row/cell size and reflows justified and uniform — the reflow the 0017 #6 pinch called for, driven by a slider that also works under touch. A shortcut HUD (ShortcutHelp.svelte) replaces the one-line keys toast: a grouped, two-column overlay opened from the toolbar Keys button or the ? key, closed on ×/backdrop/Esc. Follow-up: a two-finger pinch gesture as an alternate input to the size slider (the reflow plumbing is in place; only the gesture handler is deferred, to avoid perturbing the virtualised scroll under touch).
  11. Editorial Light sibling theme. (IMPLEMENTED 2026-07-16.) The two-layer token system's payoff: Editorial Light is a redefinition of the semantic aliases under :root[data-theme="light"] (warm paper grounds, deep-aubergine ink, the same gold accent + verdict semantics retuned) — components untouched. A header toggle (persisted) flips data-theme; the always-dark surfaces (image backdrop, scrims, msg toast, the contact-sheet tiles + score chip) are inherited unchanged. Both grounds verified at WCAG AA (the check also lifted the eyebrow/% culled labels from --faint to --muted, fixing a latent dark-theme miss too).

7. Requirements (fold into 0002)

  • R-REDESIGN-1 Components consume only semantic tokens; no hardcoded colour literals remain in src/*.svelte (lint/grep-gate).
  • R-REDESIGN-2 The accent hue (gold) is never used to carry verdict meaning; keep/ maybe/reject are a distinct semantic set.
  • R-REDESIGN-3 Justified-rows remains the shipped default; masonry stays engine-only and unsurfaced.
  • R-REDESIGN-4 Every restyle slice passes the 0014 bounded-DOM + jank-budget Playwright trace over 4,000 frames and the existing e2e suite (no perf regression).
  • R-REDESIGN-5 All text/ground pairs meet WCAG AA; focus is always visible on the dark ground.
  • R-REDESIGN-6 No behaviour change: the Stage-1 behavioural UX is restyled, not rebuilt; no pkg//engine change; non-destructive + WASM guarantees hold.

8. Decisions (RESOLVED 2026-07-14)

  • Q1 — light theme: RESOLVED → Pro Dark first, Editorial Light as a fast-follow. Build the two-layer token system now so a light theme is a cheap later redefinition, but ship Pro-Dark-only first (slice 9 deferred to a follow-up pass).
  • Q2 — landing strategy: RESOLVED → one stacked feat/prodark-redesign branch, merged once. All slices build on this branch; a single MR at the end. Slices stay individually coherent/green internally, but land together.
  • Q3 — serif: RESOLVED → system serif stack now ("Iowan Old Style",Palatino, Georgia,ui-serif). Offline, zero bundle cost. A licensed brand serif is a possible later polish slice.
  • Q4 — RESOLVED → freeze docs/development/mockups/prodark as the v1 target; fork a new mockup if the direction moves.

9. Non-goals

  • No new features / behaviours — presentation only (see R-REDESIGN-6).
  • Not a component re-architecture0014 already did that; this reskins it.
  • No engine / pkg/ / provider changes, no schema, no XMP/export change.
  • Not the native/Wails shell, not the hosted UI (0019 platform/hosted tracks) — those are downstream and out of scope.

10. Palette revision — tri-model design review (2026-07-16)

After the slice plan shipped, a three-way design critique (GPT-5.5 via codex, Gemini 3 Flash, and Claude) unanimously flagged the original palette as subtly dating the UI, with one functional finding: the warm cream cull canvas biases white-balance judgement (a photographer reads photos as cooler against a warm ground — pro tools use a neutral surround). Revision applied (all token changes; the two-layer system made it cheap):

  • Cull canvas: cream → a NEUTRAL content well — near-black #141118 in Pro Dark, light-grey #e7e5e2 in Editorial Light. Photos are the hero; no warm cast. The .gridwrap cream re-scope and the --canvas-ink/muted/… tokens are retired (the well now uses the root theme aliases).
  • Aubergine chrome desaturated (bg #241823#1c1822, panel→#262029, …) — a restrained identity, deliberately not full neutral charcoal (the deliberate divergence from the two models, to keep krites ownable).
  • Accent gold: brass #d3b057 → refined champagne #c7ac6c.
  • Tile score numbers quieted so faces read first.

The re-review confirmed the pivot ("the palette is no longer the dated part"). Both models' remaining ask — a structural flatten of the chrome (fewer pills/ borders, smaller/consistent radii, ghost secondary buttons, lighter tile treatment) + crisper Editorial-Light chrome — is the follow-up pass.