Repository navigation
feat(html): run physics while dragging a node in graph.html - #4864
afrianluthfan wants to merge 1 commit into
Conversation
graph.html turns physics off after the initial layout, so a dragged node stays wherever it is dropped and its neighbours do not react. Dragging a node now turns physics back on. Nodes pushed too close repel each other, and edges pull a node dragged too far back toward its neighbours. Physics turns off again on the next 'stabilized' event after the drag ends. Panning the view leaves physics off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the pull request, @afrianluthfan. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Not checked: tests were not run; formal verification proved 0 of 2 changed function(s) (1 sampled, not proven).
Formal verification. 1 change(s) alter behavior, breaking input(s) attached. PR-changed functions: 2/2 verified (0 proven, 1 may-equivalent, 1 distinguished) · 0 not verified.
Behavior changes: \_html\_script changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_html\_script behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.
Evidence: On input \{"nodes\_json":"''","edges\_json":"''","legend\_json":"'\\\\x00'"\}, the old code produced '\<script\>\\nconst RAW\_NODES = ;\\nconst RAW\_EDGES = ;\\nconst LEGEND = \\x00;\\n\\n// HTML\-escape helper — prevents XSS when injecting graph data into innerHTML\\nfunction esc\(s\) \{\\n return… but the new code produces '\<script\>\\nconst RAW\_NODES = ;\\nconst RAW\_EDGES = ;\\nconst LEGEND = \\x00;\\n\\n// HTML\-escape helper — prevents XSS when injecting graph data into innerHTML\\nfunction esc\(s\) \{\\n return…. Paste that input straight into a regression test.
Graphify review — findings
Adds physics back on when a node is dragged in the exported graph.html, so nodes pushed together repel and edges pull an over-dragged node back toward its neighbours. A stabilized handler turns physics off again once the layout settles after the drop, and the initial freeze after stabilizationIterationsDone is unchanged. The re-enable is gated on params.nodes being non-empty in the dragStart handler, which is meant to keep plain view pans from waking the simulation, though a pan while a node is selected is the case that gate has to get right.
Worth a look
- View pan with a node already selected re-enables physics —
graphify/exporters/html.py:260· Escalate · medium- NOT verified (no proof, no reproducing execution) — needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 518 functions depend on the 18 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
to_html()— 31 callers, 11 callees
Verification — 518 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 80 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
8 of 470 test file(s) selected (2%) via static blast radius.
tests/test_export.py— impacttests/test_export_direction.py— impacttests/test_html_drag_physics.py— impact, changed-testtests/test_html_script_breakout.py— impacttests/test_html_theme.py— impacttests/test_labeling.py— impacttests/test_pipeline.py— impacttests/test_watch.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Behavior changes: \_html\_script changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_html\_script behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.
Evidence: On input \{"nodes\_json":"''","edges\_json":"''","legend\_json":"'\\\\x00'"\}, the old code produced '\<script\>\\nconst RAW\_NODES = ;\\nconst RAW\_EDGES = ;\\nconst LEGEND = \\x00;\\n\\n// HTML\-escape helper — prevents XSS when injecting graph data into innerHTML\\nfunction esc\(s\) \{\\n return… but the new code produces '\<script\>\\nconst RAW\_NODES = ;\\nconst RAW\_EDGES = ;\\nconst LEGEND = \\x00;\\n\\n// HTML\-escape helper — prevents XSS when injecting graph data into innerHTML\\nfunction esc\(s\) \{\\n return…. Paste that input straight into a regression test.
No difference found (not proven): No behavior difference found in to\_html (not a proof).
The verifier ran both versions of to\_html on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 1 more finding(s) on lines outside this diff (see the check run).
|
Re the advisory "View pan with a node already selected re-enables physics": Checked this against vis-network 9.1.6: on a view pan, |
What does this PR do?
graph.htmlturns physics off once the initial layout stabilizes. After that, a dragged node stays wherever it is dropped. Its neighbours do not react, so a node can be dropped on top of another or dragged far away from its group.This PR turns physics back on while a node is dragged:
forceAtlas2Basedrepulsion andavoidOverlap).stabilizedevent after the drag ends.Notes from the vis-network 9.1.6 source (the pinned version):
Network.setOptions({physics})only emitsstartSimulation. It does not re-run stabilization or refit the view, so the camera does not jump on drag.dragStartalso fires for view pans, withparams.nodesempty. The guard keeps pans from enabling physics.stabilizedcan fire while a node is still held, if the user pauses mid-drag.draggingNodekeeps physics on until the drag ends.Solver parameters and the initial
stabilizationIterationsDonefreeze are unchanged.Type of change
Verification & Invariants
Physics runs only during the initial layout and while a node drag is settling. It is off at rest and during view pans.
Limitations:
How was this tested?
tests/test_html_drag_physics.py(3 tests): the initial freeze is still present,dragStartreturns before enabling physics when no node is dragged, andstabilizeddisables physics only when no node is held. 2 of the 3 fail onupstream/v8.I also checked it manually in headless Chromium (Playwright) on a hub with 7 leaves plus a second small community:
The 8 failures are in
tests/test_ext_tagged_id_clash.pyandtests/test_skill_no_backtick_substitution.py. They fail the same way on a cleanupstream/v8checkout in my environment, so they are unrelated to this change.Graphify-specific checklist
uv run python -m tools.skillgen --bless) when changing their source fragments. (Not touched.)🤖 Generated with Claude Code