Skip to content

feat(html): add Hide orphans toggle to graph.html - #4863

Open
afrianluthfan wants to merge 1 commit into
Graphify-Labs:v8from
afrianluthfan:feat/html-hide-orphans
Open

afrianluthfan wants to merge 1 commit into
Graphify-Labs:v8from
afrianluthfan:feat/html-hide-orphans

Conversation

@afrianluthfan

Copy link
Copy Markdown

What does this PR do?

Adds a "Hide orphans" checkbox to graph.html, next to "Select All" in the Communities panel. When checked, nodes with degree 0 in the rendered graph are hidden. It is off by default, so the default output looks the same as before.

Community filtering and the new toggle both set the vis hidden flag, so they now share one predicate, isNodeHidden(n):

hiddenCommunities.has(n.community) || (hideOrphans && n.degree === 0)

The per-community checkbox and toggleAllCommunities() call isNodeHidden() and no longer write hidden: !cb.checked / hidden: hide directly. Without this, re-showing a community would un-hide its orphans while the toggle is on.

Hyperedge hulls already skip hidden nodes (network.getPositions returns nothing for them), so they need no change.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Tests or CI
  • Refactor
  • Security fix

Verification & Invariants

Visibility is one function of (community filter state, orphan toggle state). Toggling either control in any order gives the same result. The default render is unchanged apart from the new unchecked control.

  • Read the CONTRIBUTING.md guide.
  • Reproduced the issue and identified the invariant. (Feature, no bug to reproduce.)
  • Made the smallest fix necessary.
  • Added a regression test (if bug fix) or isolated boundary test.
  • Kept the PR description synchronized with the final implementation.
  • Documented any limitations / unsupported cases explicitly.

Limitations:

  • "Orphan" means degree 0 in the graph passed to to_html. A node whose only edge is a self-loop has degree 2 and stays visible.
  • In the aggregated community view (graphs over the viz node limit), a community node with no cross-community edges counts as an orphan.

How was this tested?

tests/test_html_hide_orphans.py (3 tests): orphan degree in RAW_NODES, checkbox present and unchecked by default, and every visibility update goes through isNodeHidden(). 2 of the 3 fail on upstream/v8.

I also checked it manually in headless Chromium (Playwright) on a 12-node graph with two orphans. Toggle on hid exactly the 2 orphans. They stayed hidden after a community off/on and after Select All off/on. Toggle off showed everything again.

uv run --frozen pytest tests/test_html_hide_orphans.py -q       # 3 passed
uv run --frozen pytest tests -q                                 # 8654 passed, 8 failed
uv run --frozen ruff check graphify/exporters/html.py tests/test_html_hide_orphans.py
uv run --frozen pyright graphify/exporters/html.py              # 0 errors

The 8 failures are in tests/test_ext_tagged_id_clash.py and tests/test_skill_no_backtick_substitution.py. They fail the same way on a clean upstream/v8 checkout in my environment, so they are unrelated to this change.

Graphify-specific checklist

  • I updated generated skill artifacts (uv run python -m tools.skillgen --bless) when changing their source fragments. (Not touched.)
  • I confirmed that AST/structural extraction remains deterministic (no ambient state dependencies like ENV variables).
  • I reviewed changes for security implications (no unsafe interpolation into shell/Python).
  • I confirmed no API keys or local-only graph data are included.
  • (If applicable) I disclosed AI authorship in my commit messages.

🤖 Generated with Claude Code

Adds a "Hide orphans" checkbox next to Select All in the Communities
panel. When checked, nodes with degree 0 in the rendered graph are hidden.

Community filtering and the orphan toggle now share one predicate,
isNodeHidden(), so re-showing a community keeps its orphans hidden while
the toggle is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

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.

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

Graphify reviewed this change.

No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.

Not checked: tests were not run; formal verification proved 0 of 3 changed function(s) (1 sampled, not proven).

Formal verification. 2 change(s) alter behavior, breaking input(s) attached. PR-changed functions: 3/3 verified (0 proven, 1 may-equivalent, 2 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.

Behavior changes: \_html\_styles changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_html\_styles behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This is a sound refutation from a mathematical proof (SMT solver, over a bounded input domain): a concrete input on which the two versions provably differ.


Graphify review — findings

Adds a "Hide orphans" checkbox to the graph.html legend controls, off by default, that hides nodes with zero edges in the rendered graph. Community visibility now goes through a single isNodeHidden predicate (community filtered out, or orphan with the toggle on), so the Select All and per-community checkboxes no longer un-hide orphans while the toggle is active. New tests check the checkbox's default state, the zero-degree values in the rendered node data, and that every visibility update uses the shared predicate.

No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 519 functions depend on the 19 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: to_html() — 31 callers, 11 callees

Verification — 519 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: 81 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 — impact
  • tests/test_export_direction.py — impact
  • tests/test_html_hide_orphans.py — impact, changed-test
  • tests/test_html_script_breakout.py — impact
  • tests/test_html_theme.py — impact
  • tests/test_labeling.py — impact
  • tests/test_pipeline.py — impact
  • tests/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.

Behavior changes: \_html\_styles changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_html\_styles behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This is a sound refutation from a mathematical proof (SMT solver, over a bounded input domain): a concrete input on which the two versions provably differ.

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

This branch has not been deployed

No deployments
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