Skip to content

Fix the desktop window chrome and remember its geometry - #81

Merged
timhartmann7 merged 3 commits into
mainfrom
fix/gui-window-chrome-and-state
Aug 20, 2026
Merged

Fix the desktop window chrome and remember its geometry#81
timhartmann7 merged 3 commits into
mainfrom
fix/gui-window-chrome-and-state

Conversation

@timhartmann7

Copy link
Copy Markdown
Owner

Both bugs reported at the end of #69.

Fixes #69

macOS: traffic lights sit across the edge of a collapsed sidebar

The cluster is positioned by the system against the window, not by the app's layout,
and it runs to x=69pt. The collapsed rail was 3.5rem (56px), so its right border
landed on the green button's left edge and left that button over the content pane.

--rail-w now carries the collapsed width: 3.5rem by default, 5rem under
:root[data-os='macos'] — the only platform that draws its title bar over the app.
Measured against the compiled stylesheet in headless Chromium at 1100x720 @2x: the
rail is 56px unchanged off macOS and 80px on it, holding the whole 9..69pt cluster
with the border at 79..80 and no interactive element intersecting it.

Widening the rail exposed a latent centring bug in the session rows, so it is fixed
here too: the collapsed row's inner button carried flex-1 and absorbed the row, so
the wrapper's justify-center had nothing to distribute and the icon sat at the start
edge. At 56px that was 1.5px off centre and invisible; at 80px it would have been
10.5px, a visibly left-shifted column against every other icon in the rail. The button
now only takes flex-1 when the sidebar is expanded, where the label needs it.
Measured after the fix: header, nav and session icons all centre at 39.5.

Considered and rejected: trafficLightPosition in tauri.conf.json. The cluster is
60pt wide, so no offset fits it inside a 56px rail, and Tauri 2.11.5 exposes the
setting at window-creation only — there is no runtime setter to follow the collapse.

Window size and position are not remembered

Every launch reset the window to the configured 1100x720 wherever the system put it.
Adds tauri-plugin-window-state, registered Rust-side only — no JS permission is
granted for its commands.

The flag set is the load-bearing part. The plugin's default is StateFlags::all(),
and it applies every flag from on_window_ready, which runs before the page exists.
Only sizing and moving leave a hidden window hidden (set_outer_position and
set_inner_size_physical reach SetWindowPos without SWP_SHOWWINDOW); the rest do
not:

  • VISIBLE calls show() + set_focus() there, defeating visible: false and the
    page-load reveal on every launch — reintroducing the blank frame 1.1.1 removed —
    while tests/startup_contract.rs stayed green, since it only reads JSON.
  • MAXIMIZED reaches ShowWindow(SW_MAXIMIZE) on Windows (tao window_state.rs
    apply_diff), which has no visibility guard. The window is re-hidden a few lines
    later, so the exposure is a frame rather than a session, but it is a show-then-hide
    on the one window this code keeps off screen until it has painted.
  • FULLSCREEN touches the same hidden window; DECORATIONS re-derives the macOS
    style mask the overlay title bar depends on.

So the plugin gets SIZE | POSITION and nothing else. That is not free, and the
comment on the constant says so: the plugin records a move without checking whether
the window is maximised, so a window quit while maximised reopens at its pre-maximise
size but in the display's corner. Restoring the maximised state instead would mean
touching the window before it has painted, which is the trade this slice refuses.

Two guards, because the failure mode is silent and the app has no tray icon to
recover through:

  • a compile-time assert that the flag set intersects none of VISIBLE, MAXIMIZED,
    FULLSCREEN, DECORATIONS, matching the existing RENDER_HEAL_DEADLINE idiom.
    Verified non-vacuous: adding VISIBLE fails the build with evaluation panicked.
  • a startup_contract.rs test for the other half — a call site that stops taking its
    flags from the constant.

Upstream limits worth knowing, not introduced here and not papered over in the
changelog: the plugin's monitor-intersection fallback covers position only, so a size
saved on a larger display is restored as-is; geometry is stored in physical pixels, so
moving between a 1x and 2x display rescales the window; and a window quit from macOS
fullscreen saves the fullscreen frame as its windowed size. All three are recoverable
by resizing.

Verification

cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --all --check,
cargo test -p omnyssh-gui --locked, npm run check (417 files, 0/0), npm test
(215/215), npm run build — all clean. committed_bindings_are_in_sync passes: this
slice adds no DTO, command or event, so the IPC contract is untouched.

Reviewed per CLAUDE.md: /code-review at high, then an adversarial pass against the
acceptance criteria. Both found real defects — the Windows MAXIMIZED breach and the
session-icon regression above — and both are fixed here rather than waived.

Unrelated, found while running the suite

e2e/terminal.spec.ts:119 fails on main too. It looks for the accessible name
Close web-1 · terminal, while Sidebar.svelte renders Close web-1 — the two
drifted when sessionLabel was shortened to the host name. CI does not run the e2e
suite, so it went unnoticed. Left alone here; worth its own fix.

@timhartmann7
timhartmann7 merged commit 7da8cc9 into main Aug 20, 2026
6 checks passed
@timhartmann7
timhartmann7 deleted the fix/gui-window-chrome-and-state branch August 20, 2026 07:14
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.

[Feature Request] Multi-selection; hide hosts; batch open terminals, keyboard shortcuts and so on

1 participant