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:
@@ -89,6 +89,9 @@ struct CardWindowHost: View {
|
||||
@State private var windowController = HostedWindowController()
|
||||
@State private var session = CardWindowSession()
|
||||
@State private var phase: Phase = .opening
|
||||
/// This window's body column — the Preview/Edit mode, and the ⌘F hook a menu item reaches
|
||||
/// through the focus system (`CardBodyPresentation`).
|
||||
@State private var bodyPresentation = CardBodyPresentation()
|
||||
|
||||
private enum Phase {
|
||||
case opening
|
||||
@@ -149,6 +152,10 @@ struct CardWindowHost: View {
|
||||
// rename retitles the window and a lane move re-subtitles it with no notification of
|
||||
// our own (05-card-window.md ▸ Window).
|
||||
.navigationSubtitle(windowSubtitle)
|
||||
// Edit ▸ Find (⌘F) is find-in-text in a card window (11-command-nexus.md) — the menu
|
||||
// item reaches the frontmost one's body surface through this, exactly as board-window
|
||||
// items reach their window's store (`FocusedBoardStoreKey`).
|
||||
.focusedSceneValue(\.cardBody, bodyPresentation)
|
||||
.task { start() }
|
||||
.onChange(of: shouldDismiss, initial: true) { _, dismisses in
|
||||
guard dismisses else { return }
|
||||
@@ -163,14 +170,37 @@ struct CardWindowHost: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
if let placement {
|
||||
CardWindowView(card: placement.card)
|
||||
if case let .open(store) = phase, let placement {
|
||||
CardWindowView(
|
||||
card: placement.card,
|
||||
cardFolder: Self.cardFolder(root: store.rootURL, placement: placement),
|
||||
bodyPresentation: bodyPresentation,
|
||||
// "Under the read-only lock the controls disable in place — an in-content mutation
|
||||
// menu validation can't reach" (05 ▸ Preview). The checkbox is that control, and
|
||||
// the store's own lock is the whole predicate.
|
||||
isEditable: !store.isReadOnly,
|
||||
onToggleTask: { offset, checked in
|
||||
store.toggleTaskMarker(inCard: placement.card.id, bodyOffset: offset, checked: checked)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// Nothing to render and nothing worth animating: this window is on its way out.
|
||||
Color.clear
|
||||
}
|
||||
}
|
||||
|
||||
/// `<root>/<lane>/<card>` — the card's own folder, which is what its body's relative images and
|
||||
/// links resolve against (05-card-window.md ▸ Preview).
|
||||
///
|
||||
/// Built off the store's *current* `rootURL` rather than the ref's captured one, for
|
||||
/// `BoardStore.liveItem`'s reason: a mid-session folder rename moves the board, and a preview
|
||||
/// resolving images against where the board used to be would quietly stop showing them.
|
||||
static func cardFolder(root: URL, placement: CardPlacement) -> URL {
|
||||
root
|
||||
.appendingPathComponent(placement.lane.id.rawValue, isDirectory: true)
|
||||
.appendingPathComponent(placement.card.id.rawValue, isDirectory: true)
|
||||
}
|
||||
|
||||
/// Where this window's card is in this board's snapshot, or `nil` when it is not — which is the
|
||||
/// same condition `shouldDismiss` reads, one moment before the window goes.
|
||||
private var placement: CardPlacement? {
|
||||
|
||||
Reference in New Issue
Block a user