Skip to content

fix(webview): persistence logic + API sync (branch 2/3) - #1139

Draft
easonLiangWorldedtech wants to merge 13 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:feat/view-local-persistence-and-api
Draft

fix(webview): persistence logic + API sync (branch 2/3)#1139
easonLiangWorldedtech wants to merge 13 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:feat/view-local-persistence-and-api

Conversation

@easonLiangWorldedtech

@easonLiangWorldedtech easonLiangWorldedtech commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Persistence logic, API integration, and refactoring built on Branch 1. This is Branch 2 of 3 split from PR #977 for easier review.

Changes

  • test(webview): restore parallel mode coverage (+165)
  • fix(webview): sync after profile mutations (+133 / -3)
  • fix(webview): persist view-local safely (+245 / -38)
  • fix(provider): sync when activating profile (+35)
  • fix(api): sync setConfiguration (+52 / -1)
  • test: assert stale modelId absent (+1)
  • fix(webview): preserve isolated writes (+99 / -16)
  • chore(lint): update suppression baseline (+12 / -2)
  • refactor: consolidate persistence (+35 / -21)

~740 lines changed (incremental)

Related

Summary by CodeRabbit

  • New Features

    • Added isolated, persistent state for each webview instance, including mode and API configuration selections.
    • Preserved view identity across webview reloads and supported multiple independent views.
    • Improved state recovery when browser storage is unavailable.
  • Bug Fixes

    • Corrected configuration updates so changes are applied and reflected consistently in the active view.
  • Tests

    • Expanded coverage for state persistence, synchronization, cleanup, multi-view behavior, and configuration updates.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds stable view-state identifiers and durable per-view state for webview instances. ClineProvider persists and hydrates local mode and API profile settings, while launch messaging, configuration updates, and tests use the isolated state.

Changes

Per-view state isolation

Layer / File(s) Summary
View identity and launch handshake
packages/types/src/..., webview-ui/src/context/..., webview-ui/src/utils/..., src/core/webview/webviewMessageHandler.ts
Adds the viewStates schema, stable viewStateId generation, launch-message propagation, and launch-time persistence.
Provider persistence and state merging
src/core/webview/ClineProvider.ts, src/core/webview/__tests__/ClineProvider*.spec.ts, src/eslint-suppressions.json
Adds bounded durable view-state storage, serialized writes, hydration, cleanup, profile synchronization, mode persistence, and merged state resolution.
State consumers and configuration updates
src/extension/api.ts, src/extension/__tests__/api-set-configuration.spec.ts, webview-ui/src/context/..., src/core/webview/webviewMessageHandler.ts
Routes configuration updates through ClineProvider.setValues and reseeds webview-local mode and API configuration from incoming state.

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

Possibly related issues

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: taltas, navedmerchant, hannesrudolph

Sequence Diagram(s)

sequenceDiagram
  participant ExtensionStateContext
  participant VSCodeAPIWrapper
  participant webviewMessageHandler
  participant ClineProvider
  participant GlobalState

  ExtensionStateContext->>VSCodeAPIWrapper: Get stable viewStateId
  VSCodeAPIWrapper-->>ExtensionStateContext: Return viewStateId
  ExtensionStateContext->>webviewMessageHandler: Send webviewDidLaunch(viewStateId)
  webviewMessageHandler->>ClineProvider: Store viewStateId
  ClineProvider->>GlobalState: Persist viewStates
  ClineProvider-->>webviewMessageHandler: Return merged state
  webviewMessageHandler-->>ExtensionStateContext: Post view-local mode and API profile
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description summarizes the changes but omits the required test procedure, checklist, documentation section, and additional reviewer context. Add the required template sections, including reproducible test steps, completed checklist items, documentation impact, and any relevant reviewer notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main changes: webview persistence logic and API synchronization.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/types/src/__tests__/index.test.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

packages/types/src/global-settings.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

packages/types/src/vscode-extension-host.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 13 others

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.

@easonLiangWorldedtech
easonLiangWorldedtech marked this pull request as draft August 4, 2026 16:18

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/webview/ClineProvider.ts (1)

3090-3113: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep direct ContextProxy mutations in sync with viewLocalState.

getValue() overlays viewLocalState, but writes through provider.contextProxy.setValue, setValues, and setProviderSettings bypass ClineProvider.setValue/setValues and do not call _saveViewLocalStateFromMutation. This happens in the settings import flow, provider-profile creation/update, profile activation, profile deletion, and the Settings updateSettings handler. Route these writes through provider.setValue/setValues, or invalidate the affected viewLocalState keys from ContextProxy.

