P3-0: Design system and screen designs — BLOCKS all UI work #51

Open
opened 2026-09-10 18:23:38 +00:00 by Cordy · 2 comments
Owner

Do this first in phase 3. It blocks #32, #33, #34 and #49.

Why this exists

Four separate issues produce UI. In a subagent-driven run those are four fresh sessions with no
shared visual reference, and the result is four dialects: mismatched padding, different button
hierarchies, inconsistent empty-state voice. The product looks assembled rather than designed,
and fixing it afterwards means touching every screen.

The fix is a single stylesheet plus a living style guide page, built once here and consumed
by every later UI task. Later issues then style nothing from scratch — they use the classes.

Deliverables

  • frontend/design-system.css — every token and component, light and dark.
  • frontend/design-system.html — a living style guide rendering every component in every
    state. Open it, see the whole vocabulary. This is the artifact later tasks read.
  • frontend/icons/ — the mark and tray icons as SVG (see "The signature" below).
  • Screen designs as static HTML using only the above: setup wizard, main window, conflict view,
    settings.

Tokens

Cairn's existing palette. Do not invent a new one — the client and the web UI must read as one
product.

:root {
  --snow: #f6f7f5;        --surface: #ffffff;
  --slate-900: #23272b;   --slate-700: #3d444b;   --slate-400: #8a939c;
  --lichen: #e8ebe4;
  --glacier: #2f7f8f;     --glacier-deep: #235f6b;
  --danger: #a8543f;      --ok: #4a7a4e;
}
html[data-theme="dark"] {
  --snow: #191c1f;        --surface: #23272b;
  --slate-900: #e4e7e3;   --slate-700: #c6ccc5;   --slate-400: #838d96;
  --lichen: #353c43;
  --glacier: #4da3b3;     --glacier-deep: #6dbccb;
  --danger: #c97a63;      --ok: #6d9a70;
}

Follow the OS theme by default; allow an override in settings.

Typography — the one deliberate idea

Two faces, and the split carries meaning.

--sans: -apple-system, "Segoe UI Variable Text", system-ui, sans-serif;
--mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;
  • Sans for prose — labels, descriptions, buttons. Use the platform's UI font rather than a
    brand face. A desktop utility should feel native on each OS; a custom webfont here reads as a
    website pretending to be an app.
  • Mono for everything that is data — file paths, byte sizes, counts, timestamps, ETags,
    licence keys, percentages.

That second rule is the typographic personality of this product. Cairn's entire argument is
that your files are real objects on a real disk, not rows in someone's database. Setting every
path and size in monospace makes the interface say that on every screen, at no cost. It is
already the convention in Cairn's web UI and CLI — this makes it deliberate.

Scale (13px base — desktop density, not web): 11 · 12 · 13 · 15 · 20 · 26.
Weights: 400 and 600 only. Never 700; it reads heavy against native UI chrome.

Spacing, shape, elevation

  • Space: 4px base — 4 · 8 · 12 · 16 · 24 · 32 · 48. Nothing between.
  • Radius: 6px controls, 10px cards, 2px mark bars.
  • Borders: 1px --lichen. This is the primary separator.
  • Shadows: none, except a modal dialog. Flat surfaces, borders do the work.

The signature: the mark is the status indicator

The Cairn mark is three stacked rounded bars of increasing width — a cairn, stones marking a
path. It is currently used at 20px in one header and nowhere else. Make it the sync status
indicator
, and it becomes both the brand and the most-looked-at element in the product.

 ▬▬        8×4
▬▬▬▬      14×4      3px gaps, 2px radius
▬▬▬▬▬▬    20×4
State Treatment
Up to date All three bars solid --slate-900
Syncing Bars illuminate bottom→top in sequence, ~1.2s loop, in --glacier
Needs attention Top bar --danger, lower two --slate-400
Paused All three at 40% opacity
Offline All three --slate-400, hairline outline

One idea, five places: the tray icon, the window header glyph, the app icon, the installer
artwork, the favicon. Almost no other product's logo is also its spinner — it is genuinely
ownable, and it costs nothing because the shape is three rectangles.

Motion: only the mark animates. Nothing else in this application moves. Under
prefers-reduced-motion, the syncing state becomes a static --glacier tint instead of a loop.

This resolves the icon-asset problem

The mark is pure geometry — rounded rectangles — so every icon can be generated as SVG in this
repo
, with no external design tool:

  • Tray icons: 5 states × light/dark, 16×16 and 32×32 SVG.
  • App icon: rounded square --slate-900 with the mark in --snow, as SVG.
  • A build step converting SVG → .ico (Windows), .icns (macOS), PNG set (Linux).

