Skip to content

fix(webview): isolate parallel mode and provider profile writes (vps2 F3) - #1560

Draft
easonLiangWorldedtech wants to merge 8 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:vps2/f3-mode-profile-writes
Draft

fix(webview): isolate parallel mode and provider profile writes (vps2 F3)#1560
easonLiangWorldedtech wants to merge 8 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:vps2/f3-mode-profile-writes

Conversation

@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor

Draft PR — vps2 unit F3 (mode/profile writes).

Tracking: easonLiangWorldedtech#41 (vps2 series ledger). Upstream issue: #1559 (this series' gap record; the original upstream bug is #978). Port source: the F1c-head-to-CS residual, cross-checked against upstream PR #979 (fix(webview): isolate parallel mode and provider profile writes) — closed/superseded by the vps2 series; the #41 ledger names #979 as the F3 port source.

Scope

10 files, 919 insertions, 65 deletions (measured vs stack base aa0f3b1):

  • src/core/webview/ClineProvider.ts (112/6) — the webview delete-path (deleteProviderProfile + repointPersistedViewStates), loadViewState dead-mode-pin validation, the handleModeSwitch profile-write isolation (per-view upsert/activate instead of the shared write), the as-any cast drop in delegateParentAndOpenChild, and a 3-line Stryker exclusion comment (next-line CallExpression,ObjectLiteral directive) above the no-op listApiConfigMeta view-state write (two equivalent mutants; see the Mutation gate trust note).
  • src/core/task/Task.ts (16/2) + Task.spec.ts (40/2) — the task-side per-view mode/profile write: mode switches route through provider.handleModeSwitch and the submitted message is kept on failure.
  • src/core/tools/SwitchModeTool.ts (7/4) + switchModeTool.spec.ts (22/14) — the tool-side per-view write port.
  • src/core/webview/webviewMessageHandler.spec.ts (5/0) — ported mock surface only: fix(webview): isolate parallel mode and provider profile writes #979's 4 mock fields (captureCustomModeCreated, captureModeSettingChanged, updateCustomMode, handleModeSwitch) plus the defaultModeSlug import. webviewMessageHandler.ts itself is 0/0 — see Port fidelity.
  • src/core/webview/tests/ClineProvider.sticky-mode.spec.ts (72/31) — the sticky-mode spec rewrites (concurrent-switch assertions re-targeted from the shared mode key to the per-view viewStates key).
  • src/core/webview/tests/ClineProvider.spec.ts (20/4) — exactly the two handleModeSwitch assertion-rewrite hunks (L3328, L3362 vs base).
  • src/core/webview/tests/ClineProvider.parallelMode.spec.ts (623/0) — 3 import lines + a 620-line append to the file created by F2: the CS F3 describes (310 lines, byte-identical to CS L1364-1673), 116 lines of F3-authored mutation-killing tests (corrupt-entry repoint skip, legacy-pin repoint, unknown-mode-slug no-op, released-provider switch tool path, ModeChanged event emission, empty-profile no-op), and a 194-line append of 5 further mutation-killing tests (no-task switch race, non-focused profile-load skip, upsert-without-activation list refresh, and the two deleteProviderProfile pin-behavior tests).
  • src/eslint-suppressions.json (2/2) — no-explicit-any counts decrease for core/webview/ClineProvider.ts (12 to 11) and core/webview/tests/ClineProvider.sticky-mode.spec.ts (36 to 33); no count increases.

Budget

  • a+d 984 vs the 400-soft / 1000-hard budget: soft-over, hard-under. The unit carries the production isolation logic (CP 112/6, Task 16/2, SwitchModeTool 7/4), three spec rewrites (sticky 72/31, CP.spec 20/4, switchModeTool.spec 22/14), and the 623-line parallelMode append. The 310 authored test lines (116 + 194) are gate-mandated: the Stryker diff gate cannot be satisfied by the CS-residual tests alone (several CP conditional/object-literal/string-literal mutants and one NoCoverage have no CS-side killer), and the skill rule is to kill by spec, never suppress. The only non-test production addition is the 3-line Stryker exclusion comment (equivalent mutants, documented in the Mutation gate trust note).
  • Stryker-diff gate (vs aa0f3b1, final head 8da5c6e): 63 mutants generated on the 78 changed executable lines — 61 killed, 2 ignored (the excluded equivalent pair at the listApiConfigMeta view-state write), 0 survived, 0 uncovered; gate exit 0.
  • vitest: 475/475 passed (138 suites, 16 spec files) — per file: ClineProvider.spec 206, Task.spec 101, ClineProvider.parallelMode 24, Task.persistence 24, ClineProvider.sticky-mode 19, Task.throttle 19, ClineProvider.taskHistory 18, ClineProvider.apiHandlerRebuild 15, ClineProvider.sticky-profile 15, ClineProvider.flicker-free-cancel 10, Task.pending-action 9, Task.dispose 7, ClineProvider.pending-action 4, ClineProvider.lockApiConfig 2, Task.resume-eviction-race 1, Task.sticky-profile-race 1.
  • check-types, eslint (--prune-suppressions, max-warnings 0; suppression counts flat-or-down), prettier (--end-of-line=auto): all pass — check-types exit 0; eslint exit 0 on the touched files (suppression file unchanged from the stack base: no-explicit-any counts down on the two listed files, flat-or-down invariant holds); prettier --end-of-line=auto clean.

