Skip to content

refactor(ui): unify typography on 13/12 and tighten sidebar geometry#320

Merged
Astro-Han merged 21 commits into
devfrom
claude/sidebar-typography
Apr 29, 2026
Merged

refactor(ui): unify typography on 13/12 and tighten sidebar geometry#320
Astro-Han merged 21 commits into
devfrom
claude/sidebar-typography

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

Merge of #315 (sidebar geometry) and #316 (typography 14→13 body / 12 code) into one bundled visual PR. Bundling avoids phantom diffs that would appear if the two were landed sequentially against the same sidebar files. After review feedback the PR also collapses the 14px hierarchy size layer down to 13 across primitives, reshapes the sidebar footer, and tightens the composer rail to the new density.

Why

PawWork's positioning is "open-source AI agent for everyone". The 14px / 13px split inherited from opencode was creating a denser / lighter rhythm that did not survive PawWork's actual usage: visual hierarchy in this product is carried by weight, not size, and the size layer mostly created drift between TSX class callsites and the inline rules in primitive component CSS. Sidebar geometry was also drifting (X=8 dot vs X=10 icon vs X=15 PIN, size-6 action containers, mismatched right rail) and needed a single unifying pass.

The end state: a single 13px body across chat, sidebar, settings, dialogs, and the composer; 12px for code (fenced, inline, permission patterns); a sidebar left rail where every leading visual lines up at X=8 and every right-side action lands on the same pr-3 column; and a sidebar footer that is just one Settings row in the same shape as the top New session and Search rows.

Related Issue

Closes #315 and #316. Out of scope: the regression in #319 (manually stopping a turn clears the todo dock) is filed separately and will be fixed in its own PR.

How To Verify

bun install --frozen-lockfile
bun --cwd packages/opencode run typecheck
bun run dev:desktop

Manual visual sweep performed locally on macOS dev:desktop with the regression script (markdown rendering, list/glob/grep tool cards, diff changes summary, permission dialog, question dialog) — all surfaces match the new 13px / 12px / X=8 / pr-3 grid. Toast surface was not exercised in the manual run because no flow surfaced one; risk is low because the toast title rule was re-pointed from a 14px literal to var(--font-size-hierarchy) which is now 13px, no shape changes.

Verification gates:

  • grep -rE 'text-14-regular' packages/app/src packages/ui/src --include='*.tsx' --exclude='*.stories.tsx' returns 0 hits
  • grep -rE 'text-14-medium' packages/app/src packages/ui/src --include='*.tsx' --exclude='*.stories.tsx' returns 0 hits
  • grep -rnE 'font-size:\s*14px' packages/app/src packages/ui/src --include='*.css' returns 0 hits
  • grep -rE 'fontSize:\s*14' packages/app/src still returns the two xterm callsites (settings.tsx:122 and terminal.tsx:368) — intentionally out of scope, xterm is a separate font system

Screenshots or Recordings

Not attached. The change is broad-but-shallow density work and is most legible by running the dev:desktop build directly.

Checklist

  • 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 listed the relevant verification steps, including tests when behavior changed
  • 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, or generated/local file changes when relevant
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English

Summary by CodeRabbit

  • Style

    • Refined global typography (most text reduced from 14px → 13px, added a 12px option) and tightened button/icon sizes for more compact, consistent UI across dialogs, sidebars, lists, and session views.
    • Simplified sidebar footer by removing project/help controls and the sidebar "Help" label.
  • Localization

    • Revised English and Simplified Chinese session/context wording for clearer count and status phrasing.
  • Tests

    • Updated two frontend smoke tests to stop asserting the removed sidebar "Open project" visibility.

Re-points .text-14-medium and .text-14-mono to a new
--font-size-hierarchy token (initialized to 14px). No visual change;
prepares for flipping --font-size-base to 13px without dragging
hierarchy callsites.
- Change --font-size-base from 14px to 13px
- Add --font-size-x-small: 12px token
- Add .text-12-regular / .text-12-medium / .text-12-mono utilities

Hierarchy callsites (.text-14-medium / .text-14-mono) remain 14px via
--font-size-hierarchy. .text-14-regular callsites now render at 13px;
they will be renamed to .text-13-regular in the next commit.
Mass sed across packages/app/src and packages/ui/src (.tsx only,
excluding .stories.tsx). 30 files updated. The .text-14-regular
class definition stays in utilities.css as a transitional shim;
no .tsx file references it anymore.

Behavioral note: callsites previously rendered at 14px / line-height
171% now render at 13px / line-height 167% (the .text-13-regular
metrics). This is the intended body-copy density change.
…kens

Comment-driven decision rule applied across 12 component CSS files:
- Rules with /* text-14-medium */ neighbor → var(--font-size-hierarchy)
  (button.css, list.css, tabs.css, tag.css, toast.css)
- Rules with /* text-14-regular */ neighbor → var(--font-size-base)
  (dialog.css, list.css, text-field.css, message-nav.css)
- No-comment rules judged by selector and weight: titles/headers
  → hierarchy; row bodies, hints, descriptions, inputs → base
  (basic-tool.css, message-part.css, diff-changes.css, markdown.css)

The [data-slot="permission-patterns"] code rule at message-part.css:803
is intentionally left at 14px; Task 6 takes ownership and routes it to
12px alongside the other code-styling changes.
- markdown.css .shiki: 13px → 12px (fenced code blocks)
- markdown.css :not(pre) > code: uncomment and set to 12px (inline code)
- message-part.css permission-patterns code: 14px → 12px

reasoning-part defensive 13px override left in place.
Add data-leading-slot attribute to 3 leading-icon wrappers in the
sidebar: SessionRow indicator, NewSessionItem leading slot, and the
ProjectPreviewPanel branch icon. New sidebar.css force-sizes any svg
or [data-size="small"] descendant to 14×14 inside those wrappers,
without touching the global Icon size="small" default (16px) used
elsewhere.

The branch icon wrapper stays size-6 (24px container) here; resizing
to size-5 is owned by the action-container task.
Switch SessionRow and NewSessionItem leading slots from a 20×20
centered container to 14×14 left-anchored. With the row's existing
pl-2 (8px), every leading visual (PIN dot, status dots, leading icon,
spinner) now starts at the same X=8 left edge, removing the per-shape
centering offset that previously pushed icons to X=10 and dots to X=15.

Spinner shrunk 15→14 to fit the new slot; gap-2 keeps text X at
8 + 14 + 8 = 30. Branch row resize is owned by the next commit.
Replace the top-of-sidebar New session and Search Buttons with the
same row JSX shape SessionRow uses: pl-2 row padding, 14×14
data-leading-slot, gap-2, text-13-medium label. New session keeps the
primary surface fill and the brand-color leading icon; Search uses
the same hover background as session rows.

Container gap-2 → gap-0.5 to match the rest of the session list
spacing now that both buttons are list rows. The empty-state
"Open project" CTA and titlebar IconButtons are unchanged.
- Resize size-6 (24px) to size-5 (20px) for: SessionItem archive
  IconButton, SessionItem reserved action area (w-6 → w-5),
  PawworkSidebar dot-grid menu trigger, and the project-preview
  branch icon container.
