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:
@@ -277,6 +277,58 @@ struct AccessibilityPhrasesTests {
|
||||
#expect(AccessibilityPhrases.readOnlyLockCleared == "The board is editable again")
|
||||
#expect(AccessibilityPhrases.reloadBreakageCleared == "The board is loading again")
|
||||
}
|
||||
|
||||
// MARK: - The comments pane
|
||||
|
||||
/// 10-accessibility.md ▸ Comments: "the pane is a labeled container ('Comments, N')".
|
||||
@Test("The pane's container names itself and its count")
|
||||
func commentsContainer() {
|
||||
#expect(AccessibilityPhrases.commentsContainerLabel(count: 3) == "Comments, 3")
|
||||
#expect(
|
||||
AccessibilityPhrases.commentsContainerLabel(count: 0) == "Comments, 0",
|
||||
"a comment-less card still shows the pane — the invitation is the point"
|
||||
)
|
||||
}
|
||||
|
||||
@Test("A comment count folds its plural, like every other count in the app")
|
||||
func commentCounts() {
|
||||
#expect(AccessibilityPhrases.commentCount(1) == "1 comment")
|
||||
#expect(AccessibilityPhrases.commentCount(4) == "4 comments")
|
||||
}
|
||||
|
||||
/// The flattened element: the author line is what it *is*, the body is what it holds.
|
||||
@Test("A comment's label is its author line, and its value is its body")
|
||||
func commentElement() {
|
||||
#expect(AccessibilityPhrases.commentLabel(authorLine: "Ada Lovelace · 1 Jan 2026") == "Ada Lovelace · 1 Jan 2026")
|
||||
#expect(AccessibilityPhrases.commentValue(body: "A remark.\n", attachments: 0) == "A remark.")
|
||||
#expect(
|
||||
AccessibilityPhrases.commentValue(body: "A remark.\n", attachments: 2) == "A remark., 2 attachments"
|
||||
)
|
||||
}
|
||||
|
||||
@Test("A comment with nothing to attribute is still a named element")
|
||||
func unattributedComment() {
|
||||
// "Unattributed" is the absence of a name, never a name to speak (`CommentAuthorLine`) — but an
|
||||
// unlabeled element is an audit failure, so the fallback is the noun itself.
|
||||
#expect(AccessibilityPhrases.commentLabel(authorLine: nil) == "Comment")
|
||||
#expect(AccessibilityPhrases.commentValue(body: " \n", attachments: 0) == "")
|
||||
}
|
||||
|
||||
/// The custom actions must be the *same* strings as the context menu's rows (10 ▸ Comments), so a
|
||||
/// user who has learned the pointer inventory hears the same three words from the rotor.
|
||||
@Test("The comment's custom actions are its context menu's rows")
|
||||
func commentActions() {
|
||||
#expect(AccessibilityPhrases.commentEditAction == "Edit")
|
||||
#expect(AccessibilityPhrases.commentDeleteAction == "Delete")
|
||||
#expect(AccessibilityPhrases.commentRevealAction == "Reveal in Finder")
|
||||
}
|
||||
|
||||
@Test("The composer, the sort control and the paperclip are labeled")
|
||||
func commentControls() {
|
||||
#expect(AccessibilityPhrases.commentComposerLabel == "Add a comment")
|
||||
#expect(AccessibilityPhrases.commentSortLabel == "Sort")
|
||||
#expect(AccessibilityPhrases.commentAttachFilesLabel == "Attach Files")
|
||||
}
|
||||
}
|
||||
|
||||
/// Distinct identities for the digest cases, which care only about *counts* — the diff's own suite
|
||||
|
||||
Reference in New Issue
Block a user