html.escape() the values that land in <title> and <h1>, and replace </
with <\/ in the JSON embedded inside <script> so crafted graph labels
or --label values cannot break out. Mirrors the _js_safe() pattern in
export.py.
Reported by Qodo on PR #557.
Adds a new `graphify tree` subcommand that emits a self-contained D3
v7 collapsible-tree HTML view of an existing graph.json.
Why
---
The existing `graph.html` (force-directed) is great for finding hubs
and unexpected connections. But for code review and onboarding, a
hierarchical tree-of-modules view is much faster: you can collapse
everything and expand only the package you care about, the depth-
based colour palette gives instant orientation, and the layout
mirrors the on-disk structure.
UX choices include expand-all / collapse-all / reset-view buttons,
multi-line `wrapText` labels with separately-coloured name and count,
a depth-based palette, click-to-toggle subtrees, and a hover-inspector
that surfaces the top-K outbound edges per symbol.
Implementation
--------------
- `graphify/tree_html.py` (575 LOC, single file, no new runtime
dependencies). D3 v7 is loaded from cdn.jsdelivr.net at view time.
- Hierarchy is built from `source_file` longest-common-prefix;
symbols are grouped by containing module so the tree mirrors the
on-disk layout exactly.
- Inspector pre-computes top-K outbound edges per symbol so the page
works fully offline once loaded.
- `__main__.py` adds the subcommand + help text after the
`check-update` block.
Configuration
-------------
- `--graph PATH` path to graph.json (default: graphify-out/graph.json)
- `--output HTML` output path (default: graphify-out/GRAPH_TREE.html)
- `--root PATH` filesystem root (default: LCP of source_files)
- `--max-children N` cap visible children per node (default: 200)
- `--top-k-edges N` per-symbol outbound edges in inspector (default: 12)
- `--label NAME` project label shown in the page header
Tested locally on a 17 641-node graph — emits a 4.9 MB HTML file
that renders smoothly in Firefox / Chromium.