fix(web): drop a pre-#2001 selectedToolName on OAuth resume

A resume snapshot written before this change carries `selectedToolName`
where `ToolsUiState` now expects `selectedToolKey`. The blind cast in
`restoreTabUiFromSnapshot` carried it through as a stray field that would be
re-serialized on the next redirect. Route the Tools entry through a
`normalizeToolsUi` that drops it explicitly instead.

The name is deliberately not migrated to a row key: a snapshot lives for one
redirect (so this only arises when the app is upgraded mid-flight), and the
tools list is fetched after reconnect, long after restore — there is nothing
to resolve the name against. The selection is lost; search, form values, and
the run-as-task toggle restore intact.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-17 00:19:22 -04:00
commit 63b37e05ec

View file

@ -96,6 +96,23 @@ export function buildTabUiSnapshot(
};
}
/**
* A snapshot written before #2001 carries `selectedToolName` (a tool's name)
* where {@link ToolsUiState} now expects `selectedToolKey` (its `index:name`
* row identity). A snapshot only lives for the length of one OAuth redirect,
* so this matters exactly when the app is upgraded mid-redirect — and the name
* cannot be mapped to a row key here, since the tools list is fetched after
* reconnect, long after restore. So the stale selection is dropped rather than
* carried as a stray field that would be re-serialized on the next redirect;
* the rest of the tab state (search text, form values) is restored intact and
* the screen opens on its "select a tool" placeholder.
*/
function normalizeToolsUi(value: unknown): ToolsUiState {
const ui = { ...((value as ToolsUiState | undefined) ?? EMPTY_TOOLS_UI) };
delete (ui as ToolsUiState & { selectedToolName?: string }).selectedToolName;
return ui;
}
export function restoreTabUiFromSnapshot(
tabUi: Partial<Record<InspectorTabId, unknown>> | undefined,
setters: TabUiSetters,
@ -110,9 +127,7 @@ export function restoreTabUiFromSnapshot(
const value = tabUi[tabId];
switch (tabId) {
case "Tools":
setters.setToolsUi(
(value as ToolsUiState | undefined) ?? EMPTY_TOOLS_UI,
);
setters.setToolsUi(normalizeToolsUi(value));
break;
case "Prompts":
setters.setPromptsUi(