Mutation gate trust note

  • Gate of record: the final sandbox-mode run at head 8da5c6e (vs stack base aa0f3b1; 78 changed executable lines; the 16-file direct test set): 63 mutants generated — 61 killed, 2 ignored, 0 survived, 0 uncovered; gate exit 0.
  • The 2 ignored mutants are the excluded pair at the createNewApiConfiguration else-branch call this._updateViewLocalStateFromMutation({ listApiConfigMeta }) at L2238: the CallExpression mutant (statement deleted) and the ObjectLiteral mutant (object emptied). Both are true equivalent mutants: the method (byte-identical to the CS method) applies only mode, currentApiConfigName, apiConfiguration and provider-settings keys, and a listApiConfigMeta-only payload matches none of them, so the deleted-statement and empty-object forms are behaviorally indistinguishable from the original. The exclusion is the narrowest mutator-specific directive the gate accepts (next-line, two named mutators, concrete reason committed at the call site); broad exclusions are rejected by the gate.
  • Four mutants that surfaced as Survived in earlier sandbox-mode runs on this machine — L2053 ConditionalExpression, L2072 ConditionalExpression, L2275 ConditionalExpression and L2283 ObjectLiteral — are all killed in the final run. The earlier survivors were an environment artifact (sandbox-mode per-mutant verification of ClineProvider.ts running against an un-mutated file copy in this worktree setup, while the Task.ts and SwitchModeTool.ts mutants verified and killed correctly in the same runs); the artifact was reproduced and isolated in a detached probe worktree. Killability of record is the plain-vitest true-form matrix over the committed 16-file suite (475 tests): warm baseline 475/475; L2053 true form 1 failure (the no-task switch-race test); L2072 true form 1 failure (the non-focused profile-load skip test); the L2234 string-literal form 1 failure (the upsert-without-activation list-refresh test); the L2238 excluded forms 0 failures (equivalent); L2275 true form 3 failures (the committed replacement-profile sync test plus both deleteProviderProfile pin-behavior tests); L2283 form 2 failures (both pin tests); no test fails under any mutant other than its designed killer(s).
  • In-place run (STRYKER_IN_PLACE=true, same selectors and test set): not run — the canonical sandbox-mode gate passed at the final head, so a separate in-place run adds no evidence.

