fix(web): role=button + aria-label on Timeline rows (#700)#809
Merged
Conversation
This was referenced May 5, 2026
Timeline rows are constructed in two views: chunks (.timeline-item) and files (.timeline-file-item / .tl-file-chunk-item). Each view's rows are fully click-activated — the chunks-view item toggles inline expansion, the files-view outer row toggles a chunk list, and the files-view inner chunk row navigates to source — but they were rendered as bare <div> with no role, no tabindex, no accessible name, and no keyboard handler. Screen readers announced them as anonymous "group" landmarks and keyboard users could not activate them at all. Add role="button", tabindex="0", aria-label (filename + time / file + count + last-time / chunk-type + time), and Enter/Space keydown handlers that mirror the existing click handlers. Initialize aria-expanded="false" on the two expandable row types so screen readers announce the collapsed state on first focus. Inner navigate row (.tl-file-chunk-item) stops keydown propagation so the parent files-view row's expand handler does not also fire. Pin the contract with TestTimelineRowA11y in test_web_a11y.py — six source-scan assertions covering both views. Mutation-validated: removing role= from the chunks-view row fails its specific test. Refs #700, umbrella #702. Co-Authored-By: Claude <[email protected]>
6ab8ea6 to
2d5935f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Timeline rows are constructed in two views — chunks (
.timeline-item) and files (.timeline-file-item/.tl-file-chunk-item). Each view's rows are fully click-activated (chunks-view toggles inline expansion, files-view outer toggles a chunk list, files-view inner navigates to source) but they were rendered as bare<div>with no role, no tabindex, no accessible name, and no keyboard handler at all — keyboard users couldn't even activate them.Second of three split PRs against #700 (
Timeline entriestarget). Independent of the search PR (#808).Changes (
timeline.js)renderChunkView—.timeline-itemrow getsrole="button",tabindex="0",aria-expanded="false",aria-label(filename + time), and akeydownhandler mirroring the existing click toggle.renderFileView—.timeline-file-item(outer expand row) and each.tl-file-chunk-item(inner navigate row) get the same a11y attributes; the inner row stops keydown propagation so the outer expand handler does not also fire on Enter.aria-expandedis initialized tofalseon first render, then updated on every toggle, so screen readers announce the collapsed state on first focus.tests/test_web_a11y.py— newTestTimelineRowA11yclass, six pin assertions (role / aria-label / keydown × 2 views). Mutation-validated: removingrole=from the chunks-view row fails its dedicated test.Why these patterns
role="button"(matcheshome-source-itemprecedent atapp.js:1703).aria-labeluses data already on the row — no new server data, no i18n keys (filename / time / chunk_type are not localized prose).role="button"would be a lie. Click + keydown form a parity pair throughout.Out of scope
.result-item) — separate PR (fix(web): role=button + aria-label on search result rows (#700) #808)..chunk-card) — separate PR (next)..tl-heatmap-col) — already hadrole="button"+ Enter/Space pattern; untouched.Test plan
uv run pytest packages/memtomem/tests/test_web_a11y.py— 6 pass.role=fromrenderChunkView.timeline-itemfails the relevant test.uv run ruff check packages/memtomem/src packages/memtomem/testsclean..timeline-itemwith Tab, press Enter — confirm expand. Same for.timeline-file-itemouter row and.tl-file-chunk-iteminner row.Merge interaction with #808
Both PRs touch
tests/test_web_a11y.py: #808 creates the file withTestSearchResultItemA11y; this PR creates it with the same fixtures +TestTimelineRowA11y. Whichever lands first, the other will need a trivial rebase to keep both classes side-by-side.Refs #700, umbrella #702.
🤖 Generated with Claude Code