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:
@@ -44,6 +44,11 @@ struct CommentTextEditor: NSViewRepresentable {
|
||||
/// Bumped to ask for the keyboard — File ▸ Add Comment's second half, and an inline session
|
||||
/// opening. A counter rather than a flag: two requests in a row are two requests.
|
||||
var focusRequest: Int = 0
|
||||
/// The pane's focus register. This surface reports itself as an **authoring** one, which is what
|
||||
/// makes ⌘F here the editor's ordinary find rather than the thread's (05-card-window.md ▸ Preview:
|
||||
/// "the composer and an inline comment edit are their own focused text surfaces with the editor's
|
||||
/// ordinary find"). `nil` in a preview or a test that mounts the editor alone.
|
||||
var focus: CardComments?
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator()
|
||||
@@ -98,6 +103,25 @@ struct CommentTextEditor: NSViewRepresentable {
|
||||
context.coordinator.textView = textView
|
||||
context.coordinator.onEdit = onEdit
|
||||
context.coordinator.onBlur = onBlur
|
||||
|
||||
// The pane's focus register, and — while this editor holds the keyboard — the find ⌘F runs.
|
||||
// `performTextFinderAction` takes its verb from the sender's `tag`, which is how the standard
|
||||
// Edit ▸ Find item drives it (`CardBodySurface`'s own note); the menu item's key equivalent
|
||||
// fires before this view ever sees ⌘F, so the action has to be reachable from outside.
|
||||
let focus = focus
|
||||
textView.onFocusChange = { [weak textView] gained in
|
||||
guard gained else {
|
||||
focus?.focusLeft(.authoring)
|
||||
return
|
||||
}
|
||||
focus?.focusEntered(.authoring) { [weak textView] in
|
||||
guard let textView else { return }
|
||||
textView.window?.makeFirstResponder(textView)
|
||||
let sender = NSMenuItem()
|
||||
sender.tag = NSTextFinder.Action.showFindInterface.rawValue
|
||||
textView.performTextFinderAction(sender)
|
||||
}
|
||||
}
|
||||
return scrollView
|
||||
}
|
||||
|
||||
@@ -187,6 +211,24 @@ final class CommentEditorTextView: NSTextView {
|
||||
var onCommandReturn: (() -> Void)?
|
||||
var onEscape: (() -> Void)?
|
||||
|
||||
/// **It says when it has the keyboard** — `CommentBodyTextView`'s pair, and for its reason: ⌘F's
|
||||
/// route is a focus question, and the responder is the only thing that can answer it. Reported
|
||||
/// here rather than through `textDidEndEditing` because that fires when the *field editor* ends,
|
||||
/// which for an uneditable editor (the read-only lock) never happens at all.
|
||||
var onFocusChange: ((Bool) -> Void)?
|
||||
|
||||
override func becomeFirstResponder() -> Bool {
|
||||
let accepted = super.becomeFirstResponder()
|
||||
if accepted { onFocusChange?(true) }
|
||||
return accepted
|
||||
}
|
||||
|
||||
override func resignFirstResponder() -> Bool {
|
||||
let resigned = super.resignFirstResponder()
|
||||
if resigned { onFocusChange?(false) }
|
||||
return resigned
|
||||
}
|
||||
|
||||
/// **⌘↩** — "Post the draft … / end the edit session at its commit point"
|
||||
/// (11-command-nexus.md ▸ Fixed grammar keys). Intercepted before `super`, which would otherwise
|
||||
/// insert a newline: the chord is the gesture, not a decorated Return.
|
||||
|
||||
Reference in New Issue
Block a user