That last step unblocks packaging (#37, #38, #39), which otherwise stalls waiting for artwork.

Component inventory

Build each, in every state, into the style guide:

Buttons — primary (glacier fill), secondary (border), ghost (text), danger. States: rest,
hover, active, focus-visible, disabled, loading.
Form — text input, password, select, checkbox, toggle, folder picker. Plus a managed
variant: read-only with a "Managed by your organisation" note, for policy-set values (#30).
Data display — list row (icon + primary + mono secondary + action), card, badge, progress
bar (determinate and indeterminate), storage meter.
Feedback — inline alert (info/warning/danger/ok), modal dialog, toast, skeleton row.
Navigation — settings sub-nav (vertical list, left side), tab row.

Every list gets three extra states: empty, loading, error. They are the ones that get
forgotten and the ones users hit on their worst day.

Screens to design

  1. Setup wizard (#33) — server URL, credentials with the OIDC app-password branch, folder
    choice, confirmation.
  2. Main window (#34), 720×520 default, 560×420 minimum — header with the mark glyph and
    status line, activity list, "needs attention" section, storage footer.
  3. Conflict view (#34) — both versions side by side with size and time, three actions.
  4. Settings — vertical sub-nav: Account · Folder · Network · Appearance · Advanced. Never
    make the user scroll to find a setting.

Voice

Sentence case everywhere. No exclamation marks. Plain verbs. An action keeps its name through
the whole flow — "Free up space" produces "Space freed", not "Operation complete". Errors say
what happened and what to do. Empty states are an invitation, never a shrug.

Steps

  • Write design-system.css with tokens, type scale and every component.
  • Build design-system.html showing all of it, both themes, all states.
  • Generate the mark in all five states as SVG; build the icon conversion step.
  • Lay out the four screens as static HTML using only design-system classes.
  • Review at 100%, 125% and 150% OS scaling, and at the 560×420 minimum.
  • Verify contrast ≥ 4.5:1 for body text in both themes.
  • Commit: git commit -s -m "feat(frontend): design system, mark states and screen designs"

Acceptance criteria

  • Every component exists in the style guide in every state, both themes.
  • The four screens are laid out using only design-system classes — zero bespoke CSS.
  • Tray and app icons generated from the mark, converted for all three platforms.
  • Later UI issues can be implemented without inventing a single visual decision.
**Do this first in phase 3. It blocks #32, #33, #34 and #49.** ## Why this exists Four separate issues produce UI. In a subagent-driven run those are four fresh sessions with no shared visual reference, and the result is four dialects: mismatched padding, different button hierarchies, inconsistent empty-state voice. The product looks assembled rather than designed, and fixing it afterwards means touching every screen. The fix is a **single stylesheet plus a living style guide page**, built once here and consumed by every later UI task. Later issues then style nothing from scratch — they use the classes. ## Deliverables - `frontend/design-system.css` — every token and component, light and dark. - `frontend/design-system.html` — a living style guide rendering every component in every state. Open it, see the whole vocabulary. This is the artifact later tasks read. - `frontend/icons/` — the mark and tray icons as SVG (see "The signature" below). - Screen designs as static HTML using only the above: setup wizard, main window, conflict view, settings. ## Tokens Cairn's existing palette. Do not invent a new one — the client and the web UI must read as one product. ```css :root { --snow: #f6f7f5; --surface: #ffffff; --slate-900: #23272b; --slate-700: #3d444b; --slate-400: #8a939c; --lichen: #e8ebe4; --glacier: #2f7f8f; --glacier-deep: #235f6b; --danger: #a8543f; --ok: #4a7a4e; } html[data-theme="dark"] { --snow: #191c1f; --surface: #23272b; --slate-900: #e4e7e3; --slate-700: #c6ccc5; --slate-400: #838d96; --lichen: #353c43; --glacier: #4da3b3; --glacier-deep: #6dbccb; --danger: #c97a63; --ok: #6d9a70; } ``` Follow the OS theme by default; allow an override in settings. ## Typography — the one deliberate idea **Two faces, and the split carries meaning.** ```css --sans: -apple-system, "Segoe UI Variable Text", system-ui, sans-serif; --mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace; ``` - **Sans for prose** — labels, descriptions, buttons. Use the *platform's* UI font rather than a brand face. A desktop utility should feel native on each OS; a custom webfont here reads as a website pretending to be an app. - **Mono for everything that is data** — file paths, byte sizes, counts, timestamps, ETags, licence keys, percentages. That second rule is the typographic personality of this product. Cairn's entire argument is that your files are real objects on a real disk, not rows in someone's database. Setting every path and size in monospace makes the interface say that on every screen, at no cost. It is already the convention in Cairn's web UI and CLI — this makes it deliberate. **Scale** (13px base — desktop density, not web): `11 · 12 · 13 · 15 · 20 · 26`. **Weights: 400 and 600 only.** Never 700; it reads heavy against native UI chrome. ## Spacing, shape, elevation - **Space:** 4px base — `4 · 8 · 12 · 16 · 24 · 32 · 48`. Nothing between. - **Radius:** `6px` controls, `10px` cards, `2px` mark bars. - **Borders:** 1px `--lichen`. This is the primary separator. - **Shadows:** none, except a modal dialog. Flat surfaces, borders do the work. ## The signature: the mark *is* the status indicator The Cairn mark is three stacked rounded bars of increasing width — a cairn, stones marking a path. It is currently used at 20px in one header and nowhere else. **Make it the sync status indicator**, and it becomes both the brand and the most-looked-at element in the product. ``` ▬▬ 8×4 ▬▬▬▬ 14×4 3px gaps, 2px radius ▬▬▬▬▬▬ 20×4 ``` | State | Treatment | |---|---| | Up to date | All three bars solid `--slate-900` | | Syncing | Bars illuminate bottom→top in sequence, ~1.2s loop, in `--glacier` | | Needs attention | Top bar `--danger`, lower two `--slate-400` | | Paused | All three at 40% opacity | | Offline | All three `--slate-400`, hairline outline | One idea, five places: the tray icon, the window header glyph, the app icon, the installer artwork, the favicon. Almost no other product's logo is also its spinner — it is genuinely ownable, and it costs nothing because the shape is three rectangles. **Motion:** only the mark animates. Nothing else in this application moves. Under `prefers-reduced-motion`, the syncing state becomes a static `--glacier` tint instead of a loop. ### This resolves the icon-asset problem The mark is pure geometry — rounded rectangles — so **every icon can be generated as SVG in this repo**, with no external design tool: - [ ] Tray icons: 5 states × light/dark, 16×16 and 32×32 SVG. - [ ] App icon: rounded square `--slate-900` with the mark in `--snow`, as SVG. - [ ] A build step converting SVG → `.ico` (Windows), `.icns` (macOS), PNG set (Linux). That last step unblocks packaging (#37, #38, #39), which otherwise stalls waiting for artwork. ## Component inventory Build each, in every state, into the style guide: **Buttons** — primary (glacier fill), secondary (border), ghost (text), danger. States: rest, hover, active, focus-visible, disabled, loading. **Form** — text input, password, select, checkbox, toggle, folder picker. Plus a **managed** variant: read-only with a "Managed by your organisation" note, for policy-set values (#30). **Data display** — list row (icon + primary + mono secondary + action), card, badge, progress bar (determinate and indeterminate), storage meter. **Feedback** — inline alert (info/warning/danger/ok), modal dialog, toast, skeleton row. **Navigation** — settings sub-nav (vertical list, left side), tab row. **Every list gets three extra states: empty, loading, error.** They are the ones that get forgotten and the ones users hit on their worst day. ## Screens to design 1. **Setup wizard** (#33) — server URL, credentials with the OIDC app-password branch, folder choice, confirmation. 2. **Main window** (#34), 720×520 default, 560×420 minimum — header with the mark glyph and status line, activity list, "needs attention" section, storage footer. 3. **Conflict view** (#34) — both versions side by side with size and time, three actions. 4. **Settings** — vertical sub-nav: Account · Folder · Network · Appearance · Advanced. Never make the user scroll to find a setting. ## Voice Sentence case everywhere. No exclamation marks. Plain verbs. An action keeps its name through the whole flow — "Free up space" produces "Space freed", not "Operation complete". Errors say what happened and what to do. Empty states are an invitation, never a shrug. ## Steps - [ ] Write `design-system.css` with tokens, type scale and every component. - [ ] Build `design-system.html` showing all of it, both themes, all states. - [ ] Generate the mark in all five states as SVG; build the icon conversion step. - [ ] Lay out the four screens as static HTML using only design-system classes. - [ ] Review at 100%, 125% and 150% OS scaling, and at the 560×420 minimum. - [ ] Verify contrast ≥ 4.5:1 for body text in both themes. - [ ] Commit: `git commit -s -m "feat(frontend): design system, mark states and screen designs"` ## Acceptance criteria - Every component exists in the style guide in every state, both themes. - The four screens are laid out using only design-system classes — **zero bespoke CSS**. - Tray and app icons generated from the mark, converted for all three platforms. - Later UI issues can be implemented without inventing a single visual decision.
Author
Owner

Visual target committed

docs/mockup-main-window.html — open it in a browser. It is an approved mockup of the main
window plus the five mark states, in both themes.

It is a target, not the deliverable. What it settles, so you do not have to re-decide:

  • The tokens in use, and how the dark theme flips them.
  • Sans for prose, mono for every path, size and timestamp. Look at how much of the interface
    turns out to be data rather than prose — that split is the product's visual argument.
  • The mark as status indicator, all five states, including the bottom-to-top illumination for
    syncing and the prefers-reduced-motion fallback.
  • Information order: "Needs attention" sits above "Recent activity". The thing that
    requires the user is never below the thing that does not.
  • Conflict copy leads with reassurance — "Both versions saved" before the ask. A conflict is
    the moment a user fears they have lost work; answer that first.
  • Row anatomy: glyph · prose · mono metadata · action.

Extract the CSS in that file into frontend/design-system.css and grow it into the full
component inventory. The mockup uses placeholder glyphs (HTML entities) where the real build
needs proper icons — replace those, keep everything else.

The other three screens — setup wizard, conflict view, settings — still need designing against
this vocabulary.

## Visual target committed `docs/mockup-main-window.html` — open it in a browser. It is an **approved** mockup of the main window plus the five mark states, in both themes. It is a target, not the deliverable. What it settles, so you do not have to re-decide: - The **tokens** in use, and how the dark theme flips them. - **Sans for prose, mono for every path, size and timestamp.** Look at how much of the interface turns out to be data rather than prose — that split is the product's visual argument. - **The mark as status indicator**, all five states, including the bottom-to-top illumination for syncing and the `prefers-reduced-motion` fallback. - **Information order**: "Needs attention" sits *above* "Recent activity". The thing that requires the user is never below the thing that does not. - **Conflict copy leads with reassurance** — "Both versions saved" before the ask. A conflict is the moment a user fears they have lost work; answer that first. - Row anatomy: glyph · prose · mono metadata · action. Extract the CSS in that file into `frontend/design-system.css` and grow it into the full component inventory. The mockup uses placeholder glyphs (HTML entities) where the real build needs proper icons — replace those, keep everything else. The other three screens — setup wizard, conflict view, settings — still need designing against this vocabulary.
Author
Owner

Amendment — 2026-09-14: phase-3 pre-flight rulings for #51

These rulings come from the phase-3 pre-flight survey. The owner approved posting them and may veto any of them. They bind this issue.

  • Icon build (P3-R8). The SVG → PNG / .ico / .icns step is a pure-Go program under build/icons/ (package main, CGO_ENABLED=0). The mark is plain geometry, so the program draws the rectangles directly, with no SVG rasteriser, and writes the PNG set, .ico and .icns. It runs on the Pi runner and on macOS. It never lives under internal/sync.
  • Five mark states (P3-R1). The states are Up to date, Syncing, Needs attention, Paused and Offline. #32's tray uses exactly these five, under these names; its four-state list (idle/syncing/error/paused) is superseded.
  • Inventory additions (P3-R9). Beyond the list above, the design system and the style guide must also cover:
    • a Privacy section in the settings sub-nav (Account · Folder · Network · Appearance · Privacy · Advanced), for #35;
    • a high-friction destructive confirmation modal that states the number of files affected, for #34's empty-folder remedy;
    • a language selector, for #41;
    • the managed, read-only "set by your organisation" variant of every form control, for #30.
  • Owner sign-off (D5). Only the main window has prior approval. The owner reviews the style guide, the wizard, the conflict view, the settings screens, and the tray icons rendered at 16 px and 32 px before #51 closes. The code may land on main before that review.

Posted by Claude on behalf of @Cordy: phase-3 pre-flight, owner-approved process; the owner may veto any point.

## Amendment — 2026-09-14: phase-3 pre-flight rulings for #51 These rulings come from the phase-3 pre-flight survey. The owner approved posting them and may veto any of them. They bind this issue. - **Icon build (P3-R8).** The SVG → PNG / `.ico` / `.icns` step is a pure-Go program under `build/icons/` (`package main`, `CGO_ENABLED=0`). The mark is plain geometry, so the program draws the rectangles directly, with no SVG rasteriser, and writes the PNG set, `.ico` and `.icns`. It runs on the Pi runner and on macOS. It never lives under `internal/sync`. - **Five mark states (P3-R1).** The states are Up to date, Syncing, Needs attention, Paused and Offline. #32's tray uses exactly these five, under these names; its four-state list (idle/syncing/error/paused) is superseded. - **Inventory additions (P3-R9).** Beyond the list above, the design system and the style guide must also cover: - a **Privacy** section in the settings sub-nav (Account · Folder · Network · Appearance · Privacy · Advanced), for #35; - a **high-friction destructive confirmation** modal that states the number of files affected, for #34's empty-folder remedy; - a **language selector**, for #41; - the **managed, read-only** "set by your organisation" variant of every form control, for #30. - **Owner sign-off (D5).** Only the main window has prior approval. The owner reviews the style guide, the wizard, the conflict view, the settings screens, and the tray icons rendered at 16 px and 32 px before #51 closes. The code may land on main before that review. _Posted by Claude on behalf of @Cordy: phase-3 pre-flight, owner-approved process; the owner may veto any point._
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/cairn-desktop#51
No description provided.