From 3906774eba2b06cf9dcda86021f2070fc8ec1fcc Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Wed, 30 Sep 2026 10:27:23 -0700 Subject: [PATCH] fix: give a page without the band's chrome what the band was carrying MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second review round, three reachable defects plus one decision: - A public raw app was 150px tall. The bundle's iframe is `h-full`, and dropping its `min-h-screen` left nothing to resolve against on the share, embed and custom-path routes — none of which has a layout above them. Measured: 150px in a 900px viewport. The floor goes back for those routes and stays off inside the workspace, where the layout hands a height down and `min-h-screen` would overflow it by the band. - The band retracted out from under its own open menu on a deployed app. Last round taught the sidebar card to hold while the pointer is in a portaled menu and left its sibling alone, but the band opens menus too — the workspace disc, the fork family, every path level. One helper now, used by both closers. - `bandPeeked` outlived the page it belonged to. Clicking a control in the band navigates away, and the next page may not be one the band floats over, so the hover handlers that would clear it are gone — leaving the band already down when a full-bleed page came back. `beforeNavigate` clears it. - An embed has no band to carry a control, so the app's Edit floats over the canvas there, where it sat before the band existed. Registering it as the band's `afterName` under `navHidden` instead would have cost the app 44px of the iframe for a row holding one button. Reading "is the workspace navigation hidden" now has one definition (`isMenuHidden`), since a page has to answer it too: the flag is sticky inside an iframe, so the raw query param is the wrong thing to read. Also inset the band by the rail only when there is a rail — an embed reserved its width and crowded its own controls into a compact layout early. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/lib/components/PageHeaderBar.svelte | 6 -- .../apps/editor/InWorkspaceAppViewer.svelte | 24 ++++++-- .../components/apps/editor/PublicApp.svelte | 19 +++--- .../components/sessions/sessionMode.svelte.ts | 24 ++++++++ .../src/routes/(root)/(logged)/+layout.svelte | 58 +++++++++---------- 5 files changed, 83 insertions(+), 48 deletions(-) diff --git a/frontend/src/lib/components/PageHeaderBar.svelte b/frontend/src/lib/components/PageHeaderBar.svelte index 380c81ad11..d011b291fd 100644 --- a/frontend/src/lib/components/PageHeaderBar.svelte +++ b/frontend/src/lib/components/PageHeaderBar.svelte @@ -160,12 +160,6 @@ The row's height matches the sidebar's own header row, so the two read as one ba actingWorkspaceId={content?.actingWorkspaceId} /> - {:else if content?.afterName} - -
- {@render content.afterName()} -
{/if} {#if item && !navHidden && (item.summaryContent || item.summary)} diff --git a/frontend/src/lib/components/apps/editor/InWorkspaceAppViewer.svelte b/frontend/src/lib/components/apps/editor/InWorkspaceAppViewer.svelte index b60d770236..12de836650 100644 --- a/frontend/src/lib/components/apps/editor/InWorkspaceAppViewer.svelte +++ b/frontend/src/lib/components/apps/editor/InWorkspaceAppViewer.svelte @@ -20,6 +20,7 @@ import { getUserExt } from '$lib/user' import { Pen } from 'lucide-svelte' import { page } from '$app/state' + import { isMenuHidden } from '$lib/components/sessions/sessionMode.svelte' let { workspace, @@ -53,6 +54,11 @@ const hideEditBtn = page.url.searchParams.get('hideEditBtn') === 'true' const hideRefreshBar = page.url.searchParams.get('hideRefreshBar') === 'true' + const showEdit = $derived(canWriteApp && !hideEditBtn) + // Decides where Edit goes, not whether it appears: without the workspace navigation there is + // no band to put it in. Same rule the layout hides the sidebar by. + const menuHidden = $derived(isMenuHidden(page.url)) + // Embedder side: mint a scoped embed token (by path) from the member's session. async function fetchEmbedToken(opts?: { sdkConsent?: boolean }): Promise<{ token?: string }> { const headers: Record = {} @@ -150,17 +156,23 @@ + no item, and the breadcrumb would fall back to the section name "Apps". --> + +{#if showEdit && menuHidden} +
+ {@render editAction()} +
+{/if} + {#snippet editAction()}