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
This commit is contained in:
tegwick 2026-10-19 10:00:20 +02:00
parent 749752df4e
commit b5b6f6bc00
15 changed files with 1290 additions and 52 deletions

View file

@ -4,19 +4,19 @@ type: workplan
title: "Relation explorer and knowledge journeys"
domain: infotech
repo: content-explorer
status: proposed
status: finished
flavor: implementation
depends_on:
- CONTENT-WP-0002
- CONTENT-WP-0003
created: "2026-10-12"
updated: "2026-10-12"
updated: "2026-10-19"
state_hub_workstream_id: "d78fa2e0-9052-5e3b-93b6-e8c86248bad1"
---
# Relation explorer and knowledge journeys
Make interconnected knowledge explorable beyond the basic browse and detail path.
Make interconnected knowledge explorable beyond the basic browse and detail path. T01 through T08 are done. See `workplans/evidence/CONTENT-WP-0004-ImplementationNotes.md` and `workplans/evidence/CONTENT-WP-0004-Review.md`.
## Work packages
@ -24,7 +24,7 @@ Make interconnected knowledge explorable beyond the basic browse and detail path
```task
id: CONTENT-WP-0004-T01
status: todo
status: done
priority: high
state_hub_task_id: "e64d9cdd-10ee-5a6e-b714-76037ca8bacc"
```
@ -33,7 +33,7 @@ state_hub_task_id: "e64d9cdd-10ee-5a6e-b714-76037ca8bacc"
```task
id: CONTENT-WP-0004-T02
status: todo
status: done
priority: high
state_hub_task_id: "78c2c68d-fe5c-532e-892d-73939d647bbe"
```
@ -42,7 +42,7 @@ state_hub_task_id: "78c2c68d-fe5c-532e-892d-73939d647bbe"
```task
id: CONTENT-WP-0004-T03
status: todo
status: done
priority: high
state_hub_task_id: "fad1f2fb-0f97-5512-88ae-dff430004811"
```
@ -51,7 +51,7 @@ state_hub_task_id: "fad1f2fb-0f97-5512-88ae-dff430004811"
```task
id: CONTENT-WP-0004-T04
status: todo
status: done
priority: high
state_hub_task_id: "274e7a5b-ce17-51c6-a073-6945b9a34ff8"
```
@ -60,7 +60,7 @@ state_hub_task_id: "274e7a5b-ce17-51c6-a073-6945b9a34ff8"
```task
id: CONTENT-WP-0004-T05
status: todo
status: done
priority: high
state_hub_task_id: "edf84d8c-8913-544a-9f39-3ef1d8a99468"
```
@ -71,7 +71,7 @@ state_hub_task_id: "edf84d8c-8913-544a-9f39-3ef1d8a99468"
```task
id: CONTENT-WP-0004-T06
status: todo
status: done
priority: high
state_hub_task_id: "d582553b-3966-55e0-b294-85d6cb42e516"
```
@ -80,7 +80,7 @@ state_hub_task_id: "d582553b-3966-55e0-b294-85d6cb42e516"
```task
id: CONTENT-WP-0004-T07
status: todo
status: done
priority: high
state_hub_task_id: "61d43835-8da7-5af3-9368-c3aa3120f486"
```
@ -89,7 +89,7 @@ state_hub_task_id: "61d43835-8da7-5af3-9368-c3aa3120f486"
```task
id: CONTENT-WP-0004-T08
status: todo
status: done
priority: high
state_hub_task_id: "018814db-3aab-5fd2-bebf-8e4c7d7047d2"
```

View file

@ -0,0 +1,59 @@
# 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.

View file

@ -0,0 +1,11 @@
# CONTENT-WP-0004 review
Date: 2026-10-19
Hop bound: 2 (`HOP_BOUND` in `ui/lib/graph.ts`). Requests above 2 are clamped to that bound.
Synthetic fixture: `ui/fixtures/large-graph.json`, a chain of 9 nodes (`n0`–`n8`, 8 edges) plus the isolated node `solo`. From `n0` at the hop bound, the neighborhood is `n0`, `n1`, and `n2`. `n8` stays outside. `excludedCount` is 6, matching the relations left out. Focusing `solo` keeps that node in the model and excludes all 8 edges. The same functions cover a cycle, two predicates between one pair, and a node with two parents (`ui/lib/graph.test.ts`).
R6: no deviation. Large-graph handling is the hop bound. Nodes and relations past it are counted as excluded. They are not drawn, and they are not dropped without that count.
T01–T08 are done. The test log is `npm test` in `ui/` (32 tests, two runs, both exit 0), saved as `/tmp/grok-goal-aedf5fe6975e/implementer/ui-test-1.txt` and `ui-test-2.txt`. The in-repo proof is `ui/lib/graph.test.ts`, `ui/components/EntityDetail.test.tsx`, and `ui/components/GraphExplorer.history.test.tsx`. Fixture validation and the dev-server bodies are recorded in `workplans/evidence/CONTENT-WP-0004-ImplementationNotes.md`.