Stabilize Browser modal switching

Keep Browser modal activation passive when switching from Desktop by reusing existing Browser sessions instead of creating a blank tab on viewer subscribe. Add a Focus mode control to the Browser modal header matching Desktop's fullscreen/restore behavior. Cover the passive subscribe path and Browser modal focus button in regression tests.

Alessandro committed May 5, 2026 at 14:34 UTC f9175ed00bf3b259ad2e390c8d6061b7a094b8f9
9 files changed +234 -58
plugins/_browser/extensions/webui/right-canvas-panels/browser-panel.html
+5 -2
@@ -1,8 +1,11 @@
1 <div
2 class="right-canvas-surface-panel browser-canvas-surface"
3 data-surface-id="browser"
4 - x-show="$store.rightCanvas && $store.rightCanvas.isOpen && $store.rightCanvas.activeSurfaceId === 'browser'"
5 - style="display: none;"
4 + :class="{
5 + 'is-active': $store.rightCanvas?.isSurfaceVisible('browser'),
6 + 'is-mounted': $store.rightCanvas?.isSurfaceRendered('browser')
7 + }"
8 + :aria-hidden="(!$store.rightCanvas?.isSurfaceVisible('browser')).toString()"
9 >
10 <x-component path="/plugins/_browser/webui/browser-panel.html" mode="canvas"></x-component>
11 </div>
plugins/_office/extensions/webui/right-canvas-panels/office-panel.html
+9 -3
@@ -1,9 +1,15 @@
1 <div
2 class="right-canvas-surface-panel office-canvas-surface"
3 data-surface-id="office"
4 - x-show="$store.rightCanvas && $store.rightCanvas.isOpen && $store.rightCanvas.activeSurfaceId === 'office'"
5 - x-effect="(() => { const visible = Boolean($store.rightCanvas && $store.rightCanvas.isOpen && $store.rightCanvas.activeSurfaceId === 'office'); (globalThis.queueMicrotask || ((callback) => globalThis.setTimeout(callback, 0)))(() => $store.office?.setDesktopHostVisible?.(visible)); })()"
6 - style="display: none;"
4 + :class="{
5 + 'is-active': $store.rightCanvas?.isSurfaceVisible('office'),
6 + 'is-mounted': $store.rightCanvas?.isSurfaceRendered('office')
7 + }"
8 + :aria-hidden="(!$store.rightCanvas?.isSurfaceVisible('office')).toString()"
9 + x-effect="(() => {
10 + const visible = Boolean($store.rightCanvas?.isSurfaceRendered('office'));
11 + (globalThis.queueMicrotask || ((callback) => globalThis.setTimeout(callback, 0)))(() => $store.office?.setDesktopHostVisible?.(visible));
12 + })()"
13 >
14 <x-component path="/plugins/_office/webui/office-panel.html"></x-component>
15 </div>
plugins/_office/webui/office-store.js
+13 -5
@@ -229,6 +229,7 @@ const model = {
229 _desktopPrimeTimer: null,
230 _desktopPrimeAttempts: 0,
231 _desktopKeyboardActive: false,
232 + _desktopFocusInProgress: false,
233 _desktopBridgeReady: false,
234 _desktopKeyboardCaptureState: { ready: false, active: false, capture: false, focused: false },
235 _desktopLastState: null,
@@ -1011,8 +1012,8 @@ const model = {
1012
1013 isDesktopHostVisible() {
1014 if (this._mode === "modal") return true;
1014 - const canvas = globalThis.Alpine?.store?.("rightCanvas") || rightCanvasStore;
1015 - return Boolean(canvas?.isOpen && canvas.activeSurfaceId === "office");
1015 + const canvas = rightCanvasStore;
1016 + return Boolean(canvas?.isOpen && (canvas.isSurfaceMounted?.("office") ?? canvas.activeSurfaceId === "office"));
1017 },
1018
1019 setDesktopHostVisible(visible) {
@@ -1247,9 +1248,11 @@ const model = {
1248 },
1249
1250 focusDesktopFrame(frame = null, options = {}) {
1251 + if (this._desktopFocusInProgress) return false;
1252 const target = this.desktopFrame(frame);
1253 if (!target) return false;
1254 if (options.arm !== false) this._desktopKeyboardActive = true;
1255 + this._desktopFocusInProgress = true;
1256 try {
1257 target.setAttribute("tabindex", "0");
1258 target.focus?.({ preventScroll: true });
@@ -1261,6 +1264,8 @@ const model = {
1264 if (target.contentWindow?.client) target.contentWindow.client.capture_keyboard = true;
1265 } catch {
1266 target.focus?.({ preventScroll: true });
1267 + } finally {
1268 + this._desktopFocusInProgress = false;
1269 }
1270 const focused = Boolean(document.activeElement === target || target.contentDocument?.hasFocus?.());
1271 this.updateDesktopKeyboardCaptureState(target);
@@ -1908,7 +1913,10 @@ const model = {
1913 frame.setAttribute("tabindex", "0");
1914 if (remoteWindow.__a0XpraDesktopKeyboardBridgeInstalled) return;
1915
1911 - const activate = () => this.focusDesktopFrame(frame, { arm: true });
1916 + const activate = () => {
1917 + if (this._desktopFocusInProgress) return;
1918 + this.focusDesktopFrame(frame, { arm: true });
1919 + };
1920 const events = ["pointerdown", "mousedown", "touchstart", "focusin"];
1921 for (const eventName of events) {
1922 remoteDocument.addEventListener(eventName, activate, true);
@@ -2339,8 +2347,8 @@ const model = {
2347 focusButton.className = "modal-dock-button office-modal-focus-button";
2348 focusButton.innerHTML = '<span class="material-symbols-outlined" aria-hidden="true">fullscreen</span>';
2349 const updateFocusButton = (active) => {
2342 - focusButton.title = active ? "Restore size" : "Focus mode";
2343 - focusButton.setAttribute("aria-label", focusButton.title);
2350 + const label = active ? "Restore size" : "Focus mode";
2351 + focusButton.setAttribute("aria-label", label);
2352 focusButton.querySelector(".material-symbols-outlined").textContent = active ? "fullscreen_exit" : "fullscreen";
2353 };
2354 updateFocusButton(false);
tests/test_browser_agent_regressions.py
+17 -1
@@ -671,10 +671,18 @@ def test_browser_and_desktop_surface_buttons_remember_latest_window_mode():
671 )
672 modals_js = (PROJECT_ROOT / "webui" / "js" / "modals.js").read_text(encoding="utf-8")
673 modals_css = (PROJECT_ROOT / "webui" / "css" / "modals.css").read_text(encoding="utf-8")
674 + surface_button_block = modals_js[
675 + modals_js.index("function createModalSurfaceButton"):
676 + modals_js.index("function configureModalSurfaceSwitcher")
677 + ]
678
679 assert "surfaceModes: {}" in canvas_store
680 + assert "mountedSurfaces: {}" in canvas_store
681 assert "recordSurfaceMode(surfaceId" in canvas_store
682 assert "latestSurfaceMode(surfaceId)" in canvas_store
683 + assert "markSurfaceMounted(targetId)" in canvas_store
684 + assert "isSurfaceRendered(id)" in canvas_store
685 + assert "isSurfaceVisible(id)" in canvas_store
686 assert "async openLatest(surfaceId" in canvas_store
687 assert "async openModalSurface(surfaceId" in canvas_store
688 assert "this.recordSurfaceMode(targetId, SURFACE_MODE_CANVAS" in canvas_store
@@ -689,9 +697,16 @@ def test_browser_and_desktop_surface_buttons_remember_latest_window_mode():
697 assert "configureModalSurfaceSwitcher" in modals_js
698 assert "modal-surface-switcher" in modals_js
699 assert "modal-surface-button" in modals_js
700 + assert "SINGLE_VISIBLE_MODAL_SURFACE_PATHS" in modals_js
701 + assert "modal-surface-parked" in modals_js
702 + assert "parkSiblingSurfaceModals(activeModal)" in modals_js
703 + assert "activateModal(modal)" in modals_js
704 + assert "button.title = title" not in modals_js
705 + assert "button.title = metadata.title" not in modals_js
706 assert "rightCanvasStore.panelSurfaces" in modals_js
707 assert 'rightCanvasStore.recordSurfaceMode?.(surface.id, "modal")' in modals_js
694 - assert "await closeModal(modal.path)" in modals_js
708 + assert "const openPromise = ensureModalOpen(targetModalPath)" in surface_button_block
709 + assert "await closeModal(modal.path)" not in surface_button_block
710 assert "modalRequiresExplicitClose" in modals_js
711 assert '"plugins/_browser/webui/main.html"' in modals_js
712 assert '"plugins/_office/webui/main.html"' in modals_js
@@ -700,6 +715,7 @@ def test_browser_and_desktop_surface_buttons_remember_latest_window_mode():
715 assert ".modal-surface-switcher" in modals_css
716 assert ".modal-surface-button.is-active" in modals_css
717 assert ".modal-surface-image" in modals_css
718 + assert ".modal.modal-surface-parked" in modals_css
719 assert "grid-auto-flow: column" in modals_css
720
721
tests/test_office_canvas_setup.py
+6
@@ -59,6 +59,9 @@ def test_document_canvas_uses_markdown_editor_and_official_libreoffice_desktop_f
59 assert "clearDesktopViewportSyncTimers" in store
60 assert "setDesktopHostVisible" in canvas_panel
61 assert "queueMicrotask" in canvas_panel
62 + assert "isSurfaceRendered('office')" in canvas_panel
63 + assert "isSurfaceVisible('office')" in canvas_panel
64 + assert "canvas.isSurfaceMounted?.(\"office\")" in store
65 assert "Starting Agent Zero Desktop environment" in store
66 assert "handleOfficialOfficeClosed" in store
67 assert "ResizeObserver" in store
@@ -100,6 +103,8 @@ def test_document_canvas_uses_markdown_editor_and_official_libreoffice_desktop_f
103 assert "_desktopKeyboardCaptureState" in store
104 assert "installXpraDesktopKeyboardBridge" in store
105 assert "focusDesktopFrame" in store
106 + assert "_desktopFocusInProgress" in store
107 + assert "if (this._desktopFocusInProgress) return" in store
108 assert "_desktopKeyboardActive" in store
109 assert "isEditableInputTarget" in store
110 assert "reloadDesktopFrame" in store
@@ -127,6 +132,7 @@ def test_document_canvas_uses_markdown_editor_and_official_libreoffice_desktop_f
132 assert "__a0AllowScreenResize" in store
133 assert "_desktopHeartbeatTimer" in store
134 assert "office-modal-focus-button" in store
135 + assert "focusButton.title" not in store
136 assert "officialOfficeUrl" in store
137 assert 'parsed.searchParams.set("offscreen", secureContext ? "true" : "false")' in store
138 assert 'parsed.searchParams.set("clipboard_poll", secureContext ? "true" : "false")' in store
webui/components/canvas/right-canvas-store.js
+75 -17
@@ -31,6 +31,7 @@ const model = {
31 surfaces: [],
32 activeSurfaceId: "",
33 surfaceModes: {},
34 + mountedSurfaces: {},
35 isOpen: false,
36 width: DEFAULT_WIDTH,
37 isOverlayMode: false,
@@ -130,6 +131,7 @@ const model = {
131 }
132
133 this.activeSurfaceId = targetId;
134 + this.markSurfaceMounted(targetId);
135 this.isOpen = true;
136 this.recordSurfaceMode(targetId, SURFACE_MODE_CANVAS, { persist: false });
137 this._lastPayloadBySurface[targetId] = payload || {};
@@ -144,6 +146,41 @@ const model = {
146 return true;
147 },
148
149 + markSurfaceMounted(surfaceId) {
150 + const targetId = String(surfaceId || "").trim();
151 + if (!targetId) return;
152 + this.mountedSurfaces = {
153 + ...this.mountedSurfaces,
154 + [targetId]: true,
155 + };
156 + },
157 +
158 + markSurfaceUnmounted(surfaceId) {
159 + const targetId = String(surfaceId || "").trim();
160 + if (!targetId || !this.mountedSurfaces[targetId]) return;
161 + const next = { ...this.mountedSurfaces };
162 + delete next[targetId];
163 + this.mountedSurfaces = next;
164 + },
165 +
166 + mountedSurfaceIds() {
167 + return Object.entries(this.mountedSurfaces)
168 + .filter(([, mounted]) => mounted)
169 + .map(([surfaceId]) => surfaceId);
170 + },
171 +
172 + isSurfaceMounted(id) {
173 + return Boolean(this.mountedSurfaces[String(id || "").trim()]);
174 + },
175 +
176 + isSurfaceRendered(id) {
177 + return Boolean(this.isOpen && this.isSurfaceMounted(id));
178 + },
179 +
180 + isSurfaceVisible(id) {
181 + return Boolean(this.isOpen && this.activeSurfaceId === id && this.isSurfaceMounted(id));
182 + },
183 +
184 recordSurfaceMode(surfaceId, mode = SURFACE_MODE_CANVAS, options = {}) {
185 const targetId = String(surfaceId || "").trim();
186 if (!targetId) return;
@@ -169,14 +206,19 @@ const model = {
206 },
207
208 async close() {
172 - const surface = this.currentSurface();
209 + const mountedIds = this.mountedSurfaceIds();
210 this.isOpen = false;
211 + this.mountedSurfaces = {};
212 this.persist();
213 this.applyLayoutState();
176 - try {
177 - await surface?.close?.(this._lastPayloadBySurface[this.activeSurfaceId] || {});
178 - } catch (error) {
179 - console.error(`Canvas surface ${this.activeSurfaceId} failed to close`, error);
214 +
215 + for (const surfaceId of mountedIds) {
216 + const surface = this.getSurface(surfaceId);
217 + try {
218 + await surface?.close?.(this._lastPayloadBySurface[surfaceId] || {});
219 + } catch (error) {
220 + console.error(`Canvas surface ${surfaceId} failed to close`, error);
221 + }
222 }
223 },
224
@@ -237,13 +279,19 @@ const model = {
279 const openModal = globalThis.ensureModalOpen || globalThis.openModal;
280 if (!openModal) return false;
281 if (this.activeSurfaceId === targetId) {
282 + const mountedIds = this.mountedSurfaceIds();
283 this.isOpen = false;
284 + this.mountedSurfaces = {};
285 this.persist();
286 this.applyLayoutState();
243 - try {
244 - await surface.close?.(this._lastPayloadBySurface[targetId] || {});
245 - } catch (error) {
246 - console.error(`Canvas surface ${targetId} failed to close while undocking`, error);
287 +
288 + for (const mountedId of mountedIds) {
289 + const mountedSurface = this.getSurface(mountedId);
290 + try {
291 + await mountedSurface?.close?.(this._lastPayloadBySurface[mountedId] || {});
292 + } catch (error) {
293 + console.error(`Canvas surface ${mountedId} failed to close while undocking`, error);
294 + }
295 }
296 }
297 this.recordSurfaceMode(targetId, SURFACE_MODE_MODAL);
@@ -263,13 +311,19 @@ const model = {
311 if (!openModal) return false;
312
313 if (this.isOpen && this.activeSurfaceId === targetId) {
314 + const mountedIds = this.mountedSurfaceIds();
315 this.isOpen = false;
316 + this.mountedSurfaces = {};
317 this.persist();
318 this.applyLayoutState();
269 - try {
270 - await surface.close?.(this._lastPayloadBySurface[targetId] || {});
271 - } catch (error) {
272 - console.error(`Canvas surface ${targetId} failed to close before modal open`, error);
319 +
320 + for (const mountedId of mountedIds) {
321 + const mountedSurface = this.getSurface(mountedId);
322 + try {
323 + await mountedSurface?.close?.(this._lastPayloadBySurface[mountedId] || {});
324 + } catch (error) {
325 + console.error(`Canvas surface ${mountedId} failed to close before modal open`, error);
326 + }
327 }
328 }
329
@@ -413,12 +467,16 @@ const model = {
467 this.isMobileMode = width <= MOBILE_BREAKPOINT;
468 if (this.isMobileMode) {
469 const wasOpen = this.isOpen;
416 - const surface = wasOpen ? this.currentSurface() : null;
417 - const payload = this._lastPayloadBySurface[this.activeSurfaceId] || {};
470 + const mountedIds = this.mountedSurfaceIds();
471 this.isOpen = false;
419 - if (surface && wasOpen) {
472 + this.mountedSurfaces = {};
473 + if ((wasOpen || mountedIds.length > 0) && mountedIds.length > 0) {
474 globalThis.setTimeout?.(() => {
421 - surface.close?.({ ...payload, reason: "mobile" });
475 + for (const surfaceId of mountedIds) {
476 + const surface = this.getSurface(surfaceId);
477 + const payload = this._lastPayloadBySurface[surfaceId] || {};
478 + surface?.close?.({ ...payload, reason: "mobile" });
479 + }
480 }, 0);
481 }
482 } else if (wasMobileMode && this.width < MIN_WIDTH) {
webui/components/canvas/right-canvas.css
+13 -1
@@ -239,12 +239,24 @@ body.right-canvas-resizing {
239 }
240
241 .right-canvas-surface-panel {
242 + position: absolute;
243 + inset: 0;
244 display: flex;
243 - flex: 1 1 auto;
245 + flex: 0 0 auto;
246 width: 100%;
247 + height: 100%;
248 min-width: 0;
249 min-height: 0;
250 overflow: hidden;
251 + opacity: 0;
252 + pointer-events: none;
253 + z-index: 0;
254 +}
255 +
256 +.right-canvas-surface-panel.is-active {
257 + opacity: 1;
258 + pointer-events: auto;
259 + z-index: 2;
260 }
261
262 .right-canvas-surface-panel > x-component,
webui/css/modals.css
+14 -2
@@ -18,6 +18,16 @@ the old and the new system. */
18 display: block;
19 }
20
21 +.modal.modal-surface-parked {
22 + display: block;
23 + opacity: 0;
24 + pointer-events: none;
25 +}
26 +
27 +.modal.modal-surface-parked .modal-inner {
28 + pointer-events: none;
29 +}
30 +
31 .modal.modal-floating {
32 pointer-events: none;
33 }
@@ -153,9 +163,11 @@ the old and the new system. */
163 }
164
165 .modal-surface-switcher {
156 - display: inline-flex;
166 + display: grid;
167 + grid-auto-flow: column;
168 + grid-auto-columns: 34px;
169 align-items: center;
158 - gap: 4px;
170 + gap: 5px;
171 }
172
173 .modal-dock-button,
webui/js/modals.js
+82 -27
@@ -9,6 +9,25 @@ const EXPLICIT_CLOSE_MODAL_PATHS = new Set([
9 "plugins/_browser/webui/main.html",
10 "plugins/_office/webui/main.html",
11 ]);
12 +const SINGLE_VISIBLE_MODAL_SURFACE_PATHS = new Set([
13 + "plugins/_browser/webui/main.html",
14 + "plugins/_office/webui/main.html",
15 +]);
16 +const CANVAS_SURFACE_MODAL_GROUP = "canvas-surfaces";
17 +const DEFAULT_MODAL_SURFACES = [
18 + {
19 + id: "browser",
20 + title: "Browser",
21 + icon: "language",
22 + modalPath: "/plugins/_browser/webui/main.html",
23 + },
24 + {
25 + id: "office",
26 + title: "Desktop",
27 + icon: "desktop_windows",
28 + modalPath: "/plugins/_office/webui/main.html",
29 + },
30 +];
31
32 function normalizeModalPath(modalPath = "") {
33 return String(modalPath || "").replace(/^\/+/, "");
@@ -26,6 +45,44 @@ function modalRequiresExplicitClose(modalOrElement) {
45 || element?.querySelector?.(".modal-inner")?.classList?.contains("modal-explicit-close");
46 }
47
48 +function modalSurfaceGroup(modalOrElement) {
49 + const element = modalOrElement?.element || modalOrElement;
50 + const path = normalizeModalPath(modalOrElement?.path || element?.path || "");
51 + return SINGLE_VISIBLE_MODAL_SURFACE_PATHS.has(path) ? CANVAS_SURFACE_MODAL_GROUP : "";
52 +}
53 +
54 +function setModalParked(modal, parked = false) {
55 + const element = modal?.element;
56 + if (!element) return;
57 + element.classList.toggle("modal-surface-parked", parked);
58 + if (parked) {
59 + element.classList.remove("show");
60 + element.setAttribute("aria-hidden", "true");
61 + } else {
62 + element.classList.add("show");
63 + element.removeAttribute("aria-hidden");
64 + }
65 +}
66 +
67 +function parkSiblingSurfaceModals(activeModal) {
68 + const group = modalSurfaceGroup(activeModal);
69 + if (!group) {
70 + setModalParked(activeModal, false);
71 + return;
72 + }
73 +
74 + for (const modal of modalStack) {
75 + setModalParked(modal, modal !== activeModal && modalSurfaceGroup(modal) === group);
76 + }
77 +}
78 +
79 +function activateModal(modal) {
80 + if (!modal) return;
81 + parkSiblingSurfaceModals(modal);
82 + updateModalZIndexes();
83 + restoreModalScrollSnapshot(modal);
84 +}
85 +
86 function findModalIndexByPath(modalPath) {
87 return modalStack.findIndex((modal) => sameModalPath(modal.path, modalPath));
88 }
@@ -33,10 +90,13 @@ function findModalIndexByPath(modalPath) {
90 function focusModal(modalPath) {
91 const modalIndex = findModalIndexByPath(modalPath);
92 if (modalIndex === -1) return false;
36 - if (modalIndex === modalStack.length - 1) return true;
93 + const currentTopModal = modalStack[modalStack.length - 1];
94 + if (currentTopModal) {
95 + currentTopModal.savedScrollSnapshot = captureModalScrollSnapshot(currentTopModal);
96 + }
97 const [modal] = modalStack.splice(modalIndex, 1);
98 modalStack.push(modal);
39 - updateModalZIndexes();
99 + activateModal(modal);
100 return true;
101 }
102
@@ -206,29 +266,31 @@ function getDockMetadata(doc, modalPath) {
266 }
267
268 function getModalSwitchSurfaces(metadata) {
209 - if (!metadata) return [];
269 + const surfacesById = new Map(DEFAULT_MODAL_SURFACES.map((surface) => [surface.id, surface]));
270 const surfaces = Array.isArray(rightCanvasStore.panelSurfaces)
271 ? rightCanvasStore.panelSurfaces
272 : [];
213 - const modalSurfaces = surfaces.filter((surface) => (
214 - surface?.id
215 - && surface?.modalPath
216 - && !surface.actionOnly
217 - ));
218 -
219 - if (modalSurfaces.some((surface) => surface.id === metadata.surfaceId)) {
220 - return modalSurfaces;
273 +
274 + for (const surface of surfaces) {
275 + if (!surface?.id || !surface.modalPath || surface.actionOnly) continue;
276 + surfacesById.set(surface.id, {
277 + ...surface,
278 + modalPath: surface.modalPath,
279 + });
280 }
281
223 - return [
224 - {
282 + if (metadata?.surfaceId && !surfacesById.has(metadata.surfaceId)) {
283 + surfacesById.set(metadata.surfaceId, {
284 id: metadata.surfaceId,
285 title: metadata.title,
286 icon: metadata.icon,
287 modalPath: metadata.modalPath,
229 - },
230 - ...modalSurfaces,
231 - ];
288 + });
289 + }
290 +
291 + return Array.from(surfacesById.values())
292 + .filter((surface) => surface?.id && surface.modalPath && !surface.actionOnly)
293 + .sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
294 }
295
296 function createModalSurfaceButton(surface, metadata, modal) {
@@ -239,7 +301,6 @@ function createModalSurfaceButton(surface, metadata, modal) {
301 button.type = "button";
302 button.className = "modal-surface-button";
303 button.dataset.canvasSurface = surface.id;
242 - button.title = title;
304 button.setAttribute("aria-label", title);
305 button.setAttribute("aria-pressed", isActive.toString());
306 if (isActive) button.classList.add("is-active");
@@ -259,7 +320,7 @@ function createModalSurfaceButton(surface, metadata, modal) {
320 button.appendChild(icon);
321 }
322
262 - button.addEventListener("click", async () => {
323 + button.addEventListener("click", () => {
324 if (button.disabled || isActive || !targetModalPath) return;
325 button.disabled = true;
326 try {
@@ -268,7 +329,6 @@ function createModalSurfaceButton(surface, metadata, modal) {
329 if (openPromise?.catch) {
330 openPromise.catch((error) => console.error(`Modal surface ${surface.id} failed to open`, error));
331 }
271 - await closeModal(modal.path);
332 } finally {
333 if (document.contains(button)) button.disabled = false;
334 }
@@ -310,7 +370,6 @@ function configureModalDockButton(modal, doc) {
370 const button = document.createElement("button");
371 button.type = "button";
372 button.className = "modal-dock-button";
313 - button.title = metadata.title;
373 button.setAttribute("aria-label", metadata.title);
374 button.innerHTML = `<span class="material-symbols-outlined" aria-hidden="true">${metadata.icon}</span>`;
375 button.addEventListener("click", async () => {
@@ -409,11 +468,9 @@ export async function openModal(modalPath, beforeClose = null) {
468 // Add modal to stack
469 modal.path = modalPath;
470 modalStack.push(modal);
412 - modal.element.classList.add("show");
471 document.body.style.overflow = "hidden";
472
415 - // Update modal z-indexes
416 - updateModalZIndexes();
473 + activateModal(modal);
474 } catch (error) {
475 console.error("Error loading modal content:", error);
476 resolve();
@@ -450,7 +507,7 @@ export async function closeModal(modalPath = null) {
507
508 if (modalPath) {
509 // Find the modal with the specified name in the stack
453 - modalIndex = modalStack.findIndex((modal) => modal.path === modalPath);
510 + modalIndex = findModalIndexByPath(modalPath);
511 if (modalIndex === -1) return; // Modal not found in stack
512
513 // Get the modal from stack at the found index
@@ -529,9 +586,7 @@ export async function closeModal(modalPath = null) {
586 backdrop.style.display = "none";
587 document.body.style.overflow = "";
588 } else {
532 - // Update modal z-indexes
533 - updateModalZIndexes();
534 - restoreModalScrollSnapshot(modalStack[modalStack.length - 1]);
589 + activateModal(modalStack[modalStack.length - 1]);
590 }
591
592 document.dispatchEvent(