🤖 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/ClineProvider.ts` around lines 3090 - 3113, Update the
direct ContextProxy write paths in the settings import flow, provider-profile
creation/update, profile activation/deletion, and Settings updateSettings
handler to use ClineProvider.setValue or setValues so
_saveViewLocalStateFromMutation keeps viewLocalState synchronized. Locate these
callers via contextProxy.setValue, contextProxy.setValues, and
setProviderSettings, while preserving existing mutation values and behavior.
🧹 Nitpick comments (7)
src/core/webview/ClineProvider.ts (3)

565-573: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type saveViewState generically instead of using any.

value: any and the as chain at lines 569-570 add to the @typescript-eslint/no-explicit-any count for this file, which rose from 12 to 16 in src/eslint-suppressions.json. A generic key parameter removes both.

♻️ Proposed change
-	private async saveViewState(key: keyof ExtensionState, value: any): Promise<void> {
-		await this._saveViewLocalStateFromMutation({ [key]: value } as Partial<RooCodeSettings> &
-			Partial<ExtensionState>)
+	private async saveViewState<K extends keyof ExtensionState>(key: K, value: ExtensionState[K]): Promise<void> {
+		await this._saveViewLocalStateFromMutation({ [key]: value })

The coding guidelines state: "Suppression counts in src/eslint-suppressions.json must never increase; when touching a file, reduce its count when the fix is local and low-risk."

🤖 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/ClineProvider.ts` around lines 565 - 573, Update
saveViewState to use a generic key parameter constrained to keyof ExtensionState
and derive the value type from that key, so callers receive key-specific typing.
Replace the current any value and assertion chain passed to
_saveViewLocalStateFromMutation with a type-safe mapped object compatible with
its expected partial state types, without increasing eslint suppressions.

Source: Coding guidelines


3146-3162: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Build the provider-settings delta without a spread inside reduce.

The accumulator is re-allocated for every matching key, which makes the reduction quadratic in the number of PROVIDER_SETTINGS_KEYS. This runs on every setValue and setValues call. Mutate a single accumulator instead.

♻️ Proposed change
-			const providerSettingsUpdate = PROVIDER_SETTINGS_KEYS.reduce((acc, key) => {
-				if (key in values) {
-					return { ...acc, [key]: values[key as keyof RooCodeSettings] }
-				}
-
-				return acc
-			}, {} as ProviderSettings)
+			const providerSettingsUpdate: ProviderSettings = {}
+
+			for (const key of PROVIDER_SETTINGS_KEYS) {
+				if (key in values) {
+					;(providerSettingsUpdate as Record<string, unknown>)[key] = values[key as keyof RooCodeSettings]
+				}
+			}
🤖 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/ClineProvider.ts` around lines 3146 - 3162, Update the
providerSettingsUpdate construction in the setValue/setValues handling branch to
mutate one accumulator when a key exists in values, instead of returning a new
object with a spread from reduce. Preserve the existing ProviderSettings result
and subsequent apiConfiguration merge behavior.

1257-1258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Route the restored mode through the view-local persistence helper.

Line 1258 writes this.viewLocalState.mode directly. Every other mode mutation in this class now goes through saveViewState or _saveViewLocalStateFromMutation, which also persist the viewStates entry. Today the value survives a reload only because line 1257 still writes the shared global mode. Use the helper so the two paths stay consistent.

♻️ Proposed change
 			await this.updateGlobalState("mode", historyItem.mode)
-			this.viewLocalState.mode = historyItem.mode
+			await this.saveViewState("mode", historyItem.mode)
🤖 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/ClineProvider.ts` around lines 1257 - 1258, Update the
history restoration flow around the mode assignment in ClineProvider so restored
modes use the class’s view-local persistence helper, such as saveViewState or
_saveViewLocalStateFromMutation, instead of directly assigning
this.viewLocalState.mode. Preserve the existing global-state update while
ensuring the corresponding viewStates entry is persisted consistently.
src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts (1)

872-881: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that a viewStates write happened before inspecting the last call.

If no viewStates write occurs, lastViewStateCall is undefined and the optional chaining turns the failure into expect(undefined).toMatchObject(...). That hides the actual cause. Add an explicit check first.

💚 Proposed change
 			const lastViewStateCall = viewStateCalls[viewStateCalls.length - 1]
 
+			expect(viewStateCalls.length).toBeGreaterThan(0)
+
 			// Verify the last mode switch wins
 			expect(lastViewStateCall?.[1]).toMatchObject({
 				[provider.viewId]: { mode: "code" },
 			})
