Skip to content

feat(desktop): switch Windows shell to native Win11 frame and add cross-platform About modal#231

Merged
Astro-Han merged 13 commits into
devfrom
worktree-feat-windows-chrome
Apr 25, 2026
Merged

feat(desktop): switch Windows shell to native Win11 frame and add cross-platform About modal#231
Astro-Han merged 13 commits into
devfrom
worktree-feat-windows-chrome

Conversation

@Astro-Han

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

Copy link
Copy Markdown
Owner

Summary

  • Switch Windows main + loading windows to native Win11 frame (frame: true), kill the frameless+overlay setup; remove the unused setTitlebar IPC chain end to end.
  • Refactor menu template into per-platform builders (buildMacosMenuTemplate, buildWindowsMenuTemplate); install Win/macOS application menu via dispatch; Linux remains menu-free.
  • Add cross-platform About modal: macOS keeps system role:about panel; Windows + Linux ship a Kobalte Dialog modal (AboutModal) wired through new about:get-info / about:open preload bridge.
  • Drop Windows-only titlebar chrome (138px placeholder + conditional drag region) so the renderer header behaves like macOS now that the OS draws the chrome.
  • Migrate menu accelerators from Cmd/Option literals to CmdOrCtrl/Alt so Windows shortcuts resolve correctly without a second template.

Why

Windows users currently see: extra app-drawn border, right-corner buttons pushed inboard by the WCO placeholder, and no menu bar (so no in-app discovery for About / Check for Updates / file actions). This PR aligns the Windows shell with native Win11 conventions and exposes a parallel About entry point.

Related Issue

No issue filed; captured directly via brainstorming + spec.

How To Verify

```bash
bun --cwd packages/desktop-electron run typecheck
bun --cwd packages/app run typecheck
bun --cwd packages/desktop-electron test
bun --cwd packages/app test
```

All pass locally (295 desktop-electron, 571 app, including new `menu-template.test.ts` with 5 cases and `shell-frame-contract.test.ts` regression for the linux-only inset selector + Win-no-placeholder).

