Skip to content

fix: connect graphify Obsidian output into a single component#221

Closed
CodeAbra wants to merge 1 commit into
safishamsi:v4from
CodeAbra:fix/community-hub-links-and-md-double-ext
Closed

fix: connect graphify Obsidian output into a single component#221
CodeAbra wants to merge 1 commit into
safishamsi:v4from
CodeAbra:fix/community-hub-links-and-md-double-ext

Conversation

@CodeAbra

Copy link
Copy Markdown

Summary

Two long-standing bugs in the Obsidian export cause /graphify output to form disconnected components when it lands in a vault. On one real-world vault (4549 notes, 6 graphify projects) the bugs produced 52 connected components with ~21% of nodes (945/4549) stranded in 51 islands. After this PR re-runs of the same projects produce 1 component, 100% of nodes reachable from `master_index.md`.

Bug 1 — `safe_name()` produces `.md.md` filenames

Both `safe_name()` helpers in `export.py` (inside `to_obsidian` and `to_canvas`) sanitize unsafe chars but do not strip trailing `.md`/`.mdx`/`.markdown`. When a node label happens to be a markdown filename (e.g. `CLAUDE.md`, `Guidelines.md` — which is common when graphify ingests a `CLAUDE.md` project file), the caller then appends `.md` to the result and writes a file named `CLAUDE.md.md`. Meanwhile the wikilinks written inside other notes are `[[CLAUDE.md]]`, which Obsidian resolves to the non-existent `CLAUDE.md` — so every incoming link breaks.

Bug 2 — `GRAPH_REPORT.md` has zero wikilinks to community hubs

`report.py`'s `generate()` emits community headings as plain prose:

```

Community 15 - "Haptics"

Cohesion: 0.25
Nodes (8): PFFTHaptics, ...
```

No wikilinks to the `_COMMUNITY_Haptics.md` hub that `to_obsidian` actually writes. Once `GRAPH_REPORT.md` is copied into a vault as `-graph-report.md`, it becomes a dead-end — the community subgraph it describes is unreachable from it, and the whole project cluster splits off into its own connected component.

Changes

`graphify/export.py`

Both `safe_name()` helpers now strip trailing Markdown extensions after the existing unsafe-char / newline sanitization:

```python
cleaned = re.sub(r"\.(md|mdx|markdown)$", "", cleaned, flags=re.IGNORECASE)
```

Idempotent and safe — normal node labels (`Haptics`, `useFormField()`, `Friends & Badges UI`) pass through unchanged; only labels ending in a markdown extension get their extension stripped.

`graphify/report.py`

  1. Added a module-level `_safe_community_name()` that mirrors `export.safe_name`, so community hub filenames written by `to_obsidian` and link targets written by the report always agree.
  2. Added a `## Community Hubs (Navigation)` section right after `## Summary` that emits `[[COMMUNITY|]]` for every detected community.
  3. Upgraded each `### Community N` heading to embed the same wikilink, so either entry point (the nav block or the community walkthrough) reaches the hub.

Before / after on one real vault

Metric Before After
Connected components 52 1
Main component 3605 (79%) 4550 (100%)
Stranded nodes 945 0
Broken `.md.md` links 9 0
Edges in vault graph 15,767 15,860 (+93)

Test plan

  • Syntax check: `python3 -c "from graphify.report import _safe_community_name, generate; from graphify.export import to_obsidian"`
  • `_safe_community_name('CLAUDE.md')` → `'CLAUDE'`
  • `_safe_community_name('Guidelines.md')` → `'Guidelines'`
  • `_safe_community_name('Haptics')` → `'Haptics'` (unchanged)
  • `_safe_community_name('Friends & Badges UI')` → `'Friends & Badges UI'` (unchanged)
  • `generate()` on a 3-node fixture produces `## Community Hubs (Navigation)` section with `[[_COMMUNITY_Docs Cluster|Docs Cluster]]` link
  • Full regression on a medium corpus (deferred to reviewer if available)

Notes

  • The `communities` dict is iterated in its native insertion order for the navigation block; if deterministic ordering matters you may want to sort by `cid`.
  • No changes to graph data, extraction, or cluster detection — only the Markdown emission layer.
  • Fully backwards-compatible: an existing vault whose graph-report was already curated with inline links will simply gain a second navigation block and linked community headings.

🤖 Generated with Claude Code

Two bugs caused /graphify output to form disconnected components when
dropped into an Obsidian vault:

