Build card faces with edge-accent styling

The card face becomes real: leading SF Symbol (card default doc.text,
tinted by a valid hand-written iconColor — schema yes, control no),
title or the quiet untitled placeholder, and a quiet paperclip when
the card has attachments — title-only by design, no body excerpt.
Color is the settled K1 edge accent, not a fill: background paints a
4pt stripe down the left edge, resolved through the ported pathfinder
palette (12 icon tints + 12 backgrounds carried over verbatim, plus
raw #RRGGBB[AA]); anything unresolvable paints nothing and stays on
disk exactly as written. The snapshot now carries each card's flat
attachment names — the loader's one read inside a card folder, shared
with the Writer's listing so the m5 carousel and m6 sidebar can never
disagree on order (Finder order, the Writer's existing comparator).
The face keeps its top-aligned structure so the sole-selection
carousel can expand inside the card without moving masonry neighbors.
18 new tests.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-27 13:48:50 -04:00
parent b35566e0fe
commit b4c90838b4
13 changed files with 692 additions and 69 deletions
+24 -3
View File
@@ -151,9 +151,11 @@ public struct Lane: Identifiable, Sendable, Equatable {
public var isDeleted: Bool { !deleted.isMissing }
}
/// A card: `<root>/<guid>/<guid>/index.md`. Leaf of the fractal tree `attachments/` and the
/// (future, out-of-scope) `comments/` live alongside `index.md` on disk but are not modeled
/// here.
/// A card: `<root>/<guid>/<guid>/index.md`, plus the *names* of its attachments. Structurally
/// still a leaf `comments/` (future, out-of-scope) and the attachment files' contents live
/// alongside `index.md` on disk and are not modeled here; `attachments` is the one thing the
/// snapshot reaches inside a card folder for, because two board-window surfaces need it before
/// any card window exists (see its own doc comment).
public struct Card: Identifiable, Sendable, Equatable {
public let id: ItemID
@@ -171,6 +173,25 @@ public struct Card: Identifiable, Sendable, Equatable {
/// comment; the same reasoning applies here.
public let order: Double
/// The card's attachment file names **flat: top-level regular files only, in Finder
/// order** (01-storage-format.md § Attachments: "the app's attachment surfaces are flat:
/// top-level files only", and "subfolders are tolerated, preserved verbatim, never created
/// by the app, and not surfaced"). Empty when the card has no `attachments/` folder, and
/// empty when it has one that couldn't be listed the field is cosmetic, so a
/// directory-listing race degrades to "nothing to show" rather than failing a load.
///
/// Names, not URLs: the two board-window consumers only need to know *whether*, *how many*,
/// and in *what order* the face's quiet paperclip indicator (03-board-ui.md § Card face)
/// and the sole-selected card's attachment carousel. The card window's sidebar does its own
/// listing through `BoardWriter.listAttachments`, since it acts on the files rather than
/// showing them; that call answers through the very same enumeration
/// (`BoardLoader.attachmentNames(in:)`), so the two surfaces agree by construction.
///
/// Freshness is the watcher's, by construction: it reloads on any change anywhere under the
/// board root, so an attachment added in Finder rebuilds the snapshot exactly like an edited
/// `index.md` does no separate invalidation path to keep honest.
public let attachments: [String]
/// The full parsed `index.md`; unknown/reserved keys ride along uninterpreted.
public let document: FrontmatterDocument