macOS visual smoke (manually verified):

  • Traffic lights / hidden titlebar / integrated chrome unchanged
  • PawWork ▸ About PawWork still opens system About panel (not the modal)
  • `Cmd+B` `Cmd+Shift+S` `Cmd+O` `Cmd+[` `Cmd+]` `Alt+Up` `Alt+Down` `Cmd+Alt+Up` `Cmd+Alt+Down` all function (verifies `CmdOrCtrl+Alt` migration didn't regress mac)
  • No About modal auto-opens

Screenshots or Recordings

No new visible UI on macOS (regression-only). Windows visuals validated via design comparison; real-device verification deferred to release smoke per PawWork policy (no Windows runner in `desktop-smoke.yml`).

Checklist

  • No issue, captured via spec/plan in `docs/superpowers/`
  • Labels: `enhancement` + `desktop` + `P1` + `windows`
  • Verification commands listed; targeted tests added/updated for menu templates and titlebar contract
  • macOS visual smoke completed; Windows deferred to release smoke (policy)
  • Considered macOS and Windows impact, explicit per-platform branches throughout
  • No docs / dependency / credential changes; CI change limited to env var injection
  • Targeting `dev`; Conventional Commits in English (13 commits, one reversible intent each)

Known follow-ups (not in scope)

  • `process.env.PAWWORK_BUILD_SHA` is read at runtime, but Electron's main process loses build-time env in packaged apps. Modal `Build` field will read "unknown" until SHA is baked in via `electron-vite` `define`. Tracked separately.
  • About modal trigger for Linux (Linux currently menu-free per spec D6); shared modal component is ready.
  • About `triggerAbout` falls back to `BrowserWindow.getFocusedWindow()` when called without an explicit window, corner case if loading window has focus.

Summary by CodeRabbit

  • New Features

    • Added an "About PawWork" dialog showing app version, build SHA, Electron and Chromium versions.
    • Added "About PawWork" menu item on Windows and macOS.
  • Chores

    • Build now exposes commit SHA so About shows a build identifier.
    • Added English and Chinese localizations for About text.
    • Adjusted titlebar and desktop shell styling/behavior on Windows and desktop platforms.

@Astro-Han Astro-Han added enhancement New feature or request windows Windows-specific P1 High priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions labels Apr 25, 2026
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an "About" feature (menu trigger, main IPC, preload API, renderer modal), injects build SHA into the Electron build, removes Windows titlebar overlay/dynamic theming, splits desktop menu generation for macOS/Windows, and updates related tests, CSS, and i18n. (50 words)

Changes

Cohort / File(s) Summary
About feature
packages/desktop-electron/src/main/ipc/about.ts, packages/app/src/components/about-modal.tsx
New About IPC: about:get-info returns version, electron/chrome versions, and PAWWORK_BUILD_SHA; triggerAbout sends about:open; renderer AboutModal subscribes to onAboutOpen, requests info, and shows dialog.
Preload & types
packages/desktop-electron/src/preload/index.ts, packages/desktop-electron/src/preload/types.ts, packages/app/src/app.tsx
Removed setTitlebar from preload/types and renderer window API; added getAboutInfo() and onAboutOpen(handler) in preload and renderer typings; app no longer calls setTitlebar.
Menu system & wiring
packages/desktop-electron/src/main/menu-template.ts, packages/desktop-electron/src/main/menu.ts, packages/desktop-electron/src/main/menu-template.test.ts, packages/desktop-electron/src/main/menu.test.ts
Split menu builders into buildMacosMenuTemplate/buildWindowsMenuTemplate; added triggerAbout dep and Windows Help→About wiring; expanded click typing; added tests for Windows/macOS menu expectations; introduced wrapClicks to bind BrowserWindow.
Windows chrome removal
packages/desktop-electron/src/main/windows.ts, packages/desktop-electron/src/main/window-chrome.ts, packages/desktop-electron/src/main/window-chrome.test.ts
Removed Windows titlebar overlay support and setTitlebar export; deleted nativeTheme/overlay helpers and Windows-specific window options; updated tests to remove overlay constant expectations.
App UI & titlebar
packages/app/src/components/titlebar.tsx, packages/app/src/index.css, packages/app/src/shell-frame-contract.test.ts
Made data-shell-drag-region conditional (not set on Windows), removed Windows spacer element, narrowed desktop shell-frame CSS selector to Linux-only, and adjusted contract tests for these changes.
Build & env
.github/workflows/build.yml, packages/desktop-electron/electron.vite.config.ts, packages/desktop-electron/src/main/env.d.ts, packages/opencode/test/github/build-workflow.test.ts
Propagates PAWWORK_BUILD_SHA from workflow into Electron build via import.meta.env; added env typings and updated workflow/test expectations.
i18n
packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Added about.* localization keys (title, version, build, electron, chromium) for English and Chinese.
Tests & contracts
packages/app/src/shell-frame-contract.test.ts, packages/desktop-electron/src/main/menu-template.test.ts, packages/desktop-electron/src/main/menu.test.ts
Updated/added tests to reflect menu split, About menu item, accelerator patterns, and CSS selector/titlebar structural changes.

Sequence Diagram

sequenceDiagram
    participant Menu as Menu System
    participant Main as Main Process
    participant Preload as Preload
    participant Renderer as App Renderer
    participant Modal as About Modal

    Menu->>Main: triggerAbout(window?)
    Main->>Main: select target BrowserWindow
    Main->>Preload: send 'about:open' IPC
    Preload->>Renderer: invoke onAboutOpen callback
    Renderer->>Modal: AboutModal receives open event
    Modal->>Preload: call getAboutInfo()
    Preload->>Main: invoke 'about:get-info'
    Main->>Main: gather version, electron/chrome, buildSha
    Main->>Preload: return AboutInfo
    Preload->>Modal: deliver AboutInfo
    Modal->>Modal: render dialog with metadata
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped to menus, fetched a SHA,

Whispered "about" and opened the way.
Windows trimmed, dialogs now sing,
Versions gleam like a tiny spring.
Nibble, bounce — new features sway!

🚥 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 accurately and concisely summarizes the main changes: switching Windows to native Win11 frame and adding a cross-platform About modal, which are the primary objectives of this PR.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required template sections: Summary, Why, How to Verify, Screenshots/Recordings rationale, and a complete Checklist with all items addressed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-feat-windows-chrome

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: 4

🤖 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/components/about-modal.tsx`:
- Around line 20-26: The dialog opens even if getAboutInfo throws or returns
undefined; wrap the async callback passed to window.api?.onAboutOpen in a
try/catch and only call setOpen(true) after verifying a non-undefined response
from window.api?.getAboutInfo (i.e., call setInfo(data) and then setOpen(true)
only when data is truthy); also handle and optionally log errors from
getAboutInfo so unsubscribe remains assigned by onMount and the dialog does not
open on failure.

In `@packages/app/src/i18n/zh.ts`:
- Line 1004: The about.title i18n entry currently reads "关于 PawWork" and must
use the repo's fully localized branding; update the "about.title" key's value to
"关于爪印" (remove the ASCII "PawWork" and use the Chinese brand "爪印") so it matches
the zh localization convention used elsewhere.

In `@packages/desktop-electron/src/main/index.ts`:
- Line 537: The call to registerAboutIpc() in index.ts must be removed and the
About IPC should be routed through the centralized IPC bootstrap in
src/main/ipc.ts: export or import the registerAboutIpc function in that file and
invoke it from the central registration function (e.g., a top-level
registerAllIpc or initIpc function) so all IPC handlers are registered from one
place; update index.ts to only call the central IPC bootstrap (not
registerAboutIpc directly) and ensure registerAboutIpc remains an exported
symbol so ipc.ts can call it.

In `@packages/desktop-electron/src/main/menu-template.test.ts`:
- Around line 29-35: The test uses a non-null assertion on the result of
find(...) when locating the Help menu which will throw a confusing runtime error
if missing; update the test that calls buildWindowsMenuTemplate and currently
assigns help via const help = tpl.find((m) => m.label === "Help")! to first
assert or guard that help is defined (e.g. expect(help).toBeDefined() or if
(!help) fail("Help menu not found in Windows template")) before accessing
help.submenu, so the failure message is explicit and the subsequent labels
extraction (help.submenu ?? []) is safe.
🪄 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: 6b810f53-fea3-41f5-80db-d7cde47f0719

📥 Commits

Reviewing files that changed from the base of the PR and between b9e7fa7 and 6c8516f.

📒 Files selected for processing (20)
  • .github/workflows/build.yml
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/index.css
  • packages/app/src/shell-frame-contract.test.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/window-chrome.test.ts
  • packages/desktop-electron/src/main/window-chrome.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
💤 Files with no reviewable changes (3)
  • packages/desktop-electron/src/main/window-chrome.test.ts
  • packages/desktop-electron/src/main/window-chrome.ts
  • packages/desktop-electron/src/main/ipc.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-desktop
  • GitHub Check: typecheck
  • GitHub Check: unit-desktop
  • GitHub Check: unit-opencode
  • GitHub Check: unit-app
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: analyze-js-ts
  • GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (2)
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/i18n/en.ts
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/shell-frame-contract.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.tsx
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/index.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
🧠 Learnings (16)
📓 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:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
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/i18n/zh.ts
📚 Learning: 2026-04-24T17:12:23.931Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.

Applied to files:

  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
  • packages/app/src/app.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/i18n/en.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/app/src/app.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/i18n/en.ts
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/shell-frame-contract.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.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/i18n/en.ts
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/shell-frame-contract.test.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/main/ipc.ts : Main process should register IPC handlers in `src/main/ipc.ts`

Applied to files:

  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/ipc/about.ts
📚 Learning: 2026-04-24T05:39:56.086Z
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:56.086Z
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
📚 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-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

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

Applied to files:

  • packages/app/src/shell-frame-contract.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use locator assertions like `toBeVisible()`, `toHaveCount(0)`, and `toHaveAttribute(...)` for normal UI state verification

Applied to files:

  • packages/app/src/shell-frame-contract.test.ts
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`

Applied to files:

  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/app/src/app.tsx
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/desktop-electron/src/main/menu.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Import test utilities from `../fixtures` instead of `playwright/test`

Applied to files:

  • packages/desktop-electron/src/main/menu.test.ts
🔇 Additional comments (28)
packages/app/src/index.css (1)

181-181: Linux-only frame selector is correctly scoped.

Line 181 now limits the inset frame chrome to Linux, which aligns with the native Windows frame switch.

.github/workflows/build.yml (1)

317-317: Build SHA injection in CI is a good addition.

Line 317 correctly passes the workflow commit SHA into the Electron build environment.

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

94-94: Conditional drag-region attribute matches the Windows native-frame move.

Line 94 correctly omits data-shell-drag-region on Windows while keeping behavior for other platforms.

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

1033-1038: About translation keys are complete and consistent.

Lines 1033-1038 provide the full about.* set needed by the new About modal.

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

480-481: About menu trigger wiring looks correct.

Line 480 cleanly forwards the selected BrowserWindow into the About trigger path.

packages/desktop-electron/src/main/menu.test.ts (2)

2-13: Test dependency shape update is correct.

Lines 2-13 correctly align test imports/mocks with the new macOS menu builder and About trigger dependency.


127-214: Mac-specific template calls are updated consistently.

These call-site updates correctly migrate test coverage to buildMacosMenuTemplate.

packages/app/src/shell-frame-contract.test.ts (2)

16-16: Linux-only frame selector contract is correctly updated.

Line 16 now validates the intended Linux-specific frame rule.


56-60: New titlebar contract test covers the intended regressions.

Lines 56-60 properly guard both the removed Windows placeholder and the conditional drag-region attribute.

packages/desktop-electron/src/main/menu-template.test.ts (2)

1-27: LGTM — Well-structured test coverage for the menu template refactor.

The test setup with stubDeps correctly implements all required MenuTemplateDeps properties, including the new triggerAbout. The baseOptions typing with locale: "en" as const ensures proper type narrowing.


37-60: Good regression tests for accelerator migration and macOS role preservation.

The accelerator test (lines 37-47) properly validates the Cmd/OptionCmdOrCtrl/Alt migration mentioned in the PR objectives. The macOS tests correctly verify the system About panel is retained via role: "about".

packages/desktop-electron/src/preload/index.ts (1)

83-90: LGTM — Clean About API surface.

The getAboutInfo and onAboutOpen methods correctly wire up to the IPC channels defined in ipc/about.ts. The unsubscribe pattern matches other event handlers like onMenuCommand.

Minor note: The wrapper const wrapped = () => handler() could explicitly ignore the event parameter (like (_: unknown) => handler()) for consistency with other handlers, but this is purely stylistic since about:open sends no data.

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

111-113: LGTM — Appropriate cleanup for Windows loading window.

Removing the menu from the Windows loading window via win.removeMenu() prevents a flickering or unnecessary menu bar on what should be a transient splash screen. This aligns with the PR's goal to streamline the Windows shell experience.


78-85: LGTM — Correct platform-specific window chrome handling.

macOS correctly retains titleBarStyle: "hidden" with custom traffic light positioning, while Windows now uses the native frame (no special overrides). The spread pattern cleanly applies platform-specific options.

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

74-87: LGTM — Window API types correctly updated.

The optional chaining (getAboutInfo?, onAboutOpen?) correctly handles non-Electron environments. The removal of setTitlebar aligns with the broader titlebar cleanup.


240-243: LGTM — AboutModal correctly positioned in component tree.

The modal is mounted within LanguageProvider context (required for useLanguage) and renders as a sibling to props.children, ensuring it's available app-wide without interfering with the main content hierarchy.


45-45: 🧹 Nitpick | 🔵 Trivial

AboutInfo type is duplicated across renderer and preload.

The AboutInfo type is defined in both packages/app/src/components/about-modal.tsx and packages/desktop-electron/src/preload/types.ts. Consider importing from a shared location to avoid drift.

One approach: export from types.ts and re-export through the preload surface so the app can import from the desktop-api package.

⛔ Skipped due to learnings
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 99
File: packages/desktop-electron/src/renderer/i18n/index.ts:30-35
Timestamp: 2026-04-21T13:45:45.149Z
Learning: In Astro-Han/pawwork, the locale normalization helpers in `packages/app/src/context/language.tsx` (`normalizeLocale`) and `packages/desktop-electron/src/renderer/i18n/index.ts` (`parseLocale`) are intentionally kept separate and have different fallback shapes: `normalizeLocale` always returns a concrete `Locale` (falling back to `"en"`), while `parseLocale` returns `Locale | null` so the desktop shell can decide whether to fall back to browser detection. Do not suggest extracting a shared normalization helper across these two runtimes.
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.
packages/desktop-electron/src/main/ipc/about.ts (2)

1-26: LGTM — Clean About IPC implementation.

The handler correctly leverages Electron's process.versions for runtime info and provides sensible "unknown" fallbacks. The triggerAbout function properly handles the optional browserWindow parameter with a fallback to the focused window.


10-12: Build SHA may show "unknown" in development — expected per PR notes.

The PAWWORK_BUILD_SHA env var is only injected during CI release builds. For local development, this will display "unknown" which is acceptable as noted in the PR's known follow-ups.

packages/desktop-electron/src/main/menu.ts (2)

17-35: LGTM — Clean adapter between template and Electron menu APIs.

The wrapClicks function correctly adapts the custom MenuItemTemplate abstraction to Electron's MenuItemConstructorOptions. The instanceof BrowserWindow check on line 29 properly handles the fact that Electron's click handler may receive BaseWindow or undefined, ensuring type safety for handlers expecting BrowserWindow.


37-59: LGTM — Platform-specific menu creation with proper dependency injection.

The early return for Linux (line 38) correctly implements the "Linux menu-free" spec. The openExternal wrapper with error logging (lines 42-46) adds resilience without cluttering the template builders.

packages/desktop-electron/src/preload/types.ts (2)

24-30: This is the canonical AboutInfo type location.

The preload types file is the correct authoritative source for IPC type definitions. The duplicate definitions in ipc/about.ts and about-modal.tsx should ideally import from here to prevent drift. (Related to earlier comment on app.tsx.)


97-98: LGTM — API surface correctly extended.

The getAboutInfo and onAboutOpen signatures match the implementation in preload/index.ts and follow established patterns for async handlers and event subscriptions respectively.

packages/app/src/components/about-modal.tsx (2)

15-16: Signal usage is acceptable here.

Per project convention, createStore is preferred only when signals represent coupled state updated together in the same transaction. Here, open and info are updated in sequence but within the same logical flow, making separate signals a reasonable choice.


30-52: LGTM — Accessible dialog with proper localization.

The dialog uses Kobalte's Dialog primitives (Dialog.Title, Dialog.CloseButton) which provide correct ARIA semantics. All user-facing strings use the language.t() helper with keys that are defined in both en.ts and zh.ts localization files.

packages/desktop-electron/src/main/menu-template.ts (3)

1-24: Menu callback contract update looks good.

Making browserWindow optional in the template click signature and threading triggerAbout through MenuTemplateDeps keeps the About action explicit without forcing a focused window at the type level.


34-145: macOS template split is clean.

This keeps the macOS-only roles and window menu behavior localized in one builder while still normalizing shared accelerator syntax.


148-220: Windows menu composition looks solid.

Separating the Windows template makes the native-frame transition easier to maintain, and the Help submenu wiring for updates/About is clear and consistent.

Comment thread packages/app/src/components/about-modal.tsx
Comment thread packages/app/src/i18n/zh.ts Outdated
Comment thread packages/desktop-electron/src/main/index.ts
Comment thread packages/desktop-electron/src/main/menu-template.test.ts

@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 introduces a new "About" modal for the desktop application, including the necessary IPC handlers, translations, and UI components. It also refactors the Electron menu system to provide platform-specific templates for macOS and Windows, standardizing accelerators using CmdOrCtrl. Additionally, the custom Windows titlebar overlay logic and the setTitlebar API have been removed. Feedback focuses on a missing "Go" menu in the Windows template and an inconsistent keyboard shortcut for creating new sessions.

Comment thread packages/desktop-electron/src/main/menu-template.ts
Comment thread packages/desktop-electron/src/main/menu-template.ts Outdated
Comment thread packages/app/src/components/about-modal.tsx Outdated
Comment thread packages/desktop-electron/src/main/menu-template.ts Outdated
Comment thread packages/app/src/components/about-modal.tsx Outdated
@Astro-Han Astro-Han force-pushed the worktree-feat-windows-chrome branch 2 times, most recently from 9513c4a to e477651 Compare April 25, 2026 09:37

@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 @.github/workflows/build.yml:
- Line 317: CI sets PAWWORK_BUILD_SHA but the app reads
process.env.PAWWORK_BUILD_SHA at runtime; packaged Electron doesn't preserve
that, so add PAWWORK_BUILD_SHA to the main build defines in
packages/desktop-electron/electron.vite.config.ts (alongside OPENCODE_CHANNEL
and PAWWORK_FEEDBACK_FORM_URL) and then update the about IPC handler in
packages/desktop-electron/src/main/ipc/about.ts to read the inlined define
(PAWWORK_BUILD_SHA) instead of process.env.PAWWORK_BUILD_SHA so the build-time
SHA is available in packaged builds.

In `@packages/desktop-electron/src/main/ipc/about.ts`:
- Around line 23-25: triggerAbout currently sends "about:open" to the focused/
clicked BrowserWindow which can be a loading window that doesn't mount the About
bridge; update triggerAbout to select the main app shell window instead of
blindly using BrowserWindow.getFocusedWindow(). Locate the function triggerAbout
and replace the target resolution with logic that iterates
BrowserWindow.getAllWindows() to find the window that hosts the app shell (e.g.,
check webContents.getURL() or other identifying property used for the main app
window) and send "about:open" to that window; if no app shell is found, fall
back to the focused window as a last resort. Ensure the change still uses the
same IPC channel "about:open".
🪄 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: f651230e-8abd-4068-9e45-7d13ec8244c5

📥 Commits

Reviewing files that changed from the base of the PR and between f477d2c and 9513c4a.

📒 Files selected for processing (14)
  • .github/workflows/build.yml
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.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). (3)
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (2)
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/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.tsx
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/preload/index.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/main/menu-template.ts
🧠 Learnings (22)
📓 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:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 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/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/menu-template.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
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/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-24T17:12:23.931Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-23T17:02:40.117Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:40.117Z
Learning: In Astro-Han/pawwork, Stylelint is NOT configured in the project. Per AGENTS.md, only linting (not formatting) is used, and the Biome formatter is explicitly disabled. Do NOT raise Stylelint-based review comments (e.g., `declaration-empty-line-before`) in this repo, as they are false positives from the static analysis environment and not enforced by the project toolchain.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-21T13:45:45.149Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 99
File: packages/desktop-electron/src/renderer/i18n/index.ts:30-35
Timestamp: 2026-04-21T13:45:45.149Z
Learning: In Astro-Han/pawwork, the locale normalization helpers in `packages/app/src/context/language.tsx` (`normalizeLocale`) and `packages/desktop-electron/src/renderer/i18n/index.ts` (`parseLocale`) are intentionally kept separate and have different fallback shapes: `normalizeLocale` always returns a concrete `Locale` (falling back to `"en"`), while `parseLocale` returns `Locale | null` so the desktop shell can decide whether to fall back to browser detection. Do not suggest extracting a shared normalization helper across these two runtimes.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`

Applied to files:

  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/windows.ts
📚 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/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.

Applied to files:

  • packages/desktop-electron/src/preload/index.ts
📚 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/index.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/main/menu-template.ts
📚 Learning: 2026-04-24T13:03:10.835Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 222
File: packages/desktop-electron/src/main/index.ts:686-692
Timestamp: 2026-04-24T13:03:10.835Z
Learning: In `packages/desktop-electron/src/main/index.ts`, the `checkForUpdates()` function intentionally uses recursive self-invocation for the "Retry" path in the update-check failure dialog. This is mandated by the v5.2 design spec (`#213`): "Await the retry recursion and log any rejection so support can see repeated failures." Because retries are user-paced (require a button click), all prior async frames have already unwound through microtasks before the next attempt, so there is no stack/frame-nesting problem in practice. Do not suggest refactoring this to an iterative loop.

