Skip to content

fix(core/webview): merge view-local state into getState for per-view overrides - #1550

Draft
easonLiangWorldedtech wants to merge 2 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:vps2/f1b-getstate-merge
Draft

fix(core/webview): merge view-local state into getState for per-view overrides#1550
easonLiangWorldedtech wants to merge 2 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:vps2/f1b-getstate-merge

Conversation

@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor

Part of the vps2 durable per-view state series — tracked in easonLiangWorldedtech#41 (cross-repo: standalone a+d measured against the stack base; the displayed vs-main diff includes F1a #1546 until it merges).

Issue (created at PR-open time): #1549

What

Fix unit F1b (2/3 of the F1 split). F1a landed the per-view identity, the durable viewStates pipeline, and the in-memory viewLocalState buffer, but getState() still read every field straight from the shared ContextProxy — so a webview reporting state ignored its own hydrated per-view selections. This PR merges viewLocalState on top of the context values in getState() and ports the getState merging + local state isolation spec coverage from the superseded vps2 source. It also pins the full default surface of the merged read path (mutation-diff gate). The webview-side identity / launch wiring (F1c) lands in the follow-up.

Design decisions

  • getState() builds mergedStateValues = { ...contextProxy.getValues(), ...viewLocalState } and serves every per-view-capable field off the merge: a view-local selection always beats the shared global value; unset fields fall back to the shared value with the existing defaults untouched.
  • apiConfiguration is the merged object { ...providerSettings, ...mergedStateValues.apiConfiguration } — the flat provider-settings mutation path (F1a) keeps repopulating the buffer field, so the re-merge at read time stays coherent (parked item 7, documented).
  • mode / modeApiConfigs read from the merge with the existing defaults (?? defaultModeSlug / ?? {}); no new validation at the read path — unknown modes are already dropped at write time by F1a's setValues / setValue validation.
  • The read path stays side-effect free: getState() never writes, so no queue / rekey behavior is introduced here.

Measurements

  • a+d vs stack base F1a head 0a8ffc9e1: 717 (622+/95−) — over the 400 soft budget (spec-heavy unit: 518 of the added lines are the new spec describes); under the 1000 hard cap. Measured git diff --numstat 0a8ffc9e1..HEAD.
  • src executable lines (mutation preflight): ClineProvider.ts 104+/95−, confined to the getState() region — under the 500-line cap; the spec file is test-only.

Gates

  • eslint --prune-suppressions: pass (suppression counts unchanged: ClineProvider.ts no-explicit-any 12; ClineProvider.spec.ts 198; prune-only reindent reverted)
  • check-types: pass
  • prettier: both files stable
  • vitest: ClineProvider.spec.ts 206 pass (185 pre-existing + 17 ported + 4 new default-value tests)
  • stryker-diff ci @ 0a8ffc9: pass — 127/127 mutants killed (0 Survived, 0 NoCoverage; under the 400-mutant and 500-executable-line caps)
  • e2e / i18n / visual: n/a (zero new i18n strings; no webview-ui changes)

Parked / documented

From the gap-review parked-items register (F1b scope, all bounded):

  1. Flat-mutation apiConfiguration replace (item 7) — a flat setValues provider-settings write replaces the buffer apiConfiguration object; coherent via the getState() re-merge introduced here.
  2. Editor-tab viewStates orphan after window reload (item 8) — no panel serializer; prune-bounded. No change in this unit.

Porting notes

  • Ported hunk-by-hunk (re-implemented against this base from the fix(webview): add durable per-view state base #977 source of record e9a44b2): ClineProvider.ts h17 (the mergedStateValues merge in getState()) + h18 (the full getState() return block: ~85 field reads switched from stateValues.* to mergedStateValues.*, the apiConfiguration object merge, the mode / modeApiConfigs defaults). The getState() method region verified identical to the source of record line-for-line (198 lines).
  • Spec port: local state isolation describe (2 tests) + getState merging describe (15 tests) from the CS ClineProvider.parallelMode.spec.ts, adapted to this file's fixture: the file-level getModeBySlug mock resolves every slug, so the unknown-mode test narrows the mock per-test (same try/finally pattern as F1a); that test's first assertion targets the empty proxy cache (toBeUndefined()) instead of the CS fixture's seeded global default.
  • Gate-driven addition (not in CS): getState default values describe (4 tests) — the mutation-diff gate requires every changed-code mutant killed, so the merged read path's ~45 default-fallback lines are pinned to their defaults (including the codebaseIndexModels fallback, which is only observable after clearing the value the constructor seeds into the context — with a truthy stored value the ?? and && forms of the line are indistinguishable), plus the apiProvider fill-in is exercised through a non-retired provider and through a value that ContextProxy sanitizes away (the only path where the ternary's retired check is observable in the returned apiConfiguration).
  • CS hunks intentionally NOT ported (register in the tracking issue, observed by this PR as well): all six register entries (kimi-code OAuth try/catch, ApiConfigManager className tweak, visual.tsx deletion + baselines, mojibake comment, unused defaultModeSlug import — lands with F1c/F3, repo-config churn).

…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).
@coderabbitai

coderabbitai Bot commented Sep 6, 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: 1575ec7c-b655-424c-9222-0a98c15bfe9b

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 6, 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.

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.59459% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/webview/ClineProvider.ts 94.59% 3 Missing and 7 partials ⚠️

📢 Thoughts on this report? Let us know!

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