Skip to content

refactor(app): reduce sidebar to two states (docked / hidden)#346

Merged
Astro-Han merged 11 commits into
devfrom
worktree-claude+sidebar-two-states
Apr 30, 2026
Merged

refactor(app): reduce sidebar to two states (docked / hidden)#346
Astro-Han merged 11 commits into
devfrom
worktree-claude+sidebar-two-states

Conversation

@Astro-Han

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

Copy link
Copy Markdown
Owner

Summary

Closes #343. Collapses the sidebar from three render modes (docked / rail+hover / mobile drawer) to two: docked or hidden. Removes the rail+hover state machine, the mobile drawer overlay, and the xl: viewport gating that switched between them. Net −837 lines.

Bundled with two adjacent polish items the audit surfaced: composer chip row narrow-mode behavior + a redundant empty-sidebar paragraph.

Why

Three-question audit on issue #343 concluded:

  • Mode B (rail+hover) borrows VSCode's activity-bar pattern and serves no PawWork user (non-technical knowledge workers either dock the sidebar or hide it via mod+\, they don't learn to hover-reveal a rail).
  • Mode C (mobile drawer) is unreachable in practice. PawWork ships only as Electron desktop; the sole way to trigger it was dragging the window narrower than 1280px. Raising minWidth to 768 is the honest fix.

Removing both leaves a single mental model that matches ChatGPT / Claude.ai / Cursor desktop.

Related Issue

Closes #343.

Human Review Status

Pending.

Review Focus

  • packages/app/src/pages/layout.tsx: largest deletion (peek, aim, arm/disarm, hoverProject, mobileSidebar overlay, xl: gates). Verify no stale references.
  • packages/app/src/context/layout.tsx: migrateStoredLayout strips persisted mobileSidebar key on read, so existing users don't error on boot.
  • packages/desktop-electron/src/main/windows.ts: minWidth 480 → 768. electron-window-state auto-clamps stored bounds, so users with smaller saved windows resize on next launch (no migration code needed).
  • Composer chip narrow-mode (packages/app/src/components/prompt-input.tsx, packages/app/src/components/prompt-input/workspace-chip.tsx): staggered container-query thresholds, model name collapses first (28rem), workspace next (24rem), variant clamps last (20rem). Smooth max-width transition replaces hard hidden.
  • packages/app/src/pages/session.tsx: middle column gets min-w-[24rem] so composer never collapses to a vertical sliver when both panels are open in narrow windows. Right side panel clips off-screen via parent overflow-hidden; user toggle in titlebar is the recovery path.

Risk Notes

  • Behavior change for Mode B users: anyone who relied on the rail+hover mode loses it. The audit concluded this audience is empty; flagging in case a tester disagrees.
  • Window minimum raised: users with stored window bounds below 768px will see the window resize up on next launch. electron-window-state clamps automatically.
  • Persisted layout migration: mobileSidebar key is stripped from stored layout on read; no boot error.

How To Verify

typecheck (packages/app):       pass
typecheck (packages/desktop):   pass
unit tests (packages/app):      617/617 pass
i18n parity + zh-branding:      3/3 pass
electron dev:desktop manual:    sidebar two-state toggle works at all widths;
                                titlebar toggle button visible at narrow widths;
                                composer chip row reflows progressively;
                                middle column does not collapse below 24rem
grep audit (rg sidebarHovering / sidebarExpanded / mobileSidebar /
            hoverProject / peek / createAim across packages/app/src):
                                zero references

Screenshots or Recordings

Manual screenshots taken during interview-driven verification iterations (chip overflow, narrow-window column squeeze, EN home greeting). Behavior matches issue #343 spec.

Checklist

  • Human review status stated as pending
  • Linked issue [Refactor] Reduce sidebar to two states: docked or hidden #343
  • Labels applied: app, desktop, enhancement, P2, ui
  • Review focus and risks called out above
  • Verification steps listed with results
  • No unrelated refactors (composer chip + empty-state copy + EN greeting are issue-adjacent polish surfaced during testing, all small)
  • Manually verified UI at multiple widths via dev:desktop
  • macOS verified; minWidth change applies to Windows and Linux desktop too (no platform-specific code)
  • Final diff reviewed; no suspicious deps
  • Targeting dev, Conventional Commits in English

Summary by CodeRabbit

  • UI & Layout Improvements

    • Responsive sizing and animated collapse for model/variant selectors and workspace labels; tighter action-bar spacing
    • Sidebar simplified: hover-to-peek and mobile sidebar toggle removed, tooltip behavior unified, no empty-sessions fallback, session panel min width enforced
    • Titlebar gradient updated; app window minimum width raised to 768px
  • Text Updates

    • “New session” subtitle and several sidebar UI strings removed/updated across locales (including Chinese)

@Astro-Han Astro-Han added enhancement New feature or request P2 Medium priority app Application behavior and product flows ui Design system and user interface platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions labels Apr 30, 2026

