Skip to content

feat(gui): use the compositor's native title bar where it exists - #6

Open
plutack wants to merge 3 commits into
mainfrom
feat/gui-native-titlebar
Open

feat(gui): use the compositor's native title bar where it exists#6
plutack wants to merge 3 commits into
mainfrom
feat/gui-native-titlebar

Conversation

@plutack

@plutack plutack commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Why

On Wayland sessions whose compositor provides server-side decorations (COSMIC, KDE Plasma), the GUI showed GTK3's fallback client-side title bar — rendered light gray against the app's dark theme, looking foreign next to every other window. (This is why the same binary looked "more native" when run under XWayland: the compositor decorates X11 clients itself.)

What

  • Mark the window frameless so the compositor draws its native bar — verified live on a COSMIC session (dark bar, min/max/close, matches other windows).
  • Gated by a new gui.native_titlebar config key: auto (default — on for COSMIC/KDE), always, never.
  • Linux-only: Windows (DWM) and macOS (NSWindow) always get their native bar with a normal decorated window — going frameless there would mean no bar at all, so auto/always are ignored off Linux.

Stacked on #5 (only the last commit is unique until that merges).

Exploring captures (rapidly clicking rows in the All-traffic view)
froze the UI, and large bodies eventually crashed the WebKit web
process (JS heap exhaustion; confirmed by a user-session WebKitWeb-
Process SIGABRT coredump). Four compounding causes, all fixed:

- The 2s poll loop always installed fresh array identities into
  state, re-rendering the entire tree — including any open detail
  pane — even when nothing changed. Loaders now bail out via
  functional setState when a row fingerprint is unchanged.

- BodyViewer re-ran prettyBody (JSON.parse + stringify), regex
  syntax highlighting, and a full innerHTML swap on every render,
  because dangerouslySetInnerHTML got a new object identity each
  time. All transforms are now memoized, pretty-print/highlight
  are skipped above 1 MiB (with an explanatory notice), and the
  innerHTML prop identity is stable.

- Every capture detail embedded an ExportSnippet that auto-fired a
  snippet conversion on each selection change — one goja JS VM +
  httpsnippet eval per click. Conversion is now debounced 250ms and
  the snippet <pre> no longer re-assigns innerHTML per poll.

- Rapid row clicks each fetched the full body payload and rendered
  every response on arrival. Detail fetches are now deduplicated
  per row while in flight, and superseded responses are dropped.
  GetCapture also stopped shipping bodies twice (UTF-8 + base64);
  base64 alone halves the response the webview has to parse.

Validated live on Linux/webkit2gtk: a 5 MiB body that killed the
app outright now opens fine, and 12 rapid clicks across 1 MiB-body
rows settle cleanly (transient RSS spike, full GC recovery, UI
responsive throughout). Idle polling no longer rebuilds the detail
DOM: RSS is flat between real data changes.
Memoization removed the repeated cost but the FIRST render of a
multi-megabyte body still blocked the webview for 0.5-2s: the whole
body was base64-decoded per byte, escaped with three regex passes,
and materialized as DOM text. Measured on a 4.8 MB JSON body, one
open plus a few row switches cost the shipped v0.2.0 ~7.1s of
webview CPU and ~1 GB of RSS growth; the same interaction now costs
~0.5s with flat RSS.

The viewer now decodes and renders at most DISPLAY_LIMIT (256 KiB)
up front, with a notice and a 'Show entire body' escalation. Copy,
Save, and Image still operate on the full payload, decoded on
demand. escapeHTML is a single pass, and the base64 prefix is cut on
a 4-character boundary so the slice always decodes cleanly.
On Wayland desktops whose compositor draws server-side decorations
(COSMIC, KDE Plasma), GTK3's fallback client-side bar renders light
gray against the app's dark theme and looks foreign next to every
other window. Marking the window frameless makes the compositor draw
its own native bar instead — verified live on a COSMIC session.

Gated by a new gui.native_titlebar config key (auto | always | never,
default auto). auto enables it only on Linux desktops that actually
decorate frameless clients; Windows and macOS never go frameless
(DWM and NSWindow already provide the native bar, and frameless there
means no bar at all).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant