fix(app): stabilize session composer dock scrolling#354
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
🧰 Additional context used📓 Path-based instructions (1)packages/app/**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Files:
🧠 Learnings (6)📓 Common learnings📚 Learning: 2026-04-29T04:31:25.068ZApplied to files:
📚 Learning: 2026-04-30T14:25:19.902ZApplied to files:
📚 Learning: 2026-04-30T10:26:59.483ZApplied to files:
📚 Learning: 2026-04-23T07:23:23.849ZApplied to files:
📚 Learning: 2026-04-23T15:10:21.635ZApplied to files:
🔇 Additional comments (2)
📝 WalkthroughWalkthroughRefactors the session page into a composition root and extracts multiple session controllers; centralizes composer-dock height ownership and bottom-scroll compensation; adds scroll-dock, timeline/history, active-message, followups, revert, review, desktop-context, comment-context, keyboard-focus controllers and tests (including an E2E verifying dock expansion behavior). Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ScrollDock as ScrollDock
participant DOM
participant RAF as RAF/Timer
User->>ScrollDock: prompt dock height changes
ScrollDock->>ScrollDock: measure previousDockHeight, compute nextDockHeight
ScrollDock->>ScrollDock: shouldStickToBottomAfterDockResize(userScrolled?)
alt user at bottom
ScrollDock->>DOM: setCssHeight(nextDockHeight)
ScrollDock->>RAF: forceScrollToBottom()
RAF->>DOM: set scrollTop = scrollHeight
else user scrolled up
ScrollDock->>DOM: setCssHeight(nextDockHeight)
ScrollDock->>DOM: scheduleScrollState() (no forced scroll)
end
DOM->>ScrollDock: report clientHeight, scrollHeight, scrollTop
ScrollDock->>ScrollDock: calculateSessionScrollState(...) -> update store (overflow/bottom/jump)
sequenceDiagram
participant Page as Session Page
participant Followups as Followups Hook
participant Storage as Workspace/Sync
participant Client as SDK Client
Page->>Followups: createSessionFollowups(session context)
Followups->>Storage: persist queued followup
Followups->>Followups: evaluate shouldAutoSendFollowup(...)
alt auto-send allowed
Followups->>Client: sendFollowupDraft()
Client->>Server: POST /followup
Server-->>Client: response
alt success
Client-->>Followups: ack -> remove persisted item
else failure
Client-->>Followups: error -> mark failed, optionally retry
end
else gated
Followups->>Followups: defer until conditions clear
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 8/10 reviews remaining, refill in 7 minutes and 17 seconds. Comment |
There was a problem hiding this comment.
Code Review
This pull request refactors the session page by extracting complex logic into modular hooks, including management for desktop context, followups, history windows, revert actions, review states, and scroll dock behavior. It also introduces a new E2E test for composer dock scrolling. The review feedback highlights several improvement opportunities: ensuring SolidJS ref callbacks handle null/undefined values to prevent runtime errors, clearing global CSS variables during component cleanup to avoid side effects, and replacing 'any' types with concrete TypeScript interfaces in the new hooks to restore type safety.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/session/session-composer-dock.spec.ts`:
- Around line 969-988: The test reads `afterUserScroll` immediately after
`dock.open(...)`, which can race with dock resize/scroll; change the direct
`page.evaluate` call to poll until the viewport metrics settle using
Playwright's `expect.poll` (or `expect(...).poll`) to repeatedly evaluate the
same function that queries '[data-component="scroll-viewport"]' and returns `{
scrollTop, distanceFromBottom }`, and assert/poll for `scrollTop` to be <
`before` and `distanceFromBottom` to be >= `distanceBeforeExpansion - 40` (or
poll `distanceFromBottom` first then assert `scrollTop`) so the test waits for
the dock's resize/scroll compensation before making final assertions on
`afterUserScroll`.
In `@packages/app/src/pages/session/use-session-revert.test.ts`:
- Around line 10-18: The test and implementation rely on lexicographic
comparison of message IDs causing incorrect slicing; update the
rolledRevertItems implementation to find the index of the message whose id
equals revertMessageID and slice the messages array by index (i.e., include
items after that index) instead of using string comparison (remove any `id >=
revertMessageID` style logic in rolledRevertItems), and update the
use-session-revert.test.ts case to use non-lexical IDs (e.g., "x1","a2","b3" or
random GUID-like strings) to assert index-based behavior matches expected order.
In `@packages/app/src/pages/session/use-session-review-state.ts`:
- Around line 45-47: The state uses createStore for a single independent field
"changes"; replace the createStore/createStore-set pattern with a
createSignal<ReviewChangeMode>("turn") to simplify state: remove createStore and
setStore, introduce a signal (e.g., const [changes, setChanges] =
createSignal<ReviewChangeMode>("turn")) and update all usages of store.changes
and setStore to use changes() and setChanges(...) respectively; keep the
ReviewChangeMode type and behavior unchanged.
- Around line 179-191: The two createEffect blocks (watching
input.sessionID()/input.turnDiffs() and input.sync.data.session_diff[id]) can
both call refetchArtifactHistory() and cause duplicate fetches; consolidate them
into a single createEffect that reads input.sessionID(), input.turnDiffs(), and
input.sync.data.session_diff[id] together and only calls
refetchArtifactHistory() once when the combined guard conditions pass, or add a
short debounce/one-shot guard (e.g., a local lastRefetchSessionID or a timeout)
inside that unified effect to ensure back-to-back triggers for the same session
do not call refetchArtifactHistory() twice. Ensure you reference the existing
symbols createEffect, input.sessionID(), input.turnDiffs(),
input.sync.data.session_diff and refetchArtifactHistory when making the change.
In `@packages/app/src/pages/session/use-session-scroll-dock.test.ts`:
- Around line 109-137: The test "syncs composer height through one path and
scrolls once when sticky" modifies the global document.documentElement.style by
setting the "--composer-dock-height" CSS property, but does not clean up after
itself. Add a cleanup step after the test assertions to remove the
"--composer-dock-height" property using removeProperty method on
document.documentElement.style to prevent this mutation from affecting other
tests. Consider using an afterEach hook if this pattern is repeated across
multiple tests, or add cleanup directly at the end of this test function.
🪄 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: 8fe63b45-8370-43a1-992a-755e5a2a0ea0
📒 Files selected for processing (16)
packages/app/e2e/session/session-composer-dock.spec.tspackages/app/src/components/prompt-input/submit.test.tspackages/app/src/pages/session.tsxpackages/app/src/pages/session/message-timeline.tsxpackages/app/src/pages/session/use-session-desktop-context.test.tspackages/app/src/pages/session/use-session-desktop-context.tspackages/app/src/pages/session/use-session-followups.test.tspackages/app/src/pages/session/use-session-followups.tspackages/app/src/pages/session/use-session-history-window.test.tspackages/app/src/pages/session/use-session-history-window.tspackages/app/src/pages/session/use-session-revert.test.tspackages/app/src/pages/session/use-session-revert.tspackages/app/src/pages/session/use-session-review-state.test.tspackages/app/src/pages/session/use-session-review-state.tspackages/app/src/pages/session/use-session-scroll-dock.test.tspackages/app/src/pages/session/use-session-scroll-dock.ts
|
Updated with the follow-up session page split pass. What changed:
Verification rerun locally:
Note: |
|
Follow-up split pushed. Additional changes:
Verification rerun locally:
|
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/pages/session.tsx`:
- Around line 253-275: The code uses a forward-reference where
createSessionScrollDock is passed clearActiveMessage: () =>
activeMessage?.clearActiveMessage() before activeMessage is assigned, which
relies on the callback being called later; add a concise inline comment next to
the scrollDock creation (near scrollDock, createSessionScrollDock, and the
clearActiveMessage argument) explaining that activeMessage is intentionally
assigned after scrollDock via createSessionActiveMessage and that the closure is
safe because the callback is only invoked after activeMessage is initialized;
reference activeMessage, createSessionScrollDock, and createSessionActiveMessage
in the comment for clarity.
In `@packages/app/src/pages/session/session-main-view.tsx`:
- Around line 86-119: The Switch branch currently gates only on
props.activeSessionID so it can win before props.timelineSessionID is set,
leaving the nested Show empty and preventing NewSessionView from rendering;
change the condition that selects the MessageTimeline branch so it requires both
props.activeSessionID and props.timelineSessionID (e.g. make the Match predicate
check both) and/or move the timeline branch to explicitly check
props.timelineSessionID before rendering MessageTimeline (referencing Match,
Show, MessageTimeline, props.activeSessionID, props.timelineSessionID, and
NewSessionView) so the timeline branch only wins when both IDs are present.
In `@packages/app/src/pages/session/use-session-comment-context.ts`:
- Around line 59-62: The update payload omits intentionally empty-string
previews because the current truthy check on comment.preview skips "" — change
the guard to check for undefined instead of falsiness so updateComment receives
preview when it's an empty string; specifically update the call to
input.promptContext.updateComment (the object merged from comment.comment and
preview) to include preview whenever comment.preview !== undefined (or use
hasOwnProperty) rather than using a truthy check.
In `@packages/app/src/pages/session/use-session-review-panel.tsx`:
- Around line 219-229: The effect on input.activeFileTab that unconditionally
calls showAllFiles() when fileTreeTab() === "changes" is too broad and should be
removed or narrowed; instead, rely on createOpenReviewFile() to trigger
showAllFiles() for the explicit open-from-review path. Locate the
createEffect/on block that references input.activeFileTab, fileTreeTab(), and
showAllFiles() and delete it (or replace its logic with a more specific guard
tied to the review-open flow), ensuring that only createOpenReviewFile() calls
showAllFiles() for review opens and normal tab switches do not flip the explorer
to "all".
In `@packages/app/src/pages/session/use-session-route-tabs.ts`:
- Around line 7-8: The types for layout and tabs are currently declared as loose
any; replace them with explicit interfaces and use those in useSessionRouteTabs:
define an interface (e.g., SessionLayout) for layout that includes the handoff
object with methods like tabs: () => ReturnType and clearTabs: () => void, and a
tabs accessor signature (e.g., TabsAccessor) that maps keys to a specific Tab
type or structure; then change the declarations from "layout: any" and "tabs:
any" to "layout: SessionLayout" and "tabs: TabsAccessor" (or appropriate names)
and update any call sites in useSessionRouteTabs to match the narrowed
method/return types so the compiler enforces the expected structure.
In `@packages/app/src/pages/session/use-session-timeline-data.ts`:
- Around line 16-17: The loose any types for the variables sync and local reduce
type-safety; replace them with narrow structural interfaces that reflect the
properties actually used (for example declare an interface like SyncRecord {
session: Session; data: Record<string, unknown> } and a LocalRecord { /* fields
used from local */ } or reuse existing Session/Timeline types), update the
declarations of sync and local to those interfaces, and adjust any consumers of
useSessionTimelineData, getSyncData, or related functions to accept/return the
new types so TypeScript catches mismatches at compile time.
In `@packages/app/src/pages/session/use-session-vcs-refresh.ts`:
- Around line 4-15: The input types to useSessionVcsRefresh are too loose:
tighten branch and defaultBranch from () => unknown to () => string (or a
specific BranchName type) and replace load(mode: any, force: true) with
load(mode: Mode | undefined, force: true) where Mode is a union of the allowed
string modes (e.g., "view" | "edit" | "review") or string if more generic; also
change mode(): string | undefined to mode(): Mode | undefined so all callers and
the useSessionVcsRefresh signature share the same explicit Mode type. Update the
function signature and any call sites inside useSessionVcsRefresh that use
branch, defaultBranch, mode, or load to use these narrowed types and adjust any
runtime guards if necessary.
🪄 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: 9b2b9ebe-c42b-4928-94dc-0b17c6955711
📒 Files selected for processing (12)
packages/app/src/pages/session.tsxpackages/app/src/pages/session/session-composer-region.tsxpackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-active-message.tspackages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-comment-context.tspackages/app/src/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session/use-session-refresh-effects.tspackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-timeline-data.tspackages/app/src/pages/session/use-session-vcs-refresh.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: unit-windows-opencode-session
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: unit-windows-app
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-desktop
- GitHub Check: unit-opencode
- GitHub Check: smoke-macos-arm64
- GitHub Check: analyze-js-ts
- GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (1)
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/use-session-timeline-data.tspackages/app/src/pages/session/session-composer-region.tsxpackages/app/src/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-comment-context.tspackages/app/src/pages/session/use-session-vcs-refresh.tspackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-refresh-effects.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-active-message.tspackages/app/src/pages/session.tsx
🧠 Learnings (35)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 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-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.
Applied to files:
packages/app/src/pages/session/use-session-timeline-data.tspackages/app/src/pages/session/session-composer-region.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-vcs-refresh.tspackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-refresh-effects.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-active-message.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T18:53:46.234Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 308
File: packages/opencode/test/server/global-session-list.test.ts:224-259
Timestamp: 2026-04-28T18:53:46.234Z
Learning: In `packages/opencode/test/server/global-session-list.test.ts` (Astro-Han/pawwork), all `it.live(...)` test bodies intentionally use `Effect.promise(async () => { ... })` rather than `Effect.gen(function* () { ... })`. This is the established local convention for the route/pagination tests in this file (e.g., "supports cursor pagination", "session route orders by creation time", "session routes omit undefined optional fields"). Do NOT flag individual `it.live` cases in this file as needing conversion to `Effect.gen`; any harness-style migration would need to cover the entire file.
Applied to files:
packages/app/src/pages/session/use-session-timeline-data.tspackages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-refresh-effects.tspackages/app/src/pages/session/use-session-active-message.ts
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.
Applied to files:
packages/app/src/pages/session/use-session-timeline-data.tspackages/app/src/pages/session/session-composer-region.tsxpackages/app/src/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-comment-context.tspackages/app/src/pages/session/use-session-vcs-refresh.tspackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-refresh-effects.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-active-message.tspackages/app/src/pages/session.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/use-session-timeline-data.tspackages/app/src/pages/session/session-composer-region.tsxpackages/app/src/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-comment-context.tspackages/app/src/pages/session/use-session-vcs-refresh.tspackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-refresh-effects.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-active-message.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-24T05:39:58.329Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/app/src/pages/session/session-composer-region.tsxpackages/app/src/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-30T14:25:19.902Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Applied to files:
packages/app/src/pages/session/session-composer-region.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use `modKey` from utils for cross-platform keyboard shortcuts (Meta on Mac, Control on Linux/Windows)
Applied to files:
packages/app/src/pages/session/use-session-keyboard-focus.ts
📚 Learning: 2026-04-24T03:51:56.211Z
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:56.211Z
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/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session/use-session-comment-context.test.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.
Applied to files:
packages/app/src/pages/session/use-session-keyboard-focus.tspackages/app/src/pages/session/use-session-active-message.ts
📚 Learning: 2026-04-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In pawwork’s Bun+Solid test environment (under packages/app/src/pages/session/**.test.ts), do not expect `createMemo((current) => ...)` to be recomputed/invalidated the same way it would in a real browser runtime. Avoid adding signal-driven transition tests that rely on Solid reactive invalidation paths in this environment (e.g., flipping inputs via `createSignal`/`createRoot` just to observe `createMemo` behavior), since it can be misleading. Instead, review/author tests as follows: (1) cover pure transition/state-update logic with plain unit tests (e.g., `nextSessionViewState`), and (2) cover the actual browser reactive behavior with E2E tests (e.g., the session-switch E2E spec). When reviewing this Bun+Solid test suite, do not treat the absence of signal-driven `createSessionViewController` tests as a missing-coverage gap.
Applied to files:
packages/app/src/pages/session/use-session-comment-context.test.ts
📚 Learning: 2026-04-28T11:24:35.312Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/test/session/subagent-lifecycle-integration.test.ts:41-47
Timestamp: 2026-04-28T11:24:35.312Z
Learning: In `packages/opencode/test/session/subagent-lifecycle-integration.test.ts` (Astro-Han/pawwork, PR `#287`), the test suite intentionally uses a manual `run` helper (`Effect.runPromise(program.pipe(Effect.provide(Layer.mergeAll(SubagentRun.defaultLayer, Session.defaultLayer)), Effect.orDie))`) and raw `bun:test` test cases rather than the `testEffect(...)` harness. All 27 lifecycle tests pass with this pattern. Migration to `testEffect` + `it.live(...)` is deferred to a dedicated test-harness sweep PR to avoid fixture drift before merge. Do NOT re-flag the manual `Effect.runPromise` runner in this file as needing harness migration until that sweep PR lands.
Applied to files:
packages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-refresh-effects.ts
📚 Learning: 2026-04-29T13:27:25.687Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/e2e/session/session-scroll-position.spec.ts:171-230
Timestamp: 2026-04-29T13:27:25.687Z
Learning: In `packages/app/e2e/session/session-scroll-position.spec.ts`, the `describe()` formatter is intentionally duplicated in `installPageErrorProbe` (inside `page.addInitScript`, runs in browser context) and `collectPageErrors` (runs in Node.js/Playwright context). These are separate execution contexts — `addInitScript` serializes its closure to a string for injection — so extracting a shared helper would add cross-context indirection. Do NOT flag this duplication as a maintenance issue.
Applied to files:
packages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-active-message.ts
📚 Learning: 2026-04-29T10:34:22.399Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 327
File: packages/opencode/test/pty/pty-session.test.ts:124-136
Timestamp: 2026-04-29T10:34:22.399Z
Learning: In `packages/opencode/test/pty/pty-session.test.ts` (Astro-Han/pawwork, PR `#327`), the `bun-pty` backend re-merges the parent process environment internally after `withoutInternalServerAuthEnv` strips auth values. This means `OPENCODE_SERVER_USERNAME` and `OPENCODE_SERVER_PASSWORD` will be **present-but-empty** (not fully unset) inside the PTY session. Using a `-unset` shell default expansion (e.g. `${OPENCODE_SERVER_USERNAME-unset}`) would never trigger, so asserting `username=unset` / `password=unset` would always fail. The correct assertion strategy is to check `expect(text).not.toContain("secret")` and `expect(text).not.toContain("PawWork")`. Do NOT suggest converting to `-unset` expansion or asserting `username=unset` / `password=unset` for PTY auth-env tests in this file.
Applied to files:
packages/app/src/pages/session/use-session-comment-context.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/pages/session/use-session-comment-context.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/src/pages/session/use-session-comment-context.test.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/src/pages/session/use-session-comment-context.test.ts
📚 Learning: 2026-04-28T04:56:21.338Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/test/tool/tool-define.test.ts:2-9
Timestamp: 2026-04-28T04:56:21.338Z
Learning: In `packages/opencode/test/tool/tool-define.test.ts` (Astro-Han/pawwork, PR `#270`), the test suite intentionally uses a `ManagedRuntime.make(Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer))` wrapper and raw `bun:test` `test(...)` cases rather than the `testEffect(...)` harness. This code was adopted wholesale from upstream via the graft strategy (per `project_upstream_strategy.md`). Migrating to `testEffect` is deferred to a follow-up PR so as not to mix refactor + harness-migration intents and to avoid drifting the diff from the upstream baseline needed for clean future grafts. Do NOT re-flag the `ManagedRuntime.make` usage in this file as needing harness migration until that follow-up lands.
Applied to files:
packages/app/src/pages/session/use-session-comment-context.test.ts
📚 Learning: 2026-04-30T10:41:33.826Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/desktop-electron/src/main/ipc.ts:342-344
Timestamp: 2026-04-30T10:41:33.826Z
Learning: In Astro-Han/pawwork, the `export-session` IPC handler in `packages/desktop-electron/src/main/ipc.ts` accepts an optional localized `title` argument for `dialog.showSaveDialog`, keeping `"Export session"` as an English fallback. The localized title is plumbed from the renderer through `platform.exportSession` → `window.api.exportSession` → preload (`preload/types.ts`, `preload/index.ts`, `renderer/index.tsx`) → main, mirroring the same shape as the `save-file-picker` handler. Both renderer callsites (`layout.tsx` and `message-timeline.tsx`) pass `language.t("session.export.action.export")`. Do NOT flag the hard-coded fallback as unlocalized — the renderer is responsible for supplying the localized title.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-comment-context.tspackages/app/src/pages/session/session-main-view.tsx
📚 Learning: 2026-04-28T04:38:11.727Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:35-45
Timestamp: 2026-04-28T04:38:11.727Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork, PR `#270`), the module-scoped `Map<string, Semaphore.Semaphore>` (`locks`) and `lock(filePath)` helper are intentionally left as a global, never-cleaned registry. PR `#270` is a pure upstream-sync graft and mixing in a bugfix would drift the diff from the upstream baseline. The fix (moving per-file semaphores into `InstanceState`/`ScopedCache` for per-instance lifecycle management) is tracked as a follow-up to land in its own PR or be reported upstream. Do NOT re-flag the absence of InstanceState wiring for the lock map in this file until the follow-up PR is opened.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsx
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics
Applied to files:
packages/app/src/pages/session/use-session-vcs-refresh.tspackages/app/src/pages/session/use-session-refresh-effects.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/src/pages/session/use-session-route-tabs.ts
📚 Learning: 2026-04-28T05:36:22.128Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/session/compaction.ts:169-191
Timestamp: 2026-04-28T05:36:22.128Z
Learning: In `packages/opencode/src/session/compaction.ts` (Astro-Han/pawwork), the internal helper `splitTurn` intentionally returns a raw `Effect.gen(...)` rather than being wrapped with `Effect.fnUntraced`. Converting `splitTurn` (and similar plain-Effect.gen internal helpers in this file, e.g. `turns`, `completedCompactions`, `buildPrompt`) to `Effect.fnUntraced` is a repo-wide convention sweep deferred to a dedicated follow-up PR. The same sweep covers `tool/tool.ts` and other helpers flagged in the same sync. Do NOT re-flag the absence of `Effect.fnUntraced` on `splitTurn` or other internal helpers in `compaction.ts` during upstream-sync PRs; flag it only in the dedicated convention-sweep PR.
Applied to files:
packages/app/src/pages/session/use-session-refresh-effects.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/pages/session/use-session-refresh-effects.ts
📚 Learning: 2026-04-25T12:52:49.735Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/share/session.ts:27-27
Timestamp: 2026-04-25T12:52:49.735Z
Learning: In `packages/opencode/src/share/session.ts`, the local `ensureEnabled` closure is intentionally duplicated from `ShareRuntime.ensureEnabled` in `runtime.ts`. Using the shared `ShareRuntime.ensureEnabled` effect directly would leak `CloudShareGate` back into the `R` (requirement) type of `share`, `unshare`, and `create`, because the shared effect re-yields the service rather than capturing an already-resolved gate reference. The current pattern resolves `gate` once at the top of the outer `Effect.gen` and then closes over it in a local `ensureEnabled`, keeping the inner effects' requirement types clean. A comment in the file points to `runtime.ts` to make the intentional duplication discoverable. The real fix would be refactoring `ensureEnabled` to accept a gate parameter, but that change is larger than the DRY benefit warrants. Do not flag this duplication as a maintenance issue.
Applied to files:
packages/app/src/pages/session/use-session-refresh-effects.ts
📚 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.addFinalizer` or `Effect.acquireRelease` inside the `InstanceState.make` closure for cleanup (subscriptions, process teardown, etc.)
Applied to files:
packages/app/src/pages/session/use-session-refresh-effects.ts
📚 Learning: 2026-04-25T12:52:37.850Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/session/export.ts:24-31
Timestamp: 2026-04-25T12:52:37.850Z
Learning: In `packages/opencode/src/session/export.ts`, the `hashFile` async helper intentionally uses raw `node:fs/promises` (`fs.readFile`) rather than `FileSystem.FileSystem`. It is a small, self-contained helper called inside `Effect.promise(...)` with broad failure tolerance (try/catch returning `undefined`). Using `FileSystem.FileSystem` here would add unnecessary layers without payoff. The `FileSystem.FileSystem` preference applies to Effect-service-style operations, not thin async boundary helpers like `hashFile`.
Applied to files:
packages/app/src/pages/session/use-session-refresh-effects.ts
📚 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 : For background loops or scheduled tasks, use `Effect.repeat` or `Effect.schedule` with `Effect.forkScoped` in the layer definition
Applied to files:
packages/app/src/pages/session/use-session-refresh-effects.ts
📚 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.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually
Applied to files:
packages/app/src/pages/session/use-session-refresh-effects.ts
📚 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/pages/session/use-session-refresh-effects.ts
📚 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.forkScoped` inside the `InstanceState.make` closure for background stream consumers — the fiber is interrupted when the instance is disposed
Applied to files:
packages/app/src/pages/session/use-session-refresh-effects.ts
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-25T12:52:35.631Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/desktop-electron/src/main/ipc.ts:238-263
Timestamp: 2026-04-25T12:52:35.631Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/ipc.ts`), `deps.getServerReadyData()` (backed by `serverReady.promise` in `index.ts`) resolves once at server startup and remains settled; it is not expected to reject in practice. Do not flag the absence of a try-catch around it in the `export-session` IPC handler — the network/fetch layer in `server-client.ts` already has a 10-second AbortController timeout and returns a typed `{ok: false, error}` payload, covering the real failure modes.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T13:10:01.345Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/src/tool/agent.ts:89-102
Timestamp: 2026-04-28T13:10:01.345Z
Learning: In `packages/opencode/src/tool/agent.ts` (Astro-Han/pawwork, PR `#287`), `makeReadLastCompletedAssistantText` intentionally scans the full child session message history (no `limit`) to find the latest completed assistant text part for `partial_result` on cancellation. Cancellation is a rare path so the extra read cost is acceptable; tool-heavy children can push the last stable assistant text far back. Do NOT suggest re-adding a fixed small limit (e.g., `limit: 5`) to this function.
Applied to files:
packages/app/src/pages/session.tsx
🔇 Additional comments (17)
packages/app/src/pages/session/use-session-comment-context.test.ts (1)
4-84: Coverage for add/update/remove synchronization looks solidThe tests validate the core dual-write behavior (
comments+promptContext) and preview derivation path in a focused, readable way.packages/app/src/pages/session/use-session-timeline-data.ts (2)
101-122: LGTM!The effects are well-structured: the first syncs the local model when
lastUserMessagechanges (with properontracking), and the second handles session reset on route changes with appropriate deferral and identity checks.
70-78: No action needed — message IDs are lexicographically sortable.MessageIDs are generated using a timestamp-prefixed format (
msg_+ 12-character hex encoding of timestamp+counter + random suffix). The hex portion encodes a monotonically increasing value, ensuring that string comparison (m.id < revert) correctly orders messages by creation time. The filter logic works as intended.packages/app/src/pages/session/session-composer-region.tsx (1)
7-26: LGTM!Clean typed wrapper that constrains the
SessionComposerRegionprop interface for the session page context. The prop types are well-defined with appropriate optionality.packages/app/src/pages/session/use-session-route-tabs.ts (1)
34-68: LGTM!The tab handoff effect is well-guarded: it validates freshness (60s TTL), ensures identity match, avoids overwriting existing tabs, and properly cleans up both handoff and workspace state after transfer.
packages/app/src/pages/session/use-session-keyboard-focus.ts (2)
14-66: LGTM!Well-structured keyboard focus handler with proper considerations:
- Shadow DOM traversal for active element detection
- Multiple protection layers (
data-prevent-autofocus, editable targets, dialog state)- Clean separation of scroll gesture keys vs. printable character focus
- Terminal focus prioritization when opened
68-70: LGTM!Using
makeEventListenerfrom@solid-primitives/event-listenerinsideonMountensures automatic cleanup when the component unmounts.packages/app/src/pages/session/use-session-refresh-effects.ts (2)
20-51: LGTM!The session refresh effect properly handles:
- Cancellation of pending work on dependency change
- Staleness detection via prefetch TTL
- Deferred forced sync using RAF + setTimeout(0) to batch with frame timing
- Identity check before executing stale refresh
53-88: LGTM!Todo refresh effect mirrors the session refresh pattern with appropriate gating on session status and blocked state. The
onCleanupproperly cancels all four scheduled operations.packages/app/src/pages/session/use-session-active-message.ts (3)
13-20: LGTM!The store groups related active-message state (
messageId,pendingMessage,scrollGesture). The mutablescrollMarkandmessageMarkvariables appropriately track gesture timing without triggering reactivity.
27-56: LGTM!The geometry-based cursor detection is well-implemented:
- Queries all
[data-message-id]elements within the scroller- Filters to visible elements intersecting the viewport
- Prioritizes elements crossing the reference line, then nearest, then falls back gracefully
58-75: LGTM!Navigation correctly determines the current index based on whether the user has scrolled since the last message selection, handles boundary cases (past-end triggers
resumeScroll), and pauses auto-scroll when navigating to a specific message.packages/app/src/pages/session.tsx (3)
140-144: LGTM!The page-level store consolidates UI state appropriately. Per project learnings,
createStoreis preferred when fields share a logical domain (page-level view state), even if they're not always updated together.
97-130: LGTM!Clean integration of
createSessionTimelineData. The destructuring of timeline accessors into local constants improves readability while maintaining reactivity.
536-572: LGTM!The
SessionMainViewintegration cleanly passes composed state and handlers. The prop drilling is explicit and traceable, which is appropriate for a composition root.packages/app/src/pages/session/use-session-vcs-refresh.ts (2)
45-55: LGTM!The file watcher integration properly:
- Filters to
file.watcher.updatedevents- Safely extracts the file path from properties
- Ignores
.git/internal file changes- Cleans up the listener via
onCleanup
16-43: LGTM!The refresh logic correctly uses
untrackto readmodeandwantsReviewwithout creating dependencies, and the branch-change effect uses structural equality viasame()to avoid spurious refreshes.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/pages/session.tsx`:
- Around line 141-144: The current use of createStore for independent fields
mobileTab and deferRender can be changed to separate signals to follow the
guideline; replace the createStore call initializing mobileTab and deferRender
with two createSignal calls (e.g., const [mobileTab, setMobileTab] =
createSignal<"session" | "changes">("session") and const [deferRender,
setDeferRender] = createSignal(false)), then update every reference and setter
usage of store.mobileTab and store.deferRender (and any setStore({...}) calls)
to use the new signals and setters (e.g., mobileTab(), setMobileTab(...),
deferRender(), setDeferRender(...)) in the handlers and components that
currently access mobileTab and deferRender.
In `@packages/app/src/pages/session/use-session-new-worktree.ts`:
- Around line 8-10: The code uses createStore for a single field ({ value:
"main" })—replace it with a createSignal to simplify state: remove
createStore/setStore and instead create a signal (e.g., const [value, setValue]
= createSignal("main")), then update all usages of store.value to call the
signal getter (value()) and replace setStore updates with setValue(newValue);
keep createStore only if you later need coupled object state updated in one
transaction.
🪄 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: 2f657671-c223-4b57-9a24-feb9017cc5d0
📒 Files selected for processing (3)
packages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session/use-session-new-worktree.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: unit-windows-app
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-windows-opencode-session
- GitHub Check: unit-opencode
- GitHub Check: unit-desktop
- GitHub Check: e2e-artifacts
- GitHub Check: analyze-js-ts
- GitHub Check: smoke-macos-arm64
🧰 Additional context used
📓 Path-based instructions (1)
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/use-session-new-worktree.tspackages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session.tsx
🧠 Learnings (24)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/session/compaction.ts:169-191
Timestamp: 2026-04-28T05:36:22.128Z
Learning: In `packages/opencode/src/session/compaction.ts` (Astro-Han/pawwork), the internal helper `splitTurn` intentionally returns a raw `Effect.gen(...)` rather than being wrapped with `Effect.fnUntraced`. Converting `splitTurn` (and similar plain-Effect.gen internal helpers in this file, e.g. `turns`, `completedCompactions`, `buildPrompt`) to `Effect.fnUntraced` is a repo-wide convention sweep deferred to a dedicated follow-up PR. The same sweep covers `tool/tool.ts` and other helpers flagged in the same sync. Do NOT re-flag the absence of `Effect.fnUntraced` on `splitTurn` or other internal helpers in `compaction.ts` during upstream-sync PRs; flag it only in the dedicated convention-sweep PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 353
File: packages/app/e2e/session/session-composer-dock.spec.ts:53-70
Timestamp: 2026-05-01T02:06:33.069Z
Learning: In `packages/app/e2e/session/session-composer-dock.spec.ts` (Astro-Han/pawwork), local question-dataset fixtures (`defaultQuestions`, `multiQuestions`, etc.) intentionally use camelCase, not SCREAMING_SNAKE_CASE. The SCREAMING_SNAKE_CASE guideline for `packages/app/e2e/**/*.spec.ts` is not applied uniformly to local fixture objects in this file. Do NOT re-flag camelCase question-fixture names here as style violations.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/test/session/subagent-lifecycle-integration.test.ts:41-47
Timestamp: 2026-04-28T11:24:35.312Z
Learning: In `packages/opencode/test/session/subagent-lifecycle-integration.test.ts` (Astro-Han/pawwork, PR `#287`), the test suite intentionally uses a manual `run` helper (`Effect.runPromise(program.pipe(Effect.provide(Layer.mergeAll(SubagentRun.defaultLayer, Session.defaultLayer)), Effect.orDie))`) and raw `bun:test` test cases rather than the `testEffect(...)` harness. All 27 lifecycle tests pass with this pattern. Migration to `testEffect` + `it.live(...)` is deferred to a dedicated test-harness sweep PR to avoid fixture drift before merge. Do NOT re-flag the manual `Effect.runPromise` runner in this file as needing harness migration until that sweep PR lands.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/e2e/session/session-scroll-position.spec.ts:171-230
Timestamp: 2026-04-29T13:27:25.687Z
Learning: In `packages/app/e2e/session/session-scroll-position.spec.ts`, the `describe()` formatter is intentionally duplicated in `installPageErrorProbe` (inside `page.addInitScript`, runs in browser context) and `collectPageErrors` (runs in Node.js/Playwright context). These are separate execution contexts — `addInitScript` serializes its closure to a string for injection — so extracting a shared helper would add cross-context indirection. Do NOT flag this duplication as a maintenance issue.
📚 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/src/pages/session/use-session-new-worktree.ts
📚 Learning: 2026-04-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.tspackages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-24T03:51:56.211Z
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:56.211Z
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/pages/session/use-session-new-worktree.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.ts
📚 Learning: 2026-04-25T12:52:37.850Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/session/export.ts:24-31
Timestamp: 2026-04-25T12:52:37.850Z
Learning: In `packages/opencode/src/session/export.ts`, the `hashFile` async helper intentionally uses raw `node:fs/promises` (`fs.readFile`) rather than `FileSystem.FileSystem`. It is a small, self-contained helper called inside `Effect.promise(...)` with broad failure tolerance (try/catch returning `undefined`). Using `FileSystem.FileSystem` here would add unnecessary layers without payoff. The `FileSystem.FileSystem` preference applies to Effect-service-style operations, not thin async boundary helpers like `hashFile`.
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.tspackages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T04:38:11.727Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:35-45
Timestamp: 2026-04-28T04:38:11.727Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork, PR `#270`), the module-scoped `Map<string, Semaphore.Semaphore>` (`locks`) and `lock(filePath)` helper are intentionally left as a global, never-cleaned registry. PR `#270` is a pure upstream-sync graft and mixing in a bugfix would drift the diff from the upstream baseline. The fix (moving per-file semaphores into `InstanceState`/`ScopedCache` for per-instance lifecycle management) is tracked as a follow-up to land in its own PR or be reported upstream. Do NOT re-flag the absence of InstanceState wiring for the lock map in this file until the follow-up PR is opened.
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.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/pages/session/use-session-new-worktree.ts
📚 Learning: 2026-04-30T10:41:33.826Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/desktop-electron/src/main/ipc.ts:342-344
Timestamp: 2026-04-30T10:41:33.826Z
Learning: In Astro-Han/pawwork, the `export-session` IPC handler in `packages/desktop-electron/src/main/ipc.ts` accepts an optional localized `title` argument for `dialog.showSaveDialog`, keeping `"Export session"` as an English fallback. The localized title is plumbed from the renderer through `platform.exportSession` → `window.api.exportSession` → preload (`preload/types.ts`, `preload/index.ts`, `renderer/index.tsx`) → main, mirroring the same shape as the `save-file-picker` handler. Both renderer callsites (`layout.tsx` and `message-timeline.tsx`) pass `language.t("session.export.action.export")`. Do NOT flag the hard-coded fallback as unlocalized — the renderer is responsible for supplying the localized title.
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.ts
📚 Learning: 2026-04-28T05:36:22.128Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/session/compaction.ts:169-191
Timestamp: 2026-04-28T05:36:22.128Z
Learning: In `packages/opencode/src/session/compaction.ts` (Astro-Han/pawwork), the internal helper `splitTurn` intentionally returns a raw `Effect.gen(...)` rather than being wrapped with `Effect.fnUntraced`. Converting `splitTurn` (and similar plain-Effect.gen internal helpers in this file, e.g. `turns`, `completedCompactions`, `buildPrompt`) to `Effect.fnUntraced` is a repo-wide convention sweep deferred to a dedicated follow-up PR. The same sweep covers `tool/tool.ts` and other helpers flagged in the same sync. Do NOT re-flag the absence of `Effect.fnUntraced` on `splitTurn` or other internal helpers in `compaction.ts` during upstream-sync PRs; flag it only in the dedicated convention-sweep PR.
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.tspackages/app/src/pages/session/use-session-history-backfill.ts
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.
Applied to files:
packages/app/src/pages/session/use-session-new-worktree.tspackages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session.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/use-session-new-worktree.tspackages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T13:10:01.345Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/src/tool/agent.ts:89-102
Timestamp: 2026-04-28T13:10:01.345Z
Learning: In `packages/opencode/src/tool/agent.ts` (Astro-Han/pawwork, PR `#287`), `makeReadLastCompletedAssistantText` intentionally scans the full child session message history (no `limit`) to find the latest completed assistant text part for `partial_result` on cancellation. Cancellation is a rare path so the extra read cost is acceptable; tool-heavy children can push the last stable assistant text far back. Do NOT suggest re-adding a fixed small limit (e.g., `limit: 5`) to this function.
Applied to files:
packages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-29T13:27:25.687Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/e2e/session/session-scroll-position.spec.ts:171-230
Timestamp: 2026-04-29T13:27:25.687Z
Learning: In `packages/app/e2e/session/session-scroll-position.spec.ts`, the `describe()` formatter is intentionally duplicated in `installPageErrorProbe` (inside `page.addInitScript`, runs in browser context) and `collectPageErrors` (runs in Node.js/Playwright context). These are separate execution contexts — `addInitScript` serializes its closure to a string for injection — so extracting a shared helper would add cross-context indirection. Do NOT flag this duplication as a maintenance issue.
Applied to files:
packages/app/src/pages/session/use-session-history-backfill.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T18:53:46.234Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 308
File: packages/opencode/test/server/global-session-list.test.ts:224-259
Timestamp: 2026-04-28T18:53:46.234Z
Learning: In `packages/opencode/test/server/global-session-list.test.ts` (Astro-Han/pawwork), all `it.live(...)` test bodies intentionally use `Effect.promise(async () => { ... })` rather than `Effect.gen(function* () { ... })`. This is the established local convention for the route/pagination tests in this file (e.g., "supports cursor pagination", "session route orders by creation time", "session routes omit undefined optional fields"). Do NOT flag individual `it.live` cases in this file as needing conversion to `Effect.gen`; any harness-style migration would need to cover the entire file.
Applied to files:
packages/app/src/pages/session/use-session-history-backfill.ts
📚 Learning: 2026-04-30T14:25:19.902Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-30T10:26:58.545Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-27T10:33:12.228Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-25T12:52:49.735Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/share/session.ts:27-27
Timestamp: 2026-04-25T12:52:49.735Z
Learning: In `packages/opencode/src/share/session.ts`, the local `ensureEnabled` closure is intentionally duplicated from `ShareRuntime.ensureEnabled` in `runtime.ts`. Using the shared `ShareRuntime.ensureEnabled` effect directly would leak `CloudShareGate` back into the `R` (requirement) type of `share`, `unshare`, and `create`, because the shared effect re-yields the service rather than capturing an already-resolved gate reference. The current pattern resolves `gate` once at the top of the outer `Effect.gen` and then closes over it in a local `ensureEnabled`, keeping the inner effects' requirement types clean. A comment in the file points to `runtime.ts` to make the intentional duplication discoverable. The real fix would be refactoring `ensureEnabled` to accept a gate parameter, but that change is larger than the DRY benefit warrants. Do not flag this duplication as a maintenance issue.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-25T12:52:35.631Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/desktop-electron/src/main/ipc.ts:238-263
Timestamp: 2026-04-25T12:52:35.631Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/ipc.ts`), `deps.getServerReadyData()` (backed by `serverReady.promise` in `index.ts`) resolves once at server startup and remains settled; it is not expected to reject in practice. Do not flag the absence of a try-catch around it in the `export-session` IPC handler — the network/fetch layer in `server-client.ts` already has a 10-second AbortController timeout and returns a typed `{ok: false, error}` payload, covering the real failure modes.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-24T05:39:58.329Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/app/src/pages/session.tsx
🔇 Additional comments (6)
packages/app/src/pages/session.tsx (4)
240-260: Forward reference pattern flagged in previous review.This pattern where
scrollDockreceives callbacks referencingactiveMessageandhistoryBackfill(assigned later) works correctly because callbacks are invoked after initialization, not during construction. A previous review already suggested adding a clarifying comment.
488-525: LGTM!The refactored
SessionMainViewwiring correctly passes all 34 props from the various composables. The evaluated accessors (e.g.,timelineSessionID(),centered()) follow standard Solid JSX patterns where the expressions become property getters for reactivity.
425-486: LGTM!The
renderComposerRegionfunction correctly wires thefollowupandrevertprops from their respective composables. The conditional logic properly gates followups to non-child sessions and revert to sessions with rolled items.
54-103: Well-structured composition root.The refactored
Pagecomponent successfully delegates to focused composables while maintaining clear data flow. ThecreateSessionTimelineDatacomposable cleanly encapsulates timeline state derivation, and the route tabs handoff viacreateSessionRouteTabsproperly separates concerns.packages/app/src/pages/session/use-session-history-backfill.ts (1)
1-62: LGTM!The composable correctly implements history backfill with proper safeguards:
- The
fillFrameguard prevents concurrent rAF scheduling- Conditions are re-checked inside the rAF callback to handle state changes during the async delay
- The
onCleanupproperly cancels pending frames- The double-checking pattern (effect filters +
fill()filters) is appropriate defensive programmingpackages/app/src/pages/session/use-session-new-worktree.ts (1)
12-33: LGTM!The
selectedmemo correctly handles the three cases (create, worktree directory, main), and the directory-change effect properly resets state. Thedefer: trueoption ensures the reset doesn't fire on initial mount.
|
Follow-up timeline interaction split pushed. Additional changes:
Verification rerun locally:
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/pages/session.tsx`:
- Around line 142-149: The createComputed block that sets deferRender uses
requestAnimationFrame/setTimeout callbacks that can be executed out of order;
guard against stale callbacks by tracking and cancelling pending handles or
using a monotonic version token: in the createComputed where
timelineSessionKey() is compared to prev, store the raf and timeout handles
(from requestAnimationFrame and setTimeout) or a numeric version id scoped to
that createComputed, cancel previous handles with
cancelAnimationFrame/clearTimeout or ignore callbacks whose version doesn't
match before calling setStore("deferRender", false), and also clear any
remaining pending handles in onCleanup to avoid leaks; reference createComputed,
timelineSessionKey, setStore("deferRender", ...), requestAnimationFrame,
setTimeout, onCleanup, cancelAnimationFrame, and clearTimeout when making the
fix.
In `@packages/app/src/pages/session/use-session-followups.test.ts`:
- Around line 60-78: The test omits assertions for the two required positive
prerequisites; add checks that shouldAutoSendFollowup returns false when
hasSession is false and when hasItem is false (and optionally when both are
false) to ensure regressions in hasSession/hasItem are caught; update the test
in use-session-followups.test.ts around the existing shouldAutoSendFollowup
assertions to include expect(shouldAutoSendFollowup({ ...base, hasSession: false
})).toBe(false) and expect(shouldAutoSendFollowup({ ...base, hasItem: false
})).toBe(false) (and optionally expect(shouldAutoSendFollowup({ ...base,
hasSession: false, hasItem: false })).toBe(false)).
🪄 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: 5a0145d8-5898-4c2f-b34f-b53060457339
📒 Files selected for processing (3)
packages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-followups.test.tspackages/app/src/pages/session/use-session-timeline-interaction.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: analyze-js-ts
- GitHub Check: smoke-macos-arm64
- GitHub Check: e2e-artifacts
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-windows-opencode-session
- GitHub Check: unit-windows-app
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: typecheck
- GitHub Check: unit-opencode
- GitHub Check: unit-desktop
🧰 Additional context used
📓 Path-based instructions (1)
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/use-session-followups.test.tspackages/app/src/pages/session/use-session-timeline-interaction.tspackages/app/src/pages/session.tsx
🧠 Learnings (29)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/session/compaction.ts:169-191
Timestamp: 2026-04-28T05:36:22.128Z
Learning: In `packages/opencode/src/session/compaction.ts` (Astro-Han/pawwork), the internal helper `splitTurn` intentionally returns a raw `Effect.gen(...)` rather than being wrapped with `Effect.fnUntraced`. Converting `splitTurn` (and similar plain-Effect.gen internal helpers in this file, e.g. `turns`, `completedCompactions`, `buildPrompt`) to `Effect.fnUntraced` is a repo-wide convention sweep deferred to a dedicated follow-up PR. The same sweep covers `tool/tool.ts` and other helpers flagged in the same sync. Do NOT re-flag the absence of `Effect.fnUntraced` on `splitTurn` or other internal helpers in `compaction.ts` during upstream-sync PRs; flag it only in the dedicated convention-sweep PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/test/session/subagent-lifecycle-integration.test.ts:41-47
Timestamp: 2026-04-28T11:24:35.312Z
Learning: In `packages/opencode/test/session/subagent-lifecycle-integration.test.ts` (Astro-Han/pawwork, PR `#287`), the test suite intentionally uses a manual `run` helper (`Effect.runPromise(program.pipe(Effect.provide(Layer.mergeAll(SubagentRun.defaultLayer, Session.defaultLayer)), Effect.orDie))`) and raw `bun:test` test cases rather than the `testEffect(...)` harness. All 27 lifecycle tests pass with this pattern. Migration to `testEffect` + `it.live(...)` is deferred to a dedicated test-harness sweep PR to avoid fixture drift before merge. Do NOT re-flag the manual `Effect.runPromise` runner in this file as needing harness migration until that sweep PR lands.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 353
File: packages/app/e2e/session/session-composer-dock.spec.ts:53-70
Timestamp: 2026-05-01T02:06:33.069Z
Learning: In `packages/app/e2e/session/session-composer-dock.spec.ts` (Astro-Han/pawwork), local question-dataset fixtures (`defaultQuestions`, `multiQuestions`, etc.) intentionally use camelCase, not SCREAMING_SNAKE_CASE. The SCREAMING_SNAKE_CASE guideline for `packages/app/e2e/**/*.spec.ts` is not applied uniformly to local fixture objects in this file. Do NOT re-flag camelCase question-fixture names here as style violations.
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: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/e2e/session/session-scroll-position.spec.ts:171-230
Timestamp: 2026-04-29T13:27:25.687Z
Learning: In `packages/app/e2e/session/session-scroll-position.spec.ts`, the `describe()` formatter is intentionally duplicated in `installPageErrorProbe` (inside `page.addInitScript`, runs in browser context) and `collectPageErrors` (runs in Node.js/Playwright context). These are separate execution contexts — `addInitScript` serializes its closure to a string for injection — so extracting a shared helper would add cross-context indirection. Do NOT flag this duplication as a maintenance issue.
📚 Learning: 2026-04-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In pawwork’s Bun+Solid test environment (under packages/app/src/pages/session/**.test.ts), do not expect `createMemo((current) => ...)` to be recomputed/invalidated the same way it would in a real browser runtime. Avoid adding signal-driven transition tests that rely on Solid reactive invalidation paths in this environment (e.g., flipping inputs via `createSignal`/`createRoot` just to observe `createMemo` behavior), since it can be misleading. Instead, review/author tests as follows: (1) cover pure transition/state-update logic with plain unit tests (e.g., `nextSessionViewState`), and (2) cover the actual browser reactive behavior with E2E tests (e.g., the session-switch E2E spec). When reviewing this Bun+Solid test suite, do not treat the absence of signal-driven `createSessionViewController` tests as a missing-coverage gap.
Applied to files:
packages/app/src/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-28T11:24:35.312Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/test/session/subagent-lifecycle-integration.test.ts:41-47
Timestamp: 2026-04-28T11:24:35.312Z
Learning: In `packages/opencode/test/session/subagent-lifecycle-integration.test.ts` (Astro-Han/pawwork, PR `#287`), the test suite intentionally uses a manual `run` helper (`Effect.runPromise(program.pipe(Effect.provide(Layer.mergeAll(SubagentRun.defaultLayer, Session.defaultLayer)), Effect.orDie))`) and raw `bun:test` test cases rather than the `testEffect(...)` harness. All 27 lifecycle tests pass with this pattern. Migration to `testEffect` + `it.live(...)` is deferred to a dedicated test-harness sweep PR to avoid fixture drift before merge. Do NOT re-flag the manual `Effect.runPromise` runner in this file as needing harness migration until that sweep PR lands.
Applied to files:
packages/app/src/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-24T03:51:56.211Z
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:56.211Z
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/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-28T18:53:46.234Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 308
File: packages/opencode/test/server/global-session-list.test.ts:224-259
Timestamp: 2026-04-28T18:53:46.234Z
Learning: In `packages/opencode/test/server/global-session-list.test.ts` (Astro-Han/pawwork), all `it.live(...)` test bodies intentionally use `Effect.promise(async () => { ... })` rather than `Effect.gen(function* () { ... })`. This is the established local convention for the route/pagination tests in this file (e.g., "supports cursor pagination", "session route orders by creation time", "session routes omit undefined optional fields"). Do NOT flag individual `it.live` cases in this file as needing conversion to `Effect.gen`; any harness-style migration would need to cover the entire file.
Applied to files:
packages/app/src/pages/session/use-session-followups.test.tspackages/app/src/pages/session.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/src/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-29T13:27:25.687Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/e2e/session/session-scroll-position.spec.ts:171-230
Timestamp: 2026-04-29T13:27:25.687Z
Learning: In `packages/app/e2e/session/session-scroll-position.spec.ts`, the `describe()` formatter is intentionally duplicated in `installPageErrorProbe` (inside `page.addInitScript`, runs in browser context) and `collectPageErrors` (runs in Node.js/Playwright context). These are separate execution contexts — `addInitScript` serializes its closure to a string for injection — so extracting a shared helper would add cross-context indirection. Do NOT flag this duplication as a maintenance issue.
Applied to files:
packages/app/src/pages/session/use-session-followups.test.tspackages/app/src/pages/session/use-session-timeline-interaction.tspackages/app/src/pages/session.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/src/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-28T04:56:21.338Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/test/tool/tool-define.test.ts:2-9
Timestamp: 2026-04-28T04:56:21.338Z
Learning: In `packages/opencode/test/tool/tool-define.test.ts` (Astro-Han/pawwork, PR `#270`), the test suite intentionally uses a `ManagedRuntime.make(Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer))` wrapper and raw `bun:test` `test(...)` cases rather than the `testEffect(...)` harness. This code was adopted wholesale from upstream via the graft strategy (per `project_upstream_strategy.md`). Migrating to `testEffect` is deferred to a follow-up PR so as not to mix refactor + harness-migration intents and to avoid drifting the diff from the upstream baseline needed for clean future grafts. Do NOT re-flag the `ManagedRuntime.make` usage in this file as needing harness migration until that follow-up lands.
Applied to files:
packages/app/src/pages/session/use-session-followups.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/src/pages/session/use-session-followups.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/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-23T15:25:31.118Z
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:31.118Z
Learning: In Astro-Han/pawwork E2E tests, reaching a real "running" session state requires the `project` fixture (for model bootstrap) plus `llm.wait(1)` orchestration. The bare `sdk` fixture used by `withSession` does not trigger an LLM call even with `agent: "build"` + `system` prompt set via `sdk.session.promptAsync`, so simulating running state is not lightweight in the current test infrastructure.
Applied to files:
packages/app/src/pages/session/use-session-followups.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} : 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/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-27T11:19:24.963Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.
Applied to files:
packages/app/src/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.
Applied to files:
packages/app/src/pages/session/use-session-followups.test.ts
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.
Applied to files:
packages/app/src/pages/session/use-session-followups.test.tspackages/app/src/pages/session/use-session-timeline-interaction.tspackages/app/src/pages/session.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/use-session-followups.test.tspackages/app/src/pages/session/use-session-timeline-interaction.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.
Applied to files:
packages/app/src/pages/session/use-session-timeline-interaction.tspackages/app/src/pages/session.tsx
📚 Learning: 2026-04-30T10:41:33.826Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/desktop-electron/src/main/ipc.ts:342-344
Timestamp: 2026-04-30T10:41:33.826Z
Learning: In Astro-Han/pawwork, the `export-session` IPC handler in `packages/desktop-electron/src/main/ipc.ts` accepts an optional localized `title` argument for `dialog.showSaveDialog`, keeping `"Export session"` as an English fallback. The localized title is plumbed from the renderer through `platform.exportSession` → `window.api.exportSession` → preload (`preload/types.ts`, `preload/index.ts`, `renderer/index.tsx`) → main, mirroring the same shape as the `save-file-picker` handler. Both renderer callsites (`layout.tsx` and `message-timeline.tsx`) pass `language.t("session.export.action.export")`. Do NOT flag the hard-coded fallback as unlocalized — the renderer is responsible for supplying the localized title.
Applied to files:
packages/app/src/pages/session/use-session-timeline-interaction.ts
📚 Learning: 2026-04-30T14:25:19.902Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-30T10:26:58.545Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-27T10:33:12.228Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T13:10:01.345Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/src/tool/agent.ts:89-102
Timestamp: 2026-04-28T13:10:01.345Z
Learning: In `packages/opencode/src/tool/agent.ts` (Astro-Han/pawwork, PR `#287`), `makeReadLastCompletedAssistantText` intentionally scans the full child session message history (no `limit`) to find the latest completed assistant text part for `partial_result` on cancellation. Cancellation is a rare path so the extra read cost is acceptable; tool-heavy children can push the last stable assistant text far back. Do NOT suggest re-adding a fixed small limit (e.g., `limit: 5`) to this function.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-25T12:52:49.735Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/share/session.ts:27-27
Timestamp: 2026-04-25T12:52:49.735Z
Learning: In `packages/opencode/src/share/session.ts`, the local `ensureEnabled` closure is intentionally duplicated from `ShareRuntime.ensureEnabled` in `runtime.ts`. Using the shared `ShareRuntime.ensureEnabled` effect directly would leak `CloudShareGate` back into the `R` (requirement) type of `share`, `unshare`, and `create`, because the shared effect re-yields the service rather than capturing an already-resolved gate reference. The current pattern resolves `gate` once at the top of the outer `Effect.gen` and then closes over it in a local `ensureEnabled`, keeping the inner effects' requirement types clean. A comment in the file points to `runtime.ts` to make the intentional duplication discoverable. The real fix would be refactoring `ensureEnabled` to accept a gate parameter, but that change is larger than the DRY benefit warrants. Do not flag this duplication as a maintenance issue.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T05:36:22.128Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/session/compaction.ts:169-191
Timestamp: 2026-04-28T05:36:22.128Z
Learning: In `packages/opencode/src/session/compaction.ts` (Astro-Han/pawwork), the internal helper `splitTurn` intentionally returns a raw `Effect.gen(...)` rather than being wrapped with `Effect.fnUntraced`. Converting `splitTurn` (and similar plain-Effect.gen internal helpers in this file, e.g. `turns`, `completedCompactions`, `buildPrompt`) to `Effect.fnUntraced` is a repo-wide convention sweep deferred to a dedicated follow-up PR. The same sweep covers `tool/tool.ts` and other helpers flagged in the same sync. Do NOT re-flag the absence of `Effect.fnUntraced` on `splitTurn` or other internal helpers in `compaction.ts` during upstream-sync PRs; flag it only in the dedicated convention-sweep PR.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-24T05:39:58.329Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T04:38:11.727Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:35-45
Timestamp: 2026-04-28T04:38:11.727Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork, PR `#270`), the module-scoped `Map<string, Semaphore.Semaphore>` (`locks`) and `lock(filePath)` helper are intentionally left as a global, never-cleaned registry. PR `#270` is a pure upstream-sync graft and mixing in a bugfix would drift the diff from the upstream baseline. The fix (moving per-file semaphores into `InstanceState`/`ScopedCache` for per-instance lifecycle management) is tracked as a follow-up to land in its own PR or be reported upstream. Do NOT re-flag the absence of InstanceState wiring for the lock map in this file until the follow-up PR is opened.
Applied to files:
packages/app/src/pages/session.tsx
🔇 Additional comments (1)
packages/app/src/pages/session.tsx (1)
160-168: UsetimelineSessionKeyfor all three controller instantiations to ensure consistent reset boundaries.
useSessionLayout().sessionKeyfollows route params, whilecreateSessionTimelineData()exposestimelineSessionKeywhich tracksvisibleSessionID. During new-session flows or route handoffs, these diverge while messages are loading:visibleSessionIDlags behindrouteSessionIDuntilrouteMessagesReadyis true. UsingsessionKeyforreviewState(line 162),reviewPanel(line 224), andtimelineInteraction(line 236) means these controllers reset on a different boundary than the timeline data they depend on.Pass
timelineSessionKeyto all three instead:
- Line 162:
reviewStateconstructor- Line 224:
reviewPanelconstructor- Line 236:
timelineInteractionconstructor
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/app/src/pages/session.tsx (1)
140-149:⚠️ Potential issue | 🟡 Minor | 💤 Low valueGuard
deferRenderagainst stale scheduled callbacks.The
deferRendertransition guard can race if the user rapidly switches sessions. An earlier callback may flipdeferRenderback tofalseduring a subsequent transition, causing a brief render flash.Suggested fix with epoch tracking
+ let deferRenderEpoch = 0 + createComputed((prev) => { const key = timelineSessionKey() if (key !== prev) { + const epoch = ++deferRenderEpoch setDeferRender(true) requestAnimationFrame(() => { - setTimeout(() => setDeferRender(false), 0) + setTimeout(() => { + if (epoch === deferRenderEpoch) setDeferRender(false) + }, 0) }) } return key }, timelineSessionKey())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/app/src/pages/session.tsx` around lines 140 - 149, The deferRender toggle in createComputed can be flipped by stale callbacks when sessions change rapidly; modify the logic in the createComputed that reads timelineSessionKey() to use an epoch/token (incremented whenever you schedule a defer) and capture that token when calling requestAnimationFrame/setTimeout, then only call setDeferRender(false) if the captured token matches the current epoch; update references in this block (timelineSessionKey, setDeferRender, requestAnimationFrame, setTimeout) so stale callbacks are ignored and deferRender cannot be reset by older transitions.
🤖 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/src/pages/session.tsx`:
- Around line 140-149: The deferRender toggle in createComputed can be flipped
by stale callbacks when sessions change rapidly; modify the logic in the
createComputed that reads timelineSessionKey() to use an epoch/token
(incremented whenever you schedule a defer) and capture that token when calling
requestAnimationFrame/setTimeout, then only call setDeferRender(false) if the
captured token matches the current epoch; update references in this block
(timelineSessionKey, setDeferRender, requestAnimationFrame, setTimeout) so stale
callbacks are ignored and deferRender cannot be reset by older transitions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 828afa18-1c62-4a29-9f56-7e6c3666b5ae
📒 Files selected for processing (15)
packages/app/e2e/session/session-composer-dock.spec.tspackages/app/src/pages/session.tsxpackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-comment-context.tspackages/app/src/pages/session/use-session-new-worktree.tspackages/app/src/pages/session/use-session-revert.test.tspackages/app/src/pages/session/use-session-revert.tspackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-review-state.tspackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-scroll-dock.test.tspackages/app/src/pages/session/use-session-scroll-dock.tspackages/app/src/pages/session/use-session-timeline-data.tspackages/app/src/pages/session/use-session-vcs-refresh.ts
Astro-Han
left a comment
There was a problem hiding this comment.
PR #354 Review
Overall this is a solid refactor. The scroll dock bug fix is correct — centralizing dock height measurement into a single path eliminates the duplicate measurement that caused the original issue. Module extraction is clean, test coverage is comprehensive.
Two things need attention before merge:
P0 — Restore mutation skips API call
In use-session-revert.ts, the restoreMutation has an early return inside batch() when next exists. This means the API call is never awaited and merge() is never called for that code path. The revertMutation correctly awaits the API and merges the response — restore should do the same.
P1 — use-session-review-panel.tsx exceeds 200-line target
The issue states a soft 200-line target. At 374 lines this is the largest extracted module and was essentially copied from the old session.tsx without further decomposition. The JSX builders (reviewContent, reviewPanel, mobileFallback) and the DOM scroll helpers (scrollToReviewDiff, reviewDiffTop) could be split into a separate review-panel-view.ts.
P2 — as never[] bypasses type safety
In use-session-review-panel.tsx line 28, source as never[] suppresses the type mismatch between the diff arrays. Should use a proper union type or adapter.
P2 — Missing paddingBottom in setScrollRef
The old session.tsx set el.style.paddingBottom = "calc(var(--composer-dock-height, 0px) + 16px)" in setScrollRef. The new use-session-scroll-dock.ts omits this. If this was intentional (handled elsewhere), no issue — but worth confirming.
P3 — 26-line pass-through component
session-composer-region.tsx is a pure prop-forwarding wrapper with no added logic. Fine if kept for future extension, but could be inlined.
Recommendation: Fix P0 before merge. P1/P2 can be follow-up PRs.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
packages/app/src/pages/session/use-session-revert.ts (1)
64-66:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRestore target selection should be index-based, not
idcomparison.Line 65 uses
item.id > request.id, which is not safe when IDs are not lexically ordered. Compute the next message by array position from the matched ID.💡 Proposed fix
- const next = readUserMessages(readSessionMessages(input.sync.data.message[request.sessionID])).find( - (item) => item.id > request.id, - ) + const timeline = readUserMessages(readSessionMessages(input.sync.data.message[request.sessionID])) + const start = timeline.findIndex((item) => item.id === request.id) + const next = start >= 0 ? timeline[start + 1] : undefined🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/app/src/pages/session/use-session-revert.ts` around lines 64 - 66, The selection of the "next" message must be index-based rather than by comparing IDs; instead of using readUserMessages(readSessionMessages(input.sync.data.message[request.sessionID])).find((item) => item.id > request.id), first build the array via readSessionMessages/readUserMessages, locate the index of the message whose id === request.id, then pick the element at index+1 (or undefined if out of bounds). Update the logic around readUserMessages/readSessionMessages and request.sessionID/request.id to use this index lookup and handle the no-next-message case gracefully.packages/app/src/pages/session/use-session-timeline-data.ts (1)
72-77:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAvoid lexicographic ID filtering for reverted visibility.
Line 76 uses
m.id < revert, which can produce incorrect timeline visibility when IDs are not lexically ordered. Use index-based slicing in timeline order instead.💡 Proposed fix
const visibleUserMessages = createMemo( () => { const revert = revertMessageID() - if (!revert) return userMessages() - return userMessages().filter((m) => m.id < revert) + const items = userMessages() + if (!revert) return items + const index = items.findIndex((m) => m.id === revert) + if (index < 0) return items + return items.slice(0, index) }, emptyUserMessages, { equals: same }, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/app/src/pages/session/use-session-timeline-data.ts` around lines 72 - 77, The visibleUserMessages memo uses lexicographic comparison (m.id < revert) which fails for non-lexical IDs; instead, compute the index of the revert message in the timeline returned by userMessages() (e.g., findIndex(m => m.id === revert)) and return a slice of userMessages() up to that index (or the full array if not found), preserving timeline order; update the createMemo block that references revertMessageID, userMessages, and visibleUserMessages to use findIndex + slice rather than filter by id comparison.packages/app/src/pages/session/use-session-review-state.ts (1)
142-162:⚠️ Potential issue | 🟠 MajorAvoid the extra artifact fetch on every session change.
createResource(input.sessionID, ...)already loads artifacts when the session ID changes. The effect on lines 201–206 schedulesrefetchArtifactHistory()for that same transition, so opening a session performs twosession.artifacts(...)requests before any later diff-triggered refresh.💡 Suggested fix
- createEffect(() => { - const id = input.sessionID() - if (!id) return - input.turnDiffs() - queueArtifactHistoryRefetch() - }) + createEffect((prevID) => { + const id = input.sessionID() + if (!id) return id + input.turnDiffs() + if (prevID !== undefined && id === prevID) { + queueArtifactHistoryRefetch() + } + return id + })Also applies to: 201–206
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/app/src/pages/session/use-session-review-state.ts` around lines 142 - 162, The code schedules a refetch via queueArtifactHistoryRefetch on session changes even though createResource(input.sessionID, ...) already fetches artifacts, causing duplicate requests; update queueArtifactHistoryRefetch (and the places that call it) to skip scheduling a refetch when the current artifactHistory.sessionID does not match input.sessionID (meaning the createResource initial load is already fetching the new session), e.g., check artifactHistory()?.sessionID === input.sessionID before setting artifactHistoryPending and calling requestAnimationFrame so refetchArtifactHistory is only queued for subsequent updates, not the initial session change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/pages/session.tsx`:
- Around line 79-88: The effect currently reads params.id and
searchParams.prompt inside untrack(), which prevents createEffect from tracking
them as dependencies and breaks later prefill behavior; update the createEffect
so that params.id and searchParams.prompt are read outside of untrack (or
assigned to local constants before calling untrack) and only the side-effectful
calls (prompt.set and setSearchParams) run inside untrack; in other words,
ensure createEffect explicitly depends on params.id and searchParams.prompt by
referencing them before the untrack call and then call prompt.set(...) and
setSearchParams(...) inside untrack to perform the updates.
In `@packages/app/src/pages/session/session-main-view.tsx`:
- Around line 86-124: The fallback Match currently uses when={true} which
renders NewSessionView even while a session route is active but
timelineSessionID is still resolving; change that Match to only render when
there is no active session (e.g., when={!props.activeSessionID}) so
NewSessionView does not mount during session switches—update the Match that
renders <NewSessionView composer={props.composerHome} /> accordingly and keep
the existing Show when={props.activeSessionID} for the session composer.
In `@packages/app/src/pages/session/use-session-review-panel.tsx`:
- Around line 78-82: The review panel never sets
tree.activeDiff/tree.pendingDiff, so diff focus/scroll restore never runs; wire
these fields to the currently selected review file by updating the logic that
manages selection (e.g., when input.activeFileTab() changes or during the
review-open flow) to compute the diff id/path via input.file.pathFromTab(...)
and call setTree({ activeDiff: <selectedDiffId>, pendingDiff: <selectedDiffId>
}) (or set pendingDiff first then activeDiff when initiating scroll retry).
Ensure any existing clear-to-undefined paths (where
tree.activeDiff/tree.pendingDiff are reset) still occur only when closing the
review or switching away, and reference the tree and setTree usage in
use-session-review-panel.tsx to attach this assignment where
focusedFile/select-tab changes are handled.
---
Duplicate comments:
In `@packages/app/src/pages/session/use-session-revert.ts`:
- Around line 64-66: The selection of the "next" message must be index-based
rather than by comparing IDs; instead of using
readUserMessages(readSessionMessages(input.sync.data.message[request.sessionID])).find((item)
=> item.id > request.id), first build the array via
readSessionMessages/readUserMessages, locate the index of the message whose id
=== request.id, then pick the element at index+1 (or undefined if out of
bounds). Update the logic around readUserMessages/readSessionMessages and
request.sessionID/request.id to use this index lookup and handle the
no-next-message case gracefully.
In `@packages/app/src/pages/session/use-session-review-state.ts`:
- Around line 142-162: The code schedules a refetch via
queueArtifactHistoryRefetch on session changes even though
createResource(input.sessionID, ...) already fetches artifacts, causing
duplicate requests; update queueArtifactHistoryRefetch (and the places that call
it) to skip scheduling a refetch when the current artifactHistory.sessionID does
not match input.sessionID (meaning the createResource initial load is already
fetching the new session), e.g., check artifactHistory()?.sessionID ===
input.sessionID before setting artifactHistoryPending and calling
requestAnimationFrame so refetchArtifactHistory is only queued for subsequent
updates, not the initial session change.
In `@packages/app/src/pages/session/use-session-timeline-data.ts`:
- Around line 72-77: The visibleUserMessages memo uses lexicographic comparison
(m.id < revert) which fails for non-lexical IDs; instead, compute the index of
the revert message in the timeline returned by userMessages() (e.g., findIndex(m
=> m.id === revert)) and return a slice of userMessages() up to that index (or
the full array if not found), preserving timeline order; update the createMemo
block that references revertMessageID, userMessages, and visibleUserMessages to
use findIndex + slice rather than filter by id comparison.
🪄 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: ac531ec5-38a8-435a-8714-dc3ad88cfd60
📒 Files selected for processing (18)
packages/app/e2e/session/session-composer-dock.spec.tspackages/app/src/pages/session.tsxpackages/app/src/pages/session/message-timeline.tsxpackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-comment-context.test.tspackages/app/src/pages/session/use-session-comment-context.tspackages/app/src/pages/session/use-session-followups.test.tspackages/app/src/pages/session/use-session-new-worktree.tspackages/app/src/pages/session/use-session-revert.test.tspackages/app/src/pages/session/use-session-revert.tspackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-review-state.tspackages/app/src/pages/session/use-session-route-tabs.tspackages/app/src/pages/session/use-session-scroll-dock.test.tspackages/app/src/pages/session/use-session-scroll-dock.tspackages/app/src/pages/session/use-session-timeline-data.tspackages/app/src/pages/session/use-session-vcs-refresh.tspackages/app/src/shell-frame-contract.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/pages/session/use-session-revert.ts`:
- Around line 64-66: The current logic selects the next restore target by
comparing opaque message ids with '>' which assumes lexicographic order; instead
use the ordered timeline returned by readUserMessages(readSessionMessages(...))
and pick the message by position: find the index of the message whose id ===
request.id and take the element at index+1 as the next target (handle not-found
and end-of-array cases). Update the variable construction around
readUserMessages/readSessionMessages/input.sync.data.message[request.sessionID]
and request.id to use findIndex + index+1 rather than an id '>' comparison.
- Around line 46-58: The SDK call input.client.session.revert(request) (and the
other SDK calls in the same file around lines 79-98) must be invoked with
throwOnError: true so HTTP errors are thrown instead of returning { error } and
bypassing the .catch; update the call sites (e.g., where
input.client.session.revert(request) is used) to pass the option (throwOnError:
true) to the generated SDK method so that failures trigger the existing .catch
block and the rollback code (input.roll, input.prompt.set, input.fail) runs as
intended.
🪄 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: b8f01e71-6118-4b91-9436-9490c76903c7
📒 Files selected for processing (6)
packages/app/src/pages/session.tsxpackages/app/src/pages/session/review-panel-scroll.tspackages/app/src/pages/session/review-panel-view.tsxpackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-revert.tspackages/app/src/pages/session/use-session-review-panel.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). (9)
- GitHub Check: analyze-js-ts
- GitHub Check: e2e-artifacts
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: unit-windows-opencode-session
- GitHub Check: unit-windows-app
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: unit-desktop
- GitHub Check: smoke-macos-arm64
🧰 Additional context used
📓 Path-based instructions (1)
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/review-panel-view.tsxpackages/app/src/pages/session/review-panel-scroll.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
🧠 Learnings (36)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/session/compaction.ts:169-191
Timestamp: 2026-04-28T05:36:22.128Z
Learning: In `packages/opencode/src/session/compaction.ts` (Astro-Han/pawwork), the internal helper `splitTurn` intentionally returns a raw `Effect.gen(...)` rather than being wrapped with `Effect.fnUntraced`. Converting `splitTurn` (and similar plain-Effect.gen internal helpers in this file, e.g. `turns`, `completedCompactions`, `buildPrompt`) to `Effect.fnUntraced` is a repo-wide convention sweep deferred to a dedicated follow-up PR. The same sweep covers `tool/tool.ts` and other helpers flagged in the same sync. Do NOT re-flag the absence of `Effect.fnUntraced` on `splitTurn` or other internal helpers in `compaction.ts` during upstream-sync PRs; flag it only in the dedicated convention-sweep PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/test/session/subagent-lifecycle-integration.test.ts:41-47
Timestamp: 2026-04-28T11:24:35.312Z
Learning: In `packages/opencode/test/session/subagent-lifecycle-integration.test.ts` (Astro-Han/pawwork, PR `#287`), the test suite intentionally uses a manual `run` helper (`Effect.runPromise(program.pipe(Effect.provide(Layer.mergeAll(SubagentRun.defaultLayer, Session.defaultLayer)), Effect.orDie))`) and raw `bun:test` test cases rather than the `testEffect(...)` harness. All 27 lifecycle tests pass with this pattern. Migration to `testEffect` + `it.live(...)` is deferred to a dedicated test-harness sweep PR to avoid fixture drift before merge. Do NOT re-flag the manual `Effect.runPromise` runner in this file as needing harness migration until that sweep PR lands.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/src/tool/agent.ts:89-102
Timestamp: 2026-04-28T13:10:01.345Z
Learning: In `packages/opencode/src/tool/agent.ts` (Astro-Han/pawwork, PR `#287`), `makeReadLastCompletedAssistantText` intentionally scans the full child session message history (no `limit`) to find the latest completed assistant text part for `partial_result` on cancellation. Cancellation is a rare path so the extra read cost is acceptable; tool-heavy children can push the last stable assistant text far back. Do NOT suggest re-adding a fixed small limit (e.g., `limit: 5`) to this function.
📚 Learning: 2026-04-29T13:27:28.494Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/src/pages/session/session-view-controller.test.ts:5-45
Timestamp: 2026-04-29T13:27:28.494Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/session/session-view-controller.test.ts` and related Solid + Bun test files), the Bun + Solid test environment does NOT reliably exercise `createMemo((current) => ...)` signal recomputation the way a browser runtime does. Adding signal-driven transition tests (e.g., using `createSignal` + `createRoot` to flip reactive inputs) is misleading in this environment because the reactive invalidation/recomputation path is not faithfully replicated. The correct strategy is: cover pure transition logic with plain unit tests (e.g., `nextSessionViewState`), and cover the browser reactive path with E2E tests (e.g., session-switch E2E spec). Do NOT re-flag the absence of signal-driven `createSessionViewController` tests in this environment as a gap.
Applied to files:
packages/app/src/pages/session/review-panel-view.tsxpackages/app/src/pages/session/review-panel-scroll.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-30T14:25:19.902Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 346
File: packages/app/src/pages/session.tsx:2071-2071
Timestamp: 2026-04-30T14:25:19.902Z
Learning: In `packages/app/src/pages/session.tsx` (Astro-Han/pawwork), the session-panel container uses `min-w-[24rem]` unconditionally (no `md:` prefix). This is intentional: PawWork is Electron-only and PR `#346` raises `minWidth` in `packages/desktop-electron/src/main/windows.ts` from 480 → 768px, which matches the Tailwind `md` breakpoint. Windows narrower than 768px are unreachable in production, so there is no narrow-viewport flex-col case to guard against. Do NOT suggest scoping this `min-w-[24rem]` to `md:` in future reviews.
Applied to files:
packages/app/src/pages/session/review-panel-view.tsxpackages/app/src/pages/session/review-panel-scroll.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.
Applied to files:
packages/app/src/pages/session/review-panel-view.tsxpackages/app/src/pages/session/review-panel-scroll.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.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/pages/session/review-panel-view.tsxpackages/app/src/pages/session/review-panel-scroll.tspackages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-29T13:27:25.687Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 329
File: packages/app/e2e/session/session-scroll-position.spec.ts:171-230
Timestamp: 2026-04-29T13:27:25.687Z
Learning: In `packages/app/e2e/session/session-scroll-position.spec.ts`, the `describe()` formatter is intentionally duplicated in `installPageErrorProbe` (inside `page.addInitScript`, runs in browser context) and `collectPageErrors` (runs in Node.js/Playwright context). These are separate execution contexts — `addInitScript` serializes its closure to a string for injection — so extracting a shared helper would add cross-context indirection. Do NOT flag this duplication as a maintenance issue.
Applied to files:
packages/app/src/pages/session/review-panel-scroll.tspackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-30T10:41:33.826Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/desktop-electron/src/main/ipc.ts:342-344
Timestamp: 2026-04-30T10:41:33.826Z
Learning: In Astro-Han/pawwork, the `export-session` IPC handler in `packages/desktop-electron/src/main/ipc.ts` accepts an optional localized `title` argument for `dialog.showSaveDialog`, keeping `"Export session"` as an English fallback. The localized title is plumbed from the renderer through `platform.exportSession` → `window.api.exportSession` → preload (`preload/types.ts`, `preload/index.ts`, `renderer/index.tsx`) → main, mirroring the same shape as the `save-file-picker` handler. Both renderer callsites (`layout.tsx` and `message-timeline.tsx`) pass `language.t("session.export.action.export")`. Do NOT flag the hard-coded fallback as unlocalized — the renderer is responsible for supplying the localized title.
Applied to files:
packages/app/src/pages/session/session-main-view.tsx
📚 Learning: 2026-04-27T10:33:12.228Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-28T04:38:11.771Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/agent.ts:23-27
Timestamp: 2026-04-28T04:38:11.771Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/tool/agent.ts`), the `subagent_session_id` field in the `Parameters` schema accepts any `Schema.String` rather than a branded `SessionID`. This is inherited upstream behavior (adopted in PR `#270`, an upstream-sync graft of upstream PR `#23244`). The fix — validating `subagent_session_id` as a `SessionID` brand up front so malformed/typo'd IDs fail explicitly rather than silently forking a new subagent session — is intentionally deferred to a follow-up PR or upstream report. Do NOT re-flag this as a blocking issue in PR `#270` or in future upstream-sync PRs that carry the same schema; flag it only in a PawWork-authored PR that touches `agent.ts` parameter validation.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-24T05:39:58.329Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:58.329Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T11:24:35.312Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/test/session/subagent-lifecycle-integration.test.ts:41-47
Timestamp: 2026-04-28T11:24:35.312Z
Learning: In `packages/opencode/test/session/subagent-lifecycle-integration.test.ts` (Astro-Han/pawwork, PR `#287`), the test suite intentionally uses a manual `run` helper (`Effect.runPromise(program.pipe(Effect.provide(Layer.mergeAll(SubagentRun.defaultLayer, Session.defaultLayer)), Effect.orDie))`) and raw `bun:test` test cases rather than the `testEffect(...)` harness. All 27 lifecycle tests pass with this pattern. Migration to `testEffect` + `it.live(...)` is deferred to a dedicated test-harness sweep PR to avoid fixture drift before merge. Do NOT re-flag the manual `Effect.runPromise` runner in this file as needing harness migration until that sweep PR lands.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-30T10:26:58.545Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 342
File: packages/app/src/components/dialog-delete-session.tsx:22-31
Timestamp: 2026-04-30T10:26:58.545Z
Learning: In `packages/app/src/components/dialog-delete-session.tsx` (Astro-Han/pawwork, PR `#342`), `handleDelete` uses a `deleting` signal with `try/finally` but intentionally omits an explicit `catch` block. The caller (`deleteSession` in `packages/app/src/pages/layout.tsx` around line 1083) already swallows errors with a toast, so `onConfirm` does not reject in practice. The `try/finally` is sufficient to keep the Delete button usable if a future caller throws. Do NOT re-flag the missing explicit catch in this component.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-28T12:01:16.559Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/src/session/session.ts:518-542
Timestamp: 2026-04-28T12:01:16.559Z
Learning: In `packages/opencode/src/session/session.ts` (Astro-Han/pawwork, PR `#287`), the `updatePart` subtask guard intentionally allows first writes (where `existing === undefined`) to pass through without calling `lifecycleFieldsChanged`. This is required for `Session.fork()`, migration, and import paths that replay historical `SubtaskPart` rows via `updatePart()` outside any `SubagentRunWriterContext`. Only mutations of an already-persisted row (`existing` is defined) are policed. Do NOT suggest adding a lifecycle check on first-write in this guard.
Applied to files:
packages/app/src/pages/session/session-main-view.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-24T03:51:56.211Z
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:56.211Z
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/pages/session/session-main-view.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-28T05:36:22.128Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/session/compaction.ts:169-191
Timestamp: 2026-04-28T05:36:22.128Z
Learning: In `packages/opencode/src/session/compaction.ts` (Astro-Han/pawwork), the internal helper `splitTurn` intentionally returns a raw `Effect.gen(...)` rather than being wrapped with `Effect.fnUntraced`. Converting `splitTurn` (and similar plain-Effect.gen internal helpers in this file, e.g. `turns`, `completedCompactions`, `buildPrompt`) to `Effect.fnUntraced` is a repo-wide convention sweep deferred to a dedicated follow-up PR. The same sweep covers `tool/tool.ts` and other helpers flagged in the same sync. Do NOT re-flag the absence of `Effect.fnUntraced` on `splitTurn` or other internal helpers in `compaction.ts` during upstream-sync PRs; flag it only in the dedicated convention-sweep PR.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-28T08:29:02.858Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/file/ripgrep.ts:469-470
Timestamp: 2026-04-28T08:29:02.858Z
Learning: In `packages/opencode/src/file/ripgrep.ts` (Astro-Han/pawwork, PR `#270`), the `tree()` function filters files with `if (file.includes(".opencode")) continue`, which is a raw substring match that would also drop unrelated paths whose names merely contain `.opencode` (e.g., `.opencode-backup`). The correct fix is `if (file.split(path.sep).includes(".opencode")) continue` to match only the exact path segment. However, upstream `dev:packages/opencode/src/file/ripgrep.ts` carries the identical false-positive filter. PR `#270` is an upstream-sync graft; fixing it here would diverge from the baseline. The fix is deferred to a PawWork ripgrep filter cleanup PR or an upstream-first fix, to land alongside the other ripgrep deferrals (Schema.Class, bytes-union, abort short-circuit, mixed-separator). Do NOT re-flag the `.opencode` substring filter in upstream-sync PRs.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsx
📚 Learning: 2026-04-28T04:38:21.935Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/test/file/ripgrep.test.ts:9-10
Timestamp: 2026-04-28T04:38:21.935Z
Learning: In `packages/opencode/test/file/ripgrep.test.ts` (Astro-Han/pawwork, PR `#270`), the tests intentionally use a local `run` helper (`effect.pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise)`) rather than the `testEffect(...)` harness. This file was adopted from upstream wholesale as part of an upstream-sync graft; migrating to `testEffect` + `it.live(...)` was deferred to a separate follow-up PR to avoid mixing refactor and harness-migration intents within the sync. Do NOT re-flag the local `run` wrapper as needing harness migration until that follow-up lands.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T18:53:46.234Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 308
File: packages/opencode/test/server/global-session-list.test.ts:224-259
Timestamp: 2026-04-28T18:53:46.234Z
Learning: In `packages/opencode/test/server/global-session-list.test.ts` (Astro-Han/pawwork), all `it.live(...)` test bodies intentionally use `Effect.promise(async () => { ... })` rather than `Effect.gen(function* () { ... })`. This is the established local convention for the route/pagination tests in this file (e.g., "supports cursor pagination", "session route orders by creation time", "session routes omit undefined optional fields"). Do NOT flag individual `it.live` cases in this file as needing conversion to `Effect.gen`; any harness-style migration would need to cover the entire file.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsxpackages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-28T04:38:05.946Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/patch/index.ts:337-346
Timestamp: 2026-04-28T04:38:05.946Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/patch/index.ts`), the BOM-transition surfacing gap — where `Bom.split` strips BOM before building `unified_diff`/`new_content` but `Bom.join` later re-attaches BOM on disk write, meaning BOM changes are not reflected in the diff payload — is intentionally deferred. PR `#270` is an upstream-sync graft; fixing the issue here would mix refactor + bugfix intents and drift the diff from the upstream baseline needed for clean future grafts. A dedicated follow-up PR (or upstream report) will address this. Do NOT re-flag the missing BOM-change surfacing in `ApplyPatchFileUpdate`/`ApplyPatchFileChange` as a blocking issue in PR `#270` or in future sync PRs that carry the same upstream baseline.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T04:56:18.533Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/test/file/ripgrep.test.ts:172-175
Timestamp: 2026-04-28T04:56:18.533Z
Learning: In `packages/opencode/test/file/ripgrep.test.ts` (Astro-Han/pawwork, PR `#270`), the `files dies on nonexistent directory` test hardcodes `/tmp/nonexistent-dir-12345` as the missing-directory path. This is upstream-inherited behaviour adopted wholesale via the graft strategy (`project_upstream_strategy.md`). The fix — replacing the hardcoded path with a platform-neutral guaranteed-nonexistent child path derived from the `tmpdir` fixture — is intentionally deferred to a follow-up PR or upstream report, to avoid mixing bugfix + refactor intents and drifting the diff from the upstream baseline future syncs need. Do NOT re-flag the hardcoded `/tmp/nonexistent-dir-12345` path in this file as a blocking or actionable issue until the follow-up PR lands.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsx
📚 Learning: 2026-04-28T04:38:11.727Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/edit.ts:35-45
Timestamp: 2026-04-28T04:38:11.727Z
Learning: In `packages/opencode/src/tool/edit.ts` (Astro-Han/pawwork, PR `#270`), the module-scoped `Map<string, Semaphore.Semaphore>` (`locks`) and `lock(filePath)` helper are intentionally left as a global, never-cleaned registry. PR `#270` is a pure upstream-sync graft and mixing in a bugfix would drift the diff from the upstream baseline. The fix (moving per-file semaphores into `InstanceState`/`ScopedCache` for per-instance lifecycle management) is tracked as a follow-up to land in its own PR or be reported upstream. Do NOT re-flag the absence of InstanceState wiring for the lock map in this file until the follow-up PR is opened.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsxpackages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T07:27:59.666Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/file/ripgrep.ts:364-368
Timestamp: 2026-04-28T07:27:59.666Z
Learning: In `packages/opencode/src/file/ripgrep.ts` (Astro-Han/pawwork, PR `#270`), both `files()` and `search()` do not short-circuit on an already-aborted `AbortSignal` before `spawner.spawn(...)`. The `check(input.cwd)` effect before spawn already provides a natural cancellation point for interrupted fibers. Upstream `dev:packages/opencode/src/file/ripgrep.ts` has identical ordering with no pre-spawn `signal.aborted` guard; adding one would diverge from the graft baseline. The fix (explicit pre-spawn `signal.aborted` check) is deferred to a dedicated PawWork ripgrep cancellation pass or upstream PR. Do NOT re-flag the missing pre-spawn cancellation short-circuit in `files()`/`search()` in upstream-sync PRs.
Applied to files:
packages/app/src/pages/session/use-session-review-panel.tsx
📚 Learning: 2026-04-28T13:10:01.345Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 287
File: packages/opencode/src/tool/agent.ts:89-102
Timestamp: 2026-04-28T13:10:01.345Z
Learning: In `packages/opencode/src/tool/agent.ts` (Astro-Han/pawwork, PR `#287`), `makeReadLastCompletedAssistantText` intentionally scans the full child session message history (no `limit`) to find the latest completed assistant text part for `partial_result` on cancellation. Cancellation is a rare path so the extra read cost is acceptable; tool-heavy children can push the last stable assistant text far back. Do NOT suggest re-adding a fixed small limit (e.g., `limit: 5`) to this function.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-27T10:33:02.677Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:33:02.677Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `loopLastError` is stored as raw (unscrubbed) error text in loop metadata intentionally. The trust boundary for sensitive data scrubbing is the renderer (`LoopRenderer.render` already applies `scrubErrorText` before any user/model-facing output), not the storage layer. Failed tool parts also already store raw errors in `state.error` (PR `#204` default behavior), so scrubbing only `loopLastError` would not close that pre-existing leak and would add lossy storage. If export-side hardening is needed, the correct approach is a single sanitizer pass over both `state.error` and metadata in the export layer. Do NOT suggest scrubbing `loopLastError` at the `observeToolError` call site.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-22T09:32:58.310Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T06:51:54.812Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/todo.ts:9-18
Timestamp: 2026-04-28T06:51:54.812Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork), `TodoItem.status` and `TodoItem.priority` are intentionally declared as plain `Schema.String` rather than closed literal unions. This matches the upstream opencode baseline (`dev:packages/opencode/src/tool/todo.ts`). The tightening — `Schema.Literals(["pending","in_progress","completed","cancelled"])` for `status` and `Schema.Literals(["high","medium","low"])` for `priority` — is tracked as a follow-up under the harness/tool-set-v1 series (issue `#129`) to land either as part of a tool-schema tightening sweep or upstream-first. Do NOT re-flag the free-form strings for these fields in upstream-sync PRs; flag it only in a PawWork-authored PR or the dedicated sweep that touches `TodoItem` schema.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-24T17:08:46.780Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-28T06:47:20.342Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/question.ts:6-16
Timestamp: 2026-04-28T06:47:20.342Z
Learning: In Astro-Han/pawwork, `packages/opencode/src/tool/question.ts` is a permanent PawWork carve-out: `Question.Prompt` (defined in `packages/opencode/src/question/index.ts`) remains a Zod schema, NOT an Effect Schema. The `ts-expect-error` boundary in `tool/registry.ts` marks the Zod ↔ Effect Schema seam. The dropped `packages/opencode/test/tool/question.test.ts` covered the old Zod-mixed surface. Schema-bound regression tests for this tool are intentionally deferred until `question/index.ts` migrates to Effect Schema in its own dedicated PR. Do NOT request restoring question tool tests in upstream-sync PRs; flag it only in the PR that migrates `question/index.ts` to Effect Schema.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-27T11:19:24.963Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.
Applied to files:
packages/app/src/pages/session.tsx
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.
Applied to files:
packages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-28T04:56:13.350Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 270
File: packages/opencode/src/tool/tool.ts:8-10
Timestamp: 2026-04-28T04:56:13.350Z
Learning: In `packages/opencode/src/tool/tool.ts` (Astro-Han/pawwork, PR `#270`), the `Metadata` interface uses `[key: string]: any` as its index signature. This is upstream-inherited code adopted wholesale via the graft strategy (per `project_upstream_strategy.md`). The fix — tightening to `Record<string, unknown>` and explicitly narrowing framework-owned fields like `truncated` — is intentionally deferred to a follow-up PR or upstream report. Do NOT re-flag the `any` index signature on `Metadata` in PR `#270` or in future upstream-sync PRs that carry the same upstream baseline.
Applied to files:
packages/app/src/pages/session/use-session-revert.ts
📚 Learning: 2026-04-28T01:24:29.645Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.
Applied to files:
packages/app/src/pages/session/use-session-revert.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/src/pages/session/use-session-revert.ts
Summary
packages/app/src/pages/session.tsxinto focused session controllers with unit coverage.Why
session.tsxmixed route composition, scroll dock layout, history windowing, desktop context sync, followup drafts, revert flow, and review state. The scroll bug came from duplicated dock measurement and bottom-padding paths, which could leave the latest message underneath the Composer after the dock height changed.Related Issue
Fixes #351
Human Review Status
Pending maintainer review.
Review Focus
use-session-scroll-dock.ts: sticky-bottom behavior, composer dock height sync, user-scroll preservation.session.tsx: integration wiring after controller extraction.use-session-followups.tsanduse-session-review-state.ts: extracted state ownership and unchanged behavior.session-composer-dock.spec.ts: regression coverage for the covered latest-turn case.Risk Notes
How To Verify
bun test --preload ./happydom.ts src/pages/session/use-session-scroll-dock.test.ts src/pages/session/session-auto-scroll.test.ts src/pages/session/use-session-history-window.test.ts src/pages/session/use-session-desktop-context.test.ts src/pages/session/use-session-followups.test.ts src/pages/session/use-session-revert.test.ts src/pages/session/use-session-review-state.test.ts src/pages/session/review-change-mode.test.ts src/pages/session/session-messages.test.ts src/components/prompt-input/submit.test.tsbun typecheckbun test:e2e:local -- --grep "composer dock keeps latest turn visible"git diff --checkScreenshots or Recordings
Checklist
Summary by CodeRabbit
Refactor
New Features
Tests
Style