Summary
ServerSettingsModal is a fully controlled component whose settings prop comes from useServers() (the in-memory mirror of ~/.mcp-inspector/mcp.json). On every change, the modal calls onSettingsChange up to App.tsx, which stashes the new value in a useRef and schedules a 300ms-debounced PUT. Because the ref doesn't trigger a re-render, the form's displayed value can only update after the debounced PUT round-trips and useServers refetches.
Repro
- Open any server's settings modal.
- Click "Add Header" or "Add Metadata" — no input row appears until ~300-500ms later (after debounce + PUT + refetch).
- Type in the OAuth Client ID / Client Secret / Scopes fields — characters echo with the same delay; rapid typing means nothing shows until you stop typing for 300ms.
Root cause
clients/web/src/App.tsx:784-849:
const pendingSettingsRef = useRef<{...}>(null);
const onSettingsChange = useCallback((next) => {
pendingSettingsRef.current = { id, settings: next }; // ← no re-render
if (timer) clearTimeout(timer);
timer = setTimeout(flushPendingSettings, 300);
});
The form is "controlled" but the parent never updates state with the new value, so the input's value prop stays stale.
Why it surfaced now
The bug was introduced in #1353 (when the debounced-flush pattern landed). It became user-visible after #1356 because per-keystroke PUTs now also hit the OS keychain, making the round-trip latency more noticeable.
Fix
Replace pendingSettingsRef with useState<InspectorServerSettings | null> so every change re-renders the modal immediately. Keep the 300ms debounce for the PUT side. Initialize the draft from the server-list entry only when the modal opens to a new target (not on every prop change) so background refreshes don't clobber in-progress edits.
Acceptance criteria
- Clicking "Add Header" / "Add Metadata" shows a new input row instantly.
- Typing in any field echoes characters with no perceptible lag.
- Edits still persist via the debounced PUT (no extra round-trips per keystroke).
- Closing the modal flushes pending edits.
Summary
ServerSettingsModalis a fully controlled component whosesettingsprop comes fromuseServers()(the in-memory mirror of~/.mcp-inspector/mcp.json). On every change, the modal callsonSettingsChangeup toApp.tsx, which stashes the new value in auseRefand schedules a 300ms-debounced PUT. Because the ref doesn't trigger a re-render, the form's displayed value can only update after the debounced PUT round-trips anduseServersrefetches.Repro
Root cause
clients/web/src/App.tsx:784-849:The form is "controlled" but the parent never updates state with the new value, so the input's
valueprop stays stale.Why it surfaced now
The bug was introduced in #1353 (when the debounced-flush pattern landed). It became user-visible after #1356 because per-keystroke PUTs now also hit the OS keychain, making the round-trip latency more noticeable.
Fix
Replace
pendingSettingsRefwithuseState<InspectorServerSettings | null>so every change re-renders the modal immediately. Keep the 300ms debounce for the PUT side. Initialize the draft from the server-list entry only when the modal opens to a new target (not on every prop change) so background refreshes don't clobber in-progress edits.Acceptance criteria