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:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user