Repository navigation
refactor(studio): audit native labels, disclosures and ad-hoc alerts against the primitives - #2352
Merged
Merged
Conversation
…anges Pins the failed-turn and reasoning disclosures (names, aria-expanded, the controlled region, click and Enter/Space, the expand-details preference), keyboard toggling of a learning suggestion's Details, and the runtime status line's alert and status announcements, against the unchanged code. Refs #2340 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds the stock shadcn Collapsible over @radix-ui/react-collapsible, already in the lockfile through the Accordion, for single show/hide disclosures. Refs #2340 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The failed-turn card's Show details, the reasoning summary, and a learning suggestion's Details now use Collapsible, CollapsibleTrigger and CollapsibleContent instead of a button with hand-set aria-expanded and aria-controls over conditionally rendered content. Names, styles, the expand-details preference and the first-open detail read are unchanged. The suggestion card spaces its children with flex gap instead of space-y, so the collapsed content's hidden element adds no margin. Refs #2340 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ption cards Each eligible worktree's card is now a Label for its radio. font-normal keeps the card's text weight, since Label defaults to font-medium. Refs #2340 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…imitive The settings runtime status line's failure banner, the learned-skills publication notice, and the learned-skill dialog's action error are now Alert with AlertDescription. They keep their tints, and the destructive banner keeps full-strength destructive text: the Alert's destructive variant fades the description to 90%, which drops below 4.5:1 on white, so it uses the default variant with Studio's destructive tint instead. Refs #2340 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Screenshots showed three layout shifts from the primitives' defaults: - Label's leading-none shortened each worktree card; leading-[inherit] restores the dialog's line height. - Alert's w-full stretched the storage error beside its Refresh button and pushed the button to a new line; w-auto sizes it like the paragraph it replaced, full width in block flow and content-sized in a flex row. - AlertDescription's relaxed paragraph leading made the publication notice taller, and the Alert border framed a dialog error that had none; the notice inherits its line height again and the dialog error drops the border. Refs #2340 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
kantord
approved these changes
Oct 15, 2026
kantord
left a comment
Member
There was a problem hiding this comment.
Thanks for this, the per-call-site decision table made the audit easy to follow, and the Collapsible, Label and Alert swaps preserve roles, names and the controlled-open behaviour. Approving, with a few non-blocking nits:
- The worktree picker
Labeloverridesfont-normalandleading-[inherit]but not the primitive'stext-sm. I checked the children and visible text looks unaffected, but I could not render it, so "pixels unchanged" for that site is unverified on my side. - Label's default
font-medium leading-noneis now undone at several call sites across this PR and #2359 (font-normalon three, plusleading-[inherit]here). If it keeps recurring, the Label default may be the thing to revisit. - The worktree picker Label and the two learned-skills Alerts have no new test.
Fully or partially written by an AI agent.
Merged
2 tasks
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.
Summary
Audits every native
<label>,aria-expandeddisclosure and ad-hocrole="alert"underapps/web/src/features/against the primitives. Three disclosures move to a newly ported Collapsible primitive, the worktree picker's option cards move to Label, and three banners move to Alert. Names, roles, keyboard behaviour and pixels are unchanged in every captured state. The table under What changed gives the decision for each of the 32 call sites; 9 are in files that other open PRs change, so they are left for later.Part of #2340
Part of #2201
Part of #2210
What changed
components/ui/collapsible.tsx(new): the stock shadcn Collapsible over@radix-ui/react-collapsible. The package was already in the lockfile through the Accordion;apps/web/package.jsonnow names it directly, exact-pinned.collapsible.test.tsxcovers a trigger showing and hiding the content, and a controlled open state.Disclosures → Collapsible. The failed-turn card's Show details (
chat/failed-turn-card.tsx), the reasoning summary (chat/reasoning-disclosure.tsx), and a learning suggestion's Details (learning/proposal-row.tsx) now useCollapsible,CollapsibleTrigger asChildandCollapsibleContent, instead of a button with hand-setaria-expandedandaria-controlsover conditionally rendered content. They stay controlled, so the expand-details preference and the first-open detail read work as before. The suggestion card spaces its children withflex gapinstead ofspace-y, so the closed content adds no margin.Label. Each eligible worktree's card in
chat/worktree-picker-dialog.tsxis aLabelfor its radio.font-normalandleading-[inherit]keep the card's weight and line height against Label'sfont-medium leading-none.Banners → Alert (
AlertwithAlertDescription):settings/settings-card.tsx).w-autokeeps it content-sized beside the Refresh button.knowledge/learned-skills.tsx). It keeps its warning tint and Dismiss button.border-0keeps it unframed, as before.The destructive banner uses the default variant with Studio's destructive tint: the Alert's destructive variant fades the description to 90%, which drops below 4.5:1 on white.
Every call site
<label>,aria-expanded=androle="alert"in non-test files underapps/web/src/features/onmain(1210721be): 32 sites.apps/web/src/features/, line onmain)chat/failed-turn-card.tsx:58aria-expandedchat/reasoning-disclosure.tsx:65aria-expandedlearning/proposal-row.tsx:274aria-expandedchat/worktree-picker-dialog.tsx:98<label>settings/settings-card.tsx:111role="alert"knowledge/learned-skills.tsx:161role="alert"knowledge/learned-skills.tsx:336role="alert"chat/ask-args-view.tsx:57aria-expandedchat/delivery-note-card.tsx:75aria-expandedline-clamp-8on text that is always rendered. Already a Button.chat/edit-diff.tsx:271aria-expandedchat/authorization-review.tsx:233role="alert"chat/session-details-dialog.tsx:201role="alert"chat/session-inspection.tsx:330role="alert"chat/soul-dialog.tsx:45role="alert"chat/transcript-dialog.tsx:64role="alert"chat/worktree-picker-dialog.tsx:66role="alert"learning/learning-settings-page.tsx:293role="alert"learning/learning-settings-page.tsx:303role="alert"learning/learning-settings-page.tsx:339role="alert"learning/learning-settings-page.tsx:457role="alert"learning/proposal-row.tsx:301role="alert"memory/memory-fact-detail.tsx:134role="alert"memory/facts-about-you.tsx:29role="alert"Note, the same plain line its loading and empty states use (role="status"). Only the role differs.chat/chat-composer.tsx:720role="alert"chat/chat-composer.tsx:725role="alert"chat/chat-composer.tsx:950<label>chat/tool-call-list.tsx:209aria-expandedmemory/consolidate-memory-card.tsx:333role="alert"settings/interface-settings.tsx:181<label>settings/interface-settings.tsx:208<label>settings/settings-workspace.tsx:150<label>settings/settings-workspace.tsx:569<label>The skipped sites are left for after those PRs merge, so this PR doesn't close #2340.
Behaviour differences
None visible, and none to assistive technology. Radix now generates the
aria-controlsid and addsdata-stateto the trigger and the content. The closed content is still unmounted, as with the old conditional render.No regression
Pins first (
cfd84de23), passing onmain's code and on the head:failed-turn-card.pinning.test.tsx(5): starts collapsed with the payload out of view; click toggles, renaming the button and revealing the controlled payload; Enter and Space toggle; the expand-details preference opens it.reasoning-disclosure.pinning.test.tsx(5): the name with its line count, collapsed by default; click toggles the controlled text; the preference opens it.learning-queue.test.tsx(+1): Details opens and closes from the keyboard and hides the sources when closed.settings-card.pinning.test.tsx(2): a failed read is a single alert carrying the message; loading and notices stay polite status lines.worktree-picker.pinning.test.tsx) and both learned-skill alerts (knowledge-workspace.test.tsx).With
main's versions of the six changed feature files, these 6 test files pass 43/43. On the head they pass 45/45 together withcollapsible.test.tsx. No existing test was modified.Mutation proof. Each mutation was applied alone and the focused tests rerun:
<span>, not a LabelhtmlForPixels: 36 captures (9 states × desktop and 390px × light and dark), each compared with Before and with a second Before capture as the noise floor. All 36 are identical to Before.
pixel-report.txtin the asset folder has the counts.Screenshots
Before is
origin/main(1210721be), After is this branch (89c808291), and Prototype isstack-08. Red boxes would mark pixels that differ between Before and After; there are none, because every capture is pixel-identical.Failed turn, collapsed (identical. The prototype has no failed-turn card, so its column is the nearest transcript)
Failed turn, collapsed: desktop dark and 390px, light and dark
failed-turn-closed-desktop-dark:
failed-turn-closed-mobile-light:
failed-turn-closed-mobile-dark:
Failed turn and reasoning summary, both expanded (identical. Prototype column as above)
Failed turn and reasoning summary, both expanded: desktop dark and 390px, light and dark
failed-turn-open-desktop-dark:
failed-turn-open-mobile-light:
failed-turn-open-mobile-dark:
Worktree picker (identical. The prototype's picker has the same bordered option rows)
Worktree picker: desktop dark and 390px, light and dark
worktree-picker-desktop-dark:
worktree-picker-mobile-light:
worktree-picker-mobile-dark:
Worktree picker, a worktree chosen (identical)
Worktree picker, a worktree chosen: desktop dark and 390px, light and dark
worktree-picker-chosen-desktop-dark:
worktree-picker-chosen-mobile-light:
worktree-picker-chosen-mobile-dark:
Learning suggestion, Details closed (identical. Matches the prototype's suggestion card)
Learning suggestion, Details closed: desktop dark and 390px, light and dark
learning-closed-desktop-dark:
learning-closed-mobile-light:
learning-closed-mobile-dark:
Learning suggestion, Details open (identical. Matches the prototype's open Details)
Learning suggestion, Details open: desktop dark and 390px, light and dark
learning-open-desktop-dark:
learning-open-mobile-light:
learning-open-mobile-dark:
Storage settings, failed read banner (identical. The prototype has no failure state here, so its column shows the healthy page)
Storage settings, failed read banner: desktop dark and 390px, light and dark
storage-error-desktop-dark:
storage-error-mobile-light:
storage-error-mobile-dark:
Learned skills, publication notice (identical. The prototype has no publication notice)
Learned skills, publication notice: desktop dark and 390px, light and dark
learned-notice-desktop-dark:
learned-notice-mobile-light:
learned-notice-mobile-dark:
Learned skill dialog, action error (identical. The prototype's dialog has no action error or action buttons)
Learned skill dialog, action error: desktop dark and 390px, light and dark
learned-error-desktop-dark:
learned-error-mobile-light:
learned-error-mobile-dark:
Gates
On the head rebased onto
origin/main(1210721be):task studio:check: passed. Web 1503 tests in 175 files, server 210 tests in 26 files. The first run had one failure in an unrelated file,auth/auth-gate-account.test.tsx, under load. It passed 5 times in a row on its own, and the full rerun passed.task studio:browser: passed. 124 passed, 30 skipped.🤖 Generated with Claude Code