Skip to content

fix(editor): the viewport fills its slot, no reserved scrollbar gutters - #48

Closed
pythonlearner1025 wants to merge 1 commit into
mainfrom
fix/canvas-gutter
Closed

pythonlearner1025 wants to merge 1 commit into
mainfrom
fix/canvas-gutter

Conversation

@pythonlearner1025

Copy link
Copy Markdown
Member

What part this touches

The editor screen is one row of panels. WindowPanesLayout builds it with
react-resizable-panels: a left slot, a center slot, a right slot, and a bottom slot inside the
center. Each slot holds a Blueprint Tabs, and each tab draws its content in a .bp5-tab-panel.
The center slot's tab panel holds the 3D viewport canvas. The right slot's Inspector tab holds a
card, a Blueprint panel stack, and the stack's view, which is the box the Inspector rows scroll in.

react-resizable-panels gives every Panel and every PanelGroup overflow: hidden.

packages/editor/index.html carries a small style block for scrollbars. It styles the * selector,
so it reaches every element on the page.

packages/uiconfig-blueprint is a workspace package. Its src/renderer.scss styles the Inspector
card and the panel stack. packages/editor/src/renderer.scss imports it at line 1, and npm links
the workspace, so the editor compiles that file from source into its own bundle.

The problem

The style block set scrollbar-gutter: stable on *.

In Chrome an overflow: hidden box is a scroll container. stable tells a scroll container to keep
the scrollbar's width whether it shows a scrollbar or not. So every overflow: hidden box in the
editor kept 11 px it could never use.

The canvas and every box above it, read in a headless Chrome at 1512 by 982 on the build before
this branch:

box offsetWidth clientWidth overflow gutter kept
canvas 860 860 clip 0
.editorCanvasContainer 860 860 visible 0
.window-panel-card 860 860 visible 0
.bp5-tab-panel 860 860 hidden 0
.bp5-tabs.window-panels-tabs 860 860 visible 0
.center-top-panel 871 860 hidden 11
the vertical PanelGroup 882 871 hidden 11
#center-panel 893 882 hidden 11
.editorSplitContainer 1501 1490 hidden 11
html 1501 1512 visible 11 at the window

Three panel levels each took 11 px. The owner saw the innermost one as a dark band to the right of
the canvas, 33 px wide. The root element took another 11 px from the window, so the whole layout was
1501 px wide in a 1512 px window, and the last 11 px painted white in the dark theme.

The Inspector lost more, because five boxes nest there. The same run, same viewport:

box offsetWidth clientWidth overflow gutter kept
#right-panel 298 287 hidden 11
.bp5-tab-panel 287 276 auto 11
.bpInspectorCard.inspector-stack 276 265 auto / scroll 11
.bp5-panel-stack2 265 254 hidden 11
.bp5-panel-stack2-view 255 243 auto 12

The tab list above read 286.6 px and the content read 243 px, so the content was 44 px narrower than
the strip. Only the last box ever scrolls. The card also set overflow-y: scroll, which paints a
track with nothing to scroll: that was the second, empty strip beside the real scrollbar.

The content was too narrow to fit its own rows, so the Inspector also scrolled sideways: the view
reported scrollWidth 284 against clientWidth 243.

PR #33 fixed the center tab panel alone, with scrollbar-gutter: auto. That is why the canvas was
not narrower still.

The fix

  • packages/editor/index.html drops scrollbar-gutter: stable from the * rule. The rule keeps
    scrollbar-width, scrollbar-color and the ::-webkit-scrollbar sizing.
  • A stable gutter goes on the four boxes that really scroll, one line each, each with a comment.
  • .hierarchy-stack, the left slot's tab panel, reserves one. The Objects tree scrolls in it.
  • .hierarchy-stack:has(.resources-panel) reserves none. The Resources tab brings its own scroll
    box, so that tab panel never scrolls.
  • .resources-panel reserves one. The three resource sections scroll in it.
  • .files-panel-grid reserves one. The Files grid scrolls in it.
  • .bp5-panel-stack2-view reserves one, in packages/uiconfig-blueprint/src/renderer.scss. The
    Inspector rows scroll in it.
  • .bpInspectorCard changes overflow-y: scroll to auto. It never scrolls, so it now paints no
    track.
  • .center-top-panel > .window-panels-tabs > .bp5-tab-panel drops scrollbar-gutter: auto from
    PR fix(editor): the center slot shows its tab strip #33. Nothing gives that box a gutter any more, so the line had no work left.
  • That block keeps overflow: hidden. That line still works: with auto a PageDown scrolls the
    panel 4 px and takes the canvas with it. I measured the canvas moving from y 81 to y 77.

