Repository navigation
fix(approval-card, diff-review): accessibility and touch: Approve all's focus, what a card runs, code left to right, 16px fields - #174
Merged
Conversation
…ement, describe what runs, code left to right, touch and forced colours - ToolApprovalBatch: the announcement outlives the bar, and once the answers reach the parts focus moves to the first card answered, not <body>. - ApprovalCard: described by the risk, what runs and the rule a lasting decision saves; lasting decisions announce that rule; a critical press let go says "Confirmation cancelled."; the card's focus ring draws again. - DiffReview: Apply is announced; a narrow hunk's buttons wrap rather than cut its range. - dir="ltr" on commands, JSON, rule patterns, diff lines, ranges and paths; dir="auto" on comments and argument fields. - 16px text fields and no key caps under pointer: coarse; borders and pressed states that forced colours keep.
…pnpm a11y After axe, the audit checks what it cannot see: in forced colours, focus rings on a card and a hunk, borders on risk badges and the primary buttons, the diff's + and -, and pressed or picked toggles; with dir=rtl, code, commands and rule patterns compute to ltr; on a coarse pointer, text fields are 16px and no key caps show.
…n touch screens
The Ask button's aria-label ("Ask the docs") left out what it shows
("Ask ⌘K"), which WCAG 2.5.3 asks the name to contain. Its name now comes
from its words, with the key cap hidden and given as aria-keyshortcuts.
This was referenced Oct 22, 2026
Open
…us-describe-ltr-coarse The two default-markdown-output snapshots conflicted: main's Sources container queries (#209) and this branch's card markup. vitest -u rewrote both, the React 18 one with React 18 installed as CI's React 18 job does, then package.json and pnpm-lock.yaml were restored.
…and review pnpm size after merging main: ApprovalCard 22.3 kB, DiffReview 44.3 kB, useDiffReview 16.4 kB, AgentMessage 91.5 kB (36.8 kB from /custom-markdown), signoffTools 55.8 kB, the main entry 129.0 kB, and useAgUiAgent 3.6 kB (already on main). styles.css is 10.7 kB gzipped, 289 kB uncompressed (main: 10.5 kB, 284 kB). The README badge reads 0.4-91.5 kB, and the README and comparison page give DiffReview's 44.3 kB in the same words.
… to right - DiffReview cleared "Review sent" only by comparison: once the review later said again what it said before Apply (A, Apply, R, A on one hunk), the stale "Review sent" came back. It now clears once the review says anything else. - When onSubmit returns a promise, "Review sent" is announced once it resolves, and nothing is announced if it rejects (the app says what failed). A sync onSubmit announces at once, as before. - Kbd is dir="ltr", so in a right-to-left page the deny-always key cap reads Shift then N, not the other way round. - A comment on why a card's description reads the whole command. - Docs: accessibility.md and the DiffReview page say what a promise does; the changeset says both.
Merged
kasettadoo-sys
pushed a commit
to kasettadoo-sys/signoff-ui
that referenced
this pull request
Oct 28, 2026
…he repository - /docs/api lists every export of each entry point in package.json, with its declaration and doc comment, read from dist/*.d.ts when the site builds (lib/api.ts, getApiIndex). content/docs/api.md holds the prose and one `<!-- exports: <entry point> -->` line per entry point, which readGuide fills in, so the page, its .md, the docs search, Ask and llms-full.txt all have the exports. An export several entry points share is listed once, under signoff-ui/core when it is there. An export a component page documents links that page; the others link the pages that use them. In the sidebar under Resources. - CONTRIBUTING.md: a Where to start section. The checks list adds pnpm perf, test:site, registry:build, smoke:assistant-ui and the CopilotKit smoke, which CI runs, and says how to run the browser checks in WebKit and Firefox. The kit-scope path is packages/signoff-ui/scripts. The version step moves every example but the playground, not only examples/coding-agent. - RELEASING.md: the same for the version pull request. - ARCHITECTURE.md: the package's layers and entry points, the build, the registry, the site, the examples and the checks. - ROADMAP.md: dgesteves#172 and dgesteves#173 (0.10.1) and dgesteves#174 (0.11.0) move to a Released section, with dgesteves#171, dgesteves#175 and dgesteves#211, which merged with no release. In review is empty. - test/api-index.test.ts (site) reads each entry point's exports with TypeScript and checks the page lists each, with its declaration and doc comment, and that its links land. test/contributor-docs.test.ts (library) checks CONTRIBUTING's checks against ci.yml, the paths CONTRIBUTING and ARCHITECTURE name, what the version step moves, and the roadmap against the changelog.
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Roadmap item: su-a11y-touch (SU2-44 and SU2-35), with the review follow-ups from SU2-27 (#151) and SU2-45 (#157).
What was wrong
onRespondupdates the parts, as the AI SDK'saddToolApprovalResponsedoes,ToolApprovalBatchreturnednullin the same commit that set "2 approvals approved.", so its live region left the page with the text, and focus fell from the removed button to<body>. The old unit test used avi.fn()that never updated the parts, so the bar stayed mounted and the test passed.outline-nonesets--tw-outline-style: none, sofocus-visible:outline-2drew no outline when focus landed on the card after a decision (Chrome:outline-style: none, width 2px).dir="rtl"page,~/acme/chat-appshowed asacme/chat-app/~, the rule patternpnpm add*as*pnpm add, the deny-always key cap⇧NasN⇧, and diff lines moved their trailing;.@@ -11,6 …(and to 4px on a decided hunk); "Show all 1 unchanged lines"; the playground's Ask button was named "Ask the docs" while it shows "Ask ⌘K" (WCAG 2.5.3).What changed
Library (
approval-card.tsx,diff-review.tsx,lib/labels.ts, andKbdinlib/primitives.tsx):ToolApprovalBatchkeeps its status element (withdata-signoff, sostyles.cssstill hides it when used on its own) while it has something to say, so the announcement outlives the bar. Once the answered requests leavewaiting, and only if focus fell to<body>, it focuses the firstToolApprovalCardit answered, found by a newdata-approval-id. I kept this inside the batch rather than hoisting a live region intoAgentMessage, so a batch rendered on its own (as in the approval rules demo) gets the fix too. The announcement clears after 3 s, as the card's does.describeRule's words), then the key hint. The reason stays first, so apps that match their description at the start are unaffected. Invisible characters in the command are named asRevealednames them. The command is read whole, however long, where an argument is cut at 80 characters: what runs is never cut in what is announced (a comment says so). Lasting decisions announce "Approved for this session: run_command with command npm test*.".useArmedtakes anonCancel, called once when a press is let go by Escape, focus leaving, an outside click or a newer card taking the shortcut, and not when a decision clears it. In Chrome, removing the bar firesfocusouton its button, which first overwrote "2 approvals approved." with "Confirmation cancelled."; a decision now marks the press as let go before that happens.focus-visible:outline-solidon the card, so its ring draws.dir="ltr"on the command preview, the JSON preview and editor, the rule pattern inputs and tool name, the hunk code and unchanged lines, hunk ranges, file paths and every key cap (Kbd).dir="auto"on comments and argument fields, which are the person's or the agent's text.pointer-coarse:text-[length:max(16px,1rem)]on every text field (16px even where the app's root font is smaller),pointer-coarse:hiddenon key caps and hint lines.forced-colors:borderon the primary buttons (no change outside forced colours), a 2px border on a pressed Accept or Reject, and an underline on the picked layout.DiffReviewannounces "Review sent: 3 of 4 accepted, 1 comment." in its own live region: at once after a synconSubmit, once the promise resolves whenonSubmitreturns one, and not at all if it rejects (saying what failed is the app's, as the DiffReview and accessibility pages now say). It holds until the review announces anything else, and is then gone, even when the review later says again what it said before Apply.flex-wrap, so the buttons drop to their own line instead of truncating the range.approvalCard.summary,approvalCard.resolvedFor,approvalCard.confirmCancelled,diffReview.submitted, with the Portuguese in all three copies and the count inlabels.md(218: 143 strings, 75 functions, counted from the builtdefaultLabels).showAll,moreAfterandmoreBeforesay "line" for 1. Minor changeset, since a translation typed asSignoffLabelsneeds the new keys.Site:
accessibility.mddescribes the new behaviour (and no longer says the batch takes focus with it), plus touch, forced colours and right to left sections;labels.mdupdates the right-to-left note. The Ask button is named by its words ("Ask" visible, " the docs" for screen readers, key caparia-hiddenand given asaria-keyshortcuts). The docs search is 16px on a coarse pointer.limits.mdgives the sizespnpm sizemeasures with this branch:ApprovalCard22.3 kB,DiffReview44.3 kB,useDiffReview16.4 kB,AgentMessage91.5 kB (36.8 kB from/custom-markdown),signoffTools55.8 kB, the main entry 129.0 kB, anduseAgUiAgent3.6 kB (already so on main, within the check's 0.2 kB).styles.cssis now 10.7 kB gzipped and 289 kB uncompressed, gzipped aspnpm sizedoes (a build of main measures 10.6 kB and 286.6 kB, so the old "9.9 kB, 269 kB" was already behind). The README badge reads 0.4–91.5 kB (readme.test.tschecks it against the table), and the README and the comparison page, whichreadme-claims.test.tskeeps in the same words, giveDiffReviewas 44.3 kB.scripts/a11y-audit.mjs: after axe, it checks forced colours (focus rings on a card and through its controls and on a hunk, borders on risk badges and the primary buttons, + and − on every changed line, a pressed Accept against an unpressed Reject, the underlined layout),dir=rtl(diff lines, the command, card JSON and rule inputs compute toltron three docs pages) and a coarse pointer at 390px (fields at 16px or more, docs search included, and no visible key caps).How it was tested
default-markdown-outputsnapshots conflicted (main'sSourcescontainer queries from fix(sources): use container queries instead of viewport breakpoints #209 against this branch's card markup);vitest -urewrote both, React 18's with React 18 installed as the React 18 CI job does, thenpackage.jsonandpnpm-lock.yamlrestored. Each differs from the branch's only by main's@containerand@lg:inline. The lockfile is untouched.pnpm install --frozen-lockfile --prefer-offline,pnpm format:check,pnpm lint,pnpm lint:registry,pnpm build:lib,pnpm size(every budget and thelimits.mdtable),pnpm typecheck,pnpm test(93 files, 1549 tests),pnpm test:coverage,pnpm test:site(84 tests),pnpm registry:check,pnpm --filter signoff-ui lint:package,pnpm smoke:styles,pnpm registry:build,pnpm build:playgroundandpnpm perf: all pass. With React 18 installed:tsc -p tsconfig.src.jsonand the suite (1539 passed, 10 skipped) pass.approval-card-description.test.tsx(14),touch-fields.test.tsx(5),rtl-code.test.tsx(4),diff-review-submit.test.tsx(2),labels-plurals.test.ts(1), and 4 inapproval-rules.test.tsx: the batch with parts that update (Approve all and Deny all), alone and in anAgentMessage, focus on the first answered card, and the cancelled press. Against main'ssrc/after the merge, 28 of the new and changed tests fail, and the markup snapshot with them.pnpm a11ypasses in Chrome and in WebKit (BROWSER=webkit), 53 checks each. Before the merge, the same audit against a build of main failed the new checks: Approveborder solid 0pxin forced colours, the cardoutline none, diff lines and rule inputsrtl, fields at 12, 13 and 13.5px.pnpm smoke:playgroundpasses after the merge in Chrome (75), Firefox (75) and WebKit (74, plus the known timeline line from test(ci): WebKit and Firefox legs, focus after a click, examples installed fresh from npm #157).git checkoutofpackage.jsonand the lockfile): the suite andtsc -p tsconfig.src.jsonpass there too.approval-card.tsx.Files outside the listed boundary, each needed by the change:
lib/primitives.tsx(onlyKbd'sdir, added to this PR's scope in the second review round),content/components/diff-review.md(one sentence on a promise fromonSubmit),test/labels-locale.test.tsx(two steps, so a state showsresolvedForandsubmitted, as that test requires of every label),test/docs-claims.test.ts(it asserted the old "takes focus with it" wording), and the twodefault-markdown-outputsnapshots. Since X2-22 (#175) merged,limits.mdis free, and its size table andstyles.cssline are updated here;README.md(the size badge andDiffReview's size) andcomparison.md(the sameDiffReviewsize, in the README's words) go with it.Second review round
/docs/components/diff-review, A on hunk 2, Apply, R on hunk 2, A on hunk 2 read "Review sent: 2 of 6 accepted, 1 comment." instead of "Hunk 2 of 6 accepted. 3 remaining.". It is now cleared, during render, once the announcement differs. A promise fromonSubmitis announced when it resolves, and nothing is announced if it rejects (the rejection still reaches the app as before).Kbdisdir="ltr". A comment on why the card's description reads the whole command. Thestyles.cssfigure for main above is corrected.diff-review-submit.test.tsxnow has 5 (A, Apply, R, A on one hunk reads "Hunk 1 of 2 accepted. 1 remaining."; a promise that resolves, then announces; one that rejects, announces nothing and reports one rejection), andrtl-code.test.tsxchecks the⇧Nkey cap. With the source changes reverted, all four fail. Against main'ssrc/, the new and changed tests now fail 31, the markup snapshot among them. Bothdefault-markdown-outputsnapshots gaindir="ltr"on four key caps and nothing else, React 18's written with React 18 installed as the React 18 CI job does, thenpackage.jsonandpnpm-lock.yamlrestored.pnpm install --frozen-lockfile --prefer-offline,pnpm format:check,pnpm lint,pnpm lint:registry,pnpm build:lib,pnpm size(no row changed),pnpm typecheck,pnpm test(93 files, 1552 tests),pnpm test:site(84 tests),pnpm registry:check: all pass. With React 18 installed:tsc -p tsconfig.src.jsonand the suite (1542 passed, 10 skipped) pass.dir="rtl", the card's⇧Nkey cap computesltrand draws ⇧ left of N in both.pnpm a11ypasses in Chrome (53 checks).Follow-ups
dir="ltr"onJsonViewin the timeline, Markdown code and the timeline's call summary (lib/primitives.tsx,markdown.tsx,tool-call-timeline.tsx);JsonView's always-focusable<pre>and a spoken "Copied" (lib/primitives.tsx); the timeline trigger cut to "Run c…" at 320px (tool-call-timeline.tsx); anannounce="important"mode (agent-status.tsx); touch range selection and Alt+A on AZERTY (use-diff-review.ts); focus after Apply in the playground (playground.tsx).components/docs/component-page.tsx) are notdir="ltr"; the site is never right to left, so they only reorder when a page is forced todir="rtl".DiffReviewas a group rather than a region landmark (a semantics change that four test files outside this PR assert); showing the saved rule on the resolved card, not only announcing it; mirroring the layout with logical properties.