Skip to content

fix(installer): keep the Install button reachable when the window is shorter than the content - #428

Merged
PathGao merged 1 commit into
masterfrom
fix/installer-button-reachable
Aug 3, 2026
Merged

fix(installer): keep the Install button reachable when the window is shorter than the content#428
PathGao merged 1 commit into
masterfrom
fix/installer-button-reachable

Conversation

@PathGao

@PathGao PathGao commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Addresses #75 (thanks @wargoblin for the repro detail):

Had to grab edge of window and drag down to see the button.

v2.6.2 made the installer window taller, which moved the threshold. The failure mode is still there, and it is not really about text scaling — it is about the pane being 100vh with overflow: hidden, so any window shorter than the content hides the button and nothing scrolls it back.

Why the button leaves the window

.installer-container is height: 100vh; overflow: hidden. .content is its flex child. A flex item's min-height is auto in the block axis, so .content never shrinks below its own contentflex: 1 grows it, nothing shrinks it. When the content is taller than the window, .content keeps its full height, overflows the container, and the container clips it. overflow: hidden is not user-scrollable, so there is no wheel, no scrollbar, no keyboard route to what was cut.

Measured in the real component (450 wide, the shipped installer width), fresh-install state, English:

Window 450 × 650 logical (lib.rs, LogicalSize)
Pane content 581px
Bottom of the Install button 504.5px from the top of the pane
Headroom 69px, for every text size, locale and installer state

So the button is fully visible only while the window's inner height is ≥ 505px, and fully gone below 466px. At 470px the screenshot is the issue report: a 4px sliver of blue at the bottom edge, container.scrollHeight 581 vs clientHeight 470, scrollTop pinned at 0.

Two independent things push past that 69px on Windows, and neither is under the frontend's control: content that grows (system text size — every font size in this component is a fixed px, and the boxes around them are fixed px too), and a window that ends up shorter than requested. Both land on the same number, which is why the fix is stated in terms of that number rather than in terms of scaling.

The fix

.content {
    flex: 1;
    min-height: 0;              /* let it shrink to the window */
    justify-content: center;
    justify-content: safe center;
    overflow-y: auto;           /* and make the remainder reachable */
    overflow-x: hidden;
}

The scroll goes on .content, not on .installer-container. The container carries data-tauri-drag-region, and tauri's drag.js keys the drag off the mousedown target alone — e.target.getAttribute(...) in the locked 2.10.2, the same check walked over the composed path in 2.11.5. A scrollbar belongs to its scrolling element, and mousedown on it reports that element as the target, so a scrollbar on the container would start a window drag instead of scrolling — in exactly the case this PR is about, since the scrollbar only exists when the content overflows. .content has no drag attribute; its scrollbar is just a scrollbar.

safe center. Centring a column that overflows pushes the top out past the start edge, which no amount of scrolling reaches — the header would go where the button used to. safe falls back to start-alignment only when it overflows, and only then. Declared after a plain center so an engine without safe drops the second declaration and keeps today's behaviour; both survive the production CSS minifier (verified in build/).

Four heightmin-height (.options-container, .error-container, .notice-container, .scope-toggle). These reserve space so the box does not resize between installer states — a floor does that as well as a fixed height does. As ceilings they let content escape: at a 2.25× text scale (the Windows maximum) the option list is 221px tall inside its 140px box and lands 71px on top of the error container, which is under the buttons. A scrollable pane does not fix overlap, and the overlap is what makes the button unclickable rather than merely off-screen.

.installer-container itself is untouched — still height: 100vh, still overflow: hidden, still the drag region over the whole window. .content can no longer exceed it, so that overflow: hidden now never has anything to clip.

Verified in a browser

Built with npm run build and driven in Chromium at the installer's own 450px width, against the real components (invoke stubbed to the two calls the installer makes), at both the dev server and the production build.

Inner height Before After
650 (shipped) fits, 69px spare identical — every element rect equal to the byte, scrollHeight === clientHeight, no scrollbar
470 button bottom 504.5, clipped, scrollTop stuck at 0 scrolls 110.5px, button fully in view, header still at the top at scrollTop: 0
420, update/repair state both Uninstall and Update / Repair clipped content 521px, scrolls 100.5px, both buttons reachable
300, uninstaller clipped content 327px, scrolls 26.5px, both buttons reachable

Text scaling simulated by multiplying every computed font-size in the pane while holding the window at 450 × 650 — text grows, the window does not, which is the shape of the Windows setting:

Text scale Content height Option list vs. its 140px box Button
1.0× 581 fits visible, no scroll
1.75× 650 box grows to 150, no overlap visible, no scroll
2.25× 831 box grows to 221, no overlap reachable at scrollTop: 180.5
2.25×, with the old fixed heights restored 801 overflows by 81px, 71px of it over the error container

Drag region, hit-tested on a 3px grid over the whole window before and after: .installer-container is the topmost element at 0 points in both.content covers it completely, and the container is the same size it always was. Worth saying plainly since the PR touches it: with target-only matching in drag.js, that means the installer window is not draggable by its background today, before this change or after. Not touched here; see Not covered.