@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 streamlines the application layout by removing the mobile sidebar and the sidebar hover/peek features, resulting in the deletion of the aim utility and the sidebar-project component. The update simplifies layout state, navigation logic, and i18n strings while transitioning UI responsiveness to use container queries. Furthermore, the desktop application's minimum width is increased to 768px. Review feedback suggests using object destructuring for cleaner state migration and utilizing CSS variables for consistent sidebar width styling.

Comment thread packages/app/src/context/layout.tsx Outdated
Comment thread packages/app/src/pages/layout.tsx Outdated
@coderabbitai

coderabbitai Bot commented Apr 30, 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 5 minutes and 17 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: 879301c4-bf8d-4585-926d-95a8810005c8

📥 Commits

Reviewing files that changed from the base of the PR and between 0a0e6a5 and cc1a899.

📒 Files selected for processing (20)
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/context/layout.test.ts
  • packages/app/src/context/layout.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/parity.test.ts
  • packages/app/src/i18n/zh-branding.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/index.css
  • 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/shell-frame-contract.test.ts
  • packages/app/src/utils/aim.ts
  • packages/desktop-electron/src/main/windows.ts
📝 Walkthrough

Walkthrough

Removes mobile-drawer and hover/peek sidebar modes; deletes mobileSidebar, createAim, and hover/peek state; simplifies sidebar props/tooltip behavior and navigation; updates prompt/workspace chip responsive sizing; prunes i18n keys/tests; raises Electron minWidth and enforces session panel min-width.

Changes

Cohort / File(s) Summary
Layout context
packages/app/src/context/layout.tsx
Removes mobileSidebar from default state and public API; migrateStoredLayout strips legacy mobileSidebar during migration.
Top-level layout
packages/app/src/pages/layout.tsx
Deletes hover/peek state machine (hoverProject, peek, timers), mobile drawer DOM, aim logic and related navigation/hover-clear calls.
Sidebar components
packages/app/src/pages/layout/pawwork-sidebar.tsx, packages/app/src/pages/layout/sidebar-items.tsx, packages/app/src/pages/layout/sidebar-workspace.tsx
Removes mobile, sidebarExpanded, sidebarHovering, and clearHoverProjectSoon props/consumption; fixes tooltip placement to "right"; simplifies setScrollContainerRef signature; removes empty-sessions fallback.
Removed module
packages/app/src/pages/layout/sidebar-project.tsx
Entire module deleted (project sidebar context, sortable project tiles, drag overlay, hover card, and exports removed).
Utility removal
packages/app/src/utils/aim.ts
Deletes exported createAim mouse-aim/activation utility.
Titlebar
packages/app/src/components/titlebar.tsx
Removes mobile sidebar toggle button and xl-restricted wrappers; simplifies session-new tooltip visibility classes.
Prompt / workspace chips
packages/app/src/components/prompt-input.tsx, packages/app/src/components/prompt-input/workspace-chip.tsx
Adds @container/composer; replaces fixed widths with animated max-width constraints and collapse behavior; reduces action-bar gap.
CSS / titlebar
packages/app/src/index.css
Changes titlebar-shell background to unconditional linear-gradient based on --sidebar-width; removes right-panel boundary drawing.
Session panel & window constraints
packages/app/src/pages/session.tsx, packages/desktop-electron/src/main/windows.ts
Session panel min-width set to 24rem; Electron main window minWidth increased from 480 → 768.
i18n & tests
packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts, packages/app/src/i18n/parity.test.ts, packages/app/src/i18n/zh-branding.test.ts, packages/app/src/shell-frame-contract.test.ts
Removes session.new.subtitle, sidebar.menu.toggle, sidebar.pawwork.empty.sessions translation keys and corresponding test assertions; adjusts relative-time short labels and drops a specific top CSS assertion from shell-frame test.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LayoutContext
  participant Sidebar
  participant Main
  participant Electron

  rect rgba(200,200,255,0.5)
  User->>LayoutContext: toggle sidebar (mod+\ or UI)
  LayoutContext->>Sidebar: set sidebar.opened (single boolean)
  Sidebar-->>Main: update layout (docked or hidden)
  Main-->>Electron: layout constrained by minWidth (>=768)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • #276 — Titlebar/window overlap: touches titlebar and Electron window sizing changes in this PR, so it may be linked.

Possibly related PRs

Poem

🐰 I hopped through code, nibbled aim and rail away,

One toggle left to guide the day.
Drawer folded, hover tricks are gone,
The sidebar sleeps until called upon.
Hopping onward, tidy paths I play.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly summarizes the main change: reducing sidebar from three states to two (docked/hidden), which aligns with the core refactoring throughout the changeset.
Description check ✅ Passed The description follows the template structure with all major sections present: Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, Screenshots, and Checklist.
Linked Issues check ✅ Passed The PR comprehensively implements all coding objectives from #343: removes Mode B/C logic (hoverProject, peek, createAim, arm/disarm), deletes mobileSidebar store, raises minWidth to 768, and migrates persisted layout with zero stale references.
Out of Scope Changes check ✅ Passed All changes align with issue #343 scope. Polish items (composer chip responsiveness, relative-time format, empty-state removal) are noted as issue-adjacent and intentional; no unrelated refactors or suspicious dependencies detected.

