Skip to content

feat(app): unify composer across home and session per #181#208

Merged
Astro-Han merged 6 commits into
devfrom
feat/composer-unify
Apr 24, 2026
Merged

feat(app): unify composer across home and session per #181#208
Astro-Han merged 6 commits into
devfrom
feat/composer-unify

Conversation

@Astro-Han

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

Copy link
Copy Markdown
Owner

Summary

  • Flatten Session composer: remove DockTray overlay, Agent Select control, and standalone shell-mode label. Session and Home now share one bottom bar; homeMode narrows to "show WorkspaceChip or not".
  • PawWork visual language: brand-orange circular SendButton (#E56A2E), localized Variant chip labels (new variant.* i18n group + translateVariant typed helper with whitelist + defensive ?? key fallback), WorkspaceChip popover refined with folder icon, per-row hover, active check.
  • Session TopBar: SessionHeader already portals <project> / <session> into the titlebar center slot; added "click to open in Finder/Explorer" hint to the project button's title attr (the duplicate PawworkBreadcrumb I first shipped was removed after discovering SessionHeader's existing portal).
  • Unified chip style: Model / Variant / Workspace all use the same border, radius, height, text size. Model chip has fixed w-48 + centered name so long model IDs truncate instead of pushing siblings off-screen.
  • Popover parity: Model, Variant, Workspace all use Popover with placement=bottom-start + auto-flip; items use the same py-1.5 px-2 text-14-medium + surface-raised-base-hover bg as the Model List rows.

Resolves #181 (per design at #181 (comment)).

Test plan

  • bun --cwd packages/app run typecheck passes
  • bun --cwd packages/app run test:unit — 564 pass, 0 fail
  • bun --cwd packages/app run test:e2e:local:smoke — 16 pass, 1 skipped, 0 fail
  • Manual: home composer single-row bar, brand orange send, workspace chip folder icon, popover hover parity
  • Manual: session composer matches home (no tray, no agent, Workspace hidden); titlebar breadcrumb; @agent popover

Out of scope

  • Default sidebars collapsed for new users — reverted; breaks unrelated smoke specs that expect open panels. Will do as a separate PR with proper test updates.

Summary by CodeRabbit

  • New Features

    • Composer: "@" agent suggestions, shell-mode indicator, and a dedicated send/stop button.
  • Improvements

    • Unified bottom composer bar; compact variant selector with localized labels (EN/ZH).
    • Workspace chip/popover accessibility and behavior refined; model popover placement adjusted.
    • UI polish: updated dock surface shadows, larger rounded corners, and small layout width tweaks.
  • Tests

    • Many new and updated E2E and unit tests covering composer, workspace, variants, and send button.

@Astro-Han Astro-Han added enhancement New feature or request P1 High priority app Application behavior and product flows ui Design system and user interface labels Apr 24, 2026
@coderabbitai

coderabbitai Bot commented Apr 24, 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

Reworks the composer prompt UI: replaces variant Select with a Popover-driven radio menu, introduces a SendButton component and tests, changes workspace-chip loading/semantics and popover roles, adds variant i18n keys and helpers, many Playwright E2E tests, minor style/icon tweaks, and exports a language Dictionary type.

Changes

Cohort / File(s) Summary
E2E Tests
packages/app/e2e/app/a11y-chip.spec.ts, packages/app/e2e/app/composer-agent-syntax.spec.ts, packages/app/e2e/app/composer-parity.spec.ts, packages/app/e2e/app/composer-send.spec.ts, packages/app/e2e/app/composer-shell-mode.spec.ts, packages/app/e2e/app/composer-variant-locale.spec.ts, packages/app/e2e/app/composer-workspace-chip.spec.ts, packages/app/e2e/app/home.spec.ts, packages/app/e2e/app/session.spec.ts
Adds and updates many Playwright tests covering accessibility, at-agent syntax, parity between home/session composers, send-button enabling, shell mode, localized variant labels, workspace chip interactions, and additional smoke checks.
PromptInput & SendButton
packages/app/src/components/prompt-input.tsx, packages/app/src/components/prompt-input/send-button.tsx, packages/app/src/components/prompt-input/send-button.test.tsx, packages/app/src/components/prompt-input/slash-popover.tsx
Removes Select in favor of Popover radio menu for variants, consolidates bottom composer controls into a unified bar, adds exported SendButton (and tests), and marks slash/at popovers with data-component.
Variant Localization & Helper
packages/app/src/components/prompt-input/variant-label.ts, packages/app/src/components/prompt-input/variant-label.test.ts, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Adds KNOWN_VARIANTS and translateVariant (with fallbacks) plus seven variant.* translation keys for en/zh and corresponding unit tests.
Workspace Chip & Helpers
packages/app/src/components/prompt-input/workspace-chip.tsx, packages/app/src/components/prompt-input/workspace-chip-helpers.ts, packages/app/src/components/prompt-input/workspace-chip.test.ts
Changes workspace choices to typed { path, branch? }, always-loads workspace data (no on-open fetch), switches popover semantics from listbox/option to menu/menuitemradio, adjusts trigger attributes/icons and selection comparison, and updates tests for object-shaped choices.
Session Header & Pages
packages/app/src/components/session/session-header.tsx, packages/app/src/pages/session/composer/session-composer-region.tsx
Adjusts project-directory button title to include localized "open" when eligible; tweaks home composer max-width class.
Context & Types
packages/app/src/context/language.tsx
Exports the Dictionary type alias for external use.
UI Styling & Icons
packages/ui/src/components/dock-surface.css, packages/ui/src/components/icon.tsx, packages/app/src/components/dialog-select-model.tsx
Updates dock-surface shadow/radii, adds stop-square icon, and changes model selector popover placement to bottom-start.
Session/New view styling
packages/app/src/components/session/session-new-view.tsx
Adjusts skill card rounding and increases composer section max width.
Test harness inventory
packages/opencode/test/config/e2e-smoke-tagging.test.ts
Adds two expected @smoke test titles to the smoke-test inventory.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Composer as PromptInput
    participant Variant as VariantPopover
    participant Workspace as WorkspaceChip
    participant Send as SendButton

    User->>Composer: Focus / type (e.g., "@", "!", or prompt text)
    Composer->>Variant: open variant popover (role="menu")
    Composer->>Workspace: open workspace popover (data loaded immediately)
    User->>Variant: select menuitemradio option
    Variant-->>Composer: set variant (map "default" -> undefined), close popover, restore focus
    Composer->>Send: enable SendButton when prompt content present
    User->>Send: submit (click/enter)
    Send-->>Composer: emit submit/form submit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

"I hopped and nudged the popover bright,
SendButton gleamed like moonlit white,
Variants sing in two-tongue cheer,
Workspace paths now crisp and clear—
Tests clap paws, the meadow's right." 🐇

🚥 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 pull request title clearly and concisely summarizes the main change: unifying the composer across home and session views per issue #181.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, motivation, related issue, test verification, and manual checks; all required template sections are present and completed.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/composer-unify

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

@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 unifies the composer UI across home and session views, introducing a single-row bar structure. Key changes include the introduction of a dedicated SendButton component, refactoring variant and model selectors to use popovers, and adding comprehensive E2E tests for composer functionality. Feedback focuses on improving maintainability by replacing hardcoded hex colors with theme tokens, reducing the use of the !important modifier in Tailwind classes, and avoiding as any type assertions to maintain type safety.

