Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
26a4e2a
feat(graph-source-ux): phases 1-3 — load lifecycle, picker modal, dir…
bitmosh Jul 11, 2026
f92bc08
test(adapters): replace synthetic fixtures; expand edge-case coverage
bitmosh Jul 11, 2026
4751e40
feat(graph-source-ux): phases 4+5 — library tile, thumbnails
bitmosh Jul 11, 2026
5a6e167
feat(graph-source-ux): phase 6 — dev mode gating (SA-026, SA-027)
bitmosh Jul 11, 2026
5ac2794
feat(graph-source-ux): SA-028 — user-editable source labels
bitmosh Jul 11, 2026
2acbf62
feat(graph-source-ux): SA-003b + file picker — extension detect, nati…
bitmosh Jul 11, 2026
8c5781c
fix(tests): refresh stale assertions, un-quarantine camera reload test
bitmosh Jul 11, 2026
b341393
feat(graph-sources): add commitSource verb and a draft config layer
bitmosh Jul 11, 2026
67ff407
fix(theme): promote shell tokens to documentElement so portals inheri…
bitmosh Jul 12, 2026
af83c7c
fix(graph-sources): single owner for lifecycle side effects; stop ren…
bitmosh Jul 12, 2026
6781b92
docs(roadmap): add the graph-source UX review
bitmosh Jul 12, 2026
8b88916
refactor(graph-sources): hoist the load lifecycle to a single owner
bitmosh Jul 12, 2026
e39ba04
chore(deps): pin @tauri-apps/api to ~2.10 to match the Rust crate
bitmosh Jul 12, 2026
7c8484d
fix(theme): route hardcoded colors through tokens; rename theme presets
bitmosh Jul 12, 2026
5daee13
fix(app): show the demo graph when there is no Tauri bridge
bitmosh Jul 12, 2026
cff9b85
fix(physics): give sibling directories an angular budget
bitmosh Jul 12, 2026
782bd2a
docs(roadmap): layout & physics roadmap
bitmosh Jul 12, 2026
f9e5629
fix(graph): measure the seed against the right scale, and budget the …
bitmosh Jul 12, 2026
f74c850
docs(roadmap): record the node scale and the global angular budget
bitmosh Jul 12, 2026
a939c6a
feat(physics): derive the layout from the content instead of tuning it
bitmosh Jul 12, 2026
962999d
docs(ledger): append-only ledgers, and guards that keep them honest
bitmosh Jul 12, 2026
3ae932c
chore(graph): delete the dead renderer scaffolding
bitmosh Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/CURRENT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Evidence: `src-tauri/src/fs.rs`, `.github/workflows/ci.yml`, and `tests/e2e/`.

### Renderer abstraction and 3D data

- `graphRendererInterface.ts` is an extension seam, not a second renderer implementation.
- There is no renderer seam. The `graphRendererInterface.ts` stub was deleted (zero importers, too thin to implement against); the real one is designed in `docs/ledger/RENDERER_MIGRATION.md` (RM-005). Today the de-facto renderer API is the `window.__lwSigma` global.
- GWells seeders can write `z`, but the active force integration and Sigma renderer use two-dimensional positions.
- Three.js, React Three Fiber, and Drei are dependencies without runtime imports in `src/`.

Expand Down
297 changes: 297 additions & 0 deletions docs/canonical/GRAPH_DISPLAY_MAP.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/canonical/GRAPH_SIGMA_AND_RENDERING.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Theme runtime tokens are resolved into graph visual tokens before policy writes

## Future renderer boundary

`src/graph/rendering/graphRendererInterface.ts` describes a small mount/camera/refresh contract, but integrating a second renderer requires real adapter work:
There is **no renderer seam today.** A `graphRendererInterface.ts` stub once existed; it had zero importers and was too thin to be the real boundary (no hit-testing, no viewport projection, no program registration), so it was deleted rather than implemented against. The real seam is designed in `docs/ledger/RENDERER_MIGRATION.md` (RM-005). Integrating a second renderer requires real adapter work:

