From a402caacd8b1c521e6aea7463f299a16badc4ee2 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 2 Mar 2026 12:35:38 -0800 Subject: [PATCH] fix: app-switcher routes through tab system + canvas fills viewport in tab pane - App-switcher now dispatches module-select event instead of full page navigation for same-origin links; shell routes through TabCache for instant tab switching - Tab pane gets height:100% in canvas-layout mode so #canvas fills the viewport (fixes pan/zoom not working on empty canvas background) Co-Authored-By: Claude Opus 4.6 --- server/shell.ts | 30 ++++++++++++++++++++++++ shared/components/rstack-app-switcher.ts | 21 +++++++++++++++++ website/public/shell.css | 5 ++++ 3 files changed, 56 insertions(+) diff --git a/server/shell.ts b/server/shell.ts index 1bb714e..4cf6a67 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -324,6 +324,36 @@ export function renderShell(opts: ShellOptions): string { document.dispatchEvent(new CustomEvent('layer-view-mode', { detail: { mode } })); }); + // ── App-switcher → tab system integration ── + // When user picks a module from the app-switcher, route through tabs + // instead of doing a full page navigation. + const appSwitcher = document.querySelector('rstack-app-switcher'); + if (appSwitcher) { + appSwitcher.addEventListener('module-select', (e) => { + const { moduleId } = e.detail; + // Already on this module? No-op. + if (moduleId === currentModuleId && !tabCache) return; + if (moduleId === currentModuleId && tabCache) { + tabCache.switchTo(moduleId); + return; + } + // Add tab if not already open + if (!layers.find(l => l.moduleId === moduleId)) { + layers.push(makeLayer(moduleId, layers.length)); + } + saveTabs(); + tabBar.setLayers(layers); + tabBar.setAttribute('active', 'layer-' + moduleId); + if (tabCache) { + tabCache.switchTo(moduleId).then(ok => { + if (!ok) window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); + }); + } else { + window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); + } + }); + } + // Expose tabBar for CommunitySync integration window.__rspaceTabBar = tabBar; diff --git a/shared/components/rstack-app-switcher.ts b/shared/components/rstack-app-switcher.ts index ba98177..8f3fa00 100644 --- a/shared/components/rstack-app-switcher.ts +++ b/shared/components/rstack-app-switcher.ts @@ -254,6 +254,27 @@ export class RStackAppSwitcher extends HTMLElement { el.addEventListener("click", (e) => e.stopPropagation()); }); + // Intercept same-origin module links → dispatch event for tab system + this.#shadow.querySelectorAll("a.item").forEach((el) => { + el.addEventListener("click", (e) => { + const moduleId = (el as HTMLElement).dataset.id; + if (!moduleId) return; + // Only intercept same-origin links (skip bare-domain landing pages) + const href = (el as HTMLAnchorElement).href; + try { + const url = new URL(href, window.location.href); + if (url.origin !== window.location.origin) return; + } catch { return; } + e.preventDefault(); + menu.classList.remove("open"); + this.dispatchEvent(new CustomEvent("module-select", { + detail: { moduleId }, + bubbles: true, + composed: true, + })); + }); + }); + document.addEventListener("click", () => menu.classList.remove("open")); } diff --git a/website/public/shell.css b/website/public/shell.css index 6d87d3e..4b8e788 100644 --- a/website/public/shell.css +++ b/website/public/shell.css @@ -392,6 +392,11 @@ body[data-theme="light"] { display: block; } +/* When canvas is active, the tab pane must fill the viewport so #canvas height:100% works */ +#app.canvas-layout > .rspace-tab-pane--active { + height: 100%; +} + .rspace-tab-loading { display: flex; align-items: center;