Comment thread packages/app/src/components/prompt-input/send-button.tsx
Comment thread packages/app/src/components/prompt-input/send-button.tsx
Comment thread packages/app/src/components/prompt-input.tsx Outdated
Comment thread packages/app/src/components/prompt-input.tsx
Comment thread packages/app/src/components/prompt-input/workspace-chip.tsx
Adds seven thinking-effort variant labels (none, minimal, low, medium,
high, xhigh, max) to both zh and en dictionaries under variant.*.
Introduces translateVariant(t, key) in components/prompt-input/variant-label.ts
with a whitelist + defensive '?? key' fallback to let the Variant chip
surface dynamic effort keys through the typed i18n pipeline. Exports
Dictionary type from context/language.tsx so the helper can reference
keyof Dictionary without weakening the contract.
Brand-orange (#E56A2E) circular send button, 32x32, transitions
smoothly to disabled neutral when input is empty or shell-mode
is active. Uses inline Icon color style to override the default
icon-base cascade. New stop-square icon added to @opencode-ai/ui
so the stopping-state shows a plain filled square instead of the
circle+square (avoiding the 'icon-in-circle' look inside the
already-round button).
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch from 6136b1c to 92875df Compare April 24, 2026 05:03

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

Caution

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

⚠️ Outside diff range comments (1)
packages/app/src/components/prompt-input.tsx (1)

1489-1496: ⚠️ Potential issue | 🟠 Major

Don't refocus the editor when clicking the new chip controls.

The onMouseDown guard still only exempts attach/send. With the new model / variant / workspace controls mounted in this same overlay, clicking them will also hit this handler and can steal focus back to the editor before those controls manage their own focus. That makes the popovers brittle for keyboard users. Please widen the guard to all interactive descendants here, not just the two legacy actions.

Suggested fix
-            if (target.closest('[data-action="prompt-attach"], [data-action="prompt-submit"]')) {
+            if (target.closest('button, a, input, select, textarea, [role="button"], [data-action]')) {
               return
             }

Also applies to: 1596-1602

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

In `@packages/app/src/components/prompt-input.tsx` around lines 1489 - 1496, The
onMouseDown handler on the prompt overlay (the arrow function using
editorRef?.focus()) currently only ignores clicks on elements matching
'[data-action="prompt-attach"], [data-action="prompt-submit"]' and thus still
steals focus when clicking the new chip controls; update this guard in the
onMouseDown handler(s) (the anonymous function referencing editorRef?.focus())
to also detect and ignore clicks on any interactive descendants—e.g. elements
matching button, [role="button"], input, select, textarea, a[href], [tabindex],
or any general '[data-action]' attribute (or a combination that covers your chip
controls like '[data-action^="prompt-"]')—so the handler returns early for all
interactive controls and only focuses the editor for non-interactive clicks.
🤖 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/e2e/app/a11y-chip.spec.ts`:
- Around line 26-32: The test currently allows an empty targetLabel which makes
expect(chip).toContainText(targetLabel) always pass; before clicking the target,
assert that targetLabel (from target.locator("span").first().textContent()) is
non-empty (e.g. expect(targetLabel).not.toBe("") or throw if blank) so the test
fails when label extraction failed, then proceed to click and assert chip
contains that label using the existing expect(chip).toContainText(targetLabel).

In `@packages/app/e2e/app/composer-agent-syntax.spec.ts`:
- Around line 18-21: After typing "@" with page.keyboard.type in the test, wait
for the mention popover/options to be mounted before sending the Enter key to
avoid flakiness; replace the immediate page.keyboard.press("Enter") with an
explicit wait using Playwright expectations (e.g.,
expect(page.locator('<mention-popover-selector>')).toBeVisible() or
expect.poll(...) for the mention options) and only then call
page.keyboard.press("Enter") so the popover is present when the selection
occurs.

In `@packages/app/e2e/app/composer-parity.spec.ts`:
- Around line 13-30: The test compares sorted arrays (homeActions and
sessionActions) which can fail if duplicate data-action attributes exist;
instead dedupe to compare unique action sets: when building homeActions and
sessionActions from homeBar and sessionBar (selectors using "[data-action]" and
sessionComposerDockSelector), convert the mapped lists to a Set (or filter
uniques) and then compare their contents (e.g., ensure sessionActions set equals
homeActions set minus "prompt-workspace"); update the expectation to compare
sets of unique values rather than sorted arrays while keeping the
withSession/gotoSession flow the same.

In `@packages/app/e2e/app/composer-variant-locale.spec.ts`:
- Line 24: Rename the test constants zhLabels and enLabels to
SCREAMING_SNAKE_CASE (e.g., ZH_LABELS and EN_LABELS) across the spec, updating
every usage and any imports or references (look for symbol names zhLabels and
enLabels in composer-variant-locale.spec.ts) so the test follows the repo
convention for test constants.
- Around line 25-29: The current assertion only checks matched > 0 but should
ensure every rendered variant option is localized: update the test that calls
countMatches(page, zhLabels) (and the similar assertion around lines 39-40) to
compare the number of matched labels (matched) against the total number of
rendered variant options (e.g., query the same page for the variant option
elements and get their count) and assert matched === totalOptions (or
expect(matched).toBe(totalOptions)); if needed, enhance countMatches to return
both matched and total so the assertion can verify all rendered labels belong to
zhLabels.

In `@packages/app/e2e/app/composer-workspace-chip.spec.ts`:
- Around line 42-44: The test currently dismisses the popover by calling
page.mouse.click(10, 10) after awaiting chip.click(), which is flaky; replace
that mouse coordinate click with a deterministic click on a stable locator
outside the popover/trigger (e.g., a header, footer, or a specific container) so
the dismiss action is reliable, i.e., after await chip.click() call
page.locator('a-stable-outside-selector').click() (or another stable locator)
and then assert await expect(page.getByRole("listbox")).toHaveCount(0).

In `@packages/app/e2e/app/session.spec.ts`:
- Line 18: The test title string in the test(...) call for the "@smoke session
composer matches home structure without DockTray or Agent control" spec uses
uppercase words; rename that string to all lowercase (e.g., "@smoke session
composer matches home structure without docktray or agent control") to follow
the suite convention and E2E naming rule, updating the test(...) invocation
accordingly.

In `@packages/app/src/components/prompt-input.tsx`:
- Around line 1218-1269: The listbox markup inside Popover uses role="listbox"
and role="option" but lacks keyboard focus management; either implement full
listbox keyboard support or remove ARIA roles. To implement: add a focusedIndex
signal (e.g., focusedIndex()), give each option a stable id and
tabindex={focusedIndex===i?0:-1} (roving tabindex) or set aria-activedescendant
on the listbox to the focused option id, add onKeyDown on the listbox to handle
ArrowUp/ArrowDown/Home/End to update focusedIndex and Enter/Space to invoke the
same selection logic used in the onClick (local.model.variant.set(...),
setVariantOpen(false), restoreFocus()), ensure aria-selected reflects active()
and move DOM focus into the listbox when opened; alternatively, remove
role="listbox" and role="option" from the container and option buttons to revert
to plain button semantics.

In `@packages/app/src/components/prompt-input/workspace-chip.tsx`:
- Around line 87-107: The active() memo currently compares raw workspace.path to
current(), which uses a normalized/encoded workspace key, causing mismatches;
update the createMemo that defines active to compare the same normalized key
used elsewhere (e.g., base64Encode(workspace.path) === current()) so
aria-selected, the check Icon render (Show when={active()}), and any visual
state all use the same normalized identifier; keep the rest of the button logic
(navigate(`/${base64Encode(workspace.path)}/session`), setOpen(false),
getFilename(workspace.path)) unchanged.

---

Outside diff comments:
In `@packages/app/src/components/prompt-input.tsx`:
- Around line 1489-1496: The onMouseDown handler on the prompt overlay (the
arrow function using editorRef?.focus()) currently only ignores clicks on
elements matching '[data-action="prompt-attach"], [data-action="prompt-submit"]'
and thus still steals focus when clicking the new chip controls; update this
guard in the onMouseDown handler(s) (the anonymous function referencing
editorRef?.focus()) to also detect and ignore clicks on any interactive
descendants—e.g. elements matching button, [role="button"], input, select,
textarea, a[href], [tabindex], or any general '[data-action]' attribute (or a
combination that covers your chip controls like '[data-action^="prompt-"]')—so
the handler returns early for all interactive controls and only focuses the
editor for non-interactive clicks.
🪄 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: a21525dd-4216-46c1-bec8-506038dd90af

📥 Commits

Reviewing files that changed from the base of the PR and between a2e7a3a and 6136b1c.

📒 Files selected for processing (26)
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/context/language.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/ui/src/components/dock-surface.css
  • packages/ui/src/components/icon.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-opencode
  • GitHub Check: unit-desktop
  • 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/components/prompt-input/slash-popover.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/src/context/language.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
packages/app/e2e/**/*.spec.ts

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

packages/app/e2e/**/*.spec.ts: Import test utilities from ../fixtures instead of @playwright/test
Test files should be named with the pattern feature-name.spec.ts
Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')
Use camelCase for variable names in tests
Use SCREAMING_SNAKE_CASE for constants in tests
Use fixture-managed cleanup with withSession(sdk, title, callback) for temporary sessions instead of calling sdk.session.delete(...) directly
Prefer the project fixture for tests that need a dedicated project with LLM mocking
Use data-component, data-action, or semantic roles for selectors instead of CSS class names or IDs
Use modKey from utils for cross-platform keyboard shortcuts (Meta on Mac, Control on Linux/Windows)
In terminal tests, type through the browser using runTerminal() and waitTerminalReady() instead of writing to the PTY through the SDK
Never use wall-clock waits like page.waitForTimeout(...) to make a test pass
Wait on observable state with expect(...), expect.poll(...), or existing helpers instead of assuming work is finished after an action
Use locator assertions like toBeVisible(), toHaveCount(0), and toHaveAttribute(...) for normal UI state verification
Use expect.poll(...) for probing mock or backend state rather than transient DOM visibility
Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests
Use direct locators when the interaction is simple and a helper would not add clarity
When validating routing, assert against canonical or resolved workspace slugs using shared helpers from ../actions to account for Windows canonicalization
Test one feature per test file
Call project.trackSession(sessionID, directory?) and project.trackDirectory(directory) for any resources created outside the fixture so teardown can clean them up

Files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
🧠 Learnings (39)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/src/context/language.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

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

Applied to files:

  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/context/language.tsx
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests under packages/app/e2e, do not manually call `project.trackSession(sessionID)` when you obtain a `sessionID` via `project.prompt(text)`. The `project.prompt()` implementation already registers `trackSession(next.sessionID, active.directory)` automatically after the prompt submission is observed and the active session is resolved, so calling `project.trackSession(sessionID)` again will create duplicate session ownership/teardown handling.

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 : Test files should be named with the pattern `feature-name.spec.ts`

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : Test one feature per test file

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : In terminal tests, type through the browser using `runTerminal()` and `waitTerminalReady()` instead of writing to the PTY through the SDK

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.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/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/send-button.test.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 lowercase, descriptive test names (e.g., 'sidebar can be toggled')

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : Prefer the `project` fixture for tests that need a dedicated project with LLM mocking

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
📚 Learning: 2026-04-23T15:25:27.182Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/e2e/sidebar/sidebar-leading-slot.spec.ts:5-55
Timestamp: 2026-04-23T15:25:27.182Z
Learning: In Astro-Han/pawwork E2E tests (e.g., *.spec.ts under packages/app/e2e), reaching a real "running" session state is not achievable with the bare `sdk` fixture. Use the `project` fixture (to bootstrap the model) and orchestrate the transition with `llm.wait(1)`; even if you set `agent: "build"` and a `system` prompt via `sdk.session.promptAsync`, the current test infrastructure does not trigger an actual LLM call, so it won’t simulate "running" cheaply. Review any attempt to mock/force "running" using only `sdk` as likely ineffective unless it also uses `project` + `llm.wait(1)`.

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
📚 Learning: 2026-04-24T00:02:50.599Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 203
File: packages/app/e2e/sidebar/sidebar-session-links.spec.ts:34-55
Timestamp: 2026-04-24T00:02:50.599Z
Learning: For Astro-Han/pawwork E2E tests under packages/app/e2e/**/*.spec.ts, do not call project.trackDirectory() or project.trackSession() before project.open() has run. The project fixture throws until open() initializes internal state. Use this ordering pattern: (1) call project.trackSession(sessionID) inside the beforeGoto callback (where state is already available), (2) call project.trackDirectory(directory) and any cross-workspace tracking like project.trackSession(id, directory) immediately after project.open() returns, and (3) if you create any resources before open() that cannot yet be tracked via the fixture, ensure you clean them up explicitly in finally blocks (e.g., cleanupSession / cleanupTestProject).

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 camelCase for variable names in tests

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 SCREAMING_SNAKE_CASE for constants in tests

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 `expect.poll(...)` for probing mock or backend state rather than transient DOM visibility

Applied to files:

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

Applied to files:

  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/prompt-input/workspace-chip.test.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/session/session-header.tsx
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

  • packages/app/src/components/session/session-header.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 : When validating routing, assert against canonical or resolved workspace slugs using shared helpers from `../actions` to account for Windows canonicalization

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 fixture-managed cleanup with `withSession(sdk, title, callback)` for temporary sessions instead of calling `sdk.session.delete(...)` directly

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : Call `project.trackSession(sessionID, directory?)` and `project.trackDirectory(directory)` for any resources created outside the fixture so teardown can clean them up

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 direct locators when the interaction is simple and a helper would not add clarity

Applied to files:

  • packages/app/e2e/app/a11y-chip.spec.ts
📚 Learning: 2026-04-23T08:51:04.230Z
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:04.230Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/util/filesystem.ts`), the `Filesystem` utility does NOT expose a `remove` or `unlink` helper. The established repository pattern for auth.json teardown in tests (e.g. `provider.test.ts`, `amazon-bedrock.test.ts`, `workspace-adaptor.test.ts`) is to combine `Filesystem.write` with `node:fs/promises unlink`. Do not flag this mixed usage as inconsistent — it is the correct and intentional pattern.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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} : 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/app/src/components/prompt-input/workspace-chip.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} : 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.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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/app/src/components/prompt-input/workspace-chip.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 a test needs instance-local state, prefer `provideTmpdirInstance(...)` or `provideInstance(...)` over manual `Instance.provide(...)` inside Promise-style tests.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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} : 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.

Applied to files:

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

Applied to files:

  • packages/ui/src/components/dock-surface.css
📚 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/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations

Applied to files:

  • packages/app/src/components/prompt-input/send-button.test.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} : 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`.

Applied to files:

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

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/app/src/components/prompt-input.tsx
🔇 Additional comments (18)
packages/app/src/components/session/session-header.tsx (1)

98-102: Localized title behavior is correct and state-aware.

The conditional title string cleanly preserves path visibility while adding a contextual “open in …” hint only when the action is available.

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

159-159: Looks good — width normalization is clean and scoped.

This keeps the home composer width explicit without affecting behavior.

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

156-156: Popover placement update is safe and consistent.

No functional concerns with this directional placement change.

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

40-40: Great selector hook addition.

The new data-component marker is a solid, non-fragile contract for UI tests.

packages/ui/src/components/icon.tsx (1)

52-52: Icon registry extension looks correct.

stop-square is added in the expected shape and plugs into the existing Icon pipeline cleanly.

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

260-266: Variant localization additions are complete and consistent.

The new Chinese labels cover the full expected variant set.

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

241-247: English variant labels are correctly wired.

This is aligned with the variant-key translation path and looks good.

packages/ui/src/components/dock-surface.css (1)

3-6: Visual token updates are coherent.

Shadow and radius changes are internally consistent and scoped to dock surfaces as intended.

Also applies to: 15-15

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

12-12: Type export looks good.

Exporting Dictionary improves type reuse without changing runtime behavior.

packages/app/e2e/app/composer-send.spec.ts (1)

4-17: Nice focused coverage for send enable/disable states.

The tests are clear and directly validate the intended submit behavior.

packages/app/e2e/app/home.spec.ts (1)

61-86: Good smoke coverage for unified home composer behavior.

This test validates the key structural and interaction expectations introduced by the composer unification.

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

33-70: Test updates correctly align with WorkspaceChoice object shape.

The new assertions now validate the actual contract (path required, branch optional).

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

48-82: Strong behavioral coverage for SendButton.

The suite checks the key contract points (type, data-action, icon state, and prop forwarding) well.

packages/app/src/components/prompt-input/variant-label.test.ts (1)

30-73: Comprehensive translation-path coverage looks solid.

This suite validates both locale mappings and fallback behavior thoroughly.

packages/app/e2e/app/composer-shell-mode.spec.ts (1)

4-20: Good shell-mode regression coverage.

The assertions for enter/exit shell mode plus dock-tray absence are clear and targeted.

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

16-56: Object-based choice migration is clean and consistent.

The WorkspaceChoice transition and path-based dedupe/insertion logic look correct.

packages/app/src/components/prompt-input/variant-label.ts (1)

3-14: Variant whitelist + fallback behavior is well-implemented.

The helper’s default/known/unknown branching is clear and test-backed.

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

13-33: SendButton extraction looks good.

Props, accessibility labeling, and stop/send icon switching are cleanly handled.

Comment thread packages/app/e2e/app/a11y-chip.spec.ts Outdated
Comment thread packages/app/e2e/app/composer-agent-syntax.spec.ts
Comment thread packages/app/e2e/app/composer-parity.spec.ts Outdated
Comment thread packages/app/e2e/app/composer-variant-locale.spec.ts Outdated
Comment thread packages/app/e2e/app/composer-variant-locale.spec.ts Outdated
Comment thread packages/app/e2e/app/composer-workspace-chip.spec.ts Outdated
Comment thread packages/app/e2e/app/session.spec.ts Outdated
Comment thread packages/app/src/components/prompt-input.tsx
Comment thread packages/app/src/components/prompt-input/workspace-chip.tsx Outdated

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed for release readiness. I found one current CI blocker and a couple of release-risk nits inline. The smoke inventory issue is already reproducing in unit-opencode.

Comment thread packages/app/e2e/app/home.spec.ts
Comment thread packages/app/e2e/app/session.spec.ts Outdated
Comment thread packages/app/src/components/prompt-input.tsx Outdated
Comment thread packages/app/e2e/app/composer-variant-locale.spec.ts Outdated
Comment thread packages/app/src/components/prompt-input/workspace-chip.tsx Outdated
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch from 40f9086 to ad234a2 Compare April 24, 2026 05:08

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

♻️ Duplicate comments (8)
packages/app/e2e/app/composer-agent-syntax.spec.ts (1)

18-21: ⚠️ Potential issue | 🟠 Major

Wait for the @ popover before sending Enter.

On Line 20, pressing Enter can race with popover mount, causing flaky insertion behavior.

🔧 Proposed fix
   await page.keyboard.type("@")
+  const popover = page.locator('[data-component="prompt-at-popover"]')
+  await expect(popover).toBeVisible()

   await page.keyboard.press("Enter")

As per coding guidelines "Wait on observable state with expect(...), expect.poll(...), or existing helpers instead of assuming work is finished after an action".

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

In `@packages/app/e2e/app/composer-agent-syntax.spec.ts` around lines 18 - 21,
After typing the "@" with page.keyboard.type("@"), wait for the mention popover
to be visible before calling page.keyboard.press("Enter") to avoid a race;
replace the blind Enter with an explicit wait using an observable assertion such
as expect(page.locator('<mention-popover-selector>')).toBeVisible() or
expect.poll(...) (or the project's existing helper) and only call
page.keyboard.press("Enter") once that expectation passes.
packages/app/e2e/app/a11y-chip.spec.ts (1)

26-32: ⚠️ Potential issue | 🟠 Major

Prevent false positives from empty workspace labels.

On Line 26, defaulting targetLabel to "" allows Line 32 (toContainText) to always pass when extraction fails.

🔧 Proposed fix
-  const targetLabel = (await target.locator("span").first().textContent())?.trim() ?? ""
+  const targetLabel = (await target.locator("span").first().textContent())?.trim()
+  expect(targetLabel, "workspace option label should be non-empty").toBeTruthy()
@@
-  await expect(chip).toContainText(targetLabel)
+  await expect(chip).toContainText(targetLabel!)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/a11y-chip.spec.ts` around lines 26 - 32, The test
currently defaults targetLabel to "" when extracting text from
target.locator("span"), which can produce a false-positive on the final
assertion chip.toContainText(targetLabel); update the test to fail when the
extraction returns null/empty by asserting that the label is non-empty (e.g.,
expect(targetLabel).not.toBe("") or throw if missing) before clicking, or use a
sentinel value instead of "" so chip.toContainText cannot trivially pass; adjust
references to targetLabel, target.locator("span"), and chip.toContainText
accordingly.
packages/app/e2e/app/session.spec.ts (1)

