Skip to content

fix(export): restore graph.html for large graphs - #2894

Closed
oleksii-tumanov wants to merge 1 commit into
Graphify-Labs:v8from
oleksii-tumanov:fix/restore-large-graph-html
Closed

fix(export): restore graph.html for large graphs#2894
oleksii-tumanov wants to merge 1 commit into
Graphify-Labs:v8from
oleksii-tumanov:fix/restore-large-graph-html

Conversation

@oleksii-tumanov

@oleksii-tumanov oleksii-tumanov commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Use the configured visualization limit for label, cluster-only, and update, producing the existing aggregated community view for large graphs.
  • Regenerate missing or stale graph.html during an unchanged update without reclustering.
  • Publish HTML atomically so failed or interrupted renders preserve the last complete file and remain retryable. A limit of 0 still disables HTML generation.

Before and after

Before:

  • label and cluster-only removed graph.html when the graph exceeded the visualization limit.
  • An unchanged update reported that outputs were untouched and did not restore the missing file.

After:

  • label, cluster-only, and update generate the aggregated community view above the limit.
  • An unchanged update regenerates missing or stale graph.html.
  • Failed renders preserve the last complete HTML and remain retryable.

Result

On the final patch, graphify update . produced:

Graph has 14191 nodes (above 5000 limit). Building aggregated community view...
graph.html written (aggregated: 963 community nodes, 1292 cross-community edges)

Testing

  • pytest tests/test_atomic_writes.py tests/test_export.py tests/test_labeling.py tests/test_watch.py -q (231 passed, 3 skipped)
  • pytest tests/ -q --ignore=tests/test_non_regular_files.py --ignore=tests/test_pipeline.py --ignore=tests/test_security.py (4640 passed, 52 skipped); excluded environment-dependent suites passed separately (77 passed)
  • ruff check . and the required python -m tools.skillgen validation commands (passed)

Fixes #2853

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

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

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

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

The verifier found a concrete input on which to\_html 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 \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","communities":"\{'k': 'v'\}","output\_path":"'racecar'","community\_labels":"\{'k': 'v'\}","member\_counts":"\{'a': 1\}","node\_limit":"2","learning\_overlay":"\{'a': 1, 'b…, the old code produced None but the new code produces False. Paste that input straight into a regression test.


Graphify review — findings

Adds a stale-HTML marker (.graph.html.stale) protocol so graphify/exporters/html.py's to_html writes atomically via write_text_atomic and returns a bool signaling whether output was produced, and threads that signal through cli.py's viz path and a new _reconcile_graph_html in watch.py that regenerates missing/stale/disabled HTML on the unchanged-topology update path. Drops the old _over_cap-driven node_limit=5000 fallback in cli.py in favor of _viz_node_limit(), including a <=0 opt-out that removes the HTML. Adds watch/labeling/CLI/html tests covering the marker lifecycle, node-limit policy, and reconcile behavior.

Worth a look

  • Stale HTML marker left behind when to_html raises non-ValueError exceptiongraphify/cli.py:2101 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Optional HTML recovery aborts if stale marker cannot be createdgraphify/watch.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Unchanged-topology rebuild can now delete existing graph.html when HTML is disabledgraphify/watch.py:1018 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Unexpected HTML export failures no longer propagate from rebuildgraphify/watch.py:1043 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Pre-advance HTML stale marker can be cleared by a concurrent reconcile against the old graphgraphify/watch.py:1807 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 686 functions depend on the 474 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 98 callers, 50 callees
  • new: dispatch_command() — 2 callers, 118 callees
  • new: to_html() — 17 callers, 11 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: watch() — 5 callers, 7 callees
  • new: _run_hook_guard() — 4 callers, 7 callees
  • new: _reconcile_graph_html() — 5 callers, 4 callees
  • …and 2 more — each is listed as a finding

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

Formal verification

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

The verifier found a concrete input on which to\_html 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 \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","communities":"\{'k': 'v'\}","output\_path":"'racecar'","community\_labels":"\{'k': 'v'\}","member\_counts":"\{'a': 1\}","node\_limit":"2","learning\_overlay":"\{'a': 1, 'b…, the old code produced None but the new code produces False. Paste that input straight into a regression test.

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 2 grounded finding(s) anchored inline below; 8 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/watch.py
return False


def _reconcile_graph_html(out: Path, graph_data: dict) -> str | None:

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 regression_reconcile_graph_html()

high coupling complexity (Ca·Ce = 20).

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

Comment thread graphify/watch.py
return "rendered"


def _rebuild_code(

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 regression_rebuild_code()

fans out to 50 callees (efferent coupling); 98 callers depend on it (afferent coupling).

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

@oleksii-tumanov
oleksii-tumanov force-pushed the fix/restore-large-graph-html branch from abde2e1 to ae86650 Compare August 20, 2026 09:34

@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: to\_html changes behavior, here is the input that shows it.

The verifier found a concrete input on which to\_html 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 \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","communities":"\{'k': 'v'\}","output\_path":"'racecar'","community\_labels":"\{'k': 'v'\}","member\_counts":"\{'a': 1\}","node\_limit":"2","learning\_overlay":"\{'a': 1, 'b…, the old code produced None but the new code produces False. Paste that input straight into a regression test.


Graphify review — findings

Adds a .graph.html.stale marker written before graph.json advances and cleared only on a successful render, so dispatch_command never leaves an older graph.html looking current after interruption or a failed viz step. Makes to_html write atomically and return a bool signaling whether output was produced, replacing the old _over_cap node-limit fallback with a single _viz_node_limit()-driven policy (including =0 to disable HTML). Adds _reconcile_graph_html to watch.py to rebuild or remove HTML on the unchanged-topology update path when it's missing, stale, or disabled.

No blocking issues surfaced. 19 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 695 functions depend on the 483 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 98 callers, 50 callees
  • new: dispatch_command() — 2 callers, 119 callees
  • new: to_html() — 17 callers, 11 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: watch() — 5 callers, 7 callees
  • new: _reconcile_graph_html() — 6 callers, 5 callees
  • new: _run_hook_guard() — 4 callers, 7 callees
  • …and 2 more — each is listed as a finding

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

Formal verification

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

The verifier found a concrete input on which to\_html 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 \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","communities":"\{'k': 'v'\}","output\_path":"'racecar'","community\_labels":"\{'k': 'v'\}","member\_counts":"\{'a': 1\}","node\_limit":"2","learning\_overlay":"\{'a': 1, 'b…, the old code produced None but the new code produces False. Paste that input straight into a regression test.

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 2 grounded finding(s) anchored inline below; 8 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/watch.py
return False


def _reconcile_graph_html(out: Path, graph_data: dict) -> str | None:

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 regression_reconcile_graph_html()

6 callers depend on it (afferent coupling).

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

Comment thread graphify/watch.py
return "rendered"


def _rebuild_code(

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 regression_rebuild_code()

fans out to 50 callees (efferent coupling); 98 callers depend on it (afferent coupling).

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

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.48 via authorship-preserving cherry-pick. Thanks @oleksii-tumanov! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.48

@safishamsi safishamsi closed this Aug 20, 2026
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.

graphify label and cluster-only delete graph.html on graphs over the viz limit, and update will not regenerate it

2 participants