The risk trade

The cost is that a gutter is now a decision per box, not a blanket. A future scroll box that wants
a stable gutter has to ask for one, or its content shifts 11 px when a scrollbar appears. What
lowers the cost: the four rules each say in one line why that box scrolls, and the acceptance run
counts every box that keeps a gutter, so a new one that does not scroll shows up as a number.

The Inspector still scrolls sideways at a 300 px panel: it needs 294 px against 289 px of room, so
5 px overflow. Before it needed 284 px against 245 px, so 39 px. The row that sets the floor is
Render Scale, a label with a slider and a number box. At a 306 px panel the overflow is gone. I did
not change any row's width, because that is inspector layout and not this bug.

Rejected alternative: put the gutter on .editorSplitContainer .bp5-tabs > .bp5-tab-panel, which
is one rule instead of four. I measured the tab panels and rejected it. Only the left slot's tab
panel scrolls. The right slot's and the bottom slot's hold a scroll box of their own, so that rule
would keep the Inspector's 11 px loss and the Files panel's 11 px loss, which is the bug again.

uiconfig-blueprint is not published by scripts/release.mjs. Its two lines reach users through
the editor bundle, which compiles the workspace source. A build that resolved
uiconfig-blueprint@0.1.0-dev.14 from the registry instead of the workspace would not carry them.
npm ci in this repo links the workspace, because the versions match.

Tests

Headless Google Chrome 153, the owner's own binary, fresh profile, viewport 1512 by 982 and 1600 by
1000, device pixel ratio 2. The dev server ran with CI=1 and --no-open on port 4476, serving
/Users/minjunes/games/terminator-next. I never ran kite3d install. Nothing took focus. Zero page
errors in every run.

cd /Users/minjunes/games/terminator-next && CI=1 node \
  /Users/minjunes/kite3d-worktrees/canvas-gutter/packages/kite3d/dist/cli.js dev --port 4476 --no-open

I confirmed the served editor was my build. The sha256 of each served bundle matched
packages/editor/dist/assets:
/private/tmp/claude-501/-Users-minjunes-blitz/278f5f97-7e16-4722-b1bc-c061014966db/scratchpad/evidence/canvas-gutter/served-is-my-build-final.txt.

The acceptance table, same script both ways, at 1600 by 1000:

number before after
boxes that keep a gutter of 8 px or more 65 3
of those, boxes that never scroll 56 1
visible strips, gutter 8 px or more on a box 100 px tall 11 2
the same, inside the right panel 5 1
canvas offsetWidth against its slot's offsetWidth 913 against 946 959 against 959
band right of the canvas 33 px 0 px
Inspector content against its tab list 260 against 304 309 against 320
horizontal overflow at a 300 px right panel 39 px 5 px
Objects tree gutter, short and long content 11 and 11 11 and 11
Inspector gutter, short and long content 12 and 12 12 and 12
Files panel gutter, short and long content 11 and 11 11 and 11

Two numbers need their definition stated. A box keeps a gutter when offsetWidth minus
clientWidth minus its borders is 8 px or more. Inside the right panel I count 5 before, because I
count the #right-panel box itself; a count that starts below it reads 4. Counting elements whose
computed scrollbar-gutter is not auto is not a useful number before the change: the * rule
gives every element stable, so the count is the document's element count, 777 in my run. After the
change it is 3.

The one box that keeps a gutter without scrolling is the Objects tree's tab panel with the tree
collapsed. That is the point of a stable gutter: the rows keep their width as branches open.

The before column comes from the same page with the deleted rules put back with addStyleTag. I
checked that stand in against the real baseline build at 1512 by 982: canvas 860, slot 893, band 33,
Inspector content 243, tab strip 286.6, right panel 297.6, 65 boxes keeping a gutter. Every number
matched the build I measured before I touched anything.

