refactor(app): reduce sidebar to two states (docked / hidden)#346
Conversation
There was a problem hiding this comment.
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.
|
Warning Rate limit exceeded
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 We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (20)
📝 WalkthroughWalkthroughRemoves mobile-drawer and hover/peek sidebar modes; deletes Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 0/3 reviews remaining, refill in 5 minutes and 17 seconds. Comment |
There was a problem hiding this comment.
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 winThe new top divider is painted underneath the main pane.
This border sits on a
z-0sibling, while the overlapping main shell is az-20pane 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
📒 Files selected for processing (18)
packages/app/src/components/prompt-input.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/context/layout.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/parity.test.tspackages/app/src/i18n/zh-branding.test.tspackages/app/src/i18n/zh.tspackages/app/src/index.csspackages/app/src/pages/layout.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/pages/layout/sidebar-project.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/session.tsxpackages/app/src/shell-frame-contract.test.tspackages/app/src/utils/aim.tspackages/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.apifromsrc/preload
Files:
packages/desktop-electron/src/main/windows.ts
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/app/src/i18n/en.tspackages/app/src/components/titlebar.tsxpackages/app/src/context/layout.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/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.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/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.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/app/src/index.csspackages/app/src/components/titlebar.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/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.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/app/src/i18n/en.tspackages/app/src/components/titlebar.tsxpackages/app/src/context/layout.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/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.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/app/src/i18n/en.tspackages/app/src/components/titlebar.tsxpackages/app/src/context/layout.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/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.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/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.tsxpackages/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.tsxpackages/app/src/index.csspackages/app/src/components/prompt-input.tsxpackages/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.tsxpackages/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.tsxpackages/app/src/index.csspackages/app/src/components/titlebar.tsxpackages/app/src/context/layout.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/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.tspackages/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.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/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-widthwith container-query collapse while keeping the trigger accessible viaaria-label.packages/app/src/components/prompt-input.tsx (1)
1145-1148: The composer container-query rollout and responsive chip collapse look good.
@container/composeron the shell form plus per-chipmax-widthtransitions 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
minWidthto 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
hasMobileSidebarguard 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
b9a3895 to
65f3134
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (18)
packages/app/src/components/prompt-input.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/context/layout.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/parity.test.tspackages/app/src/i18n/zh-branding.test.tspackages/app/src/i18n/zh.tspackages/app/src/index.csspackages/app/src/pages/layout.tsxpackages/app/src/pages/layout/pawwork-sidebar.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/pages/layout/sidebar-project.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/session.tsxpackages/app/src/shell-frame-contract.test.tspackages/app/src/utils/aim.tspackages/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.apifromsrc/preload
Files:
packages/desktop-electron/src/main/windows.ts
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/i18n/en.tspackages/app/src/context/layout.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/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.tspackages/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.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/pages/layout.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/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.tsxpackages/app/src/index.csspackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/pages/layout.tsxpackages/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.tsxpackages/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.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/i18n/en.tspackages/app/src/context/layout.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/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.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/i18n/en.tspackages/app/src/context/layout.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout.tsxpackages/app/src/components/prompt-input/workspace-chip.tsxpackages/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.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/pages/layout.tsxpackages/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.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout/sidebar-items.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout.tsxpackages/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.tsxpackages/app/src/index.csspackages/app/src/context/layout.tsxpackages/app/src/pages/layout/sidebar-workspace.tsxpackages/app/src/pages/layout.tsxpackages/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.csspackages/app/src/components/prompt-input.tsxpackages/app/src/pages/layout.tsxpackages/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.tspackages/app/src/pages/layout.tsxpackages/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.tsxpackages/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.tsxpackages/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:setScrollContainerRefsignature 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:NewSessionItemsimplification 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
WorkspaceSidebarContexttype andLocalWorkspaceusage 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
0a0e6a5 to
f12e009
Compare
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
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.
f12e009 to
cc1a899
Compare
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−837lines.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:
mod+\, they don't learn to hover-reveal a rail).minWidthto 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,mobileSidebaroverlay,xl:gates). Verify no stale references.packages/app/src/context/layout.tsx:migrateStoredLayoutstrips persistedmobileSidebarkey on read, so existing users don't error on boot.packages/desktop-electron/src/main/windows.ts:minWidth480 → 768.electron-window-stateauto-clamps stored bounds, so users with smaller saved windows resize on next launch (no migration code needed).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). Smoothmax-widthtransition replaces hardhidden.packages/app/src/pages/session.tsx: middle column getsmin-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 parentoverflow-hidden; user toggle in titlebar is the recovery path.Risk Notes
electron-window-stateclamps automatically.mobileSidebarkey is stripped from stored layout on read; no boot error.How To Verify
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
app,desktop,enhancement,P2,uidev:desktopdev, Conventional Commits in EnglishSummary by CodeRabbit
UI & Layout Improvements
Text Updates