1. `safe_name()` in `export.py` (both `to_obsidian` and `to_canvas`) did
   not strip trailing `.md`/`.mdx`/`.markdown` from the label before the
   caller appended the file extension. Nodes labelled after markdown
   sources (e.g. `CLAUDE.md`, `Guidelines.md`) produced filenames like
   `CLAUDE.md.md` while their incoming `[[CLAUDE.md]]` wikilinks
   resolved to the non-existent `CLAUDE.md` file. Every such link broke.

2. `report.py`'s `generate()` emitted community headings as plain prose
   (`### Community 15 - "Haptics"`) with zero wikilinks. Once the
   generated `GRAPH_REPORT.md` was copied into a vault as
   `<proj>-graph-report.md`, the entire community subgraph became
   unreachable from the report — the whole project cluster split off
   into a disconnected component.

On one real vault (4549 notes across six graphify projects) these two
bugs produced 52 connected components with ~21% of nodes stranded in
51 islands. After the fixes, re-running the report generator and
regenerating the Obsidian export collapses the graph back to 1
component with 100% of nodes reachable from `master_index.md`.

Fixes:

- `export.py`: both `safe_name()` helpers now strip trailing Markdown
  extensions (`.md`, `.mdx`, `.markdown`, case-insensitive) after
  removing Obsidian-unsafe chars. Idempotent with the existing
  newline-stripping and sanitization.

- `report.py`: added a module-level `_safe_community_name()` that
  mirrors `export.safe_name` so hub filenames and report links stay
  in sync. Inserted a `## Community Hubs (Navigation)` bullet list
  right after `## Summary` that emits
  `[[_COMMUNITY_<safe>|<label>]]` for every community. Upgraded each
  `### Community N` heading to embed the same wikilink so either
  entry point (nav block or community walkthrough) reaches the hub.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
safishamsi added a commit that referenced this pull request Apr 11, 2026
- build/validate: accept NetworkX <=3.1 "links" key alongside "edges" (#212)
- __main__: skip version check during install/uninstall, deduplicate paths (#220)
- all file IO: explicit encoding="utf-8" to prevent crashes on Windows CJK locales (#204)
- hooks: add newline="\n" on write to prevent CRLF shebang breakage on Windows (#204)
- export: strip trailing .md from safe_name so "CLAUDE.md" doesn't become "CLAUDE.md.md" (#221)
- report: add Community Hubs navigation block so Obsidian vault stays connected (#221)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@safishamsi

Copy link
Copy Markdown
Owner

Cherry-picked into v0.4.2 (safe_name .md strip + community hub nav block) — thank you!

@safishamsi safishamsi closed this Apr 11, 2026
joyshmitz pushed a commit to joyshmitz/graphify that referenced this pull request Apr 13, 2026
…hamsi#221 into 0.4.2

- build/validate: accept NetworkX <=3.1 "links" key alongside "edges" (safishamsi#212)
- __main__: skip version check during install/uninstall, deduplicate paths (safishamsi#220)
- all file IO: explicit encoding="utf-8" to prevent crashes on Windows CJK locales (safishamsi#204)
- hooks: add newline="\n" on write to prevent CRLF shebang breakage on Windows (safishamsi#204)
- export: strip trailing .md from safe_name so "CLAUDE.md" doesn't become "CLAUDE.md.md" (safishamsi#221)
- report: add Community Hubs navigation block so Obsidian vault stays connected (safishamsi#221)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
safishamsi added a commit that referenced this pull request Apr 23, 2026
- build/validate: accept NetworkX <=3.1 "links" key alongside "edges" (#212)
- __main__: skip version check during install/uninstall, deduplicate paths (#220)
- all file IO: explicit encoding="utf-8" to prevent crashes on Windows CJK locales (#204)
- hooks: add newline="\n" on write to prevent CRLF shebang breakage on Windows (#204)
- export: strip trailing .md from safe_name so "CLAUDE.md" doesn't become "CLAUDE.md.md" (#221)
- report: add Community Hubs navigation block so Obsidian vault stays connected (#221)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
matzls pushed a commit to matzls/graphify that referenced this pull request May 10, 2026
…hamsi#221 into 0.4.2

- build/validate: accept NetworkX <=3.1 "links" key alongside "edges" (safishamsi#212)
- __main__: skip version check during install/uninstall, deduplicate paths (safishamsi#220)
- all file IO: explicit encoding="utf-8" to prevent crashes on Windows CJK locales (safishamsi#204)
- hooks: add newline="\n" on write to prevent CRLF shebang breakage on Windows (safishamsi#204)
- export: strip trailing .md from safe_name so "CLAUDE.md" doesn't become "CLAUDE.md.md" (safishamsi#221)
- report: add Community Hubs navigation block so Obsidian vault stays connected (safishamsi#221)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

2 participants