Applied to files:

  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
📚 Learning: 2026-04-20T17:03:40.214Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 73
File: packages/opencode/src/cli/cmd/tui/context/sync.tsx:486-489
Timestamp: 2026-04-20T17:03:40.214Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/cli/cmd/tui/context/sync.tsx`), `sync.ready` returning `true` when `process.env.OPENCODE_FAST_BOOT` is set is intentional. The plugin-facing data properties `state.config` (initialized to `{}`) and `state.provider` (initialized to `[]`) expose safe-empty defaults, so they are safe to access before bootstrap completes. Do not flag these as needing null-guards or conditional patterns to match `vcs` — the difference is intentional because `vcs` starts as `undefined` while the others have initialized defaults. Changing this would alter the plugin API contract without a concrete failing case.

Applied to files:

  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-24T06:50:02.712Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.

Applied to files:

  • packages/desktop-electron/src/main/index.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/menu.test.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.

Applied to files:

  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/app/src/components/about-modal.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/about-modal.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `modKey` from utils for cross-platform keyboard shortcuts (Meta on Mac, Control on Linux/Windows)

Applied to files:

  • packages/desktop-electron/src/main/menu-template.ts
🔇 Additional comments (10)
packages/app/src/i18n/zh.ts (1)

1004-1008: About zh localization entries look correct.

The new About keys are complete and consistent with the existing dictionary style.

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

1033-1037: About en localization entries are complete and aligned with the new modal.

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

59-60: About IPC/menu integration is wired correctly in bootstrap.

Direct registration plus menu-trigger plumbing looks consistent and cohesive.

Based on learnings: In Astro-Han/pawwork (packages/desktop-electron/src/main/), the bootstrap entry (index.ts) should directly call each module’s exported register*Ipc() function.

Also applies to: 480-481, 537-537

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

2-14: Test updates correctly follow the macOS-specific menu builder migration.

Mock deps and template usage are consistent with the new menu-template contract.

Also applies to: 127-227

packages/desktop-electron/src/preload/index.ts (1)

83-90: About preload bridge implementation is solid.

getAboutInfo and onAboutOpen are correctly exposed with proper listener teardown.

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

111-113: Win32 loading-window menu handling looks correct for the native frame transition.

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

83-85: Renderer-side About wiring is correctly integrated.

The window.api typings and AboutModal placement align with the new preload/event flow.

Also applies to: 240-243

packages/desktop-electron/src/main/menu-template.test.ts (1)

45-55: Nice regression guard for cross-platform accelerators.

The recursive accelerator scan should catch future regressions back to bare Cmd/Option tokens on Windows.

packages/app/src/components/about-modal.tsx (1)

44-59: Good failure handling around the About bridge.

The try/catch plus early return keeps the shared dialog stack from opening an empty panel when the preload API is missing or about:get-info rejects.

packages/desktop-electron/src/preload/types.ts (1)

25-30: 🧹 Nitpick | 🔵 Trivial

Move AboutInfo into a shared IPC contract.

This shape is re-declared here, in packages/desktop-electron/src/main/ipc/about.ts, and in packages/app/src/components/about-modal.tsx. The next field change can drift across main/preload/renderer and only fail at runtime. Importing one shared contract from all three layers would make this boundary much safer.

⛔ Skipped due to learnings
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/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 99
File: packages/desktop-electron/src/renderer/i18n/index.ts:30-35
Timestamp: 2026-04-21T13:45:45.149Z
Learning: In Astro-Han/pawwork, the locale normalization helpers in `packages/app/src/context/language.tsx` (`normalizeLocale`) and `packages/desktop-electron/src/renderer/i18n/index.ts` (`parseLocale`) are intentionally kept separate and have different fallback shapes: `normalizeLocale` always returns a concrete `Locale` (falling back to `"en"`), while `parseLocale` returns `Locale | null` so the desktop shell can decide whether to fall back to browser detection. Do not suggest extracting a shared normalization helper across these two runtimes.

Comment thread .github/workflows/build.yml
Comment thread packages/desktop-electron/src/main/ipc/about.ts
@Astro-Han Astro-Han force-pushed the worktree-feat-windows-chrome branch from e477651 to f75ef44 Compare April 25, 2026 09:41

@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

🤖 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/desktop-electron/src/main/menu-template.ts`:
- Around line 178-189: Extract shared submenu builders for repeated menu blocks:
create helper functions like buildEditSubmenu(roleLabel) and
buildGenericSubmenu(roleLabelList, roleLabel) (or separate
buildViewSubmenu/buildGoSubmenu) that return the submenu array used by the menu
templates, then replace the duplicated submenu literals in the "edit" menu
(label "edit") and the similar "view" and "go" menus with calls to those
helpers; ensure the helper accepts the roleLabel function and any
platform-specific differences (e.g., terminal accelerator) as parameters so
macOS and Windows templates can pass a small platform override while keeping a
single source of truth for the common items.
🪄 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: 5c46f47b-afb0-4959-9efe-88d6440cfe93