npm run check   440 files, 0 errors, 0 warnings
npm test        546 / 546
npm run build   ok

No Rust touched.

Uninstaller

Uninstaller.svelte is the same component shape — same .installer-container with height: 100vh; overflow: hidden, same .content flex child, same data-tauri-drag-region. It carries less content (327px vs 581px), so it needs a much shorter window to break, but the mechanism is identical and it does break. Included: same defect, same file pattern, adjacent file, and the .content block is byte-identical to the installer's. Its fixed heights do not exist, so only the one block changed.

The reporter's suggestion

the window should auto-size to fit its content

That is the right instinct and it is not what this PR does. Sizing the window to content is a Rust-side change (window.set_size in lib.rs), it has to cope with a window taller than the work area — where auto-size cannot help and only scrolling can — and it belongs with the larger discussion about how Markpad installs itself on Windows (#395). A scrollable pane is the floor under both: whatever the window ends up doing, the button stays reachable.

Not covered

  • Not tested on Windows. No Windows machine here. Everything above is Chromium on macOS at the installer's dimensions. WebView2 is Chromium, but three things genuinely differ and none of them were exercised: classic non-overlay scrollbars take ~15px of .content's width when they appear (.setup-box shrinks from 370 to ~355, the two-button row shrinks with it), the OS text-size setting was simulated by scaling font sizes rather than by the setting itself, and browser zoom / a short viewport is a proxy for a small window, not for a display-scaling change.
  • No synthetic mouse input. Wheel scrolling and scrollbar dragging were not driven; the scroll geometry, computed styles and the target-only rule in drag.js were read instead.
  • The window background is not draggable, before or after — see above. Fixing it means data-tauri-drag-region on .content or ="deep" on the container, both of which change what a click on the pane does, and neither belongs in a fix for a clipped button.
  • The window is still a fixed 450 × 650. Nothing here makes it fit its content, and nothing here touches setup.rs, hooks.nsi or the window creation.
  • Horizontal overflow is still clipped (overflow-x: hidden, as before). A long translated title is cut rather than scrollable; the buttons are centred and flex-shrink, so they are not what overflows.
  • No test. This is layout in a component with no rendering harness in scripts/; the numbers above come from measuring the real component in a browser, and nothing in the suite would catch a regression here.

🤖 Generated with Claude Code

…shorter than the content

Reported on Windows 11 at 125% scaling (#75): the Install button sat below
the bottom edge with no way to get to it. "Had to grab edge of window and
drag down to see the button."

`.installer-container` is `height: 100vh; overflow: hidden`, and `.content`
inside it is a flex item, so it never shrinks below its own content -
`min-height: auto` is the default in the block axis. Content taller than the
window therefore overflows a container that clips it and, because
`overflow: hidden` is not user-scrollable, nothing brings it back. The
window is 450x650 logical and the fresh-install pane is 581px tall: 69px of
headroom for every text size, locale and installer state. v2.6.2 raised the
window height, which moved that number without removing the failure mode.

The pane scrolls now. `min-height: 0` lets `.content` shrink to the window,
`overflow-y: auto` makes the remainder reachable.

The scroll goes on `.content` rather than on `.installer-container` because
the container carries `data-tauri-drag-region`, and tauri's drag.js keys off
`e.target` alone (2.10.2 `e.target.getAttribute`, 2.11.5 the same check via
the composed path). A scrollbar on the drag region would report the
container as the mousedown target, so dragging the scrollbar would drag the
window - and the scrollbar only exists in the case this fix is for.
`.content` has no drag attribute, so its scrollbar is just a scrollbar.

`justify-content: safe center` because centring an overflowing column pushes
the top out past the start edge, where scrolling cannot reach it. Declared
after the plain `center` so engines without `safe` keep today's behaviour.

The four fixed heights that made the pane rigid become `min-height`. They
exist to stop the box changing size between installer states, which a floor
still does; as ceilings they let the option list overlap the error slot and
the buttons at large text sizes. At a 2.25x text scale the option list is
221px inside its 140px box, 71px of it on top of the error container.

Nothing changes at the shipped window size: every element rect is identical
and no scrollbar appears.

`Uninstaller.svelte` is the same component shape with the same container and
the same defect, three lines apart in the same directory, so it gets the
same treatment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao force-pushed the fix/installer-button-reachable branch from a0158ef to 314076c Compare August 3, 2026 07:53
@PathGao
PathGao merged commit ca8aebf into master Aug 3, 2026
4 checks passed
@PathGao
PathGao deleted the fix/installer-button-reachable branch August 3, 2026 08:10
PathGao added a commit that referenced this pull request Aug 5, 2026
Eleven macOS screenshots for the v2.7.0 release notes, following the
pics/<version>/ convention already used by 2.6.2 and 2.6.3. Taken from local
builds of master at ebc20ab and of the tag branch, against the same document
throughout so the before/after pairs differ only in what they are about.

Not included, and why: the installer at large text (#428) is Windows-only; a PDF
"before" is impossible because macOS PDF export did not work at all until #287,
so there is no earlier build to compare against; and the collapsed floating ToC
is visible in several of the other shots rather than needing its own.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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