Skip to content

Commit 3215581

Browse files
committed
Keep routing activity listener through slow panel mounts
1 parent d26fdb6 commit 3215581

2 files changed

Lines changed: 15 additions & 3 deletions

File tree

‎src/client/routing.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -832,11 +832,19 @@ export function routingPanel(isAdmin: boolean, confirm: Dialog): HTMLElement {
832832
applyRoutingActivity(state, activity);
833833
const old = content.querySelector(".routing-fabric");
834834
if (old) old.replaceWith(routingFabric(state));
835-
if (!shell.isConnected) setTimeout(() => {
836-
if (!shell.isConnected) routingActivityListeners.delete(shellActivityListener);
837-
}, 1_000);
838835
};
839836
routingActivityListeners.add(shellActivityListener);
837+
// routingPanel() is built before Settings attaches it to the document. Never
838+
// interpret that initial detached state as disposal: slower browsers can take
839+
// more than a second to mount the panel and would silently lose live events.
840+
let shellWasConnected = false;
841+
const shellLifecycle = new MutationObserver(() => {
842+
if (shell.isConnected) { shellWasConnected = true; return; }
843+
if (!shellWasConnected) return;
844+
routingActivityListeners.delete(shellActivityListener);
845+
shellLifecycle.disconnect();
846+
});
847+
shellLifecycle.observe(document.documentElement, { childList: true, subtree: true });
840848
const expandedAccounts = new Set<string>();
841849
const expandedGroups = new Set<string>();
842850
const views: Array<[RoutingView, string]> = isAdmin

‎test/routing-ui-contract.mjs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ test("provider controls expose the live dotted router flow and credential-free h
3535
assert.doesNotMatch(server, /pendingSystemRequests|candidate\.model === String\(event\.request\?\.model/,
3636
"system classification never guesses from an imminent same-model user request");
3737
assert.doesNotMatch(client, /routing-pyramid-tier/);
38+
assert.match(client, /let shellWasConnected = false;[\s\S]*if \(shell\.isConnected\) \{ shellWasConnected = true; return; \}[\s\S]*routingActivityListeners\.delete\(shellActivityListener\)/,
39+
"the Settings routing listener survives its initial detached construction and is removed only after a real mount/unmount lifecycle");
40+
assert.doesNotMatch(client, /if \(!shell\.isConnected\) setTimeout/,
41+
"slow Settings mounts never lose live routing events to a timer-based detached-state guess");
3842
assert.doesNotMatch(client.slice(client.indexOf("export async function openRoutingPopover"), client.indexOf("function sourceCatalog")), /routing\/credentials|apiKey/);
3943
});
4044

0 commit comments

Comments
 (0)