📥 Commits

Reviewing files that changed from the base of the PR and between 9513c4a and f75ef44.

📒 Files selected for processing (17)
  • .github/workflows/build.yml
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/electron.vite.config.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/opencode/test/github/build-workflow.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). (12)
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-app
  • GitHub Check: typecheck
  • GitHub Check: unit-desktop
  • GitHub Check: unit-opencode
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (4)
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/env.d.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu-template.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/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/app.tsx
  • packages/app/src/components/about-modal.tsx
packages/opencode/**/*.ts

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

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
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
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/test/github/build-workflow.test.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/github/build-workflow.test.ts
🧠 Learnings (24)
📓 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:56.086Z
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: 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/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.
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: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.
📚 Learning: 2026-04-24T17:12:23.931Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/desktop-electron/electron-builder.config.ts:14-18
Timestamp: 2026-04-24T17:12:23.931Z
Learning: In Astro-Han/pawwork, the `localizedMacDisplayNameByChannel` map in `packages/desktop-electron/electron-builder.config.ts` is intentionally kept separate from `localizedAppDisplayName` in `packages/desktop-electron/src/main/app-display-name.ts`. The former is a build-time packaging helper; the latter is a runtime UI helper that localizes the current app name by locale. Coupling them would introduce a build-time dependency on runtime main logic. Do not suggest deduplicating or sharing this mapping — the explicit local table is covered by focused regression tests in `electron-builder-app-update.test.ts`.