🤖 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.sticky-mode.spec.ts` around lines
872 - 881, In the sticky-mode test, add an explicit assertion after collecting
viewStatesCalls and before reading lastViewStateCall to verify at least one
write occurred. Keep the existing last-call inspection and mode assertion
unchanged once the non-empty condition is established.
webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx (1)

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

Remove the unnecessary as any cast.

Mode is declared as export type Mode = string in src/shared/modes.ts, so setMode("ask") type-checks without a cast. The coding guidelines ask you to avoid as any and to document any unavoidable cast.

♻️ Proposed change
-			<button data-testid="set-local-mode" onClick={() => setMode("ask" as any)}>
+			<button data-testid="set-local-mode" onClick={() => setMode("ask")}>
🤖 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 `@webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx` at line 89,
Remove the unnecessary as any cast from the setMode call in the set-local-mode
test button, passing "ask" directly because it satisfies the Mode type.

Source: Coding guidelines

src/core/webview/__tests__/webviewMessageHandler.spec.ts (1)

234-239: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the single-tick flush with vi.waitFor.

webviewMessageHandler does not await the providerSettingsManager.listConfig().then(...) chain, so hasConfig runs on a later microtask/macrotask. A single setImmediate tick happens to be enough today, but the assertion breaks if any additional await is added inside that chain. Use vi.waitFor so the test waits for the observable effect.

♻️ Proposed change
 		await webviewMessageHandler(mockClineProvider, { type: "webviewDidLaunch", viewStateId: "view-1" })
-		await new Promise((resolve) => setImmediate(resolve))
-
 		expect((mockClineProvider as any).setViewStateId).toHaveBeenCalledWith("view-1")
-		expect((mockClineProvider as any).providerSettingsManager.hasConfig).toHaveBeenCalledWith("view-local-profile")
+		await vi.waitFor(() => {
+			expect((mockClineProvider as any).providerSettingsManager.hasConfig).toHaveBeenCalledWith(
+				"view-local-profile",
+			)
+		})
 		expect((mockClineProvider as any).providerSettingsManager.hasConfig).not.toHaveBeenCalledWith("shared-profile")
🤖 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__/webviewMessageHandler.spec.ts` around lines 234 -
239, In the webviewDidLaunch test around webviewMessageHandler, replace the
single setImmediate-based flush with vi.waitFor that waits until
providerSettingsManager.hasConfig has been called with "view-local-profile".
Keep the existing assertions for setViewStateId and rejection of
"shared-profile", ensuring assertions run only after the observable asynchronous
effect completes.
src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts (1)

694-695: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Introduce one typed accessor for the private members instead of repeating (provider as any).

This file adds 143 @typescript-eslint/no-explicit-any suppressions in src/eslint-suppressions.json, and most come from (provider as any) reads of private members such as saveViewState, setViewStateId, loadViewState, viewLocalState, prunePersistedViewStates, and _clearViewLocalState. The test at lines 849-853 already shows the pattern that avoids the cast. Declare that shape once and reuse it, or use bracket notation for the private members.

♻️ Proposed helper
type ProviderInternals = {
	saveViewState: (key: keyof ExtensionState, value: unknown) => Promise<void>
	setViewStateId: (viewStateId: string) => Promise<void>
	loadViewState: () => Promise<void>
	prunePersistedViewStates: (states: Record<string, unknown>) => Record<string, unknown>
	_clearViewLocalState: () => void
	viewLocalState: Partial<ExtensionState>
}

const internals = (provider: ClineProvider) => provider as unknown as ProviderInternals

The coding guidelines state: "Avoid as any; use typed APIs, bracket notation for private members where appropriate, or precise test doubles and unknown with type guards."

Also applies to: 717-718, 1002-1002

🤖 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
694 - 695, Replace repeated `(provider as any)` accesses in the parallel-mode
tests with one typed `ProviderInternals` accessor, based on the existing pattern
near the later test. Reuse it for private members including `saveViewState`,
`setViewStateId`, `loadViewState`, `viewLocalState`, `prunePersistedViewStates`,
and `_clearViewLocalState`, using `unknown` rather than `any` and removing the
corresponding eslint suppressions.

Source: Coding guidelines

