Skip to content

fix(webview): add durable per-view state base#977

Open
easonLiangWorldedtech wants to merge 10 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:feat/view-local-state-base
Open

fix(webview): add durable per-view state base#977
easonLiangWorldedtech wants to merge 10 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:feat/view-local-state-base

Conversation

@easonLiangWorldedtech

@easonLiangWorldedtech easonLiangWorldedtech commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Add the foundational per-view state infrastructure for parallel mode. This is the root PR that all subsequent parallel-mode PRs depend on.

Addresses old Issue #908 (root mechanism). Fixes persistence limitation by introducing registered global viewStates with bounded cleanup and proper ContextProxy hydration.

Changes

  • Per-view identity: Assign stable IDs to each webview instance via webview_state_id
  • viewLocalState buffer: Transient per-view state that merges with global state via getState() layer
  • Durable viewStates persistence: Registered global setting key storing only non-secret selections (mode, currentApiConfigName, updatedAt) — bounded to most recent 50 entries
  • Stable webview state ID plumbing: Ensures persistence lookups are consistent across reloads
  • No-op compatibility: Existing single-tab behavior unchanged

Key Design Decisions

Durable per-view persistence via registered global viewStates

Previously, persistence relied on scattered dynamic __view_state_* keys that were not formally registered. Now:

Tests

  • Targeted tests passed
  • Full core run: 129 files passed / 2184 passed / 9 skipped
  • Types test: 6 passed

Related

Summary by CodeRabbit

  • New Features

    • Added durable, per-webview “view state” persistence via a new viewStates setting, including per-view isolation for mode and API config selection.
    • Introduced viewStateId so each webview instance hydrates the correct persisted state after launch/reload.
    • Updated launch handling to hydrate the current API configuration from the latest provider state.
    • Enabled production source map initialization/exposure on app mount.
  • Bug Fixes

    • Prevented secret keys (for example apiKey) from being persisted in durable per-view storage.
    • Improved resilience when browser storage APIs are unavailable.
  • Tests

    • Expanded provider and webview context tests to validate per-view persistence, pruning, concurrency, and launch-time behavior.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds durable per-view state schemas, identifiers, persistence, restoration, and state merging across webview and extension layers, with coverage for parallel instances, mode/API selections, pruning, launch handling, and local-state isolation.

Changes

Per-view state persistence

Layer / File(s) Summary
State contracts and keys
packages/types/src/global-settings.ts, packages/types/src/vscode-extension-host.ts, packages/types/src/__tests__/index.test.ts
Defines persisted viewStates entries and adds viewStateId to webview messages, with key and secret-state assertions.
Webview identity and launch wiring
webview-ui/src/utils/vscode.ts, webview-ui/src/context/ExtensionStateContext.tsx, webview-ui/src/App.tsx, src/core/webview/webviewMessageHandler.ts, related tests
Generates or restores webview IDs, sends them during launch, applies them to the provider, reads API configuration from provider state, and updates launch handling.
Provider persistence and state merging
src/core/webview/ClineProvider.ts
Adds per-view identifiers, persistence and restoration helpers, pruning, mutation synchronization, mode persistence, and view-local precedence in getState().
Provider persistence validation
src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts, src/core/webview/__tests__/ClineProvider.spec.ts, src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts
Tests instance identifiers, isolation, persistence, restoration, pruning, state merging, clearing, and updated mode-storage expectations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Webview
  participant ExtensionStateContext
  participant webviewMessageHandler
  participant ClineProvider
  participant globalState

  Webview->>ExtensionStateContext: initialize viewStateId
  ExtensionStateContext->>webviewMessageHandler: webviewDidLaunch with viewStateId
  webviewMessageHandler->>ClineProvider: setViewStateId(viewStateId)
  ClineProvider->>globalState: load and save viewStates
  ClineProvider-->>Webview: merged per-view state
Loading

