Files
lanework/Kanban/UI/Card/CardWindowActions.swift
T
rzen dfc6057f0d The sidebar's Actions section retires — Delete and Reveal in Finder move to the card window's toolbar
Actions is gone from the trailing sidebar. Its two rows land on the card
window's toolbar instead: Delete Card is a new default item (trash SF
Symbol), and Reveal in Finder joins the customizable catalog. The sidebar's
final order is now Style, Details, Attachments — no fourth section.

Delete Card is a push button gated by the same read-only predicate the
sidebar button carried, and it fires the identical write
(BoardStore.deleteCard(_:)) — same bracket, same stamps, no confirmation,
matching the delete flow exactly: recovery is the board's trash lane, so
there is nothing here for an alert to guard. It sits behind a trailing
flexibleSpace in the default set, apart from the four creation/view items
ahead of it, the HIG separation Mail.app's own toolbar Delete models —
one-click, no-confirm, recoverable by trash.

Reveal in Finder is catalog-only: it already had a menu-bar twin with no
default chord (File ▸ Reveal in Finder / RevealInFinderCommand), so nothing
was unreachable before this — the toolbar item is Customize's shortcut to
the same computation (CardAttachments.revealURLs), not a new path.

Delete Card needed a menu-row twin of its own before it could sit on the
toolbar at all ("toolbars are pure enhancement: every function they host
already has a menu item + shortcut" — 03-board-ui.md ▸ Toolbar). File ▸
Delete Card is that row: distinctly titled from the board-scope File ▸
Delete (whose title 11-command-nexus.md calls out as the ⌘⌫ chord's
singleton), and deliberately chord-less — an enabled delete-key equivalent
in the card window would steal delete-to-line-start from its text surfaces,
the same reason the board's own ⌘⌫ was never extended here in the first
place. A new small handle, CardWindowActions, carries the wiring through the
focus system the way CardAttachments and CardPrintSubject already do for
their own single-purpose seams — kept separate from CardAttachments on
purpose, since a delete has nothing to do with the attachments section that
type is scoped to.

CardToolbarTests grows the BoardToolbarTests split (defaults vs. catalog,
now that they differ) plus two new suites: Delete Card firing the wired
write under the lock, and Reveal in Finder's enablement mirroring the menu
row's own card-window computation.

05-card-window.md's Actions section and 11-command-nexus.md's File-menu
inventory are now stale; both amendments are owed and tracked on the card's
own thread rather than made here.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-09 10:37:55 -04:00

82 lines
4.0 KiB
Swift

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))
}
}