A plot
One kind, one act. The city, the district, the derived form and the routed face all exist before a line of UI is written.
The garden after chapter 1, loading…
defineNode("plot")defineMutation("add-plot") with creates: ["plot"]
One example, the community garden, declared a little at a time. Every
chapter below is a real declaration that passes its own check, opened
in a real browser by scripts/progression.mjs on every push.
Nothing here is a mock-up; a chapter that stops holding its claim fails
the build.
Chapter one is what graview create writes. The loop from
there is the whole method: declare, check, look, declare more.
One kind, one act. The city, the district, the derived form and the routed face all exist before a line of UI is written.
The garden after chapter 1, loading…
defineNode("plot")defineMutation("add-plot") with creates: ["plot"]A second kind and one edge. The line is drawn from the declaration, captioned in its own words, and made and unmade by the act that names it.
The garden after chapter 2, loading…
defineNode("gardener")edges: { "tended-by": { to: ["gardener"], description, inverse } } on plotdefineMutation("tend") with connects and seversA rule is data on the map, and it names its own repair. The moment it lands, an untended plot is a problem with a one-click fix, not a bug report.
The garden after chapter 3, loading…
defineNode("rule") with requiresInvariantdefineInvariant("every-plot-tended") with repairs: ["tend"]defineMutation("adopt-rule")A harvested planting leaves the counts but never the graph. The district says +1 past, and last season is one stop away rather than deleted — and the season calendar draws each planting across the days it was actually in the ground.
The garden after chapter 4, loading…
defineNode("planting") with lifecycle: { field: "status", retired: ["harvested", "failed"] }an appendOnly edge "grows-in"defineMutation("sow"), defineMutation("harvest") with writes: ["status", "harvested"]An agent gets the same acts a person does, through one declared seam. Its turn is attributed in the log, watchable from altitude, and undoable out of order.
The garden after chapter 5, loading…
intelligence: [{ name: "starter", kind: "graph", may: [...] }] on defineAppan AgentSeat in the rail, gated on add-gardenerPersistence is the operation log. One adapter in main.tsx and an edit survives a reload, still attributed, still undoable, with the way back to empty on the rail.
The garden after chapter 6, loading…
openStore({ app, adapter: createBrowserAdapter(), fresh: browserStartsFresh() }) in main.tsxremembers on the ShellOne policy, declared once. The store refuses, the actions strip narrows, and an agent's seat narrows with it, so a gardener never sees a button that would fail.
The garden after chapter 7, loading…
policy: { roles, grants } on defineAppa principal with roles on the storeOne accent, a mark and a typeface, and both schemes are derived and measured. Every text pair is checked against AA before the brand is allowed to ship.
The garden after chapter 8, loading…
brand: { name, logo, typography, accents, schemes: brandFromAccent(...) } on defineAppThe same declaration is also an ordinary web application, routed, at phone width: it lands on a gallery — every kind drawn as a picture until the garden names a lens, then the lens by its name — with a list, a record and a form per kind, the problems, a map of how the kinds fit together, and the seat on every route. Any page can be replaced with one the app writes, in its own words, over the same derivations.
The garden after chapter 9, loading…
PagesApp at /pages, from the same storeviews in the page context: every lens a page at /places, and the assistant with itcreatePageRegistry(schema).register("plot", "record", PlotPage) — the plot's page in the garden's wordsA lens binds roles, not field names. The coverage grid was written for requirements and tests; pointed at gardeners and plots it says, in a picture, which plot nobody tends.
The garden after chapter 10, loading…
lenses: [{ name: "coverage", binds: "entities", bindings: { rows, columns, link } }] on defineAppthe lens's View registered for the gardeners, cardinality manyA second lens, written for a seating plan, drawn over the plots where they lie in the garden. Things sit where the domain says they sit, and an empty bed is a picture of something to do, not a missing row.
The garden after chapter 11, loading…
x and y on plot, 0..1 across the gardena second lens declaration binding slots, x, y and fill to plot and grows-infillFrom: "occupant" — the lens reads the edge from the planting's endDeployment is one declaration plus one adapter. A version and a migration on the app carry a garden stored last season forward, as a logged, attributed, undoable operation.
The garden after chapter 12, loading…
version: 2 and migrations: [{ from: 1, to: 2, title, apply }] on defineAppopenStore against a stored version-1 gardenEvery surface of the pages face replaced with the garden's own design — an almanac, not an admin panel — and a lens the garden drew of itself in the scene. The same store, acts, rules and permissions underneath; only what a reader sees is the app's.
The garden after chapter 13, loading…
createPageRegistry(schema).surface("shell" | "home" | "problems", …) and .register(kind, "list" | "record", …) for every kindregister("plot", { cardinality: "many", fidelity: "full" }, GardenMapView, { title: "The garden map" }) — a lens of the garden's ownUsers, invitations and roles are nodes and acts like everything else: drawn only for the seat that keeps the installation, refused for everyone else by the same policy, and a person's own record is theirs to edit. A lens over the policy shows what each role reaches.
The garden after chapter 14, loading…
declareInstallation({ roles, admin }) — two kinds, six acts, a module drawn only for those who administer it, and a self grant for a profilecreateSchema([...yours, ...installation.kinds]), mutations: [...yours, ...installation.mutations], policy: installation.withPolicy(policy)reachLens registered over the people as "Who may do what"The declaration itself is a graph: every kind, field, edge, act, rule, role and grant of chapter fourteen is a node here, edited with ordinary acts, checked before it is applied, migrated when a stored garden needs it, and written back as the files graview create writes.
The garden after chapter 15, loading…
createStudio(app) — the declaration as a store of the meta-schema; studio.check(), studio.apply(), studio.files()add-kind, add-field, add-edge, add-act, add-rule, name-repair, add-role, grant — acts on the declaration, undone like any othercreateStudioLens(app) registered over the kinds as "What the checker says"A bed is turned through four families and comes back to the first four years later. The same calendar lens that drew the season draws the rotation, a month per cell over as many years as the garden says it turns through — and how far out that is is the garden's word, not the framework's.
The garden after chapter 16, loading…
defineNode("rotation") with an appendOnly edge "turns-over" to the plot it turnsan appendOnly edge "holds" from a rotation to what went in during it — so a turn and its plantings are connected, not strangers on the same plotdefineMutation("rotate") — which family a plot grows, and until when; it claims what is already in the ground, and sow puts new plantings under the turnthe same calendar binding at range: "years" with horizon: { years: 4, title: "The rotation" }export const task = defineNode("task", { description: "One thing to do.", fields: z.object({ label: z.string().min(1), done: z.boolean(), due: isoDate.optional(), day: dayOfWeek.optional(), plannedAt: minutes.optional(), }), plural: "Tasks", // A lens asks for roles, not field names. fieldRoles: { start: "plannedAt", day: "day" }, edges: { "waits-for": { to: ["task"], description: "what has to happen first", inverse: "what is waiting on this", }, }, });
An edge pointing at a kind the schema never declared fails
tsc. Not at runtime. At build.
Everything below comes out of that block. None of it is written twice.
description and inverse.
Eleven packages, layered. Take the whole stack or take the parts that
earn their place. layout is pure geometry with no React in
it; core runs headless in a worker or a test; embed
puts an app on somebody else's page.
| Package | What is in it |
|---|---|
| graview | The command line a person installs: create, check, docs, describe, lens, figure, serve, skills. It dispatches; every subcommand lives in the package whose concern it is. |
| @graview/core | Schema, graph, invariants, mutations, the operation log, persistence and sync adapters, the installation as a module, the brand and its kit, and the graview check CLI. |
| @graview/layout | Where everything sits. Headless and pure. No DOM, no framework. |
| @graview/tools | Derived affordances, and one tool surface an agent and an interface both use. |
| @graview/render | Capture, depth compositing, pointer routing. Only this package touches the GPU, and only through its /gpu entry. |
| @graview/react | The binding, and the routes a line may take. Deliberately thin. |
| @graview/primitives | View primitives, three lenses, the named places, the way into the installation and the lens over its policy, and the workbench: inspector, standing, activity, undo, trail, the shell. |
| @graview/pages | The routed face: home, lists, records, forms and problems derived from the declaration, every surface replaceable. |
| @graview/ship | Persistence in the browser, versions and migrations, and the deploy shape. |
| @graview/embed | mount(el, { app, seed, stop }): an app inside any element, themed to itself, with faces, places and seats on a strip. |
| @graview/studio | The declaration itself as a graph: edited with ordinary acts, checked before it is applied, migrated, and written back as the files graview create writes. |
| @graview/skills | Authoring moves for Claude Code and Codex. Each one ends in a check rather than a claim. |
| create-graview | The door: npm create graview runs graview create, which writes chapter one for your domain. |
The repository also carries example apps. They are fixtures for the harnesses and are not published.
A view further away is not drawn smaller. It is asked for less. That split carries real weight: capture runs about 0.016 ms per node up to roughly 128 live captures a frame, then falls off a cliff.
Live DOM, captured every frame it moves. Whatever you are working on, at whatever size it wants.
Whatever the focus touches, captured on change into a cached texture. Still real elements. Still clickable and focusable.
Every declared kind, always, in the same place. Painted in the shader and never captured, so a graph that is not changing costs nothing per frame.
A lens binds roles, not field names. Point the timeline at
plannedAt in one app and at leaveAt in
another; it never learns what either one means. That is the line
between a lens and a component you copied. The garden proves it
twice: the coverage grid, written for requirements and tests, shows
which plot nobody tends; the
board, written for a seating plan, shows
what grows where — and the
empty bed.
Lenses are yours to make. A lens is an ordinary React component that takes the nodes, the bindings and the selection the framework hands it, and draws whatever your domain needs to see — a map, a ledger, a floor plan, a Gantt of the week. Register it over a kind with a title and it is a named place in the bar; every mark it draws is a real pick target, so selection, ties and actions land on the things themselves. Chapter thirteen is the garden drawing itself: beds as beds, a sprout per planting, and the same drawing on the home page of a pages face that replaced every surface. The core ships with three starters — the board, the coverage grid and the timeline — as the shapes most domains reach for first, and every one of them is one file you can read and copy.
Things sit where the domain says they sit, not where a layout engine decided. The empty slot is the whole point.
An empty row is a requirement nobody answered. An empty column is work nobody asked for. Reading a document never finds the paragraph that was never written.
Intervals against columns. The minimum window is the app's call, because an eight-hour default is right for a school day and wrong for a Tuesday evening.
Everything the scene draws that is not a view is declared on the brand: the lines, their route and their stroke, the ground's grid and lattice, the kind tags, the captions. A route is a named strategy — curve, straight, right-angled — and the next one is one case in one file. Colour and visibility are per edge kind. Try it on the garden after chapter eight: the declaration you are writing is beside it, and the checker's verdict on any colour you pick is under that.
The garden after chapter 8, loading…
Who is here is in the graph too. Users, invitations and the roles they hold are nodes and acts like everything else: judged by the policy, offered in the strip and the pages, written to the log. The seat that keeps the installation sees "Show the installation" in the bar and the people rise as districts beside the domain; nobody else ever sees them. A person's record is their profile and theirs alone to edit. Chapter fourteen is the garden with its gardeners at the keyboard, and a lens over the policy that says what each role reaches.
The declaration is a graph too. In the studio every
kind, field, edge, act, rule, role and grant is a node, and a change
to the declaration is an act with an author, an intent and an
inverse. The checker judges what it would become before it is
applied; a stored graph gets the migration it needs; the result is
written back as the files graview create writes. An
agent seat proposes by acting, and a person keeps or takes back its
turn. Chapter fifteen is the
garden's own declaration, open in the studio.
Invariants run on every change. The bar states the result, and zero is an answer too. Open a problem and it selects what it names, marks those nodes wherever they happen to be drawn, and offers the mutations that would fix it. You never go hunting.
Suggestions fall out of the same machinery. Select three tasks where two share a day and align the third appears, because the schema already said which mutation writes that field. Nobody wrote a rule about days.
The graph is a fold over an append-only log. Every operation carries its author, its batch, its intent, its inverse, and the nodes it read.
That last one turns "drop the agent's turn, keep my edits" into a dependency check rather than a stack pop. When it will not work, the check names the operation in the way and offers to bring it along.
The tool runtime emits every call as it happens, reads included, so a turn reads back as looked at the whole graph, looked at the Tuesday task, moved it. Every node it names is one click away.