Implement live search filtering
The board's live title+body filter per 04-interactions.md § Search: - SearchFilter — a pure value folding the query once (case- and diacritic-insensitive substring, locale-stable); title OR body matches, attachment filenames never searched; only the literal empty string is inactive. - One universe: the filter threads through SelectionGrammar's order lists as a defaulted parameter, so ranges, Select All, arrow navigation, the marquee, drop zones, count badges, and the shown trash all read the same filtered set by construction; lanes are deliberately never filtered out (an emptied lane keeps its slot with a 0 badge). Hidden cards leave the selection through the existing constrain primitive, run on every query change and as the last line of the reload resolve; the delete successor is filtered so ⌫ never selects a hidden neighbour. - The field: an NSSearchField-backed toolbar item (the toolbar's sole default item); Edit ▸ Find ⌘F focuses it through a focused-value presentation; stock field-editor dispatch — Return swallowed, Tab is the keep-filter path to the board, board commands stay enabled except the caret-chord pair, now one shared caretChordsYield expression. - Escape is staged: clear the non-empty query (focus stays), hand an empty field back to the board, clear an active search from board focus — before Escape's clear-selection meaning. - Creating a card clears the search (the placeholder funnel); a rename deliberately gets no carve-out; filter reflow rides the content spring keyed narrowly on the query. 903 unit tests (24 new). Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -50,6 +50,12 @@ struct BoardWindowHost: View {
|
||||
/// the board half of a card window's `(board, card)` identity — see `CardOpener`.
|
||||
@State private var cardOpener = CardOpener()
|
||||
|
||||
/// This window's toolbar search field, as a handle (`BoardSearchPresentation`). `@State` for
|
||||
/// `boardInfo`'s reason — one per window — and published the same way, because Edit ▸ Find ⌘F
|
||||
/// and the caret-chord commands are menu-bar items that have to reach the frontmost board
|
||||
/// window's field.
|
||||
@State private var boardSearch = BoardSearchPresentation()
|
||||
|
||||
@State private var phase: Phase = .opening
|
||||
|
||||
private enum Phase {
|
||||
@@ -89,13 +95,32 @@ struct BoardWindowHost: View {
|
||||
store: store,
|
||||
window: { windowController.window },
|
||||
confirmations: trashConfirmations,
|
||||
openCard: openCard
|
||||
openCard: openCard,
|
||||
search: boardSearch
|
||||
)
|
||||
}
|
||||
// **The board window's toolbar: the search field, nothing else** (03-board-ui.md ▸
|
||||
// Toolbar, "trailing, the one default item; the titlebar stays clean"). It is a toolbar
|
||||
// rather than a strip inside the content because that is where 03 puts it, and it hosts
|
||||
// an `NSSearchField` rather than `.searchable` for the reasons `BoardSearchField`
|
||||
// records — explicit first-responder control, and stock key behaviour.
|
||||
//
|
||||
// m6-toolbar: the rest of 03's toolbar story is the customization card's — the
|
||||
// Customize palette, the New Card / New Lane / Undo / Redo / Show Trash catalog, and
|
||||
// with it ⌘F's transient surfacing of a *removed* field. That work replaces this
|
||||
// declaration with an identified, customizable toolbar; the item itself does not move.
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
BoardSearchField(store: store, presentation: boardSearch)
|
||||
.frame(width: 220)
|
||||
}
|
||||
}
|
||||
// "The board in front", for the menu items that act on it (`LaneWidthCommands`), and
|
||||
// beside it the window's own popover flag, which is what File ▸ Board Info toggles, and
|
||||
// its purge-alert host, which the trash's two confirmed commands raise.
|
||||
// beside it the window's own popover flag, which is what File ▸ Board Info toggles, its
|
||||
// purge-alert host, which the trash's two confirmed commands raise, and its search
|
||||
// field, which Edit ▸ Find focuses and the caret-chord commands yield to.
|
||||
.focusedSceneValue(\.boardStore, store)
|
||||
.focusedSceneValue(\.boardSearch, boardSearch)
|
||||
// The window's identity beside its store — File ▸ Duplicate flushes a *session*, which
|
||||
// is keyed on the window rather than on the board it is showing.
|
||||
.focusedSceneValue(\.boardWindowRef, ref)
|
||||
|
||||
Reference in New Issue
Block a user