Skip to content

Fix: clicking into a filtered-out community no longer dies silently (#3378) - #3457

Open
shivamtiwari3 wants to merge 1 commit into
Graphify-Labs:v8from
shivamtiwari3:fix/3378-focus-hidden-community
Open

Fix: clicking into a filtered-out community no longer dies silently (#3378)#3457
shivamtiwari3 wants to merge 1 commit into
Graphify-Labs:v8from
shivamtiwari3:fix/3378-focus-hidden-community

Conversation

@shivamtiwari3

Copy link
Copy Markdown

Fixes #3378.

The bug

The community filter hides nodes rather than removing them, and vis has no rendered position for a hidden node. So for a target in an unticked community, two of focusNode()'s three actions were no-ops:

call result
network.focus(nodeId, …) no rendered position — silent no-op
network.selectNodes([nodeId]) selects something invisible
showInfo(nodeId) works, reads the underlying data

The panel navigated to the new node and listed its neighbours while the canvas sat still, with nothing saying why. A dead click.

The fix

focusNode reveals the target's community first, and does it by firing that community's legend checkbox's own change handler rather than duplicating its body — so hiddenCommunities, the dimmed styling, nodesDS and the Select-All tri-state all stay consistent through one code path. This is the approach @evaldnet suggested and had been running locally.

Only the target's community is revealed; the rest of the filter the user set up survives the click. The issue also offered an alternative (annotate/disable links into hidden communities instead), but making the click work seems strictly better than explaining why it can't — happy to switch if you'd rather the filter never change implicitly.

One thing beyond the report: the search-result handler carried its own copy of focusNode's body and had the identical defect — a search hit can land in a filtered-out community just as a neighbour can. It goes through focusNode now, which takes the zoom as a parameter so search keeps the 1.5 it has always used and neighbour focus keeps 1.4.

Tests

Four tests in tests/test_export.py. Three of them execute the emitted viewer script under node against a stub DOM and vis, following the precedent of test_hyperedge_convex_hull_js_is_geometrically_sound (skipped when node is absent).

The stub models focus() the way real vis behaves for a hidden node — a silent no-op — so what the tests assert is observable browser state: what the camera focused, what is selected, which nodes are hidden, and the legend's own checkboxes and tri-state. Test setup addresses the checkboxes through the DOM rather than through the map this fix adds, so the pre-fix failure is the reported symptom (assert None == 'b' — "focus() was still refused") rather than a ReferenceError.

  • the reported repro: hide all → tick one community → click a neighbour in a hidden one; asserts focus/selection land, only that community is revealed, and Select-All goes indeterminate
  • focusing a node whose community is already visible does not touch the filter (this one passes before the fix too — it guards against over-revealing)
  • a search hit in a filtered-out community is focused the same way
  • the two zoom levels are unchanged and search no longer carries a copy of focusNode's body

3 of the 4 fail on unpatched code. Full suite: 5497 passed, with only the 3 pre-existing environment-dependent failures on this tree (test_extract_code_only_cli, 2× test_ollama) unchanged.

🤖 Generated with Claude Code

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

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

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

Reveals a filtered-out community when you click a neighbor link or search hit into it, instead of leaving the canvas untouched while only the NODE INFO panel updates — focusNode now checks whether the target's community is in hiddenCommunities and, if so, re-ticks that community's legend checkbox and fires its own change handler before calling focus()/selectNodes(), so only that one community is revealed and the rest of the user's filter survives. Routes the search-result click through focusNode (keeping its 1.5 scale) rather than a duplicated copy of its body, and registers each legend checkbox in a new CB_BY_CID map so the reveal reuses the checkbox's existing handler. Adds a Node-based harness with a stubbed DOM/vis that models a hidden node's focus() as a no-op to assert the reveal, selection, and legend tri-state behavior.

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

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 636 functions depend on the 340 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 115 callers, 51 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: to_html() — 17 callers, 11 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: watch() — 5 callers, 7 callees
  • new: _reconcile_graph_html() — 6 callers, 5 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 636 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: 361 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

262 of 262 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • tests/test_csharp_enum_members.py — full-run-safety
  • tests/test_csharp_field_generic_args.py — full-run-safety
  • tests/test_csharp_generic_callsites.py — full-run-safety
  • … and 212 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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.

· 7 more finding(s) on lines outside this diff (see the check run).

…raphify-Labs#3378)

The community filter hides nodes rather than removing them, and vis has no
rendered position for a hidden node. So for a target in an unticked
community, two of focusNode()'s three actions were no-ops: focus() did
nothing and selectNodes() selected something invisible. Only showInfo()
worked, because it reads the underlying data. The panel navigated to the
new node and listed its neighbours while the canvas sat still, with nothing
saying why — a dead click.

focusNode now reveals the target's community first, and does it by firing
that community's legend checkbox's own change handler rather than
duplicating its body, so hiddenCommunities, the dimmed styling, nodesDS and
the Select-All tri-state all stay consistent through one code path. Only
the target's community is revealed; the rest of the filter the user set up
survives the click.

The search-result handler carried its own copy of focusNode's body and had
the identical defect — a search hit can land in a filtered-out community
just as a neighbour can. It goes through focusNode now, which takes the
zoom as a parameter so search keeps the 1.5 it has always used.

Tests execute the emitted viewer script under node against a stub DOM and
vis, following test_hyperedge_convex_hull_js_is_geometrically_sound. The
stub models focus() the way real vis behaves for a hidden node — a silent
no-op — so what the tests assert is observable browser state: what the
camera focused, what is selected, which nodes are hidden, and the legend's
own checkboxes and tri-state. Setup addresses the checkboxes through the
DOM rather than through the map this fix adds, so the pre-fix failure is
the reported symptom rather than a ReferenceError.
@shivamtiwari3
shivamtiwari3 force-pushed the fix/3378-focus-hidden-community branch from 46c65aa to 3eaa2c6 Compare September 10, 2026 05:16

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

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

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

Fixes dead clicks in graph.html when the target's community is filtered out: focusNode now reveals that one community by firing its legend checkbox's change handler before calling network.focus/selectNodes, so hiddenCommunities, the dimmed styling, nodesDS, and the Select-All tri-state all stay consistent through the existing code path. Search-result clicks route through focusNode (at their usual 1.5 scale) instead of duplicating its body, so they too can reach a hidden node, and a new CB_BY_CID map lets focusNode find each community's checkbox. Only the clicked node's community is un-hidden, leaving the rest of the user's filter intact.

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

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 636 functions depend on the 340 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 115 callers, 51 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: to_html() — 17 callers, 11 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: watch() — 5 callers, 7 callees
  • new: _reconcile_graph_html() — 6 callers, 5 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 636 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: 361 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

262 of 262 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — full-run-safety
  • tests/test_analyze.py — full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — full-run-safety
  • tests/test_astro_import_ids.py — full-run-safety
  • tests/test_atomic_canvas_export.py — full-run-safety
  • tests/test_atomic_version_stamp.py — full-run-safety
  • tests/test_atomic_writes.py — full-run-safety
  • tests/test_backend_extras.py — full-run-safety
  • tests/test_benchmark.py — full-run-safety
  • tests/test_benchmark_raw_graph.py — full-run-safety
  • tests/test_build.py — full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — full-run-safety
  • tests/test_build_merge_shrink_guard.py — full-run-safety
  • tests/test_builtin_global_type_refs.py — full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — full-run-safety
  • tests/test_case_sensitive_resolution.py — full-run-safety
  • tests/test_charmap_encoding.py — full-run-safety
  • tests/test_chunking.py — full-run-safety
  • tests/test_cjs_module_extension.py — full-run-safety
  • tests/test_claude_cli_backend.py — full-run-safety
  • tests/test_claude_md.py — full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — full-run-safety
  • tests/test_codebuddy.py — full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — full-run-safety
  • tests/test_corrupt_graph_json.py — full-run-safety
  • tests/test_cpp_nested_and_cli.py — full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — full-run-safety
  • tests/test_cross_language_call_resolution.py — full-run-safety
  • tests/test_cross_repo_member_calls.py — full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — full-run-safety
  • tests/test_csharp_enum_members.py — full-run-safety
  • tests/test_csharp_field_generic_args.py — full-run-safety
  • tests/test_csharp_generic_callsites.py — full-run-safety
  • … and 212 more

non-code file(s) changed (CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests

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.

· 7 more finding(s) on lines outside this diff (see the check run).

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.html: clicking a neighbor link is a no-op when its community is filtered out

1 participant