✏️ 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 worktree-claude+sidebar-two-states

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

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

@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/app/src/pages/layout.tsx (1)

2100-2114: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The new top divider is painted underneath the main pane.

This border sits on a z-0 sibling, while the overlapping main shell is a z-20 pane with an opaque background, so the divider never becomes visible. Move the border onto the main container or raise it above that layer.

🧩 Suggested change
-              <div
-                class="pointer-events-none absolute top-0 right-0 z-0 border-t border-border-weaker-base"
-                style={{ left: layout.sidebar.opened() ? `${side()}px` : "0" }}
-              />
-
               <div
                 classList={{
-                  "absolute inset-y-0 right-0 left-[var(--main-left)]": true,
+                  "absolute inset-y-0 right-0 left-[var(--main-left)] border-t border-border-weaker-base": true,
                   "z-20": true,
                   "transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none":
                     !state.sizing,
                 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/pages/layout.tsx` around lines 2100 - 2114, The top divider
div (the element with class "pointer-events-none absolute top-0 right-0 z-0
border-t..." that uses layout.sidebar.opened() and side()) is rendered beneath
the main pane (the sibling div with classList including "z-20" and style
"--main-left"), so it never shows; fix by either moving the divider markup into
the main container div (the element with classList that contains "z-20" and uses
state.sizing) so it becomes part of that layer, or raise its stacking context
above the main pane (change its z-0 to a value >20, e.g., z-30) while keeping
pointer-events-none and the same left calculation using layout.sidebar.opened()
and side().
🤖 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.tsx`:
- Around line 2068-2078: The outer navigation landmark in layout.tsx (the <nav>
that sets aria-label using language.t("sidebar.nav.projectsAndSessions") and
wraps sidebarContent()) is creating nested nav landmarks because PawworkSidebar
renders its own session-list <nav>; change the outer element to a non-landmark
container (e.g., <aside> or a <div>) or alternatively ensure the inner nav
inside PawworkSidebar is given an appropriate aria-label so it is not unnamed;
update the element around sidebarContent() where side(), setState("nav", el) and
data-component="sidebar-nav-desktop" are used to avoid creating a second,
unnamed <nav>.

---

Outside diff comments:
In `@packages/app/src/pages/layout.tsx`:
- Around line 2100-2114: The top divider div (the element with class
"pointer-events-none absolute top-0 right-0 z-0 border-t..." that uses
layout.sidebar.opened() and side()) is rendered beneath the main pane (the
sibling div with classList including "z-20" and style "--main-left"), so it
never shows; fix by either moving the divider markup into the main container div
(the element with classList that contains "z-20" and uses state.sizing) so it
becomes part of that layer, or raise its stacking context above the main pane
(change its z-0 to a value >20, e.g., z-30) while keeping pointer-events-none
and the same left calculation using layout.sidebar.opened() and side().
🪄 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: d2911e46-c231-4fc3-a763-2372b08a0af9

📥 Commits

Reviewing files that changed from the base of the PR and between b8c747c and eaaf3a5.

📒 Files selected for processing (18)
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/context/layout.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/parity.test.ts
  • packages/app/src/i18n/zh-branding.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/index.css
  • 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/shell-frame-contract.test.ts
  • packages/app/src/utils/aim.ts
  • packages/desktop-electron/src/main/windows.ts
💤 Files with no reviewable changes (6)
  • packages/app/src/shell-frame-contract.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/i18n/parity.test.ts
  • packages/app/src/utils/aim.ts
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/i18n/zh-branding.test.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). (8)
  • GitHub Check: check
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-opencode
  • GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (2)
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)

Renderer process should only call window.api from src/preload

Files:

  • packages/desktop-electron/src/main/windows.ts
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/session.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.tsx
🧠 Learnings (26)
📓 Common learnings
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: 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: 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: 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: 342
File: packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py:90-90
Timestamp: 2026-04-30T06:29:54.043Z
Learning: In `packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py` (Astro-Han/pawwork), the dark-mode ΔL* threshold for sidebar-vs-main background is intentionally `(2.0, 5.0)`, NOT `8.0–14.0`. The higher range was the old contract before commit `a716c83b2` ("dark composer lift"), which moved visual elevation from the main background to the composer surface. The sidebar→main background delta in dark mode is now intentionally flat (~2–5 ΔL*); the elevation step is expressed at the composer level instead. Do NOT flag `(2.0, 5.0)` as mismatched or too narrow for the dark ΔL* assertion in future reviews.
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.

Applied to files:

  • packages/desktop-electron/src/main/windows.ts