- Bump the ALL group header from pr-2 to pr-3 so the sort/filter
  button's right hit-edge lines up with the row archive button's
  right edge (both at the panel's pr-3 rail).

The branch icon glyph automatically renders at 14×14 inside the
20×20 container via the data-leading-slot CSS rule introduced
earlier.
@Astro-Han Astro-Han added enhancement New feature or request P2 Medium priority ui Design system and user interface labels Apr 29, 2026
@coderabbitai

coderabbitai Bot commented Apr 29, 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 9 minutes and 32 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 811468cb-a26c-4173-bd83-4c1b5ea89c96

📥 Commits

Reviewing files that changed from the base of the PR and between d61be8e and 0202473.

📒 Files selected for processing (2)
  • packages/ui/src/components/markdown.css
  • packages/ui/src/i18n/en.ts
📝 Walkthrough

Walkthrough

This PR shifts the UI typography baseline (14px → 13px, adds 12px), replaces many hard-coded font sizes with theme variables, tightens component sizing (buttons, inputs, sidebar icons/leading slots), and removes several PawworkSidebar help/open-project props and footer controls. Several i18n strings and an e2e assertion were also updated.

Changes

Cohort / File(s) Summary
Theme & utilities
packages/ui/src/styles/theme.css, packages/ui/src/styles/utilities.css
Add --font-size-x-small (12px), reduce --font-size-base (14→13px), add --font-size-hierarchy and new .text-12-* utilities; adjust which vars text-14-* classes reference.
Global CSS migrations
packages/ui/src/components/basic-tool.css, .../button.css, .../dialog.css, .../diff-changes.css, .../list.css, .../markdown.css, .../message-nav.css, .../message-part.css, .../tabs.css, .../tag.css, .../text-field.css, .../toast.css
Replace hard-coded 14px/13px sizes with theme variables (--font-size-base, --font-size-hierarchy, --font-size-x-small).
App-wide typography class replacements
packages/app/src/components/... (dialogs, settings, status, prompt-input, session, server, file components), packages/app/src/app.tsx, packages/app/src/pages/home.tsx, packages/app/src/pages/session.tsx
Systematic swap of text-14-*text-13-* (and several text-14-mediumtext-13-medium) across many UI callsites; no logic changes.
Prompt input & related controls
packages/app/src/components/prompt-input.tsx, .../drag-overlay.tsx, .../send-button.tsx, .../slash-popover.tsx, .../workspace-chip.tsx
Reduce control heights and button sizes (h-[32px]→h-[28px], size-8→size-7), update typography classes, and change add-workspace icon name.
Sidebar layout, sizing & API
packages/app/src/pages/layout.tsx, packages/app/src/pages/layout/pawwork-sidebar.tsx, packages/app/src/pages/layout/sidebar-items.tsx, packages/app/src/pages/layout/sidebar-project.tsx, packages/app/src/pages/layout/sidebar-workspace.tsx, packages/app/src/pages/layout/sidebar.css
Standardize leading-slot sizing, introduce data-leading-slot with 14px icon sizing, tighten row paddings/gaps and action widths, and remove PawworkSidebar props (onOpenHelp, openProjectLabel, openProjectKeybind, helpLabel) while simplifying footer controls.
Small component tweaks
packages/ui/src/components/file-media.tsx, .../file-search.tsx, .../message-part.tsx, packages/app/src/components/server/server-row.tsx, packages/app/src/components/...session...
Minor typography/class updates across file, server, and session components to match new baseline; no behavior changes.
i18n
packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts, packages/ui/src/i18n/en.ts, packages/ui/src/i18n/zh.ts
Remove sidebar.help translations and update pluralized/context strings and Chinese phrasing (e.g., 已探索探索完毕, refined count templates).
Tests
packages/app/e2e/app/home.spec.ts
Remove assertions expecting an "Open project" button in home e2e tests to reflect sidebar/footer control removal.

Sequence Diagram(s)

(omitted — changes are primarily styling, theme, and minor API removal without new multi-actor control flow)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #315 — Sidebar icon scale and alignment polish: changes standardize leading-slot sizes, icon sizing, and sidebar spacing addressing that issue's visual-rhythm objectives.
  • #316 — Typography baseline redesign: this PR implements the global typography baseline/token changes referenced by the issue.
  • #300 — Font-hierarchy refactor: theme/utilities edits align with the refactor's tokenization and hierarchy goals.

Possibly related PRs

  • #306 — Font-hierarchy changes: overlaps on theme/utilities and many text-size callsites.
  • #202 — Sidebar sizing adjustments: both PRs modify sidebar row leading/action sizing (direct overlap).
  • #217 — Prompt/input and theme adjustments: touches prompt-input/send-button/workspace-chip and theme tokens similarly.

Suggested labels

app

Poem

A rabbit trims the fonts just so,
From fourteen down to thirteen's glow,
Icons tuck to neat, small rows,
Sidebar rhythm softly grows,
Hops and tweaks — the UI sings, hello! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor(ui): unify typography on 13/12 and tighten sidebar geometry' clearly and specifically summarizes the main changes: typography unification and sidebar geometry refinement.
Description check ✅ Passed The PR description comprehensively covers all template sections including Summary, Why, Related Issues, How to Verify, and a complete Checklist with all items addressed.
Linked Issues check ✅ Passed The code changes fully implement the requirements from #315: unifying sidebar leading visuals on an X=8 left rail, standardizing right-side actions to pr-3, converting top controls to matching row form, reducing icons to 14px, and updating typography to 13px body/12px code.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives from #315 and #316. Out-of-scope items (xterm font-size callsites, #319 regression) are explicitly noted and excluded as intended.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sidebar-typography

Review rate limit: 0/3 reviews remaining, refill in 9 minutes and 32 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

- Bump inter-row gap from 2px (gap-0.5) to 4px (gap-1) and row vertical
  padding from py-1 to py-1.5, so the sessions list and the top
  New session / Search rows feel less squeezed.
- Bump the leading icons on top New session, Search, and Settings rows
  to 16x16 (default Icon size="small") for visual weight; SessionRow
  indicators stay at 14x14 via the data-leading-slot override.
- Drop the orange brand fill on New session so it matches the
  Search and Settings rows; brand differentiation now lives only in
  the icon glyph, not the row background.
- Replace the three-IconButton footer (open project / help / settings)
  with a single Settings row that aligns with the sidebar's left rail
  and uses the same row shape as the top buttons.
- Swap the workspace chip menu's "add directory" leading icon from
  plus-small to folder-add-left so the chip menu carries the
  open-workspace affordance the footer button used to.
- Drop the now-dead onOpenHelp / helpLabel / openProjectLabel /
  openProjectKeybind props on PawworkSidebar, the unused
  PAWWORK_GITHUB_ISSUE_URL import in layout.tsx, the unused Tooltip
  import in pawwork-sidebar.tsx, and the orphan sidebar.help i18n key
  in zh and en.

diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index 25f386a..0cf99a5 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -758,7 +758,6 @@ export const dict = {
   "sidebar.menu.toggle": "Toggle menu",
   "sidebar.nav.projectsAndSessions": "Projects and sessions",
   "sidebar.settings": "Settings",
-  "sidebar.help": "Help",
   "sidebar.workspaces.enable": "Enable workspaces",
   "sidebar.workspaces.disable": "Disable workspaces",
   "sidebar.gettingStarted.title": "Getting started",
diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts
index adc8ef9..eff9aee 100644
--- a/packages/app/src/i18n/zh.ts
+++ b/packages/app/src/i18n/zh.ts
@@ -672,7 +672,6 @@ export const dict = {
   "sidebar.menu.toggle": "切换菜单",
   "sidebar.nav.projectsAndSessions": "项目和会话",
   "sidebar.settings": "设置",
-  "sidebar.help": "帮助",
   "sidebar.workspaces.enable": "启用工作区",
   "sidebar.workspaces.disable": "禁用工作区",
   "sidebar.gettingStarted.title": "入门",
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 0940ac7..9797e0c 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -18,7 +18,6 @@ import { useNavigate, useParams } from "@solidjs/router"
 import { useLayout, LocalProject } from "@/context/layout"
 import { useGlobalSync } from "@/context/global-sync"
 import { Persist, persisted } from "@/utils/persist"
-import { PAWWORK_GITHUB_ISSUE_URL } from "@/utils/support-links"
 import { base64Encode } from "@opencode-ai/util/encode"
 import { decode64 } from "@/utils/base64"
 import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
@@ -2170,7 +2169,7 @@ export default function Layout(props: ParentProps) {
               <div class="flex-1 min-h-0 -mt-4 flex items-center justify-center px-6 pb-64 text-center">
                 <div class="mt-8 flex max-w-60 flex-col items-center gap-6 text-center">
                   <div class="flex flex-col gap-3">
-                    <div class="text-14-medium text-text-strong">{language.t("sidebar.empty.title")}</div>
+                    <div class="text-13-medium text-text-strong">{language.t("sidebar.empty.title")}</div>
                     <div class="text-13-regular text-text-base" style={{ "line-height": "var(--line-height-normal)" }}>
                       {language.t("sidebar.empty.description")}
                     </div>
@@ -2195,8 +2194,8 @@ export default function Layout(props: ParentProps) {
                       if (!item) return
                       renameProject(item, next)
                     }}
-                    class="text-14-medium text-text-strong truncate"
-                    displayClass="text-14-medium text-text-strong truncate"
+                    class="text-13-medium text-text-strong truncate"
+                    displayClass="text-13-medium text-text-strong truncate"
                     stopPropagation
                   />

@@ -2376,7 +2375,7 @@ export default function Layout(props: ParentProps) {
           <div class="rounded-xl bg-background-base shadow-xs-border-base" data-component="getting-started">
             <div class="p-3 flex flex-col gap-6">
               <div class="flex flex-col gap-2">
-                <div class="text-14-medium text-text-strong">{language.t("sidebar.gettingStarted.title")}</div>
+                <div class="text-13-medium text-text-strong">{language.t("sidebar.gettingStarted.title")}</div>
                 <div class="text-13-regular text-text-base" style={{ "line-height": "var(--line-height-normal)" }}>
                   {language.t("sidebar.gettingStarted.line1")}
                 </div>
@@ -2425,12 +2424,8 @@ export default function Layout(props: ParentProps) {
       onSearch={() => command.show()}
       onOpenProject={chooseProject}
       onOpenSettings={openSettings}
-      onOpenHelp={() => platform.openLink(PAWWORK_GITHUB_ISSUE_URL)}
-      openProjectLabel={() => language.t("command.project.open")}
-      openProjectKeybind={() => command.keybind("project.open")}
       settingsLabel={() => language.t("sidebar.settings")}
       settingsKeybind={() => command.keybind("settings.open")}
-      helpLabel={() => language.t("sidebar.help")}
     />
   )
   const sidebarContent = (mobile?: boolean) =>
diff --git a/packages/app/src/pages/layout/pawwork-sidebar.tsx b/packages/app/src/pages/layout/pawwork-sidebar.tsx
index 4a0c4e4..7400d2e 100644
--- a/packages/app/src/pages/layout/pawwork-sidebar.tsx
+++ b/packages/app/src/pages/layout/pawwork-sidebar.tsx
@@ -4,7 +4,7 @@ import { ContextMenu } from "@opencode-ai/ui/context-menu"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
 import { Icon } from "@opencode-ai/ui/icon"
 import { IconButton } from "@opencode-ai/ui/icon-button"
-import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
+import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
 import { createEffect, createMemo, createSignal, For, Show, type Accessor, type JSX } from "solid-js"
 import { useLanguage } from "@/context/language"
 import { createInlineEditorController } from "./inline-editor"
@@ -48,12 +48,8 @@ export const PawworkSidebar = (props: {
   onSearch: () => void
   onOpenProject: () => void
   onOpenSettings: () => void
-  onOpenHelp: () => void
-  openProjectLabel: Accessor<string>
-  openProjectKeybind: Accessor<string | undefined>
   settingsLabel: Accessor<string>
   settingsKeybind: Accessor<string | undefined>
-  helpLabel: Accessor<string>
 }): JSX.Element => {
   const language = useLanguage()
   const editor = createInlineEditorController()
@@ -242,25 +238,25 @@ export const PawworkSidebar = (props: {
       class="flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-background-weak"
     >
       <div class="shrink-0 px-3 pt-3">
-        <div class="flex flex-col gap-0.5">
+        <div class="flex flex-col gap-1">
           <button
             type="button"
             data-action="pawwork-session-new"
             onClick={props.onNew}
-            class="w-full flex items-center gap-2 pl-2 pr-3 py-1 rounded-xl bg-surface-interactive-base hover:bg-surface-interactive-hover transition-colors text-left focus:outline-none"
+            class="w-full flex items-center gap-2 pl-2 pr-3 py-1.5 rounded-xl hover:bg-surface-raised-base-hover transition-colors text-left focus:outline-none"
           >
-            <span data-leading-slot class="shrink-0 w-[14px] h-[14px] flex items-center" style={{ color: "var(--accent-brand)" }}>
-              <Icon name="new-session" size="small" />
+            <span class="shrink-0 w-4 h-4 flex items-center">
+              <Icon name="new-session" size="small" class="text-icon-base" />
             </span>
-            <span class="text-13-medium text-text-strong min-w-0 flex-1 truncate">{language.t("command.session.new")}</span>
+            <span class="text-13-medium text-text-base min-w-0 flex-1 truncate">{language.t("command.session.new")}</span>
           </button>
           <button
             type="button"
             data-action="pawwork-session-search"
             onClick={props.onSearch}
-            class="w-full flex items-center gap-2 pl-2 pr-3 py-1 rounded-xl hover:bg-surface-raised-base-hover transition-colors text-left focus:outline-none"
+            class="w-full flex items-center gap-2 pl-2 pr-3 py-1.5 rounded-xl hover:bg-surface-raised-base-hover transition-colors text-left focus:outline-none"
           >
-            <span data-leading-slot class="shrink-0 w-[14px] h-[14px] flex items-center">
+            <span class="shrink-0 w-4 h-4 flex items-center">
               <Icon name="magnifying-glass" size="small" class="text-icon-base" />
             </span>
             <span class="text-13-medium text-text-base min-w-0 flex-1 truncate">{language.t("sidebar.pawwork.search")}</span>
@@ -273,7 +269,7 @@ export const PawworkSidebar = (props: {
         fallback={
           <div class="flex flex-1 items-center px-3">
             <div class="flex w-full flex-col gap-3 rounded-xl border border-border-weak-base bg-surface-base p-4">
-              <div class="text-14-medium text-text-strong">{language.t("sidebar.empty.title")}</div>
+              <div class="text-13-medium text-text-strong">{language.t("sidebar.empty.title")}</div>
               <p class="text-13-regular text-text-weak">{language.t("sidebar.pawwork.empty.description")}</p>
               <Button data-action="pawwork-open-project" size="large" onClick={props.onOpenProject}>
                 {language.t("command.project.open")}
@@ -294,9 +290,9 @@ export const PawworkSidebar = (props: {
             when={props.sessions().length > 0}
             fallback={<div class="px-2 text-13-regular text-text-weak">{language.t("sidebar.pawwork.empty.sessions")}</div>}
           >
-            <nav class="flex flex-col gap-0.5">
+            <nav class="flex flex-col gap-1">
               <Show when={pinnedRows().length > 0}>
-                <section data-component="pawwork-sidebar-pinned" class="flex flex-col gap-0.5">
+                <section data-component="pawwork-sidebar-pinned" class="flex flex-col gap-1">
                   <div class="px-2 pb-0.5 text-13-medium text-text-weak">{language.t("sidebar.pawwork.pinned")}</div>
                   <For each={pinnedRows()}>{(entry) => renderSessionItem(entry)}</For>
                 </section>
@@ -326,7 +322,7 @@ export const PawworkSidebar = (props: {
               <Show when={props.sortMode() === "project"}>
                 <For each={groupedRows()}>
                   {(group) => (
-                    <section class="flex flex-col gap-0.5">
+                    <section class="flex flex-col gap-1">
                       <div data-component="pawwork-group-header" class="px-2 pt-2 pb-0.5 text-13-medium text-text-weak">
                         {group.label}
                       </div>
@@ -342,48 +338,26 @@ export const PawworkSidebar = (props: {

       <div
         data-component="pawwork-sidebar-footer"
-        class="shrink-0 border-t border-border-weaker-base px-3 py-2 flex items-center justify-between"
+        class="shrink-0 border-t border-border-weaker-base px-3 py-2"
       >
         <TooltipKeybind
           placement={tooltipPlacement()}
-          title={props.openProjectLabel()}
-          keybind={props.openProjectKeybind() ?? ""}
+          title={props.settingsLabel()}
+          keybind={props.settingsKeybind() ?? ""}
         >
-          <IconButton
-            icon="folder-add-left"
-            variant="ghost"
-            size="large"
-            data-action="pawwork-open-project"
-            onClick={props.onOpenProject}
-            aria-label={props.openProjectLabel()}
-          />
-        </TooltipKeybind>
-        <div class="flex items-center gap-1">
-          <Tooltip placement={tooltipPlacement()} value={props.helpLabel()}>
-            <IconButton
-              icon="help"
-              variant="ghost"
-              size="large"
-              data-action="pawwork-open-help"
-              onClick={props.onOpenHelp}
-              aria-label={props.helpLabel()}
-            />
-          </Tooltip>
-          <TooltipKeybind
-            placement={tooltipPlacement()}
-            title={props.settingsLabel()}
-            keybind={props.settingsKeybind() ?? ""}
+          <button
+            type="button"
+            data-action="pawwork-open-settings"
+            onClick={props.onOpenSettings}
+            aria-label={props.settingsLabel()}
+            class="w-full flex items-center gap-2 pl-2 pr-3 py-1.5 rounded-xl hover:bg-surface-raised-base-hover transition-colors text-left focus:outline-none"
           >
-            <IconButton
-              icon="settings-gear"
-              variant="ghost"
-              size="large"
-              data-action="pawwork-open-settings"
-              onClick={props.onOpenSettings}
-              aria-label={props.settingsLabel()}
-            />
-          </TooltipKeybind>
-        </div>
+            <span class="shrink-0 w-4 h-4 flex items-center">
+              <Icon name="settings-gear" size="small" class="text-icon-base" />
+            </span>
+            <span class="text-13-medium text-text-base min-w-0 flex-1 truncate">{props.settingsLabel()}</span>
+          </button>
+        </TooltipKeybind>
       </div>
     </section>
   )
diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx
index 7ca7ada..a25d479 100644
--- a/packages/app/src/pages/layout/sidebar-items.tsx
+++ b/packages/app/src/pages/layout/sidebar-items.tsx
@@ -110,7 +110,7 @@ const SessionRow = (props: {
   return (
     <A
       href={`/${props.slug}/session/${props.session.id}`}
-      class={`flex items-center gap-2 min-w-0 w-full text-left focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
+      class={`flex items-center gap-2 min-w-0 w-full text-left focus:outline-none ${props.dense ? "py-1" : "py-1.5"}`}
       onPointerDown={props.warmPress}
       onFocus={props.warmFocus}
       onClick={() => {
@@ -293,7 +293,7 @@ export const NewSessionItem = (props: {
     <A
       href={`/${props.slug}/session`}
       end
-      class={`flex items-center gap-2 min-w-0 w-full text-left focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
+      class={`flex items-center gap-2 min-w-0 w-full text-left focus:outline-none ${props.dense ? "py-1" : "py-1.5"}`}
       onClick={() => {
         if (layout.sidebar.opened()) return
         props.clearHoverProjectSoon()
diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx
index ada8787..1bcfff5 100644
--- a/packages/app/src/pages/layout/sidebar-project.tsx
+++ b/packages/app/src/pages/layout/sidebar-project.tsx
@@ -198,7 +198,7 @@ const ProjectPreviewPanel = (props: {
 }): JSX.Element => (
   <div class="-m-3 p-2 flex flex-col w-72">
     <div class="px-4 pt-2 pb-1 flex items-center gap-2">
-      <div class="text-14-medium text-text-strong truncate grow">{displayName(props.project)}</div>
+      <div class="text-13-medium text-text-strong truncate grow">{displayName(props.project)}</div>
     </div>
     <div class="px-4 pb-2 text-13-medium text-text-weak">{props.language.t("sidebar.project.recentSessions")}</div>
     <div class="px-2 pb-2 flex flex-col gap-2">
@@ -229,7 +229,7 @@ const ProjectPreviewPanel = (props: {
                   <div data-leading-slot class="shrink-0 size-5 flex items-center justify-center">
                     <Icon name="branch" size="small" class="text-icon-base" />
                   </div>
-                  <span class="truncate text-14-medium text-text-base">{props.label(directory)}</span>
+                  <span class="truncate text-13-medium text-text-base">{props.label(directory)}</span>
                 </div>
                 <For each={sessions().slice(0, 2)}>
                   {(session) => (
- Model, variant, and workspace chips: h-[32px] → h-[28px], label
  text-14-medium → text-13-medium. Same shape, less vertical weight.
- Plus-attach button and send button: size-8 → size-7. The plus icon
  inside the attach button shrinks size-4.5 → size-4 to match the
  smaller container.
- Workspace chip popover menu items: text-14-medium → text-13-medium
  to stay consistent with the chip surface.

Visually puts the composer rail at the same density as the new
13px-driven sidebar and chat body.

diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index a64e660..2c6feaf 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -1121,7 +1121,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               as="div"
               variant="ghost"
               size="normal"
-              class="h-[32px]! min-w-0 w-44 px-3 justify-start! text-14-medium! text-text-base group rounded-full! border! border-border-strong-base! transition-colors hover:bg-surface-base-hover"
+              class="h-[28px]! min-w-0 w-44 px-3 justify-start! text-13-medium! text-text-base group rounded-full! border! border-border-strong-base! transition-colors hover:bg-surface-base-hover"
               style={triggerStyle()}
               onClick={() => {
                 void import("@/components/dialog-select-model-unpaid").then((x) => {
@@ -1158,7 +1158,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               size: "normal",
               style: triggerStyle(),
               class:
-                "h-[32px]! min-w-0 w-44 px-3 justify-start! text-14-medium! text-text-base group rounded-full! border! border-border-strong-base! transition-colors hover:bg-surface-base-hover",
+                "h-[28px]! min-w-0 w-44 px-3 justify-start! text-13-medium! text-text-base group rounded-full! border! border-border-strong-base! transition-colors hover:bg-surface-base-hover",
               "data-action": "prompt-model",
             }}
             onClose={restoreFocus}
@@ -1202,7 +1202,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               "aria-haspopup": "menu",
               style: triggerStyle(),
               class:
-                "h-[32px] px-3 max-w-[140px] inline-flex items-center gap-1.5 rounded-full border border-border-strong-base text-14-medium text-text-base transition-colors hover:bg-surface-base-hover",
+                "h-[28px] px-3 max-w-[140px] inline-flex items-center gap-1.5 rounded-full border border-border-strong-base text-13-medium text-text-base transition-colors hover:bg-surface-base-hover",
             } as any
           }
           trigger={
@@ -1225,7 +1225,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     type="button"
                     role="menuitemradio"
                     aria-checked={active()}
-                    class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-14-medium text-text-strong outline-none hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover"
+                    class="flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-13-medium text-text-strong outline-none hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover"
                     classList={{ "font-medium": active() }}
                     onClick={() => {
                       local.model.variant.set(variant === "default" ? undefined : variant)
@@ -1561,14 +1561,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                   data-action="prompt-attach"
                   type="button"
                   variant="ghost"
-                  class="size-8 shrink-0 p-0"
+                  class="size-7 shrink-0 p-0"
                   style={buttons()}
                   onClick={pick}
                   disabled={store.mode !== "normal"}
                   tabIndex={store.mode === "normal" ? undefined : -1}
                   aria-label={language.t("prompt.action.attachFile")}
                 >
-                  <Icon name="plus" class="size-4.5" />
+                  <Icon name="plus" class="size-4" />
                 </Button>
               </TooltipKeybind>
               <Show when={store.mode === "normal"}>
diff --git a/packages/app/src/components/prompt-input/send-button.tsx b/packages/app/src/components/prompt-input/send-button.tsx
index 92ada1a..8168ce5 100644
--- a/packages/app/src/components/prompt-input/send-button.tsx
+++ b/packages/app/src/components/prompt-input/send-button.tsx
@@ -20,7 +20,7 @@ export const SendButton: Component<SendButtonProps> = (props) => {
       aria-label={props["aria-label"]}
       onClick={props.onClick}
       style={props.style}
-      class="inline-flex size-8 items-center justify-center rounded-full bg-button-brand-base shadow-[0_1px_3px_var(--button-brand-shadow)] transition-colors duration-150 hover:bg-button-brand-hover disabled:bg-border-weak-base disabled:cursor-not-allowed disabled:shadow-none"
+      class="inline-flex size-7 items-center justify-center rounded-full bg-button-brand-base shadow-[0_1px_3px_var(--button-brand-shadow)] transition-colors duration-150 hover:bg-button-brand-hover disabled:bg-border-weak-base disabled:cursor-not-allowed disabled:shadow-none"
     >
       <Icon
         name={props.stopping ? "stop-square" : "arrow-up"}
diff --git a/packages/app/src/components/prompt-input/workspace-chip.tsx b/packages/app/src/components/prompt-input/workspace-chip.tsx
index f45a2a0..5e67527 100644
--- a/packages/app/src/components/prompt-input/workspace-chip.tsx
+++ b/packages/app/src/components/prompt-input/workspace-chip.tsx
@@ -63,7 +63,7 @@ export function WorkspaceChip(props: { style?: JSX.CSSProperties | string } = {}
           "aria-label": language.t("workspace.chip.ariaLabel"),
           "aria-haspopup": "menu",
           class:
-            "h-[32px] px-3 inline-flex items-center gap-1.5 rounded-full border border-border-strong-base text-14-medium text-text-base transition-colors hover:bg-surface-base-hover",
+            "h-[28px] px-3 inline-flex items-center gap-1.5 rounded-full border border-border-strong-base text-13-medium text-text-base transition-colors hover:bg-surface-base-hover",
           style: props.style,
         } as any
       }
@@ -96,7 +96,7 @@ export function WorkspaceChip(props: { style?: JSX.CSSProperties | string } = {}
                   type="button"
                   role="menuitemradio"
                   aria-checked={active()}
-                  class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-14-medium outline-none hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover"
+                  class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-13-medium outline-none hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover"
                   onClick={() => {
                     navigate(`/${base64Encode(workspace.path)}/session`)
                     setOpen(false)
@@ -122,13 +122,13 @@ export function WorkspaceChip(props: { style?: JSX.CSSProperties | string } = {}
             type="button"
             role="menuitem"
             data-action="workspace-chip-add"
-            class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-14-medium text-text-base outline-none hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover"
+            class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-13-medium text-text-base outline-none hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover"
             onClick={() => {
               setOpen(false)
               layoutPage.openProject()
             }}
           >
-            <Icon name="plus-small" size="small" class="shrink-0 text-text-weak" />
+            <Icon name="folder-add-left" size="small" class="shrink-0 text-text-weak" />
             <span class="min-w-0 flex-1 truncate">{language.t("workspace.chip.add")}</span>
           </button>
         </div>
PawWork's product direction does not need a separate 14px hierarchy
size on top of the 13px body size. Visual hierarchy is carried by
weight (medium vs regular), so the size layer was redundant and only
created drift.

Three coordinated changes:

- Mass rename text-14-medium → text-13-medium across all .tsx
  callsites in packages/app/src and packages/ui/src (excluding
  .stories.tsx). Covers settings section heads, settings item titles,
  dialog headers, home recent-projects header, sidebar workspace
  labels, sidebar empty state, file-tabs filenames, session review
  header, message timeline group titles, and the context-tool group
  title (the "已探索" / "Explored" wrapper).
- Re-classify three component-CSS hierarchy escapes back to body:
  basic-tool tool-title, basic-tool task-tool-title, and message-part
  message-part-title now read --font-size-base (13) instead of
  --font-size-hierarchy.
- Flip the --font-size-hierarchy token from 14px to 13px so the
  remaining primitives that still resolve through it (Button labels,
  Tag chips, Tabs labels, Toast titles, markdown h4-h6, .text-14-mono)
  also fall to 13. The token stays defined as a permanent carve-out
  point in case PawWork ever wants to bring back a hierarchy step.
Reword the context-gathering wrapper and its child counts so the
group reads as a coherent sentence in both languages instead of a
bag of words.

zh:
- gatheredContext: 已探索 → 探索完毕 (matches the 完毕 pattern)
- read: {{count}} 次读取 → 阅读 {{count}} 篇
- search: {{count}} 次搜索 → 搜索 {{count}} 次
- list: {{count}} 个列表 → 浏览 {{count}} 处

All counts now use a verb + count + classifier shape that mirrors
how 通义灵码 documents the same tools (阅读 / 检索) and stays
4-character symmetric.

en:
- read: {{count}} read/reads → Read {{count}} file/files
- list: {{count}} list/lists → Listed {{count}} dir/dirs
- search: kept as {{count}} search/searches (already noun-natural)

The wrapper "Exploring/Explored" is unchanged; "explore the codebase"
is established dev jargon (Cursor docs, opencode subagent name) and
already conveys goal-directed pre-task investigation.

diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts
index 929f25f..9bed434 100644
--- a/packages/ui/src/i18n/en.ts
+++ b/packages/ui/src/i18n/en.ts
@@ -73,12 +73,12 @@ export const dict: Record<string, string> = {
   "ui.messagePart.review.title": "Review your answers",
   "ui.messagePart.questions.dismissed": "Questions dismissed",
   "ui.messagePart.compaction": "Session compacted",
-  "ui.messagePart.context.read.one": "{{count}} read",
-  "ui.messagePart.context.read.other": "{{count}} reads",
+  "ui.messagePart.context.read.one": "Read {{count}} file",
+  "ui.messagePart.context.read.other": "Read {{count}} files",
   "ui.messagePart.context.search.one": "{{count}} search",
   "ui.messagePart.context.search.other": "{{count}} searches",
-  "ui.messagePart.context.list.one": "{{count}} list",
-  "ui.messagePart.context.list.other": "{{count}} lists",
+  "ui.messagePart.context.list.one": "Listed {{count}} dir",
+  "ui.messagePart.context.list.other": "Listed {{count}} dirs",

   "ui.list.loading": "Loading",
   "ui.list.empty": "No results",
diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts
index df466b9..0bfb4ec 100644
--- a/packages/ui/src/i18n/zh.ts
+++ b/packages/ui/src/i18n/zh.ts
@@ -55,7 +55,7 @@ export const dict = {
   "ui.sessionTurn.status.delegating": "正在委派工作",
   "ui.sessionTurn.status.planning": "正在规划下一步",
   "ui.sessionTurn.status.gatheringContext": "正在探索",
-  "ui.sessionTurn.status.gatheredContext": "已探索",
+  "ui.sessionTurn.status.gatheredContext": "探索完毕",
   "ui.sessionTurn.status.searchingCodebase": "正在搜索代码库",
   "ui.sessionTurn.status.searchingWeb": "正在搜索网页",
   "ui.sessionTurn.status.makingEdits": "正在修改",
@@ -67,12 +67,12 @@ export const dict = {

   "ui.messagePart.questions.dismissed": "问题已忽略",
   "ui.messagePart.compaction": "会话已压缩",
-  "ui.messagePart.context.read.one": "{{count}} 次读取",
-  "ui.messagePart.context.read.other": "{{count}} 次读取",
-  "ui.messagePart.context.search.one": "{{count}} 次搜索",
-  "ui.messagePart.context.search.other": "{{count}} 次搜索",
-  "ui.messagePart.context.list.one": "{{count}} 个列表",
-  "ui.messagePart.context.list.other": "{{count}} 个列表",
+  "ui.messagePart.context.read.one": "阅读 {{count}} 篇",
+  "ui.messagePart.context.read.other": "阅读 {{count}} 篇",
+  "ui.messagePart.context.search.one": "搜索 {{count}} 次",
+  "ui.messagePart.context.search.other": "搜索 {{count}} 次",
+  "ui.messagePart.context.list.one": "浏览 {{count}} 处",
+  "ui.messagePart.context.list.other": "浏览 {{count}} 处",
   "ui.messagePart.diagnostic.error": "错误",
   "ui.messagePart.title.edit": "编辑",
   "ui.messagePart.title.write": "写入",
@Astro-Han Astro-Han force-pushed the claude/sidebar-typography branch from 35c896a to 7a885f1 Compare April 29, 2026 04:10

@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 implements a broad UI update focused on reducing the base font size from 14px to 13px and tightening the layout across the application. Key modifications include updating global CSS variables, utility classes, and adjusting component-specific styles and icon sizes. Additionally, the 'Help' link has been removed from the sidebar. Feedback was provided regarding the replacement of custom Button components with native button elements in the sidebar, which may negatively impact accessibility and styling consistency; it is recommended to use style overrides on the existing components instead.

Comment thread packages/app/src/pages/layout/pawwork-sidebar.tsx

@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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/pages/layout/pawwork-sidebar.tsx`:
- Around line 246-247: The row buttons in pawwork-sidebar.tsx (the New, Search,
and Settings rows) remove default keyboard focus by including
"focus:outline-none" but do not add any focus-visible replacement; update the
class strings on those row button elements (the ones containing
"focus:outline-none") to keep the visual outline for keyboard users by replacing
the blanket removal with accessible focus-visible utilities (e.g., keep or add
"focus-visible:outline" and an appropriate size/color such as
"focus-visible:outline-2" and "focus-visible:outline-primary" or your design
system equivalents) so keyboard focus is visible while mouse focus remains
unchanged.

In `@packages/app/src/pages/layout/sidebar.css`:
- Around line 1-6: Sidebar leading-slot container is using size-5 (20px) causing
asymmetric padding with the 14×14 icon rule; update the container in
sidebar-project.tsx (around the leading-slot element at the component rendering)
to replace the `size-5` class with `w-[14px] h-[14px]` so it matches the icon
constraint and mirrors the usage in sidebar-items.tsx.

In `@packages/ui/src/components/markdown.css`:
- Line 124: Replace the hard-coded "font-size: 12px;" occurrences with the
shared x-small design token (e.g. use var(--font-size-x-small) or the project's
equivalent token name) wherever "font-size: 12px;" appears (the occurrences
currently at the two "font-size: 12px;" lines in markdown.css); update both
places to reference the token to ensure consistency and maintainability.

In `@packages/ui/src/components/message-part.css`:
- Line 803: Replace the literal font-size: 12px in message-part.css (the rule
that sets permission pattern/code text) with the shared x-small design token
used across the project; change the declaration to use the token (for example
var(--font-size-x-small) or the project's equivalent --x-small token) so the
component stays in sync with the design system.

In `@packages/ui/src/i18n/en.ts`:
- Around line 80-81: Replace the terse jargon "dir"/"dirs" in the localization
entries with a full user-facing term; update the translation keys
"ui.messagePart.context.list.one" and "ui.messagePart.context.list.other" in
en.ts to use "directory" and "directories" (or "folder"/"folders" if you prefer)
so the UI reads clearly for users.

In `@packages/ui/src/i18n/zh.ts`:
- Around line 70-75: Update the Chinese copy entries for the context labels to
use explicit nouns matching existing tool terminology: replace values for
"ui.messagePart.context.read.one" and "ui.messagePart.context.read.other" with
phrasing that explicitly mentions files (e.g., "读取文件 {{count}} 个"), and replace
"ui.messagePart.context.list.one" and "ui.messagePart.context.list.other" with
phrasing that explicitly mentions directories or listings (e.g., "列出目录 {{count}}
个" or "列出目录 {{count}} 次"), ensuring both .one and .other variants are consistent
and grammatically correct for numeric interpolation.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5464f4d5-e526-4f3d-b64a-80c470506f82

📥 Commits

Reviewing files that changed from the base of the PR and between c2d75c2 and 35c896a.

📒 Files selected for processing (59)
  • packages/app/src/app.tsx
  • packages/app/src/components/dialog-connect-provider.tsx
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/components/dialog-custom-provider.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/dialog-release-notes.tsx
  • packages/app/src/components/dialog-select-directory.tsx
  • packages/app/src/components/dialog-select-file.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-select-provider.tsx
  • packages/app/src/components/dialog-select-server.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/drag-overlay.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/server/server-row.tsx
  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/status-popover-body.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar.css
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/composer/session-revert-dock.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/app/src/pages/session/terminal-panel.tsx
  • packages/ui/src/components/basic-tool.css
  • packages/ui/src/components/button.css
  • packages/ui/src/components/dialog.css
  • packages/ui/src/components/diff-changes.css
  • packages/ui/src/components/file-media.tsx
  • packages/ui/src/components/file-search.tsx
  • packages/ui/src/components/list.css
  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-nav.css
  • packages/ui/src/components/message-part.css
  • packages/ui/src/components/message-part.tsx
  • packages/ui/src/components/tabs.css
  • packages/ui/src/components/tag.css
  • packages/ui/src/components/text-field.css
  • packages/ui/src/components/toast.css
  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/styles/theme.css
  • packages/ui/src/styles/utilities.css
💤 Files with no reviewable changes (2)
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-desktop
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (1)
packages/app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/app/AGENTS.md)

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/dialog-select-server.tsx
  • packages/app/src/components/dialog-custom-provider.tsx
  • packages/app/src/components/prompt-input/drag-overlay.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/server/server-row.tsx
  • packages/app/src/components/dialog-select-provider.tsx
  • packages/app/src/components/dialog-release-notes.tsx
  • packages/app/src/pages/session/terminal-panel.tsx
  • packages/app/src/app.tsx
  • packages/app/src/pages/session/composer/session-revert-dock.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/components/dialog-select-directory.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/components/dialog-select-file.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/status-popover-body.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/dialog-connect-provider.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input.tsx
🧠 Learnings (19)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/dialog-select-server.tsx
  • packages/app/src/components/dialog-custom-provider.tsx
  • packages/app/src/components/prompt-input/drag-overlay.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/server/server-row.tsx
  • packages/app/src/components/dialog-select-provider.tsx
  • packages/app/src/components/dialog-release-notes.tsx
  • packages/app/src/pages/session/terminal-panel.tsx
  • packages/app/src/app.tsx
  • packages/app/src/pages/session/composer/session-revert-dock.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/components/dialog-select-directory.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/components/dialog-select-file.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/status-popover-body.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/dialog-connect-provider.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/dialog-select-server.tsx
  • packages/app/src/components/dialog-custom-provider.tsx
  • packages/app/src/components/prompt-input/drag-overlay.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/server/server-row.tsx
  • packages/app/src/components/dialog-select-provider.tsx
  • packages/app/src/components/dialog-release-notes.tsx
  • packages/app/src/pages/session/terminal-panel.tsx
  • packages/app/src/app.tsx
  • packages/app/src/pages/session/composer/session-revert-dock.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/components/dialog-select-directory.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-connect-websearch.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/components/dialog-select-file.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/status-popover-body.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/dialog-connect-provider.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.

Applied to files:

  • packages/app/src/pages/layout/sidebar.css
  • packages/ui/src/components/toast.css
  • packages/ui/src/components/dialog.css
  • packages/ui/src/components/button.css
  • packages/ui/src/components/tabs.css
  • packages/ui/src/components/tag.css
  • packages/ui/src/components/diff-changes.css
  • packages/ui/src/components/text-field.css
  • packages/ui/src/components/markdown.css
  • packages/ui/src/styles/theme.css
  • packages/ui/src/components/basic-tool.css
  • packages/ui/src/components/list.css
  • packages/ui/src/styles/utilities.css
  • packages/ui/src/components/message-nav.css
  • packages/ui/src/components/message-part.css
📚 Learning: 2026-04-24T05:39:58.329Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.

Applied to files:

  • packages/app/src/components/prompt-input/drag-overlay.tsx
  • packages/app/src/pages/session/composer/session-revert-dock.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-24T05:48:39.493Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:39.493Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.

Applied to files:

  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-24T17:08:46.780Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/ui/src/i18n/zh.ts
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-22T09:32:58.310Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.

Applied to files:

  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-28T07:27:41.971Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/file/ripgrep.ts:29-118
Timestamp: 2026-04-28T07:27:41.971Z
Learning: In `packages/opencode/src/file/ripgrep.ts` (Astro-Han/pawwork), the internal Effect schemas `TimeStats`, `Stats`, `PathText`, `Begin`, `End`, `Summary`, and the exported `SearchMatch`/`Match`/`Result` union are all declared as `Schema.Struct` — matching upstream `dev:packages/opencode/src/file/ripgrep.ts` lines 60-118. PR `#270` is an upstream-sync graft per `project_upstream_strategy.md`. Converting these to `Schema.Class` would diverge from the upstream baseline and create recurring merge friction on future syncs. The `Schema.Class` convention applies to PawWork-authored schemas, not to upstream-grafted shapes in this file. Do NOT re-flag the use of `Schema.Struct` for these schemas in upstream-sync PRs; flag it only in a PR that simultaneously converts the upstream side, or in a dedicated PawWork carve-out PR for ripgrep schemas.

Applied to files:

  • packages/app/src/pages/layout/sidebar-workspace.tsx
📚 Learning: 2026-04-28T03:01:37.478Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 282
File: packages/ui/src/i18n/zh.ts:109-109
Timestamp: 2026-04-28T03:01:37.478Z
Learning: In Astro-Han/pawwork PR `#282` (`packages/ui/src/i18n/zh.ts`), the translation for `ui.tool.questions` is intentionally `提出问题` (verb-object structure), NOT `向你提问` (prepositional-phrase structure). The choice was made after a UI smoke run to keep consistent verb-object phrasing across all zh tool-card labels (`执行命令`, `列出目录`, `查找文件`, `搜索文本`, `读取文件`, `读取网页`, `批量修改`, `查看待办`). Do NOT flag `提出问题` as incorrect for this key.

Applied to files:

  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-28T06:51:54.812Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.

Applied to files:

  • packages/app/src/components/status-popover-body.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.

Applied to files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `data-component`, `data-action`, or semantic roles for selectors instead of CSS class names or IDs

Applied to files:

  • packages/app/src/pages/session/composer/session-todo-dock.tsx
📚 Learning: 2026-04-28T08:14:31.436Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:48-48
Timestamp: 2026-04-28T08:14:31.436Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork), the `filePath` schema description (`"The absolute path to the file to modify"`) is upstream-inherited from `dev:packages/opencode/src/tool/edit.ts:48`. The runtime actually accepts relative paths (resolved via `Instance.directory` at lines 79-81), but the description fix is intentionally deferred to a single PawWork-authored description-cleanup PR that will also cover the identical mismatch in `packages/opencode/src/tool/write.ts:24`. Do NOT re-flag the too-narrow `filePath` description in upstream-sync PRs; flag it only in the dedicated description-cleanup PR.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T07:28:14.317Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/lsp.ts:23-32
Timestamp: 2026-04-28T07:28:14.317Z
Learning: In `packages/opencode/src/tool/lsp.ts` (Astro-Han/pawwork, PR `#270`), the `Parameters` schema requires `line` and `character` for all operations, including `workspaceSymbol` and `documentSymbol` which never use coordinates. This matches the upstream `dev:packages/opencode/src/tool/lsp.ts:23-32` baseline exactly — both fields are declared as required `Schema.Number` with `>= 1` checks. The fix (per-operation schema split, or making `line`/`character` optional with handler-side presence validation for operations that need them like `goToDefinition`/`findReferences`) is deferred to a follow-up PR or upstream report to avoid mixing refactor + bug-fix intents and drifting the diff from the upstream baseline. Do NOT re-flag the required coordinates on `workspaceSymbol`/`documentSymbol` in upstream-sync PRs; flag it only in a PawWork-authored PR that directly touches `lsp.ts` parameter validation.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T04:56:13.350Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/tool.ts:8-10
Timestamp: 2026-04-28T04:56:13.350Z
Learning: In `packages/opencode/src/tool/tool.ts` (Astro-Han/pawwork, PR `#270`), the `Metadata` interface uses `[key: string]: any` as its index signature. This is upstream-inherited code adopted wholesale via the graft strategy (per `project_upstream_strategy.md`). The fix — tightening to `Record<string, unknown>` and explicitly narrowing framework-owned fields like `truncated` — is intentionally deferred to a follow-up PR or upstream report. Do NOT re-flag the `any` index signature on `Metadata` in PR `#270` or in future upstream-sync PRs that carry the same upstream baseline.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-25T09:51:18.951Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/menu-template.ts:178-189
Timestamp: 2026-04-25T09:51:18.951Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/menu-template.ts`), the `buildMacosMenuTemplate` and `buildWindowsMenuTemplate` functions are intentionally kept as separate, literal per-platform templates rather than sharing extracted submenu helpers. The split exists to allow future per-platform divergence; the templates already differ structurally (File menu: macOS omits `quit` because it lives in the App menu, Windows places `quit` next to `close`; Window submenu differs; macOS has an App menu with no Windows equivalent; View differs on terminal accelerator `Ctrl+\`` vs `CmdOrCtrl+\``). Do NOT suggest extracting shared submenu builder helpers (e.g., `buildEditSubmenu`, `buildViewSubmenu`) as YAGNI — the literal duplication is intentional and easy to scan.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
🔇 Additional comments (56)
packages/ui/src/components/file-search.tsx (1)

34-34: Typography update is consistent and safe.

text-13-regular here aligns with the PR-wide 13px body baseline and matches the adjacent counter text scale in this component.

packages/ui/src/components/file-media.tsx (1)

255-255: Typography update is consistent with the 13px baseline refactor.

This class change is aligned with the PR’s body-text unification goal and is safe (style-only, no behavior impact).

packages/ui/src/i18n/en.ts (1)

76-77: Good pluralized read-copy update.

This keeps {{count}} interpolation and improves readability without changing key structure.

packages/ui/src/i18n/zh.ts (1)

58-58: gatheredContext wording looks good.

“探索完毕” is clear and reads naturally as a completed state label.

packages/ui/src/styles/theme.css (1)

8-11: Typography token shift is coherent and safely centralized.

--font-size-x-small, --font-size-base, and --font-size-hierarchy are aligned with the intended 12/13 scale and provide a clean tokenized base for downstream components.

packages/ui/src/styles/utilities.css (2)

46-72: New 12px utility set is consistent with existing typography primitives.

The added regular/medium/mono variants are structured correctly and reuse the shared font/spacing tokens as expected.


113-114: Hierarchy-token wiring for 14-medium/mono is correct.

Switching these to --font-size-hierarchy matches the new token-driven typography strategy.

Also applies to: 123-124

packages/ui/src/components/basic-tool.css (1)

73-74: Font-size token migration is applied consistently across tool text slots.

Good consolidation on --font-size-base; this keeps the component aligned with the global 13px baseline.

Also applies to: 98-99, 123-124, 169-170, 238-239

packages/ui/src/components/tabs.css (1)

52-55: Tabs trigger typography now correctly follows hierarchy tokenization.

This change is in line with the typography unification and keeps the component-level rule consistent with shared tokens.

packages/ui/src/components/toast.css (1)

122-125: Toast title size now correctly depends on shared hierarchy token.

The tokenized size update is clean and consistent with the rest of the typography migration.

packages/ui/src/components/dialog.css (1)

90-93: Dialog description typography is correctly migrated to base token.

This keeps dialog body text aligned with the global 13px baseline.

packages/ui/src/components/message-nav.css (1)

74-74: Message-nav title preview correctly uses base typography token.

Good replacement of fixed sizing with the shared scale.

packages/ui/src/components/tag.css (1)

29-32: Large tag typography is correctly routed through hierarchy token.

This is consistent with the broader tokenized typography refactor.

packages/ui/src/components/button.css (1)

162-162: Tokenized hierarchy size looks correct.

Line 162 correctly moves large-button typography to --font-size-hierarchy, keeping the component aligned with the shared scale.

packages/ui/src/components/diff-changes.css (1)

10-10: Good token migration for diff counters.

Lines 10 and 22 now inherit base typography from theme tokens, which prevents future drift from hard-coded sizes.

Also applies to: 22-22

packages/ui/src/components/text-field.css (1)

10-10: Input typography update is consistent.

Lines 10 and 90 correctly standardize text-field font sizing on --font-size-base.

Also applies to: 90-90

packages/ui/src/components/list.css (1)

164-164: List typography tokenization is well applied.

These replacements cleanly unify list text sizing on the theme scale without changing layout logic.

Also applies to: 202-202, 243-243, 318-318

packages/ui/src/components/markdown.css (1)

26-26: Heading hierarchy token usage is correct.

Line 26 aligns markdown heading sizing with the shared hierarchy token.

packages/ui/src/components/message-part.css (1)

440-440: Token migration across message-part typography is solid.

These updates consistently move user-facing copy to --font-size-base / --font-size-hierarchy without changing behavior.

Also applies to: 761-761, 778-778, 858-858, 944-944, 1070-1070, 1078-1078, 1106-1106

packages/ui/src/components/message-part.tsx (1)

923-923: Class swap is correct for the new text scale.

Line 923 cleanly aligns the context tool group title with the 13px typography baseline.

packages/app/src/components/prompt-input/drag-overlay.tsx (1)

20-20: Overlay label typography update looks good.

Line 20 correctly applies the new 13px body class for consistency with the rest of the prompt UI.

packages/app/src/components/dialog-select-server.tsx (1)

558-558: LGTM — typography update aligns with the PR baseline shift.

The server default status badge now uses text-13-regular, matching the PR-wide typography downscale from 14px to 13px.

packages/app/src/pages/session/terminal-panel.tsx (1)

247-247: LGTM — terminal typography updates match the baseline shift.

Both the loading fallback chip (line 247) and the drag overlay label (line 328) now use text-13-regular, consistent with the PR-wide typography downscale.

Also applies to: 328-328

packages/app/src/components/dialog-manage-models.tsx (1)

37-37: LGTM — dialog action button typography updated correctly.

The "Connect Provider" button now uses text-13-medium, aligning with the PR-wide typography baseline shift.

packages/app/src/components/session/session-sortable-tab.tsx (1)

25-25: LGTM — file tab label typography updated correctly.

The filename display now uses text-13-medium, matching the PR-wide typography baseline shift.

packages/app/src/pages/session/message-timeline.tsx (1)

641-641: LGTM — session header and dialog typography updated consistently.

All session title elements (parent link, separator, child title, draft input) and the delete confirmation dialog text now use text-13-* variants, aligning with the PR-wide typography baseline shift.

Also applies to: 759-759, 766-766, 795-795, 809-809

packages/app/src/components/server/server-row.tsx (1)

85-85: LGTM — server version typography updated correctly.

The server version display now uses text-13-regular, matching the PR-wide typography baseline shift.

packages/app/src/pages/session/files-tab.tsx (1)

115-115: LGTM — files tab typography updated consistently.

Both the empty state text (line 115) and the filename display (line 133) now use text-13-* variants, aligning with the PR-wide typography baseline shift.

Also applies to: 133-133

packages/app/src/pages/session/composer/session-revert-dock.tsx (1)

47-49: Typography downscale is consistent and safe.

These class updates are purely presentational and align with the 13px hierarchy objective without affecting dock behavior.

packages/app/src/components/settings-general.tsx (1)

383-383: Heading/title typography normalization looks good.

The updated text-13-medium usage is consistent across settings sections and row titles, with no functional regressions introduced.

Also applies to: 492-492, 536-536, 589-589, 655-655, 688-688, 728-728

packages/app/src/app.tsx (1)

344-344: Connection error panel typography update is clean.

Both text class changes are consistent with the 13px baseline and do not alter retry/server-selection behavior.

Also applies to: 364-364

packages/app/src/components/status-popover-body.tsx (1)

305-305: Popover text hierarchy is now consistent across tabs.

Good consolidation to text-13-regular for labels and empty states; behavior remains unchanged.

Also applies to: 348-348, 375-375, 401-401, 414-414, 428-428, 434-434

packages/app/src/components/dialog-custom-provider.tsx (1)

184-184: Description text update is correct and low-risk.

This is a straight visual adjustment and preserves dialog form behavior.

packages/app/src/components/dialog-select-model-unpaid.tsx (1)

46-46: Unpaid model dialog typography changes are cohesive.

The updated text sizing is consistent across headings, provider annotations, and the “view all” row without changing interaction behavior.

Also applies to: 90-90, 112-112, 119-119, 126-126, 133-133

packages/app/src/components/prompt-input/send-button.tsx (1)

23-23: Send button density tweak matches the composer tightening pass.

The size reduction is localized to styling and keeps existing submit/stop behavior intact.

packages/app/src/components/dialog-select-provider.tsx (1)

69-69: Provider-note typography change is solid.

These updates are consistent with the global 13px body baseline and preserve existing provider-list behavior.

Also applies to: 77-77

packages/app/src/components/settings-keybinds.tsx (1)

411-411: Typography downshift is consistent and behavior-preserving.

These class updates align with the 13px baseline and do not alter rendering logic or interactions.

Also applies to: 416-416, 446-446, 448-448

packages/app/src/pages/layout/sidebar-project.tsx (1)

201-201: Leading-slot + typography adjustments look correct.

The slot marker and size reduction are coherent with sidebar rail unification, and text class changes are safely scoped.

Also applies to: 229-229, 232-232

packages/app/src/components/dialog-select-model.tsx (1)

246-246: Dialog action typography change is clean and isolated.

This is a safe visual-only update with no behavior impact.

packages/app/src/pages/layout/sidebar-workspace.tsx (1)

78-78: Workspace typography updates are consistent and low-risk.

All edits are class-level density adjustments with unchanged behavior paths.

Also applies to: 103-103, 109-109, 123-124, 277-277

packages/app/src/pages/session/session-side-panel.tsx (1)

471-471: Side-panel fallback typography changes are good.

These changes are purely presentational and align with the global 13px shift.

Also applies to: 505-505

packages/app/src/components/prompt-input/slash-popover.tsx (1)

68-68: Popover typography adjustments are consistent and safe.

No interaction or filtering behavior was altered; the class updates are clean.

Also applies to: 97-97, 99-99

packages/app/src/components/dialog-select-directory.tsx (1)

366-366: Directory row typography migration looks good.

Both branches now consistently use the new text scale without affecting logic.

Also applies to: 378-378

packages/app/src/pages/session/composer/session-todo-dock.tsx (1)

138-138: Todo dock typography update is consistent and non-disruptive.

The revised text scale is applied uniformly across related todo text surfaces.

Also applies to: 170-170, 253-253

packages/app/src/components/dialog-release-notes.tsx (1)

77-77: Typography update is clean and behavior-preserving.

This class change is scoped to presentation and is consistent with the PR’s 13px baseline goal.

packages/app/src/components/dialog-select-file.tsx (1)

447-447: LGTM on the typography consolidation in list rows.

These updates stay purely presentational and keep the command/file/session rendering paths intact.

Also applies to: 460-463, 476-477, 483-484

packages/app/src/components/dialog-connect-websearch.tsx (1)

102-102: State-copy typography updates look consistent across all dialog branches.

The modified lines are presentation-only and do not alter any save/remove/status behavior.

Also applies to: 161-161, 169-169, 174-174, 180-180, 229-237

packages/app/src/components/settings-providers.tsx (1)

140-140: Provider settings typography tightening is solid.

All touched segments remain behavior-neutral and align with the 13px standardization.

Also applies to: 145-146, 159-159, 165-166, 182-182, 194-194, 227-227, 249-249

packages/app/src/pages/home.tsx (1)

77-77: Home page typography adjustments look good.

These are clean visual refinements with no behavioral side effects.

Also applies to: 92-92, 107-107, 128-128

packages/app/src/pages/layout/sidebar-items.tsx (1)

103-103: Sidebar rail/density refinements are coherent and safe.

The updated leading-slot/action geometry is consistent, and interaction logic remains intact.

Also applies to: 113-113, 122-123, 236-236, 252-252, 296-296, 302-302

packages/app/src/components/settings-models.tsx (1)

19-19: Models settings typography pass looks good.

Scoped presentation-only updates with no behavior changes.

Also applies to: 27-30, 103-103, 112-112

packages/app/src/components/prompt-input/workspace-chip.tsx (1)

66-66: Workspace chip visual updates are consistent and non-breaking.

Typography and icon swap are well-scoped UI refinements; interaction behavior remains unchanged.

Also applies to: 99-99, 125-125, 131-131

packages/app/src/pages/session.tsx (1)

1315-1315: Typography downshift is consistent and safe.

The updated text-13-* callsites in these review-empty and selector-value surfaces align cleanly with the PR’s density objective without changing behavior.

Also applies to: 1322-1322, 1329-1330, 1361-1361

packages/app/src/components/prompt-input.tsx (1)

1124-1124: Prompt-input density updates look coherent.

The class changes are internally consistent with the 13/12 typography rollout and do not alter interaction behavior in the modified segments.

Also applies to: 1161-1161, 1205-1205, 1228-1228, 1505-1505, 1515-1515, 1564-1564, 1571-1571

packages/app/src/components/dialog-connect-provider.tsx (1)

279-279: Dialog typography changes are clean.

These updates are consistent visual downscales only; no behavioral risks were introduced in the touched states.

Also applies to: 303-303, 364-364, 427-430, 439-439, 500-500, 558-558, 570-570, 606-606, 617-617, 628-628

packages/app/src/pages/layout.tsx (1)

1754-1758: Layout text-scale updates are consistent with the new baseline.

The changed typography callsites are coherent with the PR scope and don’t affect behavior.

Also applies to: 1828-1833, 2172-2174, 2197-2198, 2378-2383

Comment thread packages/app/src/pages/layout/pawwork-sidebar.tsx Outdated
Comment thread packages/app/src/pages/layout/sidebar.css
Comment thread packages/ui/src/components/markdown.css Outdated
Comment thread packages/ui/src/components/message-part.css Outdated
Comment thread packages/ui/src/i18n/en.ts Outdated
Comment thread packages/ui/src/i18n/zh.ts Outdated
The footer button was removed in 9ad00d1 (folded into the workspace
chip menu via the folder-add-left icon). Update home.spec.ts so it no
longer waits for a button that no longer exists.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review pass focused on edge cases, regressions, and cross-module impact.

Comment thread packages/app/src/pages/layout/sidebar-items.tsx Outdated
Comment thread packages/app/src/pages/layout.tsx
The New session, Search, and Settings rows kept focus:outline-none
without a focus-visible replacement, leaving keyboard users without
any visible focus affordance. Add focus-visible:bg-surface-raised-base-hover
so keyboard focus reuses the same hover treatment the rows already
have for mouse hover.

Addresses coderabbitai review on #320.
Replace the three literal 12px declarations introduced for code
styling (markdown.css .shiki and inline code, message-part.css
permission-patterns code) with var(--font-size-x-small). The token
was added to theme.css in the same PR; routing through it keeps the
design system consistent and lets a future code-size change ripple
from one place.

Addresses coderabbitai review on #320.
Astro-Han added a commit that referenced this pull request Apr 29, 2026
en: "Listed N dir / dirs" was implementation jargon; use the full
word "directory / directories" so the UI reads cleanly for
non-technical users (PawWork's "for everyone" positioning).

zh: collapse the prior verb-only labels (阅读 N 篇 / 浏览 N 处) to
verb-object form that matches PR #282's tool-card labels (读取文件
/ 列出目录) and stays visually symmetric across all three counts:

- 读取 N 个文件 (was 阅读 N 篇)
- 搜索 N 处匹配 (was 搜索 N 次, now matches verb+量+名 shape)
- 列出 N 个目录 (was 浏览 N 处)

"处" stays as the search measure word because grep-style search
returns hits at file:line locations, which is exactly what 处
denotes (an occurrence at a position) — more concrete than 项.

Addresses coderabbitai review on #320.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/pages/layout/sidebar-items.tsx (1)

121-127: ⚠️ Potential issue | 🟠 Major

Keep the leading slot width in sync with the pin control.

props.leadingSlot now lands in a 14px rail, but the pin button rendered from packages/app/src/pages/layout/pawwork-sidebar.tsx:115-137 is still size-5. That control will overflow the slot and reintroduce horizontal drift in the tightened sidebar.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/layout/sidebar-items.tsx` around lines 121 - 127, The
leading slot in sidebar-items.tsx is fixed to 14px causing overflow because the
pin control in pawwork-sidebar.tsx renders at size-5; update the leading slot
(the div with data-leading-slot and indicator()) to use the same sizing as the
pin button instead of hardcoded 14px—either replace w-[14px] h-[14px] with the
same utility/class/variable used by the pin button (or a shared CSS
variable/token), or adjust the pin button to match the slot, so
props.leadingSlot and indicator() align with the pin control size-5.
♻️ Duplicate comments (4)
packages/ui/src/i18n/en.ts (1)

80-81: ⚠️ Potential issue | 🟡 Minor

Replace dir/dirs with user-facing wording.

dir/dirs reads as internal shorthand in UI copy.

💡 Suggested wording
-  "ui.messagePart.context.list.one": "Listed {{count}} dir",
-  "ui.messagePart.context.list.other": "Listed {{count}} dirs",
+  "ui.messagePart.context.list.one": "Listed {{count}} directory",
+  "ui.messagePart.context.list.other": "Listed {{count}} directories",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/i18n/en.ts` around lines 80 - 81, The UI copy uses internal
shorthand "dir/dirs" in the i18n keys "ui.messagePart.context.list.one" and
"ui.messagePart.context.list.other"; update both translation strings to
user-facing wording (e.g., "Listed {{count}} directory" and "Listed {{count}}
directories" or another user-friendly term like "item/items" or "entry/entries")
so the text reads naturally to end users and preserves the {{count}} variable.
packages/ui/src/i18n/zh.ts (1)

70-75: ⚠️ Potential issue | 🟡 Minor

Prefer explicit file/directory nouns in context counters.

These strings are less specific than the existing zh tool terms in this file (读取文件, 列出目录), so the summary can feel semantically drifted.

💡 Suggested copy alignment
-  "ui.messagePart.context.read.one": "阅读 {{count}} 篇",
-  "ui.messagePart.context.read.other": "阅读 {{count}} 篇",
+  "ui.messagePart.context.read.one": "读取 {{count}} 个文件",
+  "ui.messagePart.context.read.other": "读取 {{count}} 个文件",
   "ui.messagePart.context.search.one": "搜索 {{count}} 次",
   "ui.messagePart.context.search.other": "搜索 {{count}} 次",
-  "ui.messagePart.context.list.one": "浏览 {{count}} 处",
-  "ui.messagePart.context.list.other": "浏览 {{count}} 处",
+  "ui.messagePart.context.list.one": "列出 {{count}} 个目录",
+  "ui.messagePart.context.list.other": "列出 {{count}} 个目录",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/i18n/zh.ts` around lines 70 - 75, The context-count strings
are too generic and should use explicit nouns to match existing
terminology—update the translations for the keys
ui.messagePart.context.read.one, ui.messagePart.context.read.other,
ui.messagePart.context.list.one, and ui.messagePart.context.list.other to
reference 文件 for read counters and 目录 for list counters (aligning with existing
terms like 读取文件 and 列出目录), so the summaries stay semantically consistent across
the file.
packages/ui/src/components/message-part.css (1)

803-803: 🧹 Nitpick | 🔵 Trivial

Use a font-size token instead of a literal at Line 803.

font-size: 12px; is still hardcoded in code styles; this should use the shared x-small token for consistency with the design system.

Proposed refactor
-    code {
-      font-size: 12px;
+    code {
+      font-size: var(--font-size-x-small);
       line-height: var(--line-height-large);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/message-part.css` at line 803, Replace the
hardcoded font-size: 12px in the code styles inside message-part.css with the
shared "x-small" font-size design token; locate the selector targeting inline
code (the `code` styles in message-part.css) and swap the literal 12px for the
project's x-small token (for example use the project's token variable such as
var(--font-size-x-small) or the equivalent token reference used elsewhere) so
the component uses the centralized typography token instead of a literal.
packages/app/src/pages/layout/pawwork-sidebar.tsx (1)

242-359: ⚠️ Potential issue | 🟠 Major

Restore visible focus styling on the raw sidebar buttons.

The New/Search/Settings <button>s still remove the browser outline via focus:outline-none but do not add a focus-visible replacement, so keyboard focus disappears on the primary sidebar actions. This is the same accessibility gap already flagged in the prior review.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/layout/pawwork-sidebar.tsx` around lines 242 - 359,
The sidebar buttons (data-action="pawwork-session-new",
"pawwork-session-search", "pawwork-open-settings") currently remove the native
focus outline via "focus:outline-none" and lack a keyboard-visible replacement;
update these elements to restore visible focus by replacing or augmenting
"focus:outline-none" with appropriate focus-visible utility classes (e.g.,
Tailwind's focus-visible:outline-* / focus-visible:ring-* or your design
system's focus class) so keyboard users see a clear focus indicator while
preserving the visual style for mouse focus.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/app/src/pages/layout/sidebar-items.tsx`:
- Around line 121-127: The leading slot in sidebar-items.tsx is fixed to 14px
causing overflow because the pin control in pawwork-sidebar.tsx renders at
size-5; update the leading slot (the div with data-leading-slot and indicator())
to use the same sizing as the pin button instead of hardcoded 14px—either
replace w-[14px] h-[14px] with the same utility/class/variable used by the pin
button (or a shared CSS variable/token), or adjust the pin button to match the
slot, so props.leadingSlot and indicator() align with the pin control size-5.

---

Duplicate comments:
In `@packages/app/src/pages/layout/pawwork-sidebar.tsx`:
- Around line 242-359: The sidebar buttons (data-action="pawwork-session-new",
"pawwork-session-search", "pawwork-open-settings") currently remove the native
focus outline via "focus:outline-none" and lack a keyboard-visible replacement;
update these elements to restore visible focus by replacing or augmenting
"focus:outline-none" with appropriate focus-visible utility classes (e.g.,
Tailwind's focus-visible:outline-* / focus-visible:ring-* or your design
system's focus class) so keyboard users see a clear focus indicator while
preserving the visual style for mouse focus.

In `@packages/ui/src/components/message-part.css`:
- Line 803: Replace the hardcoded font-size: 12px in the code styles inside
message-part.css with the shared "x-small" font-size design token; locate the
selector targeting inline code (the `code` styles in message-part.css) and swap
the literal 12px for the project's x-small token (for example use the project's
token variable such as var(--font-size-x-small) or the equivalent token
reference used elsewhere) so the component uses the centralized typography token
instead of a literal.

In `@packages/ui/src/i18n/en.ts`:
- Around line 80-81: The UI copy uses internal shorthand "dir/dirs" in the i18n
keys "ui.messagePart.context.list.one" and "ui.messagePart.context.list.other";
update both translation strings to user-facing wording (e.g., "Listed {{count}}
directory" and "Listed {{count}} directories" or another user-friendly term like
"item/items" or "entry/entries") so the text reads naturally to end users and
preserves the {{count}} variable.

In `@packages/ui/src/i18n/zh.ts`:
- Around line 70-75: The context-count strings are too generic and should use
explicit nouns to match existing terminology—update the translations for the
keys ui.messagePart.context.read.one, ui.messagePart.context.read.other,
ui.messagePart.context.list.one, and ui.messagePart.context.list.other to
reference 文件 for read counters and 目录 for list counters (aligning with existing
terms like 读取文件 and 列出目录), so the summaries stay semantically consistent across
the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 56899805-07cc-494f-8309-bd7e8577c856

📥 Commits

Reviewing files that changed from the base of the PR and between 35c896a and d49ea27.

📒 Files selected for processing (29)
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/ui/src/components/basic-tool.css
  • packages/ui/src/components/message-part.css
  • packages/ui/src/components/message-part.tsx
  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/styles/theme.css
💤 Files with no reviewable changes (3)
  • packages/app/src/i18n/en.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/i18n/zh.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
packages/app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/app/AGENTS.md)

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
🧠 Learnings (19)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/lsp.ts:23-32
Timestamp: 2026-04-28T07:28:14.317Z
Learning: In `packages/opencode/src/tool/lsp.ts` (Astro-Han/pawwork, PR `#270`), the `Parameters` schema requires `line` and `character` for all operations, including `workspaceSymbol` and `documentSymbol` which never use coordinates. This matches the upstream `dev:packages/opencode/src/tool/lsp.ts:23-32` baseline exactly — both fields are declared as required `Schema.Number` with `>= 1` checks. The fix (per-operation schema split, or making `line`/`character` optional with handler-side presence validation for operations that need them like `goToDefinition`/`findReferences`) is deferred to a follow-up PR or upstream report to avoid mixing refactor + bug-fix intents and drifting the diff from the upstream baseline. Do NOT re-flag the required coordinates on `workspaceSymbol`/`documentSymbol` in upstream-sync PRs; flag it only in a PawWork-authored PR that directly touches `lsp.ts` parameter validation.
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/components/session/session-sortable-tab.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/pages/session/files-tab.tsx
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-24T05:39:58.329Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.

Applied to files:

  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-24T05:48:39.493Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:39.493Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.

Applied to files:

  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `data-component`, `data-action`, or semantic roles for selectors instead of CSS class names or IDs

Applied to files:

  • packages/app/src/components/dialog-manage-models.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-28T04:56:18.533Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/test/file/ripgrep.test.ts:172-175
Timestamp: 2026-04-28T04:56:18.533Z
Learning: In `packages/opencode/test/file/ripgrep.test.ts` (Astro-Han/pawwork, PR `#270`), the `files dies on nonexistent directory` test hardcodes `/tmp/nonexistent-dir-12345` as the missing-directory path. This is upstream-inherited behaviour adopted wholesale via the graft strategy (`project_upstream_strategy.md`). The fix — replacing the hardcoded path with a platform-neutral guaranteed-nonexistent child path derived from the `tmpdir` fixture — is intentionally deferred to a follow-up PR or upstream report, to avoid mixing bugfix + refactor intents and drifting the diff from the upstream baseline future syncs need. Do NOT re-flag the hardcoded `/tmp/nonexistent-dir-12345` path in this file as a blocking or actionable issue until the follow-up PR lands.

Applied to files:

  • packages/ui/src/i18n/en.ts
📚 Learning: 2026-04-28T08:14:31.436Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:48-48
Timestamp: 2026-04-28T08:14:31.436Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork), the `filePath` schema description (`"The absolute path to the file to modify"`) is upstream-inherited from `dev:packages/opencode/src/tool/edit.ts:48`. The runtime actually accepts relative paths (resolved via `Instance.directory` at lines 79-81), but the description fix is intentionally deferred to a single PawWork-authored description-cleanup PR that will also cover the identical mismatch in `packages/opencode/src/tool/write.ts:24`. Do NOT re-flag the too-narrow `filePath` description in upstream-sync PRs; flag it only in the dedicated description-cleanup PR.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T03:01:37.478Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 282
File: packages/ui/src/i18n/zh.ts:109-109
Timestamp: 2026-04-28T03:01:37.478Z
Learning: In Astro-Han/pawwork PR `#282` (`packages/ui/src/i18n/zh.ts`), the translation for `ui.tool.questions` is intentionally `提出问题` (verb-object structure), NOT `向你提问` (prepositional-phrase structure). The choice was made after a UI smoke run to keep consistent verb-object phrasing across all zh tool-card labels (`执行命令`, `列出目录`, `查找文件`, `搜索文本`, `读取文件`, `读取网页`, `批量修改`, `查看待办`). Do NOT flag `提出问题` as incorrect for this key.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-22T09:32:58.310Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.

Applied to files:

  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/ui/src/styles/theme.css
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-24T17:08:46.780Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/ui/src/i18n/zh.ts
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.

Applied to files:

  • packages/ui/src/styles/theme.css
  • packages/ui/src/components/basic-tool.css
  • packages/ui/src/components/message-part.css
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.

Applied to files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T07:28:14.317Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/lsp.ts:23-32
Timestamp: 2026-04-28T07:28:14.317Z
Learning: In `packages/opencode/src/tool/lsp.ts` (Astro-Han/pawwork, PR `#270`), the `Parameters` schema requires `line` and `character` for all operations, including `workspaceSymbol` and `documentSymbol` which never use coordinates. This matches the upstream `dev:packages/opencode/src/tool/lsp.ts:23-32` baseline exactly — both fields are declared as required `Schema.Number` with `>= 1` checks. The fix (per-operation schema split, or making `line`/`character` optional with handler-side presence validation for operations that need them like `goToDefinition`/`findReferences`) is deferred to a follow-up PR or upstream report to avoid mixing refactor + bug-fix intents and drifting the diff from the upstream baseline. Do NOT re-flag the required coordinates on `workspaceSymbol`/`documentSymbol` in upstream-sync PRs; flag it only in a PawWork-authored PR that directly touches `lsp.ts` parameter validation.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T06:51:54.812Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T04:56:13.350Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/tool.ts:8-10
Timestamp: 2026-04-28T04:56:13.350Z
Learning: In `packages/opencode/src/tool/tool.ts` (Astro-Han/pawwork, PR `#270`), the `Metadata` interface uses `[key: string]: any` as its index signature. This is upstream-inherited code adopted wholesale via the graft strategy (per `project_upstream_strategy.md`). The fix — tightening to `Record<string, unknown>` and explicitly narrowing framework-owned fields like `truncated` — is intentionally deferred to a follow-up PR or upstream report. Do NOT re-flag the `any` index signature on `Metadata` in PR `#270` or in future upstream-sync PRs that carry the same upstream baseline.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T17:02:39.474Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/ui/src/theme/themes/pawwork.json:32-33
Timestamp: 2026-04-23T17:02:39.474Z
Learning: In Astro-Han/pawwork, `#FF5910` is the locked PawWork brand orange used for `button-primary-base` (and `--accent-brand` in `index.css`, theme token `interactive`/`primary`). The `text-on-interactive-base: `#FFFFFF`` on `button-primary-base: `#FF5910`` combination produces a contrast ratio of ~3.14:1. This intentionally fails WCAG AA normal-text (4.5:1) but passes AA non-text (SC 1.4.11, 3:1) and AA large-text (SC 1.4.3, 3:1). The tradeoff is accepted for brand consistency; do NOT flag this contrast pair as a WCAG violation in future reviews. If a specific surface requires stricter contrast, it should be addressed individually (e.g., larger text, ghost variant, outline button) rather than changing the palette.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
🔇 Additional comments (24)
packages/ui/src/i18n/zh.ts (1)

58-58: Good status copy update.

"探索完毕" reads naturally as the completed state paired with "正在探索".

packages/ui/src/i18n/en.ts (1)

76-77: Nice phrasing improvement for read summary.

Read {{count}} file/files is clearer and more natural than the previous count-leading form.

packages/ui/src/components/message-part.tsx (1)

922-922: Context tool group typography change is safe and consistent.

Line 922 is a presentation-only downshift to text-13-medium; behavior and rendering logic remain intact.

packages/app/src/components/dialog-manage-models.tsx (1)

37-37: Manage-models action button update looks good.

Line 37 is a clean visual-only class adjustment to text-13-medium with no behavioral risk.

packages/app/src/pages/session/message-timeline.tsx (1)

641-641: Session timeline typography pass is consistent and non-invasive.

These line edits are strictly presentational (text-13-*) and do not affect timeline interaction or mutation flows.

Also applies to: 759-759, 766-766, 795-795, 809-809

packages/app/src/components/session/session-sortable-tab.tsx (1)

25-25: File tab label sizing update is good.

Line 25 is a straightforward typography adjustment with no behavioral impact.

packages/app/src/components/prompt-input/send-button.tsx (1)

23-23: Send button size reduction is correctly scoped.

Line 23 tightens geometry only; submit behavior, disabled state, and accessibility attributes remain unchanged.

packages/app/src/components/dialog-select-model.tsx (1)

246-246: Dialog select-model action typography update is solid.

Line 246 applies the expected text-size downshift without affecting dialog behavior.

packages/app/src/pages/session/files-tab.tsx (1)

115-115: Files tab typography adjustments are clean and low-risk.

The updated text classes are strictly visual and keep existing file-state/render behavior intact.

Also applies to: 133-133

packages/app/src/pages/layout/sidebar-project.tsx (1)

201-201: Sidebar project preview alignment updates are well implemented.

The data-leading-slot addition at Line 229 correctly integrates with shared sidebar leading-slot icon sizing, and the Line 201/232 typography changes stay consistent with the new 13px baseline.

Also applies to: 229-229, 232-232

packages/app/src/components/settings-general.tsx (1)

383-383: Typography downshift is consistent and safe.

These class updates are cohesive with the 13px baseline and do not alter runtime behavior.

Also applies to: 492-492, 536-536, 589-589, 655-655, 688-688, 728-728

packages/app/src/components/settings-keybinds.tsx (1)

411-411: Keybinds typography updates look good.

The 13px class migration is applied consistently in headers, row labels, and empty state text.

Also applies to: 416-416, 446-446, 448-448

packages/app/src/components/dialog-select-model-unpaid.tsx (1)

46-46: Dialog typography refactor is clean.

The updated text-13-* usage is consistent across title, notes, and CTA label without affecting flow.

Also applies to: 90-90, 112-112, 119-120, 126-126, 133-133

packages/app/src/pages/session.tsx (1)

1315-1315: Session review empty-state typography updates are solid.

These changes keep behavior intact while aligning with the 13px hierarchy target.

Also applies to: 1322-1322, 1329-1330, 1361-1361

packages/ui/src/components/message-part.css (1)

440-440: Token-based typography migration looks correct.

These updates consistently move affected text to shared font-size tokens.

Also applies to: 761-761, 778-778, 858-858, 944-944, 1070-1070, 1078-1078, 1106-1106

packages/app/src/components/settings-providers.tsx (1)

140-140: Providers settings typography updates are coherent.

The 13px substitutions are consistently applied across headings, labels, and helper text.

Also applies to: 145-145, 159-159, 165-165, 182-182, 194-194, 227-227, 249-249

packages/app/src/components/settings-models.tsx (1)

19-19: Settings models typography changes are safe and aligned.

The updated classes maintain behavior while matching the new type scale.

Also applies to: 27-27, 29-29, 103-103, 112-112

packages/app/src/pages/home.tsx (1)

77-77: Home page typography adjustments look good.

The updated text scale is consistent and does not impact behavior.

Also applies to: 92-92, 107-107, 128-128

packages/ui/src/styles/theme.css (1)

8-11: Token update looks consistent.

The new 13px base/hierarchy tokens and 12px x-small token line up with the PR-wide typography downshift.

packages/ui/src/components/basic-tool.css (1)

69-238: Typography now follows the shared base token.

Using --font-size-base here keeps the tool and task card text aligned with the 13px theme token.

packages/app/src/components/prompt-input/workspace-chip.tsx (1)

66-131: Workspace chip density matches the new composer scale.

The 28px trigger, 13px text, and add-workspace icon update all fit the tighter layout, and the shell-mode gating remains intact.

packages/app/src/components/prompt-input.tsx (1)

1124-1571: Prompt input density changes look coherent.

The 28px controls and 13px text classes are consistent with the rest of the composer refactor, and the existing shell-mode/send-button behavior stays unchanged.

packages/app/src/pages/layout/sidebar-workspace.tsx (1)

103-358: Workspace sidebar typography and spacing stay consistent.

The 13px text updates and tighter load-more row match the shared sidebar scale.

packages/app/src/pages/layout.tsx (1)

2172-2428: Layout callsite updates line up with the sidebar API change.

The reduced typography and PawworkSidebar prop cleanup match the refactor.

…ttern

en: "Listed N dir / dirs" was implementation jargon; use the full
word "directory / directories" so the UI reads cleanly for
non-technical users (PawWork's "for everyone" positioning).

zh: collapse the prior verb-only labels (阅读 N 篇 / 浏览 N 处) to
verb-object form that matches PR #282's tool-card labels (读取文件
/ 列出目录) and stays visually symmetric across all three counts:

- 读取 N 个文件 (was 阅读 N 篇)
- 搜索 N 处匹配 (was 搜索 N 次, now matches verb+量+名 shape)
- 列出 N 个目录 (was 浏览 N 处)

"处" stays as the search measure word because grep-style search
returns hits at file:line locations, which is exactly what 处
denotes (an occurrence at a position) — more concrete than 项.

Addresses coderabbitai review on #320.
# Conflicts:
#	packages/app/e2e/app/home.spec.ts
The previous class="size-5 rounded-md" did not actually shrink
IconButton: the [data-component="icon-button"][data-size="normal"]
selector in icon-button.css beats Tailwind's .size-5, so the button
was still laid out at 24x24 and only visually clipped to 20x20 by
the parent's w-5 overflow-hidden. This cropped the hover/focus
affordance and gave the action column an inconsistent hit target.

Pass size="small" (the IconButton API for 20x20) on both the
SessionRow archive button and the pawwork-sidebar dot-grid trigger
so the layout actually matches the slot.
The session-row pin button rendered through SessionRow's leadingSlot
was size-5 (20px) but the slot container is 14x14, so the button
overflowed the slot and reintroduced horizontal drift in the
tightened sidebar.

Match the slot dimensions: w-[14px] h-[14px]. The Icon inside is
already auto-shrunk to 14x14 by the [data-leading-slot] CSS rule,
so the button container, content, and slot now all line up at the
same 14x14.

Addresses coderabbitai outside-diff comment on #320.
@Astro-Han Astro-Han force-pushed the claude/sidebar-typography branch from fb22931 to d61be8e Compare April 29, 2026 04:30
@Astro-Han

Copy link
Copy Markdown
Owner Author

@coderabbitai outside-diff finding addressed in d61be8e. The session-row pin button (pawwork-sidebar.tsx) is now w-[14px] h-[14px], matching the 14×14 leading slot in sidebar-items.tsx. The Icon inside is already auto-shrunk to 14×14 by the [data-leading-slot] CSS rule, so the button container, content, and slot all line up at the same dimensions and the overflow drift is gone.

@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Astro-Han Thanks for the fix! The w-[14px] h-[14px] constraint on the pin button container closes the loop neatly — the button wrapper, the [data-leading-slot] CSS shrink target, and the slot frame all agree on 14×14 now.


✏️ Learnings added
Learnt from: Astro-Han
URL: https://github.com/Astro-Han/pawwork/pull/320

Timestamp: 2026-04-29T04:31:25.068Z
Learning: In `packages/app/src/pages/layout/pawwork-sidebar.tsx` (Astro-Han/pawwork), the session-row pin button container must be sized `w-[14px] h-[14px]` to match the 14×14 leading slot declared in `sidebar-items.tsx`. The Icon glyph inside is auto-shrunk to 14×14 by the `[data-leading-slot]` CSS rule in `sidebar.css`, so all three dimensions (button wrapper, icon content, slot frame) align at 14×14. Do NOT suggest widening the pin button container beyond 14×14 in this context.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 320
File: packages/app/src/pages/layout/sidebar.css:1-6
Timestamp: 2026-04-29T04:23:50.832Z
Learning: In `packages/app/src/pages/layout/sidebar-project.tsx` (Astro-Han/pawwork), the branch-icon container at line 229 uses `size-5` (20px) with `justify-center items-center` and `data-leading-slot` by design. This is intentional: (1) the 3px padding is symmetric, not asymmetric; (2) the branch row lives inside the project-preview header card — a different visual context from the X=8 left rail (SessionRow/NewSessionItem/New session/Search/Settings). The `data-leading-slot` + 20×20 container combination is the deliberate mechanism to render a 14×14 icon glyph (via sidebar.css) inside a larger touch-target without affecting global Icon size="small". Do NOT flag this size-5 container as misaligned or asymmetric in future reviews.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/pages/layout/pawwork-sidebar.tsx:139-139
Timestamp: 2026-04-23T15:10:26.994Z
Learning: In Astro-Han/pawwork, the icon registry in `packages/ui/src/components/icon.tsx` uses **bare (unquoted) TypeScript object keys** for single-word icon names (e.g., `pin:` at line 102, `workspace:` at line 99) and **quoted keys** only for hyphenated names (e.g., `"review-active":`). When verifying icon registration with `rg`, always search for both forms — use a pattern like `rg -n "(\"${key}\"|${key}:)"` — otherwise bare-key icons will be missed and incorrectly flagged as unregistered.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/ui/src/theme/themes/pawwork.json:32-33
Timestamp: 2026-04-23T17:02:39.474Z
Learning: In Astro-Han/pawwork, `#FF5910` is the locked PawWork brand orange used for `button-primary-base` (and `--accent-brand` in `index.css`, theme token `interactive`/`primary`). The `text-on-interactive-base: `#FFFFFF`` on `button-primary-base: `#FF5910`` combination produces a contrast ratio of ~3.14:1. This intentionally fails WCAG AA normal-text (4.5:1) but passes AA non-text (SC 1.4.11, 3:1) and AA large-text (SC 1.4.3, 3:1). The tradeoff is accepted for brand consistency; do NOT flag this contrast pair as a WCAG violation in future reviews. If a specific surface requires stricter contrast, it should be addressed individually (e.g., larger text, ghost variant, outline button) rather than changing the palette.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/lsp.ts:23-32
Timestamp: 2026-04-28T07:28:14.317Z
Learning: In `packages/opencode/src/tool/lsp.ts` (Astro-Han/pawwork, PR `#270`), the `Parameters` schema requires `line` and `character` for all operations, including `workspaceSymbol` and `documentSymbol` which never use coordinates. This matches the upstream `dev:packages/opencode/src/tool/lsp.ts:23-32` baseline exactly — both fields are declared as required `Schema.Number` with `>= 1` checks. The fix (per-operation schema split, or making `line`/`character` optional with handler-side presence validation for operations that need them like `goToDefinition`/`findReferences`) is deferred to a follow-up PR or upstream report to avoid mixing refactor + bug-fix intents and drifting the diff from the upstream baseline. Do NOT re-flag the required coordinates on `workspaceSymbol`/`documentSymbol` in upstream-sync PRs; flag it only in a PawWork-authored PR that directly touches `lsp.ts` parameter validation.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:39.493Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/components/markdown.css (1)

6-10: ⚠️ Potential issue | 🟡 Minor

Minor: Update the stale “14px” comment on the markdown base font-size.

Line 8 uses font-size: var(--font-size-base); /* 14px */, but the theme snippet indicates --font-size-base is 13px. This comment will mislead future maintainers and can cause follow-up review churn.

Proposed fix
[data-component="markdown"] {
-  font-size: var(--font-size-base); /* 14px */
+  font-size: var(--font-size-base); /* 13px */
   line-height: 160%;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/markdown.css` around lines 6 - 10, Update the
stale inline comment that says "14px" next to the CSS rule "font-size:
var(--font-size-base);" in the markdown base styles; change the comment to
reflect the actual theme value "13px" so it reads e.g. "/* 13px */" (locate the
rule in packages/ui/src/components/markdown.css where font-size:
var(--font-size-base) is defined).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ui/src/i18n/en.ts`:
- Around line 78-79: The locale keys "ui.messagePart.context.search.one" and
"ui.messagePart.context.search.other" use a count-first format; change their
values to a verb-led format to match the surrounding keys (e.g., use "Search
{{count}}" for both plural forms) so that when rendered with other keys like
"ui.messagePart.context.read" and "ui.messagePart.context.list" the phrasing and
ordering are consistent; update the strings for the two keys in
packages/ui/src/i18n/en.ts accordingly.

---

Outside diff comments:
In `@packages/ui/src/components/markdown.css`:
- Around line 6-10: Update the stale inline comment that says "14px" next to the
CSS rule "font-size: var(--font-size-base);" in the markdown base styles; change
the comment to reflect the actual theme value "13px" so it reads e.g. "/* 13px
*/" (locate the rule in packages/ui/src/components/markdown.css where font-size:
var(--font-size-base) is defined).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 219df0b9-4784-454b-a257-d6bf41eca5c1

📥 Commits

Reviewing files that changed from the base of the PR and between d49ea27 and d61be8e.

📒 Files selected for processing (7)
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-part.css
  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
💤 Files with no reviewable changes (1)
  • packages/app/e2e/app/home.spec.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-opencode
  • GitHub Check: unit-app
  • GitHub Check: unit-desktop
  • GitHub Check: typecheck
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: analyze-js-ts
  • GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (1)
packages/app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/app/AGENTS.md)

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
🧠 Learnings (30)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 320
File: packages/app/src/pages/layout/sidebar.css:1-6
Timestamp: 2026-04-29T04:23:50.832Z
Learning: In `packages/app/src/pages/layout/sidebar-project.tsx` (Astro-Han/pawwork), the branch-icon container at line 229 uses `size-5` (20px) with `justify-center items-center` and `data-leading-slot` by design. This is intentional: (1) the 3px padding is symmetric, not asymmetric; (2) the branch row lives inside the project-preview header card — a different visual context from the X=8 left rail (SessionRow/NewSessionItem/New session/Search/Settings). The `data-leading-slot` + 20×20 container combination is the deliberate mechanism to render a 14×14 icon glyph (via sidebar.css) inside a larger touch-target without affecting global Icon size="small". Do NOT flag this size-5 container as misaligned or asymmetric in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 0
File: :0-0
Timestamp: 2026-04-29T04:31:25.068Z
Learning: In `packages/app/src/pages/layout/pawwork-sidebar.tsx` (Astro-Han/pawwork), the session-row pin button container must be sized `w-[14px] h-[14px]` to match the 14×14 leading slot declared in `sidebar-items.tsx`. The Icon glyph inside is auto-shrunk to 14×14 by the `[data-leading-slot]` CSS rule in `sidebar.css`, so all three dimensions (button wrapper, icon content, slot frame) align at 14×14. Do NOT suggest widening the pin button container beyond 14×14 in this context.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
📚 Learning: 2026-04-29T04:23:45.886Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 320
File: packages/ui/src/i18n/zh.ts:0-0
Timestamp: 2026-04-29T04:23:45.886Z
Learning: In Astro-Han/pawwork `packages/ui/src/i18n/zh.ts`, the canonical zh translations for message-part context counters (commit fa6771a35, PR `#320`) are:
- `ui.messagePart.context.read.one/.other` → `读取 {{count}} 个文件`
- `ui.messagePart.context.search.one/.other` → `搜索 {{count}} 处匹配` (`处` is the correct measure word for grep-style file:line hits, chosen for verb+count+量词+名词 symmetry with the other two labels)
- `ui.messagePart.context.list.one/.other` → `列出 {{count}} 个目录`
Do NOT suggest `搜索 {{count}} 次` or `浏览 {{count}} 处` for these keys in future reviews.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-28T04:56:18.533Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/test/file/ripgrep.test.ts:172-175
Timestamp: 2026-04-28T04:56:18.533Z
Learning: In `packages/opencode/test/file/ripgrep.test.ts` (Astro-Han/pawwork, PR `#270`), the `files dies on nonexistent directory` test hardcodes `/tmp/nonexistent-dir-12345` as the missing-directory path. This is upstream-inherited behaviour adopted wholesale via the graft strategy (`project_upstream_strategy.md`). The fix — replacing the hardcoded path with a platform-neutral guaranteed-nonexistent child path derived from the `tmpdir` fixture — is intentionally deferred to a follow-up PR or upstream report, to avoid mixing bugfix + refactor intents and drifting the diff from the upstream baseline future syncs need. Do NOT re-flag the hardcoded `/tmp/nonexistent-dir-12345` path in this file as a blocking or actionable issue until the follow-up PR lands.

Applied to files:

  • packages/ui/src/i18n/en.ts
📚 Learning: 2026-04-28T08:14:31.436Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:48-48
Timestamp: 2026-04-28T08:14:31.436Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork), the `filePath` schema description (`"The absolute path to the file to modify"`) is upstream-inherited from `dev:packages/opencode/src/tool/edit.ts:48`. The runtime actually accepts relative paths (resolved via `Instance.directory` at lines 79-81), but the description fix is intentionally deferred to a single PawWork-authored description-cleanup PR that will also cover the identical mismatch in `packages/opencode/src/tool/write.ts:24`. Do NOT re-flag the too-narrow `filePath` description in upstream-sync PRs; flag it only in the dedicated description-cleanup PR.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/components/markdown.css
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T08:29:02.858Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/file/ripgrep.ts:469-470
Timestamp: 2026-04-28T08:29:02.858Z
Learning: In `packages/opencode/src/file/ripgrep.ts` (Astro-Han/pawwork, PR `#270`), the `tree()` function filters files with `if (file.includes(".opencode")) continue`, which is a raw substring match that would also drop unrelated paths whose names merely contain `.opencode` (e.g., `.opencode-backup`). The correct fix is `if (file.split(path.sep).includes(".opencode")) continue` to match only the exact path segment. However, upstream `dev:packages/opencode/src/file/ripgrep.ts` carries the identical false-positive filter. PR `#270` is an upstream-sync graft; fixing it here would diverge from the baseline. The fix is deferred to a PawWork ripgrep filter cleanup PR or an upstream-first fix, to land alongside the other ripgrep deferrals (Schema.Class, bytes-union, abort short-circuit, mixed-separator). Do NOT re-flag the `.opencode` substring filter in upstream-sync PRs.

Applied to files:

  • packages/ui/src/i18n/en.ts
📚 Learning: 2026-04-28T03:01:37.478Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 282
File: packages/ui/src/i18n/zh.ts:109-109
Timestamp: 2026-04-28T03:01:37.478Z
Learning: In Astro-Han/pawwork PR `#282` (`packages/ui/src/i18n/zh.ts`), the translation for `ui.tool.questions` is intentionally `提出问题` (verb-object structure), NOT `向你提问` (prepositional-phrase structure). The choice was made after a UI smoke run to keep consistent verb-object phrasing across all zh tool-card labels (`执行命令`, `列出目录`, `查找文件`, `搜索文本`, `读取文件`, `读取网页`, `批量修改`, `查看待办`). Do NOT flag `提出问题` as incorrect for this key.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-28T07:28:14.317Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/lsp.ts:23-32
Timestamp: 2026-04-28T07:28:14.317Z
Learning: In `packages/opencode/src/tool/lsp.ts` (Astro-Han/pawwork, PR `#270`), the `Parameters` schema requires `line` and `character` for all operations, including `workspaceSymbol` and `documentSymbol` which never use coordinates. This matches the upstream `dev:packages/opencode/src/tool/lsp.ts:23-32` baseline exactly — both fields are declared as required `Schema.Number` with `>= 1` checks. The fix (per-operation schema split, or making `line`/`character` optional with handler-side presence validation for operations that need them like `goToDefinition`/`findReferences`) is deferred to a follow-up PR or upstream report to avoid mixing refactor + bug-fix intents and drifting the diff from the upstream baseline. Do NOT re-flag the required coordinates on `workspaceSymbol`/`documentSymbol` in upstream-sync PRs; flag it only in a PawWork-authored PR that directly touches `lsp.ts` parameter validation.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-part.css
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T06:51:54.812Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-part.css
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-24T17:08:46.780Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-part.css
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-28T07:27:49.810Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/file/ripgrep.ts:45-72
Timestamp: 2026-04-28T07:27:49.810Z
Learning: In `packages/opencode/src/file/ripgrep.ts` (Astro-Han/pawwork), `PathText`, the `lines` field inside `SearchMatch`, and `submatches[].match` accept only `{text: string}` and do NOT handle ripgrep's `{bytes: base64}` JSON variant (emitted for non-UTF-8 paths/match text). This matches the upstream `dev:packages/opencode/src/file/ripgrep.ts` baseline exactly. The fix — adding `Schema.Union([Schema.Struct({text: Schema.String}), Schema.Struct({bytes: Schema.String})])` for each location plus base64-decode in downstream consumers — is intentionally deferred to either a dedicated PawWork PR or an upstream-first fix inherited via the next sync. Do NOT re-flag the missing `{bytes: ...}` union variants in upstream-sync PRs; flag it only in a PawWork-authored PR that directly touches these schema definitions or their downstream consumers.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-28T04:38:11.727Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:35-45
Timestamp: 2026-04-28T04:38:11.727Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork, PR `#270`), the module-scoped `Map<string, Semaphore.Semaphore>` (`locks`) and `lock(filePath)` helper are intentionally left as a global, never-cleaned registry. PR `#270` is a pure upstream-sync graft and mixing in a bugfix would drift the diff from the upstream baseline. The fix (moving per-file semaphores into `InstanceState`/`ScopedCache` for per-instance lifecycle management) is tracked as a follow-up to land in its own PR or be reported upstream. Do NOT re-flag the absence of InstanceState wiring for the lock map in this file until the follow-up PR is opened.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/components/markdown.css
📚 Learning: 2026-04-28T04:38:05.946Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-part.css
📚 Learning: 2026-04-28T04:38:11.771Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/agent.ts:23-27
Timestamp: 2026-04-28T04:38:11.771Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/tool/agent.ts`), the `subagent_session_id` field in the `Parameters` schema accepts any `Schema.String` rather than a branded `SessionID`. This is inherited upstream behavior (adopted in PR `#270`, an upstream-sync graft of upstream PR `#23244`). The fix — validating `subagent_session_id` as a `SessionID` brand up front so malformed/typo'd IDs fail explicitly rather than silently forking a new subagent session — is intentionally deferred to a follow-up PR or upstream report. Do NOT re-flag this as a blocking issue in PR `#270` or in future upstream-sync PRs that carry the same schema; flag it only in a PawWork-authored PR that touches `agent.ts` parameter validation.

Applied to files:

  • packages/ui/src/i18n/en.ts
  • packages/ui/src/components/message-part.css
📚 Learning: 2026-04-28T06:47:20.342Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/question.ts:6-16
Timestamp: 2026-04-28T06:47:20.342Z
Learning: In Astro-Han/pawwork, `packages/opencode/src/tool/question.ts` is a permanent PawWork carve-out: `Question.Prompt` (defined in `packages/opencode/src/question/index.ts`) remains a Zod schema, NOT an Effect Schema. The `ts-expect-error` boundary in `tool/registry.ts` marks the Zod ↔ Effect Schema seam. The dropped `packages/opencode/test/tool/question.test.ts` covered the old Zod-mixed surface. Schema-bound regression tests for this tool are intentionally deferred until `question/index.ts` migrates to Effect Schema in its own dedicated PR. Do NOT request restoring question tool tests in upstream-sync PRs; flag it only in the PR that migrates `question/index.ts` to Effect Schema.

Applied to files:

  • packages/ui/src/i18n/en.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-29T04:23:50.832Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 320
File: packages/app/src/pages/layout/sidebar.css:1-6
Timestamp: 2026-04-29T04:23:50.832Z
Learning: In `packages/app/src/pages/layout/sidebar-project.tsx` (Astro-Han/pawwork), the branch-icon container at line 229 uses `size-5` (20px) with `justify-center items-center` and `data-leading-slot` by design. This is intentional: (1) the 3px padding is symmetric, not asymmetric; (2) the branch row lives inside the project-preview header card — a different visual context from the X=8 left rail (SessionRow/NewSessionItem/New session/Search/Settings). The `data-leading-slot` + 20×20 container combination is the deliberate mechanism to render a 14×14 icon glyph (via sidebar.css) inside a larger touch-target without affecting global Icon size="small". Do NOT flag this size-5 container as misaligned or asymmetric in future reviews.

Applied to files:

  • packages/ui/src/components/markdown.css
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/ui/src/components/message-part.css
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-22T09:32:58.310Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.

Applied to files:

  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-part.css
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.

Applied to files:

  • packages/ui/src/components/markdown.css
  • packages/ui/src/components/message-part.css
📚 Learning: 2026-04-24T05:39:58.329Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.

Applied to files:

  • packages/ui/src/components/markdown.css
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/ui/src/components/message-part.css
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.

Applied to files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-29T04:31:25.068Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 0
File: :0-0
Timestamp: 2026-04-29T04:31:25.068Z
Learning: In `packages/app/src/pages/layout/pawwork-sidebar.tsx` (Astro-Han/pawwork), the session-row pin button container must be sized `w-[14px] h-[14px]` to match the 14×14 leading slot declared in `sidebar-items.tsx`. The Icon glyph inside is auto-shrunk to 14×14 by the `[data-leading-slot]` CSS rule in `sidebar.css`, so all three dimensions (button wrapper, icon content, slot frame) align at 14×14. Do NOT suggest widening the pin button container beyond 14×14 in this context.

Applied to files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-24T05:48:39.493Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:39.493Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.

Applied to files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/ui/src/components/message-part.css
📚 Learning: 2026-04-28T04:56:13.350Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/tool.ts:8-10
Timestamp: 2026-04-28T04:56:13.350Z
Learning: In `packages/opencode/src/tool/tool.ts` (Astro-Han/pawwork, PR `#270`), the `Metadata` interface uses `[key: string]: any` as its index signature. This is upstream-inherited code adopted wholesale via the graft strategy (per `project_upstream_strategy.md`). The fix — tightening to `Record<string, unknown>` and explicitly narrowing framework-owned fields like `truncated` — is intentionally deferred to a follow-up PR or upstream report. Do NOT re-flag the `any` index signature on `Metadata` in PR `#270` or in future upstream-sync PRs that carry the same upstream baseline.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `data-component`, `data-action`, or semantic roles for selectors instead of CSS class names or IDs

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-23T17:02:39.474Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/ui/src/theme/themes/pawwork.json:32-33
Timestamp: 2026-04-23T17:02:39.474Z
Learning: In Astro-Han/pawwork, `#FF5910` is the locked PawWork brand orange used for `button-primary-base` (and `--accent-brand` in `index.css`, theme token `interactive`/`primary`). The `text-on-interactive-base: `#FFFFFF`` on `button-primary-base: `#FF5910`` combination produces a contrast ratio of ~3.14:1. This intentionally fails WCAG AA normal-text (4.5:1) but passes AA non-text (SC 1.4.11, 3:1) and AA large-text (SC 1.4.3, 3:1). The tradeoff is accepted for brand consistency; do NOT flag this contrast pair as a WCAG violation in future reviews. If a specific surface requires stricter contrast, it should be addressed individually (e.g., larger text, ghost variant, outline button) rather than changing the palette.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/app/src/pages/layout/pawwork-sidebar.tsx
🔇 Additional comments (23)
packages/ui/src/components/message-part.css (9)

433-446: LGTM: message-part title font-size now uses the shared base token.

Switching [data-slot="message-part-title"] to font-size: var(--font-size-base) keeps it aligned to the 13px baseline while preserving the existing line-height/weight semantics.


759-767: LGTM: permission header title uses hierarchy sizing token.

[data-slot="permission-header-title"] moving to var(--font-size-hierarchy) is consistent with the PR’s 13px hierarchy intent and matches the theme variable definition.


776-783: LGTM: permission hint uses shared base token.

Updating [data-slot="permission-hint"] to font-size: var(--font-size-base) ensures consistent 13px body sizing across the component.


802-806: LGTM: permission-patterns code now uses the shared x-small token (12px).

[data-slot="permission-patterns"] code now uses var(--font-size-x-small), which matches the design-system 12px intent for code-in-permission-patterns text.


856-869: LGTM: question header title uses hierarchy sizing token.

[data-slot="question-header-title"] using var(--font-size-hierarchy) is consistent with the updated typography scale and the theme token values.


942-950: LGTM: question text uses shared base token.

[data-slot="question-text"] now uses font-size: var(--font-size-base) (13px), aligning with the unified body sizing direction.


1068-1074: LGTM: option label font-size now uses shared base token.

[data-slot="option-label"] updated to var(--font-size-base) keeps option labels on the unified 13px baseline.


1076-1085: LGTM: option description font-size now uses shared base token.

[data-slot="option-description"] updated to var(--font-size-base) maintains consistent typography between label/description and matches the theme variables.


1097-1110: LGTM: custom input text font-size now uses shared base token.

[data-slot="question-custom-input"] using var(--font-size-base) keeps the input text aligned with the surrounding question typography.

packages/ui/src/components/markdown.css (3)

19-32: LGTM: Headings now consistently use the hierarchy token.

Switching h1h6 to font-size: var(--font-size-hierarchy) matches the intended typography baseline unification and avoids reintroducing hard-coded sizes.


123-128: LGTM: .shiki uses the x-small token for 12px code blocks.

Using font-size: var(--font-size-x-small) on .shiki aligns fenced code styling with the PR’s 12px code size target and with the theme token source.


214-220: LGTM: Inline code explicitly sets the x-small font-size.

The added font-size: var(--font-size-x-small) on :not(pre) > code removes ambiguity about whether inline code inherited a larger base size previously.

packages/app/src/pages/layout/sidebar-items.tsx (3)

103-123: Leading-slot normalization looks correct.

The 14px spinner plus data-leading-slot + w-[14px] h-[14px] container keeps status/icon rendering aligned with the tightened sidebar hierarchy.


236-253: Right action rail sizing is now consistent.

Using w-5 for the action column and IconButton size="small" for archive resolves the previous 20px/24px mismatch and keeps the action lane stable.


297-303: New session row is aligned with SessionRow density/geometry.

The py-1/py-1.5 density and 14px leading slot treatment match the session row primitives cleanly.

packages/ui/src/i18n/zh.ts (2)

58-58: Status wording update is clear and natural.

探索完毕 reads as a proper completion state and pairs well with the in-progress 正在探索.


70-75: Context counter phrasing is consistent and specific.

The read/search/list strings now use explicit objects and consistent grammatical structure across .one/.other.

packages/app/src/pages/layout/pawwork-sidebar.tsx (3)

130-130: Session row controls are correctly tightened to small geometry.

The 14×14 pin wrapper and IconButton size="small" menu trigger are consistent with the updated sidebar slot/action sizing.

Also applies to: 156-157


242-264: Top action rows are cleanly unified.

The New/Search rows now share a consistent row primitive (icon slot, typography, spacing, focus-visible behavior), matching the sidebar list style.


342-342: Footer simplification is coherent and accessible.

Single-row Settings with TooltipKeybind, clear labeling, and visible keyboard focus state keeps footer behavior consistent with top rows.

Also applies to: 346-361

packages/ui/src/i18n/en.ts (3)

76-77: LGTM: context.read pluralization/casing looks consistent.

“Read {{count}} file/files” preserves {{count}} interpolation and correctly uses singular/plural under one/other. No changes needed based on the provided snippet.


80-81: LGTM: context.list copy format is correct.

“Listed {{count}} directory/directories” preserves interpolation and matches one/other pluralization. No changes needed.


1-167: Overall: i18n dictionary structure is sound.

export const dict: Record<string, string> and the provided entries are syntactically correct and consistent with typical i18n-map patterns.

Comment thread packages/ui/src/i18n/en.ts Outdated
Two small follow-ups to coderabbit's review on #320.

- packages/ui/src/i18n/en.ts: switch the search count from the
  count-led "{{count}} search / {{count}} searches" to past-tense
  verb-led "Searched {{count}} time / Searched {{count}} times"
  so it lines up with the existing "Read N file" / "Listed N
  directory" siblings and mirrors the zh "搜索 N 次" register.
- packages/ui/src/components/markdown.css: update the inline
  "/* 14px */" comment next to font-size: var(--font-size-base)
  to "/* 13px */", which is the actual value after the hierarchy
  collapse landed in this PR.
@Astro-Han

Copy link
Copy Markdown
Owner Author

@coderabbitai outside-diff finding fixed in 0202473. Updated the inline comment in packages/ui/src/components/markdown.css:8 from /* 14px */ to /* 13px */ so it reflects the actual value of --font-size-base after the hierarchy collapse landed in this PR.

@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Astro-Han Thanks for the update! The /* 13px */ annotation in markdown.css:8 now correctly mirrors the collapsed --font-size-base value — no more stale 14px ghost in the comment.


🧠 Learnings used
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 320
File: packages/app/src/pages/layout/sidebar.css:1-6
Timestamp: 2026-04-29T04:23:50.832Z
Learning: In `packages/app/src/pages/layout/sidebar-project.tsx` (Astro-Han/pawwork), the branch-icon container at line 229 uses `size-5` (20px) with `justify-center items-center` and `data-leading-slot` by design. This is intentional: (1) the 3px padding is symmetric, not asymmetric; (2) the branch row lives inside the project-preview header card — a different visual context from the X=8 left rail (SessionRow/NewSessionItem/New session/Search/Settings). The `data-leading-slot` + 20×20 container combination is the deliberate mechanism to render a 14×14 icon glyph (via sidebar.css) inside a larger touch-target without affecting global Icon size="small". Do NOT flag this size-5 container as misaligned or asymmetric in future reviews.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 0
File: :0-0
Timestamp: 2026-04-29T04:31:25.068Z
Learning: In `packages/app/src/pages/layout/pawwork-sidebar.tsx` (Astro-Han/pawwork), the session-row pin button container must be sized `w-[14px] h-[14px]` to match the 14×14 leading slot declared in `sidebar-items.tsx`. The Icon glyph inside is auto-shrunk to 14×14 by the `[data-leading-slot]` CSS rule in `sidebar.css`, so all three dimensions (button wrapper, icon content, slot frame) align at 14×14. Do NOT suggest widening the pin button container beyond 14×14 in this context.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/lsp.ts:23-32
Timestamp: 2026-04-28T07:28:14.317Z
Learning: In `packages/opencode/src/tool/lsp.ts` (Astro-Han/pawwork, PR `#270`), the `Parameters` schema requires `line` and `character` for all operations, including `workspaceSymbol` and `documentSymbol` which never use coordinates. This matches the upstream `dev:packages/opencode/src/tool/lsp.ts:23-32` baseline exactly — both fields are declared as required `Schema.Number` with `>= 1` checks. The fix (per-operation schema split, or making `line`/`character` optional with handler-side presence validation for operations that need them like `goToDefinition`/`findReferences`) is deferred to a follow-up PR or upstream report to avoid mixing refactor + bug-fix intents and drifting the diff from the upstream baseline. Do NOT re-flag the required coordinates on `workspaceSymbol`/`documentSymbol` in upstream-sync PRs; flag it only in a PawWork-authored PR that directly touches `lsp.ts` parameter validation.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.

Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/ui/src/theme/themes/pawwork.json:32-33
Timestamp: 2026-04-23T17:02:39.474Z
Learning: In Astro-Han/pawwork, `#FF5910` is the locked PawWork brand orange used for `button-primary-base` (and `--accent-brand` in `index.css`, theme token `interactive`/`primary`). The `text-on-interactive-base: `#FFFFFF`` on `button-primary-base: `#FF5910`` combination produces a contrast ratio of ~3.14:1. This intentionally fails WCAG AA normal-text (4.5:1) but passes AA non-text (SC 1.4.11, 3:1) and AA large-text (SC 1.4.3, 3:1). The tradeoff is accepted for brand consistency; do NOT flag this contrast pair as a WCAG violation in future reviews. If a specific surface requires stricter contrast, it should be addressed individually (e.g., larger text, ghost variant, outline button) rather than changing the palette.

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.

[Feature] Sidebar icon scale and alignment polish

1 participant