Skip to content

feat(export): opt-in Focus Lens for graph.html — read dense regions as a layered tree - #2321

Open
katiyar-anuj wants to merge 3 commits into
Graphify-Labs:v8from
katiyar-anuj:feat/export-focus-lens
Open

feat(export): opt-in Focus Lens for graph.html — read dense regions as a layered tree#2321
katiyar-anuj wants to merge 3 commits into
Graphify-Labs:v8from
katiyar-anuj:feat/export-focus-lens

Conversation

@katiyar-anuj

Copy link
Copy Markdown

What

An opt-in Focus Lens for the interactive graph.html viewer: 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)
  • Move the mouse to sweep; [ ] resize the capture region; arrow keys nudge
  • Double-click / h holds the lens: hover a box for the longer label + file_type · source_file tooltip (its edges highlight), click a box to inspect it in the existing sidebar panel; Esc releases, then exits
  • The HUD legend explains the two rectangles (dotted = capture area, solid = tree panel)

Why

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)

  • No CLI surface. In-page toggle only, so generated skill artifacts are untouched — skillgen --check: 134 artifacts OK.
  • Byte-identical when inactive. The lens block is emitted only for graphs with ≥15 nodes and ≥2 communities (one with ≥2 members — this also suppresses it in the aggregated community meta-graph, where every community is a single super-node). Gated-off output is byte-identical to pre-feature HTML, mirroring the learning_overlay inert-when-unused guarantee — test-enforced.
  • Gates on communities, not community_labels — unlabeled builds (--no-label, missing labels sidecar) keep the lens.
  • No new dependencies. Vanilla JS drawn on the existing vis.js canvas via the same afterDrawing hook the hyperedge hull uses; single-file HTML, offline, no build step.
  • Respects existing UI state: legend-hidden communities are excluded from capture; node positions are read live (node drags honored); vis selection is disabled while the lens is on so clicks can't act on invisible nodes behind the panel; browser chords (Cmd/Ctrl+F etc.) are never intercepted.

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 via textContent; click-to-inspect delegates to the existing audited showInfo(). 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:

  • emission with static-block equality (pins placement + staticness in one assertion)
  • emission for unlabeled builds
  • gate boundary exactly at 14/15 nodes; single-community and aggregated meta-graph suppression; small-graph absence incl. no partial CSS leakage
  • sink denylist + hostile-label fixtures
  • byte-determinism

uv run pytest tests/ -q green (3792 passed; the 4 test_ollama_retry_cap failures are a missing optional openai dep locally and reproduce on clean v8). ruff clean, pyright clean on new code, all inline scripts verified with node --check.

Try it

graphify extract <some-repo> --code-only --out /tmp/lens-demo
# render graph.html from /tmp/lens-demo/graphify-out/graph.json, open in a browser
# press f, glide over a dense hub, double-click to hold, hover/click the boxes

Happy to adjust anything — naming, gate thresholds, default sizes, or splitting the HUD copy — to fit maintainer preferences. A --viz lens CLI dial could be a follow-up if #2235's --viz convention lands.

…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.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 attributesgraphify/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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionto_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.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionto_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.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 attributesgraphify/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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionto_html()

fans out to 9 callees (efferent coupling); 24 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

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