📚 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/session.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.tsx
📚 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/app/src/pages/session.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/index.css
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.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/session.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.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/session.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.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/workspace-chip.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.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/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-30T10:26:59.483Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-select-model.tsx:168-170
Timestamp: 2026-04-30T10:26:59.483Z
Learning: In `packages/app/src/components/dialog-select-model.tsx` (Astro-Han/pawwork), the `Kobalte.Content` for `ModelSelectorPopover` intentionally uses inline `style={{ "border-radius": "14px", "box-shadow": "var(--shadow-floating)" }}` rather than the shared `data-component="popover-content"` class from `packages/ui/src/components/popover.css`. Reason: this is a custom dropdown anatomy (`w-72 h-80 flex flex-col`), not a Popover composition — adopting `popover-content` would silently import min/max-width constraints, nested slot styles (`popover-header`, `popover-body`, `popover-arrow`), and `popover-open`/`popover-close` keyframe animations driven by Kobalte's `data-expanded`/`data-closed` attributes. The one-line inline style expresses exactly the two tokens needed without the anatomy/animation contract. Do NOT suggest replacing it with the shared popover-content class.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/index.css
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.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/components/prompt-input/workspace-chip.tsx
  • 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/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/index.css
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-30T10:41:33.826Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/desktop-electron/src/main/ipc.ts:342-344
Timestamp: 2026-04-30T10:41:33.826Z
Learning: In Astro-Han/pawwork, the `export-session` IPC handler in `packages/desktop-electron/src/main/ipc.ts` accepts an optional localized `title` argument for `dialog.showSaveDialog`, keeping `"Export session"` as an English fallback. The localized title is plumbed from the renderer through `platform.exportSession` → `window.api.exportSession` → preload (`preload/types.ts`, `preload/index.ts`, `renderer/index.tsx`) → main, mirroring the same shape as the `save-file-picker` handler. Both renderer callsites (`layout.tsx` and `message-timeline.tsx`) pass `language.t("session.export.action.export")`. Do NOT flag the hard-coded fallback as unlocalized — the renderer is responsible for supplying the localized title.

Applied to files:

  • packages/app/src/i18n/en.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/i18n/en.ts
  • packages/app/src/pages/layout/pawwork-sidebar.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/app/src/i18n/en.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/app/src/i18n/en.ts
📚 Learning: 2026-04-29T04:37:42.439Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 320
File: packages/ui/src/i18n/en.ts:0-0
Timestamp: 2026-04-29T04:37:42.439Z
Learning: In `packages/ui/src/i18n/en.ts` (Astro-Han/pawwork, PR `#320`, commit 02024730d), the canonical English translations for `ui.messagePart.context` counter labels are:
- `ui.messagePart.context.read.one` → `Read {{count}} file`
- `ui.messagePart.context.read.other` → `Read {{count}} files`
- `ui.messagePart.context.search.one` → `Searched {{count}} time`
- `ui.messagePart.context.search.other` → `Searched {{count}} times`
- `ui.messagePart.context.list.one` → `Listed {{count}} directory`
- `ui.messagePart.context.list.other` → `Listed {{count}} directories`
All three use past-tense verb-led format. Do NOT suggest count-first format (e.g., `{{count}} searches`) for these keys in future reviews.

Applied to files:

  • packages/app/src/i18n/en.ts
📚 Learning: 2026-04-30T06:29:54.043Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py:90-90
Timestamp: 2026-04-30T06:29:54.043Z
Learning: In `packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py` (Astro-Han/pawwork), the dark-mode ΔL* threshold for sidebar-vs-main background is intentionally `(2.0, 5.0)`, NOT `8.0–14.0`. The higher range was the old contract before commit `a716c83b2` ("dark composer lift"), which moved visual elevation from the main background to the composer surface. The sidebar→main background delta in dark mode is now intentionally flat (~2–5 ΔL*); the elevation step is expressed at the composer level instead. Do NOT flag `(2.0, 5.0)` as mismatched or too narrow for the dark ΔL* assertion in future reviews.

Applied to files:

  • packages/app/src/index.css
📚 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/index.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/components/titlebar.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.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/components/titlebar.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-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.

Applied to files:

  • packages/app/src/pages/layout/sidebar-workspace.tsx
📚 Learning: 2026-04-28T14:46:33.806Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 302
File: packages/opencode/src/plugin/codex.ts:0-0
Timestamp: 2026-04-28T14:46:33.806Z
Learning: In `packages/opencode/src/plugin/codex.ts` (Astro-Han/pawwork), the Provider SDK v1 hook `Model` type omits the `input` field on `limit`, but opencode uses `limit.input` for compaction budget. The pattern to avoid a bare `ts-expect-error` is to declare a local typed intersection `const limit: typeof model.limit & { input: number } = { context: ..., input: ..., output: ... }` and assign that to `model.limit`. Do not flag the intersection cast as unnecessary — it is the intentional way to type-safely add `input` without suppressing the whole assignment.

Applied to files:

  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-24T00:02:53.315Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 203
