diff --git a/server/shell.ts b/server/shell.ts index 9201888..4c38a7c 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -844,6 +844,15 @@ export function renderShell(opts: ShellOptions): string { // Reconcile remote layer changes (shared by BroadcastChannel + Automerge) function reconcileRemoteLayers(remoteLayers) { + // Guard: never let remote sync wipe all tabs when we have an active module. + // Empty remote layers indicate a CRDT initial state or sync race, not + // an intentional "close everything" action. + if (remoteLayers.length === 0 && currentModuleId) { + // Keep local layers intact — remote has nothing useful + tabBar.setLayers(layers); + return; + } + const prev = new Set(layers.map(l => l.moduleId)); const next = new Set(remoteLayers.map(l => l.moduleId)); @@ -854,35 +863,14 @@ export function renderShell(opts: ShellOptions): string { layers = deduplicateLayers(remoteLayers); + // Always ensure the current module stays in the tab list if (currentModuleId && !layers.find(l => l.moduleId === currentModuleId)) { - // Active tab was closed remotely — switch to nearest - if (layers.length > 0) { - const nearest = layers[layers.length - 1]; - currentModuleId = nearest.moduleId; - tabBar.setLayers(layers); - tabBar.setAttribute('active', 'layer-' + currentModuleId); - if (tabCache) { - tabCache.switchTo(currentModuleId).then(ok => { - if (!ok) window.location.href = window.__rspaceNavUrl(spaceSlug, currentModuleId); - }); - } - } else { - // No tabs left — show dashboard - const dashboard = document.querySelector('rstack-user-dashboard'); - if (dashboard && dashboard.setOpenTabs) dashboard.setOpenTabs([...layers]); - tabBar.setLayers([]); - tabBar.setAttribute('active', ''); - currentModuleId = ''; - if (tabCache) tabCache.hideAllPanes(); - if (dashboard) { dashboard.style.display = ''; if (dashboard.refresh) dashboard.refresh(); } - const app = document.getElementById('app'); - if (app) app.classList.remove('canvas-layout'); - history.pushState({ dashboard: true, spaceSlug: spaceSlug }, '', '/' + spaceSlug); - } - } else { - tabBar.setLayers(layers); + layers.push(makeLayer(currentModuleId, layers.length)); } + tabBar.setLayers(layers); + tabBar.setAttribute('active', 'layer-' + currentModuleId); + localStorage.setItem(TABS_KEY, JSON.stringify(layers)); } diff --git a/shared/tab-cache.ts b/shared/tab-cache.ts index 1c89977..71353e0 100644 --- a/shared/tab-cache.ts +++ b/shared/tab-cache.ts @@ -194,6 +194,7 @@ export class TabCache { try { const resp = await fetch(fetchUrl, { headers: { "Accept": "text/html" }, + signal: AbortSignal.timeout(10_000), }); if (!resp.ok) { loadingPane.remove();