The remaining rows of 11-command-nexus.md § Context menus, every entry a twin of an existing command path, never a parallel implementation: - Card: Open (the double-click's own openCard closure, always the clicked card alone), Rename (Board ▸ Rename's beginRename path), Style… and the quick-style recents (already present), Delete (File ▸ Delete's store.delete on the standard widened target — selection when the clicked card is a member, else the card alone). - Lane (one menu, header and empty space): Rename and Delete join the existing Style…/recents/Width rows, in table order. - Trash entries and welcome recents verified already exact against the table; the attachment row's menu is marked for m6 beside its command. - File ▸ Reveal in Finder gains its board-window scope, the branch the m4 comment deferred here: the selection's folders on either side of the trash boundary — enabled under every lock, inspection being a read — or the board root with nothing selected; a selection resolving to no folders disables rather than guessing. 904 unit tests. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
139 lines
6.7 KiB
Swift
139 lines
6.7 KiB
Swift
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")
|
|
}
|
|
}
|