fix(webview): sync cachedState on mode changes in SettingsView#925
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
ChangesSettings cache synchronization
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
webview-ui/src/components/settings/SettingsView.tsx (1)
224-234: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUpdate Vitest coverage for mode synchronization.
As per coding guidelines, please ensure that the local Vitest test (
webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx) is updated to cover this new React hook behavior. The test should verify that when themodechanges inextensionState, the component's dirty state is correctly reset and the new mode settings are synchronized tocachedState.🤖 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/components/settings/SettingsView.tsx` around lines 224 - 234, The SettingsView tests do not cover the new mode-synchronization behavior in the effect guarding on prevApiConfigName and prevMode. Update the Vitest cases in SettingsView.spec.tsx to change extensionState.mode after the component is dirty, then verify changeDetected resets and cachedState reflects the new mode settings.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.
Nitpick comments:
In `@webview-ui/src/components/settings/SettingsView.tsx`:
- Around line 224-234: The SettingsView tests do not cover the new
mode-synchronization behavior in the effect guarding on prevApiConfigName and
prevMode. Update the Vitest cases in SettingsView.spec.tsx to change
extensionState.mode after the component is dirty, then verify changeDetected
resets and cachedState reflects the new mode settings.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: baa04223-c09f-4573-8b66-24b3973d3b3c
📒 Files selected for processing (1)
webview-ui/src/components/settings/SettingsView.tsx
6372c82 to
9d21d45
Compare
There was a problem hiding this comment.
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
`@webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx`:
- Around line 607-640: Update the “does not trigger sync when mode has not
changed” test to create a dirty local provider state before rerendering, while
keeping the mocked extensionState provider as “openai.” Assert that the locally
modified provider value remains after rerender, so an unintended synchronization
would be detected rather than masked by identical values.
🪄 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: 5414c876-564b-4c0e-88e1-a770dd725d2b
📒 Files selected for processing (2)
webview-ui/src/components/settings/SettingsView.tsxwebview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- webview-ui/src/components/settings/SettingsView.tsx
9d21d45 to
13fe3ad
Compare
edelauna
left a comment
There was a problem hiding this comment.
Nice ! Thanks for taking this on - is a nice little fix. Had some comments about increasing the test robustness, otherwise looks good.
| it("does not trigger sync when mode has not changed", async () => { | ||
| const onDone = vi.fn() | ||
| const extensionState = createExtensionState({ | ||
| mode: "code", | ||
| apiConfiguration: { | ||
| apiProvider: "openai", | ||
| apiModelId: "gpt-4.1", | ||
| }, | ||
| }) | ||
|
|
||
| ;(useExtensionState as any).mockImplementation(() => extensionState) | ||
|
|
||
| const { rerender } = render( | ||
| <QueryClientProvider client={queryClient}> | ||
| <SettingsView onDone={onDone} /> | ||
| </QueryClientProvider>, | ||
| ) | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByTestId("provider-value")).toHaveTextContent("openai") | ||
| }) | ||
|
|
||
| // Make a dirty change so we can verify it isn't overwritten by a sync | ||
| fireEvent.click(screen.getByTestId("set-provider-baseten")) | ||
| expect(screen.getByTestId("provider-value")).toHaveTextContent("baseten") | ||
|
|
||
| // Re-render with same mode - should not trigger sync | ||
| await act(async () => { | ||
| rerender( | ||
| <QueryClientProvider client={queryClient}> | ||
| <SettingsView onDone={onDone} /> | ||
| </QueryClientProvider>, | ||
| ) | ||
| }) | ||
|
|
||
| // Provider value should remain unchanged from the dirty state | ||
| expect(screen.getByTestId("provider-value")).toHaveTextContent("baseten") | ||
| }, 20000) |
There was a problem hiding this comment.
It looks like this test would pass even if the whole mode fix were reverted. Since extensionState is a const (line 609) and the rerender at line 635 reuses the same object reference, React sees no dependency change — the sync effect never re-runs under either the old or new code, so provider-value stays baseten purely from the dirty click above.
Would reassigning extensionState to a new object (same values, new identity) before the rerender make this guard load-bearing? As written, I think this catches a regression of nothing.
| // Now change the mode - this should trigger the mode sync effect | ||
| await act(async () => { | ||
| extensionState = createExtensionState({ | ||
| mode: "ask", | ||
| apiConfiguration: { | ||
| apiProvider: "openrouter", | ||
| apiModelId: "claude-3.5-sonnet", | ||
| }, | ||
| }) | ||
| ;(useExtensionState as any).mockImplementation(() => extensionState) | ||
|
|
||
| rerender( | ||
| <QueryClientProvider client={queryClient}> | ||
| <SettingsView onDone={onDone} /> | ||
| </QueryClientProvider>, | ||
| ) | ||
| }) | ||
|
|
||
| // Let the mode sync effect run | ||
| await act(async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 0)) | ||
| }) | ||
|
|
||
| // Verify cachedState reflects the new mode's settings | ||
| await waitFor(() => { | ||
| expect(screen.getByTestId("provider-value")).toHaveTextContent("openrouter") | ||
| }) | ||
|
|
||
| // Verify changeDetected is reset (save button should be disabled) | ||
| const updatedSaveButton = screen.getByTestId("save-button") as HTMLButtonElement | ||
| expect(updatedSaveButton.disabled).toBe(true) | ||
| }, 20000) |
There was a problem hiding this comment.
Two mutations of the fix that this test seems to miss:
- If
modewere dropped from the dependency array, the effect would still fire here becauseextensionStateis reassigned to a new object at line 576 — so themodedep entry (part of this PR) is effectively untested. - If the
prevMode.current = modeassignment were removed, the singlecode → asktransition would still produce correct state once; the stale ref is never re-exercised without a second transition.
Would it be worth adding a rerender where only extensionState identity changes (mode + apiConfigName unchanged) to pin the dep array, and a second same-mode rerender to expose a stale prevMode?
13fe3ad to
188b53c
Compare
Problem
In parallel mode,
SettingsViewshows stalemodeandapiConfigurationvalues because its internalcachedStateonly updates viauseEffectwhencurrentApiConfigNamechanges, but doesn't refresh whenmodechanges throughhandleModeSwitch().Fix
Add
modeto the dependency array of theuseEffectthat updatescachedState:Modified Files
webview-ui/src/components/settings/SettingsView.tsxChanges
modefromextensionState(line 133)prevModeref to track previous mode value (line 150)useEffectcondition to check bothcurrentApiConfigNameandmode(line 226)modeto the dependency array (line 234)Closes #914
Summary by CodeRabbit