From fbf1bd18b1b2ab7e13fd0bded498363ea9f92c5f Mon Sep 17 00:00:00 2001 From: jelveh Date: Wed, 29 Jul 2026 21:36:47 -0700 Subject: [PATCH] Dashboard: an app launched by another app takes over the tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In dashboard mode apps are full-tab experiences, but an app launched by another app (puter.ui.launchApp) opened as a floating titlebar window over its full-tab parent: the parent stayed on screen around its edges, couldn't be raised (focusWindow never raises stay_on_top windows), and a child with no Apps-tab tile had no switcher to come back to once minimized. Give child launches the same treatment as tile launches — maximized, so they get the headless chrome and control drawer — and minimize the parent behind them, iOS-style. State only: the parent's /app/ history entry already sits beneath the child's, so Back from the child (and the child's close, which consumes its own entry) lands on the parent's entry and the existing popstate handler restores it. The parent keeps running while hidden, so parent/child IPC is unaffected. Explorer keeps its windowed form, background apps don't minimize their parent, and an explicit `maximized` option still wins. Desktop mode is untouched: both changes are gated on is_dashboard_mode, so an app launching an app there still gets a floating child over a visible parent. --- src/gui/src/helpers/launch_app.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/gui/src/helpers/launch_app.js b/src/gui/src/helpers/launch_app.js index f14976f3e..412f437fa 100644 --- a/src/gui/src/helpers/launch_app.js +++ b/src/gui/src/helpers/launch_app.js @@ -271,6 +271,16 @@ const launch_app = async (options) => { if ( app_info.maximize_on_start ) { options.maximized = 1; } + // Dashboard mode: apps are full-tab experiences (headless chrome, + // control drawer, tile/Back switching), so every app launch defaults + // to maximized — matching tile launches. Without this, an app launched + // by another app (puter.ui.launchApp) opened as a floating titlebar + // window over the full-tab parent, with no tile to switch back to. + // Explorer keeps its windowed form; an explicit option still wins. + if ( window.is_dashboard_mode && options.maximized === undefined + && options.name !== 'explorer' && options.name !== 'trash' ) { + options.maximized = 1; + } //----------------------------------- // if opened a file, sign it //----------------------------------- @@ -701,6 +711,21 @@ const launch_app = async (options) => { const el = await el_win; process.references.el_win = el; + // Dashboard mode: an app launched from another app takes over the tab, + // iOS-style — the parent app minimizes behind it. State only: the + // parent's /app/ history entry already sits beneath the child's, + // so Back from the child (and the child's close, which consumes its + // entry) lands on the parent's entry and the popstate handler restores + // it. The parent keeps running while hidden — parent/child IPC works. + if ( window.is_dashboard_mode && options.parent_instance_id + && options.maximized && !app_info.background && el ) { + const el_parent_win = window.window_for_app_instance(options.parent_instance_id); + const parent_minimized = $(el_parent_win).attr('data-is_minimized'); + if ( el_parent_win && parent_minimized !== '1' && parent_minimized !== 'true' ) { + $(el_parent_win).hideWindow(); + } + } + if ( ! options.launched_by_exec_service ) { process.onchange('ipc_status', value => { if ( value !== PROCESS_IPC_ATTACHED ) return;