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(