import SwiftUI // MARK: - The shared shape /// The shape behind every menu row this milestone ships **before** the window that answers it. /// /// 11-command-nexus.md's own contract runs both directions: "a command absent here doesn't exist, and /// adding one means adding a row here first" — so once a row *is* in the Nexus, shipping the window /// behind it is a validation-and-action change, not a menu change. `FutureCommand` (a `Button`) and /// `FutureToggleCommand` (a `Toggle`) below are that reading, applied: the row exists now, stably /// titled and stably chorded — `NSUserKeyEquivalents` already resolves it, so a user can remap it /// today — with validation pinned to `false` and the action a no-op until the milestone named at the /// call site fills both in. That milestone's whole diff then reads as "flip `.disabled`, fill the /// closure" rather than "add a menu item", which is also why every call site below carries the /// codebase's `mN-` marker for a component still owed. /// /// **The title never moves once a row ships**, disabled or not: a toggle wired live later must not /// gain a second spelling on the way (04-interactions.md ▸ Configurable bindings — "toggles keep one /// stable title, checkmark state only" — which applies to a row that has not started ticking yet /// exactly as it does to one that has). struct FutureCommand: View { let title: String var key: KeyEquivalent? var modifiers: EventModifiers = .command var body: some View { Button(title) { // No-op: the milestone named at the call site wires this in. } .keyboardShortcut(key.map { KeyboardShortcut($0, modifiers: modifiers) }) .disabled(true) } } /// `FutureCommand`'s checkmark-state twin, for a row the Nexus already marks "(checkmark toggle)". /// /// `isOn` is a constant `false` rather than real state: there is no session yet for a binding to /// read, which is exactly the disabled, unchecked state a not-yet-wired toggle should show. struct FutureToggleCommand: View { let title: String var key: KeyEquivalent? var modifiers: EventModifiers = .command var body: some View { Toggle(title, isOn: .constant(false)) .keyboardShortcut(key.map { KeyboardShortcut($0, modifiers: modifiers) }) .disabled(true) } } // MARK: - File ▸ Save as Template /// File ▸ Save as Template — no default chord (11-command-nexus.md; 09-templates.md). /// // m9-templates: copies the open board into the user templates store, close-flushed first exactly as // Duplicate is (09 ▸ Save as Template: "The copy is preceded by the close flush"), `.git` stripped, // tombstones dropped, a `template:` key stamped. Validation will be `acceptsBoardMutations` plus 09's // one carve-out from the read-only lock — live under the unwritable-location state unless an open // Edit/raw-source session holds unsaved content — so it cannot simply borrow // `DuplicateBoardCommand`'s predicate outright. struct SaveAsTemplateCommand: View { var body: some View { FutureCommand(title: "Save as Template") } } // MARK: - File ▸ Add Attachment… /// File ▸ Add Attachment… (⇧⌘A) — card window only (11-command-nexus.md). /// // m6-card-window: the menu-bar twin of the attachments section's quiet add affordance // (05-card-window.md § Attachments) and of a whole-window Finder file drop. Validation will be scope // alone — a card window in front, the read-only lock aside — `BoardInfoCommand`'s shape for its own // scope-only item. // // m5-context-menus, m6-card-window: the attachment row's own context menu is a second thing this // milestone owes — Open, Remove (system Trash), Reveal in Finder (11-command-nexus.md ▸ Context // menus' Attachment row), twinning the focused section's grammar keys (Return open / ⌫ remove — 05 // ▸ Attachments) and Reveal in Finder's attachments-focused scope, exactly the way this milestone's // card and lane menus twin their own grammar and menu-bar commands: no new store method, no parallel // implementation. There is no row view to hang a `.contextMenu` off yet, so nothing scaffolds here // beyond this marker. struct AddAttachmentCommand: View { var body: some View { FutureCommand(title: "Add Attachment…", key: "a", modifiers: [.shift, .command]) } } // MARK: - Edit ▸ Find Next / Find Previous /// Edit ▸ Find Next / Find Previous (⌘G / ⇧⌘G) — the card window's find bar stepping, /// "disabled in the board window — board search is a live filter, not a cursor" /// (11-command-nexus.md). /// // m6-card-window: joins `FindCommand` in the Edit menu once the card window's find-in-text exists // (05-card-window.md). Both rows are unconditionally disabled here rather than reading `boardStore` // to prove "board window" disables them: there is no card-window find session anywhere yet for // either validation branch to check. struct FindSteppingCommands: View { var body: some View { FutureCommand(title: "Find Next", key: "g", modifiers: .command) FutureCommand(title: "Find Previous", key: "g", modifiers: [.shift, .command]) } } // MARK: - View ▸ Edit Body / Raw Source / History /// View ▸ Edit Body (⌘E) / Raw Source (⌥⌘E) / History — the card window's three view-state rows /// (11-command-nexus.md). /// // m6-card-window: Edit Body and Raw Source are checkmark toggles reading the window's edit-mode // state ("Edit Body disables while Raw Source is active" — 05-card-window.md); History is a plain // command that focuses the sidebar's History section and disables outright on mode `none` / // repo-nested boards once that section exists (05-card-window.md, 07-sync-collab.md). All three are // unconditionally disabled here — there is no card-window mode state anywhere yet. struct CardViewCommands: View { var body: some View { FutureToggleCommand(title: "Edit Body", key: "e", modifiers: .command) FutureToggleCommand(title: "Raw Source", key: "e", modifiers: [.option, .command]) FutureCommand(title: "History") } } // MARK: - Board ▸ Pull / Push /// Board ▸ Pull / Push — no default chord, remote-backed boards only (11-command-nexus.md; /// 07-sync-collab.md: "also Board-menu items"). /// // m7-git: menu-bar twins of the board popover's own Pull/Push buttons (07-sync-collab.md). // Validation will be remote-mode plus 06's abnormal-state pause ("disabled during 06's // abnormal-state pause ... and on an unresolvable remote" — 11-command-nexus.md). Unconditionally // disabled here: there is no remote model, no popover twin, and no git mode to validate against yet. struct RemoteCommands: View { var body: some View { FutureCommand(title: "Pull") FutureCommand(title: "Push") } }