Resolves 03 ▸ Motion's self-conflict: narrow keys are the sole-selected card, the search query, and the drag's drop proposal (reflow-to-make- room animates under it); animation-free means the replica and marquee's 1:1 cursor tracking and multi-select churn, not reflow. Claude-Session: https://claude.ai/code/session_01HJ7PhFNmQ19bvy9RMD6GSb
116 lines
21 KiB
Markdown
116 lines
21 KiB
Markdown
# 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. **Lane-count changes re-divide, never resize**: new lane (⇧⌘N), lane paste or cross-board lane drop, lane tombstone, and Put Back all re-divide the existing window width across the new unit total — window-growing behavior belongs to the right-edge drag alone (Lane below).
|
||
- 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. The duplicate **opens in its own board window** once copied — macOS Duplicate convention; the original stays open too. On a git board, the duplicate **keeps `.git` but has its remote configuration stripped** — remotes only: the repo-local `user.name`/`user.email` (06-history-undo.md's identity home) survives, so the fork keeps its commit identity. The copy keeps every GUID — a whole-board copy is 01-storage-format.md's explicit carve-out from the copies-remint rule (a new identity namespace, no collision possible), and keeping them is what keeps the copied history true: its commits name paths that still exist. 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 (carousel expansion), on the search query (filter reflow), on the drag's **drop proposal** (the reflow-to-make-room above animates under it, ~0.18 s) — never on broad state like the selection set. What stays animation-free by construction rather than by suppression: the drag replica and the marquee rectangle (1:1 cursor tracking — animating input echo would be lag), and multi-select churn.
|
||
- **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.
|