Review round: pill/dashboard edge-case fixes

- Latch pop_dashboard_app_url while its history.back() is in flight: a
  double-click on minimize (or a close racing a minimize) issued two
  back()s, and the second popped the dashboard's own entry — navigating
  clean out of the page. Any popstate or push settles the latch.
- Close a running app's windows when it is uninstalled: the tile is a
  headless app's only switcher, so a minimized instance would otherwise
  be stranded — impossible to restore or quit.
- puter.ui.setWindowTitle in dashboard mode now also updates the
  browser-tab title (while that app owns the URL) and the window's
  data-name, so a later restore re-applies the app's current title
  instead of its launch-time one. Desktop behavior unchanged.
- Nudge collapsed-pill contrast (opacity 0.55 → 0.65, border alpha
  0.16 → 0.22) so the capsule stays findable over dark app content.
This commit is contained in:
jelveh
2026-07-22 13:42:32 -07:00
parent a96f074ac1
commit 4b7f6cf7a4
4 changed files with 34 additions and 2 deletions
+11
View File
@@ -618,6 +618,17 @@ const ipc_listener = async (event, handled) => {
// floating control pill 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);
// 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
// window's app actually owns the URL.
if ( window.is_dashboard_mode ) {
$(el_window).attr('data-name', event.data.new_title);
const app = $(el_window).attr('data-app');
if ( app && window.location.pathname === `/app/${encodeURIComponent(app)}` ) {
document.title = event.data.new_title;
}
}
// send confirmation to requester window
target_iframe.contentWindow.postMessage({
original_msg_id: msg_id,
+5
View File
@@ -182,6 +182,11 @@ function showUninstallModal ({ appName, appTitle, appUid, self, $el_window }) {
// restores its position if it comes back.
const removedIndex = self._apps.findIndex(a => a.name === appName);
const removedApp = removedIndex === -1 ? null : self._apps[removedIndex];
// A running instance would be stranded: the tile is a headless
// app's only switcher, so once it's gone a minimized window could
// never be restored OR quit. Close the app's windows first (close
// also consumes the app's URL entry if it owns one).
$(`.window[data-app="${html_encode(appName)}"]`).close();
self._invalidateInFlightLoads();
close();
+14
View File
@@ -4159,6 +4159,7 @@ function push_dashboard_app_url (app_name, title) {
}
window.history.pushState({ dashboard_app: app_name }, '', `/app/${encodeURIComponent(app_name)}`);
dashboard_url_app = app_name;
dashboard_url_pop_pending = false;
if ( title ) document.title = title;
}
@@ -4171,15 +4172,28 @@ function push_dashboard_app_url (app_name, title) {
* when this app doesn't own the URL (caller falls back to hiding
* directly, e.g. an app stacked under another app's entry).
*/
// True while a pop's history.back() is in flight (issued but its
// popstate not yet processed). The URL doesn't change until the popstate
// lands, so without this latch a double-click on minimize — or a close
// racing a minimize — would issue TWO back()s, and the second would pop
// the dashboard's own entry and navigate clean out of the page.
let dashboard_url_pop_pending = false;
function pop_dashboard_app_url (app_name) {
if ( ! window.is_dashboard_mode || ! app_name ) return false;
if ( dashboard_app_url_current() !== app_name ) return false;
// Duplicate request for an entry already being popped: report it
// handled so the caller doesn't ALSO hide the window.
if ( dashboard_url_pop_pending ) return true;
dashboard_url_pop_pending = true;
window.history.back();
return true;
}
window.addEventListener('popstate', () => {
if ( ! window.is_dashboard_mode ) return;
// Any traversal settles a pending pop (see pop_dashboard_app_url).
dashboard_url_pop_pending = false;
const new_app = dashboard_app_url_current();
const prev_app = dashboard_url_app;
// Same app on both sides means the traversal wasn't ours (e.g. an app
+4 -2
View File
@@ -4242,7 +4242,7 @@ body.dashboard-mode .window-dashboard-headless .window-body-app {
background: rgba(24, 24, 28, 0.62);
-webkit-backdrop-filter: blur(14px) saturate(140%);
backdrop-filter: blur(14px) saturate(140%);
border: 1px solid rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.22);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.28), 0 1px 3px rgba(0, 0, 0, 0.2);
color: #fff;
-webkit-user-select: none;
@@ -4276,7 +4276,9 @@ body.dashboard-mode .window-dashboard-headless .window-body-app {
}
.dashboard-app-pill.collapsed {
opacity: 0.55;
/* Subtle but findable over both light and dark app content the
white hairline border carries the dark-on-dark case. */
opacity: 0.65;
padding: 3px 12px;
gap: 0;
}