Skip to content

Add comprehensive research documentation for git worldmap architecture - #7

Draft
yarikoptic wants to merge 37 commits into
mainfrom
claude/git-graph-viz-research-85imhc
Draft

yarikoptic wants to merge 37 commits into
mainfrom
claude/git-graph-viz-research-85imhc

Conversation

@yarikoptic

Copy link
Copy Markdown
Member

Summary

This PR adds extensive research documentation for the "git walker/drawer" project, covering architecture decisions, prior art analysis, and technology recommendations for building an interactive, expandable knowledge map of git and git-annex repositories.

Key additions

  • architecture-persistence-and-prior-art.md: Comprehensive analysis of expandable knowledge map systems (Maltego, AWS graph-explorer, OpenCTI, Juggl, SpiderFoot, etc.), persistence strategies, and architectural recommendations. Recommends a local daemon + browser SPA model with append-only JSONL observation logs.

  • ecosystem-and-walker-data-sources.md: Detailed survey of data acquisition sources for git/git-annex discovery, ranking git-annex branch inspection, git annex map --json, git ls-remote, datalad-registry, and forge APIs by leverage.

  • semantic-layer-quay-ontosphere-ontologies.md: Evaluation of QUAY ontology and Ontosphere editor for semantic modeling. Concludes QUAY is unsuitable as the primary language but useful as a controlled vocabulary; recommends LinkML for the core model with RDF as a generated export.

  • generic-graph-explorer-and-datalad-concepts.md: Analysis of datalad-concepts as a generic property-graph model, identifying Thing, Property, Statement, and reification patterns already present in the schema.

  • pluggable-kg-exploration-and-brainkb.md: Assessment of dump-things-service as the persistence layer and BrainKB's pluggable architecture. Identifies TRAPI/Translator as prior art for pluggable expansion.

  • tech-graph-visualization-stack.md: Technology recommendations for the rendering layer, recommending Cytoscape.js + fCoSE for compound-node support, incremental layout, and multi-edge handling.

  • tools-git-native-visualizers.md: Survey of existing git visualization tools, identifying git annex map as the only existing tool with repository-level nodes and remote edges.

  • repo-embedded-things-and-collections.md: Design for storing things inside .git/ directories and managing multiple enrichment collections over shared nodes.

  • vocabulary-for-clones-and-remotes.md: Verification that no existing vocabulary covers clone/remote/submodule topology; recommends extending datalad-concepts with LinkML and mapping to ForgeFed.

  • distribution-modeling-and-repo-identity.md: Analysis of Distribution as the superclass for repository forms and the identity problem for pure git repositories.

  • README.md: Index and synthesis of all research tracks with provenance notes and caveats about egress-blocked sources.

Notable findings

  • MVP scope: git annex map extended with interactivity, persistence, and incremental expansion is achievable in weeks
  • Architecture: Python + FastAPI daemon on localhost + browser SPA (Cytoscape.js), not Electron/Tauri
  • Persistence: Append-only JSONL observation logs in git repo + gitignored SQLite index + separate view files
  • Merging: Solved by naming convention (<timestamp>--<agent>--<runid>.jsonl), not CRDTs
  • Vocabulary: No existing standard covers git clone topology; extend datalad-concepts with LinkML and map to ForgeFed/DOAP/PROV-O

https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9

claude added 30 commits August 21, 2026 22:23
…zers

