Skip to content

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

Open
easonLiangWorldedtech wants to merge 23 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 23 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

Related GitHub Issue

Closes: #984

Description

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

How:

  • Per-view identity: each webview instance gets a stable ID (generated in webview-ui/src/utils/vscode.ts via getViewStateId(), sent during launch). ClineProvider.setViewStateId() sanitizes it into a safe object key.
  • viewLocalState buffer: transient per-view state that merges on top of the shared ContextProxy values in getState() (the mergedStateValues layer), so a tab's mode never overwrites the sidebar's.
  • Durable viewStates persistence: registered global setting key storing only non-secret selections (mode, currentApiConfigName, updatedAt), bounded to the most recent 50 entries by updatedAt ordering.
  • Serialized writes: every viewStates mutation goes through a static write queue (persistedViewStateWriteQueue) that re-reads the map fresh from globalState on each write, so concurrent sidebar/tab providers cannot clobber each other.
  • No-op compatibility: existing single-tab behavior is unchanged.

Reviewers should pay attention to:

  • Only non-secret fields are persisted (mode, currentApiConfigName). Full apiConfiguration (API keys, Kimi Code keys) is never written to globalState; e2e asserts no secret paths leak into persisted entries.
  • dispose() deliberately preserves the persisted entry — retention is handled by the 50-entry pruning cap, not deletion (tracked in follow-up Preserve durable editor view state across provider disposal #1065).
  • The PR also carries task-scoped API controls (approveTaskAsk, selectTaskFollowupSuggestion) and preserveOpenTabs for new tasks. These are required so parallel views can be driven per-task by the orchestrator e2e foundation (test(vscode-e2e): add orchestrator E2E foundation for parallel-mode coverage #1064), which is why they stay in this root PR rather than being split out.

Test Procedure

Unit / integration (all green in CI):

  • pnpm --dir src test — full core suite (129 files / 2184 passed / 9 skipped), including the new ClineProvider.parallelMode.spec.ts covering persistence, restoration, isolation, pruning, and concurrent-write serialization
  • pnpm --dir packages/types test (6 passed) and pnpm --dir webview-ui test (viewStateId generation/restoration)
  • Type checks: pnpm --dir src run check-types, plus packages/types, webview-ui, and apps/vscode-e2e
  • pnpm --dir src exec eslint --prune-suppressions --max-warnings=0 <touched files> — suppression counts unchanged

E2E (real VS Code extension host, mock API):

  • USE_MOCK=true TEST_FILE=view-state.test VSCODE_VERSION=1.100.0 pnpm --dir apps/vscode-e2e run test:run
    • Sidebar and tab tasks switch modes independently through the real ContextProxy singleton; both persisted viewStates entries are visible via api.getGlobalState("viewStates") and no secret keys appear in persisted entries
    • Three panels keep follow-up option mode switches isolated across ten staggered rounds

Manual verification:

  1. Open the sidebar in code mode and a new tab task in debug mode
  2. Reload the window — each panel restores its own mode
  3. Switch API profiles in one panel only — the other panel's currentApiConfigName is unaffected

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on durable per-view state (see the Description note on the task-scoped API controls carried for test(vscode-e2e): add orchestrator E2E foundation for parallel-mode coverage #1064).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (provider unit specs, webview-ui specs, e2e).
  • Visual Snapshot (UI changes only): Not applicable — no user-visible rendered state changes (state plumbing only).
  • Documentation Impact: No documentation updates required; viewStates is an internal registered setting.
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Visual Snapshots

Not applicable — no user-visible rendered state changes.

Videos (interaction / animation only)

Not applicable.

Summary by CodeRabbit

  • New Features

    • Added durable, per-webview state persistence via a new viewStates setting, including isolated mode and API profile selection by viewStateId.
    • Updated launch flow to hydrate state (including current API configuration) from the latest provider state.
    • Added task-specific API controls (approve asks, select follow-up suggestions) and preserveOpenTabs support for new tasks.
    • Added Kimi Code sign-in/sign-out and “abandon subtask by ID” messaging support.
  • Bug Fixes

    • Prevented secret-state keys (including API and Kimi Code keys) from being persisted; improved resilience when storage is unavailable.
  • Tests

    • Expanded provider, webview context, and end-to-end coverage for per-view isolation, 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. It also adds task-specific API controls, browser storage fallbacks, and parallel-view integration coverage.

Changes

Per-view state and task control

Layer / File(s) Summary
State contracts and public API
packages/types/src/*
Defines persisted viewStates, secret-key coverage, view-state launch fields, Kimi Code state, and task-control API methods.
Webview identity and launch wiring
webview-ui/src/utils/*, webview-ui/src/context/*, src/core/webview/webviewMessageHandler.ts
Generates or restores view identifiers, sends them during launch, applies them to providers, and reads selections from provider state.
Provider persistence and state merging
src/core/webview/ClineProvider.ts
Adds per-view overlays, persistence and restoration, pruning, mutation synchronization, profile handling, and view-local state precedence.
Task registry and API controls
src/extension/api.ts, src/extension/__tests__/*
Tracks active tasks, adds task-specific approval and follow-up actions, preserves open tabs when requested, and routes configuration through provider state.
Isolation and integration validation
src/core/webview/__tests__/*, apps/vscode-e2e/*, webview-ui/src/utils/__tests__/*
Tests persistence, restoration, isolation, mode switching, storage fallback, task controls, and multi-panel end-to-end behavior.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Webview
  participant ExtensionStateContext
  participant webviewMessageHandler
  participant ClineProvider
  participant globalState

  Webview->>ExtensionStateContext: obtain stable viewStateId
  ExtensionStateContext->>webviewMessageHandler: webviewDidLaunch with viewStateId
  webviewMessageHandler->>ClineProvider: setViewStateId(viewStateId)
  ClineProvider->>globalState: load or save viewStates
  ClineProvider-->>Webview: merged view-local state
Loading

Possibly related issues

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: navedmerchant, taltas, edelauna

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several added APIs and message-handling changes (e.g. Kimi Code and task controls) are outside the durable viewStates scope. Move unrelated task-control/Kimi Code feature work and supporting API changes to separate PRs, keeping this one focused on view state persistence.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation covers #984: registered viewStates, startup hydration, profile resolution, secret exclusion, and 50-entry pruning.
Title check ✅ Passed The title clearly identifies the main change: foundational durable per-view state infrastructure for the webview.
Description check ✅ Passed The description covers the linked issue, implementation, testing, checklist, and documentation impact; omitted optional contact details do not block review.
✨ 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.79592% with 25 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/webview/ClineProvider.ts 90.55% 12 Missing and 5 partials ⚠️
webview-ui/src/utils/vscode.ts 77.27% 2 Missing and 3 partials ⚠️
src/core/webview/webviewMessageHandler.ts 83.33% 0 Missing and 2 partials ⚠️
src/extension/api.ts 96.55% 1 Missing ⚠️

📢 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

@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.

Couple more comments - thanks for continuing to iterate on this.

Comment thread src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts Outdated
Comment thread src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
Comment thread webview-ui/src/utils/__tests__/vscode.spec.ts
Comment thread src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts
@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes has-conflicts PR has merge conflicts with the base branch 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 25, 2026
@easonLiangWorldedtech
easonLiangWorldedtech force-pushed the feat/view-local-state-base branch from 2ef16bb to 37a5dd1 Compare July 27, 2026 12:55
@github-actions github-actions Bot added has-conflicts PR has merge conflicts with the base branch awaiting-review PR changes are ready and waiting for maintainer re-review and removed awaiting-review PR changes are ready and waiting for maintainer re-review has-conflicts PR has merge conflicts with the base branch labels Aug 19, 2026
easonliang28 and others added 21 commits August 19, 2026 17:54
A just-resolved globalState write can momentarily lag a synchronous globalState.get in the extension host. The per-view writes are already awaited through the serialized view-state write queue before the tasks complete, so poll until both the sidebar and tab persisted selections are visible before asserting, instead of reading globalState once.
Address the CodeRabbit docstring coverage warning on the durable per-view state PR by documenting the new view-state persistence/merge helpers in ClineProvider, the task-scoped API controls in the extension API, and the viewStateId generation/restoration helpers in the webview wrapper.
Comment thread src/core/webview/ClineProvider.ts
Comment thread src/extension/api.ts Outdated
# Conflicts:
#	src/core/webview/ClineProvider.ts
#	src/core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts
#	src/core/webview/__tests__/webviewMessageHandler.spec.ts
#	src/core/webview/webviewMessageHandler.ts
…sted view-local secrets

- selectTaskFollowupSuggestion() now passes the registered task explicitly to handleModeSwitch(), so answering a follow-up on task B no longer switches the mode of the provider's currently focused task A (resolves review comment on src/extension/api.ts).

- getConfiguration() flattens the nested view-local apiConfiguration onto the top level before the isSecretStateKey() filter, so nested provider secrets (apiKey, openRouterApiKey, ...) cannot leak through the API — a regression introduced by the per-view state base's nested apiConfiguration shape.

- Consolidate the duplicate kimi-code oauth vi.mock in the routerModels spec and replace raw provider identifier literals flagged by the merged zoo/no-raw-provider-identifiers rule with providerIdentifiers.* constants.

Validated: 112 targeted vitest tests pass, pnpm --dir src run check-types clean, eslint --max-warnings=0 clean on all touched files.
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