fix(desktop): Fleet is a tab, not a modal, and Run cannot scroll away - #18
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
upgraderecipe 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 a57 lines · runs under sh -eline 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
3600rendered as360C.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
upgraderecipe 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.waitForFunctioncompiles its predicate witheval, which the real policy refuses. Hydration is polled withpage.evaluateinstead — bypassing CSP would have hidden exactly what the harness exists to catch.Checks
486 tests across 40 files, full typecheck, and
pnpm smoke:desktopall 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