Interim landing of two of five parallel research tracks for the
git walker/drawer issues (#1, #4, #5, #6):

- tech-graph-visualization-stack.md: survey of browser graph rendering
  libraries, layout engines, exploration apps, view-state persistence
  formats and their licenses.
- tools-git-native-visualizers.md: survey of existing open source tools
  that draw/navigate git repo, branch and remote relationships.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Third of five parallel research tracks for the git walker/drawer issues
(#1, #4, #5, #6): survey of incrementally-expanded persistent knowledge
map tools (Maltego model, AWS graph-explorer, Juggl, OpenCTI, Litmaps),
application architecture options, worldmap persistence and merge design,
plus naming, scope and a phased roadmap proposal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Final two pieces of the five-track research for the git walker/drawer
issues (#1, #4, #5, #6):

- ecosystem-and-walker-data-sources.md: git-annex/DataLad prior art,
  forge and discovery APIs, measured costs for cheap remote-state
  acquisition, RIA layout, security of ssh spidering, crawl algorithm.
- README.md: synthesis across all five tracks with editorial judgement,
  ranked data sources, scope and MVP proposal, open questions.

Verified independently while synthesizing:
- 'git annex map --json' exists as of git-annex 10.20250605; the todo
  referenced from issue #1 is done.
- Command/Map.hs is AGPL-3-or-later (not GPL-3 as two tracks reported),
  so its --json output should be consumed rather than its code reused.
- The --json schema carries no host/cluster grouping and no trust level,
  and silently drops dead remotes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Reviews concepts.datalad.org (read from the psychoinformatics-de/datalad-concepts
source, as the site is egress-blocked here) and the orinoco explore view of the
CON test site, in light of the reframing towards a generic graph exploration
system with typed expansion, refresh, and per-model view tuning.

Findings:
- The 'things' module is already a generic reifiable property-graph model
  (Thing/Property/Statement/AttributeSpecification, relations-mixin), and
  Statement.characterized_by provides data-carrying edges.
- No version-control vocabulary exists at all (Repository/Clone/Remote/Branch
  all grep to zero); git commits and annex keys appear only in examples,
  though an annex-key namespace is already reserved.
- No observation/vantage layer for staleness of the description itself.
- The orinoco explore view emits untyped edges, discarding relation semantics
  the source records carry; it is static, with no expansion or saved view.
- Per-model presentation tuning already exists as LinkML sh:/dash: annotations
  driving shacl-vue forms; the proposal is a parallel graph: annotation set.

Also amends the synthesis README: the earlier claim that data-carrying edges
rule out RDF is superseded by dlthings:Statement reification.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
- Replace the inferred 'project has clearly anticipated VCS' claim with the
  actual evidence and its limits: three identifier namespaces (gitsha,
  dataset-uuid, annex-key) are already minted under concepts.datalad.org in
  demo-research-assets' XYZDistribution example, while the type still points
  at example.org; and extra-docs/usage-considerations.md lines 179-186 name
  the 'network of dataset clones' in prose. No stated roadmap exists.
- Note the shaclvue: namespace precedent for tool-specific presentation terms.
- New section 5a: plain git repos have no annex branch, so their remotes can
  only be read by running git where the repo lives. This makes ssh a primary
  walker path and fixes the backend as a real local program: shell out to the
  system ssh, inherit ssh-agent/ssh_config/ProxyJump, support interactive
  auth via BatchMode detection and retry, keep probes as POSIX sh.

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

Assesses dump-things-service (read from the christian-monch GitHub mirror) as
the record store: LinkML collections over datalad Thing, record_dir/sqlite
backends, incoming-vs-curated areas, token/Forgejo authz, JSON+Turtle output.
Its retrieval API has no traversal and no backward query, so a derived
adjacency index is needed in front of it.

Assesses BrainKB (sensein/BrainKB, sensein/brainkb-ui) against the three axes
asked about: no plugin/adapter interface; its d3 graph view operates on an
uploaded triples file with click-to-select rather than expanding the live
store; but contribution, RBAC and LLM-assisted extraction are strong, and
evidence/assertion semantics are its premise. Sibling with opposite emphasis;
align at the model level, not the code level.

Surveys pluggable-expansion prior art (NCATS Translator KP/ARA/TRAPI/Biolink,
Knowledge Beacons, Comunica, MCP as a possible transport), evidence modelling
(nanopublication assertion/provenance/publication-info split, which refines
the earlier observation record), and model-driven UI platforms (metaphactory,
LinkedDataHub, shacl-vue, Reactodia), noting none does model-driven graph
exploration.

Proposes the two collections in one store, one UI, two perspectives test as
the cheapest proof of the pluggability claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Reads con/query-things (now cloneable) and dandi/dandi-bib from source.

- Corrects the previous document: query-things already implements forward
  (filter-linked-pid), backward and recursive (filter-links-pid) traversal,
  normalises association-class edges, anticipates time-windowed matching, and
  resolves pids across collections. What is missing is an index, not the
  semantics.
- Tests git propagation directly: contents of .git/ do not survive a clone,
  and neither do refs outside refs/heads/*; only a real branch travels. This
  gives a two-tier split matching dump-things' incoming/curated areas --
  .git/orinoco/ for transactions and caches, an orinoco branch for curated
  records in record_dir layout, fetchable with one cheap command.
- Repo-as-seed browsing, with file:/// before ssh, remote exploration by
  running the same tool remotely with a POSIX sh fallback, and aggregation as
  a vantage-point-preserving merge.
- Collections split by provenance rather than entity type, with a four-step
  identity strategy for common nodes (People, repos, papers); dandi-bib is
  both a node and a collection provider.
- Sharpened first milestone that exercises all four architectural claims with
  no ssh, no forge API and no new vocabulary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Answers whether an ontology or controlled vocabulary already covers the git
graph, whether to extend datalad-concepts and map outward, and whether any
W3C group is producing a schema.org extension.

Verified from source rather than search summaries:
- schema.org release 18.0 has no repository/clone/remote/branch/fork/commit
  class at all, and codeRepository has rangeIncludes schema:URL -- a link,
  not an entity.
- ForgeFed (head 2026-05-03) is active with 41 terms; its mirroredFrom/
  mirrors family is the closest existing analogue to clone-of, and cloneUri
  and forkedFrom are directly usable.
- SEON is abandoned since 2013 and its Clone class means *code clone*
  (duplicated fragments), not repository clone -- a false friend.
- SWHID is now ISO/IEC 18670:2025; its snp object identifies the full set of
  branches of a repo at a moment, which is a usable clone-state primitive.

Concludes that existing vocabularies model the project, the content, or the
forge social layer, but never the deployment topology -- because a named
remote is local, asymmetric, mutable and private, which is exactly what
standards bodies do not mint terms for. Recommends authoring a things-vcs
module and linking by LinkML mappings rather than owl:imports, with enum
values taken verbatim from git-annex, and defers standards engagement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Resolves open question 4 of the vocabulary document.

- LinkML does support mixins (50 uses in datalad-concepts), so the premise
  about multiple inheritance is half right; slots are still correct here,
  because the variation is per-instance, not per-schema. Rule adopted: mixin
  when the variation changes which slots exist, slot when it changes a value.
- Recommends orthogonal enum slots (vcs, layout, annex_mode, packaging) over
  a flat feature bag, so layout is validatable and groupable. A RIA store is
  a Distribution containing Distributions via parts, which also gives bundles
  and submodule nesting; vcs: none covers special remotes with no git.
- Clone disappears as a class and becomes a perspective over Distribution.
- Measured the template-sibling case: a template, two independent descendants
  and one true clone all share the same root commit, so root commit cannot
  discriminate. Merge-base position plus containment does (0.19 vs 1.00).
  Recommends containment over Jaccard.
- Argues no content-based immutable repository id can exist, since false
  merge (shared template prefix) and false split (history rewriting) impose
  contradictory requirements; identity must be extrinsic or asserted.
- Verified the DataLad dataset id is tracked in the default branch
  (dandi-bib), notes the template-copy trap, and ranks identity evidence by
  cost and strength. Identity becomes a claim with provenance over objective
  computed relations, never a silent merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
- Reweight the DataLad dataset id: it settles ~99% of cases and the design
  should lean on it rather than hedge. The in-tree template-copy trap becomes
  a lint (loud error when two demonstrably different datasets declare the
  same id), not a general suspicion; matching ids otherwise merge silently.

- Add the remote-graph signal (shared common clone among remotes for the same
  tracked default branch) and test it against the same fixture. Taken alone
  it gets the case backwards in both directions: the two template siblings
  share an upstream and a tracked branch while not being the same repository,
  and the true clone has a different upstream from both. It measures lineage,
  not identity, for the same reason history overlap does.

  It becomes strong in two forms: mutual remote references, which templates
  never produce and which the spacetop map in issue #1 exhibits; and as a
  free candidate generator, where connected components over canonicalised
  remote URLs bound the set of pairs that the expensive checks are run on.

  Adopts the resulting two-stage identity algorithm: remote topology bounds
  the problem, declared ids resolve it, history overlap breaks ties for the
  plain-git remainder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Adds reproducible worldmap.json fixtures (fixed epoch, seeded RNG) for
comparing prototype graph explorers:

- s1-spacetop  24 nodes  host clustering, disagreeing remote names, special
                         remotes, duplicate annex UUID error, dead remote
- s2-babs-ria  51 nodes  superdataset/subdataset and RIA-store nesting, 40
                         per-subject result branches, worktrees, aheadness
- s3-forks     68 nodes  plain git with no UUIDs, 52 inactive forks to grey
                         out, preview-before-add, and the measured template
                         sibling trap at containment 0.19

Node/edge shape follows the Distribution model, with parent for containment
and kind naming the relation to expand along.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
The three bake-off teams are writing into issue-1/prototypes/team-{a,b,c}/.
Keep source, screenshots, exports and findings; drop node_modules and build
artifacts. Team output itself is committed once the runs finish.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Third bake-off approach: sigma 3.0.3 + graphology, deliberately without
compound nodes, testing whether perspectives and filtering can substitute
for nesting.

- stdlib http.server backend, seed-only reveal, POST /expand with a real
  300-900ms probe delay, GET /synthetic up to 30k nodes
- five perspectives (remotes, storage, lineage, topology, health) switching
  in 2.5-15.4ms without moving any node
- containment without compound nodes via four layers: nested annulus
  packing, a hull canvas behind sigma, collapse-to-meta-node, semantic zoom
- 38 Playwright screenshots, all verified non-blank, zero console errors

Their own negative findings: ~600 of 1862 client lines exist only to replace
one library feature; canvas edge labels crowd from ~150 nodes and are
already tight on s1 at 24; collapse stops paying above ~500 nodes for sparse
groups because cross-group edges grow; and the 10k hairball is edge
aggregation, which compound nodes would not have fixed either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
First bake-off approach: Cytoscape 3.34 + fcose with fixedNodeConstraint
pinning, layout-utilities for placing new nodes, node-html-label badges.

- stdlib http.server backend, seed-only reveal, POST /api/expand with a
  300-900ms probe delay; stateless, client sends known ids and the server
  returns a recomputed frontier that drives the +N hidden badges
- compound hosts and RIA stores nested 3 levels deep in s2, per-edge remote
  names, aheadness chips, hand-rolled collapse with meta-edge bundling,
  s3 grey-out / preview / add-as-remote, live layout-stability HUD
- 16 Playwright screenshots, all verified non-blank; raw metrics committed

Central claim verified from the raw metrics rather than the summary: with
pinning, displacement of already-placed leaf nodes is 0.00px mean AND max
across all 19 pinned probes, against 434-756px for the unpinned control and
455px for a naive full re-layout.

Their own negative findings: fCoSE cannot pin a compound, so a node that
becomes a container jumps 980px when it gains 40 children -- and in a
compound-first design the containers are exactly what grows; edge labels
render at 4.3-5.1px at the default fit zoom, below legibility, so s1's
remote-name story is carried by the inspector rather than the picture;
revealing all 68 nodes at once drops to 6.2fps with a 417ms blocked frame;
and s3's identity trap is a disconnected component that pure traversal
never reaches, which is a finding about the model, not the renderer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Second bake-off approach: @hpcc-js/wasm Graphviz computing real cluster
geometry in-browser, fed to Cytoscape as a preset layout, plus an ELK
layered lineage view.

- stdlib http.server backend with GET /api/dot emitting cluster_<host>
  subgraphs, so the worldmap.json -> DOT -> browser Graphviz -> cytoscape
  preset round trip is demonstrated end to end
- save/load/continue with server-side canonicalisation; three genuinely
  self-contained HTML exports (464-496kB, zero external refs, verified
  loading over file:// with non-file requests aborted)
- 25 Playwright screenshots, all verified non-blank

Correction they found to the brief: dot_json in @hpcc-js/wasm 2.35.0 returns
the parsed DOT AST with no coordinates; the format carrying layout results
is plain 'json' (xdot).

Their own negative findings, verified here: persistence is excellent
(0px drift over 14 nodes on reload) but the generator is unstable -- one
expansion adding a single node moved 62 of 62 already-placed nodes by a
median 1588px, and about half of measured expansions had zero churn while
the rest were catastrophic. I confirmed the consequence independently: two
consecutive saved views differ by 88 of 155 lines, with coordinates churning
outright. Diff-friendly formatting cannot rescue an unstable generator.
Graphviz needed three hacks for fans (rank=same gridding, server-emitted
gridpos with client snapping, and an ELK fan pre-pass).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Cross-team comparison of the three prototypes, with every claim re-verified
against the committed artefacts rather than the teams' summaries.

The main result is that two failures appeared in all three prototypes
independently, across different renderers and layout engines, which makes
them properties of the problem and our model rather than of any stack:

- per-clone remote names do not survive the default view (measured at
  4.3-5.1px by one team, colliding at 51 nodes for another, avoided only by
  abandoning free layout for a grid), so the naming relation issue #1 is
  about needs label-on-demand rather than a static edge label
- expansion from a seed silently omits whole disconnected components; s3's
  template trap was unreachable, one team's first run reported 67/68 nodes
  with the finding never firing. A seed is not a root set, and containment
  must be a walkable relation.

Judgement: keep Cytoscape as the base, but give compounds their geometry
from a whole-graph pass and let the incremental layout place only leaves,
since fCoSE pins leaves at 0.00px yet cannot pin a container that grew by
980px. Adopt Team B's persistence and self-contained export, and Team C's
aggregate tier treated as edge aggregation rather than node hiding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Implements the judgement from BAKE-OFF-RESULTS.md: container geometry from a
whole-graph pass that reruns only when the container set changes, leaves
placed inside fixed container bounds, both tiers in a Web Worker, edge
aggregation on collapse, a real root set, and label-on-demand.

Verified here against the raw metrics and saved views rather than the team's
prose: containers other than the expanded one move 0px on 16/17 expansions
(vs 980px for Team A); one expansion changes 16 of 199 view lines (vs 88 of
155 for Team B) and an identical state diffs to 0; collapse cuts edges 86->7
and 66->3 (vs Team C's 773->1205 increase); longest frame at 68 nodes falls
to 100-117ms from 416.6ms; edge labels render at 13px at fit zoom instead of
4.3-5.1px; and s3 reachability is measured and displayed as 62/68 rather
than silently reported as 67/68. It loses to Team B on reload drift
(0.462px vs 0) and to Team A on first paint (324-354ms vs 311ms).

A control arm makes this falsifiable: every scenario was run twice, sticky
and full, and the full arm reproduces the failure at 10/17 zero with
displacements to 607px.

The team's own verdict corrects the judgement's reasoning and is recorded as
such: the win came not from composing Graphviz with fCoSE but from taking
container geometry away from the layout engine and storing leaf positions in
container-local coordinates anchored to a corner that never moves. They used
no Cytoscape compound nodes at all, and fcose output survived in only 2 of
20 tier-2 runs, so tier 2 wants a packing algorithm rather than a force
layout. The durable finding is a data-model one, portable to any renderer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
The reports referenced 28 screenshots as verbatim backticked paths, so none
of them rendered. Now:

- 20 inline mentions across team-a, team-c and team-d converted to figures
  placed after the paragraph that discusses them, with the filename kept in
  the prose so the reference still reads
- team-b's screenshot index table converted from 18 filename rows into
  captioned figures, including the six file:// export shots that were
  referenced by a glob
- a screenshot gallery appended to each team README, grouped by scenario:
  16 (A), 25 (B), 38 (C), 28 (D) images
- BAKE-OFF-RESULTS.md gains a side-by-side section showing the same moment
  rendered four ways for the two shared benchmarks (the s1 duplicate annex
  UUID and s3's 52 inactive forks), plus the two things only Team D managed:
  remote-name disagreement legible at fit zoom, and the unreachable-component
  affordance

All 162 image links verified to resolve; zero broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Adds issue-1/tools/worldmap-crawl.py: emits the scenario worldmap.json schema
from real git/git-annex repositories using plain git only -- git-annex and
datalad need not be installed. Reads remotes with their per-clone names,
branch tracking, ahead/behind from local refs without network, worktrees,
submodules as containment, the DataLad dataset id out of the tree, and
uuid.log / remote.log / trust.log straight from the git-annex branch via
git cat-file. --depth follows local remotes; --ls-remote allows one round
trip per unresolved remote.

Wires Team D to accept it: server discovers any directory under WORLDMAP_DIR
holding a worldmap.json and re-scans per request, and the client populates
its dropdown from GET /api/scenarios instead of a hardcoded list.

Verified end to end on real repositories. On a shallow clone of dandi-bib the
crawl yields 4 nodes; after one 1.27s fetch of just the git-annex branch it
yields 28, discovering 23 clones with no ssh and no credentials, and
expanding the seed in the prototype returns 27 nodes and 49 edges in a single
probe. That reproduces finding 2 of the synthesis on real data rather than on
a fixture. Browser session had zero console errors.

Documents plainly what it does not do: no ssh, no forge APIs, no identity
resolution, no .git/orinoco persistence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Testing worldmap-crawl.py against a real collection of spacetop datasets
surfaced three defects that made a 108-node crawl render as 6 nodes:

- The server's WALKABLE relation list was hardcoded, so annex_knows edges
  emitted by the crawler were not expandable and 92 of 108 nodes were
  silently unreachable. WALKABLE is now derived per worldmap from the
  relations actually present, with the original vocabulary as the ordering
  baseline. Same class of bug as the hardcoded scenario list.

- There was no way to show an already-crawled graph. 'expand all' only
  un-collapses containers and never probes, so the only path was clicking
  each seed and each relation in turn. Adds a 'reveal all' button and
  ids:'*' support in /api/materialize: for a crawl the data is already on
  disk, so probing one relation at a time is theatre.

- Clones discovered through uuid.log all landed under a single 'unknown'
  host. git-annex descriptions are conventionally user@host:/path, so the
  host is now parsed out and clones cluster by real machine, which is what
  makes the issue #1 style host grouping work.

Verified end to end: a six-repo crawl now reveals 36 nodes across github.com,
localhost and the real annex-known hosts, then 41 nodes across 8 hosts once
descriptions are parsed, with zero console errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Adds issue-1/exploration-history-and-undo.md and implements it in the Team D
prototype.

Two rules make undo correct rather than merely plausible:

- Undo operates on the view, never on the store. A step is undone by hiding
  what it revealed, not by forgetting what it taught, because crawled facts
  are append-only knowledge exactly as observations are in the store design.
  Redo therefore needs no network and never re-runs a 300-900ms probe, and
  undo can never lose data. The first implementation violated this by
  truncating the edge array, which both destroyed knowledge and made redo
  impossible; the fix touches only visible, collapsed and the layout
  snapshot, since edges are already filtered by visibility at render time.

- A step snapshots container geometry so undo restores positions rather than
  recomputing them. Re-running layout on undo would move everything and throw
  away the 0px stability that is Team D's whole result. This is cheap because
  leaf positions are container-local: a snapshot is two small maps, not one
  entry per node.

Full history to 100 steps with redo, jump-to-any-point, Ctrl+Z / Ctrl+Shift+Z,
a history panel, and no entry recorded for probes that return nothing or fail.

Verified by driving the real app: 5 steps 2->51->11->51->11->3, undo walks
back the exact mirror 11->51->11->51->2, undo disables at the start, redo
replays all 5, undo/redo cycle drift 0.00px, zero console errors.

Scope explicitly excludes persistence across reloads, collaborative undo, and
undoing writes -- a view-history control silently reverting a git remote add
would be a serious surprise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Written as a TODO spec, not implemented.

Badges on repository rectangles, so the map answers questions without being
clicked: unwalked relation counts, humanised annex size held, annex-ignore,
form, trust, and health. Shaped by a measurement rather than taste -- node
text renders at 6.8px at fit zoom, so badges are glyph-and-colour first with
text only at reading zoom, capped at four plus overflow, toggled per group.

Two design points worth the spec:

- Annex sizes need neither git-annex nor the network. Keys encode their own
  size in the -s<bytes> field, so summing over the location logs in an
  already-fetched git-annex branch gives believed bytes held per UUID for
  every clone on the map. Recorded as an observation with its timestamp,
  since it is what the last sync claimed, not verified truth, and keys
  without a size field are reported separately rather than undercounted.

- annex-ignore is per-clone config, so it is an edge property; 'ignored by
  every clone that knows it' is a derived graph finding worth a node badge,
  because it means the map contains no route to content there at all. The
  fact stays on edges and the conclusion appears on a node with nothing
  denormalised.

Also specifies the relation details panel, fixing that clicking a remote
leaves the panel showing the repository. Content is ordered by cost: free
crawled facts immediately (per-clone names and the disagreement, annex-ignore,
recorded-vs-observed uuid, trust, ahead/behind with its staleness), a branch
correspondence table behind one ls-remote, and content comparison shown
log-derived and labelled believed with verification offered separately. Every
computed row is stored as an observation, so laziness keeps the panel honest
about known versus assumed rather than merely being a performance tactic.

Lists what worldmap-crawl.py must collect for each, with costs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Two corrections from review, both verified against the git-annex source.

- Do not hand-sum key sizes. The earlier draft proposed parsing -s<bytes>
  out of key names in the location log; that reimplements badly what
  git-annex already does. 'git annex info' reports the annex sizes of each
  repository in one command, with --json, --bytes, --show= to skip
  unnecessary work, and --fast; and the CHANGELOG for 10.20240831 records
  'info: Improved speed by using new repository size tracking', so it is
  maintained rather than recomputed. Where git-annex is absent the size is
  reported unknown rather than estimated, since an estimate that disagrees
  with git annex info is worse than a blank.

- For pairwise comparison use a matching expression rather than set
  arithmetic: git annex find --in=X --not --in=Y with --format exposing
  bytesize and key. Both directions are shown as two figures, never one
  signed number, because they answer different questions.

- annex-ignore: a short, explicit, overridable list of forges that cannot
  carry annexed content over plain git is allowed, but marked as an
  assumption visually distinct from an observed setting -- a wrong prior that
  looks like a measurement is a bug nobody will find. The sharper point is
  the git-lfs special remote (Remote/GitLFS.hs), which stores annexed content
  on exactly such a host: annex-capability is a property of the route, not
  the host, so the same host can carry both an annex-incapable git remote and
  an annex-capable git-lfs special remote. They are two distributions sharing
  a host, which the instance-slot model already expresses and a host-level
  flag could not. Host nodes are therefore never coloured annex-incapable,
  and a git-lfs remote to such a host is the interesting configuration rather
  than a contradiction to warn about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Implements the badge half of the badges spec plus two requested features.

- Node badges (web/src/badges.js): glyph-and-colour indicators grouped as
  health / annex policy / storage / topology / form, ordered by priority,
  capped at four plus overflow, toggled per group from the toolbar and
  persisted in localStorage. Rendered above the node label because at fit
  zoom the label text itself is ~7px. 'ignored by all known' is computed as
  the derived graph finding the spec calls for -- unanimous annex-ignore over
  at least two inbound edges -- rather than stored on the node.

- Hide node / hide container, from the details panel. Hidden nodes leave the
  view but stay in the store, appear in a Hidden panel with show / show all,
  and are un-hidden automatically when a later expansion from another clone
  reaches them. Hiding is a history step, so Ctrl+Z brings them back.

- Version stamping: the server reports ba97756-dirty of its
  checkout, the crawler stamps tool_version, git_version, git_annex_version
  and crawled_at into every map it produces, and the footer shows both, so a
  screenshot names the exact code that made it.

- Crawler now collects remote.<n>.annex-ignore and pushurl, and marks known
  annex-incapable forges with annex_incapable_assumed -- flagged as an
  assumption distinct from an observed setting, and never applied to a host
  node or to a special remote, since a git-lfs remote to the same host can
  carry content.

Bug found and fixed by testing: the history snapshot did not include the
hidden set, so undoing a hide restored the node count but not the hidden
nodes. Verified: hide node 37->36, hide container 37->33 including its four
children, undo restores 37, zero console errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
There were 13 research documents and an index, but nothing tracking features
from requested through specified, implemented and verified -- status was
buried inside individual documents and the answer to 'is X built?' required
reading several of them.

DESIGN.md is that missing document: settled architecture with links to where
each decision was argued, a feature ledger across exploration, rendering,
data and provenance with an explicit status per feature, the rules that
constrain future work, the known gaps and untested claims, and the ordered
next steps. README now points at it first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Two requested features, both cheap because of the container-local coordinate
model, and DESIGN.md updated in the same commit per the new working rule.

- Drag to reposition. A container owns a world centre, so dragging it carries
  its children for free; a repository owns an offset from its parent's
  top-left, so dragging it cannot take it out of the box. Dragged nodes are
  pinned so later expansion and layout runs work around them, the move is a
  history step, and placement is already part of the saved view. Measured:
  container drag moved its 3 children 494px each and everything else 0.00px;
  leaf drag moved only itself, stayed inside its container box, and survived
  save+load with 0.01px drift; undo of a move restored 0.00px.

- Bundle cross-container edges into one summary arrow per container pair,
  toggled from the toolbar. Implemented by reusing the collapse roll-up with
  a different notion of who represents a node, rather than adding a second
  aggregation mechanism. Edges inside a single container are deliberately
  left alone. Measured on a real crawl: 26 drawn edges -> 7.

Adds two rules to DESIGN.md: new roll-ups reuse effectiveId rather than
adding a mechanism, and user placement outranks the layout engine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Clicking a remote arrow showed the repository, so a remote's own
configuration was unreachable. A relation is a first-class thing in this
model -- RemoteLink is a reified statement carrying its own data -- so it is
now selectable and inspectable in its own right.

Content is ordered by cost, which is the design rather than an
implementation detail. Free and shown immediately: the name this clone uses,
every other name the same peer is called by across the map, URL and pushurl,
resolution, annex-ignore, the annex-incapable forge assumption rendered as an
assumption, recorded annex UUID, trust, and ahead/behind labelled as of the
last fetch with its observation age. One round trip (ls-remote branch table)
and expensive (content each side lacks) are present as explicit disabled
actions pointing at their spec, rather than being silently absent.

Bundled and collapsed arrows list their member relations and drill down into
each; a member that is not drawn on its own is still inspectable. Both
endpoints are navigable, which swaps the panel back to a repository.

Fixed while testing: aggregate() wraps every edge, so a lone edge arrived
with members:[oneId] and rendered as a summary; only treat it as one when it
actually summarises more than one.

Verified on s1-spacetop: the panel reports the peer is called 2 different
names with the clones using each, a bundled arrow of count 2 lists both
members and drills into them, and navigating to an endpoint returns to the
distribution panel. Zero console errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Isolated agent runs create a git worktree inside the repository, which shows
up as untracked and must never be committed. Ignored narrowly rather than
ignoring all of .claude/, so project-level settings there could still be
tracked if ever wanted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
An isolated UX pass drove the app with a real mouse (never by poking state)
across 18 probes on all three fixtures, and found that drag and bundling were
verified only on happy paths. Merged its fixes and its findings.

Bugs found and fixed, each measured:

1. A dragged repository escaped its container entirely -- there was no bound
   at all, and clampTo/innerBounds existed but were never called. A leaf sat
   551.9px right and 255.2px below its box; a 40x45px drag already pushed one
   42.9px through the bottom. This contradicted both the README and DESIGN.md.
2. Save->load silently lost every nested container size: viewPayload wrote
   sizes, loadView never read them, so the RIA store reloaded at 210x76 with
   its 40 children outside the box, and re-saving wrote the loss to disk.
3. S.moved was dead code -- written, snapshotted, never read -- so rule 9 was
   unimplemented and collapsing a container shoved a hand-placed neighbour
   and its 40 children 513.31px.
4. A drag during an in-flight probe had its history entry eaten by abandon(),
   making a 297.53px move un-undoable and then silently reverted by an undo
   labelled 'expand'.
5. Hide then show of a dragged leaf lost its placement (236.52px).
6. The bundle button stayed lit after an undo that turned bundling off.
7. loadView restored the wrong zoom, found by asserting save->load->save is a
   fixpoint.

Bundling itself is sound: conservation holds in all five collapse/bundle
states on all three fixtures, unbundle restores the exact edge id set, and
toggling moves nothing. But its benefit is scenario-dependent -- 47.1% on s2
against 1.5% on s3 -- so the ledger's 26->7 was not the general case and is
now stated as such.

DESIGN.md gains 21 testable requirements covering placement, nothing-else-
moves, undo, hide, save/load and aggregation, each with its measured number;
the claim that the offset model prevents escape is corrected; and the drag,
bundling, hide and hand-arranged-position rows are rewritten with evidence.

Merge verified rather than trusted: their main.js was confirmed to contain
the relation-details panel added meanwhile, and after merging both were
re-tested -- relation panel intact with zero errors, leaf clamped with 0.0px
overflow, save->load 0.01px position and 0.00px size, collapse round trip
0.00px over 51 nodes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
…tree floods

Items 1 and 2 of the ledger, plus two crawl-shape bugs reported from a real
worktree-heavy checkout.

Item 1 -- the three known-wrong interactions:
- A container was sized to fit its slot grid exactly, so whenever the child
  count landed on a tier boundary a repository had 0px of room and dragging
  it did nothing (4 of 9 s1 containers). Added SLACK to container sizing;
  measured per-container drag room is now 130-334px with zero containers at
  0px.
- Hiding a container left its children drawn but detached from the box their
  coordinates were relative to. Hiding a container now always takes its
  descendants: 37 -> 33 nodes for a container with 3 children, 0 orphans.
- A container dropped overlapping another now says so, in the toast and as a
  dashed warning border. Rule 9 says user placement wins, so the overlap is
  allowed -- but silently allowing it looked like a rendering fault.

Item 2 -- git-annex wiring:
- Crawler runs git annex info --json --bytes --fast --show='annex sizes of
  repositories' and attaches annex_bytes per node, matching by uuid or
  description. Parser unit-tested against real-shaped output, against
  git-annex being absent, and against malformed lines.
- New POST /api/relation backs the relation panel's two buttons: a real
  git ls-remote branch correspondence table (same/only-here/only-there/
  differs), and git annex find --in=X --not --in=Y as two figures never one
  signed number. Every result names the command that produced it.
- These are marked as the only unverified rows in the ledger: git-annex is
  not installed here, so no end-to-end run exists.

Crawl-shape fixes:
- git worktree list reports every worktree whichever one you run it in, so N
  worktrees emitted N*N worktree_of edges -- the green hairball. Anchored on
  the main worktree and globally deduped: 5 worktrees now yield 4 arrows, not
  25, each pointing at the original location.
- Linked worktrees share .git/config, so each re-emitted every remote: 20
  worktrees x 59 remotes drew 1180 identical arrows and implied the remotes
  belonged to each checkout. Emitted once from the main worktree: 5
  worktrees x 2 remotes now yield 2 edges, not 10.

DESIGN.md gains three crawl-shape requirements, including that a crawl-shape
bug is invisible in the viewer and belongs in the crawler's own output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
claude added 7 commits August 28, 2026 02:28
- The details-panel button now flips to 'show' on a hidden node and restores
  it. A hidden node keeps its selection so the panel stays open where the
  hide happened, and its kind line marks it hidden; showing a container
  brings its descendants back. Verified: leaf 51->50->51, container
  51->48->51, zero console errors.

  Also fixes the hide path not repainting the inspector, which is why the
  button appeared not to change.

- Remotes are classified by what tracks them: each remote edge carries
  tracked_by (the branches whose branch.<b>.remote is this remote) and
  tracking = current | branch | none. Verified against git config ground
  truth on this repository: origin is tracking=current with tracked_by
  ['claude/git-graph-viz-research-85imhc', 'main'].

  The viewer gains a current / tracked / all filter over remote edges, and
  styles current thicker and untracked dotted, so 'which remotes am I
  actually working with' is answerable without reading config. Measured on a
  mixed map: 8 drawn edges -> 6 under 'current', dropping exactly the two
  untracked remotes. The relation panel names the tracking state and the
  branches.

  tracked_by is a list rather than a flag because each worktree has its own
  HEAD, so 'current' is a question about a node, not about the edge --
  recorded as requirement 25.

Confirms for the record that hiding a container no longer leaves its children
on screen; that was fixed in 4db4f76, after the b21233b build the report came
from. Re-verified here on a real github.com container: 41 -> 35 nodes for a
container with 5 children, 0 orphans, and show restores 41.

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

The tracking data landed last commit but only reached edge styling and a
filter -- the two places actually read, the arrow-count badge and the expand
list, still lumped every remote into one bucket, so an untracked remote
looked exactly as worth expanding as the one you work with.

- rel_counts now carries remote@current:out, remote@tracked:out and
  remote@untracked:out beside the total, computed per node because current
  depends on that node's HEAD.
- expand accepts them as relations, so you can walk only the remotes the
  checked-out branch tracks.
- the details panel lists them as separate labelled entries with hints, and a
  new badge names the count of remotes the current branch tracks.

Verified per node within one map: a checkout on claude/git-graph-viz-research
shows remote:out 1 / remote@tracked:out 1 / remote@current:out 1 and badges
'current 1', while a worktree on master with two untracked remotes shows
remote:out 2 / remote@untracked:out 2 and no tracked badge.

Also renames 'expand all' to 'uncollapse all'. It only ever unfolded
collapsed containers and never probed, and was reported as doing nothing
twice; 'reveal all' remains the control that shows everything crawled. Added
as rule 10: a control that silently does less than its name is a bug report
generator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Every interaction defect found so far was found by a human clicking, so
add the suite that would have caught them.

tools/e2e/setup-fixture.sh builds eight real repositories: a clone of
datalad/testrepo_gh with a live github remote, a submodule, two clones
(one carrying remotes no branch tracks), two linked worktrees, and the
same subdataset a second time with origin removed so that only shared
history and the dataset id relate the two copies.

tools/e2e/e2e.mjs crawls that fixture, serves it, and drives Playwright
through 25 invariants -- relations, not golden numbers, so the fixture
can grow. --worldmap/--scenario replays the viewer half against the
pre-generated s1/s2/s3 maps (24, 51 and 68 nodes), which is where a
regression that only shows on a wider map gets caught.

The suite immediately earned its keep: "collapse all" folded only
containers with no *visible* parent, so a container drawn without its
own parent on screen was skipped and its repositories stayed drawn --
reported twice as "collapse all still leaves individual repos". Fold
every container with visible children instead; an inner fold is harmless
when the outer one hides it. The stray check passed throughout, because
"no orphan drawn" and "only containers drawn" are different claims --
hence the new assertion, which states what the label promises.

DESIGN.md gains section 5 (testing): what the suite is, the five rules it
follows, and what it still does not cover -- git-annex, ssh, scale above
68 nodes, save/load/export, layout quality.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Three related asks: mark which remote connections are automated, deduce
and visualise the movement of data, and fold in the standard git-annex
groups as extracted metadata.

Verified the ground truth against a real git-annex branch rather than
recalling it. The location log is `<unixtime>s <1|0> <uuid>`, not the
+/- form; size is carried in the key name (MD5E-s359--...), so flow can
be byte-weighted with plain git and no `git annex info`; per-key
metadata sits alongside in <key>.log.met, which is where a
`metadata=` preferred-content expression gets its answers.

That probe also found something worth recording as a defect rather than
a feature. dandi-bib, the repository the annex path was demonstrated on
("23 clones, 1.27s"), has 25 UUIDs in uuid.log of which 24 are ephemeral
`runner@runnervm...` CI VMs and exactly one holds any content, with the
branch authored by github-actions[bot]. The crawler is right and the map
is wrong: it draws 25 clones where there is one repository and one
automated process. Downgraded that ledger row to "mechanism correct,
misleading result" and recorded the numbers in Known gaps.

The spec keeps policy (git-annex branch, global, free), automation
(config/hooks/cron/CI, per-clone, inferred) and flow (location-log
replay, O(keys), direction unrecorded) as three separate claims with
separate confidences, and six new rules in section 4 hold that line --
notably that direction of transfer is always inference, that a
location-log entry is a claim rather than an observation, that
preferred-content expressions must never be reimplemented, and that
world time must not be merged into the undo history.

Work is ordered cheapest first, and the cheapest item -- deciding
whether a clone is a person or a bot -- is promoted to the top of
section 7 because it needs no git-annex binary and fixes the map above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
…ld gap

Reviewed the harness by making it fail on purpose. A mutation drill --
sabotage collapseAll into a no-op, expect red -- ran GREEN, because the
server serves web/dist, a Vite build, and the build predated the
sabotage. The same gap means the collapse-all fix in 1209bad was first
"verified" by a build from before the fix (dist 13:43, fix 14:07); it
passed because the old code also passes on those maps. The suite now
rebuilds the viewer whenever web/src is newer than web/dist, the drill
cycle is proven both ways (sabotage -> 2 named FAILs, exit 1, artifacts
kept; restore -> auto-rebuild -> green), and that green is the first
genuine verification of the fix. The drill is documented in the README
and rules 37-41 in DESIGN.md hold the lessons.

The rest of the hardening, each item against a failure mode that
actually happened:

* free ports by default (probed in-process) plus a double scenario
  guard: the served list must name the requested scenario and the page
  must agree it loaded it -- the zombie-server / silent-fallback trap
  that once invalidated three runs is now a named failure. Two suites
  verified running concurrently.
* the server is polled until it answers, with its own output captured,
  instead of a blind 2.5 s sleep.
* quiescence waits (two identical position samples, floor + ceiling)
  replace ~25 s of guessed sleeps: the walk is ~10 s, was ~50 s, and
  two consecutive runs plus a concurrent pair showed no flake.
* try/finally around the whole walk: an unexpected throw is a named
  FAIL with a summary; browser and server always close; temp dirs are
  removed on success and kept on failure (the old --keep deleted
  nothing and had leaked 17 directories).
* repositories are discovered from the fixture and scenarios from
  scenarios/, so growth in either gains coverage by existing.
* setup-fixture.sh sets git identity via environment variables; the
  per-repo config covered only init'ed repos while clones commit too,
  which fails on any machine without a global identity.
* chrome resolution falls back from CHROME_PATH to the sandbox symlink
  to playwright's own browser, instead of pinning chromium-1194.
* offline mode verified end to end: 26/26 with the synthesised
  upstream.
* package.json pins playwright and gives npm test; a GitHub Actions
  workflow runs the whole suite on every push touching the prototype
  and uploads failure artifacts.

Full matrix at HEAD: 26/26 fixture (online and offline), 21/21 on each
of s1/s2/s3 serial and concurrent, bogus scenario exits 2 with a
message, mutation drill red then green -- 89 checks per full run-all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
A subdataset node is now the CHECKOUT at <super>/<path>, contained in
its own super -- so a worktree's initialized submodule sits inside the
worktree box -- and the edge label is the path, never the URL. The old
model made the .gitmodules URL target the node, which labelled
subdatasets by URL, claimed the upstream lives inside the super, and
let the last worktree crawled win the containment.

Fixing that surfaced three more crawl bugs, each now pinned by an e2e
invariant (30 on the fixture, 93 across the suite):

* `git worktree list` inside a submodule reports the absorbed git dir
  (.git/modules/...) as the main worktree; taken literally this invented
  phantom repositories and marked every submodule checkout a linked
  worktree, suppressing its remotes.
* submodule names containing spaces (testrepo_gh's "sub _1") were split
  at the first space and produced garbage nodes; .gitmodules is now read
  with `git config -z`.
* relative submodule URLs resolve against the superproject's ORIGIN
  (git's own rule), not its local path.

Declared-but-uninitialized subdatasets keep a URL-target node,
uncontained, marked state=not-initialized, emitted once per repository.
Initialized checkouts are queued for crawling, so their remotes and
dataset ids are picked up. The fixture initializes the submodule in
wt-x only, keeping the declared-but-absent case on wt-y; the fixture is
stamped with its script's hash so run-all --reuse rebuilds when setup
changes (same trap as a stale web/dist, caught live).

lenses-and-joined-graphs.md answers the generalization question: the
worldmap and the psychoinformatics /explore graph are two LENSES over
graphs of the same kind. A lens is a declarative bundle (which relation
becomes geometry, edge classes structural/route/affinity, type filters,
badges, primary action, layout); joins across collections are explicit
bridge records on confident keys (dataset id, canonical URL, host, DOI,
person) -- never merges -- navigated by portal badges that pivot panes,
not by cross-lens arrows. Staged plan puts the lens descriptor
extraction and toolbar type filters before the two-collections test.
Rules 42-44 and three ledger rows added to DESIGN.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
Replace the lenses doc's cheapest-first staged plan with a six-phase
implementation plan, reordered schema-first on the load-bearing
argument: a lens classifies relations by name, and only formalizing
the deployment model (LinkML, concepts.datalad.org framework) gives
those names stable URIs a lens document, a linter, or another tool can
reference. Phases: (1) things-deployment schema + worldmap-export
converter + round-trip validation in e2e; (2) LensDefinition as a
LinkML class, two hand-written lens documents, lens linter; (3) viewer
consumes the lens, gated on the unchanged e2e suite staying green;
(4) CON group graph as second collection under the portfolio lens,
absorbing the two-collections test, dump-things-service decision made
here; (5) Bridge records and portal pivots with their own fixture
invariants; (6) panes, co-highlighting, person bridges, perspectives
as things. DESIGN.md TODO item 4 rewritten to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWoTt2cAfzT6rLU2edwQk9
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