🤖 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/__tests__/webviewMessageHandler.spec.ts`:
- Around line 217-230: Replace the per-property `as any` assignments in the test
setup with typed members declared on the `mockClineProvider` object literal,
including `setViewStateId`, `workspaceTracker`, `providerSettingsManager`,
`activateProviderProfile`, `getMcpHub`, and `getStateToPostToWebview`. In
`beforeEach`, reset these members through `vi.mocked(...)`; retain the existing
`as unknown as ClineProvider` cast on the full literal and remove the avoidable
explicit-any usage.

In `@src/core/webview/ClineProvider.ts`:
- Around line 1899-1903: Update deleteProviderProfile to call
_saveViewLocalStateFromMutation instead of only
_updateViewLocalStateFromMutation when replacing the deleted profile, so the new
currentApiConfigName and listApiConfigMeta are persisted in viewStates. Preserve
the existing mutation values and behavior for the in-memory cache.
- Around line 532-563: Update loadViewState to merge hydrated fields into the
existing this.viewLocalState rather than replacing the buffer, preserving live
per-view selections and only assigning values that were actually persisted and
resolved. Ensure both constructor-triggered hydration and setViewStateId
hydration cannot overwrite mutations made while getProfile is pending; if
replacement semantics remain necessary for setViewStateId, explicitly sequence
the constructor’s loadViewState promise before getState.
- Around line 517-526: Update setViewStateId to clean up the constructor-default
viewId entry when switching to the webview-reported ID, while preserving any
useful persisted values by migrating them if required before loading the new
state. Ensure cleanup only targets the stale ephemeral entry and does not remove
the newly selected viewStateId data.
- Around line 3215-3219: Update resetState() to clear view-local state for every
open ClineProvider in activeInstances, not only the current instance. Iterate
over activeInstances and invoke each provider’s _clearViewLocalState() after
resetting shared state, preserving the existing current-instance behavior
without leaving per-view mode or API profile overrides cached.

In `@src/eslint-suppressions.json`:
- Around line 1062-1076: Replace the any-typed saveViewState signature in
src/core/webview/ClineProvider.ts:565-573 with a generic key/value relationship
using ExtensionState[K], and remove the Partial<RooCodeSettings> &
Partial<ExtensionState> cast. In
src/core/webview/__tests__/webviewMessageHandler.spec.ts:217-230, declare the
listed provider members directly on mockClineProvider instead of assigning
through an any cast. After both fixes, update
src/eslint-suppressions.json:1062-1076 to restore ClineProvider.ts to 12
suppressions and webviewMessageHandler.spec.ts to 35; the parallelMode entry
requires no direct change.

In `@webview-ui/src/utils/__tests__/vscode.spec.ts`:
- Line 21: Replace the double assertions at the storage mock definitions in the
vscode tests with a narrow type such as Pick<Storage, "getItem" | "setItem" |
"removeItem" | "clear"> or an equivalent local mock interface, and update both
affected mocks to use it without casting to Storage.

---

Outside diff comments:
In `@src/core/webview/ClineProvider.ts`:
- Around line 3090-3113: Update the direct ContextProxy write paths in the
settings import flow, provider-profile creation/update, profile
activation/deletion, and Settings updateSettings handler to use
ClineProvider.setValue or setValues so _saveViewLocalStateFromMutation keeps
viewLocalState synchronized. Locate these callers via contextProxy.setValue,
contextProxy.setValues, and setProviderSettings, while preserving existing
mutation values and behavior.

---

Nitpick comments:
In `@src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts`:
- Around line 694-695: Replace repeated `(provider as any)` accesses in the
parallel-mode tests with one typed `ProviderInternals` accessor, based on the
existing pattern near the later test. Reuse it for private members including
`saveViewState`, `setViewStateId`, `loadViewState`, `viewLocalState`,
`prunePersistedViewStates`, and `_clearViewLocalState`, using `unknown` rather
than `any` and removing the corresponding eslint suppressions.

In `@src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts`:
- Around line 872-881: In the sticky-mode test, add an explicit assertion after
collecting viewStatesCalls and before reading lastViewStateCall to verify at
least one write occurred. Keep the existing last-call inspection and mode
assertion unchanged once the non-empty condition is established.

In `@src/core/webview/__tests__/webviewMessageHandler.spec.ts`:
- Around line 234-239: In the webviewDidLaunch test around
webviewMessageHandler, replace the single setImmediate-based flush with
vi.waitFor that waits until providerSettingsManager.hasConfig has been called
with "view-local-profile". Keep the existing assertions for setViewStateId and
rejection of "shared-profile", ensuring assertions run only after the observable
asynchronous effect completes.

In `@src/core/webview/ClineProvider.ts`:
- Around line 565-573: Update saveViewState to use a generic key parameter
constrained to keyof ExtensionState and derive the value type from that key, so
callers receive key-specific typing. Replace the current any value and assertion
chain passed to _saveViewLocalStateFromMutation with a type-safe mapped object
compatible with its expected partial state types, without increasing eslint
suppressions.
- Around line 3146-3162: Update the providerSettingsUpdate construction in the
setValue/setValues handling branch to mutate one accumulator when a key exists
in values, instead of returning a new object with a spread from reduce. Preserve
the existing ProviderSettings result and subsequent apiConfiguration merge
behavior.
- Around line 1257-1258: Update the history restoration flow around the mode
assignment in ClineProvider so restored modes use the class’s view-local
persistence helper, such as saveViewState or _saveViewLocalStateFromMutation,
instead of directly assigning this.viewLocalState.mode. Preserve the existing
global-state update while ensuring the corresponding viewStates entry is
persisted consistently.

In `@webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx`:
- Line 89: Remove the unnecessary as any cast from the setMode call in the
set-local-mode test button, passing "ask" directly because it satisfies the Mode
type.
🪄 Autofix

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: 14442d3b-62d6-44d7-85a0-264c45a0d1d0

📥 Commits

Reviewing files that changed from the base of the PR and between 7918f6b and 7d07e53.

📒 Files selected for processing (18)
  • 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
  • src/eslint-suppressions.json
  • src/extension/__tests__/api-set-configuration.spec.ts
  • src/extension/api.ts
  • webview-ui/src/App.tsx
  • webview-ui/src/__tests__/App.spec.tsx
  • webview-ui/src/context/ExtensionStateContext.tsx
  • webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
  • webview-ui/src/utils/__tests__/vscode.spec.ts
  • webview-ui/src/utils/vscode.ts
💤 Files with no reviewable changes (1)
  • webview-ui/src/App.tsx

Comment on lines +217 to +230
;(mockClineProvider as any).setViewStateId = vi.fn().mockResolvedValue(undefined)
;(mockClineProvider as any).workspaceTracker = { initializeFilePaths: vi.fn() }
;(mockClineProvider as any).providerSettingsManager = {
listConfig: vi.fn().mockResolvedValue([{ name: "shared-profile", apiProvider: "anthropic" }]),
hasConfig: vi.fn().mockResolvedValue(false),
}
;(mockClineProvider as any).activateProviderProfile = vi.fn().mockResolvedValue(undefined)
;(mockClineProvider as any).getMcpHub = vi.fn().mockReturnValue(undefined)
;(mockClineProvider as any).getStateToPostToWebview = vi
.fn()
.mockResolvedValue({ telemetrySetting: "disabled" })
vi.mocked(mockClineProvider.customModesManager.getCustomModes).mockResolvedValue([])
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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Declare the new launch dependencies on the mockClineProvider literal instead of casting.

Lines 217-227 attach setViewStateId, workspaceTracker, providerSettingsManager, activateProviderProfile, getMcpHub, and getStateToPostToWebview through as any. Those casts are part of the reason the @typescript-eslint/no-explicit-any count for this file rose from 35 to 45 in src/eslint-suppressions.json. Add the members to the mockClineProvider object literal at lines 91-119 and reset them in beforeEach with vi.mocked(...). The literal is already narrowed with as unknown as ClineProvider, so no per-property cast is needed.

The coding guidelines state: "Avoid as any" and "Suppression counts in src/eslint-suppressions.json must never increase; when touching a file, reduce its count when the fix is local and low-risk."

🤖 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__/webviewMessageHandler.spec.ts` around lines 217 -
230, Replace the per-property `as any` assignments in the test setup with typed
members declared on the `mockClineProvider` object literal, including
`setViewStateId`, `workspaceTracker`, `providerSettingsManager`,
`activateProviderProfile`, `getMcpHub`, and `getStateToPostToWebview`. In
`beforeEach`, reset these members through `vi.mocked(...)`; retain the existing
`as unknown as ClineProvider` cast on the full literal and remove the avoidable
explicit-any usage.

