Skip to content

Commit 5e38330

Browse files
committed
docs(adr-702): address spike review findings
ADR: - Engine responsibilities now list arrow-glyph rendering and edge-type coloring (propagating spike findings #1 and #2 from the findings section into the engine contract). - EngineNode interface adds label as first-class field; UnifiedGraphEngineProps adds edgePalette and showArrows props (finding #3 and full wiring of #1/#2). - Phase 1 gets an explicit pre-merge gate requiring a 1k-concept scale spike (finding #5). - LOC-reduction estimate tied to phase sequencing (~2k off phase 1, rest across phases 2–3) rather than a bare range. - Spike results section cites the committed spike-server.js path instead of the gitignored reproduction copy. Spike: - export-kg-data.sh: note the AGE agtype→jsonb cast assumption against the pinned image; switch SQL cycle to Python context manager for the JSONL read; clarify that node degree is undirected by design.
1 parent 86472b1 commit 5e38330

2 files changed

Lines changed: 54 additions & 12 deletions

File tree

docs/architecture/user-interfaces/ADR-702-unified-graph-rendering-engine.md

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,10 @@ The engine owns:
157157
- Edge index array (`Uint32Array` of length `2M`)
158158
- Force simulation (CPU or GPU, same parameter object)
159159
- Instanced node rendering (polygon or billboarded sprite)
160-
- Edge rendering (straight or bezier, via line shader)
160+
- Edge rendering (straight or bezier, via line shader) with optional
161+
edge-type coloring (see Plugin surface)
162+
- Arrow-glyph rendering on directed edges, as an instanced-triangle mesh
163+
anchored to the target end of each edge (togglable per plugin)
161164
- Camera (orthographic or perspective)
162165
- Pointer picking via `instanceId` (uniform across node modes)
163166
- Hidden mask (per-instance visibility, respected by both sim and renderer)
@@ -211,9 +214,24 @@ Plugins implement the existing `ExplorerPlugin` interface from ADR-034 and
211214
embed the engine component as their scene. The engine exposes:
212215

213216
```ts
217+
interface EngineNode {
218+
id: string; // stable key (e.g. kg concept_id)
219+
label: string; // human-readable display text
220+
category: string; // opaque string; resolved via palette
221+
degree: number; // used for size scaling
222+
pinned?: boolean;
223+
}
224+
225+
interface EngineEdge {
226+
from: string;
227+
to: string;
228+
type: string; // relationship type; resolved via edgePalette
229+
weight?: number;
230+
}
231+
214232
interface UnifiedGraphEngineProps {
215-
nodes: EngineNode[]; // { id, category, degree, pinned? }
216-
edges: EngineEdge[]; // { from, to, type, weight? }
233+
nodes: EngineNode[];
234+
edges: EngineEdge[];
217235
projection: '2D' | '3D';
218236
nodeMode: 'sprite' | 'poly';
219237
physicsBackend?: 'auto' | 'cpu' | 'gpu';
@@ -223,7 +241,12 @@ interface UnifiedGraphEngineProps {
223241
highlightedEdges?: Set<string>;
224242
selectedId?: string | null;
225243
hoveredId?: string | null;
226-
palette: (category: string) => string;
244+
palette: (category: string) => string; // node category → hex
245+
edgePalette?: (edgeType: string) => string; // edge type → hex (optional;
246+
// falls back to endpoint
247+
// gradient if absent)
248+
showArrows?: boolean; // render target-end arrow
249+
// glyphs; default true
227250
onSelect?: (id: string | null) => void;
228251
onHover?: (id: string | null) => void;
229252
onHide?: (id: string) => void;
@@ -242,9 +265,11 @@ through the engine.
242265
### Positive
243266

244267
- **One engine, one physics, one rendering path.** Maintenance surface
245-
shrinks substantially — estimated ~2000-3000 LOC reduction across the
246-
three current surfaces once migration completes, plus removal of
247-
`react-force-graph-3d` as a dependency.
268+
shrinks substantially — the three current surfaces total ~4,400 lines
269+
(`ForceGraph2D` 1840, `ForceGraph3D` 2052, `EmbeddingScatter3D` 529).
270+
Phase 1 alone takes ~2000 lines off the 3D surface (V2 replaces V1 and
271+
`react-force-graph-3d` is removed); phases 2 and 3 reduce the remaining
272+
two stacks. Expected total reduction ~2,000–3,000 lines by phase 3.
248273
- **10k-node real-time interaction** becomes viable for the first time.
249274
Current 3D hits visible frame drops at a few hundred nodes.
250275
- **2D and 3D share a camera and a sim**, so switching projection mode on
@@ -325,6 +350,11 @@ V1 stays registered. Users pick via the explorer dropdown. Once V2 reaches
325350
full parity and soaks, V1 is removed and `react-force-graph-3d` dep is
326351
dropped.
327352

353+
**Phase-1 merge gate:** before the phase-1 PR merges, a follow-up spike at
354+
kg-scale (1,000+ concepts) must validate the performance target on
355+
kg-shaped data at volume (per spike finding #5). The current spike (52
356+
concepts) validated shape compatibility but not scaling.
357+
328358
### Phase 2 — Add 2D projection
329359

330360
Extend the engine with `projection: '2D'`:
@@ -447,10 +477,12 @@ The spike lives in `spike/unified-3d/` on this branch. It consists of:
447477
- An export script (`export-kg-data.sh`) that pulls concepts and relationships
448478
from a live kg postgres and writes them to `spike/unified-3d/data/kg-graph.json`
449479
in the shape the atlassian-graph UI expects (`{nodes, edges, meta}`)
450-
- A drop-in spike server (`spike/unified-3d/reference/spike-server.js`) that
451-
replaces atlassian-graph's GraphQL-schema-backed `/api/graph`, `/api/type/:name`,
452-
`/api/stats`, `/api/categories` endpoints with static reads from the kg export,
453-
leaving the rest of the reference implementation unchanged.
480+
- A drop-in spike server (`spike/unified-3d/spike-server.js`; copied into
481+
`spike/unified-3d/reference/` at reproduction time so it resolves
482+
`express` from `reference/node_modules`) that replaces atlassian-graph's
483+
GraphQL-schema-backed `/api/graph`, `/api/type/:name`, `/api/stats`,
484+
`/api/categories` endpoints with static reads from the kg export, leaving
485+
the rest of the reference implementation unchanged.
454486

455487
The reference UI's vite dev server and the spike server both run cleanly and
456488
serve kg data through the atlassian-graph pipeline end-to-end. At the time of

spike/unified-3d/export-kg-data.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ set -euo pipefail
1515
OUT_DIR="$(dirname "$0")/data"
1616
mkdir -p "$OUT_DIR"
1717

18+
# AGE note: we cast agtype → text → jsonb. This works on the AGE image pinned
19+
# in this repo (see CLAUDE.md). Across AGE versions this cast can emit numeric
20+
# type annotations or unquoted keys that break jsonb parsing; if this script
21+
# starts failing after an AGE upgrade, switch to the kg REST API as the data
22+
# source instead of SQL.
23+
# Degree uses an undirected match (c)-[r]-() — each relationship is counted
24+
# twice (once from each endpoint) when summed across nodes, which matches how
25+
# the reference renderer scales node size.
1826
docker exec -i knowledge-graph-postgres psql -U admin -d knowledge_graph -qtA <<'SQL' > "$OUT_DIR/kg-raw.jsonl"
1927
LOAD 'age';
2028
SET search_path = ag_catalog, "$user", public;
@@ -70,7 +78,9 @@ def source_bucket(cid, _seen={}):
7078
_seen[key] = ATLASSIAN_CATEGORIES[idx]
7179
return _seen[key]
7280
73-
for line in open(src):
81+
with open(src) as f:
82+
raw_lines = f.readlines()
83+
for line in raw_lines:
7484
line = line.strip()
7585
if not line:
7686
continue

0 commit comments

Comments
 (0)