From aeac9f8fcd676c61d4df7d5f7a219d4df6b0f107 Mon Sep 17 00:00:00 2001 From: lovepixel-git Date: Wed, 5 Aug 2026 14:21:39 -0400 Subject: [PATCH 1/2] fix(editor): raise floating panels above the sidebars Opening the AI assistant while a docked properties panel was showing made it vanish: the floating shell sat at z-index 50, the sidebars sit at 85, so the sidebar won and the panel was unreachable without closing or undocking the sidebar first. The convention already existed. The shared FloatingWindow primitive is z-index 90, deliberately above the 85 sidebar tier, and floating windows built on it layer correctly. AgentPanel and PropertiesPanel each hand-roll their own floating shell rather than using that primitive, and both were left at 50, so neither followed it. Both move to 90. PropertiesPanel had the same bug unreported: undock it, open any docked sidebar panel, and it disappears the same way. Only the floating rules change. Both docked variants already reset z-index to auto, so docked layout is untouched. Nothing that must stay on top is affected: the code editor (95), the inspector's own StyleSurface (99), step-up and toolbar dialogs (200/201), the preview overlay (400/401), spotlight (9000) and tooltips (10001) all still win. Fixes #332 --- src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css | 5 ++++- .../site/panels/PropertiesPanel/PropertiesPanel.module.css | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css b/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css index 318423d45..3943e20d6 100644 --- a/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css +++ b/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css @@ -10,7 +10,10 @@ --panel-h: 480px; left: var(--panel-x); top: var(--panel-y); - z-index: 50; + /* Matches the shared FloatingWindow layer (90), which is the floating tier + above the sidebars (85). At 50 this sat *below* a docked sidebar panel and + became unreachable. The docked variant resets to auto below. */ + z-index: 90; width: var(--panel-w); height: var(--panel-h); min-width: 280px; diff --git a/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css b/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css index 5f846ed25..022ab086e 100644 --- a/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css +++ b/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css @@ -12,7 +12,10 @@ left: var(--panel-x); top: var(--panel-y); bottom: auto; - z-index: 50; + /* Matches the shared FloatingWindow layer (90), which is the floating tier + above the sidebars (85). At 50 this sat *below* a docked sidebar panel. + The docked variant resets to auto below. */ + z-index: 90; display: flex; flex-direction: column; width: var(--panel-w); From 2852cdd435d1a5d519b35ce0589b4918fddeab1b Mon Sep 17 00:00:00 2001 From: DavidBabinec Date: Sat, 8 Aug 2026 14:23:15 +0200 Subject: [PATCH 2/2] fix(editor): let all floating panels clear docked sidebars --- docs/editor.md | 13 ++++--- docs/reference/design-tokens.md | 14 +++++--- .../site-explorer/siteExplorerPanel.test.tsx | 34 +++++++++++++++++++ .../pages/site/canvas/CanvasRoot.module.css | 12 +++---- .../panels/AgentPanel/AgentPanel.module.css | 6 ++-- .../PropertiesPanel.module.css | 5 ++- .../LeftSidebar/LeftSidebar.module.css | 6 +++- 7 files changed, 65 insertions(+), 25 deletions(-) diff --git a/docs/editor.md b/docs/editor.md index f10e1d0bf..0f2177e20 100644 --- a/docs/editor.md +++ b/docs/editor.md @@ -439,7 +439,7 @@ Full details: [`docs/features/canvas-iframe-per-frame.md`](features/canvas-ifram `CanvasRoot` (`src/admin/pages/site/canvas/CanvasRoot.module.css`) sets `position: relative; z-index: 0`. This establishes an **isolating stacking context** for the entire canvas subtree. Every canvas-internal z-index value is confined inside that context and cannot compete with sibling layout elements. -Why this matters: selection rings and the floating selection toolbar are portaled into the canvas root and painted at z-index 51 (above the `PluginCanvasOverlayLayer` at 50). Without the `z-index: 0` stacking context on the canvas, those z-index 51 values escape into the shared layout context and paint over the floating `PropertiesPanel` (also z-index 50), which is a sibling of the canvas. With the isolation in place, the canvas as a whole occupies z-index 0 in the shared layout context — well below the panel's 50. +Why this matters: selection rings and the floating selection toolbar are portaled into the canvas root and painted at z-index 51 (above the `PluginCanvasOverlayLayer` at 50). Without the `z-index: 0` stacking context on the canvas, those values escape into the shared layout context instead of remaining a single isolated canvas layer. With the isolation in place, the canvas as a whole occupies z-index 0 in the shared layout context — well below the floating-panel tier at 90. **Editor layout z-index table** (shared context, outside the canvas): @@ -447,11 +447,9 @@ Why this matters: selection rings and the floating selection toolbar are portale |---------------------------------------|---------|------| | Canvas (CanvasRoot, isolation root) | 0 | `canvas/CanvasRoot.module.css` | | Toolbar (main bar) | 30 | `toolbar/Toolbar.module.css` | -| PropertiesPanel (floating) | 50 | `panels/PropertiesPanel/PropertiesPanel.module.css` | -| AgentPanel (floating) | 50 | `panels/AgentPanel/AgentPanel.module.css` | | PanelRail | 55 | `sidebars/PanelRail/PanelRail.module.css` | -| LeftSidebar, RightSidebar | 85 | `sidebars/{Left,Right}Sidebar/` | -| Undocked left-panel host | 90 | `sidebars/LeftSidebar/LeftSidebar.module.css` | +| Docked left-panel host, RightSidebar | 85 | `sidebars/{Left,Right}Sidebar/` | +| PropertiesPanel, AgentPanel, undocked left-panel host, shared floating windows | 90 | `panels/`, `sidebars/LeftSidebar/`, `shared/FloatingWindow/` | | CodeEditorPanel (floats over sidebars)| 95 | `code-editor/CodeEditorPanel.module.css` | | Toolbar popovers / dropdowns | 201 | `toolbar/Toolbar.module.css` | | PreviewOverlay | 400–401 | `preview/PreviewOverlay.module.css` | @@ -544,6 +542,11 @@ floating window in both axes (arrow keys resize by 10px; Shift+arrow by 40px). height are persisted through `siteEditorLayoutPersistence` / `workspaceLayoutStorage`. +The outer left-sidebar layout shell intentionally has no `z-index`, so it does +not trap floating descendants in a sidebar stacking context. Its docked panel +slot owns layer 85; the undocked slot and independent Agent panel participate +directly in the shared floating tier at 90. + The AI Assistant is an independent draggable and resizable floating window, so it can stay open beside Explorer/Layers or any other hosted panel. Its position, dimensions, and open state persist separately across reloads. Properties diff --git a/docs/reference/design-tokens.md b/docs/reference/design-tokens.md index 856b98e5a..a87948ed4 100644 --- a/docs/reference/design-tokens.md +++ b/docs/reference/design-tokens.md @@ -416,10 +416,10 @@ The visual editor uses additional raw z-index values that are **not** tokenised. |-------|-----------------| | 0 | `CanvasRoot` — an isolation root; all canvas-internal values are confined here | | 30 | Main toolbar | -| 50 | Floating panels: PropertiesPanel, AgentPanel, DomPanel | -| 55 | LeftSidebar, RightSidebar, PanelRail | -| 90 | Shared admin floating windows (`FloatingWindow`, `MediaViewerWindow`, agent image preview) | -| 80 | CodeEditorPanel | +| 55 | PanelRail | +| 85 | Docked left-panel host, RightSidebar | +| 90 | PropertiesPanel, AgentPanel, undocked left-panel host, shared admin floating windows | +| 95 | CodeEditorPanel | | 201 | Toolbar popovers / dropdowns | | 400–401 | PreviewOverlay | @@ -433,7 +433,11 @@ The visual editor uses additional raw z-index values that are **not** tokenised. | 60 | CanvasContextSelector | | 2147483647 | Drop-indicator layer inside iframe (must beat arbitrary module stacking contexts) | -`CanvasRoot` declares `z-index: 0; position: relative` to establish the isolation. Without it, the canvas-internal z-index 51 would escape into the layout context and paint over floating panels at z-index 50. See [`docs/editor.md`](../editor.md) → "Canvas stacking context isolation" for the full explanation. +`CanvasRoot` declares `z-index: 0; position: relative` to establish the isolation. Without it, canvas-internal values would escape into the shared layout context instead of remaining one canvas layer beneath the floating-panel tier at 90. See [`docs/editor.md`](../editor.md) → "Canvas stacking context isolation" for the full explanation. + +The left-sidebar layout shell deliberately does not create a stacking context. +Its docked panel slot owns layer 85, which lets the Agent panel and the shared +undocked panel host participate directly in the editor-wide layer 90. Raw canvas-internal values are intentional exceptions — they cannot be tokens because they are relative to an isolated stacking context, not the global one. Do not add new raw z-index values outside this established ladder. diff --git a/src/__tests__/site-explorer/siteExplorerPanel.test.tsx b/src/__tests__/site-explorer/siteExplorerPanel.test.tsx index 5a25d89fa..237c0cdef 100644 --- a/src/__tests__/site-explorer/siteExplorerPanel.test.tsx +++ b/src/__tests__/site-explorer/siteExplorerPanel.test.tsx @@ -878,6 +878,40 @@ describe('SiteExplorerPanel', () => { expect(panelZIndex).toBeGreaterThan(Math.max(...sidebarZIndexes)) }) + it('keeps every floating editor panel above the docked panel hosts', () => { + const leftSidebarCss = readFileSync( + new URL('../../admin/pages/site/sidebars/LeftSidebar/LeftSidebar.module.css', import.meta.url), + 'utf-8', + ) + const rightSidebarCss = readFileSync( + new URL('../../admin/pages/site/sidebars/RightSidebar/RightSidebar.module.css', import.meta.url), + 'utf-8', + ) + const agentPanelCss = readFileSync( + new URL('../../admin/pages/site/panels/AgentPanel/AgentPanel.module.css', import.meta.url), + 'utf-8', + ) + const propertiesPanelCss = readFileSync( + new URL('../../admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css', import.meta.url), + 'utf-8', + ) + + const sidebarRule = leftSidebarCss.match(/\.sidebar\s*\{[\s\S]*?\}/)?.[0] ?? '' + const dockedSlotRule = leftSidebarCss.match(/\.panelSlot\s*\{[\s\S]*?\}/)?.[0] ?? '' + const rightSidebarRule = rightSidebarCss.match(/\.sidebar\s*\{[\s\S]*?\}/)?.[0] ?? '' + const floatingSlotRule = leftSidebarCss.match(/\.panelSlotFloating\s*\{[\s\S]*?\}/)?.[0] ?? '' + const agentRule = agentPanelCss.match(/\.floatPanel\s*\{[\s\S]*?\}/)?.[0] ?? '' + const propertiesRule = propertiesPanelCss.match(/\.panel\s*\{[\s\S]*?\}/)?.[0] ?? '' + const dockedZIndexes = [dockedSlotRule, rightSidebarRule] + .map((rule) => Number(rule.match(/z-index:\s*(\d+)/)?.[1])) + const floatingZIndexes = [floatingSlotRule, agentRule, propertiesRule] + .map((rule) => Number(rule.match(/z-index:\s*(\d+)/)?.[1])) + + expect(sidebarRule).not.toContain('z-index:') + expect(dockedZIndexes).toEqual([85, 85]) + expect(floatingZIndexes.every((zIndex) => zIndex > Math.max(...dockedZIndexes))).toBe(true) + }) + it('opens pages and components on the canvas from concept rows', () => { loadSite() render() diff --git a/src/admin/pages/site/canvas/CanvasRoot.module.css b/src/admin/pages/site/canvas/CanvasRoot.module.css index d0d19c5d2..565c278de 100644 --- a/src/admin/pages/site/canvas/CanvasRoot.module.css +++ b/src/admin/pages/site/canvas/CanvasRoot.module.css @@ -6,14 +6,10 @@ .canvas { flex: 1; position: relative; - /* Establish a stacking context for the whole canvas subtree. The selection / - hover rings and toolbar are portaled into this element at z-index 51 (so - they paint above the in-canvas PluginCanvasOverlayLayer at 50). Without an - isolating context here, that 51 escapes into the shared layout stacking - context and paints over the floating PropertiesPanel (z-index 50), which - is a sibling of the canvas. Pinning the canvas to z-index 0 keeps the - rings' internal ordering intact while ensuring the entire canvas — rings - included — sits below floating overlay panels and sidebars. */ + /* Establish a stacking context for the whole canvas subtree. Canvas chrome + owns an internal z-index ladder for selection rings, mode controls, and + plugin overlays. Pinning the canvas to z-index 0 keeps that ordering + internal while ensuring the entire canvas sits below editor panels. */ z-index: 0; overflow: hidden; background: var(--bg-surface-2); diff --git a/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css b/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css index 3943e20d6..8bc45f60b 100644 --- a/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css +++ b/src/admin/pages/site/panels/AgentPanel/AgentPanel.module.css @@ -10,9 +10,9 @@ --panel-h: 480px; left: var(--panel-x); top: var(--panel-y); - /* Matches the shared FloatingWindow layer (90), which is the floating tier - above the sidebars (85). At 50 this sat *below* a docked sidebar panel and - became unreachable. The docked variant resets to auto below. */ + /* Shared floating tier above docked panel slots (85). The left sidebar + layout shell deliberately does not create a stacking context, so this + panel participates in the editor-wide layer. */ z-index: 90; width: var(--panel-w); height: var(--panel-h); diff --git a/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css b/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css index 022ab086e..7205a4959 100644 --- a/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css +++ b/src/admin/pages/site/panels/PropertiesPanel/PropertiesPanel.module.css @@ -12,9 +12,8 @@ left: var(--panel-x); top: var(--panel-y); bottom: auto; - /* Matches the shared FloatingWindow layer (90), which is the floating tier - above the sidebars (85). At 50 this sat *below* a docked sidebar panel. - The docked variant resets to auto below. */ + /* Shared floating tier above docked panel slots (85). The docked variant + resets to auto below. */ z-index: 90; display: flex; flex-direction: column; diff --git a/src/admin/pages/site/sidebars/LeftSidebar/LeftSidebar.module.css b/src/admin/pages/site/sidebars/LeftSidebar/LeftSidebar.module.css index dec266937..cb6593266 100644 --- a/src/admin/pages/site/sidebars/LeftSidebar/LeftSidebar.module.css +++ b/src/admin/pages/site/sidebars/LeftSidebar/LeftSidebar.module.css @@ -1,6 +1,9 @@ .sidebar { position: relative; - z-index: 85; + /* Keep the layout shell out of the stacking ladder. Floating descendants + (the Agent panel and an undocked panel slot) must participate in the + editor's shared z-index context instead of being trapped below the + later right sidebar. The docked panel owns layer 85 below. */ display: flex; --left-sidebar-rail-width: 42px; --left-sidebar-panel-width: 0px; @@ -26,6 +29,7 @@ .panelSlot { --panel-fade-bg: var(--bg-body); position: absolute; + z-index: 85; top: 0; bottom: 0; left: var(--left-sidebar-rail-width);