import SwiftUI // MARK: - The window's card-level actions, as a handle /// One card window's card-level actions, reduced to what things *outside* it need — today, exactly /// one: the write that used to be the sidebar's Actions ▸ Delete (05-card-window.md ▸ Actions, retired /// with the Actions section — Pipeline card bcd3b323), now the card window's own toolbar item /// (`CardToolbar`) and its required menu twin (`DeleteCardCommand`). /// /// `CardAttachments`'s shape and for its own reason: published through the focus system so a menu /// item and a toolbar item can reach the frontmost card window without a which-window-is-key /// register, `@State` in the host so it dies with the window it belongs to. /// /// **Deliberately not folded into `CardAttachments`.** That type is "reduced to what things outside /// it need" about the *attachments section* specifically — reused elsewhere only as the "a card /// window is frontmost" presence signal (`ShowSidebarCommand`'s own note) — and a delete has nothing /// to do with attachments. Folding it in would leave `CardAttachments`'s own doc comment wrong about /// what the type is for. @MainActor @Observable public final class CardWindowActions { /// Whether the delete write is offered at all — `!store.isReadOnly`, the read-only lock applied /// to the one mutation this handle starts (02-architecture.md's every-entry-point predicate, /// `CardActionsSection`'s own posture before it retired). public var isDeletable = false /// Moves this window's card to the trash — filled by the host with `BoardStore.deleteCard(_:)`, /// the same write ⌫ and the trash column's own drop already make (`CardActionsSection`'s former /// doc comment, carried over verbatim: "the ⌫ delete exactly — same write op, same bracket, same /// stamps"). It does not dismiss the window; the window's fate is re-derived from every snapshot, /// exactly as it was when this lived in the sidebar. public var deleteCard: (() -> Void)? public init() {} } /// The focused card window's actions handle — `FocusedCardAttachmentsKey`'s own shape, one type over. struct FocusedCardWindowActionsKey: FocusedValueKey { typealias Value = CardWindowActions } extension FocusedValues { var cardWindowActions: CardWindowActions? { get { self[FocusedCardWindowActionsKey.self] } set { self[FocusedCardWindowActionsKey.self] = newValue } } } // MARK: - File ▸ Delete Card /// The card window's delete, as a menu row — the required twin `CardToolbar`'s own item needs /// ("toolbars are pure enhancement: every function they host already has a menu item + shortcut", /// 03-board-ui.md ▸ Toolbar) now that the sidebar's Actions ▸ Delete button has retired. /// /// **Deliberately titled "Delete Card", not "Delete"** — 11-command-nexus.md's File ▸ Delete row is /// explicit that the chord's title is a singleton ("the chord's only owner — no twin menu items, no /// shared-equivalent routing"), and that row is unchanged: it still validates against the board /// window's own focused store and still carries ⌘⌫. This is a second, distinctly named row, scoped to /// the card window alone, so the two can never be mistaken for one another in the File menu. /// /// **No default chord, on purpose** — the reason the sidebar button existed in the first place and /// the reason `TrashCommands`' own ⌘⌫ was "deliberately not extended to the card window": an enabled /// delete-key equivalent here would steal delete-to-line-start from this window's text surfaces (the /// body editor, the title field). `View ▸ History`'s own "no default chord" is the precedent for a /// menu row that means something without owning a key. struct DeleteCardCommand: View { @FocusedValue(\.cardWindowActions) private var actions static func isEnabled(_ actions: CardWindowActions?) -> Bool { actions?.isDeletable == true } var body: some View { Button("Delete Card") { actions?.deleteCard?() } .disabled(!Self.isEnabled(actions)) } }