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 from0014. - Realises:
0019§9.1 Stage 2 — interface redesign ("the high-fidelity wireframe → restyle/rebuild the componentised UI onto it"); the0018"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.pngreference renders). - Anchors: new
R-REDESIGN-*requirements (§7), to fold into0002.
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.cssis light-only — noprefers-color-scheme, nodata-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
0014Playwright 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-motionrespected.
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; cream50 #F7F1E6 · 100 #F3ECDF · 200 #EADFC9 · 300 #DED0B4; gold300 #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-numson all scores/counts. (System stacks — no webfont; the studio bundle stays offline.) - Space/radius/elevation: an 8-pt spacing scale,
--rad8–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).
- 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. - The dark flip — app shell & chrome. Redefine semantic tokens to Pro Dark;
restyle
Appshell, top chrome (serif wordmark, view switch, avatar), and the left library rail. The frame goes dark; the grid canvas comes next. - 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 ofReview.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 the0014perf + e2e nets. - 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:
frameViewexposes verdict/rating/ reasons/cluster/dims but no aggregate score, andshoot.FrameVerdictpersists none;item.clustermarks burst membership, not the keeper. Surfacing either touchespkg/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-in0009AI-review aggregate — then addScore/besttoFrameVerdict+frameViewand light up the chip number + crown. - 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.sectsections — Verdict & reasons (verdict buttons with key chips, rating, located reasons; active-verdict text moved to the-inktokens for AA), Tools (faces, focus-peaking, before/after, burst, object-removal), and AI cull scores. The scores rail is wired to the real0009AI-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. - Deferred (data): the CAPTURE / EXIF block. The wireframe's capture rail (ISO ·
shutter · ƒ · focal · faces) is entirely mock — no EXIF is extracted or exposed
(
frameViewhas no capture metadata). Surfacing it needs an EXIF read inpkg/studio/pkg/shoot+frameViewfields + tests, so it joins the same data follow-up as the tile score. Omitted for now rather than shown empty. - 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 theLightboxliterals moved to--scrim/--glasstokens. The immersive full-screen loupe + adjustments + filmstrip are deferred to Phase 2. - 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.
- 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-themevar(--x, #hex)fallbacks dropped, and — the visible one — the native range sliders + checkboxes are themed for the dark ground (dark--linetrack, gold thumb, gold checkbox accent) so the cull-profile panels no longer strand light native controls. (Library/Survey/Lightboxwere done in earlier slices.) - 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 the0017#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). - 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) flipsdata-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/% culledlabels from--faintto--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
0014bounded-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-redesignbranch, 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/prodarkas 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-architecture —
0014already 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 (
0019platform/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
#141118in Pro Dark, light-grey#e7e5e2in Editorial Light. Photos are the hero; no warm cast. The.gridwrapcream 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.