File: packages/app/e2e/sidebar/sidebar-session-links.spec.ts:34-55
Timestamp: 2026-04-24T00:02:53.315Z
Learning: In Astro-Han/pawwork E2E tests (`packages/app/e2e/**/*.spec.ts`), `project.trackDirectory()` and `project.trackSession()` cannot be called before `project.open()` — the `project` fixture throws until `open()` initializes its internal state. The correct pattern is: call `project.trackSession(sessionID)` from inside the `beforeGoto` callback (where state already exists), call `project.trackDirectory(directory)` and cross-workspace `project.trackSession(id, directory)` immediately after `project.open()` returns, and rely on explicit `finally` cleanup (e.g. `cleanupSession` / `cleanupTestProject`) for any resources created before `open()` that cannot yet be tracked via the fixture.

Applied to files:

  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-30T10:26:58.545Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.

Applied to files:

  • packages/app/src/pages/layout.tsx
🔇 Additional comments (10)
packages/app/src/components/prompt-input/workspace-chip.tsx (1)

73-73: Responsive workspace label collapse is implemented cleanly.

Good use of animated max-width with container-query collapse while keeping the trigger accessible via aria-label.

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

1145-1148: The composer container-query rollout and responsive chip collapse look good.

@container/composer on the shell form plus per-chip max-width transitions provide a clean, deterministic tightening strategy at narrow widths.

Also applies to: 1182-1185, 1445-1445, 1559-1559

packages/desktop-electron/src/main/windows.ts (1)

74-75: Min-width update is consistent and safe.

Raising minWidth to 768 in the main window options cleanly matches the two-state desktop layout constraints.

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

2071-2071: Session panel min-width change looks good.

The min-w-[24rem] constraint is coherent with the stated goal of avoiding main-column collapse.

packages/app/src/index.css (1)

115-128: Titlebar gradient unification is solid.

Applying the sidebar/right-panel-aware gradient unconditionally simplifies behavior across widths and matches the intended continuous divider treatment.

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

293-317: Legacy layout key cleanup is correctly implemented.

The hasMobileSidebar guard plus explicit deletion in migrated output is a clean backward-compatibility path for persisted layouts.

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

635-635: Updated session title copy looks good.

The new prompt is concise and fits the session creation entry point.

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

49-50: Sidebar workspace API simplification is consistent.

The context signature update and all shown call-site adjustments are in sync, with no mismatch left behind in this file.

Also applies to: 159-159, 239-239, 442-443

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

106-107: Titlebar control visibility changes are coherent with the new sidebar model.

The updated class logic cleanly reflects always-available sidebar toggle behavior and sidebar-dependent new-session affordance.

Also applies to: 130-131

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

126-127: LGTM: the tooltip fallback is now correctly opt-in for the docked-only sidebar.

This keeps the default row interaction path simple and avoids carrying tooltip-only behavior from the removed rail/peek mode.

Also applies to: 183-197, 254-257