Applied to files:

  • .github/workflows/build.yml
  • packages/desktop-electron/electron.vite.config.ts
  • packages/desktop-electron/src/main/env.d.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/app/src/app.tsx
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
📚 Learning: 2026-04-24T13:03:10.835Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 222
File: packages/desktop-electron/src/main/index.ts:686-692
Timestamp: 2026-04-24T13:03:10.835Z
Learning: In `packages/desktop-electron/src/main/index.ts`, the `checkForUpdates()` function intentionally uses recursive self-invocation for the "Retry" path in the update-check failure dialog. This is mandated by the v5.2 design spec (`#213`): "Await the retry recursion and log any rejection so support can see repeated failures." Because retries are user-paced (require a button click), all prior async frames have already unwound through microtasks before the next attempt, so there is no stack/frame-nesting problem in practice. Do not suggest refactoring this to an iterative loop.

Applied to files:

  • .github/workflows/build.yml
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.

Applied to files:

  • .github/workflows/build.yml
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
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:

  • .github/workflows/build.yml
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-20T17:03:40.214Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 73
File: packages/opencode/src/cli/cmd/tui/context/sync.tsx:486-489
Timestamp: 2026-04-20T17:03:40.214Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/cli/cmd/tui/context/sync.tsx`), `sync.ready` returning `true` when `process.env.OPENCODE_FAST_BOOT` is set is intentional. The plugin-facing data properties `state.config` (initialized to `{}`) and `state.provider` (initialized to `[]`) expose safe-empty defaults, so they are safe to access before bootstrap completes. Do not flag these as needing null-guards or conditional patterns to match `vcs` — the difference is intentional because `vcs` starts as `undefined` while the others have initialized defaults. Changing this would alter the plugin API contract without a concrete failing case.

Applied to files:

  • .github/workflows/build.yml
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/app/src/components/about-modal.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:

  • .github/workflows/build.yml
  • packages/desktop-electron/electron.vite.config.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/app/src/app.tsx
  • packages/desktop-electron/src/main/windows.ts
  • packages/app/src/components/about-modal.tsx
  • packages/desktop-electron/src/preload/types.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • .github/workflows/build.yml
  • packages/opencode/test/github/build-workflow.test.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
📚 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/env.d.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
  • packages/desktop-electron/src/main/ipc/about.ts
  • packages/desktop-electron/src/main/windows.ts
  • packages/desktop-electron/src/main/menu.ts
  • packages/desktop-electron/src/main/menu.test.ts
  • packages/desktop-electron/src/main/menu-template.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-23T17:02:40.117Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:40.117Z
Learning: In Astro-Han/pawwork, Stylelint is NOT configured in the project. Per AGENTS.md, only linting (not formatting) is used, and the Biome formatter is explicitly disabled. Do NOT raise Stylelint-based review comments (e.g., `declaration-empty-line-before`) in this repo, as they are false positives from the static analysis environment and not enforced by the project toolchain.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/app/src/components/about-modal.tsx
📚 Learning: 2026-04-21T13:45:45.149Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 99
File: packages/desktop-electron/src/renderer/i18n/index.ts:30-35
Timestamp: 2026-04-21T13:45:45.149Z
Learning: In Astro-Han/pawwork, the locale normalization helpers in `packages/app/src/context/language.tsx` (`normalizeLocale`) and `packages/desktop-electron/src/renderer/i18n/index.ts` (`parseLocale`) are intentionally kept separate and have different fallback shapes: `normalizeLocale` always returns a concrete `Locale` (falling back to `"en"`), while `parseLocale` returns `Locale | null` so the desktop shell can decide whether to fall back to browser detection. Do not suggest extracting a shared normalization helper across these two runtimes.

Applied to files:

  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/menu-template.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : When using the `tmpdir` function with git repository support, pass the `git: true` option to initialize a git repo with a root commit.

Applied to files:

  • packages/opencode/test/github/build-workflow.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/github/build-workflow.test.ts
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`

