Build Preview mode rendering

The card body's resting state: swift-markdown (pinned 0.8.0, smart
typography off — Preview renders the bytes on disk) parsed into a pure
BodyMarkup model with UTF-8 source offsets, rendered on one hosted
TextKit 1 NSTextView — chosen because find-in-text is NSTextFinder,
checkbox clicks reuse AppKit character hit-testing, links are .link
attributes, and NSTextTable's automatic layout is exactly the
columns-sized-to-contents rule. The GFM subset renders per 05; HTML
stays verbatim code-styled text; relative images resolve against the
card folder while remote URLs are never fetched, drawing a quiet chip
instead. Task checkboxes are live: a click flips exactly one byte
through a fresh-read, refuse-uneditable, stamp, atomic-replace write —
the app's only offset-addressed write, so a moved target refuses as
staleTarget and what the user saw decides the direction, netting one
toggle on a double-click. Empty bodies open in Edit per CardBodyMode's
opening rule, applied once; the Edit surface itself stays an honest
read-only stub until its card. FindCommand prefers the card body's
find over board search when a card window is focused.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-28 09:59:18 -04:00
parent 7f1adf47c5
commit 6dc84176fb
15 changed files with 2608 additions and 18 deletions
+30
View File
@@ -1127,6 +1127,36 @@ public final class BoardStore {
return nil
}
// MARK: - Task checkboxes
/// Ticks or unticks a Preview task-list checkbox **the app's one write into a card's body**
/// (05-card-window.md Preview), and otherwise an entirely ordinary one: the same
/// `performWrite` bracket, the same banner on failure, the same one-way flow back through the
/// watcher. "A toggle is an ordinary user edit the standard atomic write, auto-committed and
/// undoable on git boards."
///
/// `bodyOffset` is the UTF-8 byte offset the parse handed the renderer (`BodyTask
/// .markerOffset`) and `checked` is the state the user was looking at; both travel to
/// `BoardWriter.toggleTaskMarker`, which re-verifies them against the file it reads and refuses
/// rather than write blind. Nothing here inspects the body: the store never re-parses to
/// second-guess the click, because its own snapshot is exactly as stale as the render was.
///
/// **A checkbox in a card that has gone writes nothing** the vanished-target guard every
/// gesture in this file makes, ancestor-walked through `liveItem`: the card window would be
/// dismissing itself in the same breath, and the reload that removed the card is the authority.
/// The read-only lock is `performWrite`'s refusal, which is also why the controls disable in
/// place on the Preview side rather than failing here (02-architecture.md § the lock's scope).
public func toggleTaskMarker(inCard cardID: ItemID, bodyOffset: Int, checked: Bool) {
guard let target = Self.liveItem(cardID, in: snapshot), let card = target.cardID else { return }
let folder = rootURL
.appendingPathComponent(target.laneID.rawValue, isDirectory: true)
.appendingPathComponent(card.rawValue, isDirectory: true)
try? performWrite { () throws(BoardWriteError) -> Void in
try BoardWriter.toggleTaskMarker(inItemFolder: folder, bodyOffset: bodyOffset, checked: checked)
}
}
// MARK: - Board rename
/// Writes the board's own `title` the board popover's rename field (03-board-ui.md § Board