Comment thread packages/app/src/pages/layout.tsx Outdated
@Astro-Han Astro-Han force-pushed the worktree-claude+sidebar-two-states branch from b9a3895 to 65f3134 Compare April 30, 2026 14:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/context/layout.tsx`:
- Around line 293-310: Add a unit test in
packages/app/src/context/layout.test.ts that verifies migrateStoredLayout
removes the legacy mobileSidebar key: create a test case that provides a
persisted layout object containing mobileSidebar plus valid
sidebar/review/fileTree/rightPanel/sessionTabs values, call
migrateStoredLayout(...) and assert the returned object no longer has
mobileSidebar and that
migratedSidebar/migratedReview/migratedFileTree/migratedRightPanel/migratedSessionTabs
(or the corresponding keys produced by migrateStoredLayout) retain their
expected values; reference the migrateStoredLayout function and the test file to
locate where to add this new spec.

In `@packages/app/src/pages/session.tsx`:
- Line 2071: The container div currently applies min-w-[24rem] on all screen
sizes (the element with class string containing "@container relative
min-w-[24rem] flex flex-col ..."), which causes horizontal overflow on narrow
viewports; change the Tailwind utility to apply the min width only at desktop
breakpoints (e.g., replace min-w-[24rem] with md:min-w-[24rem]) so the desktop
safeguard remains while small-screen flex-col layouts can shrink to fit.
🪄 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: 4a6fceb9-e0de-4ddf-818d-689de2944b0d

📥 Commits

Reviewing files that changed from the base of the PR and between b9a3895 and 65f3134.

📒 Files selected for processing (18)
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/context/layout.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/parity.test.ts
  • packages/app/src/i18n/zh-branding.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/index.css
  • 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/shell-frame-contract.test.ts
  • packages/app/src/utils/aim.ts
  • packages/desktop-electron/src/main/windows.ts
💤 Files with no reviewable changes (6)
  • packages/app/src/i18n/parity.test.ts
  • packages/app/src/i18n/zh-branding.test.ts
  • packages/app/src/shell-frame-contract.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/utils/aim.ts
  • packages/app/src/pages/layout/sidebar-project.tsx
📜 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). (3)
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (2)
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)

Renderer process should only call window.api from src/preload

Files:

  • packages/desktop-electron/src/main/windows.ts
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/session.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
🧠 Learnings (27)
📓 Common learnings
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: 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: 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: 342
File: packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py:90-90
Timestamp: 2026-04-30T06:29:54.043Z
Learning: In `packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py` (Astro-Han/pawwork), the dark-mode ΔL* threshold for sidebar-vs-main background is intentionally `(2.0, 5.0)`, NOT `8.0–14.0`. The higher range was the old contract before commit `a716c83b2` ("dark composer lift"), which moved visual elevation from the main background to the composer surface. The sidebar→main background delta in dark mode is now intentionally flat (~2–5 ΔL*); the elevation step is expressed at the composer level instead. Do NOT flag `(2.0, 5.0)` as mismatched or too narrow for the dark ΔL* assertion 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.
📚 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/desktop-electron/src/main/windows.ts
  • packages/app/src/components/titlebar.tsx
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.

Applied to files:

  • packages/desktop-electron/src/main/windows.ts
📚 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/session.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 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/app/src/pages/session.tsx
  • packages/app/src/index.css
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.

Applied to files:

  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/layout.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/session.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input/workspace-chip.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/session.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input/workspace-chip.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/components/titlebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.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/titlebar.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input/workspace-chip.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/components/titlebar.tsx
  • packages/app/src/index.css
  • packages/app/src/context/layout.tsx
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.tsx
📚 Learning: 2026-04-30T06:29:54.043Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py:90-90
Timestamp: 2026-04-30T06:29:54.043Z
Learning: In `packages/ui/scripts/grayscale-audit/chatgpt_gap_audit.py` (Astro-Han/pawwork), the dark-mode ΔL* threshold for sidebar-vs-main background is intentionally `(2.0, 5.0)`, NOT `8.0–14.0`. The higher range was the old contract before commit `a716c83b2` ("dark composer lift"), which moved visual elevation from the main background to the composer surface. The sidebar→main background delta in dark mode is now intentionally flat (~2–5 ΔL*); the elevation step is expressed at the composer level instead. Do NOT flag `(2.0, 5.0)` as mismatched or too narrow for the dark ΔL* assertion in future reviews.

Applied to files:

  • packages/app/src/index.css
📚 Learning: 2026-04-30T10:26:59.483Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-select-model.tsx:168-170
Timestamp: 2026-04-30T10:26:59.483Z
Learning: In `packages/app/src/components/dialog-select-model.tsx` (Astro-Han/pawwork), the `Kobalte.Content` for `ModelSelectorPopover` intentionally uses inline `style={{ "border-radius": "14px", "box-shadow": "var(--shadow-floating)" }}` rather than the shared `data-component="popover-content"` class from `packages/ui/src/components/popover.css`. Reason: this is a custom dropdown anatomy (`w-72 h-80 flex flex-col`), not a Popover composition — adopting `popover-content` would silently import min/max-width constraints, nested slot styles (`popover-header`, `popover-body`, `popover-arrow`), and `popover-open`/`popover-close` keyframe animations driven by Kobalte's `data-expanded`/`data-closed` attributes. The one-line inline style expresses exactly the two tokens needed without the anatomy/animation contract. Do NOT suggest replacing it with the shared popover-content class.

Applied to files:

  • packages/app/src/index.css
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/components/prompt-input/workspace-chip.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/index.css
📚 Learning: 2026-04-30T10:41:33.826Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/desktop-electron/src/main/ipc.ts:342-344
Timestamp: 2026-04-30T10:41:33.826Z
Learning: In Astro-Han/pawwork, the `export-session` IPC handler in `packages/desktop-electron/src/main/ipc.ts` accepts an optional localized `title` argument for `dialog.showSaveDialog`, keeping `"Export session"` as an English fallback. The localized title is plumbed from the renderer through `platform.exportSession` → `window.api.exportSession` → preload (`preload/types.ts`, `preload/index.ts`, `renderer/index.tsx`) → main, mirroring the same shape as the `save-file-picker` handler. Both renderer callsites (`layout.tsx` and `message-timeline.tsx`) pass `language.t("session.export.action.export")`. Do NOT flag the hard-coded fallback as unlocalized — the renderer is responsible for supplying the localized title.

Applied to files:

  • packages/app/src/i18n/en.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/i18n/en.ts
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/pawwork-sidebar.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/app/src/i18n/en.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/app/src/i18n/en.ts
📚 Learning: 2026-04-29T04:37:42.439Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 320
File: packages/ui/src/i18n/en.ts:0-0
Timestamp: 2026-04-29T04:37:42.439Z
Learning: In `packages/ui/src/i18n/en.ts` (Astro-Han/pawwork, PR `#320`, commit 02024730d), the canonical English translations for `ui.messagePart.context` counter labels are:
- `ui.messagePart.context.read.one` → `Read {{count}} file`
- `ui.messagePart.context.read.other` → `Read {{count}} files`
- `ui.messagePart.context.search.one` → `Searched {{count}} time`
- `ui.messagePart.context.search.other` → `Searched {{count}} times`
- `ui.messagePart.context.list.one` → `Listed {{count}} directory`
- `ui.messagePart.context.list.other` → `Listed {{count}} directories`
All three use past-tense verb-led format. Do NOT suggest count-first format (e.g., `{{count}} searches`) for these keys in future reviews.

