Files
lanework/DESIGN/03-board-ui.md
T
rzen b0763c3c82 Add design corpus and wishlist
Twelve design docs (00-vision through 11-command-nexus) plus assets and
the wishlist, authored ahead of implementation.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
2026-07-26 14:58:16 -04:00

20 KiB
Raw Blame History

Board UI

The board window: layout, lanes, cards, and styling. Interaction mechanics (selection, drag, keyboard) are in 04-interactions.md.

Layout — full visibility

  • Every lane is always on screen. The window width divides across the lanes' width units — no horizontal scroll, no enforced minimum lane width. Resizing the window is the width control. (Settled emphatically in the old app: horizontal scroll strays from what kanban is for.) The degenerate case is accepted, not floored: enough lanes/units in a small window compress every lane, titles and cards truncate gracefully, and the remedy is the user's (fewer units, bigger window). A minimum-width setting that reintroduces scroll was considered in the pathfinder and deliberately rejected.
  • A lane spans a whole number of width units (width frontmatter, ≥ 1, no cap). Cards stay standard width; a wide lane flows them into as many interior masonry columns as it has units.
  • New lanes are created via a File-menu item (the one committed surface; ⇧⌘N — 11-command-nexus.md); new cards from the lane (see 04-interactions.md for creation flows).

Toolbar (board + card windows)

Toolbars are pure enhancement: every function they host already has a menu item + shortcut (04-interactions.md's contract), so nothing below is anyone's only path. Both windows' toolbars are user-customizable, macOS-native (right-click ▸ Customize Toolbar…, drag to rearrange, system overflow and icon/text display options) — the sets below are shipped defaults, not verdicts. Toolbar item labels match their menu-item titles exactly (Show Trash, Edit Body, Raw Source, …), minus any trailing ellipsis (macOS convention: "Add Attachment…" labels as Add Attachment) — one vocabulary everywhere, and the customize palette self-documents against the menus. One exception: the Undo/Redo toolbar items keep static labels — NSUndoManager rewrites their menu titles dynamically ("Undo Move Card…", 04-interactions.md ▸ Configurable bindings), which a toolbar label doesn't track.

  • Board window default: the search field, nothing else — trailing, the one default item; the titlebar stays clean. ⌘F always summons search: with the field removed from the toolbar, invoking it surfaces the field transiently until the search clears. Catalog (available via Customize): New Card, New Lane, Undo, Redo (the pair disabled on boards without undo — no-git and repo-nested boards, matching their menu items — 06-history-undo.md), Show Trash (toggle state matching the View menu checkmark). The board popover deliberately has no toolbar item — the window-title widget is its committed home (below), and a second entry would muddy it.
  • Card window default: Edit Body · Raw Source · Add Attachment — the window's three committed functions, all discoverable from its toolbar; the catalog is the same trio. Edit Body is a single toggle button (on-state in Edit — mirroring the View ▸ Edit Body checkmark and the ⌘E/Return/Escape grammar; the pathfinder's segmented Preview|Edit is retired). Raw Source is likewise a toggle showing on-state; while source mode is active, Edit Body disables (Cancel/Apply own the exits — 05-card-window.md). Add Attachment stays enabled in every mode — attachment operations never touch index.md, so they're safe alongside a raw edit (the sidebar's feedback returns on exit).

