diff --git a/src/gui/src/IPC.js b/src/gui/src/IPC.js
index 4a7dce974..fd0fff1aa 100644
--- a/src/gui/src/IPC.js
+++ b/src/gui/src/IPC.js
@@ -615,9 +615,9 @@ const ipc_listener = async (event, handled) => {
}
// set window title (headless dashboard windows show it in the
- // floating control pill instead of a head)
+ // control drawer's tray instead of a head)
$(el_window).find('.window-head-title').html(html_encode(event.data.new_title));
- $(el_window).find('.dashboard-app-pill-title').text(event.data.new_title);
+ $(el_window).find('.dashboard-app-drawer-title').text(event.data.new_title);
// In dashboard mode the app's title is also the browser-tab title,
// and data-name is what a restore re-applies (see showWindow's
// push) — keep both fresh. The tab title only changes while this
diff --git a/src/gui/src/UI/UIWindow.js b/src/gui/src/UI/UIWindow.js
index 6573b7df5..47eec92f6 100644
--- a/src/gui/src/UI/UIWindow.js
+++ b/src/gui/src/UI/UIWindow.js
@@ -238,8 +238,8 @@ async function UIWindow (options) {
// --------------------------------------------------------
// In dashboard mode a maximized app window renders HEADLESS — the app
// covers the full tab, and the titlebar's controls live elsewhere: Back
- // minimizes (URL ownership), the floating pill overlay carries
- // minimize/close (attach_dashboard_app_pill), and the app's tile shows
+ // minimizes (URL ownership), the top-edge control drawer carries
+ // minimize/close (attach_dashboard_app_drawer), and the app's tile shows
// a running dot with a Quit item. Everything else keeps its head:
// dialogs and the dashboard window itself (update_window_url is false
// for both), explorer, and non-maximized windows (apps launching child
@@ -689,10 +689,10 @@ async function UIWindow (options) {
push_dashboard_app_url(options.app, options.title);
}
- // Headless dashboard app windows get the floating control pill in
+ // Headless dashboard app windows get the top-edge control drawer in
// place of the head (see the Dashboard app chrome section above).
if ( is_dashboard_app_chrome ) {
- attach_dashboard_app_pill(el_window, options);
+ attach_dashboard_app_drawer(el_window, options);
}
// Let the Apps tab refresh its tiles' running dots (see TabApps).
@@ -3904,6 +3904,16 @@ $.fn.showWindow = async function (options) {
// show window
const el_window = this;
+ // Stay-on-top windows (every fullpage/dashboard app window) are
+ // CREATED in the 99999999+ z band; restore must re-raise them
+ // into that same band. A plain counter z would demote the window
+ // below whatever gets focused next — and focusWindow deliberately
+ // never raises stay_on_top windows, so the demotion would stick
+ // (a dashboard-mode app would sit buried under the dashboard).
+ const raised_zindex = () => ($(el_window).attr('data-stay_on_top') === 'true'
+ ? 99999999 + (++window.last_window_zindex)
+ : ++window.last_window_zindex);
+
// A window minimized with no taskbar to animate toward (dashboard
// mode) was hidden in place by hideWindow — its geometry was
// never disturbed. If the app's tile is visible on the Apps tab's
@@ -3917,7 +3927,7 @@ $.fn.showWindow = async function (options) {
'data-is_minimized': false,
'data-minimized_in_place': '0',
});
- $(el_window).css('z-index', ++window.last_window_zindex);
+ $(el_window).css('z-index', raised_zindex());
const reduce_motion = window.matchMedia
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Skip the morph while another morph still owns the window's
@@ -3946,9 +3956,9 @@ $.fn.showWindow = async function (options) {
if ( ! morphed && was_hidden ) $(el_window).hide();
}
if ( ! morphed ) $(el_window).fadeIn(150);
- // Re-introduce the control pill on restore (headless
+ // Re-introduce the control drawer on restore (headless
// dashboard windows only; no-op otherwise).
- el_window._dashboard_pill_flash?.();
+ el_window._dashboard_drawer_flash?.();
// A restore re-claims the URL for this app — except when
// the restore was DRIVEN by a history traversal (popstate
// passes no_history: the entry is already current).
@@ -3970,7 +3980,7 @@ $.fn.showWindow = async function (options) {
width: `${$(el_window).attr('data-orig-width') }px`,
height: `${$(el_window).attr('data-orig-height') }px`,
});
- $(el_window).css('z-index', ++window.last_window_zindex);
+ $(el_window).css('z-index', raised_zindex());
$(el_window).attr({
'data-is_minimized': false,
@@ -4246,85 +4256,110 @@ window.addEventListener('popstate', () => {
});
/**
- * The floating control pill for headless dashboard app windows: a small
- * translucent capsule top-center of the app (parent DOM, above the app's
- * iframe) carrying the head's surviving controls — app identity, minimize,
- * and close. It opens expanded so first-time and deep-link users see it,
- * then collapses to a subtle handle; hovering, tapping, or keyboard-focusing
- * it expands it again. Timers rather than hover alone drive the collapse
- * because mousemove inside the app's iframe is invisible to the parent —
- * the pill itself is the only hover surface there is.
+ * The control drawer for headless dashboard app windows: a slim glass tray
+ * flush with the top edge of the app (parent DOM, above the app's iframe)
+ * carrying the head's surviving controls — app identity, minimize, and
+ * close — with a tongue-shaped handle hanging from its bottom center like a
+ * drawer pull. It opens expanded so first-time and deep-link users see the
+ * controls, then the tray retracts off the top edge leaving only the tongue
+ * peeking in; hovering the tongue (mouse), tapping it, or keyboard-focusing
+ * the drawer pulls it back down. Timers rather than hover alone drive the
+ * collapse because mousemove inside the app's iframe is invisible to the
+ * parent — the drawer itself is the only hover surface there is.
*
- * The pill is a CHILD of the window element, so it shows/hides/scales with
+ * The drawer is a CHILD of the window element, so it shows/hides/scales with
* the window for free (minimize morphs, display:none, fullscreen requests
* from the iframe hide it via the browser's own fullscreen stacking).
*/
-function attach_dashboard_app_pill (el_window, options) {
+function attach_dashboard_app_drawer (el_window, options) {
const app_name = options.app;
const icon = options.icon || window.icons['app.svg'];
const title = options.title || app_name;
- const $pill = $(`
-