Screenshots I looked at, all under
/private/tmp/claude-501/-Users-minjunes-blitz/278f5f97-7e16-4722-b1bc-c061014966db/scratchpad/evidence/canvas-gutter/shots/:

  • before-full.png and after-full.png: the whole editor. The band beside the canvas is gone.
  • before-canvas-right-edge-3x.png and after-canvas-right-edge-3x.png: the canvas's right edge at
    three times size. Before, two thirds of the crop is flat panel colour. After, it is all 3D.
  • before-inspector.png and after-inspector.png: the Inspector panel. Before, the rows stop 44 px
    short and terminator-node is cut. After, the rows reach the edge and terminator-node-map fits.
  • baseline-emulated-inspector-strips-4x.png and after-inspector-strips-4x.png: the right of the
    Inspector at four times size. Before, two empty strips beside the content. After, one gutter.
  • before-inspector-right-edge-3x.png and after-inspector-right-edge-3x.png: before, a white 11 px
    strip runs down the window's right edge, which is the root element's reserved gutter.
  • after-resources-scrolled.png: the Resources tab scrolled 4000 px. The sticky section headers
    still stick, at the panel's own width of 291 px.
  • after-tree-scrolled.png, after-1512-tree-long.png, after-files.png, after-light.png,
    after-resized.png, after-expand-all.png.

The edges I ran, all after the fix:

  • Collapse and expand the left, right and bottom panels, one at a time, and the expand all toggle.
    The canvas matched its slot every time: 906 and 906, 1208 and 1208, 1510 and 1510. The render size
    followed, 1812 by 1616 at device pixel ratio 2.
  • Both themes. Light and dark both read canvas 906 in a 906 slot.
  • A window resize to 1180 by 820 and back. Canvas 707 in a 707 slot.
  • The Objects tree with every branch open, scrolled and at rest, then collapsed again. The tab panel
    held clientWidth 291 in all three states.
  • The Inspector with the scene picked, 920 px of rows in a 920 px box, which does not scroll, and
    with a light picked, 957 px of rows, which does. The view kept 12 px both times and held
    clientWidth 309. Scrolled to 400 and back, the content did not move.
  • The Files panel with the bottom slot small, 153 px of items in a 65 px box, which scrolls, and
    dragged tall, 325 px of items in a 325 px box, which does not. The grid kept 11 px both times.
  • The Resources tab, which is the one place two boxes could both scroll.
  • The project picker popover, which holds a list with overflow-y: auto. It did not scroll in this
    project, 434 px of rows in a 434 px box, so it gets no gutter and I left it alone.

Suites, on the final tree: npm run typecheck exit 0, npm run lint exit 0,
npm test -w packages/kite3d 5 files and 10 tests passed, npm run test:scripts 2 tests passed.

What fails without the fix: putting the six deleted declarations back on the running page, with
addStyleTag, moves the canvas from 959 to 913 in the same 946 slot, the band from 0 to 33 px, the
Inspector content from 309 to 260, and the boxes keeping a gutter from 3 to 65. That is the run in
acceptance-baseline-1600.json against acceptance-after-1600.json.

No test was added. This is layout in a stylesheet, it did not flake across runs, and the proof is
the measured walkthrough above.

Deploy

The editor bundle ships it, through the next kite3d npm release. npm run build compiles
packages/editor/index.html, packages/editor/src/renderer.scss and the workspace copy of
packages/uiconfig-blueprint/src/renderer.scss into packages/editor/dist/assets, and kite3d dev
serves that folder.

npm ci
npm run build
npm run release:patch

Rollback: the previous version on npm, kite3d@0.21.0-alpha.6. npm install -g kite3d@0.21.0-alpha.6
restores the editor with the blanket gutter.

index.html asked every element for `scrollbar-gutter: stable`. In Chrome an
`overflow: hidden` box is a scroll container, so each one kept 11 px for a
scrollbar it can never show. The canvas lost 33 px to three nested panels and
the Inspector lost 44 px to four nested boxes.

The gutter now sits on the four boxes that really scroll: the Objects tree's
tab panel, the Resources sections, the Files grid and the Inspector's panel
stack view. The Resources tab brings its own scroll box, so the tab panel under
it reserves nothing. The Inspector card drops `overflow-y: scroll`, which drew a
permanent empty track beside the real scrollbar.

At 1600x1000 the canvas goes from 913 in a 946 slot to 959 in a 959 slot, and
the Inspector content from 260 to 309 against a 320 tab strip. Boxes that keep a
gutter go from 65 to 3. The three scroll boxes keep the same gutter with short
and long content: 11, 12, 11 both ways.

Verified headless on Chrome at 1512x982 and 1600x1000, both themes, every panel
collapsed and expanded, and a window resize. npm run typecheck, npm run lint,
npm test -w packages/kite3d (10 tests), npm run test:scripts (2 tests).
@pythonlearner1025
pythonlearner1025 deleted the fix/canvas-gutter branch September 17, 2026 02:51
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