Lane

  • Title bar: leading SF Symbol (the lane's icon), title, card-count badge (quiet, secondary styling), new-card button. The whole title bar is the lane's drag surface — no separate grip. The count reads the search filter like every other surface (04-interactions.md): during a search it shows the visible count, not the total.
  • Body: vertical card stack (masonry grid when wide — settled, the pathfinder's masonry works), scrolls vertically.
  • Right-edge drag-to-resize between integer widths (1×, 2×, 3×, … — no cap): shadow snaps at the inter-column gap with 10pt release hysteresis; the window grows/shrinks by one standard width per snap so other lanes keep their exact size. Growth hard-stops at the screen's visible frame, with rubber-band feedback (the dragged edge gives a fraction of the overshoot and snaps back, signalling the bound — pathfinder behavior, proven): the drag never compresses siblings and the window never overflows the screen. The header context menu's Width control (stepper, uncapped) is the precise control — and deliberately the opposite mechanism: it never touches the window, it re-divides the existing width across the new unit total (siblings compress). Widths beyond the screen's capacity stay reachable through it. The Increase/Decrease Lane Width menu items (⌥⌘→/⌥⌘← — 11-command-nexus.md) are this stepper's keyboard face — same re-divide semantics, never the window's size; window-growing behavior belongs to the drag alone.

Card face

  • Leading icon + title. The only face chip in scope is attachments (a quiet indicator when the card has files — the title dominates). Metadata chips (labels/assignees/due) went to the enhanced schema with their fields — out of scope.
  • No body excerpt (settled): the face stays title-only — the old "iterate on the card face later" item is closed with no growth.
  • Titles are optional at every level. On cards and lanes, a missing title renders as a quiet placeholder ("Untitled", secondary styling) wherever the title would appear. On boards, the fallback is the folder name (sans extension), never "Untitled" — see 01-storage-format.md's board-naming rule; window title and welcome recents show title when present, folder name otherwise.
  • Attachments: the sole selected card shows the paged media carousel when it has attachments (the pathfinder's selection-keyed in-place expansion, minus its body blurb — no body excerpt, above). Single selection only: multi-selections and unselected cards stay compact, and the expansion animates under the selection-keyed transaction (Motion below). QuickLook thumbnails for anything previewable, Finder icon fallback, page dots on a glass underlay, paged by trackpad pan / dot click / scroll wheel.

Styling

Capabilities (settled)

  • background on board / lane / card: palette name (kebab-case, hand-editable) or #RRGGBB[AA] hex. Board color paints the board window's content background (the surface behind and between lanes). Lane and card color are edge accents, not fills (settled in the pathfinder's treatment shootout — its settings matrix of C-series lane / K-series card variants landed on C7 · full-column top edge and K1 · left edge stripe): a lane's color paints a full-width band along its top edge, a card's a stripe along its left edge; the surfaces themselves keep the standard chrome, so colored title text never sits on a colored fill.
  • icon: SF Symbol per item with per-level defaults (board rectangle.split.3x1, lane square.stack, card doc.text).
  • iconColor: resolved — schema yes, control no. The field renders when hand-written (tint palette name or hex); the app offers no control for it (Controls below).
  • The pathfinder's palettes (12 icon tints, 12 backgrounds) carry over as the starting point.

Controls (settled)

One style editor component — a background palette grid and a curated symbol grid — presented from three anchors: embedded in the card window sidebar's Style section (05-card-window.md) and in the board popover's styling area, and as a popover opened by Style… from a card/lane context menu or the menu bar (Board ▸ Style…, ⌥⌘S — 11-command-nexus.md; selection-aware: it styles the selected cards or lane, and with nothing selected, the board). One component, one behavior, three anchors — replacing the pathfinder's swatch-row-plus-Style-popover split, whose functions were right and whose form wasn't.

  • Palette-only in-app: the background grid offers the 12 palette colors — every pair AA-verified at design time (10-accessibility.md) — plus a leading None well that removes the background key. Custom hex is not pickable in-app but stays fully honored from disk (runtime contrast, 10-accessibility.md): curated in-app, unlimited on disk.
  • Curated symbol grid: a hand-picked set (roughly five dozen kanban-relevant SF Symbols); its leading well is the level's default symbol and removes the icon key. Any other SF Symbol name works written by hand — the palette stance again. No full-browser escape hatch in-app; the raw file is the escape hatch.
  • Off-palette values display leniently: a hand-written hex background or uncurated symbol shows as the current value in the editor (labeled verbatim, outside the grids); choosing any well replaces it.
  • Batch edits: a multi-selection shows per-dimension mixed state (no well selected, "—" where a value would read); choosing a well applies to the whole selection — one gesture, one commit on git boards.
  • Quick-style row, recents only: card and lane context menus carry one compact row of recently used backgrounds plus the Style… item — one-click recolor for the common case; the pathfinder's second full-palette tier is gone. Recents are app-wide and persist app-side (user preference, never board data).
  • Keyboard path: Style… is a menu item with a shortcut (04-interactions.md's contract); inside the editor the grids are arrow-navigable and every well Tab-reachable (10-accessibility.md).

Board popover

The window-title widget opens the board popover — the one board-level surface, hosting:

  • Board rename (settled: this function stays in-app, unlike the pathfinder which dropped it with the inspector). Rename edits the board's frontmatter title only — the folder is never renamed by the app; the Finder document name is Finder's to change (01-storage-format.md's board-naming rule).
  • Board styling — the embedded style editor (Styling ▸ Controls above).
  • Git integration — mode-aware (06-history-undo.md, 07-sync-collab.md): on a mode-none board, the add-git action (opt-in init; on repo-nested boards replaced by the honest this-board-lives-inside-a-repository explanation — 06); on git boards, branch/source display, branch switching and creation, the commit-identity name/email fields (06), and add/change remote (a remote can be added or changed at any point — 07); for remote-backed boards additionally remote tracking (ahead/behind) with Pull/Push controls and the push-on-every-commit option. Remote authentication surfaces inline here (07 ▸ Remote authentication): credential fields on add/verify, the machine SSH key with Copy, and the Authentication-needed badge state.

Trash

Deletion is a two-stage, Finder-style story: ⌫ tombstones (01-storage-format.md), and the trash quasi-lane is where tombstoned items live on screen. It is a pure view — tombstoned cards keep their deleted: key and stay exactly where they are on disk; nothing about the storage schema is trash-specific.

  • Rendering: trailing (rightmost) position, visually distinct — dimmed/hatched header, trash SF Symbol, count badge; no new-card button; not draggable, not resizable, excluded from lane reordering and width math.
  • Contents: the board's tombstoned cards, sorted by deleted timestamp (newest first). A tombstoned lane appears as a single restorable entry — its cards were hidden with it, not individually tombstoned, and it restores as a whole.
  • Visibility: hidden by default; View ▸ Show Trash toggles it (⇧⌘T; stable title with checkmark state, per 04-interactions.md's configurable-bindings rules). Transient board-scoped state, held in the BoardStore (02-architecture.md; one board window per board, so board-scoped and per-window coincide today) — resets to hidden on open, not persisted (visiting the trash is an errand, not a layout choice). Hidden trash is invisible to search; shown, it participates in the filter like any lane.
  • Put Back (context menu, Finder vocabulary; ⌘⌫ on a tombstoned selection — Finder's own symmetry): removes deleted: — the item reappears in its lane at its old order (ties break deterministically). Putting back a card whose parent lane is tombstoned restores the lane too. Restore fidelity is perfect because nothing ever moved.
  • Drag-to-restore: dragging a card out of the trash into one of its own board's lanes restores it at the drop position (key removed, order set, folder moved only if the destination lane differs). Dropped on another board it follows the drag locality model (04-interactions.md) — a live copy by default, the tombstoned original staying put; ⌘-drag for the true restore-move.
  • Keyboard, selection, and clipboard semantics inside the shown trash (navigation, no mixed live/tombstoned selections, copy-out-only clipboard, inert moves) are specified in 04-interactions.md ▸ The trash, keyboard-first.
  • No editing in the trash: tombstoned cards don't open — double-click does nothing beyond selection; Put Back or drag out first (Finder vocabulary: the trash is for restoring or purging, not working). Tombstoning a card whose window is open dismisses that window (05-card-window.md).
  • Delete Immediately (per item, ⌥⌘⌫) and Empty Trash… (confirmed, ⇧⌘⌫) physically remove the folder(s) — Finder's trash trio throughout. Delete Immediately confirms exactly where the loss is real (settled): on boards without app-managed git history — mode none and repo-nested — the alert stands between one keystroke and unrecoverable deletion; on git boards it acts immediately, since the content remains reachable in history (06-history-undo.md's delete-never-forgets). A deliberate divergence from Finder's always-confirm: the prompt tracks actual recoverability, not ceremony. Empty Trash… confirms everywhere (bulk scope, not per-item recoverability, is what it guards). Time-based auto-purge remains a deferred follow-up (01-storage-format.md).
  • Every trash operation is an ordinary file write — auto-committed and undoable on git boards; on no-git boards the trash itself is the delete-recovery story (07-sync-collab.md).
  • Naming constraint: two "Trash" concepts coexist — attachment Remove moves the file to the system Trash (05-card-window.md), while card/lane deletion lands in this in-app quasi-lane. UI copy must keep them distinguishable: Finder's "Move to Trash" phrasing is reserved for the system Trash; board deletion says "Delete", and the quasi-lane is "Trash" / "Show Trash". Final strings settled in one naming pass when the trash UI copy is written.

Welcome screen & templates

The welcome window carries over from the pathfinder unchanged — confirmed, it works well.

  • Welcome: resizable, no title bar (background drag); recents list with board icon, name, location, counts; single click selects, double click opens; context menu Open / Reveal in Finder / Forget.
  • Templates: New Board (⌥⌘N — ⌘N is new card; 11-command-nexus.md) opens a Pages-style chooser with a mini per-lane preview per template. Inventory and definition format: 09-templates.md.
  • File menu: Open Recent (with Clear Menu; available everywhere), and Duplicate (⇧⌘S) — board window only (11-command-nexus.md), duplicating the frontmost open board to a Finder-style "copy" sibling; it never acts on a welcome-selected recent. The copy is preceded by the close flush (02-architecture.md ▸ Windows; the rule and its Edit-session exception are stated at 09-templates.md ▸ Save as Template), so neither the tree nor the copied history misses pending work. On a git board, the duplicate keeps .git but has its remote configuration stripped: a fork of the board keeps its history (undo trail, delete-never-forgets — and it opens straight in git mode via 06-history-undo.md's adoption rule), but it must not silently push into the original's remote — sharing stays a deliberate per-board opt-in. (Push-on-commit lives app-side in the board registry and never carries to a new board path anyway.)

Editing surfaces summary

What Where
Card title Inline rename on the face; card window title field
Card body Card window (05-card-window.md)
Lane title Inline rename on the header
Colors / icons The style editor — card sidebar Style section (05-card-window.md), board popover, or Style… (context menu / Board ▸ Style…)
Lane width Edge drag + header context-menu picker
Board title, board styling Board popover
Board/lane descriptions (bodies) File-only — hand-edit index.md; live-reload reflects it

Motion

The pathfinder's animation behavior carries over as the committed motion language — it grew call-site by call-site but is disciplined enough to state as rules. The rewrite gives the vocabulary the one thing the pathfinder lacked: a single named home (one animation-constants surface), so curves and durations stop being per-site literals.

  • Two curves, semantically split. Snappy springs are the structural/positional voice: drag reflow (~0.18 s), drop commit, paste, and delete (~0.25 s), keyboard nudges, scroll-into-view, and lane resize (~0.2 s). A smooth spring (~0.28 s) is the content-reflow voice: search filtering and undo/redo restore, deliberately paired so a restore reads like the search filter — leavers and arrivers run their transition, survivors reflow under one gentle spring. Named system presets only; no hand-tuned spring parameters.
  • User-initiated structural changes animate; foreign changes snap. Everything the user does through the app — drag, delete, paste, nudge, resize, search — lands in an animated transaction regardless of entry point (the pathfinder enforced this by routing every deletion/move through the same animated store methods; keep that shape). Changes arriving through the watcher — agent edits, hand edits, sync, external git — apply instantly with no transition: live-reload is the board becoming what's on disk, not an event to perform. The one deliberate crossover is app-initiated undo/redo restore — a git checkout, but ours, so it animates in the search-filter language (06-history-undo.md).
  • Equivalent operations share one dialect. Paste animates exactly like a drop commit (same curve, same duration) so the clipboard's move story feels like drag landing; keyboard one-slot moves slide for the same reason a drop does — an item that teleports is harder to follow than one that slides; cut dims the card in place, Finder-style, until paste moves it (04-interactions.md).
  • Appear/disappear is scale + fade (cards scale from ~0.8, lanes ~0.9, combined with opacity). A restore that moves a card across lanes flies it from old frame to new via matched geometry. Search-hiding rides the same structural transition — hiding is removal, not a special fade.
  • Some things deliberately never animate: the rubber-band marquee tracks the cursor 1:1 (an eased band visibly lags the mouse), and the selection highlight rides whatever transaction is active rather than easing on its own.
  • Animated transactions are keyed narrowly — on the sole-selected card, on the search query — never on broad state, so multi-select churn, marquee drags, and in-flight drags stay animation-free by construction rather than by suppression.
  • Motion never feeds back into logic (the pathfinder's animation-proof-inputs rule, kept as a hard constraint): drop-proposal math reads analytically computed resting zones, the physical mouse position, and item sizes frozen at drag start — never mid-flight measured frames, which are garbage precisely during the ~0.2 s reflow they trigger.
  • Reduce Motion is a rewrite obligation, not an inheritance: the pathfinder ships zero reduced variants; 10-accessibility.md's commitments (crossfade or instant for reflow, search animate-out, the drag replica, trash) are new work.

Changes from Kanban

  • Terminology: lane throughout the UI (menus, dialogs, agent guide).
  • Lane and card colors render as edge accents (C7 top edge / K1 left stripe — Styling above), fixed rather than the pathfinder's configurable treatment matrix; the pathfinder's full-surface painting is gone.
  • The motion language is named and centralized (Motion above); Reduce Motion variants are new work the pathfinder never had.

Open questions

  • Board rename is settled (board popover, above). Board/lane descriptions (bodies) remain file-only — keep that stance, or does the card window pattern (a "lane window"?) deserve to exist for them? Current lean: keep file-only; descriptions are edited rarely.