Skip to content

fix(zoom): bridge html{zoom} in full-view panel + drag/popover coordinates#62

Merged
BorisTyshkevich merged 1 commit into
mainfrom
fix/zoom-coordinate-bridging
Jun 28, 2026
Merged

fix(zoom): bridge html{zoom} in full-view panel + drag/popover coordinates#62
BorisTyshkevich merged 1 commit into
mainfrom
fix/zoom-coordinate-bridging

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

Problem

The app ships html { zoom: 1.2 } to upscale a deliberately pixel-dense UI. But CSS zoom has two quirks that several spots got wrong:

  1. It does not divide viewport units. An in-flow 100vh-tall element lays out against the unzoomed viewport (800px) and is then scaled ×1.2 → 960px, overflowing the window by 20%.
  2. getBoundingClientRect() is post-zoom px, while values written to style.width / flexBasis / fixed top/right are pre-zoom layout px.

Symptoms found:

  • Schema full-view detail pane — the panel rendered 1.2× tall, pushing the CREATE TABLE DDL below the viewport with no way to scroll to it (the reported bug).
  • Sidebar splitter and the detail-pane resize handle drifted ~20% ahead of the cursor (visual-px delta applied as layout px).
  • Save popover / user menu mis-anchored (fixed top/right re-scaled by zoom; the gap read 15px instead of 6px).

Fix

  • Introduce a --zoom CSS variable; size the full-viewport graph panels (in-app overlay, schema tab, and the tab body) as calc(... / var(--zoom)) so they fit exactly one screen.
  • Bridge the splitter 'col' axis, the detail-pane resize handle, and the anchored popovers through the existing zoomScale() helper — measured once per drag, not per mousemove. dragValue stays pure (injected scale param).
  • Harden zoomScale() to fall back to 1 for Infinity/0/negative ratios, not just NaN (its docstring already promised this), since three new callers now rely on it.

The '%'-based splitter axes are intentionally left alone — they derive from a (clientY-top)/height ratio where zoom cancels.

Tests

New unit tests for every path (splitter scale, resize-handle scale, zoomScale fallbacks). splitters.js, schema-detail.js, and dom.js stay at 100% coverage; full suite 1010 passing, coverage gate green.

Verification

Built and deployed to antalya (play-sql.html), then drove the live cluster in-browser:

  • Schema full view of shop → click events_raw → pane overflows, scrolls to bottom, full DDL visible at y=775 within the 800px viewport. ✅
  • User menu anchors with a 7px gap (was 15px), right-aligned. ✅
  • Sidebar drag to cursor-x 360 → width 300px → rendered edge lands exactly at 360 (was drifting ~72px ahead). ✅

🤖 Generated with Claude Code

https://claude.ai/code/session_019kE9qbgBNBrfNgwg9fRsMJ

…ordinates

The page ships `html { zoom: 1.2 }`, but `zoom` does not divide viewport units,
and getBoundingClientRect returns post-zoom px while layout values (style.width,
flexBasis, fixed top/right) are pre-zoom. Several spots mixed the two:

- Schema full-view panel rendered 1.2× tall (100vh × zoom), pushing the detail
  pane's CREATE TABLE DDL below the viewport with no way to scroll to it.
- Sidebar splitter and the detail-pane resize handle drifted ~20% ahead of the
  cursor (visual-px delta applied as layout px).
- Save popover / user menu mis-anchored (fixed top/right re-scaled by zoom).

Fixes:
- Introduce a `--zoom` CSS variable; size the full-viewport graph panels (overlay,
  schema tab, and the tab body) as `calc(... / var(--zoom))` so they fit exactly.
- Bridge the splitter ('col' axis), the detail-pane resize handle, and the
  anchored popovers through the existing `zoomScale()` helper; measure the zoom
  once per drag, not per mousemove.
- Harden `zoomScale()` to fall back to 1 for Infinity/0/negative ratios, not just
  NaN (its docstring already promised this), since three new callers rely on it.

`dragValue` stays pure (injected `scale` param). Tests added for every new path;
splitters/schema-detail/dom remain at 100% coverage. All 1010 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kE9qbgBNBrfNgwg9fRsMJ
@BorisTyshkevich BorisTyshkevich merged commit a0a22f8 into main Jun 28, 2026
6 checks passed
BorisTyshkevich added a commit that referenced this pull request Jun 28, 2026
Coordinate-system release: bridges the shipped `html{zoom:1.2}` across the
schema full-view panel and the splitter/resize-handle/popover math (#62), so the
full view fits one screen (detail-pane DDL no longer hidden) and drags/popovers
track the cursor. No new runtime deps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kE9qbgBNBrfNgwg9fRsMJ
BorisTyshkevich added a commit that referenced this pull request Jun 28, 2026
Code-review follow-ups for the zoom-bridge (#62) and schema-graph (#63) PRs.

Correctness / behavior:
- ch-client: the transitive-lineage nodeCap now counts only *linked* nodes, not
  every standalone table — so a single large DB of mostly-unrelated tables no
  longer truncates on round 1 and skips its cross-DB lineage walk (#63 review).
- schema-graph: guard the db-prefix label strip with `&& n.name` so a node with
  an empty name can't be rewritten to a blank title (#63 review).

Altitude / cleanup:
- dot-layout: split the schema-specific "lineage first, singles gridded below"
  policy out of the generic dagre wrapper into a dedicated `schemaLayout()`;
  `dagreLayout` reverts to its plain generic form (no opts). Drops the dead
  `usedCols` (== cols) term and the always-built `connected` set on the pipeline
  path (#62/#63 review).
- dom: extract the fixed-popover anchor recipe (divide client coords by the zoom)
  into a pure, 100%-covered `fixedAnchor()` helper; the File menu and the
  Save/user popover now share it instead of hand-transcribing the math — which
  also moves the previously-untested anchoredPopover arithmetic under test (#62).
- app: simplify `dragCtx.scale` to report the page zoom without the redundant
  per-axis ternary (dragValue already ignores scale for the % axes) (#62).
- dom: note in zoomScale that the measured element is immaterial (page-global) (#62).

Tests added for schemaLayout (grid extent + no-lineage + no-singles + empty),
fixedAnchor (both corners + floors), the ·inner label-skip, and the
linked-only nodeCap. All 1020 tests pass; dot-layout/dom back to 100% coverage.


Claude-Session: https://claude.ai/code/session_019kE9qbgBNBrfNgwg9fRsMJ

Co-authored-by: Claude Opus 4.8 (1M context) <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