diff --git a/demos/mandelbrot/shell.html b/demos/mandelbrot/shell.html
index 5e65c2b..0ddac56 100644
--- a/demos/mandelbrot/shell.html
+++ b/demos/mandelbrot/shell.html
@@ -25,6 +25,11 @@
@media (orientation: landscape) { body { justify-content: center; } }
/* transition eases the pop-to-bright when a freshly computed frame lands */
#app { flex: 0 0 auto; transition: filter 0.15s ease-out; }
+ /* xterm.css gives the viewport overflow-y:scroll, which reserves a scrollbar
+ even with scrollback:0. Where scrollbars are classic (Linux, Windows, macOS
+ with a mouse) it paints a 15px bar over the image's right edge. Nothing
+ scrolls here, so hide it; !important because xterm.css loads after us. */
+ .xterm .xterm-viewport { overflow-y: hidden !important; }
/* Touch-only ⌨ toggle (created in JS): re-summons the soft keyboard we
suppress by default, so the keyboard controls (hjkl ,. r q) stay reachable
on a phone. Floats over the top-right corner; .on marks it active. */
diff --git a/demos/pathtrace/shell.html b/demos/pathtrace/shell.html
index 75b7abc..4caeea8 100644
--- a/demos/pathtrace/shell.html
+++ b/demos/pathtrace/shell.html
@@ -19,6 +19,11 @@
body { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; }
@media (orientation: landscape) { body { justify-content: center; } }
#app { flex: 0 0 auto; }
+ /* xterm.css gives the viewport overflow-y:scroll, which reserves a scrollbar
+ even with scrollback:0. Where scrollbars are classic (Linux, Windows, macOS
+ with a mouse) it paints a 15px bar over the image's right edge. Nothing
+ scrolls here, so hide it; !important because xterm.css loads after us. */
+ .xterm .xterm-viewport { overflow-y: hidden !important; }
/* Touch-only ⌨ toggle: re-summons the soft keyboard so the keyboard controls
(hjkl n r q) stay reachable on a phone. */
#kbd-toggle {
diff --git a/test/viewport_fit_test.py b/test/viewport_fit_test.py
index 0625b10..649d97e 100644
--- a/test/viewport_fit_test.py
+++ b/test/viewport_fit_test.py
@@ -4,6 +4,7 @@
Drives the served browser bundle with Playwright. For each viewport it asserts:
- no horizontal overflow (the terminal box fits the viewport width),
+ - no scrollbar on the xterm viewport (Chromium runs with scrollbars drawn),
- the program adapted SCALE (smaller on the phone, 3 on desktop),
- no console/page errors,
- a center click keeps the center x within one cell and zooms in,
@@ -71,7 +72,9 @@ def new_page(p, w, h):
"""Launch a chromium page at the given viewport with error capture wired and
the document loaded. Returns (page, errs); errs accumulates pageerror +
console-error text for the caller to assert on."""
- pg = p.chromium.launch().new_context(viewport={"width": w, "height": h}).new_page()
+ # Headless Chromium hides scrollbars by default; draw them so a classic
+ # scrollbar over the terminal shows up as a measurable gutter.
+ pg = p.chromium.launch(ignore_default_args=["--hide-scrollbars"]).new_context(viewport={"width": w, "height": h}).new_page()
errs = []
pg.on("pageerror", lambda e: errs.append(str(e)))
pg.on("console", lambda m: errs.append(m.text) if m.type == "error" else None)
@@ -108,8 +111,11 @@ def check_viewport(p, tag, w, h, want_scale):
"() => {const el=document.getElementById('app')||document.getElementById('terminal');"
"const r=el.getBoundingClientRect();"
"return {scale:window.__lgScale, grid:window.__lgGrid, termW:Math.round(r.width), "
- "vpW:window.innerWidth, overflowX:Math.round(r.width)>window.innerWidth+1};}"
+ "vpW:window.innerWidth, overflowX:Math.round(r.width)>window.innerWidth+1, "
+ "gutter:(v=>v?v.offsetWidth-v.clientWidth:0)(document.querySelector('.xterm-viewport'))};}"
)
+ if info["gutter"]:
+ fails.append(f"{tag}: {info['gutter']}px scrollbar on the xterm viewport")
if info["overflowX"]:
fails.append(f"{tag}: terminal {info['termW']}px overflows viewport {info['vpW']}px (clipped)")
if info["scale"] != want_scale: