Step 2: Scaffold it
One command, two source files, and a page that already reads your world.
You need the vvd CLI and a world to run it against. If you don't have either yet,
install the CLI first — it takes about a minute and
needs nothing else on your machine.
vvd create Chronicle --wiki
→ Creating wiki Chronicle in /Users/you/chronicle ✦ Chronicle — a brand-new wiki, ready to come alive. Next: cd chronicle vvd run # render it live in your world — hot-reloads as you edit vvd save # save a new version (a private draft) ✓ Created Chronicle (wiki) → /Users/you/chronicle
There's exactly one wiki base today, so vvd create <name> --wiki is the whole command —
--template has nothing to choose between.
No live preview for this step. Scaffolding writes real files to a real filesystem and needs a real world to run against — nothing this browser tab can do. The terminal output above is the honest whole of it; Step 3 picks back up with something you can run right here.
What you got
chronicle/
├── vvd.json the manifest — identity, class, capabilities
├── src/
│ ├── app.tsx the page (live + published) and defineApp
│ └── publish-hooks.ts bake — the live world → one frozen edition
├── docs/ reference material for a coding agent working in this project
└── .claude/skills/vvd-app/SKILL.mdNo package.json, no lockfile, no node_modules. React, Yjs and @vvd/sdk are platform
singletons the host injects when your bundle loads. There's no src/codec.ts either — a
tool declares the document it edits; a wiki edits no document of its own, so it has none.
What makes it a wiki
A wiki isn't a third kind of thing — vvd has tool and app, full stop. A wiki is an app
wearing four extra manifest lines:
{
"kind": "app",
"category": "site",
"publish": "custom",
"capabilities": { "readsWorld": true, "sharing": true }
}| Line | What it buys you |
|---|---|
category: "site" | Classes the app as a wiki template — it shows up in a world's template switcher and mounts in the wiki chrome (design controls beside the page, Publish top-right). |
publish: "custom" | Your bundle exports its own bake + renderBaked (Step 7). Without it, the platform substitutes its own surface for yours. |
capabilities.sharing | Grants the sitePublish capability — the thing that lets a wiki publish its own public address. |
capabilities.readsWorld | Grants world + search, so useWorldQuery can read the world's documents. Miss this and your wiki mounts to a loud, named error instead of a blank page — never a silent one. |
vvd create --wiki writes all four. You'll rarely touch them by hand — vvd set name "The Chronicle" and vvd info cover the rest.
Your toolkit is @vvd/sdk
Every hook and every wiki-specific export in this build-along — useWorldQuery,
WIKI_THEME_PARAM, useAppCustomization, all of it — comes from @vvd/sdk, the same
package every tool and app is built on. There's no separate "site SDK" on the developer
surface: the two built-in templates (Default, Overworld) are first-party code on internal
packages that aren't injected into a third-party bundle and can't be imported from one.
Reference has the full export list.
src/app.tsx, in shape
One file, four parts, in this order — the shape every step from here builds on:
- The page — the component both live and published render. Everything it needs arrives
as props: resolved theme values, page state, write actions (or
null), which entry is open. - The live mount — reads the two things that only exist live (customization, collab state) and hands them down as props.
- The published face — the same page, fed from a frozen edition instead.
actions={null}, because a frozen edition takes no writes. defineApp— the declaration, includingpublish: { bake, renderBaked }.
The one rule this shape protects you from
Anything both lives render must never call useCollabState or useAppCustomization
directly — a published page has no collab document and no Customize panel. Pass those
values in as props from the live mount instead. The scaffold is already built this way;
every step below keeps it that way.