diff --git a/plugins/leaf/skills/leaf/CLAUDE.md b/plugins/leaf/skills/leaf/CLAUDE.md index 63e6cae9f..00a81ffe2 100644 --- a/plugins/leaf/skills/leaf/CLAUDE.md +++ b/plugins/leaf/skills/leaf/CLAUDE.md @@ -1116,7 +1116,8 @@ message is scrolled by the panel's own list and by nothing else. The open comment panel and tray panel each occupy their own strip when the viewport can hold it and cover the page under their respective media query otherwise. `stateStrip` and `stateRoom` are the geometry readings, and both count every -strip the chrome holds; CSS owns the body's corresponding layout. +strip the chrome holds and the gutter the scroller's own bar takes — a window is +the page's box on neither count; CSS owns the body's corresponding layout. Both regions fixed to a side of the window are drawn by the reader. `drawnEdge` is the one implementation: each caller supplies the side its region is held to, a diff --git a/plugins/leaf/skills/leaf/assets/leaf.js b/plugins/leaf/skills/leaf/assets/leaf.js index db1505a6d..5f2f421d2 100644 --- a/plugins/leaf/skills/leaf/assets/leaf.js +++ b/plugins/leaf/skills/leaf/assets/leaf.js @@ -1731,11 +1731,20 @@ const trayStrip = () => // owes. The width is published rather than spent here for the reason the floor is read // blind — the runtime says how wide the page's box is and never learns which idiom hangs // something in the margin, so an idiom's own rule does its own arithmetic against it, the -// way the wide rules already spend --lf-room. A query cannot see the panel and this can, -// which is the whole of what the runtime adds; a page with no runtime behind it falls back -// to the viewport in each rule that reads it. +// way the wide rules already spend --lf-room. A query cannot see the panel or the +// scroller's own bar and this can, which is the whole of what the runtime adds; a page with +// no runtime behind it falls back to the viewport in each rule that reads it. function stateStrip() { - const avail = document.documentElement.clientWidth - panelStrip() - trayStrip(); + // The same gutter stateRoom takes off, and taken the same way, for the reason it gives + // there: body is the document's scroller, so a classic bar comes out of the room this + // page has while the window says nothing about it. The coarse answer owes it as much as + // the fine one. Without it the floor was met by a window with a bar's width less page + // behind it, and the strip came out of the column the floor exists to keep it out of — + // a sidenote page at exactly 1152px read at a 705px measure, and a sidebar and a note at + // 1416px did the same. Overlay scrollbars are 0 here, which is why a Mac never saw it. + const gutter = pageScroller.offsetWidth - pageScroller.clientWidth; + const avail = + document.documentElement.clientWidth - gutter - panelStrip() - trayStrip(); document.body.toggleAttribute("data-lf-cramped", avail < stripMin()); document.documentElement.style.setProperty("--lf-avail", avail + "px"); } diff --git a/tests/test_render_pages.py b/tests/test_render_pages.py index ac535bf45..65f467e71 100644 --- a/tests/test_render_pages.py +++ b/tests/test_render_pages.py @@ -1990,7 +1990,24 @@ def test_opposite_margin_residents_wait_for_the_room_they_need( assert tight["column"]["width"] == 720 assert tight["sideways"] == 0 + # The floor is a fact about the page's box, and the window is not that box wherever + # the platform draws a classic scrollbar: body is the document's scroller, so its bar + # comes out of the room the strips and the column divide between them. So the page is + # asked for the difference rather than a number being written that is right on one + # platform. Either way the column keeps its measure at the floor itself, which is what + # the strip is floored to protect: given the room, both strips stand outside a full + # column; short of it by the width of a bar, the veto hands them back. resized(page, 1416, 800) + bar = page.evaluate( + "() => document.documentElement.clientWidth - document.body.clientWidth" + ) + at_floor = page.evaluate(reading) + assert at_floor["column"]["width"] == 720, ( + "the strip came out of the column at the combined floor, which is the one width " + f"the floor exists to keep it out of: {at_floor}" + ) + + resized(page, 1416 + bar, 800) roomy = page.evaluate(reading) assert [(s["float"], s["position"]) for s in roomy["sidebars"]] == [ ("left", "sticky"),