Port fidelity (coordinator-verified against the vps2 CS e9a44b2)

  • The F3 residual (CS vs F1c head, per file) was ported hunk-by-hunk; no whole-file copies.
  • webviewMessageHandler.ts is 0/0 vs base. The residual's kimi-code OAuth try/catch hunk is CS-only divergence (it is absent from upstream fix(webview): isolate parallel mode and provider profile writes #979, whose WMH.ts carries only the two F1c-shipped hunks) — CS not-ported register item 1, deliberately not ported. webviewMessageHandler.spec.ts carries only fix(webview): isolate parallel mode and provider profile writes #979's mock-field additions (5/0); the stack-side legacy-repair test, the Key-aware getValue mock, and the em-dash comment are retained from the F2 base (register item 4). fix(webview): isolate parallel mode and provider profile writes #979's mode-routing WMH.spec describe ("routes mode messages through handleModeSwitch instead of writing ContextProxy directly") exists in neither the CS nor the stack and is ported by no unit of this series — logged as a parked open question, not a gap of this PR.
  • ClineProvider.ts setViewStateId proto guard: the residual removes the guard (CS lineage); this PR retains the guard exactly as shipped by F1a, so the guard region is byte-identical to the F2 head (no diff hunk there). Rationale: without the guard, states["proto"] invokes the Object.prototype setter and the durable write for that view is silently dropped; no F3 spec depends on the unguarded behavior. Cross-check: upstream fix(webview): isolate parallel mode and provider profile writes #979's setViewStateId body also has no guard, confirming the guard is stack-side (F1a) hardening, not fix(webview): isolate parallel mode and provider profile writes #979/CS content.
  • ClineProvider.spec.ts: exactly the two handleModeSwitch assertion-rewrite hunks ported (each re-targets the shared-key assertion to the per-view viewStates objectContaining form, with the view pinned via setViewStateId); the F2-head describe("setViewStateId") guard test block is retained byte-identical (invisible in the diff). The remaining CS residual on this file is the deliberate F1-series describe restructure — excluded, not F3 content.
  • ClineProvider.sticky-mode.spec.ts: all 4 CS hunks present at the F3 head (including the concurrent-switch filter rewrite from the shared mode key to the per-view viewStates key) — verified byte-identical to the CS residual by the coordinator.
  • ClineProvider.parallelMode.spec.ts: the 310-line CS F3 describe region (CS L1364-1673) is present byte-identical in order at the F3 head; the 116 authored lines are targeted mutation killers, added in 3 blocks inside the CS describes; CS L1674 (the blank separator before the F4 describes) is not ported — F4 appends its own block.
  • The defaultModeSlug import (CS register item 5) lands in this unit via the webviewMessageHandler.spec.ts mock surface (F1c had dropped it as unused).

Structural note on the parallelMode spec (coordinator-verified)

The CS parallelMode.spec.ts is 1790 lines: a shared preamble (L1-672), an F1-series test section (L673-1363: viewId uniqueness, local state isolation, saveViewState, stale temporary-id load), the F3 describes (L1364-1673), a blank separator (L1674), and the F4 multi-instance describes (L1675-1790). In this series the F1-series section lives in ClineProvider.spec.ts (shipped by F1a/F1b/F1c — the deliberate F1-series describe restructure), and the F2 unit shipped the persisted-pruning and #1065 retention tests inside the parallelMode file (they are absent from the CS parallelMode file). Both placements are behaviorally covered; the divergence is structural, not a coverage gap.

Series mechanics

  • Base of record: upstream/main @ 0d937c0; PR base is main; the branch is stacked on the F2 head aa0f3b1 (the F1-series and F2 heads are merged below it in the series merge order).
  • Draft PR per unit; merge order F1a to F1b to F1c to F2 to F3 to F4 to F5 to F6 to F7.
  • CS not-ported register (for consistency): (1) kimi-code OAuth try/catch + routerModels.spec.ts +29 — also the reason WMH.ts is 0/0 here; (2) ApiConfigManager.tsx min-w-0 shrink to grow; (3) ApiConfigManager.visual.tsx deletion + 2 PNG baselines; (4) mojibake comment; (5) unused defaultModeSlug import — resolved by this unit; (6) providers/, .coderabbit.yaml, .github/, CONTRIBUTING.md, .gitignore churn.
  • Merge check against upstream main 4c7474d (merge-base = base of record 0d937c0), via git merge-tree on the full F0-to-F3 stack: nine files auto-merge, including the four files changed on both sides (Task.ts, Task.spec.ts, ClineProvider.ts, ClineProvider.spec.ts). The single content conflict is src/eslint-suppressions.json: upstream main independently decreased six no-explicit-any counts (including core/webview/ClineProvider.ts 12 to 8 and core/task/Task.ts 19 to 17) while this PR decreases two (core/webview/ClineProvider.ts 12 to 11 and the sticky-mode spec 36 to 33, vs stack base). The one overlapping entry is core/webview/ClineProvider.ts; resolution is mechanical: regenerate that entry from the post-merge file with eslint --prune-suppressions (per the AGENTS.md invariant the count can never increase; the post-merge count is flat-or-down vs upstream 8). Every other file entry touched by either side is disjoint.

@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • coderabbit-review-active

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: a3c8f55e-8859-4a9d-95c7-c5c904b79df2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

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

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Review status

Thanks for contributing. This comment tracks the review sequence and the next action.

Current step: Fix the failing required CI checks; awaiting-maintainer requires CI and automated review completion.

Review-state labels are managed by this workflow; do not edit them manually.

…States

Each ClineProvider instance now owns a unique viewId (renderContext plus a
monotonic counter) and registers a stable viewStateId for durable persistence.

- Per-view state buffer (viewLocalState) holds mode / currentApiConfigName /
  apiConfiguration overrides in memory; saveViewState persists the non-secret
  subset durably under the active view id, rekeyed to the stable id on
  registration.