Possibly related issues

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: taltas

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is missing required template sections, including Closes, Test Procedure, and checklist items. Fill in the repository template: add the linked issue number, test steps, checklist, screenshots/docs notes, and any additional context.
Out of Scope Changes check ⚠️ Warning App.tsx adds source-map debugging initialization, which is unrelated to durable per-view state persistence. Remove or justify the source-map/debugging changes unless they are required for the per-view state fix.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: durable per-view state support for webviews.
Linked Issues check ✅ Passed The changes implement registered viewStates, startup hydration, profile resolution, pruning, and secret-free persistence required by #984.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.26829% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/webview/ClineProvider.ts 90.39% 10 Missing and 7 partials ⚠️
webview-ui/src/utils/vscode.ts 77.27% 2 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@src/core/webview/ClineProvider.ts`:
- Around line 3005-3056: Update resetState(), activateProviderProfile(),
upsertProviderProfile(), and deleteProviderProfile() to clear or synchronize the
affected viewLocalState fields after mutating contextProxy. Reuse
_clearViewLocalState() for resetState() and _updateViewLocalStateFromMutation()
or equivalent targeted invalidation for profile changes, ensuring stale
currentApiConfigName and apiConfiguration values cannot mask the updated global
state.
🪄 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: 58c5e801-f818-415c-b0de-9f69e7498604

📥 Commits

Reviewing files that changed from the base of the PR and between f2bdcb6 and 605976b.

📒 Files selected for processing (13)
  • packages/types/src/__tests__/index.test.ts
  • packages/types/src/global-settings.ts
  • packages/types/src/vscode-extension-host.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • webview-ui/src/App.tsx
  • webview-ui/src/__tests__/App.spec.tsx
  • webview-ui/src/context/ExtensionStateContext.tsx
  • webview-ui/src/utils/vscode.ts
💤 Files with no reviewable changes (1)
  • webview-ui/src/App.tsx

Comment thread src/core/webview/ClineProvider.ts
@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 21, 2026

@edelauna edelauna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exciting to see this work come together! Had some implementation comments, and can we also add some ui testing:

We can leverage the UI testing setup established in McpServerRestriction.spec.tsx and webview-ui/src/utils/test-utils.tsx:

  • ExtensionStateContext.Provider Wrapper Pattern:
    Re-use the renderWithState pattern to mount webview components with specific viewStateId props and verify that UI components respond correctly to view-local mode and currentApiConfigName state without global bleed.

  • Reseed & Identity Tests:
    Similar to the slug-change reseed tests in McpServerRestriction.spec.tsx, add UI-level tests in ExtensionStateContext.spec.tsx or App.spec.tsx to verify that when viewStateId changes or a webview reloads, local React state reseeds properly from the new view's viewStateId payload.

  • vscode.getViewStateId & Messaging Spies:
    Ensure UI tests verify VSCodeAPIWrapper.getViewStateId() fallback behavior when sessionStorage / localStorage are restricted or cleared.

return viewStates
}

private async savePersistedViewState(values: Partial<PersistedViewState>): Promise<void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

savePersistedViewState reads the global viewStates dictionary from contextProxy, mutates states[this.viewStateId] in memory, and writes it back asynchronously via await contextProxy.setValue("viewStates", ...). When concurrent webview instances update mode or API profile selections simultaneously, one instance reads stale global state before the other's write completes, causing a lost update on viewStates.

@@ -2864,6 +3082,7 @@ export class ClineProvider
}

await this.contextProxy.resetAllState()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resetState() clears global settings in contextProxy but does not clear this.viewLocalState (e.g., via _clearViewLocalState()). When getState(viewStateId) runs, it merges stale viewLocalState overrides over the reset contextProxy defaults, causing pre-reset or deleted profile settings to persist in active webview instances.

Comment thread src/core/webview/ClineProvider.ts Outdated
* profile upsert/activation/deletion, or resetState. This ensures the local cache stays in
* sync with global state changes that would otherwise be invisible behind mergedStateValues.
*/
private _updateViewLocalStateFromMutation(values: Partial<RooCodeSettings>): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_updateViewLocalStateFromMutation updates in-memory viewLocalState in response to setValue/setValues calls, but never invokes savePersistedViewState. Mutations made via setValue/setValues are held only in memory and lost when the webview reloads or VS Code restarts.


describe("local state isolation", () => {
it("should isolate mode state between instances", async () => {
const provider1 = new ClineProvider(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test 'should isolate mode state between instances' reads the initial mode of two provider instances without mutating mode in either, making it incapable of verifying whether mode changes in one instance leak to other instances.

vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue("shared-profile")
vi.mocked(mockClineProvider.contextProxy.setValue).mockResolvedValue(undefined)
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The webviewDidLaunch handler test passes viewStateId: 'view-1' but omits an assertion verifying that provider.setViewStateId was called with 'view-1'.

@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-review PR changes are ready and waiting for maintainer re-review awaiting-author PR is waiting for the author to address requested changes labels Jul 23, 2026
@easonLiangWorldedtech
easonLiangWorldedtech force-pushed the feat/view-local-state-base branch from 82f9f23 to d724948 Compare July 23, 2026 20:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts (1)

1098-1195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding coverage for the two uncovered profile-mutation paths.

None of these tests exercise upsertProviderProfile(..., false) (non-activating save) or a deleteProviderProfile case where viewLocalState.currentApiConfigName diverges from the global value - both are the exact gaps flagged in src/core/webview/ClineProvider.ts (upsertProviderProfile/deleteProviderProfile). Adding cases here would catch regressions on those fixes.

🤖 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 `@src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts` around lines
1098 - 1195, The profile-mutation tests cover only activating upserts and
matching delete state; add coverage for the two missing branches. In the
“profile mutations” suite, add a test for upsertProviderProfile(..., false) that
verifies the saved profile does not activate or incorrectly synchronize current
state, and a deleteProviderProfile test where
viewLocalState.currentApiConfigName differs from the global ContextProxy value,
asserting the intended local-state behavior after deletion.
🤖 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.

Nitpick comments:
In `@src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts`:
- Around line 1098-1195: The profile-mutation tests cover only activating
upserts and matching delete state; add coverage for the two missing branches. In
the “profile mutations” suite, add a test for upsertProviderProfile(..., false)
that verifies the saved profile does not activate or incorrectly synchronize
current state, and a deleteProviderProfile test where
viewLocalState.currentApiConfigName differs from the global ContextProxy value,
asserting the intended local-state behavior after deletion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c33c4bc-cef3-4dc5-a6f1-07927265c1e5

📥 Commits

Reviewing files that changed from the base of the PR and between 6655bb1 and 82f9f23.

📒 Files selected for processing (6)
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
  • webview-ui/src/utils/__tests__/vscode.spec.ts
  • webview-ui/src/utils/vscode.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/webview/tests/webviewMessageHandler.spec.ts

@github-actions github-actions Bot added the awaiting-author PR is waiting for the author to address requested changes label Jul 24, 2026
@github-actions github-actions Bot added awaiting-review PR changes are ready and waiting for maintainer re-review and removed awaiting-author PR is waiting for the author to address requested changes labels Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Add durable per-view state persistence for parallel tabs

3 participants