Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 14 additions & 17 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t

Turn an OmniGraph graph database into a **read-and-act dashboard you describe in one YAML file** — rendered identically in a terminal and a browser.

Normally you inspect a graph by writing queries and reading JSON, or by building a bespoke UI. A notebook is the layer between: a YAML file that *declares what slices of the graph to show and what actions to allow*, not code. Each cell is a typed lens (`Table`/`Path`/`Subgraph`/`ActionList`) fed by a structured query, or a control (`Select`/`Toggle`/`Button`) that filters state or mutates the graph. See `examples/company.notebook.yaml`: a status filter, a decisions table, a `Signal → Decision → Actor` path, an ego subgraph, and a clause list with inline Approve/Reject buttons — no UI code anywhere.
Normally you inspect a graph by writing queries and reading JSON, or by building a bespoke UI. A notebook is the layer between: a YAML file that *declares what slices of the graph to show and what actions to allow*, not code. Each cell is a typed lens (`Table`/`Path`/`Subgraph`/`ActionList`) fed by a structured query, or a control (`Select`/`Toggle`/`Button`) that filters state or mutates the graph. See `examples/company-server.notebook.yaml`: a status filter, a decisions table, a `Signal → Decision → Actor` path, an ego subgraph, and a clause list with inline Approve/Reject buttons — no UI code anywhere.

Two bets make it work:

- **Typed lenses, not a generic graph viewer** — you name the view you want; the system renders it.
- **Write once, render anywhere** — the same YAML drives the Ink terminal UI and the React web UI, against an in-memory fixture (dev) or a live omnigraph-server (prod). It's bidirectional: lenses read the graph, controls and actions write back to it.
- **Write once, render anywhere** — the same YAML drives the Ink terminal UI and the React web UI, against a live omnigraph-server (a local cluster in dev, a remote server in prod). It's bidirectional: lenses read the graph, controls and actions write back to it.

## Commands

Expand All @@ -27,8 +27,7 @@ pnpm --filter @modernrelay/notebook-<pkg> build # rebuild one packag
pnpm --filter @modernrelay/notebook-<pkg> test # vitest run for one package
pnpm --filter @modernrelay/notebook-<pkg> test -- <pattern> # single test file/name

pnpm tui examples/company.notebook.yaml # Ink TUI, fixture mode
pnpm tui examples/company-server.notebook.yaml # TUI, server mode — server URL + graph id
pnpm tui examples/company-server.notebook.yaml # Ink TUI, server mode — server URL + graph id
# come from the notebook (run server-demo.sh first)
pnpm --filter @modernrelay/notebook-web dev # Vite dev server at 127.0.0.1:5173
# add ?mode=server&server=/og (same-origin proxy)
Expand All @@ -42,15 +41,14 @@ The TUI consumes built `dist/` from sibling workspace packages — **always run

## Architecture

**One catalog, two renderers, one fixture-driven dev loop.** A notebook is YAML; each cell renders as a typed lens (`Table`/`Path`/`Subgraph`/`ActionList`) or a control (`Button`/`Toggle`/`Select`). Both the Ink TUI and the React Web app share the same catalog of component definitions and the same runtime; only the leaf component implementations and the host shell differ.
**One catalog, two renderers, one server-backed runtime.** A notebook is YAML; each cell renders as a typed lens (`Table`/`Path`/`Subgraph`/`ActionList`) or a control (`Button`/`Toggle`/`Select`). Both the Ink TUI and the React Web app share the same catalog of component definitions and the same runtime; only the leaf component implementations and the host shell differ.

### Package map

