feat(provider): persist per-view view-state identity and durable viewStates - #1546
Conversation
…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).
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
Review statusThanks for contributing. This comment tracks the review sequence and the next action. Current step: Mark the PR ready. Required CI must pass before CodeRabbit starts. Review-state labels are managed by this workflow; do not edit them manually. |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Part of the vps2 durable per-view state series — tracked in easonLiangWorldedtech#41 (cross-repo: this PR is standalone against upstream/main @ 0d937c0).
Issue (created at PR-open time): #1547
What
At the base commit
ClineProviderhas no per-view state identity: every view shares the same global mode / profile / apiConfiguration keys, nothing is persisted per view,postMessageToWebviewawaits an ack a remounted page never sends, and reset / history-restore writes leak across views. This PR lands the durable per-view core (fix unit F1a, 1/3): per-view identity, theviewStatespersistence pipeline, and the view-local state buffer. ThegetState()merging of hydrated per-view values and the webview-side identity / launch wiring land in the follow-ups (F1b / F1c).Design decisions
viewId= renderContext plus a monotonic counter (unique per instance for its lifetime).viewStateIdis the stable durable key (registered by the webview launch flow in F1c);rekeyPersistedViewStateEntrymoves the temporary-id entry to the stable id, stable id winning on a collision.savePersistedViewState) so concurrent provider instances merge without lost updates;viewStatesis pruned to the newest 50 entries (missingupdatedAtsorts oldest).setViewStateIdsanitizes ids and rejects__proto__: a per-view entry must never be keyable through theObject.prototypesetter. The fresh-read guard treats a corrupted non-object storage value as an empty map.viewLocalState):mode/currentApiConfigName/apiConfiguration(non-secret subset) live per view in memory.saveViewStateawaits the durable write before logging success.loadViewStatehydrates at registration, keeps the profile name and logs when the profile lookup fails, and discards a stale load when theviewStateIdchanges mid-lookup.setValues/setValuevalidatemodeagainstgetModeBySlug(unknown → log and ignore; non-string passes through) and keep or clear the matching buffer fields;undefined/nullvalues delete the buffer field rather than storing it.getValuesmerges context values with the buffer (buffer wins).postMessageToWebviewno longer awaits the webview ack (a remounted or disposed page never acknowledges; awaiting would wedge task-critical callers).resetStateclearsviewLocalStateand the view's persisted entry (after thecustomModesManager.resetCustomModesmodal confirm).saveViewState("mode", ...)instead of the shared global mode.Measurements
git diff --numstat 0d937c050..HEAD); under the 1000 hard cap. Composition: impl + types + adapted history-restore tests ≈ 424 a+d (ClineProvider.ts 391, sticky-mode spec 15, packages/types 16, suppressions 2); the remainder is the newview state persistence edge casesdescribe (17 focused tests) plus spec fixture adaptation.Gates
no-explicit-any198; prune-only reindent reverted)Parked / documented
From the gap-review parked-items register (F1a scope, all bounded):
viewStateIddivergence — inherent to the browser mock.getState()re-merge (lands F1b).Porting notes
All F1a content is re-implemented against the base by hand-porting hunks from CS
e9a44b2fa(#977 head): the durable core (getPersistedViewStatesfresh-read guard,savePersistedViewStatequeued merge + prune,clearPersistedViewState,prunePersistedViewStates,rekeyPersistedViewStateEntry,setViewStateId,loadViewState,saveViewState), the view-local buffer with thesetValue/setValues/getValuesmutation handlers, thepostMessageToWebviewvoid-ack, theresetStateclear, theviewStatesrecord inGLOBAL_STATE_KEYS+ types (global-settings.ts / vscode-extension-host.ts / index.test.ts), and the two history-restore tests adapted in ClineProvider.sticky-mode.spec.ts. The CS F1 spec (1790-lineparallelMode.spec.ts) is NOT ported as one file: the F1-series describes are rewritten into the existingClineProvider.spec.tsfixture (drops the 588-line mock preamble).setViewStateIdgains the 5-line__proto__rejection (A1 review hardening); the CSstryker-ignorecomment is dropped — the guard is covered by the mutation gate instead.defaultModeSlugimport — F3 re-adds it; providers/* + repo-config churn) — none ported here.