Skip to content
Night Owls.dev
Jump to a page

@nightowlsdev/graph-react

UI

The React workbench for the knowledge-graph plane: search, a force-directed canvas, entity pages, the bi-temporal timeline, and the review queue, with no CSS build step and no d3.

What it does

The display surfaces for `@nightowlsdev/graph`, built as three independently replaceable layers in the same shape as `SwarmComponents` and `StudioComponents`: a CLIENT (`GraphClient`, with `httpGraphClient({ baseUrl })` pointing at a mounted `createGraphHandlers()`) so you can swap the backend and keep the UI; HOOKS (`useGraphSearch`, `useGraphNode`, `useGraphNeighborhood`, `useGraphTimeline`, `useGraphEpisodes`, `useGraphNote`, `useGraphOntology`, `useGraphLint`) so you can keep the data layer and render your own UI; and a COMPONENT slot registry (`GraphComponentsProvider` + `mergeGraphComponents`) so you can keep the composition and replace any single piece. `<GraphWorkbench client={…}>` wires the whole journey together, search → canvas → entity detail, plus the history ribbon and the review queue. The built-ins are exported individually too: `<GraphExplorer>` (a deterministic force-directed canvas, layout in `useGraphLayout`), `<EntityPage>` (summary, properties, live facts, sources, compiled note), `<GraphTimeline>` (the bi-temporal ribbon of when facts became true and when we stopped believing them), `<GraphSearch>`, `<FactRow>`/`<FactList>`, and `<LintQueue>`. Rendering is dependency-free, inline SVG and inline styles reading the `--owl-*` custom properties that FR-041 made the one theming namespace, so it drops into any React app with no CSS build step, no d3, and no class-name collisions. Prototype status; peer-depends on `@nightowlsdev/graph` + React, zero `@mastra/*`.

Install

pnpm add @nightowlsdev/graph-react

Key exports

  • GraphWorkbench (GraphWorkbenchProps)
  • httpGraphClient (GraphClient seam)
  • GraphClientProvider / useGraphClient
  • useGraphSearch / useGraphNode / useGraphNeighborhood / useGraphTimeline / useGraphEpisodes / useGraphNote / useGraphOntology / useGraphLint
  • useGraphLayout / useDebounced
  • GraphComponentsProvider / mergeGraphComponents / defaultGraphComponents
  • GraphExplorer / EntityPage / GraphSearch / GraphTimeline / FactList / LintQueue

Usage

graph-react.tsx
import { GraphWorkbench, httpGraphClient } from "@nightowlsdev/graph-react";

// Points at a mounted createGraphHandlers() from @nightowlsdev/graph.
const client = httpGraphClient({ baseUrl: "/api/graph" });

export default function KnowledgePage() {
  return <GraphWorkbench client={client} overviewQuery="team" />;
}

// Or keep the data layer and render your own UI:
//   const { data, loading, error, reload } = useGraphSearch({ query: "who owns billing" });
// Or keep the composition and swap one piece:
//   <GraphWorkbench client={client} components={{ FactRow: MyFactRow }} />

What it provides

graph-react is the display layer for the @nightowlsdev/graph knowledge plane: search, a force-directed canvas, entity pages, the bi-temporal timeline, and the lint/review queue. It follows the established Night Owls three-layer shape — a swappable CLIENT (GraphClient), headless HOOKS (the useGraph* family), and a COMPONENT slot registry — so you can replace the backend, the render, or any single piece independently. Rendering is dependency-free (inline SVG + inline styles reading the --owl-* custom properties), so it drops into any React app with no CSS build step, no d3, and no class-name collisions.