- Express graph visual policy without relying on Sigma-specific attributes.
- Map selection, camera, labels, materials, and theme tokens.
Expand All @@ -126,5 +126,5 @@ Three.js, React Three Fiber, and Drei are installed but unused by runtime source
- Programs: `src/graph/nodePrograms/`, `src/graph/edgePrograms/`
- Policies: `src/graph/visual/`
- Overlays/camera: `src/graph/overlay/`
- Renderer type seam: `src/graph/rendering/graphRendererInterface.ts`
- Renderer seam: none yet — see `docs/ledger/RENDERER_MIGRATION.md` (RM-005). The de-facto API is the `window.__lwSigma` global (GD-002).
- Physics: `src/physics/gwells/`
6 changes: 6 additions & 0 deletions docs/canonical/GWELLS_PHYSICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ All four are registry additions — the engine reads the registries; you don't t
## §5 — How it's designed to grow

- **Composition scales by registration.** N well types × their interactions compose additively in the force loop; a new dialect is a new bundle, not new engine code. The cost of growth is registry entries and per-frame pair cost, not engine complexity.
- **Node `size` is a RADIUS IN GRAPH UNITS, not pixels.** Sigma runs with `itemSizesReference: "positions"`, so `size`/`baseSize` are in the same units as `x`/`y` and are directly comparable to the seeders' spacing constants. Read them that way or the layout silently breaks: `computeNodeSize` was once inflated to a 48–360 range while `directoryOffset` stayed at 220, so the median node's *radius* equalled the entire distance to its parent and every node overlapped its neighbours at every zoom level — while the seed positions were provably correct. **A node radius is only meaningful relative to the distance to the next node.** If you change one, change the other.

- **Geometry reads `baseSize`; only rendering reads `size`.** `size` is `baseSize × settings.nodeSize`, and `graphStylePolicy` further rewrites it on hover and selection. Anything that computes *positions* from it — file orbits, repulsion, spacing — coupled the layout to what happened to be selected, and made the node-size slider silently reshape the graph. `baseSize` is the structural radius and the only one layout may touch.

- **3D is seeded already.** Seed functions store a `z` attribute (parallel-spines arranges spines in a ring around a central axis in 3D) for forward-compatibility with a future 3D camera — the data is there ahead of the renderer. This is the seam the eventual three.js/react-three-fiber path consumes.

**Caveat, learned the hard way (L-001):** seeding into `z` is free, but *arranging* into it is not. parallel-spines used to fan directory branches and orbit files in the **x/z plane**. Sigma renders only `(x, y)`, so that entire arrangement was projected away: branches that were correctly spread in 3D rendered on top of one another, and file orbits — horizontal rings viewed exactly edge-on — collapsed to a line segment in which every ±θ pair was coincident. Both now fan in the spine's own **vertical plane**, spanned by the spine's outward radial direction and ŷ, so the structure varies in `x` *and* `y` and survives the projection, while `z` still carries the azimuth for the future 3D camera. The rule: **any structure that must be visible today has to live in the plane the 2D camera actually renders.** `z` is for data that is waiting, not for structure that is load-bearing now.
- **Live tuning + pinning** are first-class via the controller (`applyConfigOverride`, `applyPins`), enabling interactive layout authoring without restarts — the basis for a future dialect-tuning UI.
- **Decoration hook** is the seam for audio-reactive and other per-frame visual modulation (deferred features) without entangling them with physics.
- **Performance note:** `stepPhysics` is benchmarked via `npm run physics:gwells:bench`, which records `benchmarks/gwells-latest.json` and coarse `GWStepTimings` buckets. `npm run physics:gwells:bench -- --update-baseline` intentionally refreshes the committed `benchmarks/gwells-baseline.json`; normal runs leave the baseline unchanged. The current fixture matrix covers filesystem-small/medium/large, current-like-400, hierarchy-1000, hierarchy-2000, stress-5000, wide-roots-30, mixed-graph, generic-no-spine, and disconnected-orphan-heavy.
Expand Down
108 changes: 108 additions & 0 deletions docs/canonical/LAYOUT_PIPELINE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# The Layout Pipeline — the standard order of operations

