refactor(web): migrate operator UI to Svelte 5 runes (core, behavior-preserving)#87
Conversation
…ctive_update to failing error Default svelte-check under CLAUDECODE emits machine output and reports non_reactive_update as a non-failing WARNING (exit 0), so a missed $state would false-green. --output human + --compiler-warnings non_reactive_update:error makes it a hard gate: verified legacy=pass(exit0), missed-$state=fail(exit1), on:click deprecation=warning-only(exit0, kept in scope).
…ctive_update) but NOT $bindable (runtime error; smoke-verified)
…Snapshot modals to runes
…nes ($derived/$state)
…$state array
Final-review Critical: the migrated `messages = messages // trigger reactivity`
self-assign is a strict-=== no-op under $state, and the streaming code mutated a
raw captured ref that bypasses the proxy — so assistant progress/output/error never
rendered (a behavior regression the migration introduced; Svelte 4 let-reassign
forced reactivity, $state does not). Now mutate messages[aIdx].{progress,output,
error,done} by index through the proxy (reviewer's verified positive-control pattern).
svelte-check 0 errors, vite build OK, uidist rebuilt.
📝 WalkthroughWalkthroughThe PR migrates 19 Svelte components from legacy reactivity to Svelte 5 runes, adds ChangesSvelte 5 migration
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md`:
- Line 286: Update the JavaScript code fence in the migration plan to include
the js language identifier, changing the opening fence from an unlabeled fence
to ```js while preserving the example content.
- Around line 1-7: Translate the prose in the Svelte 5 runes migration plan to
Korean, including the title, worker guidance, goal, and architecture
description. Preserve code identifiers, Svelte rune names, paths, commands, file
names, component counts, and other contract-specific values exactly as written.
- Line 206: Fix the Markdown table syntax by escaping every embedded || operator
in docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md at lines 206 and
253-255, and in
docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md at lines
56-57; preserve the displayed code while preventing the operators from being
interpreted as table separators.
- Around line 75-85: Update the documented web/package.json check script in Step
3 to pass the warning-promotion option required by the migration gate, ensuring
non_reactive_update warnings are treated as errors while preserving the existing
jsconfig path.
In `@docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md`:
- Line 26: Update the component-list code fence in the Svelte 5 runes migration
design document to include a language identifier, using text or another
appropriate identifier, while preserving the fenced content.
- Around line 36-42: Update the bindable-props migration guidance to state that
non-$bindable bindings are detected at runtime through the bind_not_bindable
error, not by svelte-check at compile time. Retain the manual smoke test
requirement and keep the identified BuiltinPicker.selected and ModelPicker.value
cases unchanged.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: fc730cad-ec3b-41d3-9b98-fa5f2cfa6100
⛔ Files ignored due to path filters (1)
web/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (26)
cmd/goose-daemon/uidist/assets/index-CMV4roMS.jscmd/goose-daemon/uidist/assets/index-CVS1qcEN.jscmd/goose-daemon/uidist/index.htmldocs/superpowers/plans/2026-07-19-svelte5-runes-migration.mddocs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.mdweb/jsconfig.jsonweb/package.jsonweb/src/components/ActivityFeed.svelteweb/src/components/AddAgentModal.svelteweb/src/components/BroadcastModal.svelteweb/src/components/BuiltinPicker.svelteweb/src/components/BuiltinsModal.svelteweb/src/components/ChangeRoleModal.svelteweb/src/components/CreateFlockModal.svelteweb/src/components/FlockDetail.svelteweb/src/components/ModelPicker.svelteweb/src/components/Monitoring.svelteweb/src/components/ProfileModal.svelteweb/src/components/RestoreModal.svelteweb/src/components/SendTaskModal.svelteweb/src/components/Settings.svelteweb/src/components/SnapshotModal.svelteweb/src/components/SystemPromptModal.svelteweb/src/components/TaskPanel.svelteweb/src/components/VMDetail.svelteweb/src/components/WatchdogPanel.svelte
| # Svelte 5 runes migration (core) Implementation Plan | ||
|
|
||
| > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. | ||
|
|
||
| **Goal:** Migrate the 19 operator-UI components under `web/src/components/` that use legacy Svelte 4 reactivity to Svelte 5 runes (`$props`/`$state`/`$derived`/`$effect`/`$bindable`), behavior-preserving, gated by a new `svelte-check` step. | ||
|
|
||
| **Architecture:** Hand-migrate component-by-component (grouped into tasks). Each component enters runes mode atomically: `export let`→`$props` (+ `$bindable` for parent-bound props), `$:`→`$derived`/`$effect`, and every reactive local `let`→`$state`. A new `svelte-check` gate (`--compiler-warnings non_reactive_update:error`) mechanically catches a missed `$state` (promoted to a failing error). A missed `$bindable` is a Svelte *runtime* error the static gate does NOT catch — the 2 bindable props are specified exactly and verified by the Task 7 manual smoke. Mixed legacy+runes across components is fully supported, so the app builds and works at every step. Stores, `on:` directives, `createEventDispatcher`, and `$store` auto-subscription are unchanged. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Translate the plan prose to Korean.
As per coding guidelines: **/*.md: 문서는 한국어로 작성하되 코드 식별자, API 경로, 환경 변수, 명령어, 파일명 등 계약을 구성하는 값은 원문을 유지한다. The plan is predominantly English; translate its prose while preserving identifiers, commands, paths, and API names.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md` around lines 1
- 7, Translate the prose in the Svelte 5 runes migration plan to Korean,
including the title, worker guidance, goal, and architecture description.
Preserve code identifiers, Svelte rune names, paths, commands, file names,
component counts, and other contract-specific values exactly as written.
Source: Coding guidelines
| - [ ] **Step 3: Add the `check` script to `web/package.json`** | ||
|
|
||
| In `web/package.json` `"scripts"`, add `check` alongside the existing `dev`/`build`/`preview`: | ||
| ```json | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build", | ||
| "preview": "vite preview", | ||
| "check": "svelte-check --tsconfig ./jsconfig.json" | ||
| }, | ||
| ``` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make the documented check command enforce the stated gate.
Line 83 omits the warning promotion required by Lines 21 and 143, so non_reactive_update would remain non-fatal.
Proposed fix
- "check": "svelte-check --tsconfig ./jsconfig.json"
+ "check": "svelte-check --tsconfig ./jsconfig.json --output human --compiler-warnings \"non_reactive_update:error\""📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - [ ] **Step 3: Add the `check` script to `web/package.json`** | |
| In `web/package.json` `"scripts"`, add `check` alongside the existing `dev`/`build`/`preview`: | |
| ```json | |
| "scripts": { | |
| "dev": "vite", | |
| "build": "vite build", | |
| "preview": "vite preview", | |
| "check": "svelte-check --tsconfig ./jsconfig.json" | |
| }, | |
| ``` | |
| - [ ] **Step 3: Add the `check` script to `web/package.json`** | |
| In `web/package.json` `"scripts"`, add `check` alongside the existing `dev`/`build`/`preview`: |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md` around lines 75
- 85, Update the documented web/package.json check script in Step 3 to pass the
warning-promotion option required by the migration gate, ensuring
non_reactive_update warnings are treated as errors while preserving the existing
jsconfig path.
| |---|---|---| | ||
| | `Settings:25` | `$: availableProviders = providers.filter((p) => p.available)` | `let availableProviders = $derived(providers.filter((p) => p.available))` | | ||
| | `CreateFlockModal:28` | `$: defaultProfile = profiles.length ? profiles[0].name : 'default'` | `let defaultProfile = $derived(profiles.length ? profiles[0].name : 'default')` | | ||
| | `CreateFlockModal:69` | `$: tokenRows = result ? Object.entries(result.agent_tokens || {}) : []` | `let tokenRows = $derived(result ? Object.entries(result.agent_tokens || {}) : [])` | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the Markdown syntax errors in both documents.
Unescaped || operators are being parsed as table separators, causing MD056 column-count errors.
docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md#L206-L206: escape the||operator.docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md#L253-L255: escape all embedded||operators.docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md#L56-L57: escape the embedded||operators.
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)
[warning] 206-206: Table column count
Expected: 3; Actual: 7; Too many cells, extra data will be missing
(MD056, table-column-count)
📍 Affects 2 files
docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md#L206-L206(this comment)docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md#L253-L255docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md#L56-L57
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md` at line 206,
Fix the Markdown table syntax by escaping every embedded || operator in
docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md at lines 206 and
253-255, and in
docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md at lines
56-57; preserve the displayed code while preventing the operators from being
interpreted as table separators.
Source: Linters/SAST tools
| - Produces: these 2 in runes mode; the two `$bindable` props preserved so parent `bind:` keeps working. | ||
|
|
||
| **`BuiltinPicker.svelte`** prop conversion: | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language identifier to the code fence.
Mark this JavaScript example as ```js to satisfy Markdown linting and improve rendering.
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)
[warning] 286-286: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-07-19-svelte5-runes-migration.md` at line 286,
Update the JavaScript code fence in the migration plan to include the js
language identifier, changing the opening fence from an unlabeled fence to ```js
while preserving the example content.
Source: Linters/SAST tools
|
|
||
| ## 전환 대상 (19 컴포넌트, union of export let ∪ `$:`) | ||
|
|
||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language identifier to the component-list code fence.
Use ```text (or another appropriate identifier) to satisfy Markdown linting.
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)
[warning] 26-26: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md` at line
26, Update the component-list code fence in the Svelte 5 runes migration design
document to include a language identifier, using text or another appropriate
identifier, while preserving the fenced content.
Source: Linters/SAST tools
| 1. `export let x` → `let { x } = $props()`; 기본값 있으면 `let { x = d } = $props()`. 한 컴포넌트의 모든 prop을 하나의 `$props()` 구조분해로 모은다. | ||
| 2. **bindable props**(부모가 `bind:`로 양방향 바인딩): 반드시 `$bindable()`. 확인된 2건: | ||
| - `BuiltinPicker.selected` (부모: BuiltinsModal, ProfileModal) → `let { selected = $bindable() } = $props()`. | ||
| - `ModelPicker.value` (부모: Settings, ProfileModal) → `let { value = $bindable() } = $props()`. | ||
| - (svelte-check가 non-bindable prop 바인딩을 에러로 잡으므로 누락은 게이트에서 검출된다.) | ||
| 3. `$: x = expr`(derived, 10건) → `let x = $derived(expr)`. | ||
| 4. `$:` side-effect(1건, `ModelPicker.svelte:19` `$: if (value && models.includes(value)) custom = false`) → `$effect(() => { if (value && models.includes(value)) custom = false })`. **개별 리뷰**: effect는 렌더 후 실행되고 effect 내 state 변경이 재렌더를 유발하므로, 이 케이스가 `$effect`로 정확히 보존되는지 확인하고(필요 시 파생/이벤트 기반으로 재구성) 무한 루프가 없음을 확인한다. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
For Svelte 5.56.4, does svelte-check detect binding to a prop that is not declared with $bindable(), or is this only reported at runtime?
💡 Result:
In Svelte 5, attempting to use the bind: directive on a component property that is not explicitly declared with the $bindable rune is treated as a runtime error [1][2]. Svelte-check does not currently report this mismatch at compile time [3]. When you attempt to use bind: on a non-bindable property, Svelte issues a bind_not_bindable error, which manifests at runtime [1]. Because the Svelte 5 reactivity system relies on signals rather than static analysis, the compiler does not always have sufficient information to definitively flag these binding attempts during the build process [4]. Consequently, developers typically discover these issues in the browser console during development [2].
Citations:
- 1: https://svelte.dev/docs/svelte/runtime-errors
- 2: Svelte 5 bindable not working local sveltejs/svelte#11242
- 3: Svelte 5: Add warning to compiler when using
bind:valueincorrectly sveltejs/svelte#11685 - 4: Explicit bindings with
$props.bindable()sveltejs/svelte#10768
Align the $bindable check with runtime behavior. svelte-check does not flag bindings to non-$bindable() props at compile time; keep the manual smoke test and describe this as a runtime bind_not_bindable error instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md` around
lines 36 - 42, Update the bindable-props migration guidance to state that
non-$bindable bindings are detected at runtime through the bind_not_bindable
error, not by svelte-check at compile time. Retain the manual smoke test
requirement and keep the identified BuiltinPicker.selected and ModelPicker.value
cases unchanged.
요약
anvil operator Web UI(
web/)의 19개 컴포넌트를 Svelte 4 legacy 반응성에서 Svelte 5 core runes로 마이그레이션한다 —export let→$props,$:→$derived/$effect, 지역 반응let→$state, 부모-바인딩 프롭→$bindable. behavior-preserving. stores·on:·createEventDispatcher·$store자동구독은 유지(runes 모드에서 동작, 스코프 밖). props/$:없는 나머지 ~11개는 legacy 유지(혼합 모드 지원).변경 (SDD 7 태스크)
svelte-check게이트 신설 (2a6e229+4174d9a):web/package.jsondevDepsvelte-check+typescript@^6,web/jsconfig.json,"check": "svelte-check --output human --compiler-warnings non_reactive_update:error"— 놓친$state를 failing error로 승격(teeth-verified: 누락 시 exit1).on:deprecation 경고는 예상·비차단.2fcd108/5440c3f/e588627/fcd556a/2a04fd2): 19개 →$props/$state/$derived/$effect/$bindable. 각 태스크 spec✅/Approved.1a8a5e2)://go:embed되는cmd/goose-daemon/uidist/재생성(번들 223→217kB, runes 더 경량).4184137): 아래 참조.검증
messages = messages // trigger reactivity(Svelte4 반응성 강제 관용구)는$state에서 strict-===no-op이고, 스트리밍이 프록시를 우회하는 raw 캡처 ref를 변이 → assistant progress/output/error가 렌더 안 됨. 리뷰어가 컴파일+실행으로 실증(positive control 포함). 6개 태스크 리뷰 모두 놓친(Task3는 "harmless"로 오판) 진짜 behavior regression. 수정: rawa제거,const aIdx=messages.length-1후 모든 write를messages[aIdx].{progress,output,error,done}로 프록시 경유(index). 재검증: 리뷰어가 fixed 패턴 실증 재실행 → progress 라이브 렌더·output 렌더·done flip 확인(pre-fix는0::falsefrozen). TaskPanel이 이 패턴의 유일 컴포넌트(grep 확인).svelte-check0 errors(114 warn = 전부on:deprecation, 스코프 유지),npx vite buildOK,go build ./...OK(daemon이 새 uidist embed),gofmt -l .empty(CI 게이트 —.go무변경).{@html}/innerHTML 없음)./vms·favicon 404만).비목표
on:→onevent,createEventDispatcher→callback props, stores→$state모듈, snippet, vitest/컴포넌트 테스트, props/$:없는 컴포넌트 전환.참고
$bindable은 정적 게이트가 못 잡음(missed $bindable = 런타임 에러) — 2개 프롭(BuiltinPicker.selected/ModelPicker.value)은 정확 명시 + 부모 바인딩 4곳이$state로컬로 round-trip함을 리뷰로 검증.web/의 node_modules가 스테일(svelte4)일 수 있음 →npm ci필요; web은 CI 미빌드.$effect주석 문구(결론 정확, 이유 부정확), ProfileModal 3 doc-comment 그룹핑(cosmetic).SDD / lifecycle
spec
docs/superpowers/specs/2026-07-19-svelte5-runes-migration-design.md, plandocs/superpowers/plans/2026-07-19-svelte5-runes-migration.md. brainstorming→writing-plans→SDD. lifecycle: Full(대형 리팩터, behavior-preserving).Summary by CodeRabbit
New Features
Improvements
Documentation