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:
@@ -327,6 +327,45 @@ public struct CommentThread: Sendable, Equatable {
|
||||
return CommentDraft(body: document.body, attachments: attachments)
|
||||
}
|
||||
|
||||
/// **Board search's read** — every posted comment's body under `cardFolder`, and nothing else
|
||||
/// (04-interactions.md ▸ Search: "an async sweep of `comments/*/index.md` bodies (`.draft` and
|
||||
/// `comments/.trash/` excluded)").
|
||||
///
|
||||
/// It is a second entry point rather than `load(inCard:path:)` reused, and the difference is
|
||||
/// deliberately narrow: **it is silent and it reports nothing**. The thread read logs every stray,
|
||||
/// records every coerce-tier field and hands back the claimed-name work a heal will act on — all of
|
||||
/// which is right for a window opening a thread and wrong for a sweep that re-runs whenever a
|
||||
/// reload lands under a live query. A warning per stray per keystroke's re-sweep would be a log a
|
||||
/// human could not read, and defects surfaced from a *search* would be repaired by a gesture the
|
||||
/// user never made.
|
||||
///
|
||||
/// `nonisolated` and total: this runs on a detached task (`CommentSearchIndex.sweep`), and a card
|
||||
/// with no `comments/`, one whose thread is held by a file, and one whose comments are all
|
||||
/// unreadable each answer `[]` — the same shrug the thread read gives, minus the paperwork.
|
||||
///
|
||||
/// The two exclusions are the enumeration's, exactly as in `load`: `.draft` and `.trash` are
|
||||
/// dot-prefixed, and `BoardLoader.directoryCandidates` skips hidden entries.
|
||||
public static func searchableBodies(inCard cardFolder: URL) -> [String] {
|
||||
let threadFolder = folder(inCard: cardFolder)
|
||||
guard IntegrityRules.node(at: threadFolder) == .directory else { return [] }
|
||||
|
||||
var bodies: [String] = []
|
||||
for commentURL in (try? BoardLoader.directoryCandidates(in: threadFolder)) ?? [] {
|
||||
guard IntegrityRules.isIdentityShaped(commentURL.lastPathComponent),
|
||||
let data = try? Data(contentsOf: commentURL.appendingPathComponent(IntegrityRules.indexFileName)),
|
||||
// A malformed comment is *tolerated*, which here means unsearched: a body the parser
|
||||
// could not find is not a body a query can honestly be said to miss, and searching
|
||||
// the raw bytes would let a query match frontmatter the thread never renders.
|
||||
let document = try? BoardLoader.parseDocument(data, path: commentURL.lastPathComponent),
|
||||
!document.body.isEmpty
|
||||
else {
|
||||
continue
|
||||
}
|
||||
bodies.append(document.body)
|
||||
}
|
||||
return bodies
|
||||
}
|
||||
|
||||
/// **Chronology, with the undated after the dated** (01-storage-format.md § Enhanced schema,
|
||||
/// ruled 2026-07-29): "the thread sorts by `created` ascending … ties and missing/malformed
|
||||
/// `created` (coerce-tier fallback, logged) sort after dated siblings, folder-name order".
|
||||
|
||||
Reference in New Issue
Block a user