This is the template. Every layout/seed change follows these five stages, in this order. It exists because the previous approach — independently tuned constants, adjusted whenever the picture looked wrong — broke every time the content changed, and each fix broke something else.

The distinction that matters:

- **Preservative layout** hardcodes the numbers that produced a picture we liked. It is correct for exactly one input and silently wrong for every other. `directoryOffset: 220` is preservative: it was fine until node radii changed, then fine again until a directory was deleted.
- **Adaptive layout** derives the numbers from the content. It has no opinion about how far apart things should be; it computes how far apart they *must* be, and grows when the content grows.

**We build adaptive layouts.** A constant that encodes a distance is a bug in waiting.

---

## The five stages

Each stage is a **pure function**. No stage reads the graph's mutable render state (`size`, `color`) — geometry reads `baseSize` only (see `GRAPH_DISPLAY_MAP.md`). No stage mutates anything except the last.

```
1. DERIVE content → intrinsic quantities (pure, no layout knowledge)
2. MEASURE tree + (1) → footprints, bottom-up (pure)
3. ALLOCATE footprints → disjoint regions, top-down (pure)
4. PLACE regions → coordinates (pure)
5. VERIFY coordinates → invariants hold (assertion, in a test)
```

### 1 · DERIVE — content to intrinsic quantities

Everything a node knows about itself, independent of where it will go.

- `radius(v)` from `rawSize` — the only place a node's size is decided.

**Constants are allowed here and nowhere else.** This is the single tuning surface: change `NODE_RADIUS_MIN`/`MAX` and every downstream distance re-derives automatically. That property is the whole point.

### 2 · MEASURE — bottom-up footprints

Compute, for every subtree, **how much room it actually needs** — never how much room we think it should have.

- `discRadius(v)` — the radius of the disc containing `v` *and everything that orbits it* (its files). Derived from the file radii, not from a constant:
- files must clear the parent: `orbit ≥ radius(v) + maxFileRadius + PAD`
- files must clear *each other* around the orbit: `2π·orbit ≥ Σ 2·fileRadius`, i.e. `orbit ≥ Σ fileRadius / π`
- take the max of both. A directory with 40 files gets a bigger disc than one with 2 — automatically.
- `width(v)` — the tangential width the subtree needs:
```
width(v) = max( 2·discRadius(v) + PAD , Σ width(child) )
```
A subtree is at least as wide as its own disc, and at least as wide as its children laid side by side.

This is the stage that makes the layout adaptive. **Growth propagates upward automatically:** add files to a leaf directory and its disc grows, so its width grows, so its ancestors' widths grow, so the rings and sectors that contain it grow. Nothing is re-tuned.

### 3 · ALLOCATE — top-down disjoint regions

Turn measured need into **disjoint** angular sectors and ring radii.

- **Ring radius** — the adaptive replacement for `directoryOffset`:
```
r(d) = max(
r(d-1) + maxDisc(d-1) + maxDisc(d) + PAD, // radial clearance between rings
Σ width(v) for v at depth d / 2π // enough circumference to hold them all
)
```
The second term is the one that makes the graph *spread out as it grows*. More content at a depth ⇒ a bigger ring. No constant.

- **Angular sector** — each subtree's share of its parent's sector, weighted by measured `width`, tiled exactly (no floor — a floor over-allocates, and over-allocation across a full circle is how sectors start overlapping again).
```
θ(v) = width(v) / r(depth(v))
```

**The invariant this buys, by construction:**

Children fit inside their parent's sector, always. Since `Σ width(child) ≤ width(parent)` (stage 2) and `r(d) ≥ r(d-1)` (stage 3):

```
Σ θ(child) = Σ width(child) / r(d) ≤ width(parent) / r(d) ≤ width(parent) / r(d-1) = θ(parent) ∎
```

And a node's disc fits inside its own sector, because the arc it owns is `θ(v)·r(d) = width(v) ≥ 2·discRadius(v)`.