Source: Coding guidelines

Comment on lines +517 to +526
public async setViewStateId(viewStateId: string | undefined): Promise<void> {
const normalizedViewStateId = viewStateId?.trim()

if (!normalizedViewStateId || normalizedViewStateId === this.viewStateId) {
return
}

this.viewStateId = normalizedViewStateId.replace(/[^A-Za-z0-9_-]/g, "_")
await this.loadViewState()
}

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find provider mutations that can run before webviewDidLaunch and therefore persist under the ephemeral viewId.
set -euo pipefail

# Locate setViewStateId and the persistence helpers.
ast-grep outline src/core/webview/ClineProvider.ts --items all --match 'setViewStateId|loadViewState|savePersistedViewState|clearPersistedViewState|resolveWebviewView'

# Find call sites that mutate provider state and could precede the launch handshake.
rg -nP -C4 '\b(setViewStateId|resolveWebviewView)\s*\(' --type=ts src
rg -nP -C3 'provider\.(setValue|setValues|activateProviderProfile|upsertProviderProfile|handleModeSwitch)\s*\(' --type=ts src -g '!**/__tests__/**'

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ClineProvider outline matching public/mutating symbols =="
ast-grep outline src/core/webview/ClineProvider.ts --view expanded | rg -n 'public|private|constructor|viewStateId|saveViewState|loadViewState|clearPersistedViewState|resolveWebviewView|getValue|setValue|setValues|handleModeSwitch|activateProviderProfile|upsertProviderProfile' | head -n 200 || true

echo
echo "== Relevant ClineProvider sections =="
sed -n '430,535p' src/core/webview/ClineProvider.ts
sed -n '1030,1125p' src/core/webview/ClineProvider.ts

echo
echo "== View-id initialization and save/load helpers =="
rg -n -C3 'viewStateId\s*=|viewId\s*=|generateUniqueId|saveViewState|loadViewState|clearPersistedViewState|MAX_PERSISTED_VIEW_STATES|setViewStateId' src/core/webview/ClineProvider.ts

echo
echo "== Pre-launch mutation call sites excluding tests =="
rg -n -C3 'provider\.(setValue|setValues|handleModeSwitch|activateProviderProfile|upsertProviderProfile|setValue\()' src -g '*.ts' -g '!**/__tests__/**' | head -n 240

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 26258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== webviewDidLaunch and message handler sequencing =="
sed -n '535,585p' src/core/webview/webviewMessageHandler.ts
sed -n '1,80p' src/core/webview/webviewMessageHandler.ts

echo
echo "== All viewStateId usages excluding tests =="
rg -n -C2 'viewStateId|setViewStateId|viewStates\]' src -g '*.ts' -g '!**/__tests__/**' | head -n 220

echo
echo "== ClineProvider constructor and resolve context =="
sed -n '230,310p' src/core/webview/ClineProvider.ts
sed -n '1030,1065p' src/core/webview/ClineProvider.ts

echo
echo "== Programmatic check: mutating methods use current viewStateId before switch =="
python3 - <<'PY'
from pathlib import Path
text = Path('src/core/webview/ClineProvider.ts').read_text()
start = text.index("\n\t\tif (!alreadyMigrated)")
end = text.index("\n\t\tprivate async saveViewState")
init = text[start:end]
target_index = text.index("\n\tpublic async setViewStateId")
save_start = text.index("\n\tprivate async saveViewState", target_index)
save_load = text[target_index:save_start]
print("init_contains_constructor_default_viewStateId=", "this.viewStateId = this.viewId" in init)
print("viewStateId_assigned_before_saveViewState=", text.find("this.viewStateId = this.viewId") < text.find("private async saveViewState"))
print("saveViewState_uses_viewStateId_field=", "this.viewStateId" in save_load)
print("persisted_save_uses_viewStateId_field=", "constants saved by getPersistedViewStates(this.viewStateId)" in open('src/core/webview/ClineProvider.ts').read())
PY

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 14377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== webviewDidLaunch message construction and send sites =="
rg -n -C4 'webviewDidLaunch|postStateToWebview|setState|sendHtml|setWebviewMessageListener|sendMessage|viewStateId' src -g '*.ts' -g '!**/__tests__/**' | head -n 260

