fix: make active tab local-only, stop Automerge from overriding it

The active tab was stored in Automerge's shared doc (activeLayerId),
causing different browser windows/sessions to fight over which tab
is highlighted. When one window switched to rInbox, another window
showing rFlows would have its tab bar update to highlight rInbox
while still displaying rFlows content.

Changes:
- Active tab is now always determined locally by the URL/currentModuleId
- Automerge sync only manages the tab LIST (which modules are open)
  and flows, not which tab is active
- Removed sync.setActiveLayer() calls on tab switch
- Removed activeLayerId reads from Automerge on connect and on
  remote change events
- TabCache continues to manage active state correctly when switching
  tabs within the same page load

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-04 17:49:40 -08:00
parent 0bf3b3430c
commit 2bc1b78f30
1 changed files with 12 additions and 10 deletions

View File

@ -419,10 +419,15 @@ export function renderShell(opts: ShellOptions): string {
window.__rspaceTabBar = tabBar; window.__rspaceTabBar = tabBar;
// ── CommunitySync: merge with Automerge once connected ── // ── CommunitySync: merge with Automerge once connected ──
// NOTE: The TAB LIST is shared via Automerge (which modules are open),
// but the ACTIVE TAB is always local (determined by the URL / currentModuleId).
// This prevents windows from fighting over which tab is highlighted.
document.addEventListener('community-sync-ready', (e) => { document.addEventListener('community-sync-ready', (e) => {
const sync = e.detail?.sync; const sync = e.detail?.sync;
if (!sync) return; if (!sync) return;
const localActiveId = 'layer-' + currentModuleId;
// Merge: Automerge layers win if they exist, otherwise seed from localStorage // Merge: Automerge layers win if they exist, otherwise seed from localStorage
const remoteLayers = sync.getLayers(); const remoteLayers = sync.getLayers();
if (remoteLayers.length > 0) { if (remoteLayers.length > 0) {
@ -433,24 +438,21 @@ export function renderShell(opts: ShellOptions): string {
} }
layers = sync.getLayers(); layers = sync.getLayers();
tabBar.setLayers(layers); tabBar.setLayers(layers);
const activeId = sync.doc.activeLayerId;
if (activeId) tabBar.setAttribute('active', activeId);
tabBar.setFlows(sync.getFlows()); tabBar.setFlows(sync.getFlows());
} else { } else {
// First connection: push all localStorage tabs into Automerge // First connection: push all localStorage tabs into Automerge
for (const l of layers) { for (const l of layers) {
sync.addLayer(l); sync.addLayer(l);
} }
sync.setActiveLayer('layer-' + currentModuleId);
} }
// Active tab stays local — always matches the URL
tabBar.setAttribute('active', localActiveId);
// Keep localStorage in sync // Keep localStorage in sync
saveTabs(); saveTabs();
// Sync layer changes back to Automerge // Sync layer list changes to Automerge (not active tab)
tabBar.addEventListener('layer-switch', (e) => {
sync.setActiveLayer(e.detail.layerId);
});
tabBar.addEventListener('layer-add', (e) => { tabBar.addEventListener('layer-add', (e) => {
const { moduleId } = e.detail; const { moduleId } = e.detail;
const newLayer = makeLayer(moduleId, sync.getLayers().length); const newLayer = makeLayer(moduleId, sync.getLayers().length);
@ -469,13 +471,13 @@ export function renderShell(opts: ShellOptions): string {
tabBar.addEventListener('flow-remove', (e) => { sync.removeFlow(e.detail.flowId); }); tabBar.addEventListener('flow-remove', (e) => { sync.removeFlow(e.detail.flowId); });
tabBar.addEventListener('view-toggle', (e) => { sync.setLayerViewMode(e.detail.mode); }); tabBar.addEventListener('view-toggle', (e) => { sync.setLayerViewMode(e.detail.mode); });
// Listen for remote changes // Listen for remote changes — sync tab list and flows only.
// Never touch the active tab: it's managed locally by TabCache
// and the tab-bar component via layer-switch events.
sync.addEventListener('change', () => { sync.addEventListener('change', () => {
layers = sync.getLayers(); layers = sync.getLayers();
tabBar.setLayers(layers); tabBar.setLayers(layers);
tabBar.setFlows(sync.getFlows()); tabBar.setFlows(sync.getFlows());
const activeId = sync.doc.activeLayerId;
if (activeId) tabBar.setAttribute('active', activeId);
const viewMode = sync.doc.layerViewMode; const viewMode = sync.doc.layerViewMode;
if (viewMode) tabBar.setAttribute('view-mode', viewMode); if (viewMode) tabBar.setAttribute('view-mode', viewMode);
saveTabs(); // keep localStorage in sync saveTabs(); // keep localStorage in sync