Skip to content

The record shows a map of itself, a neighbourhood at a time - #244

Open
dmarx wants to merge 7 commits into
claude/site-graph-configfrom
claude/principle-pages
Open

dmarx wants to merge 7 commits into
claude/site-graph-configfrom
claude/principle-pages

Conversation

@dmarx

@dmarx dmarx commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Stacked on #243 (itself on #242) so the diff shows only this work. GitHub retargets automatically as the stack merges.

Rebased onto main, which shed about half of what this PR used to be. Principle pages, the cite key and the citation retargeting all merged as #245 and are gone from this diff. What is left is the part that was actually still being workshopped: this record showing a map of itself.

The record shows a map of itself

[luria.site] graph now points at docs/graphs/record-map.json, drawn in strata-g from this record — 456 nodes, 922 edges, composed from two layers:

Edge Count Source
cites 548 luria record layer
markdown link 313 the repo's markdown as an Obsidian vault layer
influenced_by 58 luria record layer
superseded_by 3 luria record layer

That combination is the point: Quartz's graph knows only the links, the lineage graph knows only the typed edges, and this is both at once, told apart by edge colour.

The two layers do not fully merge, and an earlier revision of this description said they did. strata-g folds nodes together when their on-screen LABELS match, not their filenames. The record layer titles a decision ADR-001: Four layers of record…; the vault layer reads the bare heading Four layers of record…. So 122 nodes merge — where the two labels coincide by accident, a changelog fragment's title is its timestamp — and 112 documents are drawn twice. The fix would be a computed column stripping the CODE: prefix, which dmarx/strata-g#787 blocks. The docs describe what the file is rather than what it was meant to be.

Nothing is filtered. An earlier revision cut to 37 nodes on a citations >= 5 threshold for legibility; that was wrong and is gone. A map that quietly dropped two thirds of the decisions is one you cannot trust — a reader who looks for ADR-070, doesn't find it, and concludes it doesn't exist has been misled by the picture. Density is what the viewer's zoom, pan, search and Fit are for; graph_height is 560px.

Each page shows its own neighbourhood

[luria.site] graph_depth — hops from the page's own node, 1 by default. A page is a place, and the useful picture there is where you are, not the whole atlas. Measured off a real luria site stage: median 7 nodes and 1,062 gzipped bytes a page against a 39 KB whole map, 0.19 MB over the 151 pages that carry it. graph_depth = 0 restores the whole map on every page for a project that wants one poster.

Two fixes off the deployed site

