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
This commit is contained in:
2026-08-09 10:37:55 -04:00
parent 2a8fef258d
commit dfc6057f0d
9 changed files with 343 additions and 137 deletions
+40 -4
View File
@@ -289,6 +289,11 @@ struct CardWindowHost: View {
/// needs them (`CardPrintSubject`). Window-scoped for `CardBodyPresentation`'s reason: two card
/// windows are two documents, and the menu bar reaches the frontmost one through the focus system.
@State private var cardPrint = CardPrintSubject()
/// This window's card-level actions today, just Delete Card, promoted off the sidebar's former
/// Actions section to the toolbar (`CardWindowActions`, `CardToolbar`). Window-scoped for
/// `CardBodyPresentation`'s reason: File Delete Card (`DeleteCardCommand`) reaches the
/// frontmost card window through the focus system exactly as Add Attachment does.
@State private var cardActions = CardWindowActions()
/// This window's thumbnail memory. Held here rather than in the section so it survives every
/// snapshot the store applies a cache that died with the view would regenerate every thumbnail
/// on every reload (`AttachmentThumbnailCache`).
@@ -394,6 +399,10 @@ struct CardWindowHost: View {
// File Print (P) reaches the frontmost card window the same way the card-window scope
// of a row the board window answers with its whole board (11-command-nexus.md).
.focusedSceneValue(\.cardPrint, cardPrint)
// File Delete Card reaches the frontmost card window the same way the toolbar's own
// Delete Card item reads this same handle directly, wired at install time rather than
// through the focus system (`configureWindow`).
.focusedSceneValue(\.cardWindowActions, cardActions)
// The raw-source outlet's detailed alert, presented over this window a validation
// refusal on Apply, or a file that could not be opened as source. It hangs *here* rather
// than inside the editor because the second of those fires while source mode is still
@@ -536,6 +545,9 @@ struct CardWindowHost: View {
.onChange(of: store.isReadOnly, initial: true) { _, locked in
attachments.isEditable = !locked
session.comments.isEditable = !locked
// Delete Card's own read-only gate the sidebar Actions button's `.disabled(store
// .isReadOnly)`, carried over verbatim to its toolbar and menu-row twins.
cardActions.isDeletable = !locked
}
// **The thread re-reads on every landed reload** (05 The comments column: "the pane
// reloads its thread from the same FSEvents stream").
@@ -729,6 +741,24 @@ struct CardWindowHost: View {
attachments, store: store, cardID: cardID, undo: session.undo, clipboard: appModel.clipboard
)
Self.configureComments(session.comments, store: store, cardID: cardID, on: session.undo)
Self.configureActions(cardActions, store: store, cardID: cardID)
}
/// Points this window's card-level actions at its card **the one place File Delete Card and
/// the toolbar's Delete Card item learn which card they act on**, `configureAttachments`'s pattern
/// applied to the seam Actions Delete left behind when the sidebar section retired.
///
/// The store is captured **weakly**, `configureAttachments`'s own reason: a toolbar click or menu
/// row still firing after the board window has gone should write nothing rather than resurrect a
/// released store.
///
/// `static`, and taking every collaborator as a parameter, for `configureAttachments`'s reason:
/// which card a gesture lands on is invisible in a running window until it is wrong, and this
/// shape is what lets a test drive the real wiring rather than a re-typed copy of it.
static func configureActions(_ actions: CardWindowActions, store: BoardStore, cardID: ItemID) {
actions.deleteCard = { [weak store] in
store?.deleteCard(cardID)
}
}
/// Points this window's session at **its own undo stack** the three seams the two-level model
@@ -951,13 +981,19 @@ struct CardWindowHost: View {
// The window's customizable toolbar Edit Body · Raw Source · Add Attachment, "the
// window's three committed functions" (03-board-ui.md Toolbar; 05-card-window.md
// Window), joined by Show Sidebar (below). It carries the three window-scoped handles above
// rather than a store, which is why it is installed here and not at attach: those are this
// window's, and so is it. Show Sidebar needs none of them its read and write default to
// Window), joined by Show Sidebar and, since the sidebar's Actions section retired, Delete
// Card and Reveal in Finder (below). It carries the four window-scoped handles above rather
// than a store, which is why it is installed here and not at attach: those are this window's,
// and so is it. Show Sidebar needs none of them its read and write default to
// `AppPreferences.showCardSidebar` / `.setShowCardSidebar`, the same app-wide bit every card
// window answers to, so this call leaves the two injectable parameters unnamed.
windowController.installToolbar(
CardToolbar.controller(body: bodyPresentation, rawSource: rawSource, attachments: attachments)
CardToolbar.controller(
body: bodyPresentation,
rawSource: rawSource,
attachments: attachments,
actions: cardActions
)
)
// **No title in the title bar** the card's name is shown as part of the card's body