echo
echo "== ClineProvider mutation persistence helpers =="
sed -n '3080,3225p' src/core/webview/ClineProvider.ts

echo
echo "== handleModeSwitch and activation-related mutation helpers =="
sed -n '1665,1715p' src/core/webview/ClineProvider.ts
sed -n '1030,1255p' src/core/webview/ClineProvider.ts

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 31712


Remove stale pre-launch view-state entries when migrating to the webview-reported id.

setViewStateId is called from webviewDidLaunch, but mutations such as handleModeSwitch can persist mode and upsertProviderProfile can persist currentApiConfigName under the constructor default viewId before that handshake. setViewStateId switches viewStateId and loads the new key without removing the old one; only disposal/closing later clears the current viewStateId, which is already the new non-ephemeral id. When switching IDs, migrate useful persisted values if needed, or at least clear the ephemeral viewId entry to avoid accumulating discarded view-state keys.

🤖 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/ClineProvider.ts` around lines 517 - 526, Update
setViewStateId to clean up the constructor-default viewId entry when switching
to the webview-reported ID, while preserving any useful persisted values by
migrating them if required before loading the new state. Ensure cleanup only
targets the stale ephemeral entry and does not remove the newly selected
viewStateId data.

Comment on lines +532 to +563
private async loadViewState(): Promise<void> {
try {
const persisted = this.getPersistedViewStates()[this.viewStateId]
const loadedState: Partial<ExtensionState> = {}

if (persisted?.mode) {
loadedState.mode = persisted.mode as Mode
}

if (persisted?.currentApiConfigName) {
loadedState.currentApiConfigName = persisted.currentApiConfigName

try {
const { name: _name, ...apiConfiguration } = await this.providerSettingsManager.getProfile({
name: persisted.currentApiConfigName,
})
loadedState.apiConfiguration = apiConfiguration as ProviderSettings
} catch (error) {
this.log(
`[loadViewState] Unable to resolve API profile '${persisted.currentApiConfigName}' for viewId ${this.viewId}: ${error instanceof Error ? error.message : String(error)}`,
)
}
}

this.viewLocalState = loadedState
this.log(`[loadViewState] Loaded state for viewId ${this.viewId}`)
} catch (error) {
this.log(
`[loadViewState] Error loading state for viewId ${this.viewId}: ${error instanceof Error ? error.message : String(error)}`,
)
}
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

loadViewState replaces the whole viewLocalState buffer and can discard live per-view selections.

Line 556 assigns this.viewLocalState = loadedState, so every key not present in the persisted entry is dropped. Two reachable paths lose state:

  1. The constructor calls void this.loadViewState() at line 297 without awaiting it, and loadViewState awaits providerSettingsManager.getProfile. Any mutation that lands in that window — setValue, setValues, activateProviderProfile, or upsertProviderProfile, all of which now write into viewLocalState — is overwritten when line 556 runs.
  2. setViewStateId calls loadViewState on webviewDidLaunch. loadedState.apiConfiguration is only populated when the persisted entry carries a resolvable currentApiConfigName. If it does not, a previously synced viewLocalState.apiConfiguration is dropped and getState() silently falls back to the shared provider settings.

Merge the hydrated values over the existing buffer instead of replacing it, and only overwrite keys that hydration actually resolved.

🐛 Proposed fix
-			this.viewLocalState = loadedState
+			this.viewLocalState = { ...this.viewLocalState, ...loadedState }
 			this.log(`[loadViewState] Loaded state for viewId ${this.viewId}`)

If replacement is intentional for the setViewStateId path, sequence the constructor hydration explicitly (for example, store the promise and await it in getState) so it cannot race later mutations.

🤖 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/ClineProvider.ts` around lines 532 - 563, Update
loadViewState to merge hydrated fields into the existing this.viewLocalState
rather than replacing the buffer, preserving live per-view selections and only
assigning values that were actually persisted and resolved. Ensure both
constructor-triggered hydration and setViewStateId hydration cannot overwrite
mutations made while getProfile is pending; if replacement semantics remain
necessary for setViewStateId, explicitly sequence the constructor’s
loadViewState promise before getState.

Comment on lines +1899 to +1903
this._updateViewLocalStateFromMutation({
currentApiConfigName: profileToActivate,
listApiConfigMeta: entries,
})

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

deleteProviderProfile updates the cache but does not persist the replacement profile.

upsertProviderProfile (line 1851) and activateProviderProfile (line 1953) both call _saveViewLocalStateFromMutation, which persists currentApiConfigName into the viewStates entry. Deletion calls _updateViewLocalStateFromMutation, which only updates the in-memory cache.

The persisted entry therefore keeps naming the deleted profile. After a window reload, loadViewState reads that name, providerSettingsManager.getProfile throws, the error is only logged, and viewLocalState.currentApiConfigName is left pointing at a profile that no longer exists. getStateToPostToWebview then reports that stale name to the webview.

