Use a single rgba(0,0,0,0.7) glyph color across all three traffic-light
buttons instead of per-action hue-tinted darks. Black-with-opacity lets
the fill tint the symbol, matching macOS chrome and reading cleanly on
all three fills. Drops the redundant per-action glyph field.
Revert out-of-scope sprite.ts regeneration from d971696f that dropped
two unrelated dead icons (arrow-left-long, sort-desc); those belong in
a separate dead-code cleanup if desired. Add a comment at the
DesktopWindowControlsStyle type noting control width is style-dependent
(classic vs traffic-lights), so no fixed-width constant applies.
Replaces the custom corner-triangle SVG with Remixicon's `add` (+) glyph
for both maximize and restore states in traffic-lights style, matching the
close/minimize glyphs. The restore vs maximize label still flips via
isMaximized.
Add a Window controls Style setting ('classic' | 'traffic-lights',
default 'classic') next to the existing Position setting. All four
position×style combinations render correctly with side-driven order
(close,min,max left; min,max,close right).
- Restore deleted left-classic branch (h-8 w-8 rounded-md buttons)
- Add right-traffic-lights branch (cluster with ml-1 container)
- Branch on style instead of side in WindowsWindowControls
- Store field + setter with 'classic' default (no migration needed;
Zustand persist shallow-merges over initial state)
- Settings UI: SettingsTwoColumn with Position chips + Style dropdown
- Sanitize on client (persistence.ts) and server (settings-helpers.js)
- 10 locale dictionaries updated (add-only, no existing keys changed)
- Traffic-light button spacing 8px→10px edge-to-edge
- Delete dead DESKTOP_WINDOW_CONTROLS_WIDTH_PX constant
The tray controller called setToolTip and setContextMenu unconditionally on
every snapshot push. On Linux both are synchronous D-Bus calls into
plasmashell's StatusNotifierItem host. useTraySync debounced pushes at 120ms
(up to ~8/sec during token streaming), so each push made 2 blocking D-Bus
round-trips even when only dockBadgeCount changed or a token streamed into
an already-listed session — i.e., tooltip and menu content didn't change.
The native block has no JS-level log, is intermittent (depends on plasmashell
load), and eventually freezes the Electron main thread until crash.
setTitle and setImage were already deduped; setToolTip and setContextMenu
were the gap. Mirror the existing lastX pattern:
- tray.mjs: dedupe setToolTip (exact string compare via lastTooltip) and
setContextMenu (via lastMenuKey, a lightweight signature of menu-affecting
fields: sessions/approvals/usage). menuKey is a string concat, cheaper than
buildMenu itself, so skipping buildMenu when the key matches also saves
work. Cache-after-call ordering matches lastTitle/lastTooltip so a throw
forces a retry rather than skipping one. destroy() resets both new vars.
- useTraySync.ts: FLUSH_DEBOUNCE_MS 120 -> 500. Tray doesn't need sub-second
updates; approvals are rare discrete events that flush through the debounce
and the main app UI stays instant via SSE/stores.
The first change is the real fix; the second is a complement. Either alone
helps; together they eliminate the freeze under streaming.