diff --git a/apps/mana/apps/web/src/lib/stores/bottom-bar.svelte.ts b/apps/mana/apps/web/src/lib/stores/bottom-bar.svelte.ts index b4433ecc8..1bb276220 100644 --- a/apps/mana/apps/web/src/lib/stores/bottom-bar.svelte.ts +++ b/apps/mana/apps/web/src/lib/stores/bottom-bar.svelte.ts @@ -26,6 +26,17 @@ export const bottomBarStore = { barComponent = component; barProps = props; }, + /** + * Update only the props of the currently-registered bar component. + * Use this from reactive blocks that frequently produce fresh prop + * objects (e.g. derived arrays) — calling `set(...)` in those + * places needlessly re-writes barComponent every tick, which + * notifies subscribers even when the component identity hasn't + * changed. + */ + setProps(props: Record) { + barProps = props; + }, clear() { barComponent = null; barProps = {}; diff --git a/apps/mana/apps/web/src/routes/(app)/+page.svelte b/apps/mana/apps/web/src/routes/(app)/+page.svelte index 29960b8fa..3279481ee 100644 --- a/apps/mana/apps/web/src/routes/(app)/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+page.svelte @@ -121,21 +121,35 @@ } // ── Register SceneAppBar in the layout's bottom-stack ─── + // Split into two effects so prop churn (carouselPages re-deriving on + // every openApps change) doesn't re-write barComponent. The first + // effect handles registration/teardown when scenes appear/disappear; + // the second pushes fresh props on every reactive tick. + let barRegistered = $state(false); $effect(() => { - if (scenes.length > 0) { - bottomBarStore.set(SceneAppBar, { - scenes, - activeSceneId, - pages: carouselPages, - onSceneSelect: (id: string) => workbenchScenesStore.setActiveScene(id), - onSceneCreate: (name: string) => workbenchScenesStore.createScene({ name }), - onSceneContextMenu: handleSceneContextMenu, - onAppClick: scrollToPage, - onAppContextMenu: (e: MouseEvent, id: string) => handleTabContextMenu(e, id), - onAddApp: () => (showPicker = !showPicker), - }); + const hasScenes = scenes.length > 0; + if (hasScenes && !barRegistered) { + bottomBarStore.set(SceneAppBar, {}); + barRegistered = true; + } else if (!hasScenes && barRegistered) { + bottomBarStore.clear(); + barRegistered = false; } }); + $effect(() => { + if (!barRegistered) return; + bottomBarStore.setProps({ + scenes, + activeSceneId, + pages: carouselPages, + onSceneSelect: (id: string) => workbenchScenesStore.setActiveScene(id), + onSceneCreate: (name: string) => workbenchScenesStore.createScene({ name }), + onSceneContextMenu: handleSceneContextMenu, + onAppClick: scrollToPage, + onAppContextMenu: (e: MouseEvent, id: string) => handleTabContextMenu(e, id), + onAddApp: () => (showPicker = !showPicker), + }); + }); // ── App CRUD (delegated to active scene) ──────────────── function handleAddApp(appId: string) {