The mental model
World, era, project, app, tool, block — the six nouns vvd is built out of, and how your code sits among them.
You're about to write a component that renders inside somebody else's world. To place it, you need to know what a "world" is — and five other words the platform uses constantly.
Here's the whole model in one sentence:
One world, seen through swappable app lenses.
Everything below is that sentence expanded.
You will learn
- What a world, era, project, app, tool, and block each are
- Why a tool always produces a document, and what that buys you
- Where your code sits in that picture
The shape
Account
└── Studio a team
└── World the content substrate ← everything you build lives here
└── Era a version of the world — the ONLY versioning axis
└── App a lens — provides a host, owns a space
└── Project one running instance of that app
└── Tool a capability — consumes a host, makes one document
└── Block a tool's embeddable face, inside another documentYou will spend essentially all of your time at the bottom three lines. The top three are context — but they're the reason your tool doesn't need a database.
World — the content substrate
A world is a collection of collaborative documents: character cards, maps, timelines, prose, tables, and anything you build. It is the unit people invite each other into, and it's where your creation gets installed.
You need one to develop against. Create it in the app, or from the terminal:
vvd worlds create "Test Bench" --genre fantasy
✓ Created Test Bench (test-bench · 9c2e6a2e-4d1b-4f70-9d55-2a1d6e3c8f41)
That middle value — test-bench — is the world's slug, and it's what vvd run --world=
takes. Not the name.
Era — a version of the world
An era is a version of the world's content: "the world, three hundred years earlier." The platform composes the default era with a named one when it reads documents.
For most creations this is invisible and free — you read documents, and you get the era the reader is looking at. You do not have to handle it, and if you never think about eras again your tool will still be correct.
Project — an instance of an app
A project is one running instance of an app. If someone opens your travel-planner app twice — once for the northern campaign, once for the southern one — that's two projects.
A project owns its own output documents and reads the shared card pool of the era it sits in. It does not fork its own versions of the world's cards — eras are the only versioning axis. Two projects in the same era see the same cards.
Tools don't have projects; a tool's work is a document. Apps do, which is why vvd run on
an app asks which project to open (or makes one).
App and Tool — the one seam
This is the distinction worth memorising, because everything else follows from it:
A tool consumes a host. An app provides one.
They're not two technologies. They're two roles in the same composition graph, using the same SDK.
| Tool | App | |
|---|---|---|
| Relationship to the host | consumes it (useHost()) | provides it (Host + Surface) |
| Owns | one document type | a parent document + sub-documents |
| Appears as | a document in the sidebar, a tab | its own route and tab |
| Can embed elsewhere | yes — as a block | no |
| Composes other tools | no | yes |
A tool always produces a document. That isn't incidental: the platform routes by
document_type, and that one fact is how the sidebar lists your tool, how sharing attaches
to it, how search finds it, and how an agent can operate it over the API. A capability with
no document is a thing the platform cannot address.
Block — the embeddable face of a tool
A card's body is an ordered list of blocks, which is why a card can contain a map, a timeline, or another card. A block is your tool rendered inside someone else's document, and its data lives in that host document rather than in one of its own.
You get this nearly for free: declare a block on your tool and it becomes droppable into any
page. The Tools section covers defineBlock in full.
There's one variant worth knowing about now, because it surprises people: a block declared
with scope: "world" reads the world instead of a document and therefore needs no
document at all. A "what changed this week" panel is one of these. It is not a third role —
it's a block whose data source happens to be the world.
Host — how your code reaches the platform
The host is the object your tool reaches the platform through: useHost(). Media,
navigation, identity, permissions, search, icons, drag-and-drop — all of it arrives through
that one seam, and none of it through fetch.
That's what makes the same tool run unchanged in the editor, inside a wiki, on a published read-only page, and in the examples on this site. Only the host changes. There is a whole page on this, because it's the rule that keeps your creation portable.
Codec — the only place data is defined
A codec is the typed shape of one document. You declare it once with
defineStateCodec, and you get the storage, the merge behaviour, the typed read, the typed
mutators, and — when you ship — the operations an agent can perform on your documents over
the API.
It is also the only place the underlying CRDT is touched. You will not import Yjs. That is the point, and it's the next page you should read after this one.
Where your code sits
Putting it together — a tool you write:
world ─────────────────────────────────────────
│ the reader's documents, people, media
│
host ─────────────────────────────────────────
│ useHost() · identity, media, nav, world
│
┌──────┴───────┐
│ YOUR TOOL │ defineTool({ render })
│ │
│ codec ────┼── defineStateCodec({ … }) → one document
│ block ────┼── defineBlock({ … }) → embeddable face
└──────────────┘Two files. src/codec.ts says what you store; src/tool.tsx says what it looks like. The
scaffold writes both.
Words this site uses precisely
Because ambiguity here is expensive, these words mean exactly one thing everywhere on this site:
| Word | Means |
|---|---|
| World | the content substrate. A world is its default era |
| Era | a version of the world — the only versioning axis |
| Project | one running instance of an app; owns its output, reads its era's cards |
| Document | one piece of content, with one document type, edited by one tool |
| Tool | a capability that makes one kind of document; consumes a host |
| App | a lens that owns a space; provides a host |
| Block | a tool's embeddable face, rendered inside another document |
| Host | what your code reaches the platform through (useHost()) |
| Capability | one namespace on the host (world, media, nav, icons, …) |
| Codec | the typed shape of a document — the only place data is defined |
Recap
- A world is a collection of collaborative documents; a world is its default era.
- An era is a version of the world and the only versioning axis; a project is one running instance of an app, which owns its output and reads its era's shared cards.
- A tool consumes a host and makes one document; an app provides a host and owns a space.
- A block is a tool's embeddable face, and a codec is the only place your data is defined.
Next steps
- Install the CLI — the tooling, which is one line.
- Storing data — the codec, in practice, editable.