content-explorer/workplans/evidence/CONTENT-WP-0004-ImplementationNotes.md
tegwick b5b6f6bc00 Add a bounded relation graph and knowledge journeys.
CONTENT-WP-0004 keeps outgoing and incoming labels apart, limits the graph to two hops, and stores focus and detail in the URL.

Assistant: grok
Assistant-Session: 01a1531b-6698-7732-8587-f28ea604bcdf
2026-10-19 10:00:20 +02:00

59 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CONTENT-WP-0004 implementation notes
Date: 2026-10-19
Hop bound: 2 (`HOP_BOUND` in `ui/lib/graph.ts`). A requested distance above 2 is clamped. Neighborhood walks are undirected. Each edge keeps its stored `from`, `to`, predicate, and id.
Journey fields did not require a schema bump. Manifest and snapshot 0.1 still set `additionalProperties: false` at the document root, and entity `properties` is already an open object. Collections, related ids, reading paths, glossary text, and link previews live there. Today's fixtures still validate.
## T01 — outgoing and incoming blocks
`partitionIncident` places every relation that touches an entity in exactly one block. Outgoing wording is the manifest forward label. Incoming wording is the inverse label. A self-loop is outgoing. `EntityDetail` renders the two blocks separately. When a caller lists only part of the incident set, `relationRemainder` is shown as “N more relations are not listed.”
Systems-thinking Feedback uses forward “Represented by” on `represented-by`. The incoming `models` edge uses “Modeled by”, which is not the forward string “Models”.
## T02 and T05 — bounded graph
`selectNeighborhood` returns the focus plus nodes within the hop bound. Cycles stop because each node is visited once. Parallel edges stay as separate adjacency records (`id`, `predicate`, `from`, `to`, `direction: "out"`). Both parents of a node stay. A node with no edges remains in the graph model and can be focused. The route is `/{dataspace}/graph?focus=<entity id>&detail=<entity id>&hops=1`. Hop 2 omits `hops`. The page shows the excluded-relation count.
## T03 — collections, related objects, reading paths
A declared `properties.collection` lists member links. `properties.related` lists related objects. `properties.readingPath` is rendered in that order. The crumb for the open entity is the current step. Each earlier crumb is a link to that step.
Feedback declares the collection “Ways to draw feedback”, a related link to Emergence, and the path System boundary → Feedback → Causal loop diagram.
## T04 — glossary and previews
`RecordLink` shows a glossary tooltip only when the target has `properties.glossary`, and a preview only when `properties.preview` has both a title and a summary. Causal loop diagram has both. Stock and flow model has neither: its link still resolves, and the page does not render a tooltip or preview for it. System boundary has no journey properties and still renders.
## T06 — nothing incident is silent
The Feedback page lists every incident relation id from the snapshot in exactly one block. The graph page prints the count of relations outside the neighborhood.
## T07 — focus and detail in the URL
`GraphExplorer` reads and writes `focus` and `detail` through the same history helper the page uses in the browser. Reload passes the query back in as `initialSearch`. Back and forward restore the previous focus and detail. Covered in `ui/components/GraphExplorer.history.test.tsx`.
## T08 — synthetic fixture
`ui/fixtures/large-graph.json` is a chain `n0`–`n8` (8 edges) plus an isolated node `solo`. It is a test fixture, not a published dataspace, so `validate_fixtures.py` still checks only the two example dataspaces.
From `n0` at hop bound 2, the neighborhood is `n0`, `n1`, and `n2`. `n8` is outside. `excludedCount` is 6, equal to the edges that do not have both ends inside that neighborhood. Focusing `solo` returns that node and excludes all 8 edges.
## Checks
Durable tests:
- `ui/lib/graph.test.ts`
- `ui/components/EntityDetail.test.tsx`
- `ui/components/GraphExplorer.history.test.tsx`
Captured on 2026-10-19 under `/tmp/grok-goal-aedf5fe6975e/implementer/`:
- `ui-test-1.txt` and `ui-test-2.txt`: `npm test` in `ui/`, 11 files, 32 tests, both exit 0.
- `typecheck.txt`: `npm run typecheck` in `ui/`, exit 0.
- `fixtures.txt`: `python3 scripts/validate_fixtures.py`, `python3 -m unittest tests.test_contract tests.test_ingest`, and `python3 scripts/check_ui_boundary.py`, exit 0.
- `launch-1.html` and `launch-2.html`: HTTP 200 for `/systems-thinking/graph?focus=urn:ce:systems-thinking:concept:feedback`. Both bodies contain “Represented by” and “Modeled by”.
- `graph.png`: headless Chromium loaded that URL with no page error. Focusing Causal loop diagram changed the neighborhood and the `focus` query. The screenshot is not blank.
No R6 deviation. The safeguard is the hop bound of 2, not a graph database or a draw-everything view.