- viewStates is stored as a map pruned to the newest 50 entries; writes go
  through a serialized queue so concurrent provider instances merge without
  lost updates.
- setViewStateId sanitizes ids and rejects "__proto__" so a per-view entry can
  never be keyed through the Object.prototype setter.
- postMessageToWebview no longer awaits the webview ack: a remounted or
  disposed page never acknowledges, and awaiting would wedge task-critical
  callers.
- History restore falls back to the default mode view-locally instead of
  writing the shared global mode.
- GlobalState gains the "viewStates" key and GLOBAL_STATE_KEYS tracks it.

Adds F1a coverage in ClineProvider.spec.ts (viewId uniqueness, saveViewState
persistence semantics, loadViewState fallback and failure, pruning, the
__proto__ guard) and adapts the two history-restore tests in
ClineProvider.sticky-mode.spec.ts to the view-local restore. getState()
merging of hydrated per-view values and the remaining view-state suites land
in the follow-up (F1b).
…overrides

Fold ClineProvider viewLocalState on top of ContextProxy values in getState() (mode, apiConfiguration, and all per-view fields) so each webview reports its own selections while falling back to shared global state for everything else. Ports the getState-merging and local-state-isolation spec coverage from the superseded vps2 source.

Also pins the full default surface of the merged read path, including the apiConfiguration provider fill-in when provider settings sanitize the raw value away (mutation-diff gate).
… state

WMH webviewDidLaunch persists the webview view-state id via provider.setViewStateId and re-pins the view-local currentApiConfigName through provider.saveViewState when the view-local profile is missing but the shared global selection is still valid. updateSettings is routed through provider.setValue so view-local buffer and pin sync stay consistent with the other mutation paths. The webview VSCodeAPIWrapper gains a stable getViewStateId persisted via setState (with an in-memory fallback) and the launch effect posts the id with the webviewDidLaunch message.
Port vps2 F3 (mode/profile writes) from upstream 978, hunk-by-hunk against
the F1c..CS residual:

- ClineProvider: add repointPersistedViewStates() to re-point
  currentApiConfigName across per-view entries when a profile is renamed
  or replaced, and prune orphaned entries; validate handleModeSwitch slugs
  against the custom-modes manager and no-op with a log on unknown modes;
  drop the as-any cast in delegateParentAndOpenChild.
- Task: route mode switches through provider.handleModeSwitch(task) and
  keep the submitted message on failure instead of setMode().
- SwitchModeTool and specs: durable per-view mode writes.
- webviewMessageHandler: no change vs base - the kimi-code OAuth hunk in
  the residual is CS-only divergence (not-ported register item 1), not
  part of Zoo-Code-Org#978/Zoo-Code-Org#979.
- webviewMessageHandler.spec: ported only Zoo-Code-Org#979's 4 mock fields +
  defaultModeSlug import; the stack-side legacy-repair test, Key-aware
  getValue mock and em-dash comment are retained (register item 4).
  Zoo-Code-Org#979's mode-routing WMH.spec describe ("routes mode messages through
  handleModeSwitch instead of writing ContextProxy directly") exists in
  neither CS nor the stack and is ported by no unit (open question,
  logged).
- Tests: H3/H4 durable handleModeSwitch writes in ClineProvider.spec.ts;
  profile-mutation, profile-activation and handleModeSwitch-integration
  describes (incl. A4 non-focused-target regression and new
  mutation-killing tests) in ClineProvider.parallelMode.spec.ts;
  sticky-mode and webviewMessageHandler spec updates; retain the
  setViewStateId __proto__ guard + spec test - shipped F1a hardening;
  the residual's guard removal is lineage divergence, not F3 content.
- eslint-suppressions.json: no-explicit-any counts decrease for
  core/webview/ClineProvider.ts (12 -> 11) and
  core/webview/__tests__/ClineProvider.sticky-mode.spec.ts (36 -> 33).

Upstream: Zoo-Code-Org#978 (vps2 F3) - issue Zoo-Code-Org#978; content ported
hunk-by-hunk from the F1c..CS residual, cross-checked against upstream PR
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.83951% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/webview/ClineProvider.ts 98.33% 0 Missing and 4 partials ⚠️
webview-ui/src/utils/vscode.ts 86.36% 1 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot removed the has-conflicts PR has merge conflicts with the base branch label Sep 7, 2026
…/180s under runner load, 13 failing / 72 passing)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants