diff --git a/server/shell.ts b/server/shell.ts index 5af447f..de58189 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -871,7 +871,7 @@ export function renderShell(opts: ShellOptions): string { if (_tabChannel) _tabChannel.close(); }); - // Fetch tabs from server for authenticated users (merge with localStorage) + // Fetch tabs from server for authenticated users (server-authoritative) (function syncTabsFromServer() { try { const raw = localStorage.getItem('encryptid_session'); @@ -888,21 +888,17 @@ export function renderShell(opts: ShellOptions): string { saveTabs(); return; } - // Merge: union of moduleIds, server order wins for shared tabs - // Skip tabs that were closed this session (prevents resurrection) - const serverMap = new Map(data.tabs.map(t => [t.moduleId, t])); - const localMap = new Map(layers.map(t => [t.moduleId, t])); - const merged = data.tabs.filter(t => !_closedModuleIds.has(t.moduleId)); - for (const [mid, lt] of localMap) { - if (!serverMap.has(mid) && !_closedModuleIds.has(mid)) merged.push(lt); - } - merged.forEach((l, i) => { l.order = i; }); - layers = merged; - // Ensure current module is present + // Server-authoritative: adopt server tabs exactly. + // Only skip tabs the user closed in THIS session (prevents + // close-then-immediate-resurrect before save propagates). + var serverTabs = data.tabs.filter(t => !_closedModuleIds.has(t.moduleId)); + serverTabs.forEach((l, i) => { l.order = i; }); + layers = serverTabs; + // Ensure the currently navigated module is present if (!layers.find(l => l.moduleId === currentModuleId)) { layers.push(makeLayer(currentModuleId, layers.length)); } - tabBar.setLayers(layers); + reconcileRemoteLayers(layers); saveTabs(); }) .catch(() => {}); @@ -1455,7 +1451,44 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string { tabBar.setLayers(layers); tabBar.setAttribute('active', 'layer-' + currentModuleId); if (tabBar.trackRecent) tabBar.trackRecent(currentModuleId); - function saveTabs() { localStorage.setItem(TABS_KEY, JSON.stringify(layers)); } + function saveTabs() { + localStorage.setItem(TABS_KEY, JSON.stringify(layers)); + try { + var raw = localStorage.getItem('encryptid_session'); + if (raw) { + var session = JSON.parse(raw); + if (session?.accessToken) { + fetch('/api/user/tabs/' + encodeURIComponent(spaceSlug), { + method: 'PUT', + headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + session.accessToken }, + body: JSON.stringify({ tabs: layers }), + }).catch(function(){}); + } + } + } catch(e) {} + } + // Sync tabs from server (server-authoritative) + (function() { + try { + var raw = localStorage.getItem('encryptid_session'); + if (!raw) return; + var session = JSON.parse(raw); + if (!session?.accessToken) return; + fetch('/api/user/tabs/' + encodeURIComponent(spaceSlug), { + headers: { 'Authorization': 'Bearer ' + session.accessToken }, + }) + .then(function(r) { return r.ok ? r.json() : null; }) + .then(function(data) { + if (!data?.tabs || !Array.isArray(data.tabs) || data.tabs.length === 0) { saveTabs(); return; } + layers = data.tabs; + layers.forEach(function(l, i) { l.order = i; }); + if (!layers.find(function(l) { return l.moduleId === currentModuleId; })) layers.push(makeLayer(currentModuleId, layers.length)); + tabBar.setLayers(layers); + localStorage.setItem(TABS_KEY, JSON.stringify(layers)); + }) + .catch(function(){}); + } catch(e) {} + })(); tabBar.addEventListener('layer-switch', (e) => { saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, e.detail.moduleId); }); tabBar.addEventListener('layer-add', (e) => { const { moduleId } = e.detail; if (!layers.find(l => l.moduleId === moduleId)) layers.push(makeLayer(moduleId, layers.length)); saveTabs(); window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); }); tabBar.addEventListener('layer-close', (e) => { const { layerId } = e.detail; tabBar.removeLayer(layerId); layers = layers.filter(l => l.id !== layerId); saveTabs(); if (layerId === 'layer-' + currentModuleId && layers.length > 0) window.location.href = window.__rspaceNavUrl(spaceSlug, layers[0].moduleId); });