Applied to files:

  • packages/app/src/i18n/en.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/app/src/pages/layout/sidebar-workspace.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.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
📚 Learning: 2026-04-28T14:46:33.806Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 302
File: packages/opencode/src/plugin/codex.ts:0-0
Timestamp: 2026-04-28T14:46:33.806Z
Learning: In `packages/opencode/src/plugin/codex.ts` (Astro-Han/pawwork), the Provider SDK v1 hook `Model` type omits the `input` field on `limit`, but opencode uses `limit.input` for compaction budget. The pattern to avoid a bare `ts-expect-error` is to declare a local typed intersection `const limit: typeof model.limit & { input: number } = { context: ..., input: ..., output: ... }` and assign that to `model.limit`. Do not flag the intersection cast as unnecessary — it is the intentional way to type-safely add `input` without suppressing the whole assignment.

Applied to files:

  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-30T10:26:58.545Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.

Applied to files:

  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-29T13:27:25.687Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/e2e/session/session-scroll-position.spec.ts:171-230
Timestamp: 2026-04-29T13:27:25.687Z
Learning: In `packages/app/e2e/session/session-scroll-position.spec.ts`, the `describe()` formatter is intentionally duplicated in `installPageErrorProbe` (inside `page.addInitScript`, runs in browser context) and `collectPageErrors` (runs in Node.js/Playwright context). These are separate execution contexts — `addInitScript` serializes its closure to a string for injection — so extracting a shared helper would add cross-context indirection. Do NOT flag this duplication as a maintenance issue.

Applied to files:

  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/app/src/pages/layout.tsx
📚 Learning: 2026-04-24T00:02:53.315Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 203
File: packages/app/e2e/sidebar/sidebar-session-links.spec.ts:34-55
Timestamp: 2026-04-24T00:02:53.315Z
Learning: In Astro-Han/pawwork E2E tests (`packages/app/e2e/**/*.spec.ts`), `project.trackDirectory()` and `project.trackSession()` cannot be called before `project.open()` — the `project` fixture throws until `open()` initializes its internal state. The correct pattern is: call `project.trackSession(sessionID)` from inside the `beforeGoto` callback (where state already exists), call `project.trackDirectory(directory)` and cross-workspace `project.trackSession(id, directory)` immediately after `project.open()` returns, and rely on explicit `finally` cleanup (e.g. `cleanupSession` / `cleanupTestProject`) for any resources created before `open()` that cannot yet be tracked via the fixture.

Applied to files:

  • packages/app/src/pages/layout.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
🔇 Additional comments (12)
packages/desktop-electron/src/main/windows.ts (1)

74-74: LGTM! Minimum width correctly raised to support two-state sidebar.

The increase from 480 to 768 correctly implements the PR objective to eliminate the mobile drawer layout. Users with stored window widths below 768px will see their windows widened on next launch—this is an intentional breaking change to enable the simplified docked/hidden sidebar model across all window sizes.

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

73-73: Responsive label collapse looks good.

Line 73 cleanly aligns the workspace chip with the new composer container-query behavior and keeps truncation/animation consistent with the rest of the prompt controls.

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

1130-1130: Model/variant trigger responsiveness is implemented cleanly.

These updates consistently apply max-width transitions and composer-threshold collapse behavior across both model and variant controls.

Also applies to: 1145-1148, 1170-1170, 1182-1185, 1217-1217


1445-1445: Container-query wiring and action-row tightening look solid.

Line 1445 correctly enables the named composer container for descendant query rules, and Line 1559’s spacing adjustment matches the denser bar layout objective.

Also applies to: 1559-1559

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

39-40: setScrollContainerRef signature migration is clean and consistent.

Line 39 and Line 282 are aligned, and the ref wiring now cleanly uses the single-argument API.

Also applies to: 280-283


228-228: Tooltip placement simplification looks good.

The fixed right placement is coherent with removing mobile/rail-specific sidebar behavior.

Also applies to: 342-345


287-334: Session-list conditional rendering is safe after the mode cleanup.

The list only renders when data exists, without introducing control-flow issues.

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

11-12: Layout import cleanup is correct.

The narrowed import matches the removal of layout-state coupling in this file.


126-127: Tooltip behavior refactor is coherent and stable.

Default-off tooltip gating plus fixed right placement is consistent with the new sidebar model.

Also applies to: 184-188


254-257: NewSessionItem simplification looks good.

Direct rendering keeps interaction behavior straightforward with no regressions in this segment.

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

49-50: Context ref signature update is correctly propagated.