🐛 Proposed fix
-		this._updateViewLocalStateFromMutation({
-			currentApiConfigName: profileToActivate,
-			listApiConfigMeta: entries,
-		})
+		await this._saveViewLocalStateFromMutation({
+			currentApiConfigName: profileToActivate,
+			listApiConfigMeta: entries,
+		})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this._updateViewLocalStateFromMutation({
currentApiConfigName: profileToActivate,
listApiConfigMeta: entries,
})
await this._saveViewLocalStateFromMutation({
currentApiConfigName: profileToActivate,
listApiConfigMeta: entries,
})
🤖 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/ClineProvider.ts` around lines 1899 - 1903, Update
deleteProviderProfile to call _saveViewLocalStateFromMutation instead of only
_updateViewLocalStateFromMutation when replacing the deleted profile, so the new
currentApiConfigName and listApiConfigMeta are persisted in viewStates. Preserve
the existing mutation values and behavior for the in-memory cache.

Comment on lines 3215 to +3219
await this.contextProxy.resetAllState()

// Clear view-local state cache so getState() falls back to ContextProxy defaults.
this._clearViewLocalState()

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Determine whether resetAllState clears the viewStates key and whether other provider instances are reset.
set -euo pipefail

# Inspect resetAllState and the key set it clears.
ast-grep outline src/core/config/ContextProxy.ts --items all --match 'resetAllState|GLOBAL_STATE_KEYS|globalSettings'
rg -nP -C10 'resetAllState' --type=ts src/core/config

# Confirm viewStates is declared as a global settings key.
rg -nP -C4 '\bviewStates\b' packages/types/src src/core/config

# Check whether resetState fans out to other instances.
rg -nP -C6 'activeInstances|getAllInstances' --type=ts src/core/webview/ClineProvider.ts

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 12821


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the referenced symbols and inspect compact relevant slices.
printf '\n--- ClineProvider reset/clear/load views slices ---\n'
rg -n -C 8 'resetState\(|_clearViewLocalState\(|loadViewState|viewLocalState|clearViews|activeInstances\.forEach|getAllInstances' --type=ts src/core/webview/ClineProvider.ts

printf '\n--- ContextProxy resetAllState slice ---\n'
sed -n '560,620p' src/core/config/ContextProxy.ts

printf '\n--- global-settings relevant keys ---\n'
sed -n '1,80p' packages/types/src/global-settings.ts
sed -n '80,135p' packages/types/src/global-settings.ts

printf '\n--- deterministic source verifier ---\n'
python3 - <<'PY'
from pathlib import Path
import re

cp = Path("src/core/webview/ClineProvider.ts").read_text()
cx = Path("src/core/config/ContextProxy.ts").read_text()

reset = cp[cp.find("resetState("):cp.find("resetState(", cp.find("resetState(")+1)]
clear = cp[cp.find("_clearViewLocalState("):cp.find("_clearViewLocalState(", cp.find("_clearViewLocalState(")+1)] if cp.count("_clearViewLocalState(") == 1 else cp[cp.find("_clearViewLocalState("):]
reset_all = cx[cx.find("public async resetAllState"):cx.find("public async resetAllState")+2000]

checks = {
    "resetState calls contextProxy.resetAllState": "this.contextProxy.resetAllState()" in cp[cp.find("resetState("):cp.find("resetState(", cp.find("resetState(")+1)+1200] if "resetState(" in cp else False,
    "resetState calls this._clearViewLocalState": "_clearViewLocalState()" in cp[cp.find("resetState("):cp.find("resetState(", cp.find("resetState(")+1)+1200] if "resetState(" in cp else False,
    "resetState contains activeInstances or getAllInstances reference": any(id_ in cp[cp.find("resetState("):cp.find("resetState(", cp.find("resetState(")+1200)] for id_ in ("activeInstances", "getAllInstances")),
    "_clearViewLocalState targets other providers": bool(re.search(r"activeInstances|getAllInstances|ClineProvider|viewLocalState", clear, re.S) and "other" in clear.lower()),
    "resetAllState uses GLOBAL_STATE_KEYS.update(undefined)": bool(re.search(r"\.\.\.GLOBAL_STATE_KEYS\.map\(\s*key\s*=>\s*this\.originalContext\.globalState\.update\s*\(\s*key\s*,\s*undefined\s*\)", reset_all, re.S)),
    "resetAllState uses SECRET_STATE_KEYS.delete": bool(re.search(r"\.\.\.SECRET_STATE_KEYS\.map\(\s*key\s*=>\s*this\.originalContext\.secrets\.delete\s*\(\s*key\s*\)", reset_all, re.S)),
    "resetAllState uses GLOBAL_SECRET_KEYS.delete": bool(re.search(r"\.\.\.GLOBAL_SECRET_KEYS\.map\(\s*key\s*=>\s*this\.originalContext\.secrets\.delete\s*\(\s*key\s*\)", reset_all, re.S)),
}
for key, val in checks.items():
    print(f"{key}: {val}")

# Print concise snippets for evidence.
print("\n-- resetState snippet --")
idx = cp.find("resetState(")
print(cp[idx:idx+1200] if idx != -1 else "NOT_FOUND")
print("\n-- _clearViewLocalState snippet --")
idx = cp.find("_clearViewLocalState(")
print(cp[idx:idx+500] if idx != -1 else "NOT_FOUND")
PY

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 19287


Reset per-view state for every open ClineProvider.

resetState() updates viewStates and clears this.viewLocalState, but other open provider instances keep their own ClineProvider.viewLocalState. Those instances still merge per-view mode / API profile into getState() after reset, so resetState should also clear cached state for each instance in activeInstances.

🤖 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/ClineProvider.ts` around lines 3215 - 3219, Update
resetState() to clear view-local state for every open ClineProvider in
activeInstances, not only the current instance. Iterate over activeInstances and
invoke each provider’s _clearViewLocalState() after resetting shared state,
preserving the existing current-instance behavior without leaving per-view mode
or API profile overrides cached.

