feat(export): opt-in Focus Lens for graph.html — read dense regions as a layered tree - #2321
feat(export): opt-in Focus Lens for graph.html — read dense regions as a layered tree#2321katiyar-anuj wants to merge 3 commits into
Conversation
…s a layered tree
A movable lens for the interactive graph viewer: a small dotted capture
region follows the cursor and selects the nodes under it (live positions,
legend filtering respected); a larger display panel re-lays-out that
selection as a layered tree (longest-path levels, wrapped rows, capped at
the most-connected 30) that updates as the mouse moves. Double-click or 'h'
holds the lens — hover a box for the longer label + source tooltip, click
one to inspect it in the existing sidebar; Esc releases, then exits.
Scope kept deliberately tight for review:
- Off by default; toggled in-page ('f' / Lens button). No CLI surface, so
generated skill artifacts are untouched (skillgen --check: 134 OK).
- Emitted only for graphs with >=15 nodes and >=2 communities (one with >=2
members — suppresses the aggregated community meta-graph). Gated-off
output is byte-identical to pre-feature HTML (test-enforced).
- No new dependencies: vanilla JS on the existing vis.js canvas via the
same afterDrawing hook the hyperedge hull uses.
- No new XSS surface (Graphify-Labs#1838 discipline): all lens text renders via canvas
fillText from the already-sanitized labels; the sole DOM sink is the HUD
count via textContent; inspect clicks delegate to the audited showInfo().
Tests pin a sink denylist (innerHTML/outerHTML/insertAdjacentHTML/
document.write/srcdoc) plus hostile node- and community-label fixtures.
Tests: 8 new (gate boundary at 14/15 nodes, single-community and
meta-graph suppression, unlabeled-build emission, static-block equality,
sink denylist, determinism). Full suite green; ruff clean; inline scripts
verified with node --check.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This pull request removes the version 0.9.30 changelog entry from CHANGELOG.md along with several related features it described, and reverts the associated code changes. Affected areas include the cache module's root-portability logic (the _ROOT_MARKER re-anchoring machinery for warm AST-cache hits after a corpus move), the merge-graphs edge-direction preservation in build.py, and the Bedrock backend's timeout/text-block handling, plus documentation references in the README for GRAPHIFY_MAX_CONTEXTS and Bedrock timeouts. The changed symbols span extractors (resolution, XAML, imports, bash detection), LLM backends, the MCP serve tooling, CLI stale-graph handling, and corresponding tests. Reviewers should focus on whether this is an intended revert of the 0.9.30 release and its full surface area.
Worth a look
- Global graph prefixing no longer rewrites directional edge attributes —
graphify/build.py:1450· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3043 functions depend on the 1252 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
to_html()— 24 callers, 9 callees - worse:
make_graph()— 24 callers, 1 callees
Verification — 3043 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: 3005 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| })(); | ||
| </script>""" | ||
|
|
||
| def to_html( |
There was a problem hiding this comment.
to_html()
fans out to 9 callees (efferent coupling); 24 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
A tooltip open at the moment the lens is toggled would sit over the tree panel forever: the lens disables interaction.hover, so the blurNode that normally clears it never fires. Hide it on enter; the next hover after exit recreates it naturally.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This pull request reverts changes associated with the unreleased 0.9.30 version. It removes the 0.9.30 entry from the CHANGELOG and strips related documentation (the GRAPHIFY_MAX_CONTEXTS env var, the Bedrock API timeout note, and the focus-lens README mention). On the code side, it removes the root-anchored cache portability logic (_ROOT_MARKER, _id_anchor, _portability_anchors, and related helpers in cache.py) and reverts prefix_graph_for_global in build.py to no longer rewrite edge _src/_tgt directional attributes. The changed test symbols span exports, image/vision, serve HTTP, cache, merge-graphs, and global-graph areas, consistent with rolling back those features.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3043 functions depend on the 1252 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
to_html()— 24 callers, 9 callees - worse:
make_graph()— 24 callers, 1 callees
Verification — 3043 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: 3005 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| })(); | ||
| </script>""" | ||
|
|
||
| def to_html( |
There was a problem hiding this comment.
to_html()
fans out to 9 callees (efferent coupling); 24 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
vis popups are driven by interaction.tooltipDelay, not interaction.hover, so disabling hover left edge/node titles popping up over the lens panel for whatever sat behind it. Push tooltipDelay out of reach on enter, restore it on exit, and swat any popup an in-flight pre-enter timer managed to open.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This pull request appears to revert the changes associated with the 0.9.30 release. It removes the 0.9.30 changelog entry along with its documented fixes, and correspondingly strips out the related code: the AST-cache root re-anchoring/portability logic in cache.py, the edge direction (_src/_tgt) rewiring in build.py's prefix_graph_for_global, and the GRAPHIFY_MAX_CONTEXTS / Bedrock-timeout documentation from the README. The diff also touches extractors (PHP/Swift/Lua/XAML/resolution), LLM backend call paths (Claude/Azure/Bedrock, image/file reading), serve/CLI code, and numerous test files spanning cache, export, and HTTP-serve suites. Reviewers should confirm whether this is an intentional full or partial rollback of the 0.9.30 fixes and check that the surface area across extractors, backends, and tests is consistent with that intent.
Worth a look
- prefix_graph_for_global no longer rewrites _src/_tgt edge attributes —
graphify/build.py:1460· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3043 functions depend on the 1252 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
to_html()— 24 callers, 9 callees - worse:
make_graph()— 24 callers, 1 callees
Verification — 3043 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: 3005 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| })(); | ||
| </script>""" | ||
|
|
||
| def to_html( |
There was a problem hiding this comment.
to_html()
fans out to 9 callees (efferent coupling); 24 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
What
An opt-in Focus Lens for the interactive
graph.htmlviewer: a small dotted capture region follows the cursor and selects the nodes under it; a larger display panel re-lays-out that selection as a layered tree (longest-path levels, width-aware wrapped rows, community-colored boxes, arrowed edges) that updates live as the mouse moves. Dense hairball regions become readable flow diagrams in place.Interaction
f/ Lens button toggles it (off by default — the existing explorer is untouched until then)[]resize the capture region; arrow keys nudgehholds the lens: hover a box for the longer label +file_type · source_filetooltip (its edges highlight), click a box to inspect it in the existing sidebar panel;Escreleases, then exitsWhy
Force-directed layouts are great for global structure and bad for reading a specific dense region. The lens gives a magnifier-style focus+context view — capture small, display big — without leaving the page, adding a mode, or touching the data model.
Scope decisions (kept deliberately tight)
skillgen --check: 134 artifacts OK.learning_overlayinert-when-unused guarantee — test-enforced.communities, notcommunity_labels— unlabeled builds (--no-label, missing labels sidecar) keep the lens.afterDrawinghook the hyperedge hull uses; single-file HTML, offline, no build step.Security
Follows the #1838 discipline: all lens text renders via canvas
fillText(inert sink) from the already-sanitize_label'd labels; the sole DOM sink is the HUD count viatextContent; click-to-inspect delegates to the existing auditedshowInfo(). The lens block itself is a static, data-free constant. Tests pin a sink denylist (.innerHTML,.outerHTML,insertAdjacentHTML,document.write,srcdoc) with hostile node- and community-label fixtures.Testing
8 new tests in
tests/test_export.py:uv run pytest tests/ -qgreen (3792 passed; the 4test_ollama_retry_capfailures are a missing optionalopenaidep locally and reproduce on cleanv8).ruffclean,pyrightclean on new code, all inline scripts verified withnode --check.Try it
Happy to adjust anything — naming, gate thresholds, default sizes, or splitting the HUD copy — to fit maintainer preferences. A
--viz lensCLI dial could be a follow-up if #2235's--vizconvention lands.