The WorkspaceSidebarContext type and LocalWorkspace usage are consistent.

Also applies to: 442-443


159-162: Workspace action/menu and session-item prop simplification is clean.

These updates match the reduced sidebar state model and keep the component API tighter.

Also applies to: 238-240

Comment thread packages/app/src/context/layout.tsx
Comment thread packages/app/src/pages/session.tsx
@Astro-Han Astro-Han force-pushed the worktree-claude+sidebar-two-states branch from 0a0e6a5 to f12e009 Compare April 30, 2026 14:18
Delete the Mode C mobile drawer: mobileSidebar store + API, drawer DOM,
mobile hamburger in titlebar, the mobile?: boolean prop chain across
sidebar components. migrateStoredLayout now strips the legacy
mobileSidebar key from persisted records.

Sidebar still has Mode A (docked) and Mode B (rail+hover); Mode B
removal lands in the next commit.

Refs #343
Delete the Mode B rail+hover state machine: state.hoverProject,
state.peek, state.peeked; createAim mouse-aim debounce
(packages/app/src/utils/aim.ts deleted); arm/disarm/navLeave 300ms
grace timer; sidebarHovering/sidebarExpanded derived memos and all 26+
subscribers; SidebarPanel dead code; sidebar-project.tsx
(SortableProject/ProjectTile/ProjectPreviewPanel, all unreferenced
after SidebarPanel removal).

Sidebar now renders as: opened ? <docked nav /> : null. Same behavior
across every viewport. Drop the obsolete shell-frame-contract assertion
that referenced the deleted mobile drawer top var.

Refs #343
PawWork has no separate small-window layout (#343 removed the mobile
drawer). 768px guarantees a usable two-column layout while keeping
half-screen viable on 14"+ MBPs. 800+ would cut all MBP half-snap
workflows; below 768 the docked sidebar plus main content gets too
cramped.

Refs #343
Add @container/composer container query so the model, variant, and
workspace chips shrink their internal width when the composer
container is below 32rem. Avoids overflow at the new 768px minimum
window size with sidebar docked, keeps the chip row on a single line.

Refs #343
Prevents the middle column from collapsing into a vertical sliver when both
sidebars are open and the window is dragged below ~1024px. Right side panel
clips off-screen via the parent's overflow-hidden; users can close it via
the titlebar toggle to recover full visibility.
The list area being empty already conveys 'no sessions'; the New Session
button at the top is the obvious CTA. The paragraph was telling users what
they could already see, so cut it. Removes the i18n keys in zh + en too.
Choose what to do reads as imperative-pick-from-list, but the composer is
an open input. Match v0/Bolt/Lovable agent-product pattern (user as the
subject of the verb) and align with the Chinese 今天想做什么。
The key has had no UI consumer since #300 stripped it from the home screen.
The leftover comment Kept for parity/branding tests acknowledged it was
only kept alive by its own tests. Drop the value plus the matching test
assertions in zh-branding and parity.
Two leftover xl: viewport gates from the previous sidebar mode caused the
new two-state sidebar to render inconsistently across widths:

- macOS titlebar gradient (so the sidebar visually extends to the top
  edge through the titlebar) was nested inside @media (min-width:
  1280px), so narrower windows got a solid bg seam instead.
- The titlebar new-session button used xl:opacity-0 to hide when the
  sidebar was open, so narrow windows showed it duplicated alongside
  the sidebar's own New Session entry.

Drop the xl: prefixes. The sidebar/main visual seam now runs from window
top to bottom at every width, and the titlebar new-session button is
hidden whenever the sidebar is open.
The 1px right-panel divider was painted as a vertical line through the
full titlebar height, so dragging the right panel wide enough pulled the
line into the titlebar's center area where it overlapped the breadcrumb
and center-portal content.

macOS native apps (Mail, Finder, Notes) deliberately do not split their
toolbar with internal vertical lines, treating it as a single unified
chrome surface. Drop the divider stops; the right-panel boundary stays
visible through the panel's own bg / border just below the titlebar.

Windows is unaffected (no titlebar gradient there). Extending unified
chrome to Windows would conflict with Explorer / Settings / Edge native
patterns, so leave it as-is and revisit if user feedback asks.
Sidebar session list shows time next to session titles; the trailing
ago / 前 reads as redundant in that context (the column itself communicates
time-since). Match Slack / Discord / Linear / GitHub compact pattern of
1m / 2h / 3d without ago. Just now / 刚刚 stays as-is since it conveys time
without an ago suffix already.
@Astro-Han Astro-Han force-pushed the worktree-claude+sidebar-two-states branch from f12e009 to cc1a899 Compare April 30, 2026 14:24
@Astro-Han Astro-Han merged commit 0c8ca56 into dev Apr 30, 2026
24 checks passed
@Astro-Han Astro-Han deleted the worktree-claude+sidebar-two-states branch April 30, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request P2 Medium priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] Reduce sidebar to two states: docked or hidden

1 participant