The map now follows you across an ordinary link. Quartz routes in-page navigation client-side and morphs the existing DOM rather than replacing it, so the <strata-g-graph> element survived with a new data-graph attribute and an already-mounted graph inside — connectedCallback had fired once and nothing fired again. Clicking a node in the graph worked only because that path is a full page load. Fixed upstream (dmarx/strata-g#786) and re-pinned here.

Measured on a real build, with a positive control: ADR-042 → ADR-005 by a prose link draws 26 nodes, matching a hard reload of the same URL, 2 registered instances. The previously vendored viewer leaves 13 on screen — ADR-042's neighbourhood, on ADR-005's page — beside an attribute that says 26, and leaks an instance per hop (registry grows to 5).

Nodes are labelled by title, not by code. ADR-001, DP-004, ADR-093 is an address, not a name; a reader gets nothing from it they didn't have from the URL. The canvas cuts at 60 characters, which is the cost. The obvious refinement — a computed column truncating with an ellipsis — is accepted by the select and ignored by the canvas, because strata-g stamps virtual columns, metrics and transform columns after it stamps labels (dmarx/strata-g#787).

What the rebase changed

Two commits were dropped whole, because #245 is where that work merged: A document-rendered scheme's sources are published as pages and A citation goes to the cited document's page, and the target is configurable. main already carries document_source(), _rebase(), the narrowed publishable(), the cite key and retarget_view_citations, so replaying them would have been a conflict with nothing on the other side of it.

Three things did not survive that cleanly, and each is its own commit rather than a silent fixup:

  • One test in #242 asserted the behaviour main changed underneath it. test_a_scheme_rendered_into_one_document_resolves_to_its_anchor required DP-002 to resolve to docs/design-principles#dp-…; principles have pages now. Renamed and re-pointed in the commit that introduces it, so the branch never contains a test it then corrects.
  • The map had a node for a document this rebase deleted. ADR-tmp40zph was one of the dropped commits' own ADRs — drawn, labelled, and linking to a 404. So the map is drawn again by the recipe, against the rebased record and a strata-g built from its current main, and every figure in docs/graphs/README.md and the changelog fragment is re-measured rather than adjusted. The interesting move is markdown-link edges 345 → 313 against cites 421 → 548: that is A cited document gets a page, and citations point at it #245's repair taking DP citations off the assembled document's anchors and onto principle pages, where the record layer counts them as citations. The merge counts are the ones it would have been easiest to assume and they moved least — 112 documents still render as two nodes, 122 (was 120) fold together — so the label-merge caveat stands as written.
  • Three of this branch's own documents still cited principles by anchor, having been written before the key existed. luria repair reports exactly 3 files, which is how you can tell nothing else on the record moved.

The devlog entry of 2026-09-12 keeps its original numbers. It is a dated observation of what that draw contained, and its claim — the two layers merge by label, not by filename — is exactly what the re-measurement confirms.

One thing main is currently carrying that it cannot use

Worth flagging separately, because it is a defect I introduced in #245 rather than anything this PR does: that PR's branch picked up the [luria.site] config surface for graph, graph_height and graph_depth — the DEFAULTS entries, the Site dataclass fields, the docstring and the _site() reader — without the code that consumes them. main has no luria/site_graph.py and luria/site.py never imports one, so a project setting graph = "…" on main today gets silence. This stack is what makes those keys mean something; merging it closes the gap, and git log -S graph_depth is the receipt if it is worth its own note.

Verification

python -m pytest -q1138 passed, 33 skipped. luria lint → docs lint clean; its warnings are the three still-Proposed ADRs in this stack and three pre-existing DP-017 acknowledgements.

The map is a snapshot and still names two temporary ADR codes from this stack. luria concretize renumbers those on merge, so it wants one more re-export afterwards — the recipe says so.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2

@dmarx
dmarx force-pushed the claude/site-graph-config branch from a685c91 to 5094ebf Compare September 13, 2026 03:34
@dmarx
dmarx force-pushed the claude/principle-pages branch from bbe1931 to 540ea53 Compare September 13, 2026 03:34
@dmarx dmarx changed the title A document-rendered scheme's sources are published as pages The record shows a map of itself, a neighbourhood at a time Sep 13, 2026
@dmarx
dmarx force-pushed the claude/site-graph-config branch from 03f2859 to d14bcbb Compare September 13, 2026 03:42
@dmarx
dmarx force-pushed the claude/principle-pages branch from 540ea53 to 063addd Compare September 13, 2026 03:42
@dmarx
dmarx force-pushed the claude/site-graph-config branch from d14bcbb to a4952b7 Compare September 13, 2026 04:03
@dmarx
dmarx force-pushed the claude/principle-pages branch from 063addd to 358696a 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/site-graph-config branch from a4952b7 to b700c3d Compare September 13, 2026 09:11
@dmarx
dmarx force-pushed the claude/principle-pages branch from 358696a to 024c734 Compare September 13, 2026 09:11
@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/principle-pages branch from 024c734 to 1233a17 Compare September 14, 2026 09:12
@dmarx
dmarx force-pushed the claude/site-graph-config branch from 482db1f to 54073e3 Compare September 15, 2026 09:12
@dmarx
dmarx force-pushed the claude/principle-pages branch from 1233a17 to 6be41ea Compare September 15, 2026 09:12
@dmarx
dmarx force-pushed the claude/site-graph-config branch from 54073e3 to 2f99b0f Compare September 16, 2026 09:05
@dmarx
dmarx force-pushed the claude/principle-pages branch from 6be41ea to 632e9fd Compare September 16, 2026 09:05
The example `[luria.site] graph` promised by ADR-tmp0hx52, produced the way
the docs now tell you to produce one: strata-g's luria-record backend loaded
against this clone, two computed columns, a filter, four appearance bindings,
and the `Canvas — graph data (JSON)` export. Nothing hand-edited.

ADR-tmp40zph is what made it one rule. Before principles had pages the URL
column needed a special case reconstructing …/docs/design-principles#dp- and
the number; now every scheme document answers at /<code> and the expression
is `code ? "https://dmarx.github.io/luria/" & code : ""`. That special case
was the reason to give them pages in the first place.

Curated to 37 nodes, and the threshold is measured rather than picked. All
110 decisions and principles render as an unreadable ball at this height —
seen, not assumed. Cited five or more times gives a shape you can read, and
4.5 KB gzipped per page instead of 11.8. `graph_height` is 420px for the
same reason.

Two defaults luria supplies, both only when the file is silent:
navigateOnClick, because these nodes ARE pages of this site and opening one
is what a click means there; and the panel title, because a bare export
carries none and the viewer falls back to "Graph", which names nothing on a
page that already has a title.

Temporary codes are filtered out: `luria concretize` numbers those where
merges serialize, so a node for one would link to a page that does not exist
yet and then move when it does.

Verified through a real Quartz v4.5.2 build: Component.Graph gone from the
layout, the map on every page above the heading, the page's own lineage
graph still at the foot, cursor `pointer` over a node, and a click on
ADR-042 requesting ADR-042.

Four probe bugs found along the way, all mine, none the code's: extensionless
paths against http.server, an unstripped #fragment, a click at a negative
page coordinate, and a rect read before smooth scrolling had finished. The
handler was right every time.

1115 passed, 33 skipped. luria lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
The first version cut to 37 nodes on a `citations >= 5` threshold, and the
reason was legibility — which is a bad reason to drop data from a thing whose
job is navigation. A reader who looks for ADR-070, does not find it, and
concludes it does not exist has been misled by the picture.

Now 248 nodes and 482 edges: everything luria knows about. Density is what
the viewer's zoom, pan, search and Fit are for, and `graph_height` is 560px
so the shape reads at a glance.

A document with a code links to its page; a journal entry or changelog
fragment has no code and no page, so its node does not navigate. That is the
truth about it, and better than inventing an address.

The cost, stated rather than hidden: 24 KB gzipped inlined per page.

docs/graphs/README.md loses the threshold justification along with the
threshold, and gains the definition of `citations` — a document's in-degree,
typed relations plus prose citations, deduped where both join the same pair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
…d apart

dmarx's design, and better than what it replaces. The luria record backend
gives typed edges; the repository's markdown loaded as an Obsidian vault
gives the markdown-link edges. Nodes merge on filename with no configuration
— the vault labels by filename stem and the record's `code` is that same
string — and the edge Color-by `kind` tells the two kinds apart.

454 nodes, 827 edges: 345 markdown links, 421 cites, 58 influenced_by, 3
superseded_by. Quartz's graph knows only the first group; the lineage graph
at the foot of each page knows only the rest. This is both at once, which is
the argument for drawing a map by hand rather than generating one.

Load order matters and it is a strata-g bug, not a preference: with the
vault layer loaded FIRST, `kind` never reaches the edge Color-by catalog
even after the record layer is added. Reproduced independently by dmarx.
Documented in the recipe as a step, and filed upstream.

Label by `code`, because merged containers otherwise inherit the vault
node's long title and the labels collide into a paragraph — seen on a real
build before it was fixed.

Cost stated rather than hidden: 40 KB gzipped inlined per page, up from 24.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
The configured graph was one picture inlined on every page — the same
everywhere, which is a poster rather than a map of where you are. Each page
now shows the nodes within `graph_depth` hops of its own (default 1); `0`
restores the whole map for a project that wants the poster.

Every one of this record's 151 staged pages finds its node, 117 by repo path
and 34 by basename — the two shapes a multi-layer map uses, both of which
the build already knows about the page it is staging. Median 6 nodes a page
rather than 454.

The positions are the author's and are not recomputed: a document sits where
it was drawn, so it is in the same place on whichever page you meet it.

A nested record now gets NO configured graph. Its pages are not nodes of the
parent's map, and the parent's map whole is a picture of a different record
— which on an example's page said nothing and cost 38 KB gzipped a page to
say it. That was 6.1 MB of the 6.3 MB total; the whole site is now 0.17 MB.

Node sizing is uniform, at an exported size near 9, and the number is not
taste. nodeRadius is max(1.5, size * min(1, cap/maxSize) * zoom) and a label
needs radius > 6 — and min(1, ...) means the viewer only ever shrinks a node,
never grows one, so the exported size has to carry it. Sizing by citations
pins the floor at 2, which renders at ~2.5px: the low-cited nodes lost their
labels while the high-cited ones covered the edges. Two wrong guesses before
that (scale 0.5 → ratio 4.3, floor under the threshold; uniform at size 1 →
radius 1.5) came from assuming the viewer normalised the largest node to a
fraction of the canvas. Reading nodeRadius settled it.

Verified on a real Quartz build: ADR-042's page draws a 13-node / 23-edge
neighbourhood with readable labels and all four edge colours visible.

1118 passed, 33 skipped. luria lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
Two things the deployed site got wrong.

The embedded map did not update when you reached a page by any link other
than a node in the graph itself. Quartz routes in-page navigation
client-side and MORPHS the existing DOM rather than replacing it, so the
custom element survived with a new data-graph attribute and an
already-mounted graph inside — connectedCallback had fired once and
nothing fired again. Re-pin the vendored viewer to the build that watches
its own attribute and re-renders (strata-g#786).

Measured on a real Quartz build, with a positive control: ADR-042 →
ADR-005 by an ordinary prose link draws 26 nodes, matching a hard reload
of the same URL; the previously vendored viewer leaves 13 on screen
beside an attribute that says 26, and leaks a graph instance per hop.

And the nodes were labelled with bare codes, which is an address rather
than a name. Label by title instead. The obvious refinement — a computed
column that truncates with an ellipsis rather than cutting mid-word at
the canvas's 60 characters — is accepted by the UI and silently ignored,
because strata-g stamps virtual columns after it stamps labels
(strata-g#787).

That investigation also killed a claim this repository was making: the
two layers do not "merge on filename". The merge is by on-screen label,
the two layers title a document differently, and 112 of the 454 nodes
are one document drawn twice. Documented as it is, with the bug named.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
The rebase onto main dropped this branch's two commits whose content merged
as #245, and one of them was the ADR the map had a node for. A map naming a
document that no longer exists anywhere is worse than a stale one: the node
is drawn, labelled, and links to a 404.

So it is drawn again, by the recipe in docs/graphs/README.md, against the
rebased record and a strata-g built from its current main. 454 nodes / 827
edges -> 456 / 922, and ADR-tmp40zph is replaced by ADR-094, the number
concretize gave that work when #245 merged.

Every figure in the recipe and the changelog fragment is re-measured rather
than adjusted: markdown-link edges 345 -> 313 and `cites` 421 -> 548, which
is #245's repair moving DP citations off the assembled document's anchors
and onto principle pages, where the record layer sees them as citations.
The merge counts are the ones that would have been easiest to assume and
they moved least -- 112 documents still render as two nodes, 122 (was 120)
fold together -- so the label-merge caveat stands as written.

Per-page cost re-measured off a real `luria site` stage: median 7 nodes and
1,062 gzipped bytes a page against a 39 KB whole map, over the 151 pages
that carry it. Those two counts, 151 and 79, are luria's own report of the
same stage, which is what says the measurement classified the configured
graph and the lineage graphs correctly rather than counting one twice.

The devlog entry of 2026-09-12 keeps its own numbers. It is a dated
observation of what that draw contained, and its claim -- the two layers
merge by label, not by filename -- is what the re-measurement confirms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
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 2f99b0f to a34a6c2 Compare September 17, 2026 09:06
@dmarx
dmarx force-pushed the claude/principle-pages branch from 632e9fd to 0cd23c5 Compare September 17, 2026 09:06
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