Skip to content

fix: refresh provider settings on profile switch in Settings view#11460

Draft
roomote[bot] wants to merge 1 commit intomainfrom
fix/profile-switch-settings-refresh
Draft

fix: refresh provider settings on profile switch in Settings view#11460
roomote[bot] wants to merge 1 commit intomainfrom
fix/profile-switch-settings-refresh

Conversation

@roomote
Copy link
Contributor

@roomote roomote bot commented Feb 13, 2026

Related GitHub Issue

Closes: #11450

Description

This PR attempts to address Issue #11450 where switching Configuration Profiles within the Settings/Providers tab did not refresh the provider settings content.

Root Cause: When switching profiles via the dropdown in Settings, the cachedState update happened asynchronously (via useEffect) after the render cycle. This meant child components like ApiOptions and their provider sub-components would either:

  1. Not see the new configuration in time (stale props during mount)
  2. Retain local state from the previous profile (e.g., custom headers, toggle states)

Three fixes applied:

  1. Synchronous render-time state update (SettingsView.tsx): Changed the profile-switch useEffect to a synchronous state update during render (the React-recommended "adjusting state when a prop changes" pattern). This ensures cachedState is updated before child components render, so they receive the correct apiConfiguration immediately.

  2. Key-based remount (SettingsView.tsx): Added key={currentApiConfigName} on the ApiOptions component, forcing React to completely unmount and remount the entire provider settings tree when the profile changes. This resets all local state in ApiOptions and every provider component (OpenAICompatible, Anthropic, OpenRouter, etc.).

  3. Defensive sync and write-back guard (OpenAICompatible.tsx): Added a sync effect for customHeaders and azureApiVersionSelected to handle external changes, and added a guard to the debounced write-back effect to prevent stale header overwrites.

Feedback and guidance are welcome.

Test Procedure

  • All existing tests pass (64 passed, 6 skipped across 6 test files)
  • Added 2 new tests in OpenAICompatible.spec.tsx:
    • Verifies custom headers sync correctly on profile switch (no stale overwrite)
    • Verifies write-back guards against no-op writes
  • Manual testing: Switch between profiles in Settings > Providers tab and verify all fields refresh

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 the linked issue (one major feature/fix per PR).
  • 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 (if applicable).
  • Documentation Impact: No documentation updates are required.
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Documentation Updates

  • No documentation updates are required.

Additional Notes

The synchronous render-time state update pattern follows the React docs recommendation for "adjusting state when a prop changes." This is preferred over useEffect because it ensures the state update happens before the render commits, avoiding a frame of stale UI.

When switching Configuration Profiles within the Settings/Providers tab,
provider settings were not refreshing because:

1. The cachedState update happened asynchronously (useEffect) after the
   render cycle, causing child components to mount with stale config
2. ApiOptions and provider components retained local state from the
   previous profile since they were not remounted

Three fixes applied:
- Changed profile-switch useEffect to synchronous render-time state
  update (React "adjusting state when a prop changes" pattern)
- Added key={currentApiConfigName} on ApiOptions to force full remount
  on profile switch, resetting all local state in provider components
- Added sync effects and write-back guards in OpenAICompatible.tsx to
  prevent stale header overwrites

Closes #11450
@roomote
Copy link
Contributor Author

roomote bot commented Feb 13, 2026

Rooviewer Clock   See task

The core approach (synchronous render-time state update + key-based remount) is sound and correctly solves the profile-switch staleness issue. Two items flagged:

  • Sync effects in OpenAICompatible.tsx are redundant with the key-based remount for the profile-switch case; comments should be clarified or the effects removed
  • Profile switch test assertion is vacuously true when no write-back fires; add an explicit expect(writeBackCalls).toHaveLength(0)

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Comment on lines +55 to +62
// Sync local customHeaders state when apiConfiguration changes externally
// (e.g., on profile switch). This mirrors the pattern in ApiOptions.tsx.
useEffect(() => {
const propHeaders = apiConfiguration?.openAiHeaders || {}
if (JSON.stringify(customHeaders) !== JSON.stringify(Object.entries(propHeaders))) {
setCustomHeaders(Object.entries(propHeaders))
}
}, [apiConfiguration?.openAiHeaders]) // eslint-disable-line react-hooks/exhaustive-deps
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These sync effects are redundant for the profile-switch scenario described in the PR. The key={currentApiConfigName} on ApiOptions in SettingsView.tsx already causes OpenAICompatible to fully unmount and remount when the profile changes, so the useState initializer at line 50 reads fresh values from the new apiConfiguration directly. The sync effects would only fire if openAiHeaders changed without a currentApiConfigName change (no remount).

Also, the comment says this "mirrors the pattern in ApiOptions.tsx," but the ApiOptions version at line 146 includes customHeaders in its dependency array whereas this version omits it with an eslint-disable. Consider either removing these effects if the key-based remount is the intended mechanism, or updating the comments to clarify they're defensive measures for non-profile-switch config changes.

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines +365 to +372
const writeBackCalls = mockSetApiConfigurationField.mock.calls.filter(
(call: any[]) => call[0] === "openAiHeaders",
)
// If there are write-back calls, they should be writing the NEW headers, not the old ones
for (const call of writeBackCalls) {
const writtenHeaders = call[1] as Record<string, string>
expect(writtenHeaders).not.toEqual({ "X-Profile": "old-profile" })
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test uses rerender to simulate a profile switch, but in production the key={currentApiConfigName} on ApiOptions causes OpenAICompatible to be unmounted and remounted -- not re-rendered in place. The test therefore exercises the sync effect path (lines 57-62) rather than the actual profile-switch code path.

Additionally, when the sync + guard work correctly, writeBackCalls is empty and the for loop body never executes, making the assertion vacuously true. Consider adding an explicit expect(writeBackCalls).toHaveLength(0) to make the expected outcome clear and to catch regressions where a stale write-back does fire.

Suggested change
const writeBackCalls = mockSetApiConfigurationField.mock.calls.filter(
(call: any[]) => call[0] === "openAiHeaders",
)
// If there are write-back calls, they should be writing the NEW headers, not the old ones
for (const call of writeBackCalls) {
const writtenHeaders = call[1] as Record<string, string>
expect(writtenHeaders).not.toEqual({ "X-Profile": "old-profile" })
}
const writeBackCalls = mockSetApiConfigurationField.mock.calls.filter(
(call: any[]) => call[0] === "openAiHeaders",
)
// No write-back should occur since local state is synced to new headers
expect(writeBackCalls).toHaveLength(0)

Fix it with Roo Code or mention @roomote and request a fix.

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.

[BUG] with Configuration Profile Selection

1 participant