18-18: ⚠️ Potential issue | 🟡 Minor

Use a lowercase test title to match E2E conventions.

On Line 18, the title includes uppercase words; please keep it lowercase for consistency with suite naming rules.

✏️ Proposed fix
-test("@smoke session composer matches home structure without DockTray or Agent control", async ({
+test("@smoke session composer matches home structure without docktray or agent control", async ({

As per coding guidelines "Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')".

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

In `@packages/app/e2e/app/session.spec.ts` at line 18, Update the test title
string used in the test call that currently reads " `@smoke` session composer
matches home structure without DockTray or Agent control" to a lowercase,
descriptive title (e.g., " `@smoke` session composer matches home structure
without docktray or agent control") so it follows the E2E naming convention;
locate the test invocation by the test(...) call whose first argument is the
current title and replace the string with the lowercase variant while keeping
the same test identifier and body.
packages/app/e2e/app/composer-variant-locale.spec.ts (2)

24-25: 🛠️ Refactor suggestion | 🟠 Major

Rename test constants to SCREAMING_SNAKE_CASE.

zhLabels and enLabels should follow the repo’s test constant convention.

Proposed fix
-  const zhLabels = ["默认", "无", "极低", "低", "中", "高", "超高", "最高"]
-  const matched = await countMatches(page, zhLabels)
+  const ZH_LABELS = ["默认", "无", "极低", "低", "中", "高", "超高", "最高"]
+  const matched = await countMatches(page, ZH_LABELS)
@@
-  const enLabels = ["Default", "None", "Minimal", "Low", "Medium", "High", "Extra High", "Max"]
-  const matched = await countMatches(page, enLabels)
+  const EN_LABELS = ["Default", "None", "Minimal", "Low", "Medium", "High", "Extra High", "Max"]
+  const matched = await countMatches(page, EN_LABELS)

As per coding guidelines, packages/app/e2e/**/*.spec.ts: “Use SCREAMING_SNAKE_CASE for constants in tests”.

Also applies to: 38-39

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

In `@packages/app/e2e/app/composer-variant-locale.spec.ts` around lines 24 - 25,
Rename the test constants zhLabels and enLabels to SCREAMING_SNAKE_CASE (e.g.,
ZH_LABELS, EN_LABELS) in the composER-variant-locale.spec.ts file and update
every usage (such as the call to countMatches(page, zhLabels)) to reference the
new names; ensure the const declarations (ZH_LABELS, EN_LABELS) remain top-level
test constants so they follow the repository's test constant convention.

25-29: ⚠️ Potential issue | 🟡 Minor

Strengthen the locale assertion to cover all rendered options.

expect(matched).toBeGreaterThan(0) can pass even if some rendered variant options regress to raw/unlocalized labels.

Proposed fix
   const matched = await countMatches(page, ZH_LABELS)
-  expect(matched).toBeGreaterThan(0)
+  const totalOptions = await page.getByRole("option").count()
+  expect(totalOptions).toBeGreaterThan(0)
+  expect(matched).toBe(totalOptions)
@@
   const matched = await countMatches(page, EN_LABELS)
-  expect(matched).toBeGreaterThan(0)
+  const totalOptions = await page.getByRole("option").count()
+  expect(totalOptions).toBeGreaterThan(0)
+  expect(matched).toBe(totalOptions)

Also applies to: 39-40

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

In `@packages/app/e2e/app/composer-variant-locale.spec.ts` around lines 25 - 29,
The test currently only asserts that at least one option matched localized
labels via matched from countMatches(page, zhLabels), which misses partial
regressions; update the assertion to ensure all rendered options are localized
by comparing matched against the total number of rendered options: use the page
locator that selects variant option elements (the same selector used in
countMatches) to get totalOptions (e.g., via locator.count()), then replace
expect(matched).toBeGreaterThan(0) with expect(matched).toEqual(totalOptions) so
matched (from countMatches(page, zhLabels)) must equal the full count of
rendered options; apply the same change for the other occurrence at lines 39-40.
packages/app/e2e/app/composer-parity.spec.ts (1)

13-30: ⚠️ Potential issue | 🟡 Minor

Compare unique action sets, not sorted arrays.

Current parity logic can fail if duplicated data-action nodes exist in one view.

Proposed fix
-  const homeActions = (
+  const homeActions = new Set(
     await homeBar
       .locator("[data-action]")
       .evaluateAll((els) => els.map((e) => e.getAttribute("data-action")).filter((v): v is string => !!v))
-  ).sort()
+  )
@@
-    const sessionActions = (
+    const sessionActions = new Set(
       await sessionBar
         .locator("[data-action]")
         .evaluateAll((els) => els.map((e) => e.getAttribute("data-action")).filter((v): v is string => !!v))
-    ).sort()
+    )
@@
-    expect(sessionActions).toEqual(homeActions.filter((a) => a !== "prompt-workspace"))
+    expect([...sessionActions].sort()).toEqual(
+      [...homeActions].filter((a) => a !== "prompt-workspace").sort(),
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/composer-parity.spec.ts` around lines 13 - 30,
homeActions and sessionActions compare sorted arrays which can fail when there
are duplicate data-action nodes; deduplicate the lists before asserting parity.
In the block that builds homeActions and sessionActions (variables homeActions
and sessionActions) convert the evaluated arrays to unique sets (e.g.,
Array.from(new Set(...)) or compare Set objects) and then assert equality
between the unique action collections (replace the expect on raw sorted arrays
with an equality check on the deduplicated collections, while still excluding
"prompt-workspace" for the session comparison).
packages/app/e2e/app/composer-workspace-chip.spec.ts (1)

42-44: ⚠️ Potential issue | 🟡 Minor

Replace coordinate-based outside click with a deterministic target.

Line 43 clicks a hard-coded viewport point, which can hit different elements and make this test flaky.

Proposed fix
   await chip.click()
-  await page.mouse.click(10, 10)
+  await page.getByRole("textbox").first().click()
   await expect(page.getByRole("listbox")).toHaveCount(0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/composer-workspace-chip.spec.ts` around lines 42 - 44,
The test uses a hard-coded coordinate click (page.mouse.click(10, 10)) which is
brittle; replace it with a deterministic outside click target such as clicking a
known container or the document body via a locator (e.g., a header, main
container, or page.locator('body')) so the popover/listbox reliably closes;
update the sequence around chip.click and the assertion on
page.getByRole("listbox") to use that deterministic locator instead of
page.mouse.click(10, 10).
packages/app/src/components/prompt-input/workspace-chip.tsx (1)

87-107: ⚠️ Potential issue | 🟡 Minor

Normalize workspace path comparison for active state.

Using raw string equality for active() can mis-mark the selected workspace in path-equivalent cases, affecting aria-selected and check-icon rendering.

Proposed fix
+import { workspaceKey } from "@/pages/layout/helpers"
@@
-              const active = createMemo(() => workspace.path === current())
+              const active = createMemo(() => {
+                const currentPath = current()
+                return !!currentPath && workspaceKey(workspace.path) === workspaceKey(currentPath)
+              })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/components/prompt-input/workspace-chip.tsx` around lines 87
- 107, The active-state comparison uses raw string equality between
workspace.path and current(), which can mis-identify equivalent paths; update
the createMemo assigned to active to compare normalized paths instead (e.g., use
a shared helper like normalizeWorkspacePath or a small util that
trims/normalizes trailing slashes and resolves dot segments) so it becomes
createMemo(() => normalizeWorkspacePath(workspace.path) ===
normalizeWorkspacePath(current())), and keep aria-selected, the Show
conditional, and any other uses referencing active() unchanged so they reflect
the normalized comparison.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/app/e2e/app/a11y-chip.spec.ts`:
- Around line 26-32: The test currently defaults targetLabel to "" when
extracting text from target.locator("span"), which can produce a false-positive
on the final assertion chip.toContainText(targetLabel); update the test to fail
when the extraction returns null/empty by asserting that the label is non-empty
(e.g., expect(targetLabel).not.toBe("") or throw if missing) before clicking, or
use a sentinel value instead of "" so chip.toContainText cannot trivially pass;
adjust references to targetLabel, target.locator("span"), and chip.toContainText
accordingly.

In `@packages/app/e2e/app/composer-agent-syntax.spec.ts`:
- Around line 18-21: After typing the "@" with page.keyboard.type("@"), wait for
the mention popover to be visible before calling page.keyboard.press("Enter") to
avoid a race; replace the blind Enter with an explicit wait using an observable
assertion such as
expect(page.locator('<mention-popover-selector>')).toBeVisible() or
expect.poll(...) (or the project's existing helper) and only call
page.keyboard.press("Enter") once that expectation passes.

In `@packages/app/e2e/app/composer-parity.spec.ts`:
- Around line 13-30: homeActions and sessionActions compare sorted arrays which
can fail when there are duplicate data-action nodes; deduplicate the lists
before asserting parity. In the block that builds homeActions and sessionActions
(variables homeActions and sessionActions) convert the evaluated arrays to
unique sets (e.g., Array.from(new Set(...)) or compare Set objects) and then
assert equality between the unique action collections (replace the expect on raw
sorted arrays with an equality check on the deduplicated collections, while
still excluding "prompt-workspace" for the session comparison).

In `@packages/app/e2e/app/composer-variant-locale.spec.ts`:
- Around line 24-25: Rename the test constants zhLabels and enLabels to
SCREAMING_SNAKE_CASE (e.g., ZH_LABELS, EN_LABELS) in the
composER-variant-locale.spec.ts file and update every usage (such as the call to
countMatches(page, zhLabels)) to reference the new names; ensure the const
declarations (ZH_LABELS, EN_LABELS) remain top-level test constants so they
follow the repository's test constant convention.
- Around line 25-29: The test currently only asserts that at least one option
matched localized labels via matched from countMatches(page, zhLabels), which
misses partial regressions; update the assertion to ensure all rendered options
are localized by comparing matched against the total number of rendered options:
use the page locator that selects variant option elements (the same selector
used in countMatches) to get totalOptions (e.g., via locator.count()), then
replace expect(matched).toBeGreaterThan(0) with
expect(matched).toEqual(totalOptions) so matched (from countMatches(page,
zhLabels)) must equal the full count of rendered options; apply the same change
for the other occurrence at lines 39-40.

In `@packages/app/e2e/app/composer-workspace-chip.spec.ts`:
- Around line 42-44: The test uses a hard-coded coordinate click
(page.mouse.click(10, 10)) which is brittle; replace it with a deterministic
outside click target such as clicking a known container or the document body via
a locator (e.g., a header, main container, or page.locator('body')) so the
popover/listbox reliably closes; update the sequence around chip.click and the
assertion on page.getByRole("listbox") to use that deterministic locator instead
of page.mouse.click(10, 10).

In `@packages/app/e2e/app/session.spec.ts`:
- Line 18: Update the test title string used in the test call that currently
reads " `@smoke` session composer matches home structure without DockTray or Agent
control" to a lowercase, descriptive title (e.g., " `@smoke` session composer
matches home structure without docktray or agent control") so it follows the E2E
naming convention; locate the test invocation by the test(...) call whose first
argument is the current title and replace the string with the lowercase variant
while keeping the same test identifier and body.

In `@packages/app/src/components/prompt-input/workspace-chip.tsx`:
- Around line 87-107: The active-state comparison uses raw string equality
between workspace.path and current(), which can mis-identify equivalent paths;
update the createMemo assigned to active to compare normalized paths instead
(e.g., use a shared helper like normalizeWorkspacePath or a small util that
trims/normalizes trailing slashes and resolves dot segments) so it becomes
createMemo(() => normalizeWorkspacePath(workspace.path) ===
normalizeWorkspacePath(current())), and keep aria-selected, the Show
conditional, and any other uses referencing active() unchanged so they reflect
the normalized comparison.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8737fcd5-a807-4eb9-841e-351aba209e74

📥 Commits

Reviewing files that changed from the base of the PR and between 6136b1c and 92875df.

📒 Files selected for processing (26)
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/context/language.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/ui/src/components/dock-surface.css
  • packages/ui/src/components/icon.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-opencode-session
🧰 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/e2e/app/composer-send.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/context/language.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
packages/app/e2e/**/*.spec.ts

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

packages/app/e2e/**/*.spec.ts: Import test utilities from ../fixtures instead of @playwright/test
Test files should be named with the pattern feature-name.spec.ts
Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')
Use camelCase for variable names in tests
Use SCREAMING_SNAKE_CASE for constants in tests
Use fixture-managed cleanup with withSession(sdk, title, callback) for temporary sessions instead of calling sdk.session.delete(...) directly
Prefer the project fixture for tests that need a dedicated project with LLM mocking
Use data-component, data-action, or semantic roles for selectors instead of CSS class names or IDs
Use modKey from utils for cross-platform keyboard shortcuts (Meta on Mac, Control on Linux/Windows)
In terminal tests, type through the browser using runTerminal() and waitTerminalReady() instead of writing to the PTY through the SDK
Never use wall-clock waits like page.waitForTimeout(...) to make a test pass
Wait on observable state with expect(...), expect.poll(...), or existing helpers instead of assuming work is finished after an action
Use locator assertions like toBeVisible(), toHaveCount(0), and toHaveAttribute(...) for normal UI state verification
Use expect.poll(...) for probing mock or backend state rather than transient DOM visibility
Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests
Use direct locators when the interaction is simple and a helper would not add clarity
When validating routing, assert against canonical or resolved workspace slugs using shared helpers from ../actions to account for Windows canonicalization
Test one feature per test file
Call project.trackSession(sessionID, directory?) and project.trackDirectory(directory) for any resources created outside the fixture so teardown can clean them up

Files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
🧠 Learnings (43)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests under packages/app/e2e, do not manually call `project.trackSession(sessionID)` when you obtain a `sessionID` via `project.prompt(text)`. The `project.prompt()` implementation already registers `trackSession(next.sessionID, active.directory)` automatically after the prompt submission is observed and the active session is resolved, so calling `project.trackSession(sessionID)` again will create duplicate session ownership/teardown handling.

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 lowercase, descriptive test names (e.g., 'sidebar can be toggled')

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 : Test one feature per test file

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 : In terminal tests, type through the browser using `runTerminal()` and `waitTerminalReady()` instead of writing to the PTY through the SDK

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : Test files should be named with the pattern `feature-name.spec.ts`

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 : Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 camelCase for variable names in tests

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 : Prefer the `project` fixture for tests that need a dedicated project with LLM mocking

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.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/e2e/app/composer-send.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/context/language.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/src/components/prompt-input/send-button.tsx
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T15:25:27.182Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/e2e/sidebar/sidebar-leading-slot.spec.ts:5-55
Timestamp: 2026-04-23T15:25:27.182Z
Learning: In Astro-Han/pawwork E2E tests (e.g., *.spec.ts under packages/app/e2e), reaching a real "running" session state is not achievable with the bare `sdk` fixture. Use the `project` fixture (to bootstrap the model) and orchestrate the transition with `llm.wait(1)`; even if you set `agent: "build"` and a `system` prompt via `sdk.session.promptAsync`, the current test infrastructure does not trigger an actual LLM call, so it won’t simulate "running" cheaply. Review any attempt to mock/force "running" using only `sdk` as likely ineffective unless it also uses `project` + `llm.wait(1)`.

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
📚 Learning: 2026-04-24T00:02:50.599Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 203
File: packages/app/e2e/sidebar/sidebar-session-links.spec.ts:34-55
Timestamp: 2026-04-24T00:02:50.599Z
Learning: For Astro-Han/pawwork E2E tests under packages/app/e2e/**/*.spec.ts, do not call project.trackDirectory() or project.trackSession() before project.open() has run. The project fixture throws until open() initializes internal state. Use this ordering pattern: (1) call project.trackSession(sessionID) inside the beforeGoto callback (where state is already available), (2) call project.trackDirectory(directory) and any cross-workspace tracking like project.trackSession(id, directory) immediately after project.open() returns, and (3) if you create any resources before open() that cannot yet be tracked via the fixture, ensure you clean them up explicitly in finally blocks (e.g., cleanupSession / cleanupTestProject).

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/app/src/context/language.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/components/prompt-input/variant-label.test.ts
  • packages/app/src/components/prompt-input/variant-label.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
📚 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/ui/src/components/icon.tsx
  • packages/app/src/components/prompt-input/workspace-chip.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 locator assertions like `toBeVisible()`, `toHaveCount(0)`, and `toHaveAttribute(...)` for normal UI state verification

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 `expect.poll(...)` for probing mock or backend state rather than transient DOM visibility

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/send-button.test.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 : Wait on observable state with `expect(...)`, `expect.poll(...)`, or existing helpers instead of assuming work is finished after an action

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.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 : Never use wall-clock waits like `page.waitForTimeout(...)` to make a test pass

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 direct locators when the interaction is simple and a helper would not add clarity

Applied to files:

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

Applied to files:

  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/prompt-input/workspace-chip.test.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/session/session-header.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use fixture-managed cleanup with `withSession(sdk, title, callback)` for temporary sessions instead of calling `sdk.session.delete(...)` directly

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : Call `project.trackSession(sessionID, directory?)` and `project.trackDirectory(directory)` for any resources created outside the fixture so teardown can clean them up

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 : When validating routing, assert against canonical or resolved workspace slugs using shared helpers from `../actions` to account for Windows canonicalization

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip.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 SCREAMING_SNAKE_CASE for constants in tests

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/ui/src/components/dock-surface.css
📚 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} : 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`.

Applied to files:

  • packages/app/src/components/prompt-input/send-button.test.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/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations

Applied to files:

  • packages/app/src/components/prompt-input/send-button.test.tsx
📚 Learning: 2026-04-23T08:51:04.230Z
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:04.230Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/util/filesystem.ts`), the `Filesystem` utility does NOT expose a `remove` or `unlink` helper. The established repository pattern for auth.json teardown in tests (e.g. `provider.test.ts`, `amazon-bedrock.test.ts`, `workspace-adaptor.test.ts`) is to combine `Filesystem.write` with `node:fs/promises unlink`. Do not flag this mixed usage as inconsistent — it is the correct and intentional pattern.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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} : 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/app/src/components/prompt-input/workspace-chip.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} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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} : 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.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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/app/src/components/prompt-input/workspace-chip.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} : 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.

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.test.ts
📚 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/components/prompt-input/workspace-chip.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/app/src/components/prompt-input.tsx

@Astro-Han Astro-Han force-pushed the feat/composer-unify branch 2 times, most recently from c0f0985 to 8e85bb0 Compare April 24, 2026 05:27

@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

Caution

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

⚠️ Outside diff range comments (2)
packages/app/src/pages/session/composer/session-composer-region.tsx (1)

82-86: 🧹 Nitpick | 🔵 Trivial

Consider individual createSignal calls for independent state.

The ready, height, and body fields are updated independently by separate handlers (animation timer, resize observer, and ref callback) and never batched together. Based on learnings, createStore should be reserved for coupled state that updates together—using individual createSignal calls here would reduce boilerplate.

♻️ Refactor to individual signals
- const [store, setStore] = createStore({
-   ready: false,
-   height: 320,
-   body: undefined as HTMLDivElement | undefined,
- })
+ const [ready, setReady] = createSignal(false)
+ const [height, setHeight] = createSignal(320)
+ const [body, setBody] = createSignal<HTMLDivElement>()

Then update references:

  • store.readyready()
  • setStore("ready", ...)setReady(...)
  • store.heightheight()
  • setStore("height", ...)setHeight(...)
  • store.bodybody()
  • setStore("body", el)setBody(el)

Based on learnings, createStore should only be used for coupled object state updated together in shared batch-update sites; independent fields updated by separate handlers should remain as individual createSignal calls.

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

In `@packages/app/src/pages/session/composer/session-composer-region.tsx` around
lines 82 - 86, Replace the single createStore usage (store, setStore) with three
independent createSignal pairs for ready, height, and body:
createSignal<boolean>(false) -> ready()/setReady, createSignal<number>(320) ->
height()/setHeight, and createSignal<HTMLDivElement|undefined>(undefined) ->
body()/setBody; update all usages and updates that currently read store.ready,
store.height, store.body and calls setStore("ready", ...), setStore("height",
...), setStore("body", el) to use the new signals (ready()/setReady(...),
height()/setHeight(...), body()/setBody(...)) so handlers (animation timer,
resize observer, ref callback) update independently.
packages/app/src/components/prompt-input/workspace-chip.tsx (1)

77-109: ⚠️ Potential issue | 🟠 Major

Implement listbox keyboard behavior or drop the listbox roles.

This popup exposes role="listbox" with role="option" children (lines 77, 91), creating an expectation of keyboard navigation. However, no keyboard event handlers are present—only click handling is implemented. Users navigating by arrow keys, Home, or End will have no effect, violating ARIA expectations for listbox patterns.

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

In `@packages/app/src/components/prompt-input/workspace-chip.tsx` around lines 77
- 109, The popover uses role="listbox" with role="option" children but has no
keyboard handling; implement listbox keyboard behavior on the container (the div
with role="listbox") by adding an onKeyDown that handles ArrowUp/ArrowDown to
move focus between option buttons, Home/End to jump to first/last, and
Enter/Space to activate the focused option (call
navigate(`/${base64Encode(workspace.path)}/session`) and setOpen(false)); use
the existing workspaces(), current(), and the per-option button elements
(role="option") so you can query or track their indices (e.g., maintain a
focusedIndex signal or derive from document.activeElement and workspaces()) and
update aria-selected via the active() memo when selection changes;
alternatively, if you prefer not to implement keyboard support, remove
role="listbox" and role="option" attributes to avoid ARIA mismatch.
♻️ Duplicate comments (11)
packages/app/e2e/app/a11y-chip.spec.ts (1)

25-32: ⚠️ Potential issue | 🟡 Minor

Fail when label extraction fails.

Falling back to "" makes toContainText(targetLabel) vacuous, so this test can still pass if the option markup changes and no label is found.

Suggested fix
-  const targetLabel = (await target.locator("span").first().textContent())?.trim() ?? ""
+  const targetLabel = (await target.locator("span").first().textContent())?.trim()
+  expect(targetLabel, "workspace option label should be non-empty").toBeTruthy()
@@
-  await expect(chip).toContainText(targetLabel)
+  await expect(chip).toContainText(targetLabel!)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/a11y-chip.spec.ts` around lines 25 - 32, The test
silently allows missing labels because targetLabel falls back to "" — change it
to fail fast when label extraction fails by asserting or throwing if the span
text is empty/null before using it; locate the extraction (targetLabel := (await
target.locator("span").first().textContent())?.trim()) and add an explicit check
(e.g., expect(targetLabel).toBeTruthy() or throw an Error with context) so the
subsequent await expect(chip).toContainText(targetLabel) cannot succeed
vacuously.
packages/app/src/components/prompt-input/workspace-chip.tsx (2)

58-72: ⚠️ Potential issue | 🟡 Minor

Constrain the workspace trigger label.

Long folder names are still free to grow here, so the unified one-row composer can let this chip crowd the send button. Add min-w-0, a max width, and truncation on the trigger/label.

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

In `@packages/app/src/components/prompt-input/workspace-chip.tsx` around lines 58
- 72, The workspace trigger label can grow unbounded and crowd the UI; update
the trigger element in workspace-chip.tsx (referencing triggerProps and the span
that renders label()) to constrain width and enable truncation by adding a
container class like "min-w-0 max-w-[150px] overflow-hidden whitespace-nowrap
text-ellipsis" (or your project's equivalent utility classes) and ensure the
label span includes the truncation classes instead of just "leading-none" so
long names are clipped with an ellipsis.

87-95: ⚠️ Potential issue | 🟡 Minor

Normalize the active workspace comparison.

workspace.path === current() reintroduces raw-path matching, while the helper layer already normalizes workspace identity. Path-equivalent values can lose the active row/checkmark even though they represent the same workspace.

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

In `@packages/app/src/components/prompt-input/workspace-chip.tsx` around lines 87
- 95, The active-check uses raw path equality (workspace.path === current())
which breaks when equivalent paths normalize differently; update the createMemo
for active to compare the normalized workspace identity instead of raw path by
calling the existing normalization helper (e.g., workspaceIdentity(workspace) or
getWorkspaceId(workspace)) against current(); ensure the memo uses that helper
(replace workspace.path with the normalized-id call) so the active row/checkmark
shows for path-equivalent workspaces while keeping the rest of the
onClick/navigate logic intact.
packages/app/src/components/prompt-input.tsx (1)

1218-1268: ⚠️ Potential issue | 🟠 Major

Restore keyboard behavior for the custom variant popup.

Replacing Select with this popover keeps role="listbox"/role="option" but drops Arrow/Home/End handling and option focus management. The control becomes tab-only and regresses the keyboard affordance the old selector had.

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

In `@packages/app/src/components/prompt-input.tsx` around lines 1218 - 1268, The
popover replacing Select (Popover with role="listbox" and option buttons inside
For over variants()) lost keyboard navigation (ArrowUp/ArrowDown/Home/End) and
option focus management, making it tab-only; add keyboard handling on the
listbox container (the element rendered inside Popover) to implement a
roving-focus or aria-activedescendant pattern: capture keydown to move the
active option index (use variants(), the active memo, and
local.model.variant.set), focus the corresponding option element (or manage
aria-activedescendant), handle Home/End to jump to first/last, handle
Enter/Space to select and call setVariantOpen(false) and restoreFocus(), and
ensure aria-selected updates; tie this logic to Popover’s open state and ensure
focus is set to the active option when opened.
packages/app/e2e/app/session.spec.ts (2)

18-18: ⚠️ Potential issue | 🟡 Minor

Use an all-lowercase smoke test title.

At Line 18, DockTray/Agent make the title non-compliant with the suite naming rule.

As per coding guidelines "Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')".

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

In `@packages/app/e2e/app/session.spec.ts` at line 18, The test title passed to
the test(...) call is not all-lowercase; update the string in the test("@smoke
session composer matches home structure without DockTray or Agent control", ...)
invocation to an all-lowercase, descriptive name (e.g., "session composer
matches home structure without docktray or agent control") so it complies with
the lowercase test naming guideline; ensure you only change the title text and
preserve the test decorator "@smoke" and the rest of the test body.

18-18: ⚠️ Potential issue | 🟠 Major

Smoke inventory sync is required for this new @smoke title.

This test appears to expand the smoke set; ensure expectedSmokeTests is updated in the smoke-tagging contract test.

#!/bin/bash
set -euo pipefail

TARGET_FILE="$(fd -i 'e2e-smoke-tagging.test.ts' packages | head -n1)"
echo "Smoke inventory file: ${TARGET_FILE}"

rg -n "expectedSmokeTests|@smoke session composer matches home structure without DockTray or Agent control|@smoke home composer shows unified single-row bar with brand orange send" "${TARGET_FILE}" packages/app/e2e/app/session.spec.ts packages/app/e2e/app/home.spec.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/session.spec.ts` at line 18, You added a new `@smoke` test
" `@smoke` session composer matches home structure without DockTray or Agent
control" but did not update the smoke inventory; open the smoke-tagging contract
test and add this test title to the expectedSmokeTests array (the array named
expectedSmokeTests) so the smoke inventory stays in sync; ensure the exact
string matches the test name used in session.spec.ts and run the contract test
to verify no other `@smoke` tests (e.g., the related home test title) are missing
from expectedSmokeTests.
packages/app/e2e/app/composer-variant-locale.spec.ts (2)

24-24: 🧹 Nitpick | 🔵 Trivial

Use SCREAMING_SNAKE_CASE for test constants.

zhLabels and enLabels should follow the test constant naming convention.

As per coding guidelines "Use SCREAMING_SNAKE_CASE for constants in tests".

Also applies to: 38-38

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

In `@packages/app/e2e/app/composer-variant-locale.spec.ts` at line 24, Rename the
test constants zhLabels and enLabels to SCREAMING_SNAKE_CASE (e.g., ZH_LABELS
and EN_LABELS) and update every usage in the spec to the new names; ensure any
imports or references inside tests (selectors, assertions, helper calls) are
updated so the spec compiles and runs with the new constant names.

4-15: ⚠️ Potential issue | 🟠 Major

Current assertion can pass with partially untranslated variant menus.

matched > 0 (Lines 28 and 40) does not validate the test title’s claim that every visible variant is localized.

Suggested fix
-async function countMatches(page: any, labels: string[]): Promise<number> {
-  let matched = 0
-  for (const label of labels) {
-    const option = page.getByRole("option", { name: label, exact: true })
-    const count = await option.count()
-    if (count > 0) {
-      matched++
-      await expect(option.first()).toBeVisible()
-    }
-  }
-  return matched
+async function assertAllVisibleOptionsLocalized(page: any, labels: string[]): Promise<void> {
+  const options = page.getByRole("option")
+  const total = await options.count()
+  expect(total).toBeGreaterThan(0)
+  for (let i = 0; i < total; i++) {
+    const text = (await options.nth(i).innerText()).trim()
+    expect(labels).toContain(text)
+  }
 }
@@
-  const matched = await countMatches(page, zhLabels)
-  // Guard against vacuous pass: if zero options matched, the popover selector or
-  // locale fixture regressed, or the current model has no variants. Fail loud.
-  expect(matched).toBeGreaterThan(0)
+  await assertAllVisibleOptionsLocalized(page, ZH_LABELS)
@@
-  const matched = await countMatches(page, enLabels)
-  expect(matched).toBeGreaterThan(0)
+  await assertAllVisibleOptionsLocalized(page, EN_LABELS)

Also applies to: 25-29, 39-40

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

In `@packages/app/e2e/app/composer-variant-locale.spec.ts` around lines 4 - 15,
The helper countMatches currently allows partial matches because callers only
check matched > 0; update countMatches to assert that every expected label is
present: inside the function (countMatches) after the loop, add an explicit
assertion like expect(matched).toBe(labels.length) so the test fails if any
expected variant label is missing; keep the visibility checks using
option.first() and return matched as before. This ensures the check uses
labels.length (all expected localized items) rather than just > 0 for presence.
packages/app/e2e/app/composer-parity.spec.ts (1)

13-30: ⚠️ Potential issue | 🟡 Minor

Compare unique action sets, not raw sorted arrays.

At Lines 13-30, duplicate data-action nodes can cause false mismatches.

Suggested fix
-  const homeActions = (
+  const homeActions = new Set(
     await homeBar
       .locator("[data-action]")
       .evaluateAll((els) => els.map((e) => e.getAttribute("data-action")).filter((v): v is string => !!v))
-  ).sort()
+  )
@@
-    const sessionActions = (
+    const sessionActions = new Set(
       await sessionBar
         .locator("[data-action]")
         .evaluateAll((els) => els.map((e) => e.getAttribute("data-action")).filter((v): v is string => !!v))
-    ).sort()
+    )
@@
-    expect(sessionActions).toEqual(homeActions.filter((a) => a !== "prompt-workspace"))
+    expect([...sessionActions].sort()).toEqual(
+      [...homeActions].filter((a) => a !== "prompt-workspace").sort(),
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/composer-parity.spec.ts` around lines 13 - 30,
homeActions and sessionActions can contain duplicate data-action values causing
false mismatches; deduplicate both lists before sorting/comparing (e.g., convert
to Set and back or use Array.from(new Set(...))) and then sort them, then assert
the deduplicated/sorted sessionActions equals the deduplicated/sorted
homeActions filtered for "prompt-workspace" (refer to the homeActions and
sessionActions variables and the existing filter excluding "prompt-workspace").
packages/app/e2e/app/composer-agent-syntax.spec.ts (1)

18-21: ⚠️ Potential issue | 🟠 Major

Wait for @ popover readiness before pressing Enter.

At Line 20, Enter is sent before observable readiness, which can race and intermittently miss selection.

Suggested fix
   await page.keyboard.type("@")
+  const popover = page.locator('[data-component="prompt-at-popover"]')
+  await expect(popover).toBeVisible()
 
   await page.keyboard.press("Enter")
As per coding guidelines "Wait on observable state with `expect(...)`, `expect.poll(...)`, or existing helpers instead of assuming work is finished after an action".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/composer-agent-syntax.spec.ts` around lines 18 - 21, The
test types "@" with page.keyboard.type("@") and immediately sends Enter with
page.keyboard.press("Enter"), which races with the mention popover; change the
sequence to wait for the popover/selection to be ready before pressing Enter by
awaiting an observable state (for example use
expect(locatorForMentionPopover).toBeVisible(),
expect(locatorForMentionItem).toHaveCount(>0), or expect.poll(...) until the
selection appears) after page.keyboard.type("@") and before
page.keyboard.press("Enter") so the Enter selects the intended item reliably.
packages/app/e2e/app/composer-workspace-chip.spec.ts (1)

42-44: ⚠️ Potential issue | 🟡 Minor

Replace coordinate click with a deterministic outside target.

At Line 43, hard-coded viewport coordinates can intermittently hit different elements and make dismissal flaky.

Suggested fix
   await chip.click()
-  await page.mouse.click(10, 10)
+  await page.getByRole("heading", { name: /Choose what to do/i }).click()
   await expect(page.getByRole("listbox")).toHaveCount(0)
As per coding guidelines "Use `data-component`, `data-action`, or semantic roles for selectors instead of CSS class names or IDs".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/e2e/app/composer-workspace-chip.spec.ts` around lines 42 - 44,
The coordinate-based click (page.mouse.click(10, 10)) in the test is flaky;
replace it with a deterministic click on a semantic/outside target (e.g., a
data-component or role-based element) to reliably dismiss the listbox — locate
the failing call to page.mouse.click in the composer-workspace-chip.spec test
(after awaiting chip.click()) and change it to something like
page.getByRole('button', { name: 'Close' }).click() or
page.getByTestId('outside-area').click(), ensuring you use a
data-component/data-action or semantic role selector as per guidelines and then
assert the listbox count remains zero.
🤖 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/e2e/app/composer-shell-mode.spec.ts`:
- Line 4: The test title in the test declaration (the string passed to test(...)
in composer-shell-mode.spec.ts) contains uppercase letters ("DockTray"); update
the title to be fully lowercase (for example change "shell mode surfaces via
textarea placeholder and no DockTray label remains" to "shell mode surfaces via
textarea placeholder and no docktray label remains") so it follows the project's
lowercase test naming convention.

In `@packages/app/src/components/prompt-input.tsx`:
- Around line 1569-1613: The SendButton is being hidden/disabled whenever
store.mode !== "normal", which prevents submission from shell mode; update the
JSX so SendButton remains enabled/visible for shell mode (allow its disabled
prop to only reflect real blocking conditions like stopping(),
working()/blank()+selectedSkill, not store.mode), and remove it from the parent
element that has aria-hidden/pointer-events disabled when store.mode !==
"normal". Also gate WorkspaceChip rendering separately (use props.homeMode or a
Show around WorkspaceChip) so it can be hidden independently without affecting
SendButton or other controls like renderModelControl/renderVariantControl that
should remain pointer-disabled when appropriate.

---

Outside diff comments:
In `@packages/app/src/components/prompt-input/workspace-chip.tsx`:
- Around line 77-109: The popover uses role="listbox" with role="option"
children but has no keyboard handling; implement listbox keyboard behavior on
the container (the div with role="listbox") by adding an onKeyDown that handles
ArrowUp/ArrowDown to move focus between option buttons, Home/End to jump to
first/last, and Enter/Space to activate the focused option (call
navigate(`/${base64Encode(workspace.path)}/session`) and setOpen(false)); use
the existing workspaces(), current(), and the per-option button elements
(role="option") so you can query or track their indices (e.g., maintain a
focusedIndex signal or derive from document.activeElement and workspaces()) and
update aria-selected via the active() memo when selection changes;
alternatively, if you prefer not to implement keyboard support, remove
role="listbox" and role="option" attributes to avoid ARIA mismatch.

In `@packages/app/src/pages/session/composer/session-composer-region.tsx`:
- Around line 82-86: Replace the single createStore usage (store, setStore) with
three independent createSignal pairs for ready, height, and body:
createSignal<boolean>(false) -> ready()/setReady, createSignal<number>(320) ->
height()/setHeight, and createSignal<HTMLDivElement|undefined>(undefined) ->
body()/setBody; update all usages and updates that currently read store.ready,
store.height, store.body and calls setStore("ready", ...), setStore("height",
...), setStore("body", el) to use the new signals (ready()/setReady(...),
height()/setHeight(...), body()/setBody(...)) so handlers (animation timer,
resize observer, ref callback) update independently.

---

Duplicate comments:
In `@packages/app/e2e/app/a11y-chip.spec.ts`:
- Around line 25-32: The test silently allows missing labels because targetLabel
falls back to "" — change it to fail fast when label extraction fails by
asserting or throwing if the span text is empty/null before using it; locate the
extraction (targetLabel := (await
target.locator("span").first().textContent())?.trim()) and add an explicit check
(e.g., expect(targetLabel).toBeTruthy() or throw an Error with context) so the
subsequent await expect(chip).toContainText(targetLabel) cannot succeed
vacuously.

In `@packages/app/e2e/app/composer-agent-syntax.spec.ts`:
- Around line 18-21: The test types "@" with page.keyboard.type("@") and
immediately sends Enter with page.keyboard.press("Enter"), which races with the
mention popover; change the sequence to wait for the popover/selection to be
ready before pressing Enter by awaiting an observable state (for example use
expect(locatorForMentionPopover).toBeVisible(),
expect(locatorForMentionItem).toHaveCount(>0), or expect.poll(...) until the
selection appears) after page.keyboard.type("@") and before
page.keyboard.press("Enter") so the Enter selects the intended item reliably.

In `@packages/app/e2e/app/composer-parity.spec.ts`:
- Around line 13-30: homeActions and sessionActions can contain duplicate
data-action values causing false mismatches; deduplicate both lists before
sorting/comparing (e.g., convert to Set and back or use Array.from(new
Set(...))) and then sort them, then assert the deduplicated/sorted
sessionActions equals the deduplicated/sorted homeActions filtered for
"prompt-workspace" (refer to the homeActions and sessionActions variables and
the existing filter excluding "prompt-workspace").

In `@packages/app/e2e/app/composer-variant-locale.spec.ts`:
- Line 24: Rename the test constants zhLabels and enLabels to
SCREAMING_SNAKE_CASE (e.g., ZH_LABELS and EN_LABELS) and update every usage in
the spec to the new names; ensure any imports or references inside tests
(selectors, assertions, helper calls) are updated so the spec compiles and runs
with the new constant names.
- Around line 4-15: The helper countMatches currently allows partial matches
because callers only check matched > 0; update countMatches to assert that every
expected label is present: inside the function (countMatches) after the loop,
add an explicit assertion like expect(matched).toBe(labels.length) so the test
fails if any expected variant label is missing; keep the visibility checks using
option.first() and return matched as before. This ensures the check uses
labels.length (all expected localized items) rather than just > 0 for presence.

In `@packages/app/e2e/app/composer-workspace-chip.spec.ts`:
- Around line 42-44: The coordinate-based click (page.mouse.click(10, 10)) in
the test is flaky; replace it with a deterministic click on a semantic/outside
target (e.g., a data-component or role-based element) to reliably dismiss the
listbox — locate the failing call to page.mouse.click in the
composer-workspace-chip.spec test (after awaiting chip.click()) and change it to
something like page.getByRole('button', { name: 'Close' }).click() or
page.getByTestId('outside-area').click(), ensuring you use a
data-component/data-action or semantic role selector as per guidelines and then
assert the listbox count remains zero.

In `@packages/app/e2e/app/session.spec.ts`:
- Line 18: The test title passed to the test(...) call is not all-lowercase;
update the string in the test("@smoke session composer matches home structure
without DockTray or Agent control", ...) invocation to an all-lowercase,
descriptive name (e.g., "session composer matches home structure without
docktray or agent control") so it complies with the lowercase test naming
guideline; ensure you only change the title text and preserve the test decorator
"@smoke" and the rest of the test body.
- Line 18: You added a new `@smoke` test " `@smoke` session composer matches home
structure without DockTray or Agent control" but did not update the smoke
inventory; open the smoke-tagging contract test and add this test title to the
expectedSmokeTests array (the array named expectedSmokeTests) so the smoke
inventory stays in sync; ensure the exact string matches the test name used in
session.spec.ts and run the contract test to verify no other `@smoke` tests (e.g.,
the related home test title) are missing from expectedSmokeTests.

In `@packages/app/src/components/prompt-input.tsx`:
- Around line 1218-1268: The popover replacing Select (Popover with
role="listbox" and option buttons inside For over variants()) lost keyboard
navigation (ArrowUp/ArrowDown/Home/End) and option focus management, making it
tab-only; add keyboard handling on the listbox container (the element rendered
inside Popover) to implement a roving-focus or aria-activedescendant pattern:
capture keydown to move the active option index (use variants(), the active
memo, and local.model.variant.set), focus the corresponding option element (or
manage aria-activedescendant), handle Home/End to jump to first/last, handle
Enter/Space to select and call setVariantOpen(false) and restoreFocus(), and
ensure aria-selected updates; tie this logic to Popover’s open state and ensure
focus is set to the active option when opened.

In `@packages/app/src/components/prompt-input/workspace-chip.tsx`:
- Around line 58-72: The workspace trigger label can grow unbounded and crowd
the UI; update the trigger element in workspace-chip.tsx (referencing
triggerProps and the span that renders label()) to constrain width and enable
truncation by adding a container class like "min-w-0 max-w-[150px]
overflow-hidden whitespace-nowrap text-ellipsis" (or your project's equivalent
utility classes) and ensure the label span includes the truncation classes
instead of just "leading-none" so long names are clipped with an ellipsis.
- Around line 87-95: The active-check uses raw path equality (workspace.path ===
current()) which breaks when equivalent paths normalize differently; update the
createMemo for active to compare the normalized workspace identity instead of
raw path by calling the existing normalization helper (e.g.,
workspaceIdentity(workspace) or getWorkspaceId(workspace)) against current();
ensure the memo uses that helper (replace workspace.path with the normalized-id
call) so the active row/checkmark shows for path-equivalent workspaces while
keeping the rest of the onClick/navigate logic intact.
🪄 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: 03a092c1-f9ff-487d-b58a-aef6b6876279

📥 Commits

Reviewing files that changed from the base of the PR and between 92875df and c0f0985.

📒 Files selected for processing (18)
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/opencode/src/provider/models-snapshot.js
  • packages/ui/src/components/dock-surface.css

Comment thread packages/app/e2e/app/composer-shell-mode.spec.ts Outdated
Comment thread packages/app/src/components/prompt-input.tsx
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch 2 times, most recently from f2916bd to 5d810aa Compare April 24, 2026 05:39

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Second review pass with a fresh subagent plus local verification. Leaving the new, non-duplicate findings inline. One additional concern could not be attached inline because GitHub rejects review comments on packages/opencode/src/provider/models-snapshot.js as too large: this UI PR also carries a broad generated provider snapshot refresh with many unrelated added, changed, and removed models. Please split that snapshot drift out, or explain why it is required for this composer unification.

Comment thread packages/app/e2e/app/composer-variant-locale.spec.ts
Comment thread packages/app/src/components/prompt-input/workspace-chip.tsx
Comment thread packages/app/src/components/prompt-input.tsx
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch from 5d810aa to 8d389d7 Compare April 24, 2026 05:46

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/app/src/components/prompt-input/workspace-chip.tsx (1)

78-97: ⚠️ Potential issue | 🟠 Major

Don't expose menu semantics without menu keyboard behavior.

Switching this popup to role="menu" / menuitemradio changes the keyboard contract: focus is expected to move into the menu and arrow keys should cycle items. The shared Popover component handles only Escape to close (line 78–83 in packages/ui/src/components/popover.tsx), with no arrow-key, roving-focus, or Home/End support. The buttons remain plain elements with click handlers, so assistive tech announces a menu while keyboard interaction stays button-like.

Implement menu keyboard navigation in the Popover, add arrow-key focus management to this component, or use neutral button/list semantics instead.

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

In `@packages/app/src/components/prompt-input/workspace-chip.tsx` around lines 78
- 97, The code exposes "menu" semantics without implementing keyboard
navigation; fix by removing menu/menuitemradio roles and using neutral
list/button semantics or by implementing full menu keyboard behavior in Popover.
Easiest fix: in workspace-chip.tsx remove role="menu" and role="menuitemradio"
(on the container and the buttons), optionally set a non-menu container role
such as role="list" and role="listitem" (or no role) and mark the selected
workspace via aria-pressed or aria-current; keep using workspaces(), current(),
and workspaceKey(workspace.path) to compute the active item. If you choose to
implement proper menu behavior instead, add arrow-up/arrow-down, Home/End
handling, roving-focus, and focus management to the Popover component
(packages/ui/src/components/popover.tsx) so that keyboard navigation matches the
announced menu semantics.
🤖 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/e2e/app/composer-parity.spec.ts`:
- Around line 5-10: collectActionSet currently only gathers elements'
data-action attributes, so it misses the model/variant chips which are marked as
data-component="prompt-model-control" and
data-component="prompt-variant-control"; update collectActionSet (the async
function using Page.prototype.locator) to include either data-component values
or to also query for those specific data-component attributes when building the
set (e.g., include attribute values from elements matching [data-action] and
[data-component] and normalize them into the returned Set) so the parity
assertion covers the model and variant chips as intended.

In `@packages/app/e2e/app/home.spec.ts`:
- Around line 77-81: The test reads computed style once (send.evaluate) which
can catch the button mid-transition; replace that one-shot read with a retrying
locator CSS assertion. Remove the send.evaluate(...) and instead call
expect(send).toHaveCSS('background-color', 'rgb(229, 106, 46)') (or the exact
rgb string you expect) so Playwright will retry until the transition settles;
reference the existing send locator created via
composer.locator('[data-action="prompt-submit"]').

---

Outside diff comments:
In `@packages/app/src/components/prompt-input/workspace-chip.tsx`:
- Around line 78-97: The code exposes "menu" semantics without implementing
keyboard navigation; fix by removing menu/menuitemradio roles and using neutral
list/button semantics or by implementing full menu keyboard behavior in Popover.
Easiest fix: in workspace-chip.tsx remove role="menu" and role="menuitemradio"
(on the container and the buttons), optionally set a non-menu container role
such as role="list" and role="listitem" (or no role) and mark the selected
workspace via aria-pressed or aria-current; keep using workspaces(), current(),
and workspaceKey(workspace.path) to compute the active item. If you choose to
implement proper menu behavior instead, add arrow-up/arrow-down, Home/End
handling, roving-focus, and focus management to the Popover component
(packages/ui/src/components/popover.tsx) so that keyboard navigation matches the
announced menu semantics.
🪄 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: d6a8697d-a1bf-4946-967c-a492f66ebc58

📥 Commits

Reviewing files that changed from the base of the PR and between c0f0985 and 5d810aa.

📒 Files selected for processing (21)
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/opencode/src/provider/models-snapshot.js
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/ui/src/components/dock-surface.css
📜 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-desktop
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: typecheck
  • GitHub Check: unit-app
  • GitHub Check: unit-opencode
  • GitHub Check: unit-desktop
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (4)
packages/app/**/*.{ts,tsx,js,jsx}

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

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.tsx
packages/app/e2e/**/*.spec.ts

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

packages/app/e2e/**/*.spec.ts: Import test utilities from ../fixtures instead of @playwright/test
Test files should be named with the pattern feature-name.spec.ts
Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')
Use camelCase for variable names in tests
Use SCREAMING_SNAKE_CASE for constants in tests
Use fixture-managed cleanup with withSession(sdk, title, callback) for temporary sessions instead of calling sdk.session.delete(...) directly
Prefer the project fixture for tests that need a dedicated project with LLM mocking
Use data-component, data-action, or semantic roles for selectors instead of CSS class names or IDs
Use modKey from utils for cross-platform keyboard shortcuts (Meta on Mac, Control on Linux/Windows)
In terminal tests, type through the browser using runTerminal() and waitTerminalReady() instead of writing to the PTY through the SDK
Never use wall-clock waits like page.waitForTimeout(...) to make a test pass
Wait on observable state with expect(...), expect.poll(...), or existing helpers instead of assuming work is finished after an action
Use locator assertions like toBeVisible(), toHaveCount(0), and toHaveAttribute(...) for normal UI state verification
Use expect.poll(...) for probing mock or backend state rather than transient DOM visibility
Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests
Use direct locators when the interaction is simple and a helper would not add clarity
When validating routing, assert against canonical or resolved workspace slugs using shared helpers from ../actions to account for Windows canonicalization
Test one feature per test file
Call project.trackSession(sessionID, directory?) and project.trackDirectory(directory) for any resources created outside the fixture so teardown can clean them up

Files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
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/config/e2e-smoke-tagging.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/config/e2e-smoke-tagging.test.ts
🧠 Learnings (45)
📓 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: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.

Applied to files:

  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Import test utilities from `../fixtures` instead of `playwright/test`

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 : In terminal tests, type through the browser using `runTerminal()` and `waitTerminalReady()` instead of writing to the PTY through the SDK

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/home.spec.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/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 : Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.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 `expect.poll(...)` for probing mock or backend state rather than transient DOM visibility

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 : Test one feature per test file

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.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 : Test files should be named with the pattern `feature-name.spec.ts`

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/home.spec.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/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Wait on observable state with `expect(...)`, `expect.poll(...)`, or existing helpers instead of assuming work is finished after an action

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests under packages/app/e2e, do not manually call `project.trackSession(sessionID)` when you obtain a `sessionID` via `project.prompt(text)`. The `project.prompt()` implementation already registers `trackSession(next.sessionID, active.directory)` automatically after the prompt submission is observed and the active session is resolved, so calling `project.trackSession(sessionID)` again will create duplicate session ownership/teardown handling.

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
📚 Learning: 2026-04-24T00:02:50.599Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 203
File: packages/app/e2e/sidebar/sidebar-session-links.spec.ts:34-55
Timestamp: 2026-04-24T00:02:50.599Z
Learning: For Astro-Han/pawwork E2E tests under packages/app/e2e/**/*.spec.ts, do not call project.trackDirectory() or project.trackSession() before project.open() has run. The project fixture throws until open() initializes internal state. Use this ordering pattern: (1) call project.trackSession(sessionID) inside the beforeGoto callback (where state is already available), (2) call project.trackDirectory(directory) and any cross-workspace tracking like project.trackSession(id, directory) immediately after project.open() returns, and (3) if you create any resources before open() that cannot yet be tracked via the fixture, ensure you clean them up explicitly in finally blocks (e.g., cleanupSession / cleanupTestProject).

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
📚 Learning: 2026-04-23T15:25:27.182Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/e2e/sidebar/sidebar-leading-slot.spec.ts:5-55
Timestamp: 2026-04-23T15:25:27.182Z
Learning: In Astro-Han/pawwork E2E tests (e.g., *.spec.ts under packages/app/e2e), reaching a real "running" session state is not achievable with the bare `sdk` fixture. Use the `project` fixture (to bootstrap the model) and orchestrate the transition with `llm.wait(1)`; even if you set `agent: "build"` and a `system` prompt via `sdk.session.promptAsync`, the current test infrastructure does not trigger an actual LLM call, so it won’t simulate "running" cheaply. Review any attempt to mock/force "running" using only `sdk` as likely ineffective unless it also uses `project` + `llm.wait(1)`.

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.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:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Prefer the `project` fixture for tests that need a dedicated project with LLM mocking

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.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 : Call `project.trackSession(sessionID, directory?)` and `project.trackDirectory(directory)` for any resources created outside the fixture so teardown can clean them up

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 : When validating routing, assert against canonical or resolved workspace slugs using shared helpers from `../actions` to account for Windows canonicalization

Applied to files:

  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.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 direct locators when the interaction is simple and a helper would not add clarity

Applied to files:

  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 fixture-managed cleanup with `withSession(sdk, title, callback)` for temporary sessions instead of calling `sdk.session.delete(...)` directly

Applied to files:

  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/home.spec.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 camelCase for variable names in tests

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 SCREAMING_SNAKE_CASE for constants in tests

Applied to files:

  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations

Applied to files:

  • packages/opencode/test/config/e2e-smoke-tagging.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} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.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/config/e2e-smoke-tagging.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} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/home.spec.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} : 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.

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/home.spec.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/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T08:51:04.230Z
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:04.230Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/util/filesystem.ts`), the `Filesystem` utility does NOT expose a `remove` or `unlink` helper. The established repository pattern for auth.json teardown in tests (e.g. `provider.test.ts`, `amazon-bedrock.test.ts`, `workspace-adaptor.test.ts`) is to combine `Filesystem.write` with `node:fs/promises unlink`. Do not flag this mixed usage as inconsistent — it is the correct and intentional pattern.

Applied to files:

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

Applied to files:

  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/prompt-input/workspace-chip.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/e2e/app/composer-variant-locale.spec.ts
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.

Applied to files:

  • packages/ui/src/components/dock-surface.css
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

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

Applied to files:

  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
📚 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/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.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/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.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/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/app/src/components/prompt-input.tsx
🔇 Additional comments (18)
packages/app/src/components/session/session-new-view.tsx (1)

36-36: Looks good — style-only change aligns with pill-card UI.

rounded-full here is a safe visual adjustment and does not alter behavior.

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

98-102: Title tooltip behavior looks correct.

Line 98–102 correctly appends the localized “open” hint only when the directory is actually openable, while preserving the plain path title otherwise.

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

159-159: LGTM!

The max-width increase to 1200px for home mode widens the composer container and aligns with the PR objective of unifying the composer UI across home and session views.

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

156-156: Popover placement change looks correct.

This update is consistent with the unified bottom composer behavior and is safely scoped.

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

40-40: Good addition of stable popover test hooks.

data-component mapping is clear and supports robust role/attribute-based E2E targeting.

packages/ui/src/components/dock-surface.css (1)

3-3: Styling adjustments look coherent.

The shell/tray radius and shadow updates are consistent and safely scoped to data-dock-surface variants.

Also applies to: 6-6, 15-15

packages/app/e2e/app/composer-send.spec.ts (1)

1-17: These send-state E2E checks are solid.

Good selector strategy and stable expectation flow for empty vs non-empty prompt behavior.

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

33-33: Test updates match the new helper return contract.

The revised assertions and added shape checks improve confidence in WorkspaceChoice expectations.

Also applies to: 50-51, 53-61, 63-69

packages/opencode/test/config/e2e-smoke-tagging.test.ts (1)

7-7: Smoke inventory additions are consistent.

The expected list updates align with the new composer smoke scenarios tracked in this PR.

Also applies to: 13-13

packages/app/e2e/app/composer-agent-syntax.spec.ts (1)

1-28: Agent syntax coverage is well structured.

Nice sequencing and selectors here—especially waiting for the @ popover before the Enter selection path.

packages/app/e2e/app/a11y-chip.spec.ts (1)

18-20: Accessibility-semantic assertions are now much stronger.

Switching to menu/menuitemradio checks and explicit non-empty label validation improves correctness and resilience.

Also applies to: 25-27, 30-30, 32-32, 41-42, 44-44, 48-48, 50-50

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

3-31: LGTM!

The import additions are appropriate for the new Popover-based variant menu and SendButton component.


1208-1209: LGTM!

The separate variantOpen signal is appropriate here since it controls an independent UI element (chip button popover) that isn't batch-updated with the existing store.popover (inline editor popovers). Based on learnings: "use createStore only when signals represent coupled state updated together."


1218-1269: LGTM!

The Popover-based variant menu correctly uses role="menu" / role="menuitemradio" / aria-haspopup="menu" semantics, which accurately reflects the widget's actual keyboard behavior (Tab navigation + Enter activation). This aligns with the confirmed fix from the previous review discussion.


1254-1258: LGTM!

The variant selection correctly maps "default" to undefined, which aligns with the system's canonical representation where undefined means "use default variant" (as confirmed in the resolveModelVariant and cycleModelVariant utilities in model-variant.ts).


1536-1537: LGTM!

The data-component="prompt-placeholder" attribute addition provides a stable E2E selector, which aligns with the project's selector conventions.


1569-1614: LGTM!

The bottom bar restructuring correctly implements the unified composer pattern:

  • SendButton is in a separate right-side sibling div with pointer-events-auto, outside the aria-hidden group
  • SendButton.disabled does not gate on store.mode !== "normal" (shell mode Enter-key submit bypasses the button)
  • SendButton does not use the buttons() spring opacity
  • WorkspaceChip is correctly gated on props.homeMode && store.mode === "normal"
  • Left-side chip group correctly applies aria-hidden={store.mode !== "normal"}

All patterns match the documented learnings from PR #208.


1149-1202: LGTM!

The model control styling updates implement the unified chip design with consistent border styling, fixed width (w-48) for truncation of long model IDs, and centered truncated labels.

Comment thread packages/app/e2e/app/composer-parity.spec.ts Outdated
Comment thread packages/app/e2e/app/home.spec.ts Outdated
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch 3 times, most recently from 4c76184 to 4b5937f Compare April 24, 2026 05:53
workspaceChipChoices now returns { path, branch? } instead of plain
strings so consumers can surface sub-labels when needed (branch left
undefined when the SDK's worktree.list doesn't yield it). Consumer
in workspace-chip.tsx updated for the new shape.

WorkspaceChip popover refinements:
- trigger uses folder icon + chevron; border-strong-base gives a
  visible chip outline; hover brightens to background-base-hover
- data-action='prompt-workspace' exposed on trigger for E2E
- popover container matches model selector chrome (p-2,
  rounded-md, surface-raised-stronger-non-alpha, shadow-md)
- rows use text-14-medium with py-1.5 + surface-raised-base-hover,
  matching List item proportions
- active row carries a check Icon with data-icon='check'
- fetch worktree list eagerly on mount (not on popover open) so
  the list no longer flashes empty-to-full when clicked
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch 3 times, most recently from 83d980e to 7ae85d6 Compare April 24, 2026 06:00

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/app/src/components/prompt-input.tsx (1)

1208-1273: 🧹 Nitpick | 🔵 Trivial

Good implementation of menu semantics.

The variant popover correctly uses role="menu" / role="menuitemradio" with aria-checked, addressing the prior review about listbox keyboard requirements.

Consider adding aria-label to the menu container for consistency.

The workspace chip's menu container has aria-label={language.t("workspace.chip.popover.title")}, but the variant menu at line 1244 is just <div role="menu">. Adding an aria-label would improve screen reader announcements and maintain consistency.

♿ Suggested improvement
-          <div role="menu">
+          <div role="menu" aria-label={language.t("command.model.variant.cycle")}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/components/prompt-input.tsx` around lines 1208 - 1273, Add
an accessible name to the variant popover menu by giving the <div role="menu">
inside renderVariantControl an aria-label using the i18n helper (e.g.
aria-label={language.t("command.model.variant.popover.title")}) so screen
readers announce the control consistently; update the div with role="menu" in
renderVariantControl (the popup that maps over variants() and uses
local.model.variant.current()) to include this aria-label.
🤖 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/e2e/app/composer-parity.spec.ts`:
- Around line 22-24: The test is collecting parity sets immediately which can
yield false positives because evaluateAll may return empty arrays if the
composer bars haven't rendered; before calling collectBarSet with the locator
named homeBar (from sessionComposerDockSelector) add an explicit visibility wait
(e.g., use Playwright's expect(homeBar).toBeVisible() or expect.poll on the
locator) and do the same for the corresponding mirrored locator used later (the
other collectBarSet calls around the same area) so collectBarSet only runs after
the composer dock(s) are visible.

In `@packages/app/e2e/app/composer-variant-locale.spec.ts`:
- Around line 7-11: The helper assertAllRenderedInSet races the popover mount
because it reads menuitemradio count immediately; update it to wait for the
menu/popover or items to appear before counting—e.g., use the existing
Playwright expectations like await expect(menu).toBeVisible() or await
expect.poll(() => menu.getByRole("menuitemradio").count()).toBeGreaterThan(0)
inside assertAllRenderedInSet so the subsequent count and assertions run only
after the variant menu is mounted.

---

Outside diff comments:
In `@packages/app/src/components/prompt-input.tsx`:
- Around line 1208-1273: Add an accessible name to the variant popover menu by
giving the <div role="menu"> inside renderVariantControl an aria-label using the
i18n helper (e.g.
aria-label={language.t("command.model.variant.popover.title")}) so screen
readers announce the control consistently; update the div with role="menu" in
renderVariantControl (the popup that maps over variants() and uses
local.model.variant.current()) to include this aria-label.
🪄 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: f4afe24e-5cf9-45d9-bded-0bd00129562b

📥 Commits

Reviewing files that changed from the base of the PR and between 5d810aa and 83d980e.

📒 Files selected for processing (21)
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/opencode/src/provider/models-snapshot.js
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/ui/src/components/dock-surface.css
📜 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-app
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-desktop
  • GitHub Check: unit-app
  • GitHub Check: typecheck
  • 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/app/**/*.{ts,tsx,js,jsx}

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

Always prefer createStore over multiple createSignal calls in SolidJS

Files:

  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
packages/app/e2e/**/*.spec.ts

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

packages/app/e2e/**/*.spec.ts: Import test utilities from ../fixtures instead of @playwright/test
Test files should be named with the pattern feature-name.spec.ts
Use lowercase, descriptive test names (e.g., 'sidebar can be toggled')
Use camelCase for variable names in tests
Use SCREAMING_SNAKE_CASE for constants in tests
Use fixture-managed cleanup with withSession(sdk, title, callback) for temporary sessions instead of calling sdk.session.delete(...) directly
Prefer the project fixture for tests that need a dedicated project with LLM mocking
Use data-component, data-action, or semantic roles for selectors instead of CSS class names or IDs
Use modKey from utils for cross-platform keyboard shortcuts (Meta on Mac, Control on Linux/Windows)
In terminal tests, type through the browser using runTerminal() and waitTerminalReady() instead of writing to the PTY through the SDK
Never use wall-clock waits like page.waitForTimeout(...) to make a test pass
Wait on observable state with expect(...), expect.poll(...), or existing helpers instead of assuming work is finished after an action
Use locator assertions like toBeVisible(), toHaveCount(0), and toHaveAttribute(...) for normal UI state verification
Use expect.poll(...) for probing mock or backend state rather than transient DOM visibility
Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests
Use direct locators when the interaction is simple and a helper would not add clarity
When validating routing, assert against canonical or resolved workspace slugs using shared helpers from ../actions to account for Windows canonicalization
Test one feature per test file
Call project.trackSession(sessionID, directory?) and project.trackDirectory(directory) for any resources created outside the fixture so teardown can clean them up

Files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
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/config/e2e-smoke-tagging.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/config/e2e-smoke-tagging.test.ts
🧠 Learnings (49)
📓 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: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:36.205Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.
📚 Learning: 2026-04-24T05:48:36.205Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:36.205Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.

Applied to files:

  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 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/prompt-input/slash-popover.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.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/components/prompt-input/slash-popover.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.

Applied to files:

  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/components/prompt-input/workspace-chip-helpers.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.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/session/session-header.tsx
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Test one feature per test file

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 lowercase, descriptive test names (e.g., 'sidebar can be toggled')

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 : Prefer fluent helpers and drivers when they make intent obvious and reduce locator-heavy noise in tests

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 : Test files should be named with the pattern `feature-name.spec.ts`

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests under packages/app/e2e, do not manually call `project.trackSession(sessionID)` when you obtain a `sessionID` via `project.prompt(text)`. The `project.prompt()` implementation already registers `trackSession(next.sessionID, active.directory)` automatically after the prompt submission is observed and the active session is resolved, so calling `project.trackSession(sessionID)` again will create duplicate session ownership/teardown handling.

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.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 : When validating routing, assert against canonical or resolved workspace slugs using shared helpers from `../actions` to account for Windows canonicalization

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/src/components/prompt-input/workspace-chip.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/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.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 fixture-managed cleanup with `withSession(sdk, title, callback)` for temporary sessions instead of calling `sdk.session.delete(...)` directly

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.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 camelCase for variable names in tests

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.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} : 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.

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/session.spec.ts
📚 Learning: 2026-04-24T05:48:36.205Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:36.205Z
Learning: In E2E parity tests, prefer using the existing `[data-action]` coverage when asserting UI parity. For elements whose trigger props set `data-action` (e.g., `data-action="prompt-model"` and `data-action="prompt-model-variant"` on prompt input chip triggers), you generally do not need to add separate assertions driven by `[data-component]` for parity. Avoid duplicating component-specific queries when the `[data-action]` selector sweep already includes the elements; any extra unioning of selectors should be treated as optional belt-and-suspenders rather than required.

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `expect.poll(...)` for probing mock or backend state rather than transient DOM visibility

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
📚 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/e2e/app/home.spec.ts
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-24T00:02:50.599Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 203
File: packages/app/e2e/sidebar/sidebar-session-links.spec.ts:34-55
Timestamp: 2026-04-24T00:02:50.599Z
Learning: For Astro-Han/pawwork E2E tests under packages/app/e2e/**/*.spec.ts, do not call project.trackDirectory() or project.trackSession() before project.open() has run. The project fixture throws until open() initializes internal state. Use this ordering pattern: (1) call project.trackSession(sessionID) inside the beforeGoto callback (where state is already available), (2) call project.trackDirectory(directory) and any cross-workspace tracking like project.trackSession(id, directory) immediately after project.open() returns, and (3) if you create any resources before open() that cannot yet be tracked via the fixture, ensure you clean them up explicitly in finally blocks (e.g., cleanupSession / cleanupTestProject).

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.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:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-23T08:51:04.230Z
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:04.230Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/util/filesystem.ts`), the `Filesystem` utility does NOT expose a `remove` or `unlink` helper. The established repository pattern for auth.json teardown in tests (e.g. `provider.test.ts`, `amazon-bedrock.test.ts`, `workspace-adaptor.test.ts`) is to combine `Filesystem.write` with `node:fs/promises unlink`. Do not flag this mixed usage as inconsistent — it is the correct and intentional pattern.

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
📚 Learning: 2026-04-23T15:25:27.182Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/e2e/sidebar/sidebar-leading-slot.spec.ts:5-55
Timestamp: 2026-04-23T15:25:27.182Z
Learning: In Astro-Han/pawwork E2E tests (e.g., *.spec.ts under packages/app/e2e), reaching a real "running" session state is not achievable with the bare `sdk` fixture. Use the `project` fixture (to bootstrap the model) and orchestrate the transition with `llm.wait(1)`; even if you set `agent: "build"` and a `system` prompt via `sdk.session.promptAsync`, the current test infrastructure does not trigger an actual LLM call, so it won’t simulate "running" cheaply. Review any attempt to mock/force "running" using only `sdk` as likely ineffective unless it also uses `project` + `llm.wait(1)`.

Applied to files:

  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 : In terminal tests, type through the browser using `runTerminal()` and `waitTerminalReady()` instead of writing to the PTY through the SDK

Applied to files:

  • packages/app/e2e/app/composer-send.spec.ts
  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.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 : Wait on observable state with `expect(...)`, `expect.poll(...)`, or existing helpers instead of assuming work is finished after an action

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.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 : Prefer the `project` fixture for tests that need a dedicated project with LLM mocking

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.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 : Call `project.trackSession(sessionID, directory?)` and `project.trackDirectory(directory)` for any resources created outside the fixture so teardown can clean them up

Applied to files:

  • packages/app/e2e/app/composer-agent-syntax.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/app/e2e/app/composer-workspace-chip.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.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 SCREAMING_SNAKE_CASE for constants in tests

Applied to files:

  • packages/app/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/session.spec.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
📚 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/e2e/app/composer-shell-mode.spec.ts
  • packages/app/e2e/app/composer-variant-locale.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.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} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/app/e2e/app/session.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.test.ts
  • packages/opencode/test/config/e2e-smoke-tagging.test.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

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

Applied to files:

  • packages/app/src/components/prompt-input/workspace-chip.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 direct locators when the interaction is simple and a helper would not add clarity

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/app/e2e/app/composer-parity.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
📚 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/app/e2e/app/composer-parity.spec.ts
  • packages/app/e2e/app/a11y-chip.spec.ts
  • packages/app/src/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations

Applied to files:

  • packages/opencode/test/config/e2e-smoke-tagging.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/config/e2e-smoke-tagging.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/e2e/app/composer-variant-locale.spec.ts
📚 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/e2e/app/composer-variant-locale.spec.ts
📚 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/components/prompt-input/workspace-chip.tsx
  • packages/app/src/components/prompt-input.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/app/src/components/prompt-input.tsx

Comment thread packages/app/e2e/app/composer-parity.spec.ts
Comment thread packages/app/e2e/app/composer-variant-locale.spec.ts
Core structural change:
- collapse homeMode Show/fallback split in prompt-input.tsx; single
  bottom bar renders for both surfaces
- remove DockTray overlay (Agent selector + standalone shell-mode
  label); shell mode now surfaces via existing placeholder.ts
  early-return (visible once DockTray is gone)
- WorkspaceChip renders only when homeMode is true
- drop unused imports (DockTray, IconButton) and memos (control,
  shell, agentNames)

Bar layout and chip styling:
- chips ordered: +, Model, Variant, Workspace | Send (right)
- all three chips share h-[32px] px-3 rounded-lg text-14-medium
  border-border-strong-base with transition-colors hover
- Variant rewritten from @opencode-ai/ui Select to Popover so
  it uses the same popover chrome as Model and Workspace
- Model chip has fixed w-48, provider icon pinned left, name
  centered in remaining flex area, long names truncate
- ModelSelectorPopover placement top-start -> bottom-start so
  all three popovers prefer down (auto-flip to up at screen
  edges)

Composer dimensions and polish:
- dock-surface.css radius 12 -> 18, adds xs drop shadow
- textarea min-h 104 -> 120, home max-w 680 -> 1200
- textarea pl-4 pr-4 pt-4 matches bottom-bar inset-x-4 bottom-3
- prompt-placeholder div carries data-component for e2e
- slash-popover root carries data-component='prompt-at-popover'
Existing SessionHeader already portals project/session breadcrumb
into the titlebar center slot. Add 'Open in Finder/Explorer' (or
platform equivalent) to the project button's title attribute so
hover surfaces the click affordance.
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch from 7ae85d6 to 6318ea9 Compare April 24, 2026 06:03
@Astro-Han

Copy link
Copy Markdown
Owner Author

Addressed the outside-diff review items in 6318ea9 (autosquashed):

Fixed (Major):

  • prompt-input.tsx:1489-1496onMouseDown editor-refocus guard widened from [data-action="prompt-attach"], [data-action="prompt-submit"] to [data-action], button, [role="button"], so clicking the new Model / Variant / Workspace chips doesn't steal focus back to the editor before their popovers open.

Already fixed in earlier rounds (comment source was a retrospective duplicate of inline items):

  • listbox → menu role on Variant + Workspace popovers (a11y correctness)
  • composer-parity uses Set + includes data-component (belt-and-suspenders)
  • variant-locale: SCREAMING_SNAKE_CASE constants + strengthened assertion (all rendered items must be in localized set)
  • composer-workspace-chip: deterministic outside-click target (home heading)
  • a11y-chip: guards empty targetLabel before toContainText
  • composer-agent-syntax: waits for [data-component="prompt-at-popover"] visibility before Enter
  • session.spec.ts / composer-shell-mode.spec.ts: lowercase titles
  • expectedSmokeTests inventory synced for both new @smoke titles
  • workspace-chip: active comparison normalized via workspaceKey()
  • workspace chip trigger label max-w-[120px] truncate
  • SendButton stays visible + clickable in shell mode (removed opacity animation + mode gate from disabled)
  • WorkspaceChip now hidden in shell mode too (shell mode has no workspace affordance)

Out of scope:

  • session-composer-region.tsx:82-86createStore → createSignal nitpick. Code is pre-existing, not touched by this PR. Leaving for a separate cleanup PR.

PR still 6 commits via autosquash. commitlint CI green.

Adds @smoke assertions to home.spec.ts and session.spec.ts asserting
the unified bar structure, no DockTray, Model/Variant/Send present,
and send button brand orange bg (after typing a char to enable).

New specs:
- composer-parity: home and session bars expose the same set of
  data-action attributes except prompt-workspace (home-only)
- composer-send: disabled on empty, enabled with input
- composer-workspace-chip: popover opens, active row carries
  check icon, hidden in session, outside-click / Esc dismiss
- composer-variant-locale: zh + en labels localize via i18n
- composer-agent-syntax: @ triggers prompt-at-popover and Enter
  inserts an agent pill
- composer-shell-mode: ! switches placeholder to shell label,
  Esc restores default

a11y-chip.spec.ts updated for new popover item structure
(name is first span, not nested div).
@Astro-Han Astro-Han force-pushed the feat/composer-unify branch from 6318ea9 to d71168c Compare April 24, 2026 06:13
@Astro-Han Astro-Han merged commit 1b0a8b8 into dev Apr 24, 2026
22 of 23 checks passed
@Astro-Han Astro-Han deleted the feat/composer-unify branch April 24, 2026 06:22
Astro-Han added a commit that referenced this pull request Apr 24, 2026
Two coordinated layout adjustments so the composer reads as a floating input surface rather than as a wall-to-wall band.

Home composer: session-new-view.tsx wrapper bumped from max-w-[640px] to max-w-[760px] during the PR #208 composer unification to fit the worst-case one-row chip layout. After the model chip narrowed to w-44 (d27bc88) the worst-case budget (attach 32 + gaps 24 + model 176 + variant 140 + workspace 188 + gap-8 32 + send 32 = 624 plus 32 inset = 656) fits comfortably in 688 available at max-w-[720px] with 32px slack. Tighten from 760 to 720 so the home composer no longer floats in empty horizontal space on wider windows.

Session composer: session-composer-region.tsx shared the message-timeline's responsive 'md:max-w-200 md:mx-auto 2xl:max-w-[1000px]' so the composer sat edge-to-edge with the messages above it. Subtract 80px at each breakpoint: 'md:max-w-[720px] 2xl:max-w-[920px]'. The composer now insets 40px from each side of its message column and matches the home composer width (720) at typical md viewports. Below md, both the messages and the composer continue to fill the viewport, so nothing changes there.
Astro-Han added a commit that referenced this pull request Apr 24, 2026
refactor: composer and message surface polish after #208
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request P1 High priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Home visual polish to match mockup

1 participant