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:
@@ -54,14 +54,36 @@ struct FutureCommand: View {
|
||||
/// "disabled in the board window — board search is a live filter, not a cursor"
|
||||
/// (11-command-nexus.md).
|
||||
///
|
||||
// m6-card-window: joins `FindCommand` in the Edit menu once the card window's find-in-text exists
|
||||
// (05-card-window.md). Both rows are unconditionally disabled here rather than reading `boardStore`
|
||||
// to prove "board window" disables them: there is no card-window find session anywhere yet for
|
||||
// either validation branch to check.
|
||||
/// ### They are live for exactly one find, and disabled for the others on purpose
|
||||
///
|
||||
/// The card window has three finds (`CardWindowFindRoute`), and two of them are **`NSTextFinder`**'s —
|
||||
/// the body's and an authoring editor's. `NSTextView` already answers ⌘G and ⇧⌘G through the responder
|
||||
/// chain, and an *enabled* menu item's key equivalent fires before the responder chain is consulted,
|
||||
/// so a row that claimed the chord unconditionally would break the stepping it exists to provide. So
|
||||
/// these validate on the **thread** find alone — the one find with no responder to fall through to,
|
||||
/// because its bar is the app's own — and stay disabled everywhere else, which lets the platform's
|
||||
/// stepping keep working where the platform owns the find.
|
||||
///
|
||||
/// `.disabled` on the rows rather than a guard in the action, for the reason every menu row here
|
||||
/// wears its validation: a key equivalent that fires and does nothing is a chord the user cannot tell
|
||||
/// from a broken one.
|
||||
struct FindSteppingCommands: View {
|
||||
|
||||
@FocusedValue(\.cardComments) private var comments
|
||||
|
||||
/// The row's validation, as a value a test can hold: the pane's find bar is up, which is the only
|
||||
/// state in which this app owns ⌘G.
|
||||
static func isEnabled(_ comments: CardComments?) -> Bool {
|
||||
comments?.find.isShowing == true
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
FutureCommand(title: "Find Next", key: "g", modifiers: .command)
|
||||
FutureCommand(title: "Find Previous", key: "g", modifiers: [.shift, .command])
|
||||
Button("Find Next") { comments?.find.step(forward: true) }
|
||||
.keyboardShortcut("g", modifiers: .command)
|
||||
.disabled(!Self.isEnabled(comments))
|
||||
Button("Find Previous") { comments?.find.step(forward: false) }
|
||||
.keyboardShortcut("g", modifiers: [.shift, .command])
|
||||
.disabled(!Self.isEnabled(comments))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user