Fix mobile modal layering over canvas rail

Raise the shared modal stack above the mobile right-canvas rail so blocking modals remain authoritative on small screens. Compact the mobile rail below 420px to reduce intrusion on narrow phones, and document the stacking contract in the owning DOX files. Verification: pytest tests/test_browser_agent_regressions.py -q; pytest tests/test_office_canvas_setup.py -q; git diff --check; headless Chrome smoke confirmed modal content wins at the rail overlap point.

Alessandro committed Jun 23, 2026 at 16:41 UTC 8bcf1d3165d5d7f7422d1064945d27c13ee5409b
7 files changed +47 -3
tests/test_browser_agent_regressions.py
+18
@@ -898,6 +898,24 @@ def test_surface_buttons_keep_modal_and_canvas_entry_points_separate():
898 assert "&& !modalRequiresExplicitClose(newModal)" in modals_js
899 assert "if (modalRequiresExplicitClose(modalStack[modalStack.length - 1])) return;" in modals_js
900 assert ".modal-surface-switcher" not in modals_css
901 +
902 + modal_stack_z = int(re.search(r"const baseZIndex = (\d+);", modals_js).group(1))
903 + modal_css_z = int(re.search(r"\.modal \{[^}]*z-index: (\d+);", modals_css, re.S).group(1))
904 + legacy_overlay_z = int(re.search(r"\.modal-overlay \{[^}]*z-index: (\d+);", modals_css, re.S).group(1))
905 + mobile_canvas_z = int(
906 + re.search(
907 + r"body\.right-canvas-mobile-mode \.right-canvas \{[^}]*z-index: (\d+);",
908 + canvas_css,
909 + re.S,
910 + ).group(1)
911 + )
912 + assert mobile_canvas_z == 3400
913 + assert modal_stack_z > mobile_canvas_z
914 + assert modal_css_z > mobile_canvas_z
915 + assert legacy_overlay_z > mobile_canvas_z
916 + assert "@media (max-width: 420px)" in canvas_css
917 + assert "body.right-canvas-mobile-mode .right-canvas-rail {\n gap: 5px;" in canvas_css
918 + assert "transform: translateY(-50%) scale(0.92);" in canvas_css
919 assert ".surface-switcher" in surfaces_css
920 assert ".surface-button" in surfaces_css
921 assert ".modal-surface-button.is-active" in surfaces_css
webui/components/canvas/AGENTS.md
+1
@@ -15,6 +15,7 @@
15 - Keep registered surfaces compatible with WebUI extension hooks.
16 - Preserve responsive layout and avoid overlapping the chat/sidebar shells.
17 - Right-canvas rail and tab buttons are explicit canvas entry points above the mobile breakpoint; at mobile widths, keep the rail visible but route non-action surfaces into floating modals instead of the side canvas shell.
18 +- In mobile mode, keep the rail below blocking modal layers and compact it on very narrow screens instead of letting it cover modal content.
19
20 ## Work Guidance
21
webui/components/canvas/right-canvas.css
+23
@@ -354,6 +354,29 @@ body.right-canvas-mobile-mode .right-canvas-resize-handle {
354 display: none !important;
355 }
356
357 +@media (max-width: 420px) {
358 + body.right-canvas-mobile-mode .right-canvas-rail {
359 + gap: 5px;
360 + padding: 8px 6px;
361 + transform: translateY(-50%) scale(0.92);
362 + transform-origin: right center;
363 + box-shadow: -8px 0 24px rgba(0, 0, 0, 0.22);
364 + }
365 +
366 + body.right-canvas-mobile-mode .right-canvas-rail-button,
367 + body.right-canvas-mobile-mode .right-canvas-icon-button {
368 + width: 32px;
369 + height: 32px;
370 + min-width: 32px;
371 + min-height: 32px;
372 + }
373 +
374 + body.right-canvas-mobile-mode .right-canvas-rail-button .material-symbols-outlined,
375 + body.right-canvas-mobile-mode .right-canvas-icon-button .material-symbols-outlined {
376 + font-size: 18px;
377 + }
378 +}
379 +
380 @media (max-width: 480px) {
381 .right-canvas-tab-label {
382 display: none;
webui/css/AGENTS.md
+1
@@ -25,6 +25,7 @@
25 - Tall modal bodies must scroll inside `.modal-scroll`; pinned footer content must stay outside that scroll area.
26 - `.modal-floating` must keep the full-screen shell pointer-transparent while `.modal-inner` remains pointer-active.
27 - Use `.modal-no-backdrop` only for backdrop suppression without click-through floating behavior.
28 +- Shared modal layers must stay above the mobile right-canvas rail while confirmation dialogs remain above normal modals.
29 - Do not add decorative one-note palette changes that conflict with existing WebUI design.
30
31 ## Work Guidance
webui/css/modals.css
+2 -2
@@ -12,7 +12,7 @@ the old and the new system. */
12 left: 0;
13 width: 100%;
14 height: 100%;
15 - z-index: 2000;
15 + z-index: 5000;
16 }
17 .modal.show {
18 display: block;
@@ -49,7 +49,7 @@ the old and the new system. */
49 display: flex;
50 align-items: center;
51 justify-content: center;
52 - z-index: 2001;
52 + z-index: 5001;
53 }
54
55 /* Modal Backdrop */
webui/js/AGENTS.md
+1
@@ -47,6 +47,7 @@
47 - Check plugin extension callers before changing shared extension behavior.
48 - Preserve the single shared modal shell and backdrop model; do not add a parallel overlay implementation.
49 - Preserve modal z-index spacing with a stable base stack and a shared backdrop below the active modal.
50 +- Keep the shared modal stack above the mobile right-canvas rail so blocking modals remain authoritative on small screens.
51 - If opening a new modal from a close handler, schedule it with `requestAnimationFrame` to avoid stack removal races.
52 - Keep modal state cleanup explicit because stores can outlive their DOM.
53 - Device-specific styling may rely on the `device-touch` or `device-mouse` body class set during initialization.
webui/js/modals.js
+1 -1
@@ -107,7 +107,7 @@ document.body.appendChild(backdrop);
107 // Function to update z-index for all modals and backdrop
108 function updateModalZIndexes() {
109 // Base z-index for modals
110 - const baseZIndex = 3000;
110 + const baseZIndex = 5000;
111
112 // Update z-index for all modals
113 modalStack.forEach((modal, index) => {