| Package | Role |
|---|---|
| `@modernrelay/notebook-core` | The engine — start here. One package, three internal modules: `spec` (Zod schemas + YAML parser, fixture-query DSL, mutation specs), `catalog` (component+action definitions shared by both renderers; `assembleLensSpec` / `assembleControlSpec` produce json-render specs), `runtime` (capability-aware execution, state mirror, dependency invalidation, action dispatch, mutation lifecycle, optimistic reconciliation). The `@json-render/core` analog. |
| `@modernrelay/notebook-fixture` | In-memory `FixtureSource` over JSON graphs; `/node` subpath holds the Node-only fs loader so it stays out of the browser bundle. |
| `@modernrelay/notebook-client` | `ServerSource` + `translateFixtureQuery` / `translateMutation` (fixture DSL → `.gq`) + a `Client` facade over the `@modernrelay/omnigraph` SDK (`/query` + `/mutate`, graph-scoped). |
| `@modernrelay/notebook-core` | The engine — start here. One package, three internal modules: `spec` (Zod schemas + YAML parser, query model — `ref`/`rawGq` — mutation specs), `catalog` (component+action definitions shared by both renderers; `assembleLensSpec` / `assembleControlSpec` produce json-render specs), `runtime` (capability-aware execution, state mirror, dependency invalidation, action dispatch, mutation lifecycle, optimistic reconciliation). The `@json-render/core` analog. |
| `@modernrelay/notebook-client` | **The only data source.** `ServerSource` + `translate` (structured DSL → `.gq`) + a `Client` facade over the `@modernrelay/omnigraph` SDK (`/query` + `/mutate`, graph-scoped). |
| `@modernrelay/notebook-tui` | Ink renderer + CLI entry (`bin/omnigraph-tui.js`); host shell for terminal. |
| `@modernrelay/notebook-web` | Vite + React + Tailwind renderer; host shell for browser. |
| `@modernrelay/notebook` (`packages/cli`) | The published front-door CLI. Bundles every `@modernrelay/notebook-*` lib (tsup, `noExternal`) and ships the built web SPA in `web-dist/`. Subcommands: `view` (browser — static server + `/og` BFF proxy with server-side token injection, reusing `web/src/config.ts`'s URL-param contract), `tui` (calls `@modernrelay/notebook-tui` `main`), `validate`/`render`/`catalog`/`schema` (agent-DX, JSON out; schema via Zod 4 `z.toJSONSchema`). The workspace root is the private `notebook-workspace`; `@modernrelay/notebook` is the CLI, not the root. |
Expand All @@ -62,22 +60,21 @@ The TUI consumes built `dist/` from sibling workspace packages — **always run
│ │
▼ ▼
Source.capabilities/read/mutate assembleLensSpec()
(Fixture | Server) → json-render Spec
(ServerSource) → json-render Spec
```

1. `@modernrelay/notebook-core`'s `spec` module parses+validates YAML against frozen v1 Zod schemas. Defines the `FixtureQuery` DSL (`nodes` / `path` / `ego`) and the `MutationSpec` discriminated union (currently only `set_field`).
1. `@modernrelay/notebook-core`'s `spec` module parses+validates YAML against frozen v1 Zod schemas. Defines the cell query model (`query.ref` → a server-owned catalog query, or `query.rawGq` raw `.gq` escape hatch) and the `MutationSpec` discriminated union (currently only `set_field`). The v1 schema is strict.
2. `@modernrelay/notebook-core`'s `createNotebookRuntime` validates notebook compatibility against `Source.capabilities()`, resolves `{ $state: "/ptr" }` expressions for data reads, invalidates only cells whose query dependencies changed, calls `Source.read()`, and hands results to `assembleLensSpec` (core's `catalog` module). Control cells skip reads and pass props through to `assembleControlSpec`. Per-cell errors are captured on `CellExecution.error`; runtime-level compatibility failures surface on `RuntimeSnapshot.error`.
3. core's `catalog` module exports `lensComponents` (Zod prop schemas + descriptions) and `lensActions` (`setState`, `mutate`). Author-time props are validated here; the renderer's `defineCatalog` consumes the same schemas.
4. The renderer (`packages/tui` or `packages/web`) calls `defineRegistry` against its UI library, supplying concrete Ink or React+Tailwind component implementations under the same component IDs (`Table`, `Path`, ...). The App subscribes to the runtime snapshot and passes each cell's `LensSpec` to `<Renderer />`.

### The `Source` interface and its two implementations
### The `Source` interface and its implementation

Defined in `@modernrelay/notebook-core` (its `runtime` module) as a capability-aware contract: `capabilities()`, `read(request, context)`, and `mutate(command, context)`. The notebook YAML is identical between modes where source capabilities overlap; unsupported features fail during runtime compatibility validation or with explicit source errors:
Defined in `@modernrelay/notebook-core` (its `runtime` module) as a capability-aware contract: `capabilities()`, `read(request, context)`, and `mutate(command, context)`. There is one implementation; unsupported features fail during runtime compatibility validation or with explicit source errors:

- **`FixtureSource`** (`@modernrelay/notebook-fixture`): runs the fixture-DSL query against an in-memory JSON graph; mutations update nodes in place per-process (no disk writeback).
- **`ServerSource`** (`@modernrelay/notebook-client`): translates fixture-DSL queries to `.gq` source via `translateFixtureQuery`/`translateMutation` and calls the SDK's `query`/`mutate` (omnigraph-server 0.7.0+ serves these under `/graphs/{graph}/…`). `ego` queries are decomposed into center/incident reads and merged client-side. Cells may still bypass translation by setting deprecated `query.source` raw `.gq`.
- **`ServerSource`** (`@modernrelay/notebook-client`): the only source. Invokes server-owned catalog queries by name via the SDK's `og.queries.invoke` (`query.ref`, the default path), or sends raw `.gq` ad-hoc via `og.query` (`query.rawGq` escape hatch). `mutate` compiles the interim `set_field` to `.gq`. omnigraph-server 0.7.0+ serves these under `/graphs/{graph}/…`.

Mode selection: `tui/src/index.tsx` and `web/src/App.tsx` pick a source from `notebook.fixture` (relative JSON path) vs `notebook.server` (URL), with CLI flags or URL flags (`?mode=server|fixture`, `?server=...`, `?notebook=...`) as overrides.
Connection: `cli/src/source.ts` and `tui/src/index.tsx` resolve via the shared Node-only `@modernrelay/notebook-client/node` operator-config resolver (`~/.omnigraph/config.yaml` + `credentials`: named servers, profiles, keyed-token chain). Flags (`--server NAME|URL`/`--graph`/`--token`/`--branch`/`--profile`) and the notebook's `server`/`graph` layer in. `web/src/config.ts` stays on URL params + the `view` proxy — the browser can't read operator files.

### State + mutations

Expand All @@ -95,10 +92,10 @@ A data cell may additionally declare inline `controls: [...]` — control descri

### TypeScript config

Strict mode + `noUncheckedIndexedAccess`. All packages extend `tsconfig.base.json` and emit `dist/` with declaration files; consumers import from `@modernrelay/notebook-<pkg>` (resolves to `dist/index.js`). The `@modernrelay/notebook-fixture/node` subpath splits Node-only fs loaders out of the browser bundle.
Strict mode + `noUncheckedIndexedAccess`. All packages extend `tsconfig.base.json` and emit `dist/` with declaration files; consumers import from `@modernrelay/notebook-<pkg>` (resolves to `dist/index.js`).

## Server-mode prerequisites

omnigraph-server 0.7.0+ is **cluster-only** (RFC-011): every read/write is served under `/graphs/{graph_id}/…`, so server-mode notebooks must carry a `graph:` id (overridable via `--graph`/`?graph=`/`$OMNIGRAPH_GRAPH_ID`). The SDK pins to a matching server line — `@modernrelay/omnigraph@^0.7.0` talks to a 0.7.x server only.
omnigraph-server 0.7.0+ is **cluster-only** (RFC-011): every read/write is served under `/graphs/{graph_id}/…`, so server-mode notebooks must carry a `graph:` id (overridable via `--graph`/`?graph=` or the operator-config `default_graph`). The SDK pins to a matching server line — `@modernrelay/omnigraph@^0.7.0` talks to a 0.7.x server only.

`scripts/server-demo.sh` needs an omnigraph **v0.7.0+** checkout on disk — the sibling `../omnigraph` by default, or set `OMNIGRAPH_REPO`. It `cargo build`s `omnigraph-cli` + `omnigraph-server` (release), then materializes a **local filesystem-backed cluster** under `.server-demo/cluster` (graph `company`, schema `examples/server/company.pg`, seed `examples/server/company.jsonl`) via `cluster import`/`apply` + `load`, and boots `omnigraph-server --cluster … --unauthenticated` on `:8080` (PID/log under `.server-demo/`, gitignored). No RustFS/S3 required. Re-running reuses the cluster (mutations persist); delete `.server-demo` to reset. The demo runs unauthenticated, so bearer tokens are ignored; the web app reaches it same-origin through the Vite `/og` proxy (the 0.7.0 server sets no CORS headers).
Loading
Loading