Build the Raw Source outlet

The escape hatch: View > Raw Source (opt-cmd-E) unmounts the whole
content area for the literal on-disk index.md in a plain monospaced
editor with Cancel/Apply. Raw source is window-level state, not a third
body mode — entry rides setMode(.preview), which flushes the Edit
session by construction, then reads the file fresh; exit reveals
Preview, and an empty body after Apply does not reopen Edit (openIfNeeded
already ran). Apply validates the proposed bytes through the loader's
own card checks — parseDocument's strict UTF-8/BOM rejection, schema,
order — deliberately skipping the uneditable-shape refusal, since a
flow-mapping card is exactly what the hatch repairs; invalid bytes
alert in place with the loader's own error and no bracket opens. The
write is byte-for-byte with no modified stamp and no modified-by clear,
per 01's explicit carve-out — the verbatim contract outranks stamping —
and identical bytes write nothing. Escape cancels, cmd-Return applies,
toggle-off applies too, and cmd-E disables while raw is active via a
testable predicate. Tombstoned targets refuse as vanished: a foreign
delete is never reverted by a stale buffer.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-28 11:25:25 -04:00
parent e989c1f26e
commit 40c0a75c24
12 changed files with 1733 additions and 65 deletions
+24 -11
View File
@@ -7,8 +7,10 @@ import SwiftUI
///
/// **Two cases, not three.** The raw-source outlet swaps the *entire content area* title, body
/// and sidebar so it is a state of the window, not of the body column, and it does not belong in
/// this enum. Edit Body disabling while raw source is active (11-command-nexus.md) is that
/// window-level state's rule to enforce over this one.
/// this enum. It lives in `CardRawSourceSession`, which is also where the grammar's two open
/// questions are settled (which mode a raw exit lands in, and what an empty body after Apply does).
/// Edit Body disabling while raw source is active (11-command-nexus.md) is that window-level state's
/// rule over this one, and it is enforced on the row: `EditBodyCommand.isEnabled(body:rawSource:)`.
public enum CardBodyMode: Equatable, Sendable {
/// The rendered, selectable preview **the resting state**.
case preview
@@ -123,18 +125,29 @@ public final class CardBodyPresentation {
/// what changed with this milestone is the validation and the action, exactly as `FutureCommands`
/// predicts.
///
/// Validation is scope: with no card window in front there is no `cardBody` focused value, and the
/// row disables. The read-only lock is deliberately **not** part of it entering Edit is not a
/// mutation, and 02-architecture.md § the lock's scope keeps editor buffers alive under the lock
/// (only their saves suspend), so a locked board can still be read in the editor and its text
/// copied out.
/// Validation is scope **plus the raw-source clause**: with no card window in front there is no
/// `cardBody` focused value, and the row disables; with source mode active it disables too "View
/// Edit Body (E) disables while source mode is active, matching its toolbar item" (05 Raw source
/// outlet; 11-command-nexus.md files the same clause on the row). The reason is that the two would
/// be editing the same bytes from two surfaces: while the whole `index.md` is open as text, a mode
/// flip in the body column beneath it has nothing to flip *to* the column is not on screen and
/// its buffer's next debounced save would write a body the raw buffer is also about to overwrite.
/// Cancel and Apply own the exits (03-board-ui.md Toolbar).
///
// m6-raw-source: "View Edit Body (E) disables while source mode is active, matching its toolbar
// item" (05 Raw source outlet). That is one more clause on `isDisabled` once a window-level raw
// mode exists to read; the row, its title and its chord do not move.
/// The read-only lock is deliberately **not** part of it entering Edit is not a mutation, and
/// 02-architecture.md § the lock's scope keeps editor buffers alive under the lock (only their saves
/// suspend), so a locked board can still be read in the editor and its text copied out.
struct EditBodyCommand: View {
@FocusedValue(\.cardBody) private var cardBody
@FocusedValue(\.cardRawSource) private var rawSource
/// The row's validation, as a value a test can hold: a menu item's `.disabled` is otherwise only
/// observable by driving the menu bar, and "E disables while raw source is active" is precisely
/// the kind of clause that regresses silently.
static func isEnabled(body: CardBodyPresentation?, rawSource: CardRawSourceSession?) -> Bool {
body != nil && rawSource?.isActive != true
}
var body: some View {
Toggle("Edit Body", isOn: Binding(
@@ -142,7 +155,7 @@ struct EditBodyCommand: View {
set: { isOn in cardBody?.setMode(isOn ? .edit : .preview) }
))
.keyboardShortcut("e", modifiers: .command)
.disabled(cardBody == nil)
.disabled(!Self.isEnabled(body: cardBody, rawSource: rawSource))
}
}