When to use it

  • You have a @nightowlsdev/graph plane and want a ready UI — search, a graph canvas, entity detail, the when-true/when-retracted timeline, and the review queue — from one component.
  • You want a headless graph data layer (the useGraph* hooks, with per-keystroke abort/supersede) to render your own graph UI.
  • You want to embed the workbench inside the unified operator console (react's <AIStudio> mounts it as the knowledge section).

When not to

  • You don't run the @nightowlsdev/graph plane — there is nothing for this to render; it is graph-only.
  • You need a large-scale, animated, live-physics graph (thousands of nodes) — the default layout is deterministic and synchronous, not a d3 simulation; bring your own canvas via the GraphExplorer slot.
  • You want the chat/agent UI — that is @nightowlsdev/react.

Alternatives

  • The useGraph* hooks + your own componentsYou want the data layer (client + abort/supersede fetching) but a bespoke render — or a real graph library (d3, cytoscape) plugged into the GraphExplorer slot.
  • @nightowlsdev/graph read tools granted to an agentThe consumer is an AGENT asking the graph (search/neighborhood/timeline as fenced tools), not a human browsing it.
  • react's <AIStudio> graph sectionYou want the workbench inside the unified operator console rather than mounted standalone.

Strengths

  • Zero-dependency rendering — inline SVG and inline --owl-* styles — so it drops into any React app with no CSS build, no d3, and no class-name collisions.
  • Three independently swappable layers: point the client at another backend, keep the data layer and render your own UI, or keep the composition and replace one slot — each without touching the others.
  • The force-directed layout is deterministic and synchronous, so the same graph lays out the same way and does not reshuffle on remount — navigable, not merely decorative.
  • Honest, safe defaults: requests abort-and-supersede per keystroke, provenance is never behind a toggle, and LLM-authored notes render through React text nodes (never dangerouslySetInnerHTML), so a note can't be an injection vector.

Limits & trade-offs

  • Prototype status — the API may still move between minor releases.
  • The default layout is O(n²) work done synchronously during render; iteration count is scaled down as the graph grows, so a large graph reads as a shape rather than precise positions — real interactivity at scale needs your own canvas via the slot.
  • Graph-only: it peer-depends on @nightowlsdev/graph and needs a mounted createGraphHandlers() (or a hand-written GraphClient) behind it, or there is nothing to show.
  • Theming is via the --owl-* CSS variables only — inline styles mean there are no stable class hooks to target for overrides beyond the slot registry.

How it works

Everything talks to a GraphClient — httpGraphClient({ baseUrl }) points at a mounted createGraphHandlers(), or you implement the small interface (search/findNodes/node/neighborhood/timeline/episodes/note/ontology/lint) to hit any backend or a test fixture. The useGraph* hooks wrap a single abort-and-supersede async primitive over that client, so a slow early response can't overwrite a fast later one (the search-per-keystroke bug). <GraphWorkbench> composes the journey — search → the force-directed canvas (GraphExplorer, positions from useGraphLayout) → entity detail (EntityPage) — plus the bi-temporal timeline and the lint/review queue, every piece resolved through GraphComponentsProvider so a host can swap any slot and keep the composition.

Examples

Drop-in workbench

httpGraphClient points at your mounted graph handlers; overviewQuery seeds the canvas before anything is selected.

graph-react-example-1.tsx
import { GraphWorkbench, httpGraphClient } from "@nightowlsdev/graph-react";

const client = httpGraphClient({ baseUrl: "/api/graph" });

export default function KnowledgePage() {
  return <GraphWorkbench client={client} overviewQuery="team" />;
}

Headless — your own results UI

The hooks need a <GraphClientProvider>. useGraphSearch returns { data, loading, error, reload } and aborts superseded requests.

graph-react-example-2.tsx
import { GraphClientProvider, useGraphSearch, httpGraphClient } from "@nightowlsdev/graph-react";

const client = httpGraphClient({ baseUrl: "/api/graph" });

function Results({ query }: { query: string }) {
  const { data, loading } = useGraphSearch({ query, topK: 10 });
  if (loading) return <p>Searching…</p>;
  return <ul>{(data ?? []).map((f) => <li key={f.edge.id}>{f.edge.type}</li>)}</ul>;
}

export function Page() {
  return <GraphClientProvider value={client}><Results query="who owns billing" /></GraphClientProvider>;
}

Keep the composition, swap one slot

Unset slots keep the built-in; each slot's props type is exported, so an override is type-safe.

graph-react-example-3.tsx
import { GraphWorkbench, httpGraphClient } from "@nightowlsdev/graph-react";
import { MyFactCard } from "./MyFactCard"; // built from the exported FactRowProps

const client = httpGraphClient({ baseUrl: "/api/graph" });

export function Workbench() {
  return <GraphWorkbench client={client} components={{ FactRow: MyFactCard }} />;
}

Doing the parts it doesn't support

  • Point the UI at a different backendPass your own GraphClient. httpGraphClient({ baseUrl, headers, fetchImpl }) covers a mounted createGraphHandlers() with auth headers; for a wholly different API (or a test fixture), implement the GraphClient interface directly — every hook and component flows through it.
  • A large / animated interactive graphThe built-in GraphExplorer is a deterministic synchronous layout, not a physics sim. Override the GraphExplorer slot with your own canvas (d3/cytoscape/WebGL) — keep the useGraph* hooks for data, keep the rest of the composition.
  • Render a single surface, not the whole workbenchThe built-ins are exported individually — <EntityPage>, <GraphTimeline>, <GraphSearch>, <FactList>, <LintQueue>. They work outside <GraphWorkbench> with just a <GraphClientProvider> around them.

Related

  • graphThe knowledge-graph plane this renders; httpGraphClient targets its createGraphHandlers().
  • reactIts <AIStudio> operator console mounts this workbench as the knowledge section (optional peer).
  • themeThe --owl-* custom properties the inline SVG/styles read for theming.
  • knowledgeThe sibling retrieval plane — vector search you grant to an agent rather than browse.