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:
@@ -125,6 +125,28 @@ final class AccessibilityAuditTests: XCTestCase {
|
||||
try app.performAccessibilityAudit()
|
||||
}
|
||||
|
||||
/// **The comments pane**, over the fixture's seeded thread (10-accessibility.md ▸ Comments: the
|
||||
/// labeled container, the flattened comment elements with their three custom actions, the labeled
|
||||
/// composer and the Tab-reachable sort control).
|
||||
///
|
||||
/// The pane is on screen already — View ▸ Show Comments is one persisted app-wide bit and its
|
||||
/// shipped default is on (05-card-window.md ▸ The comments column) — so this test's navigation is
|
||||
/// the card window's, plus a wait on the container's own label to prove the pane rendered rather
|
||||
/// than auditing a body column that happened to be alone.
|
||||
///
|
||||
/// The thread is three comments, one of them unattributed and one edited (`UITestLaunch`), which
|
||||
/// is what makes this an audit of the *rows* rather than of an empty invitation.
|
||||
@MainActor
|
||||
func testCardWindowCommentsPane() throws {
|
||||
let app = XCUIApplication.launchedWithFixtureBoard()
|
||||
try app.openRichCardWindow()
|
||||
XCTAssertTrue(
|
||||
app.element(labeled: Phrase.comments(3)).waitForExistence(timeout: XCUIApplication.uiTimeout),
|
||||
"the comments pane did not appear"
|
||||
)
|
||||
try app.performAccessibilityAudit()
|
||||
}
|
||||
|
||||
// MARK: - Welcome, the template chooser, the board popover
|
||||
|
||||
/// The welcome window, reached by its own Window-menu row — and reached *after* the fixture board
|
||||
|
||||
@@ -30,7 +30,7 @@ Have a scratch board to hand for Part 2 — a new one from File ▸ New Board…
|
||||
|
||||
## Part 1 — run the audit suite
|
||||
|
||||
`KanbanUITests/AccessibilityAuditTests.swift` runs Xcode's accessibility audit over all eight surfaces the design names. **Violations are test failures, not warnings**, and nothing is waived: the audits pass no issue handler at all.
|
||||
`KanbanUITests/AccessibilityAuditTests.swift` runs Xcode's accessibility audit over all nine surfaces the design names. **Violations are test failures, not warnings**, and nothing is waived: the audits pass no issue handler at all.
|
||||
|
||||
```
|
||||
xcodebuild test -project Kanban.xcodeproj -scheme Kanban \
|
||||
@@ -38,7 +38,7 @@ xcodebuild test -project Kanban.xcodeproj -scheme Kanban \
|
||||
-only-testing:KanbanUITests/AccessibilityAuditTests
|
||||
```
|
||||
|
||||
The eight surfaces, and how each test gets there:
|
||||
The nine surfaces, and how each test gets there:
|
||||
|
||||
| Test | Surface | Navigation |
|
||||
| --- | --- | --- |
|
||||
@@ -47,11 +47,12 @@ The eight surfaces, and how each test gets there:
|
||||
| `testCardWindowPreviewMode` | Card window, Preview | ↓ then → to select, Board ▸ Open Card |
|
||||
| `testCardWindowEditMode` | Card window, Edit | …then View ▸ Edit Body |
|
||||
| `testCardWindowRawSourceMode` | Card window, raw source outlet | …then View ▸ Raw Source |
|
||||
| `testCardWindowCommentsPane` | Card window, comments pane over a seeded thread | The pane is shown by default; the test waits on "Comments, 3" |
|
||||
| `testWelcomeWindow` | Welcome, with a recents row | Window ▸ Welcome to Lanework |
|
||||
| `testTemplateChooser` | Template chooser | File ▸ New Board… |
|
||||
| `testBoardInfoPopover` | Board popover | File ▸ Board Info |
|
||||
|
||||
Every test launches the app with `--ui-test-fixture-board`, which makes the app build a known board inside its own container and open it — three lanes ("To Do", "Doing", "Done"), six cards, one card with a rich Markdown body and an attachment, one card already in the trash. That is the `standard` fixture variant; the bare flag means it, and the other two shapes (`large`, `malformed`) belong to the end-to-end pass. The board and the registry both live in a scratch directory that is wiped on every launch, so an audit run never touches your real boards or your recents list. See `Kanban/App/UITestLaunch.swift` for why the board cannot simply be handed to the app on the command line (the sandbox).
|
||||
Every test launches the app with `--ui-test-fixture-board`, which makes the app build a known board inside its own container and open it — three lanes ("To Do", "Doing", "Done"), six cards, one card with a rich Markdown body, an attachment and a three-comment thread (one unattributed, one edited), one card already in the trash. That is the `standard` fixture variant; the bare flag means it, and the other two shapes (`large`, `malformed`) belong to the end-to-end pass. The board and the registry both live in a scratch directory that is wiped on every launch, so an audit run never touches your real boards or your recents list. See `Kanban/App/UITestLaunch.swift` for why the board cannot simply be handed to the app on the command line (the sandbox).
|
||||
|
||||
If a test fails, read the issue's `compactDescription` and fix the app. Adding a waiver is a design change and needs an entry on the Redesign board first.
|
||||
|
||||
@@ -131,6 +132,20 @@ The four implementation cards' manual items, consolidated. Each line is a claim
|
||||
- [ ] **The banner row's spoken label is the banner's own sentence** — tone first, so a VoiceOver user hears *that* it is an error before hearing what it is: "Error: ⟨headline⟩". The strip's container reads as "Board status".
|
||||
- [ ] **Announcements never interrupt.** Start VoiceOver reading a long card body (VO-A), then trigger a foreign edit; the digest must wait its turn rather than cutting the reading off.
|
||||
|
||||
### 3.2b The comments pane
|
||||
|
||||
The pane ships with the comments feature; 10-accessibility.md ▸ Comments is the whole of what it owes. Open the fixture board's rich card ("Write the smoke script") — its thread carries an ordinary comment, an unattributed one, and an edited one on purpose.
|
||||
|
||||
- [ ] **The pane is a labeled container**: "Comments, 3", with the count matching the visible header ("Comments · 3"). It stays a container with zero comments — a comment-less card shows the empty thread and the composer, and reads "Comments, 0".
|
||||
- [ ] **Each comment is one flattened element**: the author line as label ("⟨name⟩ · ⟨date⟩", ending "· edited" on the edited one, and the date alone on the unattributed one), the body as value with "N attachments" appended when it has files. The rendered body's paragraphs, links and code are not separately focusable — the row is one stop.
|
||||
- [ ] **The three custom actions** (VO-⌘-Space, or the Actions rotor) are **Edit / Delete / Reveal in Finder**, the same three words the right-click menu carries. Edit opens the inline session — and while it is open the row stops being flattened, so the editor and its Save/Cancel buttons are reachable.
|
||||
- [ ] **A comment's attachment chips stay reachable** beside the flattened element, with the sidebar's Quick Look behaviour.
|
||||
- [ ] **The composer is a labeled text field** ("Add a comment"), and its paperclip reads "Attach Files". ⌘↩ posts from inside it.
|
||||
- [ ] **The sort control is Tab-reachable** beside the count, labeled "Sort" with the direction as its value ("Oldest First" / "Newest First").
|
||||
- [ ] **A foreign comment announces path-shaped.** With the card window open, add a comment folder to that card's `comments/` from a terminal: expect **"New comment on 'Write the smoke script'"**, once. Edit one of its files: **"Edit comment on '…'"**. Remove one: **"Delete comment on '…'"**. Then post, edit and delete comments *in the app* with VoiceOver running and expect total silence — the app never narrates its own writes.
|
||||
- [ ] **⌘F follows focus.** Click into a comment's text and press ⌘F: the pane's find bar appears (not the body's). Type a word that occurs in two different comments and press ⌘G — the highlight moves *across* rows and the thread scrolls to it; ⇧⌘G steps back; at the last hit ⌘G wraps to the first. Done takes the bar down and the highlighting with it. Click into the composer and press ⌘F: the standard AppKit find bar appears over the composer alone. Click into the body and press ⌘F: the body's find bar, unchanged.
|
||||
- [ ] **Full Keyboard Access reaches the pane's controls** with VoiceOver off: Tab must reach the sort control, the composer, its paperclip, the Comment button, and the find bar's field and chevrons when it is up.
|
||||
|
||||
### 3.3 Text scaling, visual accommodations, Full Keyboard Access
|
||||
|
||||
- [ ] **Largest system text size**: System Settings ▸ Accessibility ▸ Display ▸ Text size, at maximum. Card faces, lane headers, masonry spacing, the trash hatch, the toolbar search field and both window minimum sizes all grow with it; nothing clips, nothing overlaps.
|
||||
|
||||
@@ -107,6 +107,11 @@ enum Phrase {
|
||||
|
||||
/// The inline title editor's placeholders (`NewCardStubView`, `LaneView`, `CardFaceView`).
|
||||
static let cardTitlePrompt = "Card title"
|
||||
|
||||
/// The comments pane's container label — "Comments, N" (10-accessibility.md ▸ Comments).
|
||||
static func comments(_ count: Int) -> String {
|
||||
"Comments, \(count)"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Driving the app
|
||||
|
||||
Reference in New Issue
Block a user