Applied to files:

  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-24T06:50:02.712Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.

Applied to files:

  • packages/desktop-electron/src/main/index.ts
  • packages/app/src/components/about-modal.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/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.

Applied to files:

  • packages/desktop-electron/src/preload/index.ts
📚 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/about-modal.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `modKey` from utils for cross-platform keyboard shortcuts (Meta on Mac, Control on Linux/Windows)

Applied to files:

  • packages/desktop-electron/src/main/menu-template.ts
🔇 Additional comments (12)
packages/desktop-electron/src/main/windows.ts (1)

111-113: Verify the Electron behavior: does Menu.setApplicationMenu() apply the menu to the loading window despite prior removeMenu() call.

The bootstrap sequence confirms that on Windows, win.removeMenu() is called when the loading window is created (lines 111–113), but Menu.setApplicationMenu() is invoked later via wireMenu() in openMainWindow() (line 371) while the loading window is still open (closed only at line 441).

The concern depends on Electron's behavior: whether setApplicationMenu() reapplies the menu to windows that previously had removeMenu() called on them. If it does, the loading window should be re-cleared after menu setup. If Electron respects the explicit removeMenu() call, no additional action is needed.

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

59-60: About IPC bootstrap and menu wiring are correctly integrated.

The new About path is coherently wired end-to-end from menu action to main-process IPC registration.

