feat(ui): inputs TextField/Select/Switch redesign, remove forbidden primitives (slice 05, issue #440)#461
Conversation
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR modernizes UI primitives and flows: removes legacy Checkbox/InlineInput/RadioGroup, adds a rename dialog, introduces a picker CSS contract and Select/picker wiring, replaces todo checkboxes with icon/spinner rows, adds unified/split diff toggles, updates theme tokens, and expands contract/tests across components. ChangesUI primitives, picker contract, and app wiring
Sequence Diagram(s)sequenceDiagram
actor User
participant Sidebar as Sidebar Panel
participant Dialog as Rename Dialog
participant API as Session API
User->>Sidebar: Double-click session title
Sidebar->>Dialog: Open DialogRenameSession
activate Dialog
Dialog->>Dialog: Auto-focus input, select text
User->>Dialog: Type new name
User->>Dialog: Press Enter
Dialog->>API: Call onConfirm(newName)
API-->>Dialog: Save complete
Dialog->>Sidebar: Close dialog
deactivate Dialog
Sidebar->>User: Display updated session name
sequenceDiagram
participant User
participant SessionReview as Session Review Panel
participant DiffRenderer as Diff Renderer
User->>SessionReview: Click split-view IconButton
SessionReview->>SessionReview: Toggle aria-pressed state
SessionReview->>DiffRenderer: Call onDiffStyleChange("split")
DiffRenderer->>DiffRenderer: Re-render with split layout
DiffRenderer-->>User: Display split diff
User->>SessionReview: Click unified-view IconButton
SessionReview->>SessionReview: Toggle aria-pressed state
SessionReview->>DiffRenderer: Call onDiffStyleChange("unified")
DiffRenderer->>DiffRenderer: Re-render with unified layout
DiffRenderer-->>User: Display unified diff
sequenceDiagram
participant TodoDock as Todo Dock UI
participant TodoState as Todo State
participant Icon as Icon Renderer
TodoState->>TodoDock: Render todo list
loop For each todo
TodoDock->>TodoState: Read status (pending/completed/in_progress)
alt In Progress
TodoState-->>Icon: Render spinner
else Completed
TodoState-->>Icon: Render circle-check icon
else Pending
TodoState-->>Icon: Render circle icon
end
Icon-->>TodoDock: Display status icon
TodoDock-->>TodoDock: Apply opacity/strikethrough based on status
end
TodoDock-->>TodoDock: Display rendered todo list
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Code Review
This pull request refactors several UI components, replacing InlineInput with a ghost variant of TextField, Checkbox with Icon in todo lists, and RadioGroup with IconButton pairs in the session review panel. It also introduces a review-filter variant for the Select component and adds extensive E2E and unit tests. Feedback was provided regarding the TextField component's validation logic, suggesting that it should not implement a fallback for the validation state to avoid masking potential issues that should be handled by downstream consumers.
…forbidden primitives Refs #440 (slice 05 of 11) - TextField: height 28px, medium 13/1 font, critical ring for error state, ghost variant (no border, bottom separator) replacing InlineInput - Select: surface-base container, bg-cream hover, surface-interactive-base selected, review-filter trigger variant (h24 transparent borderless) - Switch: pill track (radius 9999), 12x12 thumb, brand-primary checked color, hover-overlay unchecked hover, duration tokens throughout - list.css: search bar no-border, no-radius, bottom separator (popover pattern) - icon registry: add circle, diff-unified, diff-split - Delete Checkbox, RadioGroup, InlineInput primitives + stories - Migrate consumers: message-part.tsx (Icon), session-todo-dock.tsx (Icon), session-review.tsx (two aria-pressed IconButtons), inline-editor.tsx (ghost TextField) - State-matrix harness: inputs-state-matrix.test.tsx (Select/TextField/Switch) - E2E: text-field-ghost-inline-rename, select-review-filter, todo-toggle specs - Update inlineInputSelector to data-variant=ghost
- styles/index.css: remove stale @import for deleted checkbox/inline-input/radio-group CSS (missing files caused Vite CSS bundle failure, blocking all slice 05 visual changes) - review-tab.tsx: wire diffStyle and onDiffStyleChange from layout context (Show guard required onDiffStyleChange to render the unified/split toggle) - session-review.tsx, session-todo-dock.tsx: drop size/variant from IconButton (slice 04 removed those props; removes type mismatch after rebase) - message-timeline.tsx: remove stale InlineInput import (rebase dropped this hunk) - todo icons: bullet-list (pending), checklist (completed), circle + pw-spin (in_progress) per STANDARDS.md §119 animated ring spec
673c1d2 to
a606681
Compare
…todo - switch.css: add box-sizing:border-box to track; fix thumb translateX 14→12px (previous calc omitted border width causing thumb flush against right edge) - dialog-rename-session.tsx: replace inline ghost TextField with centered Dialog (ghost variant caused jarring row expansion; dialog is dismissable, accessible) - pawwork-sidebar.tsx: wire DialogRenameSession; remove InlineEditor/pendingRenameID - i18n en/zh: add session.rename.title and session.rename.hint keys - review-tab.tsx: revert to diffStyle="unified" only (split intentionally removed) - session-todo-dock.tsx, message-part.tsx: revert todo icons to circle/circle-check (bullet-list/checklist icons need separate icon design work)
…nner - select: hover/highlighted use --bg-cream, selected uses --surface-interactive-base, remove checkmark indicator, item height 32 / padding 0 8 / radius-sm, transition token - dialog-rename-session: 420px max-width, no hint text, footer with --bg-cream / --border-weaker top border / padding 12/24, secondary+primary buttons, autofocus+select - todo dock + message-part: in_progress icon spins with var(--animate-pw-spin) at --brand-primary color per STANDARDS.md L119
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/ui/src/components/select.css (2)
97-99:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
select-contentbackground uses--surface-raisedinstead of--surface-base, breaking the contract testThe PR objective specifies "Select using surface-base container" and the companion
select.test.tsx(lines 70–81) asserts that:
- the
[data-component="select-content"]block contains--surface-base, and- there is no
background-color: var(--surface-raised)directly on the container.Both assertions fail against the current CSS.
🐛 Proposed fix
- background-color: var(--surface-raised); + background-color: var(--surface-base);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/select.css` around lines 97 - 99, The select content container currently uses the wrong variable; update the CSS rule that styles the select content block (the selector referencing select-content / [data-component="select-content"]) to use background-color: var(--surface-base) instead of var(--surface-raised) and ensure no direct background-color: var(--surface-raised) remains on that container so the select.test.tsx assertions (lines checking --surface-base and absence of --surface-raised) pass.
172-202:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftMissing
[data-trigger-style="review-filter"]CSS block —review-filtervariant is completely unstyled
select.tsxemitsdata-trigger-style="review-filter"on both the root and the content element, andselect.test.tsxasserts an entire suite of CSS rules for this selector (height: 24px,--radius-sm,--font-weight-regular,background: transparent). None of these assertions can pass because there is no matching CSS block anywhere in this file.At minimum, the
[data-component="select"][data-trigger-style="review-filter"]and[data-component="select-content"][data-trigger-style="review-filter"]blocks need to be added before merge.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/select.css` around lines 172 - 202, Add CSS rules for the missing review-filter variant: create a [data-component="select"][data-trigger-style="review-filter"] block (set height: 24px; border-radius: var(--radius-sm); background: transparent; font-weight: var(--font-weight-regular); and any other token-based properties the tests expect) and a corresponding [data-component="select-content"][data-trigger-style="review-filter"] block (match content sizing/padding/any variables asserted by select.test.tsx). Ensure the exact attribute selectors used by select.tsx (data-trigger-style="review-filter") are present so the tests can find those rules.packages/ui/src/components/text-field.tsx (1)
82-115:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTie error icon to derived
validationState, not rawerror.The icon visibility is keyed off
local.error, butvalidationStatecan overrideerror(e.g., a caller passingvalidationState="valid"with a staleerrorstring still triggers the icon). Pivot to the derived state so the icon and the KobaltevalidationStatealways agree:Proposed change
- {/* Error icon shown on the right when in invalid state */} - <Show when={local.error}> - <div data-slot="input-error-icon" aria-hidden="true" /> - </Show> + {/* Error icon shown on the right when in invalid state */} + <Show when={validationState() === "invalid"}> + <div data-slot="input-error-icon" aria-hidden="true" /> + </Show>Note this also affects the contract tests (
text-field.test.tsx,inputs-state-matrix.test.tsx) that currently search for the literaldata-slot="input-error-icon"andlocal.error ? "invalid". Those literal substrings still exist; just confirm they remain after the tweak.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/text-field.tsx` around lines 82 - 115, The error icon visibility is currently tied to local.error while the component derives validationState via the validationState() helper, causing mismatches when a caller overrides validationState; update the Show that renders the error icon to check the derived validationState() (e.g., validationState() === "invalid" or truthy invalid-state check) instead of local.error so the icon and Kobalte validationState prop stay in sync; locate the validationState const and the <Show when={local.error}> block in text-field.tsx (around the Kobalte render) and change the condition to use validationState(), keeping the data-slot="input-error-icon" attribute unchanged so existing tests still find that element.
🧹 Nitpick comments (4)
packages/app/e2e/inputs/todo-toggle.spec.ts (1)
7-14: ⚡ Quick winUse fixture-sourced Playwright types/imports in this spec.
Line 7 imports
Pagefrom@playwright/test; in this E2E suite, keep Playwright test utilities/types sourced from../fixtures(or rely on inferred parameter types) to stay consistent with the test harness conventions.As per coding guidelines:
packages/app/e2e/**/*.spec.ts: Import test utilities from../fixturesinstead of@playwright/test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/app/e2e/inputs/todo-toggle.spec.ts` around lines 7 - 14, The spec currently imports Playwright's Page type from "@playwright/test" (symbol Page); update the file to use the project test fixtures instead by removing the direct "@playwright/test" import and sourcing Playwright utilities/types from "../fixtures" (or simply rely on the inferred parameter types provided to test functions) so the spec uses the same fixture-provided test/expect types as the rest of the suite; ensure any references to the Page type are updated to the fixture-provided type or omitted to match the harness conventions.packages/app/e2e/inputs/select-review-filter.spec.ts (1)
37-57: ⚡ Quick winUse SCREAMING_SNAKE_CASE for test constants.
patchis test-constant data and should follow test constant naming convention.♻️ Suggested rename
- const patch = [ + const PATCH_TEXT = [ "*** Begin Patch", "*** Add File: review-filter-test.txt", "+line one", "+line two", "*** End Patch", ].join("\n") @@ - await llm.toolMatch( + await llm.toolMatch( (hit) => bodyText(hit).includes("Your only valid response is one apply_patch tool call."), "apply_patch", - { patchText: patch }, + { patchText: PATCH_TEXT }, ) @@ - `Use this JSON input: ${JSON.stringify({ patchText: patch })}`, + `Use this JSON input: ${JSON.stringify({ patchText: PATCH_TEXT })}`,As per coding guidelines "Use SCREAMING_SNAKE_CASE for constants in tests."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/app/e2e/inputs/select-review-filter.spec.ts` around lines 37 - 57, The test defines a constant named patch in select-review-filter.spec.ts but violates the test constant naming convention; rename the variable to SCREAMING_SNAKE_CASE (e.g., PATCH or REVIEW_FILTER_PATCH) and update all usages (references in the llm.toolMatch call, the JSON input passed to project.sdk.session.prompt, and any other places) to the new name so the test uses a properly cased constant.packages/ui/src/components/inputs-state-matrix.test.tsx (1)
96-100: 💤 Low valueMake the validationState contract assertion whitespace-tolerant.
expect(textFieldSrc).toContain('local.error ? "invalid"')(line 99) is sensitive to formatter changes (e.g., a future Prettier/Biome run inserting/removing whitespace, parenthesization, or moving the conditional onto multiple lines). A regex with\s*will keep the contract intent without breaking on cosmetic edits:Proposed change
- expect(textFieldSrc).toContain('"invalid"') - // The derived accessor must check the error string. - expect(textFieldSrc).toContain("local.error ? \"invalid\"") + // The derived accessor must check the error string and produce "invalid". + expect(textFieldSrc).toMatch(/local\.error\s*\?\s*"invalid"/)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/inputs-state-matrix.test.tsx` around lines 96 - 100, The test "error/invalid: error prop sets validationState to 'invalid'" is brittle because expect(textFieldSrc).toContain("local.error ? \"invalid\"") depends on exact whitespace; change the assertion to use a whitespace-tolerant regex match against textFieldSrc (e.g., use toMatch with a pattern like /local\.error\s*\?\s*["']invalid["']/) so the derived accessor check still verifies the conditional but won't break on formatter/whitespace changes; update the assertion that currently references textFieldSrc and the literal "local.error ? \"invalid\"" accordingly.packages/app/src/pages/layout/inline-editor.tsx (1)
87-91: 💤 Low valueOptional: drop redundant
hideLabelwhenlabel="".
TextFieldonly renders the label whenlocal.labelis truthy (<Show when={local.label}>), sohideLabelhas no effect whenlabel=""is passed. Either drophideLabel(the label is already not rendered) or omitlabel=""and rely on the Show gate. This is purely a clarity nit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/app/src/pages/layout/inline-editor.tsx` around lines 87 - 91, The TextField usage passes label="" and hideLabel which is redundant; remove the hideLabel prop from the TextField invocation in inline-editor (keep label="" or omit label prop if you prefer the Show gate) so the component relies on the existing <Show when={local.label}> behavior; locate the TextField JSX (the ref callback block) and delete the hideLabel attribute to clean up the props.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/app/e2e/inputs/select-review-filter.spec.ts`:
- Around line 16-18: Replace the brittle ID selector used in
page.locator("#right-panel") with a semantic or data-* locator: locate the panel
using a role (e.g., getByRole('region' / 'complementary' with an accessible
name) or a stable data attribute like data-component="right-panel"), then reuse
that locator for tabList and reviewTab (symbols: rightPanel, tabList, reviewTab)
so tests target a resilient semantic/data selector instead of the CSS ID.
In `@packages/app/e2e/inputs/text-field-ghost-inline-rename.spec.ts`:
- Around line 1-39: Update the test to target the dialog-based rename flow
instead of a sidebar-scoped ghost input: change the file header comment to
reflect that the rename uses DialogRenameSession (dialog-based) rather than a
ghost TextField, replace the locator that scopes the input to
sidebar.locator(`[data-session-id="${session.id}"] ${inlineInputSelector}`) with
a dialog-scoped lookup such as page.getByRole("dialog") to find the TextField
(since inlineInputSelector — `[data-component="input"][data-variant="ghost"]`)
no longer applies), and assert/operate on the dialog's input element (the
TextField without the ghost variant) so the locator resolves correctly.
In `@packages/app/src/i18n/en.ts`:
- Around line 1103-1104: The i18n key "session.rename.hint" is defined but not
used; update dialog-rename-session.tsx to wire it into the TextField (use
placeholder={language.t("session.rename.hint")} or equivalent) so the hint
appears, referencing the TextField component and language.t call, or remove the
unused key from en.ts/zh.ts if you prefer to delete dead i18n state.
In `@packages/ui/src/components/select.test.tsx`:
- Around line 93-100: The test "item selected state uses
--surface-interactive-base and --font-weight-medium" is falsely asserting that
--font-weight-medium appears inside the 200-char slice anchored at
"[data-selected]" even though the font-weight declaration lives in the parent
selector [data-slot="select-select-item"] which appears earlier; fix by either
removing the --font-weight-medium assertion from this test (since it's already
covered at the item-block level) or change the slice anchor to locate the parent
selector ([data-slot="select-select-item"]) and then assert that the slice
contains --font-weight-medium, keeping the existing checks for
--surface-interactive-base and [data-selected] intact.
- Around line 63-66: Add the missing CSS rule for the review-filter variant in
select.css: create a selector for [data-trigger-style="review-filter"] and
include the expected properties asserted by the tests (height: 24px, appropriate
border-radius value, expected font-weight, and background: transparent) so the
tests in components/select.test.tsx that look for
'[data-trigger-style="review-filter"]' and assert those properties pass; keep
naming and specificity consistent with the other trigger-style variants already
in select.css.
In `@packages/ui/src/components/switch.test.tsx`:
- Around line 89-91: The test named "checked thumb translate is 14px (28 - 12 -
1 - 1)" is asserting translateX(14px) but the correct value in switch.css is
translateX(12px); update the test assertion in that test (the
expect(css).toContain(...) call) to expect "translateX(12px)" and also update
the test description to reflect the correct calculation (28 - 2px borders - 1px
padding-left - 1px padding-right - 12px thumb = 12px) so the assertion and
message match the stylesheet.
In `@packages/ui/src/components/text-field.css`:
- Around line 137-176: The ghost variant ([data-variant="ghost"] with
[data-slot="input-wrapper"] and [data-slot="input-input"]) currently uses
padding: 10px 12px which makes it ~32–34px tall and can expand/clip the sidebar
inline editor row from inline-editor.tsx/SessionItem; update the styles to
either reduce vertical padding (e.g., 6–8px top/bottom) so it fits ~28px rows or
add a second modifier/class (e.g., [data-ghost="compact"] or .inline-editor) and
apply tighter vertical padding only for that modifier, then verify inline
rename/message-editing rows visually in the sidebar and that the search-bar
still looks acceptable.
---
Outside diff comments:
In `@packages/ui/src/components/select.css`:
- Around line 97-99: The select content container currently uses the wrong
variable; update the CSS rule that styles the select content block (the selector
referencing select-content / [data-component="select-content"]) to use
background-color: var(--surface-base) instead of var(--surface-raised) and
ensure no direct background-color: var(--surface-raised) remains on that
container so the select.test.tsx assertions (lines checking --surface-base and
absence of --surface-raised) pass.
- Around line 172-202: Add CSS rules for the missing review-filter variant:
create a [data-component="select"][data-trigger-style="review-filter"] block
(set height: 24px; border-radius: var(--radius-sm); background: transparent;
font-weight: var(--font-weight-regular); and any other token-based properties
the tests expect) and a corresponding
[data-component="select-content"][data-trigger-style="review-filter"] block
(match content sizing/padding/any variables asserted by select.test.tsx). Ensure
the exact attribute selectors used by select.tsx
(data-trigger-style="review-filter") are present so the tests can find those
rules.
In `@packages/ui/src/components/text-field.tsx`:
- Around line 82-115: The error icon visibility is currently tied to local.error
while the component derives validationState via the validationState() helper,
causing mismatches when a caller overrides validationState; update the Show that
renders the error icon to check the derived validationState() (e.g.,
validationState() === "invalid" or truthy invalid-state check) instead of
local.error so the icon and Kobalte validationState prop stay in sync; locate
the validationState const and the <Show when={local.error}> block in
text-field.tsx (around the Kobalte render) and change the condition to use
validationState(), keeping the data-slot="input-error-icon" attribute unchanged
so existing tests still find that element.
---
Nitpick comments:
In `@packages/app/e2e/inputs/select-review-filter.spec.ts`:
- Around line 37-57: The test defines a constant named patch in
select-review-filter.spec.ts but violates the test constant naming convention;
rename the variable to SCREAMING_SNAKE_CASE (e.g., PATCH or REVIEW_FILTER_PATCH)
and update all usages (references in the llm.toolMatch call, the JSON input
passed to project.sdk.session.prompt, and any other places) to the new name so
the test uses a properly cased constant.
In `@packages/app/e2e/inputs/todo-toggle.spec.ts`:
- Around line 7-14: The spec currently imports Playwright's Page type from
"@playwright/test" (symbol Page); update the file to use the project test
fixtures instead by removing the direct "@playwright/test" import and sourcing
Playwright utilities/types from "../fixtures" (or simply rely on the inferred
parameter types provided to test functions) so the spec uses the same
fixture-provided test/expect types as the rest of the suite; ensure any
references to the Page type are updated to the fixture-provided type or omitted
to match the harness conventions.
In `@packages/app/src/pages/layout/inline-editor.tsx`:
- Around line 87-91: The TextField usage passes label="" and hideLabel which is
redundant; remove the hideLabel prop from the TextField invocation in
inline-editor (keep label="" or omit label prop if you prefer the Show gate) so
the component relies on the existing <Show when={local.label}> behavior; locate
the TextField JSX (the ref callback block) and delete the hideLabel attribute to
clean up the props.
In `@packages/ui/src/components/inputs-state-matrix.test.tsx`:
- Around line 96-100: The test "error/invalid: error prop sets validationState
to 'invalid'" is brittle because expect(textFieldSrc).toContain("local.error ?
\"invalid\"") depends on exact whitespace; change the assertion to use a
whitespace-tolerant regex match against textFieldSrc (e.g., use toMatch with a
pattern like /local\.error\s*\?\s*["']invalid["']/) so the derived accessor
check still verifies the conditional but won't break on formatter/whitespace
changes; update the assertion that currently references textFieldSrc and the
literal "local.error ? \"invalid\"" accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 87ce8cb9-8d24-4142-a487-d2830a4d904b
📒 Files selected for processing (36)
packages/app/e2e/inputs/select-review-filter.spec.tspackages/app/e2e/inputs/text-field-ghost-inline-rename.spec.tspackages/app/e2e/inputs/todo-toggle.spec.tspackages/app/e2e/selectors.tspackages/app/e2e/tsconfig.jsonpackages/app/src/components/dialog-rename-session.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/layout/inline-editor.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/session/composer/session-todo-dock.tsxpackages/app/src/pages/session/message-timeline.tsxpackages/ui/src/components/checkbox.csspackages/ui/src/components/checkbox.stories.tsxpackages/ui/src/components/checkbox.tsxpackages/ui/src/components/icon.tsxpackages/ui/src/components/inline-input.csspackages/ui/src/components/inline-input.stories.tsxpackages/ui/src/components/inline-input.tsxpackages/ui/src/components/inputs-state-matrix.test.tsxpackages/ui/src/components/message-part.tsxpackages/ui/src/components/radio-group.csspackages/ui/src/components/radio-group.stories.tsxpackages/ui/src/components/radio-group.tsxpackages/ui/src/components/select.csspackages/ui/src/components/select.test.tsxpackages/ui/src/components/select.tsxpackages/ui/src/components/session-review.csspackages/ui/src/components/session-review.tsxpackages/ui/src/components/switch.csspackages/ui/src/components/switch.test.tsxpackages/ui/src/components/text-field.csspackages/ui/src/components/text-field.test.tsxpackages/ui/src/components/text-field.tsxpackages/ui/src/styles/index.csspackages/ui/test/components/icon-registry-rename.test.ts
💤 Files with no reviewable changes (11)
- packages/ui/src/components/checkbox.css
- packages/ui/src/components/checkbox.stories.tsx
- packages/ui/src/components/radio-group.tsx
- packages/app/src/pages/session/message-timeline.tsx
- packages/ui/src/components/radio-group.stories.tsx
- packages/ui/src/components/inline-input.tsx
- packages/ui/src/components/inline-input.stories.tsx
- packages/ui/src/components/checkbox.tsx
- packages/ui/src/components/inline-input.css
- packages/ui/src/components/radio-group.css
- packages/ui/src/styles/index.css
…name, arc spinner - select.css: item font 500→400 (--font-weight-regular) per STANDARDS L30; container background --surface-raised→--surface-base per DESIGN.md L188; add review-filter trigger variant (h24, radius-sm, 13/400, transparent bg) - switch.css: border→box-shadow inset so padding-only track gives 26px travel, checked thumb translate 12→14px; all states migrated to box-shadow ring - theme.css + pawwork.json: dark --border-weak #2c2823→#36322e, --border-weaker #25211d→#2f2b28 — recalibrate for surface-raised context - pawwork-sidebar.tsx: restore double-click rename (onDblClick opens dialog) - session-todo-dock.tsx + message-part.tsx: replace Icon spin with CSS border arc (1.5px border + border-top brand-primary + pw-spin) per STANDARDS L119 - en.ts + zh.ts: remove orphan session.rename.hint key - select.test.tsx: fix wrong --font-weight-medium assertion in selected state
switch.css: revert to real border (not box-shadow) per inputs.html reference; thumb default color --fg-on-brand → --fg-weak; remove thumb border + shadow; track unchecked bg --surface-base → --bg-base; translate 14px → 12px (content area = 28 - 2×border - 2×padding = 24px, max travel = 12px) session-todo-dock + message-part: wrap 13px spinner in 16×16 flex container to match Icon size-small dimensions, eliminating 3px layout jump on status change
default trigger: height 28px, border 1px --border-weak, bg --bg-base, font 500 13px/1, focus 1px brand-primary + 3px halo, hover --bg-cream settings trigger: 32px→28px, border-radius 6px→var(--radius-md), font-size 14px→13px, remove justify-content:flex-end, transparent border settings content popup: border-radius 8px→var(--radius-md) animation timing: 0.15s hardcoded→var(--duration-base) icon transition: 0.1s hardcoded→var(--duration-base)
- TextField: move error icon from wrapper slot to ErrorMessage row as alert-triangle Icon; add disabled/readonly CSS states; fix placeholder color to --fg-weaker - Select: fix settings trigger height (28px) and radius (--radius-md); add focus ring for default trigger; reduce settings item override to font-size only; clean up data-variant expanded branches - icon: add alert-triangle for TextField error state - tests: update error-icon assertions to check alert-triangle Icon
- select: unify trigger icon to chevron-down everywhere; remove the settings-trigger icon badge (background-color + border-radius) - toast: change title/description/action/icon from color:--fg-on-brand to color:inherit so dark mode (light cream bg) shows readable dark text - switch: change unchecked and disabled track background from --bg-base to --surface-raised so the track is visible on --bg-base page background in dark mode (#2a2622 vs #1a1714)
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/ui/src/components/toast.css (1)
88-97: ⚡ Quick win
color: inheritcascade is consistent;:first-childoverride is now redundantThe switch from
var(--fg-on-brand)toinheritis correct throughout — every child will resolve to the parent toast'scolor: var(--bg-cream).One small cleanup opportunity: the
&:first-child { color: inherit; }block at lines 178–180 is now identical to the base[data-slot="toast-action"]rule at line 167, making it a no-op. If it was previously used to override a different color for the first action button, it can be removed entirely.🧹 Proposed cleanup
[data-slot="toast-action"] { ... color: inherit; ... &:hover { text-decoration: underline; } - - &:first-child { - color: inherit; - } }Also applies to: 115-130, 132-147, 149-181
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/toast.css` around lines 88 - 97, The &:first-child color override is redundant because child icons/buttons already use color: inherit (see [data-slot="toast-icon"] and its [data-component="icon"] and the base [data-slot="toast-action"] rule); remove the redundant &:first-child { color: inherit; } block from the toast action rules so the stylesheet is cleaner and behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/components/inputs-state-matrix.test.tsx`:
- Around line 144-152: The "checked: Kobalte manages aria-checked via
checked/defaultChecked prop" test uses splitMatch =
switchSrc.match(/splitProps\(props,\s*\[([^\]]+)\]/) and then accesses
splitMatch![1] without a null guard; add the same defensive assertion used in
the "disabled" test by asserting splitMatch is not null (e.g.,
expect(splitMatch).not.toBeNull()) before accessing splitMatch[1], so the regex
failure yields a clear test assertion rather than a TypeError.
In `@packages/ui/src/components/switch.css`:
- Around line 88-109: The active state selector is too specific and overrides
the checked track color; add a checked-specific active selector to preserve
brand styling when pressed by adding a rule targeting
&[data-checked]:active:not([data-disabled],[data-readonly])
[data-slot="switch-control"] that sets background-color and border-color to the
brand-active values (mirroring the checked hover rule) so the checked track
keeps --brand-primary (or its hover/active variant) during pointer-down; update
CSS around the existing &[data-checked] [data-slot="switch-control"] and
&:active... [data-slot="switch-control"] rules to include this new selector.
---
Nitpick comments:
In `@packages/ui/src/components/toast.css`:
- Around line 88-97: The &:first-child color override is redundant because child
icons/buttons already use color: inherit (see [data-slot="toast-icon"] and its
[data-component="icon"] and the base [data-slot="toast-action"] rule); remove
the redundant &:first-child { color: inherit; } block from the toast action
rules so the stylesheet is cleaner and behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: baed61c5-5cd3-40de-9441-cdc8ed62656f
📒 Files selected for processing (19)
packages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/session/composer/session-todo-dock.tsxpackages/ui/src/components/icon.tsxpackages/ui/src/components/inputs-state-matrix.test.tsxpackages/ui/src/components/message-part.tsxpackages/ui/src/components/select.csspackages/ui/src/components/select.test.tsxpackages/ui/src/components/select.tsxpackages/ui/src/components/switch.csspackages/ui/src/components/switch.test.tsxpackages/ui/src/components/text-field.csspackages/ui/src/components/text-field.test.tsxpackages/ui/src/components/text-field.tsxpackages/ui/src/components/toast.csspackages/ui/src/styles/theme.csspackages/ui/src/theme/themes/pawwork.jsonpackages/ui/test/components/icon-registry-rename.test.ts
✅ Files skipped from review due to trivial changes (4)
- packages/ui/src/theme/themes/pawwork.json
- packages/app/src/i18n/zh.ts
- packages/ui/src/components/switch.test.tsx
- packages/ui/src/components/text-field.test.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/ui/test/components/icon-registry-rename.test.ts
- packages/ui/src/components/select.tsx
- packages/app/src/pages/layout/pawwork-sidebar.tsx
- packages/app/src/i18n/en.ts
- packages/ui/src/components/select.test.tsx
- packages/app/src/pages/session/composer/session-todo-dock.tsx
- packages/ui/src/components/text-field.tsx
- packages/ui/src/components/message-part.tsx
- settings trigger: remove vertical padding (6px top/bottom) that clipped descenders in 28px-height container with line-height:1 - list search: suppress ghost TextField border-bottom and padding inside list-search-input; also fix stale compound selector (&[data-slot="input-input"]) to child selector so the line-height constraint actually applies
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/ui/src/components/select.css (1)
49-58: ⚡ Quick winRedundant settings trigger-icon block — safe to delete.
After changing
heightto16pxon line 51, this entire block is now byte-for-byte identical to the base[data-slot="select-select-trigger-icon"]block (lines 11–19). Because the settings variant nests inside[data-component="select"], it already inherits those base declarations; the duplicate adds no override and will silently diverge if the base changes again.♻️ Proposed removal
[data-slot="select-select-trigger-icon"] { - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: var(--fg-weak); - transition: transform 0.1s ease-in-out; - } - &[data-slot="select-select-trigger"]:hover:not(:disabled),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/select.css` around lines 49 - 58, Remove the redundant CSS block nested under the settings variant that targets [data-slot="select-select-trigger-icon"]; since it is now byte-for-byte identical to the base [data-slot="select-select-trigger-icon"] declarations (and is inherited via the [data-component="select"] wrapper), delete the duplicate block to avoid unnecessary duplication and future drift—locate the nested rule inside the settings variant and remove it, leaving only the base selector's declarations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/ui/src/components/select.css`:
- Around line 49-58: Remove the redundant CSS block nested under the settings
variant that targets [data-slot="select-select-trigger-icon"]; since it is now
byte-for-byte identical to the base [data-slot="select-select-trigger-icon"]
declarations (and is inherited via the [data-component="select"] wrapper),
delete the duplicate block to avoid unnecessary duplication and future
drift—locate the nested rule inside the settings variant and remove it, leaving
only the base selector's declarations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 027e2f58-5f13-4443-9cba-f8f279a933b8
📒 Files selected for processing (2)
packages/ui/src/components/list.csspackages/ui/src/components/select.css
when no groupBy prop is provided all options collapse into one group with key "" — Kobalte still renders the Section as a <li role= "presentation"> which occupies ~19px line-height in the flex column; with only 2 items (Language) the phantom header is visually significant vs 7+ items (Theme) where it's barely noticeable fix: return null from sectionComponent when category is ""
CJK fallback fonts (PingFang SC on macOS) have large intrinsic line-gap that pushes flex item visual height above the CSS height value; also, the adjacent-sibling margin-top rule was adding effective visual height per item - item: add flex-shrink:0 + min-height/max-height triple-constraint + overflow:hidden to definitively clamp each item to 32px - list: replace margin-top:2px sibling rule with gap:2px on the flex container (cleaner, no selector needed)
- drop min-width on settings trigger so width adapts to content - add line-height to trigger-value so descenders fit in 28px height - override Button secondary hover-overlay so settings hover stays solid - drop triggerStyle min-width: 220px on Color scheme select
- select base trigger left padding 8 → 12 to match inputs.html L80 - select content min-width 104 → 184 + box-shadow ring + floating per modals-overlays.html L59 popover container spec - select item static color fg-strong → fg-base, hover/highlighted/ selected switch to fg-strong per modals-overlays.html L60-61 - review-filter trigger line-height 1.5 → 1, add fg-base color and fg-strong on hover/expanded per right-pane.html L227-231 - text-field: split ghost variant — keep ghost for popover-embedded list search bars, add new "inline" variant for in-place rename with inset 1px brand-primary focus ring per DESIGN.md L220 - text-field: add selectOnFocus prop for inline rename auto-select-all - inline-editor: switch from variant="ghost" to variant="inline" with selectOnFocus - select.test: widen review-filter slice window to 600 chars
- drop card chrome (radius-md, surface-base bg) on list-search - bump padding 8 → 10/12 to match modals-overlays.html .cmd-input - add 1px border-weaker bottom divider per DESIGN.md L188 now slice 06 popover container is in, list inside picker dialogs no longer needs the inner card and aligns visually with cmd palette.
popover container already provides framing via ring + shadow; an extra inner border-bottom under the search row read as visual clutter and chopped the model list from the search input.
Drop the box-shadow border ring on settings select trigger hover/expanded so the trigger no longer reads as a button. Pull dropdown item hover and selected to --surface-interactive-base per session-row.html (hover/active 合一); selected differentiates via medium font weight.
Previously row hover used --surface-raised and active used --surface-base-active, which felt different from the locked session-row preview. Pull session row, new-session button, search button, show-more, search-history, and the workspace collapsible trigger all to --surface-interactive-base so hover/active 合一 matches the warm-cream standard used everywhere else.
Session rename now opens DialogRenameSession instead of an inline ghost TextField. Rename the spec file accordingly, locate the dialog by data-component, and assert it closes after Enter saves.
Previously hover and selected both used --surface-interactive-base, distinguished only by font-weight. That fails when a dropdown holds both a hovered row and a selected row at the same time — the user can't tell them apart. Pull hover back to --bg-cream and keep selected on --surface-interactive-base plus weight 500. Hover-on-selected stays selected (no double overlay), matching the VSCode list.hoverBackground vs list.activeSelectionBackground convention.
The DialogRenameSession contract awaits onConfirm(next) and only closes on success, but the three sidebar wires passed (next) => void props.onRenameSession(...). The void operator coerces the promise to undefined synchronously, so the dialog closed before the rename actually settled and any rejection became an unhandled promise rejection. Drop the void so the dialog awaits the rename, and dedupe the three identical dialog.show calls into a single openRenameDialog helper.
If the upstream rename rejects, the await previously propagated as an unhandled rejection and the dialog stayed in saving=false with no user feedback. Catch and log so the failure is observable; the finally still clears saving so the user can retry.
Kobalte's Select.Trigger already sets aria-haspopup="listbox" per the WAI ARIA Listbox pattern. The variant control migrated to Select still carried "aria-haspopup": "menu" from the old hand-written Popover, which mislabels the popup as a menu to screen readers. Remove the override so Kobalte's correct value applies.
The contract suppressed both :focus and :focus-visible outlines, so a keyboard user landing on an item via Tab without triggering data-highlighted saw no focus indicator — the data-highlighted background (rgba(0,0,0,0.04)) is far below WCAG 2.4.11's 3:1 minimum. Keep :focus suppressed (avoids the default ring flashing on pointer focus), and add a styled outline on :focus-visible when no other indicator is already painted (highlighted / active).
The dark-mode thumb rules used the background shorthand and came after the [data-disabled] thumb rule with equal specificity, so source order let the dark override beat the disabled visual — disabled in dark mode rendered #f3ede4 instead of var(--icon-disabled). Narrow the dark selector with :not([data-disabled]) and use background-color so the disabled rule wins.
Mirror the disabled test's defensive check so a regex mismatch yields a clear assertion failure instead of a TypeError on splitMatch![1].
The empty-label + hideLabel was a typing workaround. aria-label states the field's purpose to assistive tech directly, which is what the accessible-name a11y check actually wants. Apply to the rename dialog and the inline editor.
- select-review-filter: rename patch -> PATCH_TEXT to follow the
SCREAMING_SNAKE_CASE convention documented in e2e/AGENTS.md
- session-rename-dialog: scope the input lookup with getByRole("textbox")
so future secondary fields in the dialog don't accidentally match
- inputs-state-matrix: regex match for the validationState ternary so
formatter whitespace changes don't break the contract test
- model-picker-hotfix: assert data-picker-item attribute name only;
consumers spreading it as data-picker-item or data-picker-item={x}
still meet the contract
After the toast color migration to inherit, the &:first-child rule sets exactly the same color as the surrounding rule, so it's a no-op.
c9e1cab to
1fd031f
Compare
Review unified/split IconButton pair was gated on onDiffStyleChange in SessionReview but the app layer never passed one, so the toggle never rendered. Add a per-mount diffStyle signal and forward both diffStyle and setter so the new e2e covers the actual UI.
Sidebar session rename moved to a global Dialog, but the existing
sidebar-session-organization spec still asserted the old inline input.
Switch the rename portion to drive the Dialog and fix inlineInputSelector
to match inline-editor's actual data-variant ("inline", not "ghost") so
workspace and session-list inline rename specs stay green.
DialogRenameSession used to silently log to console on failure, leaving users unsure why save did nothing. Surface a toast with retry guidance and add the matching i18n strings.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/ui/src/components/inputs-state-matrix.test.tsx (1)
55-57: ⚡ Quick winSeparate
[data-picker-item]/[data-selected]checks don't verify the combined selector.
pickerCssis checked for[data-picker-item]and[data-selected]as independent strings. These assertions pass even if the two selectors exist in completely unrelated rules with no interaction. The actual rule (confirmed by context snippet 4 —picker.css:72) is[data-picker-item]:where([data-selected]). A direct match on the combined selector would give a meaningful contract guarantee.♻️ Proposed tightening
- expect(pickerCss).toContain("[data-picker-item]") - expect(pickerCss).toContain("[data-selected]") + expect(pickerCss).toContain("[data-picker-item]:where([data-selected])")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/inputs-state-matrix.test.tsx` around lines 55 - 57, The test currently asserts pickerCss contains "[data-picker-item]" and "[data-selected]" separately which doesn't guarantee the combined rule exists; update the assertion to check for the exact combined selector used in the stylesheet (e.g., "[data-picker-item]:where([data-selected])") by replacing the two separate expects with a single expect on pickerCss for that combined selector (keep the "--row-active-overlay" assertion as-is if still required) so the test enforces the actual rule interaction produced by the picker CSS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/app/e2e/inputs/select-review-filter.spec.ts`:
- Around line 34-35: Remove the redundant project.trackSession(session.id) call
inside the withSession callback: since withSession manages the session lifecycle
and calls cleanupSession, delete the project.trackSession invocation (in the
withSession callback in select-review-filter.spec.ts) so sessions created by
withSession are not double-tracked; use project.trackSession only for resources
created outside the fixture.
In `@packages/ui/src/components/inputs-state-matrix.test.tsx`:
- Line 32: The test loads text-field.css into the variable textFieldCss but
never uses it; either remove that unused readFileSync call or add assertions in
the describe("state-matrix: TextField") block to verify the CSS contracts.
Specifically, if keeping it, add at least one selector check that uses
textFieldCss (similar to switchCss/selectCss tests) to assert expected selectors
like [data-invalid], ghost/inline variants, or the error ring are present;
otherwise delete the textFieldCss declaration to remove dead code. Locate
textFieldCss and the describe("state-matrix: TextField") block to implement the
change.
---
Nitpick comments:
In `@packages/ui/src/components/inputs-state-matrix.test.tsx`:
- Around line 55-57: The test currently asserts pickerCss contains
"[data-picker-item]" and "[data-selected]" separately which doesn't guarantee
the combined rule exists; update the assertion to check for the exact combined
selector used in the stylesheet (e.g.,
"[data-picker-item]:where([data-selected])") by replacing the two separate
expects with a single expect on pickerCss for that combined selector (keep the
"--row-active-overlay" assertion as-is if still required) so the test enforces
the actual rule interaction produced by the picker CSS.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3dd8765f-5f66-4882-85cf-df17149228b9
📒 Files selected for processing (21)
packages/app/e2e/inputs/select-review-filter.spec.tspackages/app/e2e/inputs/session-rename-dialog.spec.tspackages/app/e2e/selectors.tspackages/app/e2e/sidebar/sidebar-session-organization.spec.tspackages/app/src/components/dialog-rename-session.tsxpackages/app/src/components/model-picker-hotfix.test.tspackages/app/src/components/prompt-input.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/layout/inline-editor.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/session/review-tab.test.tsxpackages/app/src/pages/session/review-tab.tsxpackages/ui/src/components/inputs-state-matrix.test.tsxpackages/ui/src/components/picker.csspackages/ui/src/components/select.csspackages/ui/src/components/select.test.tsxpackages/ui/src/components/select.tsxpackages/ui/src/components/switch.csspackages/ui/src/components/text-field.csspackages/ui/src/components/toast.css
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/app/src/i18n/en.ts
- packages/app/src/i18n/zh.ts
- packages/ui/src/components/switch.css
page.goto('/') skipped the gotoSession fixture's seedStorage step, so
SDK-created sessions never showed up in the sidebar and the rename row
hover timed out. Switch to the gotoSession fixture so storage gets the
backend URL and the seeded session resolves.
filter({ has: locator('[data-state="pending"]') }) requires a descendant
match, but data-state is on the item element itself. Switch to a direct
attribute selector so the pending and completed items resolve.
The right panel tablist hides Review behind an overflow menu when the tab strip is narrow (same shape as file-tree.spec.ts). The previous direct getByRole tab lookup timed out. Reuse openRightPanel and fall back to the overflow menuitem when the tab is not directly visible.
Replaced Checkbox primitive with Icon + span in slice 05; the wrapper div lost the flex/gap that Checkbox provided, leaving icon and text without alignment or spacing. Adds display/flex + gap to the todo-item slot so the visual matches the prior Checkbox layout.
Cancel button is disabled while the rename request is in flight, but the TextField Escape handler still closed the dialog unconditionally. Gates Escape on saving() so an in-flight save can't be silently cancelled out of the dialog.
withSession already calls cleanupSession in its finally block, so the extra project.trackSession inside the callback is double-tracking. Per the fixture's own contract, trackSession is only for sessions created outside the helper.
The TextField suite loaded text-field.css but never referenced it. Added a single CSS-contract assertion mirroring the Switch and Select suites, so the file load is no longer dead code.
Root cause: - PR #464 fixed the original composer/timeline mismatch by replacing the session timeline medium-width cap with explicit `md:max-w-[800px]`. - The #440 UI slice merge chain later regressed that fix. PR #461 first changed both timeline caps back to `md:max-w-200`, which resolves to about 650px with PawWork's 13px root font. - PR #460 then kept that same `message-timeline.tsx` state while removing dead delete-session code, so #479 surfaced as the composer still appearing wider than the reading column at right-panel-hidden desktop widths. Fix: - Restore both session timeline medium-width caps to `md:max-w-[800px]`. - Keep the existing 2xl timeline cap and composer caps unchanged, preserving the prior #217/#456 intent that composer remains inset at very wide desktop sizes. - Add a focused E2E checkpoint for #479: viewport 1280x900 with `#right-panel[aria-hidden="true"]`, then reuse the existing timeline/composer bounding-box contract. Verification: - Red check: temporarily restored the old `md:max-w-200` cap and confirmed the focused E2E failed at the new 1280px/right-panel-hidden checkpoint with timeline 651px vs composer 720px. - Focused E2E passed: `bun --cwd packages/app test:e2e e2e/app/session.spec.ts -g "session timeline visible content is not narrower than composer shell"`. - Typecheck passed: `bun --cwd packages/app typecheck`. - Diff check passed: `git diff --check`. - PR CI passed, including typecheck, unit-app, unit-opencode, unit-desktop, e2e-artifacts, smoke-macos-arm64, CodeQL, dependency review, commit lint, and PR title lint. Follow-up: - No immediate product follow-up is required for #479. - A separate architecture issue may still be worth opening later for the broader session width contract, because timeline, message item, and composer width values are still split across files and have accumulated breakpoint-specific tests. Closes #479.
Summary
TextField,Select,SwitchCSS to match DESIGN.md L176/L188/L218: height 28px, medium 13/1 font, critical ring error state, pill Switch track, brand-primary checked color, surface-interactive-base hover/selected, sidebar-row-aligned dropdown itemsghostandinlinevariants toTextFieldsoInlineInputcan be removed:ghostis used by popover-embedded search bars,inlineis used by the workspace-rename and message inline-edit pathsDialog(DialogRenameSession), replacing the original inline editor in the sidebar rowlist.csssearch bar to match popover-embedded pattern (no border, no radius, no inner divider)Checkbox,RadioGroup(SegmentedControl),InlineInput— with full consumer migrationcircle,diff-unified,diff-splitto icon registrybox-shadowring on Select trigger hover; aligns Select item hover and sidebar row hover/active to--surface-interactive-baseper the lockedsession-row.htmlstandard (hover/active 合一, weight 500 +--fg-strongfor selected)Refs #440 (slice 05 of 11)
Why
DESIGN.md L176/L188/L218 alignment. The existing inputs used wrong heights (32px), inconsistent hover tokens, and hard-coded durations. Checkbox/RadioGroup/InlineInput are explicitly banned in DESIGN.md and replaced with semantic Icon + IconButton patterns. After in-app smoke, the Select trigger hover felt button-y and dropdown items used a different warm tone than the sidebar — both pulled to the locked
session-row.htmlstandard so all selectable surfaces share one hover/active token.Related Issue
Refs #440
Human Review Status
Pending. Maintainer should review the final diff and make the merge decision.
Review Focus
--brand-primarychecked color (visual regression risk)--surface-interactive-basewith weight 500 +--fg-strongdistinguishing the selected option--surface-interactive-baseacrosspawwork-sidebar.tsx,sidebar-items.tsx,sidebar-workspace.tsxDialog(DialogRenameSession) — verify open / Enter-to-save / Esc-to-cancel; workspace rename still uses theinlineTextField variantsession-review.tsxRadioGroup → twoaria-pressedIconButtons — verify toggle state is independent per sessioncircle/circle-check) replacing Checkbox — verifydata-stateattribute on wrapper divRisk Notes
WorkspaceChip) is a hand-rolled Popover, not the Select primitive; it is intentionally not retouched in this slice (its missing tooltip is deferred to slice 09 prompt-input)context-menu.tsxanddropdown-menu.tsxretain their KobalteRadioGroup/CheckboxItemcomposites — those wrap@kobalte/core/dropdown-menu, not the deleted@kobalte/core/segmented-controlHow To Verify
Screenshots or Recordings
Screenshots pending — run
bun run dev:desktopand check:Dialogopens, Enter saves, Esc cancelsLight + dark required before merge.
Checklist
Summary by CodeRabbit
New Features
Improvements
Tests