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
+8 -17
View File
@@ -1002,26 +1002,17 @@ public enum BoardWriter: Sendable {
/// directory, *is* `.unreadable` that is a caller asking about a card that isn't there,
/// not an empty listing. Purely a read: nothing here ever creates `attachments/` or
/// disturbs anything inside it, subfolders included.
///
/// The listing itself is `BoardLoader.attachmentNames(in:)`, which is also what fills
/// `Card.attachments` for the board window's face indicator and carousel. **One function, so
/// the sidebar and the face can never disagree** about a card's attachments or their order.
/// What this call adds over that one is the card-folder guard and this API's error
/// vocabulary the difference between a surface that *acts* on the files and two that
/// merely show them.
public static func listAttachments(ofCard cardFolder: URL) throws(BoardWriteError) -> [String] {
let operation = WriteOperation.listAttachments
try checkIsDirectory(cardFolder, describedAs: "card folder", operation: operation)
let attachmentsFolder = cardFolder.appendingPathComponent(attachmentsFolderName, isDirectory: true)
guard let entries = try? FileManager.default.contentsOfDirectory(
at: attachmentsFolder,
includingPropertiesForKeys: [.isRegularFileKey, .isSymbolicLinkKey],
options: [.skipsHiddenFiles]
) else {
return []
}
let files = entries.filter { url in
guard let values = try? url.resourceValues(forKeys: [.isRegularFileKey, .isSymbolicLinkKey]) else {
return false
}
return values.isRegularFile == true && values.isSymbolicLink != true
}
return files.map(\.lastPathComponent).sorted { $0.localizedStandardCompare($1) == .orderedAscending }
return BoardLoader.attachmentNames(in: cardFolder)
}
// MARK: - Move/copy pre-flight