Comment on lines 1062 to +1076
"core/webview/ClineProvider.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 12
"count": 16
}
},
"core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 34
}
},
"core/webview/__tests__/ClineProvider.parallelMode.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 143
}
},

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

New untyped code raises the ESLint suppression baseline for two existing files. core/webview/ClineProvider.ts goes from 12 to 16 and core/webview/__tests__/webviewMessageHandler.spec.ts goes from 35 to 45. The shared root cause is new any usage that has a local, low-risk typed alternative; fix the two source sites and restore both baselines.

  • src/eslint-suppressions.json#L1062-L1076: restore core/webview/ClineProvider.ts to 12 and core/webview/__tests__/webviewMessageHandler.spec.ts to 35 after the two fixes below land.
  • src/core/webview/ClineProvider.ts#L565-L573: replace saveViewState(key: keyof ExtensionState, value: any) with a generic <K extends keyof ExtensionState>(key: K, value: ExtensionState[K]) and drop the as Partial<RooCodeSettings> & Partial<ExtensionState> cast.
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts#L217-L230: declare setViewStateId, workspaceTracker, providerSettingsManager, activateProviderProfile, getMcpHub, and getStateToPostToWebview on the mockClineProvider literal instead of assigning them through (mockClineProvider as any).

The coding guidelines state: "Suppression counts in src/eslint-suppressions.json must never increase; when touching a file, reduce its count when the fix is local and low-risk."

📍 Affects 3 files
  • src/eslint-suppressions.json#L1062-L1076 (this comment)
  • src/core/webview/ClineProvider.ts#L565-L573
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts#L217-L230
🤖 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/eslint-suppressions.json` around lines 1062 - 1076, Replace the any-typed
saveViewState signature in src/core/webview/ClineProvider.ts:565-573 with a
generic key/value relationship using ExtensionState[K], and remove the
Partial<RooCodeSettings> & Partial<ExtensionState> cast. In
src/core/webview/__tests__/webviewMessageHandler.spec.ts:217-230, declare the
listed provider members directly on mockClineProvider instead of assigning
through an any cast. After both fixes, update
src/eslint-suppressions.json:1062-1076 to restore ClineProvider.ts to 12
suppressions and webviewMessageHandler.spec.ts to 35; the parallelMode entry
requires no direct change.

Source: Coding guidelines

delete state[key]
}
}),
} as unknown as Storage

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

pnpm --dir src exec eslint --prune-suppressions --max-warnings=0 ../webview-ui/src/utils/__tests__/vscode.spec.ts

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 1595


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file outline =="
ast-grep outline webview-ui/src/utils/__tests__/vscode.spec.ts --view expanded || true

echo "== relevant lines =="
cat -n webview-ui/src/utils/__tests__/vscode.spec.ts | sed -n '1,110p'

echo "== storage usages in tests =="
rg -n "Storage|setItem|getItem|Object\.defineProperty" webview-ui/src/utils/__tests__/vscode.spec.ts

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 4877


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tsconfig candidates =="
fd -a 'tsconfig.*|eslint.config.mjs|package.json' . | sed 's#^\./##' | sort

echo "== TypeScript config =="
for f in tsconfig.json tsconfig.base.json.web.json; do
  [ -e "$f" ] && { echo "--- $f"; cat -n "$f"; }
done

echo "== vscode module/API declarations =="
rg -n 'declare module|function getPlatform|type Storage|interface VSCode|class VSCodeAPIWrapper|localStorage|api\.settings' webview-ui/src -g '*.{ts,tsx}' | head -n 120

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 4058


Replace the double assertions with narrow storage mocks.

Lines 21 and 76 cast incomplete objects to Storage. This hides missing mock members. Use Pick<Storage, "getItem" | "setItem" | "removeItem" | "clear"> or a local mock interface, and do not use as未知 as Storage here.

Also applies to: 76-76

🤖 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 `@webview-ui/src/utils/__tests__/vscode.spec.ts` at line 21, Replace the double
assertions at the storage mock definitions in the vscode tests with a narrow
type such as Pick<Storage, "getItem" | "setItem" | "removeItem" | "clear"> or an
equivalent local mock interface, and update both affected mocks to use it
without casting to Storage.

Source: Coding guidelines

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 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