Skip to content

Show a graph the project designed, in place of Quartz's - #243

Open
dmarx wants to merge 2 commits into
claude/typed-lineage-graphfrom
claude/site-graph-config
Open

dmarx wants to merge 2 commits into
claude/typed-lineage-graphfrom
claude/site-graph-config

Conversation

@dmarx

@dmarx dmarx commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Stacked on #242 — based on claude/typed-lineage-graph so the diff shows only this work. It needs the vendored viewer and site_graph.py that #242 introduces. GitHub retargets this to main automatically when #242 merges.

Ported to the new config after #257. luria.toml is luria.yaml now, and Quartz 5 takes its layout from each plugin's own key — so the swap this PR is about changed shape. See What the config overhaul changed at the bottom; the behaviour is identical and it is expressed better than it was.

Summary

Both graphs a luria site can draw are computed: Quartz's page-link neighbourhood, and the typed-edge lineage graph from #242. Neither is a picture anybody drew, so a project that knows its own shape had no way to say so.

site:
  graph: docs/graphs/record-map.json   # strata-g graph data export
  graph_height: 320px                  # optional

Set, that one curated picture replaces Quartz's own graph on every page, where its local graph was — the swap is positional as well as functional, so a reader who knew where to look still finds it there. Unset, nothing changes.

The file comes from strata-g's Canvas — graph data (JSON) export (dmarx/strata-g#783): design the graph in that app, export, point the key at it.

Why replacing is right here, having argued the opposite in #242

The objection to swapping Quartz's graph for the lineage graph was measured density: 47% of scheme documents have any typed edge, so most pages would have got a box with nothing in it.

A configured graph is the same picture everywhere. There is no page it is empty on, so there is no page the swap makes worse. And two graphs stacked above every document's first paragraph is not a thing anyone chooses deliberately. The lineage graph still appears — at the foot, where it is a place to go next rather than a toll.

Failing usefully

A site that builds and then shows an empty box has told the author nothing (DP-1), so every way of getting this wrong is named at build time:

What's wrong What happens
File missing SystemExit, naming the export that produces one
Not JSON / not a graph / node with no position SystemExit, saying which
A node URL the viewer won't follow Reported, not rejected
graph set with no base_url Reported — the viewer is served from the site root

The last two are reports rather than errors on purpose. A graph whose nodes are not links is a perfectly good picture; a node that looks clickable and silently is not, is not.

Two of these were bugs the tests found before a build did. The viewer asset was written only when the lineage count was non-zero, so a project with a configured graph and no typed edges anywhere got an element and no script to mount it. And graph without base_url did nothing at all, in silence.

What the config overhaul changed

#257 landed between this PR being written and this push. Three things moved, and the middle one is the interesting one:

The swap is now a key, not a splice. Under Quartz 4 this PR removed a Component.Graph({…}) block from a generated quartz.layout.ts by leaving a substitution empty — luria wrote TSX for the sole purpose of moving one component. Quartz 5 puts layout on each plugin, so the whole of that is:

  - source: "@quartz-community/graph"
    enabled: {graph_enabled}

with graph_enabled set to "false" when the project configured its own. The overhaul made this PR smaller and clearer; nothing about the behaviour changed.

The tests parse the config instead of matching substrings. They asserted "Component.Graph({" not in layout. They now read quartz.config.yaml and assert enabled is False — which is stricter, because a substring check passes on a commented-out block.

The fixture had to stop concatenating. configure() appended a second [luria.site] table to the TOML text, which TOML merges. YAML mappings do not: pasting site: onto the end either duplicates the key or silently replaces what the base config put there. It now parses, sets, and re-dumps.

Verification

A real export is the fixture. tests/fixtures/strata_g_export.json came out of strata-g's actual export dialog, driven through its real UI. The contract between the two projects is a file format, and a fixture written here to match this module's idea of it would pass forever while the two drifted apart. One test does nothing but feed it to the loader.

The swap is checked in both directions on a real build, not only in fixtures — a reading of enabled: false means nothing unless the same build yields true when the key is absent:

this record's luria.yaml Quartz's graph plugin
site.graph set enabled: false
site.graph removed, same build enabled: true

Driven against a real browser build when first written — the lesson from #242, where local verification skipped the pipeline the bugs were in: two graphs mounted (the configured map and the page's lineage graph, 7 nodes / 6 edges each), the map above the document's # heading where Quartz's was, a click on a spoke issuing a request for docs/decisions/README, no page errors.

Measured rather than guessed: the panel scrolls at the default height. At 320px the side panel wants 198px and gets 143px — the stacked layout caps it at 45%. Nothing is lost (overflow-y: auto), and raising the default would cost every page the height to save one scroll. graph_height is the dial.

python -m pytest -q1313 passed, 33 skipped. luria lint docs-clean.

Not enabled on this record

The key is documented and tested, but which graph luria's own site shows is a picture somebody has to draw. I built one to verify the pipeline and then took it back out — inventing one would be deciding what the project's front door looks like on every page. #244, stacked on this, is where this record actually turns it on.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2