**Therefore files cannot invade a sibling subtree** — the disc that contains them is contained in the sector. That was L-020, and it is now impossible rather than merely unlikely.

### 4 · PLACE — regions to coordinates

Purely mechanical: put each node at the centre of its allocated sector, on its ring; orbit its files inside its own disc (phyllotaxis for even spread). No decisions left to make.

### 5 · VERIFY — invariants, not thresholds

**Assert relations, never magic numbers.** A test that says "no pair closer than 8 units" is preservative: it encodes a snapshot and breaks when content changes, teaching everyone to nudge the threshold. The correct invariant is *relational and scale-free*:

```
for every pair (a, b): distance(a, b) ≥ radius(a) + radius(b)
```

This is content-independent. It holds for 40 nodes and 40,000. It cannot be satisfied by fiddling a constant — only by a layout that is actually correct. **When you can't state the invariant without a magic number, the design is wrong, not the number.**

---

## Idempotence

- **Deterministic.** Same input ⇒ same output, bit for bit. No `Math.random()` anywhere in the layout path. Ordering comes from sorted ids. (Symmetry-breaking for coincident nodes uses a hash of the node ids, never randomness — see `GWELLS_PHYSICS.md` L-002.)
- **Re-runnable.** Seeding twice produces the same positions. Stages 1–4 are pure functions of the graph, not of the previous layout.
- **Stable under change.** Adding content changes the layout *proportionally* — it does not require re-tuning constants elsewhere. This is the property `directoryOffset: 220` did not have, and it is why deleting one directory could break a spacing assertion three subtrees away.

## Rules

1. **A constant that encodes a distance belongs in stage 1 or nowhere.** If you find yourself adding `const SOMETHING_OFFSET = 220`, you are writing preservative layout. Derive it.
2. **Never tune a constant to make a test pass.** If the invariant fails, the allocation is wrong. Fix stage 2 or 3.
3. **Never relax the invariant in stage 5.** It is relational; there is nothing to relax without making it meaningless.
4. **Geometry reads `baseSize`, never `size`.** `size` is presentation — it is multiplied by a user setting and rewritten on hover. A layout that reads it changes shape when you mouse over a node.
5. **Bottom-up before top-down.** You cannot allocate room before you know how much room is needed. Every attempt to place first and fix up later reintroduces exactly the class of bug this document exists to prevent.
2 changes: 0 additions & 2 deletions docs/canonical/REGISTRY_AND_LINK_NETWORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ Static list known at build time → **Tier 1**. Runtime-mutable with UI that mus
| `lensRegistry` | Tier 2 | lens | Dev extension point |
| `physicsDialectRegistry` | Tier 2 | graph/physics | Dev extension point |
| `animationPrimitiveRegistry` | Tier 2 | motion | |
| `edgeStyleRegistry` | Tier 2 | graph/edges | |
| `fontAxisRegistry` | Tier 2 | themes | |
| `typographyRegistry` | Tier 2 | themes | |
| `tileSectionRegistry` | Tier 2– (register, no subscribe) | control-plane/panels | Tile catalog |
Expand All @@ -115,7 +114,6 @@ Static list known at build time → **Tier 1**. Runtime-mutable with UI that mus
| `bookmarkRegistry` | Legacy (factory) | graph/overlay | Migration candidate |
| `assetRegistry` | Legacy (class) | themes | Empty asset bank |
| `qa-registry`, `advisory-registry` | Data registry | control-plane/qa | Large data stores, not patterns to imitate |
| `feature-registry` | Stub | control-plane/features | Currently empty |

`seedFunctions` is **no longer a registry** — it was `seedFunctionRegistry.ts`, now plain module exports in `physics/gwells/seedFunctions.ts`. Treat seed functions as a module, not a registry.

Expand Down
71 changes: 0 additions & 71 deletions docs/graph/contracts/EDGE_STYLE_REGISTRY_CONTRACT.md

This file was deleted.

86 changes: 0 additions & 86 deletions docs/graph/contracts/GRAPH_RENDERER_INTERFACE_CONTRACT.md

This file was deleted.

Loading