Skip to content

fix(desktop): Fleet is a tab, not a modal, and Run cannot scroll away - #18

Merged
ralyodio merged 1 commit into
mainfrom
feat/fleet-as-a-tab
Aug 30, 2026
Merged

fix(desktop): Fleet is a tab, not a modal, and Run cannot scroll away#18
ralyodio merged 1 commit into
mainfrom
feat/fleet-as-a-tab

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Fleet shipped in v0.2.8 as a dialog. That is the wrong container, and it broke the thing the view exists for: pick the upgrade recipe and its 57 lines of shell push the Run button off the bottom of the modal, where nothing can scroll it back. The script itself was five textarea rows, so most of what was about to run on every selected server was hidden too.

A modal is for a question you must answer before anything continues. This is somewhere you sit for minutes with a long script in front of you while a dozen servers report — the opposite.

What changed

Fleet is one of two tabs in the header, beside Transfer.

The layout has exactly three scrolling regions — the server list, the script editor, and the results — and the action bar sits outside all three, pinned to the bottom of the window. Run is on screen at the 960×600 minimum window size just as it is maximised.

The editor is clamp(120px, 26vh, 340px), resizable, with its own scrollbar and a 57 lines · runs under sh -e line beneath it — so the size of what you are about to run is stated rather than discovered.

Modals are kept for modality. The destructive-command confirmation is now a small dialog with Cancel / Run it anyway: a real blocking yes/no, which is what a dialog is for.

Also fixes a clipped field the screenshots caught: Timeout was 70px wide, and the upgrade recipe's default of 3600 rendered as 360C.

Verified by looking at it

Screenshotted the built renderer rather than reasoning about it — mock preload bridge served as an external script, the app's real hashed CSP on the response, playwright-core in headless Chromium. A plain static server sends no policy and proves nothing; that is how two blank-window regressions shipped before.

Eight shots, all inspected: the tab empty; the upgrade recipe at 1360×860 and at the 960×600 minimum; a run in flight with a failure and an unreachable host; the check sweep; the hazard dialog; light theme; and the Transfer tab intact. No CSP violations.

One trap worth recording for the next person: page.waitForFunction compiles its predicate with eval, which the real policy refuses. Hydration is polled with page.evaluate instead — bypassing CSP would have hidden exactly what the harness exists to catch.

Checks

486 tests across 40 files, full typecheck, and pnpm smoke:desktop all green (the smoke run reaches "the main process loaded", not "skipped", with the staged GUI libs on the loader path).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UeSWg1Czsb2Lwxj8vHUnA4

Fleet shipped in v0.2.8 as a dialog. It is the wrong container and it broke
the thing the view exists for: pick the `upgrade` recipe and its fifty-seven
lines of shell push the Run button off the bottom of the modal, where nothing
can scroll it back. The script itself was five textarea rows, so most of what
was about to run on every selected server was hidden too.

A modal is for a question you must answer before anything continues. This is
somewhere you sit for minutes with a long script in front of you while a dozen
servers report — the opposite.

So Fleet is now one of two tabs in the header, beside Transfer, and the layout
has exactly three scrolling regions: the server list, the script editor, and
the results. **The action bar sits outside all three**, pinned to the bottom of
the window. Run is on screen at the 960x600 minimum size just as it is
maximised. The editor is `clamp(120px, 26vh, 340px)`, resizable, with its own
scrollbar and a `57 lines - runs under sh -e` line under it, so the size of
what you are about to run is stated rather than discovered.

Modals are kept for modality: the destructive-command confirmation is now a
small dialog with Cancel / Run it anyway, which is a real blocking yes/no.

Also fixes a clipped field found in the screenshots: Timeout was 70px, and the
upgrade recipe's default of 3600 rendered as "360C".

Verified by screenshotting the built renderer rather than by reading it, per
the harness in [[diskpush-desktop-ui-preview]] — mock preload bridge served as
an external script, the app's real hashed CSP on the response, playwright-core
in headless Chromium. Eight shots: the tab empty, the upgrade recipe at
1360x860 and at the 960x600 minimum, a run in flight with a failure and an
unreachable host, the check sweep, the hazard dialog, light theme, and the
Transfer tab intact. No CSP violations.

One trap worth recording: `page.waitForFunction` compiles its predicate with
`eval`, which the real policy refuses, so hydration is polled with
`page.evaluate` instead. Bypassing CSP would have hidden exactly what the
harness is there to catch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UeSWg1Czsb2Lwxj8vHUnA4
@ralyodio
ralyodio merged commit 3d73410 into main Aug 30, 2026
4 checks passed
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