@dmarx
dmarx force-pushed the claude/typed-lineage-graph branch from 4cc7938 to c3a7f8c Compare September 13, 2026 03:34
@dmarx
dmarx force-pushed the claude/site-graph-config branch 2 times, most recently from 03f2859 to d14bcbb Compare September 13, 2026 03:42
dmarx pushed a commit that referenced this pull request Sep 13, 2026
The source half of this landed as the docs-generate bot's own `docs: repair
sources` commits on #242 and #243, where those three documents actually
live -- this branch had them only because the rebase carried them up. What
is left is the generated side: the decision index, the tag and status
pages, the devlog books, the reports, and the README's two badge counts
(needs decision 3 -> 5, cited-not-in-force 0 -> 3, both counting this
stack's still-Proposed ADRs).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
@dmarx
dmarx force-pushed the claude/typed-lineage-graph branch from 679a6a4 to 819ea9f Compare September 13, 2026 04:03
@dmarx
dmarx force-pushed the claude/site-graph-config branch from d14bcbb to a4952b7 Compare September 13, 2026 04:03
dmarx added a commit that referenced this pull request Sep 13, 2026
`[luria.site] graph`, `graph_height` and `graph_depth` are on main with
nothing behind them. There is no `luria/site_graph.py` here and `site.py`
imports none, so a project that sets `graph = "..."` gets its Quartz local
graph and no explanation -- the documented, defaulted, parsed key is simply
never looked at.

They arrived by accident. #245 was lifted out of the #242->#243->#244 stack
as an isolated change, and its branch carried this hunk across with the
`cite` work it was actually for. Nothing caught it, because from outside
there is nothing to catch: `docs/configuration.md` is generated from these
dataclasses, so a field publishes its own reference row whether or not any
code reads it, and an unimplemented setting is indistinguishable from a
working one until someone tries it.

So the check is the interesting half. A `Site` field is consumed if some
module other than config.py names it on a `site` receiver -- `site.icon`,
`cfg.site.publish`, `current().site.base_url`, which is how all nine real
readers spell it. Crude on purpose: it is the cheapest rule that separates
the nine from these three, and it fails naming them. Its own positive
control is next to it, because a pattern that matched nothing would report
this file clean.

The keys come back with the code that implements them, on the stack that
has it; nothing else on the record mentions them, and configuration.md loses
its rows without being touched.


Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2

Co-authored-by: Claude <noreply@anthropic.com>
@dmarx
dmarx force-pushed the claude/typed-lineage-graph branch from 819ea9f to 4ed3f6e Compare September 13, 2026 09:11
@dmarx
dmarx force-pushed the claude/site-graph-config branch from a4952b7 to b700c3d Compare September 13, 2026 09:11
@dmarx
dmarx force-pushed the claude/typed-lineage-graph branch from 4ed3f6e to 9930327 Compare September 14, 2026 09:12
@dmarx
dmarx force-pushed the claude/site-graph-config branch from b700c3d to 482db1f Compare September 14, 2026 09:12
@dmarx
dmarx force-pushed the claude/typed-lineage-graph branch from 9930327 to 6e38ec0 Compare September 15, 2026 09:04
claude and others added 2 commits September 15, 2026 09:08
Both graphs a luria site can draw are computed: Quartz's page-link
neighbourhood, and the typed-edge lineage graph. Neither is a picture anybody
drew, so a project that knows its own shape had no way to say so.

[luria.site] graph points at a strata-g `Canvas — graph data (JSON)` export.
Set, that one curated picture replaces Component.Graph on every page, where
Quartz's local graph was — the swap is positional as well as functional.
Unset, nothing changes.

Replacing is right here and was wrong for the lineage graph, for one reason.
The objection there was measured density: 47% of scheme documents have any
typed edge, so most pages would have got an empty box. A configured graph is
the same picture everywhere — no page it is empty on, so no page the swap
makes worse. And two graphs above every document's first paragraph is not a
thing anyone chooses.

The file is validated at build time and the error names which way it is
wrong: missing, not JSON, not a graph, a node with no position. A site that
builds and then shows an empty box has told the author nothing.

Two failure modes the tests caught before a build did. The viewer asset was
written only when the LINEAGE count was non-zero, so a project with a
configured graph and no typed edges got an element and no script to mount
it. And `graph` set without a `base_url` did nothing, silently — the viewer
is served from the site root. Both are now in the staging report. A node
whose URL the viewer will not follow is reported too, rather than rejected:
a graph whose nodes are not links is a fine picture; one that looks
clickable and silently is not, is not.

tests/fixtures/strata_g_export.json came out of strata-g's real export
dialog rather than being written here to match. The contract between the two
projects is a file format, and a fixture that only matches this module's
idea of it would pass forever while the two drifted.

Verified against a real build: luria site → Quartz v4.5.2 → the page carries
the configured map and the page's lineage graph, Component.Graph is gone
from the layout, the map sits above the document's heading, and a click on a
spoke requested docs/decisions/README. No page errors.

NOT enabled on this record. The key is documented and tested; which graph
luria's own site shows is a picture somebody has to draw, and inventing one
would be deciding what the project's front door looks like on every page.

1112 passed, 33 skipped. luria lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
@dmarx
dmarx force-pushed the claude/site-graph-config branch from 482db1f to 54073e3 Compare September 15, 2026 09:12
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