feat(desktop): first-class Linux window chrome, menu, quit and packaging

Merged
SA
2mo ago
+549-248

Why

On Linux the desktop app was unusable as a window: frame: false + titleBarStyle: "hidden" with macOS traffic-light positions on every platform meant no minimize/maximize/close anywhere (the sign-in screen had no way out), the hidden title bar also hid the menu bar, closing the last window left an invisible process, and the packaged binary was named @supersetdesktop with a desktop entry whose window class never matched and a window with no icon.

Verified on a cloud sandbox desktop (Debian 12, Xfce on Xvnc) running the AppImage built there with the full prebuild, driven over VNC screenshots, xdotool and CDP. Checklist with before/after evidence per row: docs/linux-desktop-acceptance.md — every row ticked, including the second-pass rows.

What changes

  • Window controls: Windows and Linux use Electron's window-controls overlay — native minimize/maximize/close on every screen, coloured from the app theme; every header row that sits under it (top bar, workspace tab bar, workspaces board, tasks, pull requests, task detail, new workspace) ends with an inset sized from the titlebar-area CSS variables. The React WindowControls and its procedures are gone.
  • App menu: an ≡ button at the start of the top strip on Windows/Linux pops the application menu (File/Edit/View/Window/Resources/Help); File carries Settings…, Check for Updates…, Quit and Quit Superset Completely where there is no application menu.
  • Quit semantics: the last window's close quits through the confirmation and stays when cancelled; with no window left the app quits without a second prompt; one confirmation at a time. Terminal host and pty-daemon deliberately outlive a plain quit, as on macOS.
  • Packaging: executableName: "superset", StartupWMClass=superset, Linux windows carry the build's icon.
  • host-service on Linux — inotify deadlock: when the user's inotify instance limit is reached (fs.inotify.max_user_instances, 128 by default, shared by every process — a few dev servers and an editor get there), @parcel/watcher's backend thread throws before signalling it started and the caller blocks forever. That caller is host-service's event loop: health stops answering and every workspace shows "Connecting… restart the host service from the tray menu". A throwaway fs.watch probe before the native subscribe makes the same inotify_init call and fails cleanly, so git and file views keep working without live updates. Native stack, reproduction and after-state are in checklist row 2.3.
  • git credential fill now runs with GIT_TERMINAL_PROMPT=0 like the git commands themselves: launched from a terminal with no credential helper it otherwise prompts on the tty until its timeout.

Verified on the packaged build

  • 1.1 window controls on every route; 1.2 strip drag and the overlay inset on hidden-TopBar views; 1.3 maximize/restore from the overlay and by double-click, bounds restored after quit and relaunch; 1.4 app menu; 1.5 close/quit.
  • 2.1 host-service start/stop; 2.2/2.3/2.5 via the cloud sandbox acceptance plus the inotify case above; 2.4 the app's Notification draws the desktop bubble.
  • 3.1 Help → Documentation opens the system browser; 3.2 downloads land in ~/Downloads; 3.3 clipboard; 3.4 xdg-open superset://tasks reaches the running app through the desktop entry's scheme handler and switches it to Tasks; 3.5 diffs render in the Changes pane with no worker errors (the dev-server Worker error lines are dev-only); 3.6 the packaged renderer never hits the dev-only request budget.
  • 4.1 AppImage build; 4.2 desktop entry, window class and dock icon; 4.3 bundled skill extras — root cause is fs.cp on asar directories (not Linux-specific), fixed in #7429 and proven on a rebuild with it applied.
  • 5.1 every darwin branch in the desktop main process and host-service is in the audit table with its Linux counterpart or deliberate no-op.

Wider sweep (second pass)

  • Every screen sampled for elements under the overlay (board, tasks, pull requests, pages, automations, search, settings, command palette, new workspace, workspace view with terminal / files / chat / browser panes, create-organization): two hits, the side panel's Set Run cluster and the create-organization screen's only exit (its Sign Out / Cancel button, which made that screen a dead end on Linux). Both fixed by moving the inset to whichever strip is rightmost; verified on a rebuilt package (checklist 1.6).
  • Browser pane under load: Wikipedia, GitHub, YouTube, Hacker News and MDN typed into the address bar all rendered; window.open from a page opens as a second pane; a 59 MB download from a page lands in ~/Downloads; ten rapid navigations plus a heavy page with no renderer crash and steady memory (checklist 3.7).
  • Multiple windows: a third window crashed a renderer every time on the bench. The minidump (Electron symbols) shows partition_alloc::TerminateBecauseOutOfMemory in CreateSharedImageForSoftwareCompositor: with no GPU, Chromium backs each window's tiles with shared memory, and the sandbox VM mounts a 64 MB /dev/shm (about 17 MB per window). Not an app bug; the sandbox boot script now sizes /dev/shm like a desktop (#7408), after which four windows open with every renderer alive. The menu emitter's listener ceiling is raised here since five subscriptions per window are expected (checklist 1.7).
  • Normal user on a current distro: the AppImage runtime needs libfuse2, absent on Debian 12 and Ubuntu 22.04+, so it stops at "AppImages require FUSE to run"; APPIMAGE_EXTRACT_AND_RUN=1 works. This branch adds a deb target (gzip; fpm's xz took 16 minutes) that the workflow verifies and uploads: installed on the bench and launched by a non-root user through its own desktop entry, it starts with the real Chromium sandbox and reaches sign-in. Its "New Window" desktop action pointed at AppRun, which only exists inside an AppImage; it now calls superset --new-window (checklist 4.4).

Noticed, not changed here

  • Electron's setAsDefaultProtocolClient logs xdg-settings: default-url-scheme-handler not implemented for xfce; registration comes from the desktop entry's MimeType, which AppImage integration installs.
  • The terminal host / pty-daemon children inherit the parent's --remote-debugging-port listener, so a relaunch after a plain quit cannot bind it until they exit (dev-only flag).
  • On one launch the first host-service instance exited with code 0 at startup and the coordinator respawned it ten seconds later; not reproduced.
  • Electron does not publish a window icon on X11 even when setIcon is called with a loaded image (checked in the packaged main process), so docks rely on the icon theme entry that the deb and the desktop entry install.
  • The "restart the host service from the Superset tray menu" copy points at a tray Linux does not have.

#7429 is independent and should merge first.

https://claude.ai/code/session_01WcGbULcxE9Nw1Y8YVqe3KZ

Summary by CodeRabbit

  • New Features

    • Added Linux desktop packaging in .deb format alongside AppImage.
    • Added an application menu button for Windows and Linux.
    • Added platform-aware title bars, window-control spacing, and theme-matched title-bar colors.
    • Added Settings, update-check, and quit options to the non-macOS File menu.
    • Improved close and quit confirmation behavior, including multi-window handling.
  • Bug Fixes

    • Prevented filesystem watching from hanging when Linux inotify resources are unavailable.
    • Prevented interactive Git prompts from blocking operations.
  • Documentation

    • Added Linux desktop acceptance and support documentation.