From 3c26addeaeda116a02b9bf73095c3e98d7577eda Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 4 Mar 2026 19:32:38 -0800 Subject: [PATCH 1/2] fix: defer tab bar active state until switchTo() resolves The tab bar was setting its `active` attribute synchronously on click, before TabCache.switchTo() finished fetching and injecting the new pane. This caused a visual desync where the tab highlighted immediately but the content area showed a blank flash or stale content. Now the tab bar dispatches the layer-switch event without changing its own active state. The shell event handler sets active only after switchTo() confirms the pane is ready, eliminating the race condition. Co-Authored-By: Claude Opus 4.6 --- server/shell.ts | 16 +++++++++++++--- shared/components/rstack-tab-bar.ts | 7 +++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/server/shell.ts b/server/shell.ts index efc741b..4b7c7bc 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -333,12 +333,18 @@ export function renderShell(opts: ShellOptions): string { } catch(e) { tabCache = null; } // ── Tab events ── + // Set active on tab bar ONLY after switchTo() confirms the pane is ready. + // This prevents the visual desync where the tab highlights before content loads. tabBar.addEventListener('layer-switch', (e) => { - const { moduleId } = e.detail; + const { layerId, moduleId } = e.detail; saveTabs(); if (tabCache) { tabCache.switchTo(moduleId).then(ok => { - if (!ok) window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); + if (ok) { + tabBar.setAttribute('active', layerId); + } else { + window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); + } }); } else { window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); @@ -353,7 +359,11 @@ export function renderShell(opts: ShellOptions): string { saveTabs(); if (tabCache) { tabCache.switchTo(moduleId).then(ok => { - if (!ok) window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); + if (ok) { + tabBar.setAttribute('active', 'layer-' + moduleId); + } else { + window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); + } }); } else { window.location.href = window.__rspaceNavUrl(spaceSlug, moduleId); diff --git a/shared/components/rstack-tab-bar.ts b/shared/components/rstack-tab-bar.ts index 81a1933..43b7ff8 100644 --- a/shared/components/rstack-tab-bar.ts +++ b/shared/components/rstack-tab-bar.ts @@ -794,14 +794,14 @@ export class RStackTabBar extends HTMLElement { // Clean up previous document-level listeners to prevent leak if (this.#docCleanup) { this.#docCleanup(); this.#docCleanup = null; } - // Tab clicks + // Tab clicks — dispatch event but do NOT set active yet. + // The shell's event handler calls switchTo() and sets active only after success. this.#shadow.querySelectorAll(".tab").forEach(tab => { tab.addEventListener("click", (e) => { const target = e.target as HTMLElement; if (target.classList.contains("tab-close")) return; const layerId = tab.dataset.layerId!; const moduleId = tab.dataset.moduleId!; - this.active = layerId; this.trackRecent(moduleId); this.dispatchEvent(new CustomEvent("layer-switch", { detail: { layerId, moduleId }, @@ -929,12 +929,11 @@ export class RStackTabBar extends HTMLElement { this.#shadow.querySelectorAll(".layer-plane").forEach(plane => { const layerId = plane.dataset.layerId!; - // Click to switch layer + // Click to switch layer — do NOT set active here, let shell handler confirm plane.addEventListener("click", (e) => { if (this.#flowDragSource || this.#orbitDragging) return; const layer = this.#layers.find(l => l.id === layerId); if (layer) { - this.active = layerId; this.dispatchEvent(new CustomEvent("layer-switch", { detail: { layerId, moduleId: layer.moduleId }, bubbles: true, From 991c0ed8a529a3c0b96e6a80c8a47bd22f52a4f6 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 4 Mar 2026 19:36:10 -0800 Subject: [PATCH 2/2] chore: remove rDocs and rDesign from module registry Placeholder modules not yet built out as full rApps. Removes them from the dropdown/tab bar until they have proper schemas, components, and local-first support. Co-Authored-By: Claude Opus 4.6 --- server/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/index.ts b/server/index.ts index 7126a88..e7a0f40 100644 --- a/server/index.ts +++ b/server/index.ts @@ -66,8 +66,8 @@ import { dataModule } from "../modules/rdata/mod"; import { splatModule } from "../modules/rsplat/mod"; import { photosModule } from "../modules/rphotos/mod"; import { socialsModule } from "../modules/rsocials/mod"; -import { docsModule } from "../modules/rdocs/mod"; -import { designModule } from "../modules/rdesign/mod"; +// import { docsModule } from "../modules/rdocs/mod"; +// import { designModule } from "../modules/rdesign/mod"; import { scheduleModule } from "../modules/rschedule/mod"; import { spaces, createSpace, resolveCallerRole, roleAtLeast } from "./spaces"; import type { SpaceRoleString } from "./spaces"; @@ -108,8 +108,8 @@ registerModule(dataModule); registerModule(splatModule); registerModule(photosModule); registerModule(socialsModule); -registerModule(docsModule); -registerModule(designModule); +// registerModule(docsModule); // placeholder — not yet an rApp +// registerModule(designModule); // placeholder — not yet an rApp registerModule(scheduleModule); // ── Config ──