Implement toolbar customization for both windows
NSToolbar through the existing HostedWindowController rather than SwiftUI's toolbar — for reasons that are contract, not taste: 03's transient-search clause is a decision over the toolbar's current contents, which NSToolbar publishes and SwiftUI's API cannot answer; Undo/Redo are the system's nil-target responder-chain actions so the toolbar items validate exactly as the menu rows do (disabled on base boards, alive in m8 unchanged); and the search item hosts the real NSSearchField with explicit first-responder control. Customization is all system furniture — Customize sheet, drag rearrange, display-mode popup, overflow, autosaved per window kind. Board default: the search field alone, trailing; catalog adds New Card, New Lane, Undo, Redo, Show Trash, every action extracted from its menu command so no second predicate exists. Card default: the Edit Body / Raw Source toggles and Add Attachment, mirroring their commands' own predicates live via observation tracking. Removing the search item keeps the promise — ⌘F surfaces the same field as a transient strip under the title bar, persisting until the query clears, and an overflowed item that cannot take the keyboard falls through to the strip too. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -203,36 +203,47 @@ struct ShowTrashCommand: View {
|
||||
/// Immediately enabled against a trash the user cannot see, which 04-interactions.md rules out
|
||||
/// outright ("hidden, it is invisible to every gesture"). A *live* selection is untouched — the
|
||||
/// board it names is still right there.
|
||||
///
|
||||
/// The setter's body lives on the store (`BoardStore.setTrashVisible`) because the toolbar's
|
||||
/// Show Trash item is this same command with a different face (03-board-ui.md ▸ Toolbar: "toggle
|
||||
/// state matching the View menu checkmark"), and the consequence above has to be true of both.
|
||||
private var isVisible: Binding<Bool> {
|
||||
Binding(
|
||||
get: { store?.transient.isTrashVisible ?? false },
|
||||
set: { shown in
|
||||
guard let store else { return }
|
||||
// The re-divide is a *user-initiated structural change* — every lane compresses or
|
||||
// relaxes as the trash's one unit joins or leaves the division — so it animates in
|
||||
// the structural voice (03-board-ui.md § Motion; § Trash makes Show/Hide Trash "a
|
||||
// re-divide trigger", a lane add's behaviour exactly). It is also one of the few
|
||||
// structural changes that never touches disk, which is why it wears its own
|
||||
// transaction here instead of arriving through the reload seam like the rest.
|
||||
//
|
||||
// Reduce Motion read from AppKit rather than from `@Environment`: a menu command's
|
||||
// content is built outside any rendered hierarchy, where the environment's
|
||||
// accessibility values are not reliably populated (`Motion.prefersReducedMotion`).
|
||||
withAnimation(Motion.structural(reduced: Motion.prefersReducedMotion)) {
|
||||
store.transient.isTrashVisible = shown
|
||||
// Dropped inside the same transaction: the rows it pointed at are leaving under
|
||||
// this very animation, and a selection that cleared outside it would be the
|
||||
// highlight easing on its own — which 03 § Motion rules out ("the selection
|
||||
// highlight rides whatever transaction is active").
|
||||
if !shown, store.selection.liveness == .trashed {
|
||||
store.clearSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
set: { shown in store?.setTrashVisible(shown) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension BoardStore {
|
||||
|
||||
/// Show/Hide Trash, with its one consequence — **the whole of the View-menu row's behaviour**,
|
||||
/// shared with the toolbar item that mirrors it.
|
||||
func setTrashVisible(_ shown: Bool) {
|
||||
// The re-divide is a *user-initiated structural change* — every lane compresses or relaxes
|
||||
// as the trash's one unit joins or leaves the division — so it animates in the structural
|
||||
// voice (03-board-ui.md § Motion; § Trash makes Show/Hide Trash "a re-divide trigger", a
|
||||
// lane add's behaviour exactly). It is also one of the few structural changes that never
|
||||
// touches disk, which is why it wears its own transaction here instead of arriving through
|
||||
// the reload seam like the rest.
|
||||
//
|
||||
// Reduce Motion read from AppKit rather than from `@Environment`: a menu command's content
|
||||
// is built outside any rendered hierarchy, where the environment's accessibility values are
|
||||
// not reliably populated (`Motion.prefersReducedMotion`) — and a toolbar item has no
|
||||
// environment at all.
|
||||
withAnimation(Motion.structural(reduced: Motion.prefersReducedMotion)) {
|
||||
transient.isTrashVisible = shown
|
||||
// Dropped inside the same transaction: the rows it pointed at are leaving under this
|
||||
// very animation, and a selection that cleared outside it would be the highlight easing
|
||||
// on its own — which 03 § Motion rules out ("the selection highlight rides whatever
|
||||
// transaction is active").
|
||||
if !shown, selection.liveness == .trashed {
|
||||
clearSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - The alert
|
||||
|
||||
extension View {
|
||||
|
||||
Reference in New Issue
Block a user