Skip to content

fix(graph): make the graph root a containing block for layers (#414) - #425

Merged
draedful merged 6 commits into
v2from
claude/fix-414-root-containing-block-1e08fb
Oct 22, 2026
Merged

draedful merged 6 commits into
v2from
claude/fix-414-root-containing-block-1e08fb

Conversation

@draedful

@draedful draedful commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #414

Problem

Core layers are .layer { position: absolute; inset: 0 }, but Graph.attach(root) did not make the root positioned; only the React GraphCanvas wrapper did. In a vanilla app with a static root the layers were laid out against an outer positioned ancestor or the viewport: the canvas covered the page, and pointer hit-testing (canvas-relative) disagreed with the camera and graph-playwright (root-relative), so clicks landed ~25px off. Package-contract apps worked around it with style="position: relative" on #graph.

Fix

Option 1 from the issue, done so that the application's own positioning is never overridden:

  • Graph.attach(root) adds the graph-root class (exported as GRAPH_ROOT_CLASS_NAME); a full detach (detach(), unmount(), moving to another root) removes it.
  • Core CSS: @layer gravity-graph { :where(.graph-root) { position: relative; } }. Zero specificity + its own cascade layer: only a root that would otherwise be static changes; unlayered app CSS, inline styles and later app layers still win. No getComputedStyle, so it works when the root is attached before it is in the DOM or before its CSS loads.
  • Roots are tracked per graph (markGraphRoot / unmarkGraphRoot in utils/graphInstance.ts): the class stays while another graph still uses the root, and is never removed if the application had set it itself.

React GraphCanvas is unchanged: its root .graph-canvas is position: absolute, which beats the zero-specificity rule.

The position: relative workarounds are removed from the four package-contract apps.

Tests

  • Unit (graph.test.ts): attach/detach, stop() vs full detach, unmount(), moving to another root, two graphs on one root, a class set by the application, inline position untouched.
  • Package contracts (packages/graph/contract/app/graph.pw.ts and packages/graph-playwright/contract/app/graph.pw.ts): the installed public styles make the canvas fill the root's padding box with padding and border, application positioning still applies in unlayered CSS and a later @layer app, and the existing connection-click scenario runs without a manual root-positioning workaround.

node scripts/package-contract/run.mjs graph graph-playwright passes, including packed artifacts, public consumer type checks, imports, bundling, and Chromium scenarios. The changed test passes Prettier. Regression checks against a temporary browser bundle fail when the root rule is removed or moved outside its cascade layer, and pass after restoration. The full unit and E2E suites were not rerun for this test consolidation.

Docs

The v1→v2 migration guide gets a checklist entry and a "Graph root positioning" section.

Known limitation: cascade layer order follows first declaration. If an app declares its layers (e.g. Tailwind v4 theme, base, components, utilities) before the graph styles load, gravity-graph comes last and wins over them; the migration guide shows how to list gravity-graph first. A JS fallback (add the class only when the computed position is static) is possible if this turns out to matter.

🤖 Generated with Claude Code

Summary by Sourcery

Make graph-attached roots containing blocks for their layers without overriding application positioning.

New Features:

  • Make attached graph roots containing blocks for absolutely positioned layers through the exported graph-root class and public positioning styles.
  • Export GRAPH_ROOT_CLASS_NAME for consumers that need to reference the graph root class.

Bug Fixes:

  • Fix canvas layout and pointer-coordinate offsets for vanilla graphs attached to statically positioned roots.

Enhancements:

  • Preserve application-controlled root positioning and correctly manage root markers across attach, detach, unmount, stop, root moves, and multiple graphs.

Documentation:

  • Document graph root positioning behavior and remove obsolete positioning workarounds from the v1-to-v2 migration guidance.

Tests:

  • Add unit and package-contract coverage for root lifecycle management, positioning precedence, canvas bounds, and consumer styles.

draedful and others added 2 commits October 19, 2026 13:15
Graph.attach() adds a `graph-root` class to the root and a full detach
removes it. Core styles declare `:where(.graph-root) { position: relative }`,
so a static root contains the absolutely positioned layers while any
position set by the application still wins.

Removes the `position: relative` workarounds from package-contract apps,
adds unit and E2E coverage, and documents the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up: the unlayered :where() rule beat application styles
declared in cascade layers (e.g. Tailwind v4 utilities). Move the rule
into the `gravity-graph` layer, track which graphs mark a root so the
class stays while another graph uses it and is never removed when the
application set it, export GRAPH_ROOT_CLASS_NAME, and document the
layer order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@draedful
draedful requested a review from Antamansid as a code owner October 19, 2026 11:06
@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Sorry @draedful, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 1 hour by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

Graph attachment now marks its root with an exported graph-root class, whose layered zero-specificity CSS makes static roots containing blocks for absolute layers while preserving application positioning. Root ownership and cleanup are reference-counted across graph lifecycles, with unit and E2E coverage plus updated contracts and documentation.

File-Level Changes

Change Details Files
Automatically makes attached graph roots containing blocks without overriding application positioning.
  • Adds and exports the graph-root class name.
  • Applies position: relative through a zero-specificity rule in the gravity-graph cascade layer.
  • Preserves inline, unlayered, later-layer, and application-owned class positioning behavior.
packages/graph/src/graph.ts
packages/graph/src/index.ts
packages/graph/src/services/Layer.css
packages/graph/src/utils/graphInstance.ts
Tracks graph ownership of root markers across attach, detach, unmount, stop, root moves, and shared roots.
  • Adds per-root graph tracking with reference-counted class cleanup.
  • Removes the marker only after the last graph fully detaches.
  • Retains a graph-root class that was originally set by the application.
packages/graph/src/graph.ts
packages/graph/src/utils/graphInstance.ts
packages/graph/src/graph.test.ts
Adds regression coverage for static-root layout, pointer hit-testing, and CSS precedence.
  • Verifies canvas bounds align with a padded and bordered static root.
  • Verifies connection clicks select correctly relative to the root.
  • Verifies application-defined positions remain effective in unlayered CSS and cascade layers.
apps/e2e/pages/static-root.html
apps/e2e/tests/static-root.spec.ts
Removes obsolete vanilla-root positioning workarounds and documents the new root contract.
  • Removes inline position: relative from package-contract applications.
  • Documents the exported class, lifecycle behavior, cascade-layer ordering, and migration guidance in English and Russian.
packages/graph-css-variables/contract/app/index.html
packages/graph-devtools/contract/app/index.html
packages/graph-duplicate-block/contract/app/index.html
packages/graph-playwright/contract/app/index.html
packages/graph/README.md
packages/graph/README-ru.md
packages/graph/docs/migration-guides/v1-to-v2.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gravity-ui-bot

Copy link
Copy Markdown
Contributor

Preview is ready.

@draedful draedful linked an issue Oct 19, 2026 that may be closed by this pull request
3 tasks
draedful and others added 4 commits October 19, 2026 17:21
Add the class on attach and remove it on full detach, like the instance
bridge, instead of tracking graphs per root: a root hosts one graph.
Declare the root rule in the package's styles.css entry instead of
Layer.css, since it styles the graph root rather than a layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@draedful
draedful merged commit e5f38c7 into v2 Oct 22, 2026
8 checks passed
@gravity-ui gravity-ui Bot mentioned this pull request Oct 22, 2026
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.

Graph root is not a containing block for absolutely positioned layers

2 participants