Comments, phase 3 — search, the thread find, announcements, and a11y
Board search reaches comment bodies through a search-owned transient
index: the first live-query keystroke sweeps comments/*/index.md
off-actor (.draft and comments/.trash excluded), keystrokes re-filter
in memory, the index discards on clear — the snapshot stays O(cards).
⌘F routes by focus: the comments pane gets an app-owned find bar
spanning the whole rendered thread (next/prev cross rows with
wraparound); body and composer keep NSTextFinder; Find Next/Previous
graduate from FutureCommands. Foreign comment changes speak
path-shaped beside the announcer's ladder ("New comment on 'X'",
plural folds), narrowed by EchoLedger receipts consumed through
CommentPath.classify — and that read fixed a latent footprint bug
where a comment receipt resolved against the card's attachment
listing, read .absent, and classified the user's own write as
foreign. The pane completes its a11y story: flattened comment
elements with Edit/Delete/Reveal custom actions (un-flattening
during inline edit), phrase-table vocabulary, labeled composer and
sort control, and an audit over the open pane on a comment-seeded
fixture (runnable only where automation permission exists).
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -634,7 +634,13 @@ public final class TransientBoardState {
|
||||
/// on the freshly resolved sets, and the vanish rule and the filter rule compose in the one
|
||||
/// order that makes sense: gone first, then hidden. `isTrashVisible` is the only member with
|
||||
/// nothing to say here at all.
|
||||
public func resolve(against snapshot: BoardModel) {
|
||||
///
|
||||
/// - Parameter commentMatches: the transient comment index' current answer
|
||||
/// (`CommentSearchIndex.matchingCards`), threaded straight through to the constraint below.
|
||||
/// Defaulted, because it is the store's fact rather than this container's: everything else here
|
||||
/// is a rule about *this* state, and a caller with no index — every test of the resolution rules
|
||||
/// — means "no comment matches", which is what an empty set says.
|
||||
public func resolve(against snapshot: BoardModel, commentMatches: Set<ItemID> = []) {
|
||||
selection = selection.resolved(against: snapshot)
|
||||
dragMembers = dragMembers.resolved(against: snapshot)
|
||||
pendingCut = pendingCut.resolved(against: snapshot)
|
||||
@@ -661,7 +667,7 @@ public final class TransientBoardState {
|
||||
if let head = selectionHead, !universe.contains(head) { selectionHead = nil }
|
||||
}
|
||||
|
||||
constrainToSearch(in: snapshot)
|
||||
constrainToSearch(in: snapshot, commentMatches: commentMatches)
|
||||
}
|
||||
|
||||
/// **Hidden cards leave the selection** (04-interactions.md § Search) — the constraint rule with
|
||||
@@ -692,8 +698,13 @@ public final class TransientBoardState {
|
||||
/// (`RenameEditor`) — read for the materialized trash, true container crossings — so a foreign
|
||||
/// edit that stops the renaming card matching drops it from the selection here and leaves the
|
||||
/// keystrokes exactly where the user left them.
|
||||
public func constrainToSearch(in snapshot: BoardModel) {
|
||||
let filter = SearchFilter(query: searchQuery)
|
||||
/// **A third occasion joined the two** with the comment index (04 ▸ Search, re-ruled 2026-07-29):
|
||||
/// a landed sweep can *widen* the visible universe (a card matching only through its comments
|
||||
/// appears) and, on a re-sweep, narrow it again — so `CommentSearchIndex.onRefine` calls this too.
|
||||
/// Widening constrains nothing, which is why the seam is worth having anyway: the narrowing half is
|
||||
/// the one that would otherwise leave a selection on a card nobody can see.
|
||||
public func constrainToSearch(in snapshot: BoardModel, commentMatches: Set<ItemID> = []) {
|
||||
let filter = SearchFilter(query: searchQuery, commentMatches: commentMatches)
|
||||
guard filter.isActive else { return }
|
||||
|
||||
let universe = filter.visibleIDs(in: snapshot, container: selection.container)
|
||||
|
||||
Reference in New Issue
Block a user