Skip to content
Guides— browse docs
On this page

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 document

You 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
Expected output:
✓ 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.

ToolApp
Relationship to the hostconsumes it (useHost())provides it (Host + Surface)
Ownsone document typea parent document + sub-documents
Appears asa document in the sidebar, a tabits own route and tab
Can embed elsewhereyes — as a blockno
Composes other toolsnoyes

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:

WordMeans
Worldthe content substrate. A world is its default era
Eraa version of the world — the only versioning axis
Projectone running instance of an app; owns its output, reads its era's cards
Documentone piece of content, with one document type, edited by one tool
Toola capability that makes one kind of document; consumes a host
Appa lens that owns a space; provides a host
Blocka tool's embeddable face, rendered inside another document
Hostwhat your code reaches the platform through (useHost())
Capabilityone namespace on the host (world, media, nav, icons, …)
Codecthe 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