fix(zoom): correctly size fullscreen graph panels on Safari under html{zoom} (#70)#92
Merged
Merged
Conversation
…l{zoom} (#70)
The whole UI rides on `html { zoom: var(--zoom) }`. The pointer/caret/drag math
self-calibrates to the live getBoundingClientRect/offsetWidth ratio (the zoom
factor on Chromium, 1 on WebKit/Safari), so it was already correct on every
engine. The one genuine divergence is viewport units under `zoom`: Chromium's
vw/vh ignore it (100vh overshoots one screen by the zoom factor) while
WebKit/Safari's track it (100vh == one screen). The fullscreen schema / EXPLAIN
graph panels size off vw/vh and hard-coded `calc(.../var(--zoom))`, which shrank
them to ~83% on Safari.
Fix: measure the actual overshoot at runtime (a 100vh probe vs the one-screen
#root) and publish it as --vp-zoom (~--zoom on Chromium, ~1 on Safari); the panels
divide by --vp-zoom, so they fit exactly one screen on both. --vp-zoom defaults to
var(--zoom) (Chromium-correct) when unmeasurable, and the opener mirrors it onto
the schema graph's child tab. A `@supports not (zoom: 1)` rule still neutralizes
the factor on engines that can't parse `zoom` at all.
- src/core/zoom-support.js: pure viewportZoom(vhPx, refPx) (100% covered).
- src/ui/app.js: app.applyViewportZoom() measures + publishes --vp-zoom via an
injected measurement seam; run from renderApp.
- src/ui/explain-graph.js: mirrorTheme carries --vp-zoom onto the child tab.
- src/styles.css: --vp-zoom default + the three panels divide by it.
- src/ui/icons.js: centred + symmetric expand glyph so it renders consistently
across engines at ~12px (it previously blurred into solid brackets on
Chrome/Firefox while staying crisp on Safari).
- e2e: tests/e2e/zoom-support.spec.js asserts the panel-fit mechanism on all
three engines. NOTE: Playwright WebKit applies zoom to rects like Chromium, not
like real Safari, so the real-Safari path is verified manually (#71).
Supersedes the abandoned boot-time "mis-scaling banner" approach: the rect ratio
can't distinguish a working Safari (ratio 1.0) from a broken engine, so that guard
false-positived on Safari — the very engine #70 targeted.
Closes #70. Part of #68 (Phase 0 release-blocker).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5JoqoESpfq4cFibmQX18C
6b1ede1 to
8ea9049
Compare
3 tasks
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.
What & why
The whole UI rides on
html { zoom: var(--zoom) }. The pointer/caret/drag math self-calibrates to the livegetBoundingClientRect/offsetWidthratio (the zoom factor on Chromium,1on WebKit/Safari), so it was already correct on every engine. The genuine divergence is viewport units underzoom: Chromium'svw/vhignore it (100vhovershoots one screen by the zoom factor) while WebKit/Safari's track it (100vh== one screen). The fullscreen schema / EXPLAIN graph panels size offvw/vhand hard-codedcalc(.../var(--zoom)), which shrank them to ~83% on Safari.Per the two-step plan in #68 / the issue comment, this is step 1 (the 1.0 fix); step 2 (full
zoomremoval) stays folded into the Phase 3/5 coordinate-path rewrites (#21/#66).The fix
app.applyViewportZoom()measures the real overshoot (a100vhprobe vs the one-screen#root) and publishes it as--vp-zoom(~--zoomon Chromium, ~1on Safari). The three fullscreen panels divide theirvw/vhby--vp-zoom, so they fit exactly one screen on both engines. PureviewportZoom()insrc/core/zoom-support.js(100% covered); the DOM measurement is an injected seam; the opener mirrors--vp-zoomonto the schema graph's child tab.--vp-zoomdefaults tovar(--zoom)(Chromium-correct) when unmeasurable, so behaviour never regresses;@supports not (zoom: 1)still neutralizes the factor on engines that can't parsezoomat all.src/ui/icons.js) — re-centred + symmetric so it renders consistently at ~12px instead of blurring into solid[ ]brackets on Chrome/Firefox (a pre-existing cosmetic bug surfaced during manual Safari testing; folded in here as it's the Expand button that opens these panels).This supersedes the earlier abandoned "mis-scaling banner" approach: the rect ratio can't tell a working Safari (ratio
1.0) from a broken engine, so that guard false-positived on Safari — the very engine #70 targeted.Verification
zoom-support.js100/100/100/100), build clean.1.2, panel fits with just the 48px padding gap; icon clean.tests/e2e/zoom-support.spec.jsguards the panel-fit mechanism on all three CI engines. Caveat: Playwright's WebKit applieszoomtogetBoundingClientRect/viewport units like Chromium, not like real Safari, so it is not a faithful Safari proxy for this behaviour — the real-Safari path is verified manually (tracked in the Document the supported-browser matrix (browsers, ClickHouse versions, IdP requirements) #71 matrix).Acceptance (issue #70)
--vp-zoom+@supports).zoom(CSS 1× fallback for parse-failure; runtime-measured divisor for the viewport-unit divergence).npm test+ e2e green; per-file coverage gate holds.Checklist
npm testpasses (the per-file coverage gate is non-negotiable)npm run buildsucceeds (single-filedist/sql.html)src/core/, DOM behind injected seamsCHANGELOG.md([Unreleased]) updated🤖 Generated with Claude Code