Skip to content

feat(ui): inputs TextField/Select/Switch redesign, remove forbidden primitives (slice 05, issue #440)#461

Merged
Astro-Han merged 57 commits into
devfrom
claude/i440-slice-05-inputs
May 6, 2026
Merged

feat(ui): inputs TextField/Select/Switch redesign, remove forbidden primitives (slice 05, issue #440)#461
Astro-Han merged 57 commits into
devfrom
claude/i440-slice-05-inputs

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Rewrites TextField, Select, Switch CSS 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 items
  • Adds ghost and inline variants to TextField so InlineInput can be removed: ghost is used by popover-embedded search bars, inline is used by the workspace-rename and message inline-edit paths
  • Session rename moves to a Dialog (DialogRenameSession), replacing the original inline editor in the sidebar row
  • Fixes list.css search bar to match popover-embedded pattern (no border, no radius, no inner divider)
  • Deletes forbidden primitives: Checkbox, RadioGroup (SegmentedControl), InlineInput — with full consumer migration
  • Adds circle, diff-unified, diff-split to icon registry
  • Adds state-matrix contract tests and 3 golden-path E2E specs (rename dialog, review toggle, todo dock)
  • Round-2 polish (post-smoke): drops the button-feel box-shadow ring on Select trigger hover; aligns Select item hover and sidebar row hover/active to --surface-interactive-base per the locked session-row.html standard (hover/active 合一, weight 500 + --fg-strong for 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.html standard 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

  • Switch pill track and --brand-primary checked color (visual regression risk)
  • Select trigger hover lightness (no border ring on hover); item hover/selected both use --surface-interactive-base with weight 500 + --fg-strong distinguishing the selected option
  • Sidebar row hover/active aligned to --surface-interactive-base across pawwork-sidebar.tsx, sidebar-items.tsx, sidebar-workspace.tsx
  • Session rename Dialog (DialogRenameSession) — verify open / Enter-to-save / Esc-to-cancel; workspace rename still uses the inline TextField variant
  • session-review.tsx RadioGroup → two aria-pressed IconButtons — verify toggle state is independent per session
  • Todo Icon (circle / circle-check) replacing Checkbox — verify data-state attribute on wrapper div

Risk Notes

  • PR fix(app): restore model picker hover and tooltip contrast #453 hotfix List row hover tokens are untouched; only the List search bar container and the sidebar row hover/active tokens changed in this PR
  • Picker dialogs (model/directory/MCP) visual unchanged — slice 06 scope (slice 06 has merged)
  • The workspace dropdown trigger in the composer (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.tsx and dropdown-menu.tsx retain their Kobalte RadioGroup/CheckboxItem composites — those wrap @kobalte/core/dropdown-menu, not the deleted @kobalte/core/segmented-control

How To Verify

bun --cwd packages/ui test
  250 pass / 2 fail (pre-existing: apply-patch-file, session-diff)

bun run --cwd packages/app test:unit
  907 pass / 0 fail

bun run --cwd packages/app typecheck
  0 errors

bun run --cwd packages/ui typecheck
  0 errors

E2E tsconfig check (new inputs/ specs):
  0 errors in e2e/inputs/*.spec.ts

Screenshots or Recordings

Screenshots pending — run bun run dev:desktop and check:

  • Settings page: Switch idle/checked/disabled, TextField normal/error, Select trigger hover (no border ring) and dropdown items (warm cream hover/selected, weight-500 selected)
  • Sidebar: row hover and active both warm cream
  • Sidebar session rename: Dialog opens, Enter saves, Esc cancels
  • Review panel: unified/split IconButton toggle
  • Todo dock: circle / circle-check icons

Light + dark required before merge.

Checklist

  • Human review status is stated above as pending, approved, or not required
  • I linked the related issue, or stated why there is no issue
  • This PR has type, scope, and priority labels, or I requested maintainer labeling
  • I described the review focus and any meaningful risks
  • I listed the relevant verification steps and the key result for each
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope
  • I manually checked visible UI or copy changes when needed, with screenshots or recordings
  • I considered macOS and Windows impact for desktop, packaging, updater, signing, paths, shell, or permissions changes
  • I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant
  • I reviewed the final diff for unrelated changes and suspicious dependency changes
  • I am targeting `dev`, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • New Features

    • Modal-based session rename dialog with Save/Cancel and keyboard support
    • Unified/split diff-style toggle in session review
    • Picker-driven controls for several model/workspace selectors
  • Improvements

    • Clearer todo visuals (pending/completed states with distinct icons/spinner)
    • TextField: improved error UI and optional select-on-focus
    • Updated select/switch/list styling and sidebar hover/row visuals
  • Tests

    • New E2E and unit tests validating UI contracts and picker/select behavior

@Astro-Han Astro-Han added enhancement New feature or request P2 Medium priority ui Design system and user interface labels May 5, 2026
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Astro-Han has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 12 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bd2ed812-f4d1-44e1-b612-ec44e3291720

📥 Commits

Reviewing files that changed from the base of the PR and between 1230a8f and 7154319.

📒 Files selected for processing (6)
  • packages/app/e2e/inputs/select-review-filter.spec.ts
  • packages/app/e2e/inputs/session-rename-dialog.spec.ts
  • packages/app/e2e/inputs/todo-toggle.spec.ts
  • packages/app/src/components/dialog-rename-session.tsx
  • packages/ui/src/components/inputs-state-matrix.test.tsx
  • packages/ui/src/components/message-part.css
📝 Walkthrough

Walkthrough

This 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.

Changes

UI primitives, picker contract, and app wiring

Layer / File(s) Summary
Data Shape / Types
packages/ui/src/components/icon.tsx, packages/ui/src/components/list.tsx, packages/ui/src/components/select.tsx
Icon registry gains alert-triangle, circle, diff-unified, diff-split; ListProps adds selectedIcon; SelectProps.triggerVariant expands to `"default"
Core Implementation
packages/ui/src/components/text-field.tsx, packages/app/src/pages/session/review-tab.tsx, packages/app/src/pages/session/composer/session-todo-dock.tsx
TextField: added selectOnFocus prop, derived validationState, and error alert-triangle icon; SessionReview wired to dynamic diffStyle signal; Todo dock replaced Checkbox rows with status-driven icon/spinner markup.
Styling / Contracts
packages/ui/src/components/picker.css, packages/ui/src/components/select.css, packages/ui/src/components/text-field.css, packages/ui/src/components/switch.css, packages/ui/src/components/list.css, packages/ui/src/components/session-review.css, packages/ui/src/styles/theme.css, packages/ui/script/colors.txt
New picker.css contract; select/text-field/switch/list/session-review CSS updated (layout, tokens, sizes); theme tokens updated and new overlay tokens added; checkbox/inline-input/radio-group CSS removed.
Deleted Primitives / Stories
packages/ui/src/components/checkbox.*, inline-input.*, radio-group.*
Removed Checkbox, InlineInput, RadioGroup modules, related CSS and Storybook stories.
Wiring / Integration
packages/app/src/components/prompt-input.tsx, packages/app/src/components/prompt-input/workspace-chip.tsx, packages/app/src/pages/layout/pawwork-sidebar.tsx
Model/workspace controls opt into picker contract (data-picker-*), PromptInput switches variant control to Select, PawworkSidebar rename flow replaced with dialog via useDialog().
New Component
packages/app/src/components/dialog-rename-session.tsx
Added DialogRenameSession component implementing modal rename UI with save/cancel, trim validation, autofocus/select-on-focus, and toast on error.
Tests / E2E / Contracts
packages/ui/src/components/*.{test,tsx}, packages/app/e2e/inputs/*, packages/ui/test/components/icon-registry-rename.test.ts
Added/updated many contract-style Bun tests (select, picker, text-field, switch, inputs-state-matrix), icon-registry tests; new Playwright e2e specs for rename dialog, todo toggle, and review filter; updated e2e selectors/tsconfig.
App CSS & Theming
packages/ui/src/styles/index.css, packages/ui/src/styles/tailwind/colors.css, packages/ui/src/theme/themes/pawwork.json, packages/app/src/index.css, packages/app/src/pages/layout/sidebar-items.tsx
Imported new component styles, removed obsolete imports, updated tailwind variables and pawwork theme overrides, adjusted sidebar titlebar gradient and row hover/active styles to use new overlay tokens.

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
Loading
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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

app

"I'm a rabbit, hopping through the code,
Dialogs bloom where inline once strode.
Icons circle todos, toggles click and flip,
Picker rules and themes get a fresh new script.
Hoppy commits — a tidy UI hop!" 🐇

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/i440-slice-05-inputs

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/ui/src/components/text-field.tsx
Astro-Han added 2 commits May 6, 2026 00:14
…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
@Astro-Han Astro-Han force-pushed the claude/i440-slice-05-inputs branch from 673c1d2 to a606681 Compare May 5, 2026 16:20
Astro-Han added 2 commits May 6, 2026 00:34
…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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-content background uses --surface-raised instead of --surface-base, breaking the contract test

The PR objective specifies "Select using surface-base container" and the companion select.test.tsx (lines 70–81) asserts that:

  1. the [data-component="select-content"] block contains --surface-base, and
  2. 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 lift

Missing [data-trigger-style="review-filter"] CSS block — review-filter variant is completely unstyled

select.tsx emits data-trigger-style="review-filter" on both the root and the content element, and select.test.tsx asserts 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 win

Tie error icon to derived validationState, not raw error.

The icon visibility is keyed off local.error, but validationState can override error (e.g., a caller passing validationState="valid" with a stale error string still triggers the icon). Pivot to the derived state so the icon and the Kobalte validationState always 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 literal data-slot="input-error-icon" and local.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 win

Use fixture-sourced Playwright types/imports in this spec.

Line 7 imports Page from @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 ../fixtures instead 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 win

Use SCREAMING_SNAKE_CASE for test constants.

patch is 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 value

Make 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 value

Optional: drop redundant hideLabel when label="".

TextField only renders the label when local.label is truthy (<Show when={local.label}>), so hideLabel has no effect when label="" is passed. Either drop hideLabel (the label is already not rendered) or omit label="" 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

📥 Commits

Reviewing files that changed from the base of the PR and between f8a9213 and b3bab1d.

📒 Files selected for processing (36)
  • packages/app/e2e/inputs/select-review-filter.spec.ts
  • packages/app/e2e/inputs/text-field-ghost-inline-rename.spec.ts
  • packages/app/e2e/inputs/todo-toggle.spec.ts
  • packages/app/e2e/selectors.ts
  • packages/app/e2e/tsconfig.json
  • packages/app/src/components/dialog-rename-session.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/layout/inline-editor.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/ui/src/components/checkbox.css
  • packages/ui/src/components/checkbox.stories.tsx
  • packages/ui/src/components/checkbox.tsx
  • packages/ui/src/components/icon.tsx
  • packages/ui/src/components/inline-input.css
  • packages/ui/src/components/inline-input.stories.tsx
  • packages/ui/src/components/inline-input.tsx
  • packages/ui/src/components/inputs-state-matrix.test.tsx
  • packages/ui/src/components/message-part.tsx
  • packages/ui/src/components/radio-group.css
  • packages/ui/src/components/radio-group.stories.tsx
  • packages/ui/src/components/radio-group.tsx
  • packages/ui/src/components/select.css
  • packages/ui/src/components/select.test.tsx
  • packages/ui/src/components/select.tsx
  • packages/ui/src/components/session-review.css
  • packages/ui/src/components/session-review.tsx
  • packages/ui/src/components/switch.css
  • packages/ui/src/components/switch.test.tsx
  • packages/ui/src/components/text-field.css
  • packages/ui/src/components/text-field.test.tsx
  • packages/ui/src/components/text-field.tsx
  • packages/ui/src/styles/index.css
  • packages/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

Comment thread packages/app/e2e/inputs/select-review-filter.spec.ts Outdated
Comment thread packages/app/e2e/inputs/session-rename-dialog.spec.ts
Comment thread packages/app/src/i18n/en.ts Outdated
Comment thread packages/ui/src/components/select.test.tsx Outdated
Comment thread packages/ui/src/components/select.test.tsx Outdated
Comment thread packages/ui/src/components/switch.test.tsx Outdated
Comment thread packages/ui/src/components/text-field.css
Astro-Han added 5 commits May 6, 2026 01:20
…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)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/ui/src/components/toast.css (1)

88-97: ⚡ Quick win

color: inherit cascade is consistent; :first-child override is now redundant

The switch from var(--fg-on-brand) to inherit is correct throughout — every child will resolve to the parent toast's color: 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

📥 Commits

Reviewing files that changed from the base of the PR and between b3bab1d and 5413fb9.

📒 Files selected for processing (19)
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/ui/src/components/icon.tsx
  • packages/ui/src/components/inputs-state-matrix.test.tsx
  • packages/ui/src/components/message-part.tsx
  • packages/ui/src/components/select.css
  • packages/ui/src/components/select.test.tsx
  • packages/ui/src/components/select.tsx
  • packages/ui/src/components/switch.css
  • packages/ui/src/components/switch.test.tsx
  • packages/ui/src/components/text-field.css
  • packages/ui/src/components/text-field.test.tsx
  • packages/ui/src/components/text-field.tsx
  • packages/ui/src/components/toast.css
  • packages/ui/src/styles/theme.css
  • packages/ui/src/theme/themes/pawwork.json
  • packages/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

Comment thread packages/ui/src/components/inputs-state-matrix.test.tsx
Comment thread packages/ui/src/components/switch.css Outdated
- 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ui/src/components/select.css (1)

49-58: ⚡ Quick win

Redundant settings trigger-icon block — safe to delete.

After changing height to 16px on 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5413fb9 and d1049bc.

📒 Files selected for processing (2)
  • packages/ui/src/components/list.css
  • packages/ui/src/components/select.css

Astro-Han added 11 commits May 6, 2026 08:21
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.
Astro-Han added 10 commits May 6, 2026 15:38
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.
@Astro-Han Astro-Han force-pushed the claude/i440-slice-05-inputs branch from c9e1cab to 1fd031f Compare May 6, 2026 07:46
Astro-Han added 3 commits May 6, 2026 16:17
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/ui/src/components/inputs-state-matrix.test.tsx (1)

55-57: ⚡ Quick win

Separate [data-picker-item] / [data-selected] checks don't verify the combined selector.

pickerCss is 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

📥 Commits

Reviewing files that changed from the base of the PR and between c9e1cab and 1230a8f.

📒 Files selected for processing (21)
  • packages/app/e2e/inputs/select-review-filter.spec.ts
  • packages/app/e2e/inputs/session-rename-dialog.spec.ts
  • packages/app/e2e/selectors.ts
  • packages/app/e2e/sidebar/sidebar-session-organization.spec.ts
  • packages/app/src/components/dialog-rename-session.tsx
  • packages/app/src/components/model-picker-hotfix.test.ts
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/layout/inline-editor.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/session/review-tab.test.tsx
  • packages/app/src/pages/session/review-tab.tsx
  • packages/ui/src/components/inputs-state-matrix.test.tsx
  • packages/ui/src/components/picker.css
  • packages/ui/src/components/select.css
  • packages/ui/src/components/select.test.tsx
  • packages/ui/src/components/select.tsx
  • packages/ui/src/components/switch.css
  • packages/ui/src/components/text-field.css
  • packages/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

Comment thread packages/app/e2e/inputs/select-review-filter.spec.ts Outdated
Comment thread packages/ui/src/components/inputs-state-matrix.test.tsx
Astro-Han added 7 commits May 6, 2026 16:46
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.
@Astro-Han Astro-Han merged commit c066b46 into dev May 6, 2026
20 checks passed
@Astro-Han Astro-Han deleted the claude/i440-slice-05-inputs branch May 6, 2026 09:17
Astro-Han added a commit that referenced this pull request May 6, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant