feat(question): tighten dock cap, schema bounds, and tool description#245
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 55 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (13)
📝 WalkthroughWalkthroughGates custom-answer UI per question metadata, removes prompt resize/measurement plumbing, rebuilds cached custom state on cleanup, moves selection hint into the header and caps dock height, tightens Question Zod schemas and tool limits, updates tests/docs/i18n, and sets a minimum Electron window size. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 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 unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces stricter validation for the question tool by adding character limits and constraints on the number of options to the Zod schemas. It also updates the LLM documentation and refines the SessionQuestionDock UI to handle disabled custom answers and improve layout management. Feedback identifies a critical runtime error caused by calling .omit() on a refined Zod schema and notes a redundant validation check in the schema definition.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/opencode/test/question/schema.test.ts`:
- Around line 37-39: Add a boundary acceptance test to assert Info.header
accepts exactly 30 characters: create a test that calls Question.Info.parse with
{ ...validInfo, header: "x".repeat(30) } and expects it not to throw (or to
equal the parsed object). Specifically add a test alongside the existing header
rejection case that uses Question.Info.parse, the validInfo fixture, and the
Info.header constraint to confirm 30-char headers are valid.
🪄 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: a87422a5-5d9b-48a3-87de-18424bfcfed4
📒 Files selected for processing (7)
packages/app/src/pages/session/composer/session-question-dock.tsxpackages/opencode/src/question/index.tspackages/opencode/src/tool/question.txtpackages/opencode/test/question/schema.test.tspackages/ui/src/components/message-part.csspackages/ui/src/i18n/en.tspackages/ui/src/i18n/zh.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-windows-app
- GitHub Check: unit-windows-opencode-session
- GitHub Check: typecheck
- GitHub Check: unit-desktop
- GitHub Check: unit-opencode
- GitHub Check: unit-app
- GitHub Check: smoke-macos-arm64
- GitHub Check: e2e-artifacts
- GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (3)
packages/opencode/**/*.ts
📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
packages/opencode/**/*.ts: UseEffect.gen(function* () { ... })for Effect composition
UseEffect.fn("Domain.method")for named/traced effects andEffect.fnUntracedfor internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer.pipe()wrappers
UseEffect.callbackfor callback-based APIs
PreferDateTime.nowAsDateovernew Date(yield* Clock.currentTimeMillis)when you need aDatein Effect code
UseSchema.Classfor multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
UseSchema.TaggedErrorClassfor typed errors in Effect schemas
UseSchema.Defectinstead ofunknownfor defect-like causes in Effect code
InEffect.gen/Effect.fn, preferyield* new MyError(...)overyield* Effect.fail(new MyError(...))for direct early-failure branches
UsemakeRuntimefromsrc/effect/run-service.tsfor all services; it returns{ runPromise, runFork, runCallback }backed by a sharedmemoMapthat deduplicates layers
UseInstanceStatefromsrc/effect/instance-state.tsfor per-directory or per-project state that needs per-instance cleanup; do work directly in theInstanceState.makeclosure whereScopedCachehandles run-once semantics
UseEffect.addFinalizerorEffect.acquireReleaseinside theInstanceState.makeclosure for cleanup (subscriptions, process teardown, etc.)
UseEffect.forkScopedinside theInstanceState.makeclosure for background stream consumers — the fiber is interrupted when the instance is disposed
PreferFileSystem.FileSysteminstead of rawfs/promisesfor effectful file I/O in Effect services
PreferChildProcessSpawner.ChildProcessSpawnerwithChildProcess.make(...)instead of custom process wrappers in Effect services
PreferHttpClient.HttpClientinstead of rawfetchin Effect services
PreferPath.Path,Config,Clock, andDateTimeservices when those concerns are already inside Effect code
For backgroun...
Files:
packages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.ts
packages/opencode/test/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)
packages/opencode/test/**/*.test.{ts,tsx}: Use thetmpdirfunction fromfixture/fixture.tsto create temporary directories for tests with automatic cleanup. Useawait usingsyntax to ensure automatic cleanup when the variable goes out of scope.
When using thetmpdirfunction with git repository support, pass thegit: trueoption to initialize a git repo with a root commit.
Use theconfigoption intmpdirto write anopencode.jsonconfig file during test setup by passing a partial Config.Info object.
Use theinitoption intmpdirto define custom setup functions that can return extra data accessible viatmp.extra, and use thedisposeoption for custom cleanup logic.
UsetestEffect(...)fromtest/lib/effect.tsfor tests that exercise Effect services or Effect-based workflows.
Useit.effect(...)when the test should run withTestClockandTestConsole. Useit.live(...)when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers fromfixture/fixture.tsover building manual runtimes in tests: usetmpdirScoped()for scoped temp directories,provideInstance(dir)(effect)for low-level binding without directory creation,provideTmpdirInstance(...)for single temp instance binding, orprovideTmpdirServer(...)for tests that also need the test LLM server.
Defineconst it = testEffect(...)near the top of the test file and keep the test body insideEffect.gen(function* () { ... }). Yield services directly withyield* MyService.Serviceoryield* MyTool.
Avoid customManagedRuntime,attach(...), or ad hocrun(...)wrappers in Effect tests whentestEffect(...)already provides the runtime.
When a test needs instance-local state, preferprovideTmpdirInstance(...)orprovideInstance(...)over manualInstance.provide(...)inside Promise-style tests.
Files:
packages/opencode/test/question/schema.test.ts
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/composer/session-question-dock.tsx
🧠 Learnings (15)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.
Applied to files:
packages/ui/src/i18n/en.tspackages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Applied to files:
packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Test one feature per test file
Applied to files:
packages/opencode/test/question/schema.test.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 branded schemas (`Schema.brand`) for single-value types in Effect
Applied to files:
packages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.
Applied to files:
packages/opencode/test/question/schema.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/opencode/test/question/schema.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/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T06:50:02.712Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Applied to files:
packages/opencode/src/question/index.ts
📚 Learning: 2026-04-24T05:39:56.086Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.tsxpackages/ui/src/components/message-part.css
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.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/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.
Applied to files:
packages/ui/src/components/message-part.css
🔇 Additional comments (14)
packages/ui/src/i18n/en.ts (1)
165-166: LGTM!The updated hint strings ("Pick multiple" / "Pick 1") are concise and appropriate for the inline header chip design. This aligns well with the UI changes that move the mode hint into the header.
packages/ui/src/i18n/zh.ts (1)
151-152: LGTM!The Chinese hint translations ("可选多个" / "选 1 个") appropriately mirror the English strings and fit the concise chip-style header design.
packages/ui/src/components/message-part.css (2)
835-837: LGTM!The static CSS max-height constraint (
min(70dvh, calc(100dvh - 180px))) effectively addresses the dock height cap requirement from#216. The comment documenting the 180px offset breakdown is helpful for future maintenance.
863-889: LGTM!The new header title styles support the inline mode hint design well:
- Flex row with baseline alignment keeps text vertically aligned.
- Truncation on the sequence portion prevents overflow while keeping the mode hint visible (
flex-shrink: 0).packages/opencode/test/question/schema.test.ts (1)
1-107: LGTM overall!The test suite provides comprehensive coverage for the new schema constraints. The tests are well-structured with clear boundary testing patterns for length limits and cardinality rules. The comment at lines 58-59 and 64-67 appropriately documents the nuanced behavior being tested.
packages/opencode/src/tool/question.txt (1)
1-54: LGTM!The rewritten tool description is well-structured with clear sections for when to use, when not to use, examples, format constraints, and content routing guidance. The format section accurately reflects the schema constraints, and the content routing workflow helps the LLM understand how to handle longer context appropriately.
packages/app/src/pages/session/composer/session-question-dock.tsx (4)
87-88: LGTM!The
customAllowedmemo correctly interpretscustom !== falseto treatundefinedas allowed (matching the schema's "default: true" behavior). The conditionalcount()calculation properly excludes the custom slot from keyboard navigation when disabled.
151-158: LGTM!The cache cleanup logic correctly handles per-tab custom availability by checking
customByTab(i)for each index. UsingArray.from({ length: total() }, ...)ensures consistent array sizing and avoids stale entries from unvisited tabs.
390-396: LGTM!The inline header layout with sequence, separator, and mode hint aligns with the CSS changes in
message-part.css. Thearia-hidden="true"on the separator is appropriate since it's purely decorative.
455-529: LGTM!The conditional rendering with
<Show when={customAllowed()}>correctly hides the entire custom-answer UI whencustomis explicitlyfalse. TheonMouseDownhandler (lines 485-493) provides good UX by allowing clicks anywhere in the form area to focus the textarea.packages/opencode/src/question/index.ts (4)
17-24: LGTM!The
Optionschema now enforces concise labels and descriptions with actionable error messages that guide the LLM toward the correct fix pattern (keeping labels to 1-5 words, using streamed context for longer content).
29-55: LGTM!The
Infoschema changes are well-designed:
- Length constraints on
question(200) andheader(30) with clear error guidance.- Options cardinality (2-4) enforces meaningful choices.
- The
.refine()rule ensures users always have a way to answer whencustomis disabled.The refinement condition
d.custom !== false || d.options.length > 0correctly allows the defaultcustom: undefined(treated as true) while requiring options when custom is explicitly disabled.
57-59: LGTM!The comment accurately documents the Zod 4 behavior where
.omit()strips refinements. This is important context for future maintainers and explains the intentional asymmetry betweenInfoandPromptschemas.
66-69: LGTM!The
Request.questionsconstraint (1-4) aligns with the tool documentation and provides clear guidance for splitting larger question sets.
Astro-Han
left a comment
There was a problem hiding this comment.
Found one P1 enforcement gap plus a few smaller contract and UI edge cases. Details are inline.
… refine The Question.Request bound .min(1).max(4) was placed on a schema that the runtime never parses (Question.Service.ask takes Info[] directly), so the actual tool entry point z.array(Question.Prompt) could still accept 5+ questions. Move the bound onto the tool parameter array where parameters.parse() actually fires. Drop Info.refine because options.min(2) catches the same custom: false + empty options case earlier with a clearer message. Repair the existing tool/question.test.ts fixture which had only 1 option (now caught by options.min(2)). Add header 30-char accept boundary, hint-content assertions for question-length and options-count rejections so a future refactor cannot silently drop the error contract. Addresses PR #245 review: P1 enforcement gap, P3 hint-content guard, Gemini medium (refine redundancy), CodeRabbit boundary parity.
…t viable Without a minHeight floor, a user could shrink the macOS / Windows window below the dock cap's safety threshold. The static cap formula reserves a 180px chrome buffer, so a 320px-tall window would leave 140px for the entire dock (header, options, footer) — the original symptom from #216 reappears in inverse. 480 matches the smallest height the C2 smoke checklist asserts the footer remains visible at, so any in-bounds window is guaranteed to honor the dock-budget contract. Addresses PR #245 review P3.
Run packages/sdk/js/script/build.ts so the published OpenAPI artifact and TS bindings reflect the new Question.Option/Info/Prompt bounds (maxLength on label/description/question/header, minItems/maxItems on options, minItems/maxItems on the tool questions array). The diff also picks up a few unrelated pre-existing drifts (session.export endpoint, session sort=created param, copy edits) that had accumulated since the last regen — keeping the artifact in sync is the same maintenance gate. Addresses PR #245 review P2.
…t viable Without a minHeight floor, a user could shrink the macOS / Windows window below the dock cap's safety threshold. The static cap formula reserves a 180px chrome buffer, so a 320px-tall window would leave 140px for the entire dock (header, options, footer) — the original symptom from #216 reappears in inverse. 480 matches the smallest height the C2 smoke checklist asserts the footer remains visible at, so any in-bounds window is guaranteed to honor the dock-budget contract. Addresses PR #245 review P3.
80ac51d to
2586f55
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Second pass on the fixed head. I found three remaining P2 contract regressions worth addressing before merge.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/opencode/src/tool/question.ts`:
- Around line 8-12: The questions schema currently uses Question.Prompt (which
omits the custom field) so incoming custom:false is dropped; update the schema
for the questions array to accept the full prompt type that includes the custom
boolean (e.g., replace or extend Question.Prompt with the type that includes
custom from packages/opencode/src/question/index.ts or add custom:
z.boolean().optional() to the schema) so the tool preserves and validates the
custom flag and aligns with the tool contract; ensure the updated schema is used
where questions is defined to allow model-driven disabling of custom answers.
🪄 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: af4b4e5d-dc77-4b4f-8f24-8ebad44d4953
⛔ Files ignored due to path filters (2)
packages/sdk/js/src/v2/gen/sdk.gen.tsis excluded by!**/gen/**packages/sdk/js/src/v2/gen/types.gen.tsis excluded by!**/gen/**
📒 Files selected for processing (11)
packages/app/src/pages/session/composer/session-question-dock.tsxpackages/desktop-electron/src/main/windows.tspackages/opencode/src/question/index.tspackages/opencode/src/tool/question.tspackages/opencode/src/tool/question.txtpackages/opencode/test/question/schema.test.tspackages/opencode/test/tool/question.test.tspackages/sdk/openapi.jsonpackages/ui/src/components/message-part.csspackages/ui/src/i18n/en.tspackages/ui/src/i18n/zh.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). (9)
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: unit-windows-opencode-session
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: unit-windows-app
- GitHub Check: unit-desktop
- GitHub Check: smoke-macos-arm64
- GitHub Check: e2e-artifacts
- GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (4)
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)
Renderer process should only call
window.apifromsrc/preload
Files:
packages/desktop-electron/src/main/windows.ts
packages/opencode/**/*.ts
📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
packages/opencode/**/*.ts: UseEffect.gen(function* () { ... })for Effect composition
UseEffect.fn("Domain.method")for named/traced effects andEffect.fnUntracedfor internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer.pipe()wrappers
UseEffect.callbackfor callback-based APIs
PreferDateTime.nowAsDateovernew Date(yield* Clock.currentTimeMillis)when you need aDatein Effect code
UseSchema.Classfor multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
UseSchema.TaggedErrorClassfor typed errors in Effect schemas
UseSchema.Defectinstead ofunknownfor defect-like causes in Effect code
InEffect.gen/Effect.fn, preferyield* new MyError(...)overyield* Effect.fail(new MyError(...))for direct early-failure branches
UsemakeRuntimefromsrc/effect/run-service.tsfor all services; it returns{ runPromise, runFork, runCallback }backed by a sharedmemoMapthat deduplicates layers
UseInstanceStatefromsrc/effect/instance-state.tsfor per-directory or per-project state that needs per-instance cleanup; do work directly in theInstanceState.makeclosure whereScopedCachehandles run-once semantics
UseEffect.addFinalizerorEffect.acquireReleaseinside theInstanceState.makeclosure for cleanup (subscriptions, process teardown, etc.)
UseEffect.forkScopedinside theInstanceState.makeclosure for background stream consumers — the fiber is interrupted when the instance is disposed
PreferFileSystem.FileSysteminstead of rawfs/promisesfor effectful file I/O in Effect services
PreferChildProcessSpawner.ChildProcessSpawnerwithChildProcess.make(...)instead of custom process wrappers in Effect services
PreferHttpClient.HttpClientinstead of rawfetchin Effect services
PreferPath.Path,Config,Clock, andDateTimeservices when those concerns are already inside Effect code
For backgroun...
Files:
packages/opencode/test/tool/question.test.tspackages/opencode/test/question/schema.test.tspackages/opencode/src/tool/question.tspackages/opencode/src/question/index.ts
packages/opencode/test/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)
packages/opencode/test/**/*.test.{ts,tsx}: Use thetmpdirfunction fromfixture/fixture.tsto create temporary directories for tests with automatic cleanup. Useawait usingsyntax to ensure automatic cleanup when the variable goes out of scope.
When using thetmpdirfunction with git repository support, pass thegit: trueoption to initialize a git repo with a root commit.
Use theconfigoption intmpdirto write anopencode.jsonconfig file during test setup by passing a partial Config.Info object.
Use theinitoption intmpdirto define custom setup functions that can return extra data accessible viatmp.extra, and use thedisposeoption for custom cleanup logic.
UsetestEffect(...)fromtest/lib/effect.tsfor tests that exercise Effect services or Effect-based workflows.
Useit.effect(...)when the test should run withTestClockandTestConsole. Useit.live(...)when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers fromfixture/fixture.tsover building manual runtimes in tests: usetmpdirScoped()for scoped temp directories,provideInstance(dir)(effect)for low-level binding without directory creation,provideTmpdirInstance(...)for single temp instance binding, orprovideTmpdirServer(...)for tests that also need the test LLM server.
Defineconst it = testEffect(...)near the top of the test file and keep the test body insideEffect.gen(function* () { ... }). Yield services directly withyield* MyService.Serviceoryield* MyTool.
Avoid customManagedRuntime,attach(...), or ad hocrun(...)wrappers in Effect tests whentestEffect(...)already provides the runtime.
When a test needs instance-local state, preferprovideTmpdirInstance(...)orprovideInstance(...)over manualInstance.provide(...)inside Promise-style tests.
Files:
packages/opencode/test/tool/question.test.tspackages/opencode/test/question/schema.test.ts
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/composer/session-question-dock.tsx
🧠 Learnings (22)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
📚 Learning: 2026-04-25T09:51:16.740Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/menu-template.ts:178-189
Timestamp: 2026-04-25T09:51:16.740Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/menu-template.ts`), the `buildMacosMenuTemplate` and `buildWindowsMenuTemplate` functions are intentionally kept as separate, literal per-platform templates rather than sharing extracted submenu helpers. The split exists to allow future per-platform divergence; the templates already differ structurally (File menu: macOS omits `quit` because it lives in the App menu, Windows places `quit` next to `close`; Window submenu differs; macOS has an App menu with no Windows equivalent; View differs on terminal accelerator `Ctrl+\`` vs `CmdOrCtrl+\``). Do NOT suggest extracting shared submenu builder helpers (e.g., `buildEditSubmenu`, `buildViewSubmenu`) as YAGNI — the literal duplication is intentional and easy to scan.
Applied to files:
packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.
Applied to files:
packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.
Applied to files:
packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Applied to files:
packages/ui/src/i18n/zh.tspackages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.
Applied to files:
packages/opencode/test/tool/question.test.tspackages/opencode/test/question/schema.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 : Test one feature per test file
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `init` option in `tmpdir` to define custom setup functions that can return extra data accessible via `tmp.extra`, and use the `dispose` option for custom cleanup logic.
Applied to files:
packages/opencode/test/question/schema.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/opencode/test/question/schema.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/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T17:02:40.117Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:40.117Z
Learning: In Astro-Han/pawwork, Stylelint is NOT configured in the project. Per AGENTS.md, only linting (not formatting) is used, and the Biome formatter is explicitly disabled. Do NOT raise Stylelint-based review comments (e.g., `declaration-empty-line-before`) in this repo, as they are false positives from the static analysis environment and not enforced by the project toolchain.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T06:50:02.712Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Applied to files:
packages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.ts
📚 Learning: 2026-04-24T05:39:56.086Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/ui/src/components/message-part.csspackages/app/src/pages/session/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.
Applied to files:
packages/ui/src/components/message-part.css
📚 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 branded schemas (`Schema.brand`) for single-value types in Effect
Applied to files:
packages/opencode/src/question/index.ts
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.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/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.tsx
🔇 Additional comments (14)
packages/desktop-electron/src/main/windows.ts (1)
74-75: LGTM! Minimum window dimensions align with PR objectives.Setting
minWidthandminHeightto 480px ensures adequate viewport budget for the question dock and conversation history. At the minimum window height, the 180px chrome buffer leaves 300px for content, which coordinates well with the CSS cap (calc(100dvh - 180px)) introduced in commit C2.packages/opencode/src/tool/question.txt (1)
36-53: Clear and actionable tool-routing contract.The updated bounds + “stream context first, then ask short” guidance is crisp and consistent with the dock UX goals.
packages/ui/src/i18n/en.ts (1)
165-166: Good i18n copy tightening for header chips.“Pick multiple” / “Pick 1” is clearer in the compact header layout.
packages/ui/src/components/message-part.css (1)
835-889: Dock cap + header slot styling look solid.The static cap and non-shrinking mode chip are implemented cleanly for the intended compact dock behavior.
packages/ui/src/i18n/zh.ts (1)
151-152: Concise zh hint updates are aligned with the new header presentation.packages/opencode/test/tool/question.test.ts (1)
76-79: Good fixture update for new option-count constraints.Adding the second option keeps this live test aligned with
Question.Info.optionsmin bound.packages/opencode/test/question/schema.test.ts (1)
77-98: Nice contract guard for actionable validation hints.These assertions prevent silent regressions where bounds remain but repair guidance is lost.
packages/opencode/src/question/index.ts (1)
17-24: Schema tightening is well executed.The new bounds and hint messages are consistent and provide clear repair direction for invalid payloads.
Also applies to: 31-43, 57-60
packages/app/src/pages/session/composer/session-question-dock.tsx (6)
87-88: LGTM! Clean implementation ofcustomAllowedgating.The memo correctly interprets
custom !== falseto allow custom answers by default (when undefined or true), andcountproperly adjusts the focusable option count. This ensures focus management respects the schema constraint.
122-130: LGTM! Per-tab custom check is correctly implemented.The function properly checks
questions()[tab]?.custom !== falserather than using the current tab'scustomAllowed()memo, ensuring correct focus restoration when jumping between tabs with different custom configurations.
148-159: LGTM! Robust cache persistence logic.The
customByTabhelper and sized arrays ensure that:
- Tabs with
custom: falseget cleared values (empty string/false) instead of stale data- Unvisited tabs are handled via nullish coalescing defaults
- Array length matches
total()to avoid index mismatches on restore
309-316: LGTM! Good defensive guard.While
count()should prevent focus from reaching the custom index whencustomAllowed()is false, this explicit check provides a safety net against potential race conditions during rapid tab switches.
390-396: LGTM! Clean header restructure with proper accessibility.The inline mode chip with
aria-hidden="true"on the decorative separator is correct. The semanticdata-slotstructure aligns with the CSS changes mentioned in related context.
455-529: LGTM! Well-structured custom option gating.The implementation correctly:
- Gates the entire custom UI with
<Show when={customAllowed()}>- Handles
customReflifecycle safely (focus logic can't reachoptions().lengthwhen custom is disabled sincecount()excludes it)- Implements proper form focus behavior via
onMouseDownthat delegates to textarea- Allows Shift+Enter for multiline input while Enter submits
The tool description tells the model "Set custom: false only when the options are exhaustive", but Prompt = Info.omit({ custom: true }) hid the flag from the JSON schema sent to providers, making the instruction unreachable from a real tool call. Drop the omit (Prompt = Info.omit({}) re-meta'd) so custom is advertised. The dock already honors custom: false per C3a; this connects the contract end-to-end.
Addresses PR #245 review P2.
The PlanExitTool option description "Switch to build agent and start implementing the plan" was 53 chars, over Question.Option.description.max(50) introduced in this PR. Question.Service.ask doesn't parse Info[] before publishing, so the server could emit an event whose payload violates the published OpenAPI contract. Trim to "Switch to build agent and implement the plan" (44 chars). Addresses PR #245 review P2.
The previous SDK regen turned the experimental cursor query param into a required field, breaking client.experimental.session.list({}) and list() first-page calls. Root cause: z.preprocess(transform, inner.optional()) does not propagate inner-schema optionality to the outer JSON schema in Zod 4 / hono-openapi, so toJSONSchema marked the field required. Add an outer .optional() on the preprocess wrapper so the JSON schema honors the optional contract; keep the inner .optional() so a "" → undefined preprocess still parses (regression test in global-session-list covers this).
Regenerate sdk.gen.ts and types.gen.ts to pick up the optional cursor type.
Addresses PR #245 review P2.
…t viable Without a minHeight floor, a user could shrink the macOS / Windows window below the dock cap's safety threshold. The static cap formula reserves a 180px chrome buffer, so a 320px-tall window would leave 140px for the entire dock (header, options, footer) — the original symptom from #216 reappears in inverse. 480 matches the smallest height the C2 smoke checklist asserts the footer remains visible at, so any in-bounds window is guaranteed to honor the dock-budget contract. Addresses PR #245 review P3.
The previous SDK regen turned the experimental cursor query param into a required field, breaking client.experimental.session.list({}) and list() first-page calls. Root cause: z.preprocess(transform, inner.optional()) does not propagate inner-schema optionality to the outer JSON schema in Zod 4 / hono-openapi, so toJSONSchema marked the field required. Add an outer .optional() on the preprocess wrapper so the JSON schema honors the optional contract; keep the inner .optional() so a "" → undefined preprocess still parses (regression test in global-session-list covers this).
Regenerate sdk.gen.ts and types.gen.ts to pick up the optional cursor type.
Addresses PR #245 review P2.
a714b7c to
b66689a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/opencode/src/question/index.ts`:
- Around line 21-24: The Option schema currently requires description
(z.string()) which blocks label-only choices; update the Option validation in
packages/opencode/src/question/index.ts so Option.description is optional by
changing the schema for description to accept undefined (e.g., use .optional()
on the z.string() validator) and ensure any downstream code that reads
Option.description handles absence (e.g., fallbacks or conditional rendering) in
functions/classes that consume the Option objects.
🪄 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: 270c2504-7662-499e-86f9-27a8b72c4a4c
⛔ Files ignored due to path filters (2)
packages/sdk/js/src/v2/gen/sdk.gen.tsis excluded by!**/gen/**packages/sdk/js/src/v2/gen/types.gen.tsis excluded by!**/gen/**
📒 Files selected for processing (13)
packages/app/src/pages/session/composer/session-question-dock.tsxpackages/desktop-electron/src/main/windows.tspackages/opencode/src/question/index.tspackages/opencode/src/server/instance/experimental.tspackages/opencode/src/tool/plan.tspackages/opencode/src/tool/question.tspackages/opencode/src/tool/question.txtpackages/opencode/test/question/schema.test.tspackages/opencode/test/tool/question.test.tspackages/sdk/openapi.jsonpackages/ui/src/components/message-part.csspackages/ui/src/i18n/en.tspackages/ui/src/i18n/zh.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: unit-windows-opencode-session
- GitHub Check: unit-windows-app
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: typecheck
- GitHub Check: unit-opencode
- GitHub Check: unit-desktop
- GitHub Check: unit-app
- GitHub Check: smoke-macos-arm64
- GitHub Check: e2e-artifacts
- GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (4)
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)
Renderer process should only call
window.apifromsrc/preload
Files:
packages/desktop-electron/src/main/windows.ts
packages/opencode/**/*.ts
📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
packages/opencode/**/*.ts: UseEffect.gen(function* () { ... })for Effect composition
UseEffect.fn("Domain.method")for named/traced effects andEffect.fnUntracedfor internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer.pipe()wrappers
UseEffect.callbackfor callback-based APIs
PreferDateTime.nowAsDateovernew Date(yield* Clock.currentTimeMillis)when you need aDatein Effect code
UseSchema.Classfor multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
UseSchema.TaggedErrorClassfor typed errors in Effect schemas
UseSchema.Defectinstead ofunknownfor defect-like causes in Effect code
InEffect.gen/Effect.fn, preferyield* new MyError(...)overyield* Effect.fail(new MyError(...))for direct early-failure branches
UsemakeRuntimefromsrc/effect/run-service.tsfor all services; it returns{ runPromise, runFork, runCallback }backed by a sharedmemoMapthat deduplicates layers
UseInstanceStatefromsrc/effect/instance-state.tsfor per-directory or per-project state that needs per-instance cleanup; do work directly in theInstanceState.makeclosure whereScopedCachehandles run-once semantics
UseEffect.addFinalizerorEffect.acquireReleaseinside theInstanceState.makeclosure for cleanup (subscriptions, process teardown, etc.)
UseEffect.forkScopedinside theInstanceState.makeclosure for background stream consumers — the fiber is interrupted when the instance is disposed
PreferFileSystem.FileSysteminstead of rawfs/promisesfor effectful file I/O in Effect services
PreferChildProcessSpawner.ChildProcessSpawnerwithChildProcess.make(...)instead of custom process wrappers in Effect services
PreferHttpClient.HttpClientinstead of rawfetchin Effect services
PreferPath.Path,Config,Clock, andDateTimeservices when those concerns are already inside Effect code
For backgroun...
Files:
packages/opencode/src/tool/plan.tspackages/opencode/src/server/instance/experimental.tspackages/opencode/test/tool/question.test.tspackages/opencode/src/tool/question.tspackages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.ts
packages/opencode/test/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)
packages/opencode/test/**/*.test.{ts,tsx}: Use thetmpdirfunction fromfixture/fixture.tsto create temporary directories for tests with automatic cleanup. Useawait usingsyntax to ensure automatic cleanup when the variable goes out of scope.
When using thetmpdirfunction with git repository support, pass thegit: trueoption to initialize a git repo with a root commit.
Use theconfigoption intmpdirto write anopencode.jsonconfig file during test setup by passing a partial Config.Info object.
Use theinitoption intmpdirto define custom setup functions that can return extra data accessible viatmp.extra, and use thedisposeoption for custom cleanup logic.
UsetestEffect(...)fromtest/lib/effect.tsfor tests that exercise Effect services or Effect-based workflows.
Useit.effect(...)when the test should run withTestClockandTestConsole. Useit.live(...)when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers fromfixture/fixture.tsover building manual runtimes in tests: usetmpdirScoped()for scoped temp directories,provideInstance(dir)(effect)for low-level binding without directory creation,provideTmpdirInstance(...)for single temp instance binding, orprovideTmpdirServer(...)for tests that also need the test LLM server.
Defineconst it = testEffect(...)near the top of the test file and keep the test body insideEffect.gen(function* () { ... }). Yield services directly withyield* MyService.Serviceoryield* MyTool.
Avoid customManagedRuntime,attach(...), or ad hocrun(...)wrappers in Effect tests whentestEffect(...)already provides the runtime.
When a test needs instance-local state, preferprovideTmpdirInstance(...)orprovideInstance(...)over manualInstance.provide(...)inside Promise-style tests.
Files:
packages/opencode/test/tool/question.test.tspackages/opencode/test/question/schema.test.ts
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/composer/session-question-dock.tsx
🧠 Learnings (22)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`
Applied to files:
packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-25T09:51:16.740Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/menu-template.ts:178-189
Timestamp: 2026-04-25T09:51:16.740Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/menu-template.ts`), the `buildMacosMenuTemplate` and `buildWindowsMenuTemplate` functions are intentionally kept as separate, literal per-platform templates rather than sharing extracted submenu helpers. The split exists to allow future per-platform divergence; the templates already differ structurally (File menu: macOS omits `quit` because it lives in the App menu, Windows places `quit` next to `close`; Window submenu differs; macOS has an App menu with no Windows equivalent; View differs on terminal accelerator `Ctrl+\`` vs `CmdOrCtrl+\``). Do NOT suggest extracting shared submenu builder helpers (e.g., `buildEditSubmenu`, `buildViewSubmenu`) as YAGNI — the literal duplication is intentional and easy to scan.
Applied to files:
packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.
Applied to files:
packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.
Applied to files:
packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Applied to files:
packages/ui/src/i18n/zh.tspackages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.
Applied to files:
packages/opencode/test/tool/question.test.tspackages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T05:48:36.205Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:36.205Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.
Applied to files:
packages/opencode/src/tool/question.ts
📚 Learning: 2026-04-24T06:50:02.712Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Applied to files:
packages/opencode/src/tool/question.tspackages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.
Applied to files:
packages/opencode/src/tool/question.tspackages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T05:39:56.086Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/opencode/src/tool/question.tspackages/ui/src/components/message-part.csspackages/app/src/pages/session/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.
Applied to files:
packages/ui/src/components/message-part.css
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Test one feature per test file
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.
Applied to files:
packages/opencode/test/question/schema.test.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 branded schemas (`Schema.brand`) for single-value types in Effect
Applied to files:
packages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.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/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T17:02:40.117Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:40.117Z
Learning: In Astro-Han/pawwork, Stylelint is NOT configured in the project. Per AGENTS.md, only linting (not formatting) is used, and the Biome formatter is explicitly disabled. Do NOT raise Stylelint-based review comments (e.g., `declaration-empty-line-before`) in this repo, as they are false positives from the static analysis environment and not enforced by the project toolchain.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.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/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.tsx
🔇 Additional comments (17)
packages/desktop-electron/src/main/windows.ts (1)
74-75: Good minimum-size safeguard.Line 74 and Line 75 add sensible lower bounds that prevent unusably small main windows while keeping current startup/state behavior intact.
packages/opencode/src/tool/plan.ts (1)
42-42: Wording update is clear and behavior-preserving.This keeps intent intact while making the CTA more concise.
packages/opencode/src/server/instance/experimental.ts (1)
374-380: Cursor optionality adjustment looks correct.Applying
.optional()at the outer preprocess level is consistent with accepting omitted/empty cursor inputs without changing downstream pagination decoding.packages/opencode/test/tool/question.test.ts (1)
76-79: Test fixture now matches schema cardinality rules.Good update to keep this test valid under the tightened options bounds.
packages/ui/src/i18n/en.ts (1)
165-166: English hint copy is appropriately tightened for inline mode display.The shorter labels are a good fit for constrained header space.
packages/ui/src/i18n/zh.ts (1)
151-152: Chinese hint copy update is clear and compact.“多选 / 单选” maps cleanly to the inline mode chip presentation.
packages/opencode/src/tool/question.ts (1)
8-12: Question array bounds and repair hints are well-scoped.The 1–4 limits plus actionable messages improve contract clarity for tool callers.
packages/ui/src/components/message-part.css (2)
835-837: Dock height cap change is solid and objective-aligned.This cap enforces a predictable viewport budget and avoids over-expanding the question dock.
863-889: Header mode-chip layout is resilient under truncation.Keeping separator/mode non-shrinking while truncating sequence text is the right tradeoff for readability.
packages/opencode/test/question/schema.test.ts (5)
4-18: Option length boundary tests are complete and precise.Good pairwise reject/accept coverage at 50/51 chars for both label and description.
29-59: Info schema boundary coverage is strong.The tests clearly lock in question/header length limits and 2–4 option cardinality.
61-86: Custom-flag and Prompt parsing behavior is validated well.These cases correctly guard both explicit
custom: falseand omitted-custom paths.
88-109: Actionable error-hint contract is correctly protected.Great choice to assert representative hint text so refactors can’t silently degrade repair guidance.
111-141: Request question-count bounds are covered comprehensively.Rejecting 0/5 and accepting 1–4 directly enforces the intended tool-level contract.
packages/opencode/src/tool/question.txt (1)
34-53: Good content-routing and payload-format guidance.This is clear and actionable for model behavior, and it aligns well with the bounded payload shape and “stream context first, then ask short” flow.
packages/app/src/pages/session/composer/session-question-dock.tsx (2)
87-126: Custom-slot gating is correctly wired into navigation and selection.
customAllowedis consistently applied to focus count, tab focus restoration, and custom-slot activation, preventing invalid focus/selection whencustomis disabled.Also applies to: 309-316
151-158: Cache normalization + conditional custom UI are solid.The cleanup rewrite and
Show when={customAllowed()}block prevent stale custom-state resurrection on remount for tabs that disallow custom input.Also applies to: 455-529
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/opencode/src/question/index.ts`:
- Around line 17-24: The label and description Zod schemas currently accept
empty or whitespace-only strings; update each z.string() for those fields (e.g.,
the label and description schemas shown and the other string fields referenced
31-38) to reject blank input by trimming and enforcing a non-empty length—use
z.string().trim().min(1, "Field cannot be empty or whitespace-only").max(50,
"...").describe("...") (or equivalently add .refine(s => s.trim().length > 0,
"cannot be empty or whitespace-only")) so whitespace-only values are rejected;
apply the same change to all similar string fields in this file.
In `@packages/opencode/src/tool/question.txt`:
- Around line 38-42: The wording is conflicting: "options: 2–4 distinct,
mutually exclusive choices" contradicts allowing non-exclusive choices when
multiple: true; update the spec text for `options` to remove "mutually
exclusive" and instead require choices be distinct and clearly described, and
add a short clarifying clause referencing `multiple` that explains `multiple:
true` permits non-exclusive selections (e.g., feature toggles); keep existing
constraints for `options[].label`, `options[].description`, and `custom`.
🪄 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: 0ea127cd-26e4-477f-a30e-11bd4e07ec9b
⛔ Files ignored due to path filters (2)
packages/sdk/js/src/v2/gen/sdk.gen.tsis excluded by!**/gen/**packages/sdk/js/src/v2/gen/types.gen.tsis excluded by!**/gen/**
📒 Files selected for processing (13)
packages/app/src/pages/session/composer/session-question-dock.tsxpackages/desktop-electron/src/main/windows.tspackages/opencode/src/question/index.tspackages/opencode/src/server/instance/experimental.tspackages/opencode/src/tool/plan.tspackages/opencode/src/tool/question.tspackages/opencode/src/tool/question.txtpackages/opencode/test/question/schema.test.tspackages/opencode/test/tool/question.test.tspackages/sdk/openapi.jsonpackages/ui/src/components/message-part.csspackages/ui/src/i18n/en.tspackages/ui/src/i18n/zh.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). (1)
- GitHub Check: unit-windows-desktop
🧰 Additional context used
📓 Path-based instructions (4)
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)
Renderer process should only call
window.apifromsrc/preload
Files:
packages/desktop-electron/src/main/windows.ts
packages/opencode/**/*.ts
📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
packages/opencode/**/*.ts: UseEffect.gen(function* () { ... })for Effect composition
UseEffect.fn("Domain.method")for named/traced effects andEffect.fnUntracedfor internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer.pipe()wrappers
UseEffect.callbackfor callback-based APIs
PreferDateTime.nowAsDateovernew Date(yield* Clock.currentTimeMillis)when you need aDatein Effect code
UseSchema.Classfor multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
UseSchema.TaggedErrorClassfor typed errors in Effect schemas
UseSchema.Defectinstead ofunknownfor defect-like causes in Effect code
InEffect.gen/Effect.fn, preferyield* new MyError(...)overyield* Effect.fail(new MyError(...))for direct early-failure branches
UsemakeRuntimefromsrc/effect/run-service.tsfor all services; it returns{ runPromise, runFork, runCallback }backed by a sharedmemoMapthat deduplicates layers
UseInstanceStatefromsrc/effect/instance-state.tsfor per-directory or per-project state that needs per-instance cleanup; do work directly in theInstanceState.makeclosure whereScopedCachehandles run-once semantics
UseEffect.addFinalizerorEffect.acquireReleaseinside theInstanceState.makeclosure for cleanup (subscriptions, process teardown, etc.)
UseEffect.forkScopedinside theInstanceState.makeclosure for background stream consumers — the fiber is interrupted when the instance is disposed
PreferFileSystem.FileSysteminstead of rawfs/promisesfor effectful file I/O in Effect services
PreferChildProcessSpawner.ChildProcessSpawnerwithChildProcess.make(...)instead of custom process wrappers in Effect services
PreferHttpClient.HttpClientinstead of rawfetchin Effect services
PreferPath.Path,Config,Clock, andDateTimeservices when those concerns are already inside Effect code
For backgroun...
Files:
packages/opencode/src/tool/question.tspackages/opencode/test/tool/question.test.tspackages/opencode/src/tool/plan.tspackages/opencode/test/question/schema.test.tspackages/opencode/src/server/instance/experimental.tspackages/opencode/src/question/index.ts
packages/opencode/test/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)
packages/opencode/test/**/*.test.{ts,tsx}: Use thetmpdirfunction fromfixture/fixture.tsto create temporary directories for tests with automatic cleanup. Useawait usingsyntax to ensure automatic cleanup when the variable goes out of scope.
When using thetmpdirfunction with git repository support, pass thegit: trueoption to initialize a git repo with a root commit.
Use theconfigoption intmpdirto write anopencode.jsonconfig file during test setup by passing a partial Config.Info object.
Use theinitoption intmpdirto define custom setup functions that can return extra data accessible viatmp.extra, and use thedisposeoption for custom cleanup logic.
UsetestEffect(...)fromtest/lib/effect.tsfor tests that exercise Effect services or Effect-based workflows.
Useit.effect(...)when the test should run withTestClockandTestConsole. Useit.live(...)when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers fromfixture/fixture.tsover building manual runtimes in tests: usetmpdirScoped()for scoped temp directories,provideInstance(dir)(effect)for low-level binding without directory creation,provideTmpdirInstance(...)for single temp instance binding, orprovideTmpdirServer(...)for tests that also need the test LLM server.
Defineconst it = testEffect(...)near the top of the test file and keep the test body insideEffect.gen(function* () { ... }). Yield services directly withyield* MyService.Serviceoryield* MyTool.
Avoid customManagedRuntime,attach(...), or ad hocrun(...)wrappers in Effect tests whentestEffect(...)already provides the runtime.
When a test needs instance-local state, preferprovideTmpdirInstance(...)orprovideInstance(...)over manualInstance.provide(...)inside Promise-style tests.
Files:
packages/opencode/test/tool/question.test.tspackages/opencode/test/question/schema.test.ts
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/pages/session/composer/session-question-dock.tsx
🧠 Learnings (22)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 245
File: packages/opencode/src/question/index.ts:21-24
Timestamp: 2026-04-26T15:35:31.757Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/question/index.ts`), `Question.Option.description` is intentionally **required** (`z.string()`, not `.optional()`). This matches the upstream opencode contract and all current callers (e.g. `PlanExitTool`) always supply a description. The defensive `<Show when={props.description}>` rendering in `session-question-dock.tsx` is a standard guard, not a signal that the field is intended to be optional. Do NOT suggest making `Option.description` optional without a dedicated follow-up that covers schema + tool description + dock copy + tests.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`
Applied to files:
packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.
Applied to files:
packages/desktop-electron/src/main/windows.ts
📚 Learning: 2026-04-26T15:35:31.757Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 245
File: packages/opencode/src/question/index.ts:21-24
Timestamp: 2026-04-26T15:35:31.757Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/question/index.ts`), `Question.Option.description` is intentionally **required** (`z.string()`, not `.optional()`). This matches the upstream opencode contract and all current callers (e.g. `PlanExitTool`) always supply a description. The defensive `<Show when={props.description}>` rendering in `session-question-dock.tsx` is a standard guard, not a signal that the field is intended to be optional. Do NOT suggest making `Option.description` optional without a dedicated follow-up that covers schema + tool description + dock copy + tests.
Applied to files:
packages/opencode/src/tool/question.tspackages/opencode/test/tool/question.test.tspackages/opencode/src/tool/plan.tspackages/opencode/test/question/schema.test.tspackages/opencode/src/server/instance/experimental.tspackages/ui/src/components/message-part.csspackages/opencode/src/tool/question.txtpackages/opencode/src/question/index.tspackages/app/src/pages/session/composer/session-question-dock.tsx
📚 Learning: 2026-04-24T05:48:36.205Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/e2e/app/composer-parity.spec.ts:0-0
Timestamp: 2026-04-24T05:48:36.205Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, the Model chip trigger button carries `data-action="prompt-model"` (around line 1187) and the Variant chip trigger button carries `data-action="prompt-model-variant"` (around line 1231), both set via `triggerProps`. These are therefore already captured by any `[data-action]` selector sweep in E2E tests and do not need a separate `[data-component]` query to be included in parity assertions — though unioning both is kept as belt-and-suspenders in `collectBarSet`.
Applied to files:
packages/opencode/src/tool/question.ts
📚 Learning: 2026-04-24T06:50:02.712Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:02.712Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Applied to files:
packages/opencode/src/tool/question.tspackages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.
Applied to files:
packages/opencode/src/tool/question.tspackages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T05:39:56.086Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 208
File: packages/app/src/components/prompt-input.tsx:1569-1611
Timestamp: 2026-04-24T05:39:56.086Z
Learning: In Astro-Han/pawwork `packages/app/src/components/prompt-input.tsx`, after the composer unification in PR `#208` (fixed in commit 5d810aa):
- `SendButton.disabled` does NOT gate on `store.mode !== "normal"`. Shell mode has a fully visible, clickable orange submit button that calls `handleSubmit` directly (same path as the Enter key in `handleKeyDown`). Do NOT suggest re-adding the mode gate.
- `SendButton` does NOT use the `buttons()` spring opacity animation (`style={buttons()}`). It is always fully visible regardless of mode.
- `WorkspaceChip` is gated on `props.homeMode && store.mode === "normal"` so it hides in shell mode (preventing it from appearing isolated/bright while neighboring controls fade).
- The left-side chip group (`aria-hidden={store.mode !== "normal"}`) covers attach/model/variant/workspace controls only; `SendButton` remains in a separate right-side sibling div.
Applied to files:
packages/opencode/src/tool/question.tspackages/ui/src/components/message-part.csspackages/app/src/pages/session/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.
Applied to files:
packages/opencode/test/tool/question.test.tspackages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.
Applied to files:
packages/ui/src/i18n/zh.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Applied to files:
packages/ui/src/i18n/zh.tspackages/opencode/test/question/schema.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 : Test one feature per test file
Applied to files:
packages/opencode/test/question/schema.test.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 branded schemas (`Schema.brand`) for single-value types in Effect
Applied to files:
packages/opencode/test/question/schema.test.tspackages/opencode/src/question/index.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.
Applied to files:
packages/opencode/test/question/schema.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/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T17:02:40.117Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:40.117Z
Learning: In Astro-Han/pawwork, Stylelint is NOT configured in the project. Per AGENTS.md, only linting (not formatting) is used, and the Biome formatter is explicitly disabled. Do NOT raise Stylelint-based review comments (e.g., `declaration-empty-line-before`) in this repo, as they are false positives from the static analysis environment and not enforced by the project toolchain.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.
Applied to files:
packages/opencode/test/question/schema.test.ts
📚 Learning: 2026-04-23T17:02:35.873Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 198
File: packages/app/src/index.css:95-97
Timestamp: 2026-04-23T17:02:35.873Z
Learning: In the pawwork repository, Stylelint is intentionally not configured (per AGENTS.md, only linting is enforced and the Biome formatter is disabled). When reviewing CSS files, do not raise Stylelint rule-based issues (e.g., `declaration-empty-line-before`) because they are false positives and not enforced by the project toolchain.
Applied to files:
packages/ui/src/components/message-part.css
📚 Learning: 2026-04-23T15:26:07.250Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/src/pages/layout/sidebar-items.tsx:102-107
Timestamp: 2026-04-23T15:26:07.250Z
Learning: In Astro-Han/pawwork (`packages/app/src/pages/layout/sidebar-items.tsx`), the `indicator()` function in `SessionRow` intentionally renders `props.leadingSlot` (the pin button) only as a fallback when no status indicator (running/permission/error/unseen) is active. When a higher-priority status wins the slot, the pin button is removed from the DOM — this is a deliberate design choice for the merged leading slot (`#150`). The keyboard unpin path is preserved via: (1) focusing the row anchor triggers `group-focus-within` which reveals the dots menu trigger, then Tab → Enter → "Unpin Session"; (2) the context menu (right-click / Shift+F10) exposes "Unpin Session". The "always render + CSS overlay" approach was considered but rejected due to z-index/pointer-events complexity; residual `...` slot behavior is tracked in `#192`. Do NOT flag the absence of the pin button from the DOM when a status is active as an accessibility regression.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.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/composer/session-question-dock.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.
Applied to files:
packages/app/src/pages/session/composer/session-question-dock.tsx
🔇 Additional comments (20)
packages/desktop-electron/src/main/windows.ts (1)
74-75: Good guardrail for usability.Setting
minWidthandminHeighthere is a clean, low-risk way to enforce a minimum usable window size and matches the stated objective.packages/opencode/src/server/instance/experimental.ts (1)
374-380: Good cursor optionality fix in query schema.Line 379 correctly makes the
cursorfield optional at the schema boundary while preserving empty-string normalization via preprocess. This matches the downstream decoding behavior that treats absent/empty cursor values as no cursor.packages/opencode/src/tool/plan.ts (1)
42-42: LGTM!The description text is now more concise ("implement the plan" vs "start implementing the plan"), aligning with the PR's objective of tightening tool descriptions.
packages/ui/src/i18n/zh.ts (1)
151-152: LGTM!The shortened Chinese hint strings ("多选" / "单选") are idiomatic and align well with the English counterparts ("Pick multiple" / "Pick 1"). This supports the UI refactor that moved hints into the header where space is more constrained.
packages/ui/src/components/message-part.css (2)
835-837: LGTM!The dock height cap using
min(70dvh, calc(100dvh - 180px))correctly addresses issue#216. The inline comment documenting the 180px breakdown (44 + 48 + 48 + 40) is helpful for future maintenance. Combined with the ElectronminHeight: 480constraint added in the companion commit, this ensures the footer remains visible across supported window sizes.
863-889: LGTM!The header layout correctly implements:
- Flex baseline alignment for visual consistency
- Ellipsis truncation on the sequence portion (
min-width: 0; overflow: hidden)flex-shrink: 0on separator and mode to prevent them from collapsingfont-weight: 600on mode chip as specified in PR descriptionpackages/ui/src/i18n/en.ts (1)
165-166: LGTM!The shortened hint strings ("Pick multiple" / "Pick 1") are clear, action-oriented, and appropriately concise for display in the header alongside the question progress indicator.
packages/opencode/src/tool/question.ts (1)
8-12: LGTM!The schema constraints (1–4 questions) with actionable error messages align with the PR objectives. The max error message helpfully guides the model to "split into multiple tool calls or stream context first" for self-correction.
packages/opencode/test/tool/question.test.ts (1)
76-79: LGTM!The test fixture correctly updated to provide 2 options, complying with the new
options.min(2)schema constraint. Both options include required descriptions per the established contract.packages/app/src/pages/session/composer/session-question-dock.tsx (5)
87-88: LGTM!The
customAllowedderivation correctly interpretscustom !== false(treatingundefinedas allowed, per the SDK typecustom?: boolean). Thecount()adjustment ensures keyboard navigation boundaries are accurate when custom input is disabled.
122-130: LGTM!The
pickFocusfunction correctly checks whether custom is allowed for the target tab before returning the custom slot index. This prevents focus from landing on a non-existent element when navigating between tabs with differentcustomsettings.
151-158: LGTM!Cache serialization correctly rebuilds
customandcustomOnarrays tototal()length, persisting empty/false for tabs where custom is disallowed. This prevents stale focus state when restoring a cached request with mixed custom-allowed tabs.
390-396: LGTM!The header now displays the question sequence, separator, and selection mode inline. This layout aligns with the CSS changes in
message-part.cssand the shortened i18n strings.
455-529: LGTM!The custom answer slot is correctly gated by
<Show when={customAllowed()}>, ensuring the UI respects theQuestion.Info.customflag. Both the collapsed button state and the expanded editing form are properly wrapped.packages/opencode/test/question/schema.test.ts (1)
1-142: LGTM!Comprehensive test coverage for the new schema constraints:
- Boundary tests for all length limits (Option.label/description 50, Info.question 200, Info.header 30)
- Cardinality tests for options (2–4) and questions (1–4)
- Actionable hint assertions ensuring error messages guide LLM self-correction
- Edge case coverage for
custom: falsewith empty optionsThe test structure follows established patterns with clear naming and appropriate use of fixtures.
packages/opencode/src/question/index.ts (3)
39-43: Good cardinality constraints on options.The
options2–4 bound is clear and directly supports the intended dock UX/scannability.
50-54: Prompt schema exposure forcustomlooks correct.Keeping
customon the LLM-facing prompt and preserving a separate schema ref is a solid way to align tool docs with reachable tool inputs.
61-64: Request-level question count bounds are well-defined.The
questions1–4 constraint and actionable hint text are precise and consistent with the tool contract.packages/opencode/src/tool/question.txt (2)
1-23: Use/avoidance criteria are clear and actionable.This is a strong upgrade from generic guidance and should improve proactive-but-disciplined tool use.
45-53: Content-routing guidance is solid.Separating long framing from the short tool prompt should materially improve dock readability and option visibility.
…ol description
- Add character caps: question.max(200), header.max(30), option.label.max(50), option.description.max(50)
- Add count caps: options.min(2).max(4) on Info, questions.min(1).max(4) on the tool parameters array (the actual entry-point — Question.Service.ask takes Info[] directly and never parses Question.Request, so the array bound has to live on the tool param schema to fire on a real call)
- Each bound carries an actionable hint message so LLMs see structured repair guidance on rejection; tests assert the hint content for one length-error and one count-error so a future refactor cannot silently drop the contract
- Expose `custom` flag in the LLM-facing schema (Prompt = Info.omit({})) so the tool description's "Set false only when options are exhaustive" instruction is reachable from a real tool call; the dock already honors custom: false per a later commit
- Trim PlanExitTool option description from 53 to 44 chars to fit the new option.description.max(50) cap (Question.Service.ask doesn't parse Info[] before publishing, so internal callers must self-fit)
- Rewrite question.txt with When/NOT/Examples/Format/Content-routing sections inspired by Claude Code's EnterPlanMode prompt; Content-routing teaches the model to stream long framing before invoking the tool
- Regenerate packages/sdk/openapi.json and v2 type bindings so external SDK and API docs reflect the new bounds
- Repair the existing tool/question.test.ts fixture (originally 1 option, now caught by options.min(2))
Part of #188.
Replace the dynamic measure() function (which fell back to 100dvh when sticky-class detection failed, causing the v0.2.12 footer-off-screen repro) with a static max-height: min(70dvh, calc(100dvh - 180px)). The 180px buffer is the verified PawWork session top chrome stack: 44 titlebar + 48 tabs + 48 sticky session title + 40 safety margin. No JS measurement, no ResizeObserver, no CSS variables — the cap math is structurally stable. Closes #216.
The dock previously rendered the type-your-own-answer slot regardless of the schema's custom flag, and counted it in keyboard navigation. PR A wraps the custom UI in <Show when={q.custom !== false}> and updates count(), pickFocus(), and selectOption() so the slot is fully omitted when disallowed. The cache also clears custom state for tabs whose schema disallows it, preventing stale focus on remount.
Part of #188.
Replace the dedicated [data-slot="question-hint"] row with an inline mode chip in the header title: "{n} of {total} questions · {mode}". Mode renders in --text-strong weight 600 with flex-shrink:0 so it survives the ellipsis race; separator in --icon-weak. Tighten en+zh hint copy: "Pick 1" / "Pick multiple" / "单选" / "多选" — Chinese uses the established UI convention rather than a literal translation. Other 16 locales follow upstream sync.
Closes #188.
…t viable Without a minHeight floor, a user could shrink the macOS / Windows window below the dock cap's safety threshold. The static cap formula reserves a 180px chrome buffer, so a 320px-tall window would leave 140px for the entire dock (header, options, footer) — the original symptom from #216 reappears in inverse. 480 matches the smallest height the C2 smoke checklist asserts the footer remains visible at, so any in-bounds window is guaranteed to honor the dock-budget contract. Addresses PR #245 review P3.
The previous SDK regen turned the experimental cursor query param into a required field, breaking client.experimental.session.list({}) and list() first-page calls. Root cause: z.preprocess(transform, inner.optional()) does not propagate inner-schema optionality to the outer JSON schema in Zod 4 / hono-openapi, so toJSONSchema marked the field required. Add an outer .optional() on the preprocess wrapper so the JSON schema honors the optional contract; keep the inner .optional() so a "" → undefined preprocess still parses (regression test in global-session-list covers this).
Regenerate sdk.gen.ts and types.gen.ts to pick up the optional cursor type.
Addresses PR #245 review P2.
b66689a to
1a49534
Compare
Summary
Closes #216 and #188 with four reversible commits:
feat(question)— schema bounds (question.max(200),header.max(30),option.label.max(50),option.description.max(50),options.min(2).max(4),questions.min(1).max(4)),Info.refine(custom !== false || options.length > 0), every bound carries an actionable hint message so the LLM gets a structured repair path on rejection. Tool description rewritten withWhen / NOT / Examples / Format / Content routingsections (inspired by Claude Code'sEnterPlanMode);Content routingteaches the model to stream long framing as normal output before invoking the tool.fix(ui)— replace the dynamicmeasure()(which fell back to100dvhwhen sticky-class detection failed, the v0.2.12 footer-off-screen repro) with a staticmax-height: min(70dvh, calc(100dvh - 180px)). The 180px buffer is the PawWork session top chrome stack: 44 titlebar + 48 tabs + 48 sticky session title + 40 safety margin. No JS measurement, noResizeObserver, no CSS variables.feat(ui)— honorQuestion.Info.customflag: wrap the type-your-own-answer slot in<Show when={q.custom !== false}>, updatecount() / pickFocus() / selectOption(), and clear cache for tabs whose schema disallows custom (avoids stale focus on remount).feat(ui)— inline mode chip in header:{n} of {total} questions · {Pick 1 | Pick multiple}. Mode renders weight 600 withflex-shrink:0so it survives ellipsis on narrow widths. Drop the dedicated[data-slot="question-hint"]row. Tightenen+zhhint copy:Pick 1/Pick multiple/选 1 个/可选多个. Other 16 locales follow upstream sync.Why
#216 reported the question dock blocking the footer at small viewport heights (the
measure()fallback made it render at full viewport when sticky-class detection failed). #188 asked for clearer triggers and tighter formatting so the model invokes the tool more confidently and writes shorter, more clickable questions. Both ship together because they share the dock TSX surface and the schema-versus-prompt discipline; splitting into separate PRs would have meant rebasing the dock changes twice.Related Issue
Closes #216
Closes #188
How To Verify
Manual UI smoke (passed locally on macOS arm64 dev:desktop):
100dvh - 180 = 300pxbudget). 1080px tall: dock caps at ~70dvh ≈ 756px, internal scroll for overflow options.2 of 4 questions · Pick 1(zh:第 2 题 / 共 4 题 · 选 1 个). Multi-choice:... · Pick multiple/可选多个. ~480px-wide window: title ellipsizes, mode chip remains fully visible (flex-shrink:0), progress dots stay aligned right. No dedicated hint row below the question text.custom: falserenders no textarea; keyboard navigation does not focus an empty trailing slot.Question is too long (max 200 chars)...and the model self-corrected. 5-option request rejected withEach question allows at most 4 options....Performance check from session export: average reasoning per turn 96 tok across 12 follow-up turns; first-call schema orientation 615 tok one-time, paid into the prompt cache. No simplification warranted.
Screenshots or Recordings
Verified on macOS dev:desktop. Header structure changes captured in spec at the linked issue comment; dock cap behavior is non-visual (footer-visible regression check).
Checklist
dev, and my PR title and commit messages use Conventional Commits in EnglishSummary by CodeRabbit
New Features
Bug Fixes
Documentation
UI/Style
Tests
Chores