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:
@@ -34,8 +34,9 @@ import SwiftUI
|
||||
///
|
||||
/// ### What is still a later card's
|
||||
///
|
||||
/// The **search filter** ("shown, it participates in the filter like any lane") and **⌘C copy-out**
|
||||
/// are still owed. The *pointer* grammar is here: a row's click runs the same `SelectionGrammar` the
|
||||
/// **⌘C copy-out** is still owed. The **search filter** ("shown, it participates in the filter like
|
||||
/// any lane") arrived with m5 and is one line — see `entries`, which every other surface here reads
|
||||
/// through. The *pointer* grammar is here: a row's click runs the same `SelectionGrammar` the
|
||||
/// board does, and the column's empty space rubber-bands on the trashed side. So is the drop, with
|
||||
/// the target lane highlighted and the source row dimmed in place; the drag's replica is not. The
|
||||
/// **keyboard** reaches the column entirely through the frames the rows register — arrow walks in
|
||||
@@ -83,11 +84,14 @@ struct TrashLaneView: View {
|
||||
|
||||
/// The rows the column shows.
|
||||
///
|
||||
// m5-search: the shown trash "participates in the filter like any lane", so the search predicate
|
||||
// narrows this collection exactly as it narrows `LaneView.renderedCards` — and the count badge
|
||||
// follows for free, because it reads this same value.
|
||||
/// **The shown trash "participates in the filter like any lane"** (03-board-ui.md § Trash), so
|
||||
/// the search predicate narrows this collection exactly as it narrows `LaneView.renderedCards`
|
||||
/// — card rows and lane rows alike, each by its own title and body (`SearchFilter`) — and the
|
||||
/// count badge follows for free, because it reads this same value. Hidden, the column renders
|
||||
/// nothing and registers nothing, so "hidden trash is invisible to search" needs no code at all.
|
||||
private var entries: [TrashEntry] {
|
||||
TrashModel.entries(of: store.snapshot)
|
||||
let filter = store.searchFilter
|
||||
return TrashModel.entries(of: store.snapshot).filter { filter.matches($0) }
|
||||
}
|
||||
|
||||
// MARK: - Header
|
||||
|
||||
Reference in New Issue
Block a user