Based on learnings: packages/desktop-electron/src/main/index.ts is the intended bootstrap point that directly calls each feature module’s exported register*Ipc() function.

Also applies to: 480-481, 537-537

packages/desktop-electron/src/preload/index.ts (1)

83-90: Preload About bridge implementation is clean and safe.

The API surface and unsubscribe behavior are correct and match expected preload event patterns.

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

45-46: About modal app-level integration is correctly placed.

Typing and provider placement are aligned so the modal can consume the shared dialog stack safely.

Also applies to: 83-85, 240-243

packages/desktop-electron/src/main/menu-template.test.ts (1)

22-68: Menu template regression coverage is strong for this change set.

The new tests validate platform menu structure, About presence, and accelerator token correctness in a focused way.

packages/desktop-electron/src/preload/types.ts (1)

25-30: Preload API type contract is consistent and well-scoped.

AboutInfo and the new About methods are clearly modeled and align with the implemented IPC bridge.

Also applies to: 97-98

packages/app/src/components/about-modal.tsx (1)

40-60: About modal event flow and failure handling are solid.

Subscription lifecycle, guarded info fetch, and shared dialog-stack usage are all implemented correctly.

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

17-35: Platform menu split and click rebinding are implemented correctly.

The template adaptation and platform gating preserve expected behavior while enabling Windows menu support cleanly.

Also applies to: 38-60

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

15-33: About IPC module is cohesive and correctly scoped.

Handler registration and app-shell window targeting are well implemented for cross-platform About triggering.

packages/desktop-electron/src/main/menu-template.ts (3)

1-25: LGTM!

The type updates correctly model Electron's menu click handler signature and properly extend MenuTemplateDeps with the new triggerAbout dependency for the cross-platform About flow.


34-146: LGTM!

The function rename to buildMacosMenuTemplate is appropriate, and the accelerator migration to CmdOrCtrl/Alt patterns correctly enables cross-platform shortcut resolution. The existing `Ctrl+`` for terminal toggle on macOS follows VS Code convention.


148-233: LGTM!

The Windows menu template correctly implements Windows conventions: About in Help (with triggerAbout wiring), Quit in File, and no macOS-specific items like "front" or the app-name menu. The accelerators using CmdOrCtrl/Alt patterns will resolve correctly on Windows.

Comment thread packages/desktop-electron/src/main/menu-template.ts
@Astro-Han Astro-Han merged commit a6e2aa1 into dev Apr 25, 2026
23 checks passed
@Astro-Han Astro-Han deleted the worktree-feat-windows-chrome branch April 26, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request P1 High priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions windows Windows-specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant