The garden, grown

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.

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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • defineNode("plot")
  • defineMutation("add-plot") with creates: ["plot"]

Gardeners, and who tends what

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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • defineNode("gardener")
  • edges: { "tended-by": { to: ["gardener"], description, inverse } } on plot
  • defineMutation("tend") with connects and severs

The garden's agreement

A 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…

Standing says “1 problem”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • defineNode("rule") with requiresInvariant
  • defineInvariant("every-plot-tended") with repairs: ["tend"]
  • defineMutation("adopt-rule")

Plantings, and the horizon

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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • defineNode("planting") with lifecycle: { field: "status", retired: ["harvested", "failed"] }
  • an appendOnly edge "grows-in"
  • defineMutation("sow"), defineMutation("harvest") with writes: ["status", "harvested"]

A seat for an agent

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…

Standing says “1 problem”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • intelligence: [{ name: "starter", kind: "graph", may: [...] }] on defineApp
  • an AgentSeat in the rail, gated on add-gardener

The garden remembers

Persistence 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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • openStore({ app, adapter: createBrowserAdapter(), fresh: browserStartsFresh() }) in main.tsx
  • remembers on the Shell

Who may do what

One 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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • policy: { roles, grants } on defineApp
  • a principal with roles on the store

The garden's own name

One 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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • brand: { name, logo, typography, accents, schemes: brandFromAccent(...) } on defineApp

The other face

The 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…

graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • PagesApp at /pages, from the same store
  • views in the page context: every lens a page at /places, and the assistant with it
  • createPageRegistry(schema).register("plot", "record", PlotPage) — the plot's page in the garden's words

A lens over the garden

A 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…

Standing says “1 problem”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • lenses: [{ name: "coverage", binds: "entities", bindings: { rows, columns, link } }] on defineApp
  • the lens's View registered for the gardeners, cardinality many

What grows where

A 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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • x and y on plot, 0..1 across the garden
  • a second lens declaration binding slots, x, y and fill to plot and grows-in
  • fillFrom: "occupant" — the lens reads the edge from the planting's end

Ship it

Deployment 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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • version: 2 and migrations: [{ from: 1, to: 2, title, apply }] on defineApp
  • openStore against a stored version-1 garden

The garden's own face

Every 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…

graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • createPageRegistry(schema).surface("shell" | "home" | "problems", …) and .register(kind, "list" | "record", …) for every kind
  • register("plot", { cardinality: "many", fidelity: "full" }, GardenMapView, { title: "The garden map" }) — a lens of the garden's own

Who is here

Users, 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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • declareInstallation({ roles, admin }) — two kinds, six acts, a module drawn only for those who administer it, and a self grant for a profile
  • createSchema([...yours, ...installation.kinds]), mutations: [...yours, ...installation.mutations], policy: installation.withPolicy(policy)
  • reachLens registered over the people as "Who may do what"

The studio

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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • 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 other
  • createStudioLens(app) registered over the kinds as "What the checker says"

The years it turns through

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…

Standing says “The garden keeps its agreements”. graview check: Seedbed — no problems found. This is the app itself: switch its face, click a district, take an act.
The declaration gained
  • defineNode("rotation") with an appendOnly edge "turns-over" to the plot it turns
  • an 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 plot
  • defineMutation("rotate") — which family a plot grows, and until when; it claims what is already in the ground, and sow puts new plantings under the turn
  • the same calendar binding at range: "years" with horizon: { years: 4, title: "The rotation" }

The declaration

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.

  • The picture. Layout is a pure function of focus, relation and graph. Same graph, same picture. Every stop is a URL. Any two states interpolate.
  • The actions. Which mutations are legal across the current selection, ranked, with their unanswered arguments typed and their pickers populated.
  • The agent's tools. One surface, shared with the interface. An agent edit and a human edit produce the same diff and the same log entry.
  • The words. Connector captions, group headings, accessible names. Read from description and inverse.
  • A view, before you write one. A new kind renders sensibly on day one. Replace it with a real view when you have something better to say.

The packages

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.

The thirteen packages and what each contains
PackageWhat is in it
graviewThe 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/coreSchema, 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/layoutWhere everything sits. Headless and pure. No DOM, no framework.
@graview/toolsDerived affordances, and one tool surface an agent and an interface both use.
@graview/renderCapture, depth compositing, pointer routing. Only this package touches the GPU, and only through its /gpu entry.
@graview/reactThe binding, and the routes a line may take. Deliberately thin.
@graview/primitivesView 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/pagesThe routed face: home, lists, records, forms and problems derived from the declaration, every surface replaceable.
@graview/shipPersistence in the browser, versions and migrations, and the deploy shape.
@graview/embedmount(el, { app, seed, stop }): an app inside any element, themed to itself, with faces, places and seats on a strip.
@graview/studioThe 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/skillsAuthoring moves for Claude Code and Codex. Each one ends in a check rather than a claim.
create-graviewThe 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.

Depth

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.

plane 0

Focus, full fidelity

Live DOM, captured every frame it moves. Whatever you are working on, at whatever size it wants.

plane 1

Relations, summary

Whatever the focus touches, captured on change into a cached texture. Still real elements. Still clickable and focusable.

plane 2

Context, glyph

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.

Lenses

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.

Board

Things sit where the domain says they sit, not where a layout engine decided. The empty slot is the whole point.

Coverage

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.

Timeline

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.

The kit

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…

Every line's route
The tended-by line
The ground and the marks
kit: {}

graview check: the kit is clean.

Rules & agents

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.

A rule names its own repairs

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.

History is a fold, not a stack

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.