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
+32
View File
@@ -117,6 +117,38 @@ struct FixtureRichBoardTests {
#expect(done.cards[0].title.value == "Ship v1")
}
/// `attachments/` is **flat** (01-storage-format.md § Attachments): the card's listing is its
/// top-level regular files and nothing else. This card's folder holds all four shapes on real
/// disk two ordinary files, a hidden one, and a subfolder with a file in it so the rule is
/// asserted against a filesystem rather than against a mock.
///
/// The excluded three are excluded for three different reasons and only one of them is stated
/// in the design doc: subfolders are "tolerated, preserved verbatim and not surfaced"; the
/// hidden file is the loader's uniform `.skipsHiddenFiles` stance (a `.DS_Store` is not
/// anyone's attachment); symlinks are the loader's never-resolve stance, covered in
/// `BoardLoaderTests` because git cannot carry that shape into a fixture reliably.
@Test func aCardListsOnlyTheTopLevelFilesOfItsAttachmentsFolder() throws {
let result = try loadFixture("Valid/rich-board.kanban")
let doing = try #require(result.model.lanes.first { $0.id.rawValue == RichBoard.laneDoing })
let taxonomy = try #require(doing.cards.first { $0.id.rawValue == RichBoard.cardTaxonomy })
#expect(taxonomy.attachments == ["notes.txt", "sketch.png"])
}
/// The overwhelmingly common shape: no `attachments/` folder at all. It reads as an empty
/// listing, never as a warning or a failure nothing has been attached yet is an ordinary
/// state, and it is what makes the face's paperclip indicator absent by default.
@Test func cardsWithoutAnAttachmentsFolderListNothing() throws {
let result = try loadFixture("Valid/rich-board.kanban")
let doing = try #require(result.model.lanes.first { $0.id.rawValue == RichBoard.laneDoing })
let second = try #require(doing.cards.first { $0.id.rawValue == RichBoard.cardSecond })
let done = try #require(result.model.lanes.first { $0.id.rawValue == RichBoard.laneDone })
#expect(second.attachments.isEmpty)
#expect(done.cards.map(\.attachments) == [[]])
#expect(result.warnings.isEmpty)
}
@Test func boardUnknownAndReservedKeysPreserveOrder() throws {
let result = try loadFixture("Valid/rich-board.kanban")
// schema-owned keys (schema, title, created, modified, modified-by, background, icon,