diff --git a/.gitignore b/.gitignore
index 8f0aa93..be0d98c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -163,6 +163,10 @@ PublishScripts/
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
+# ...and except our own source, where "packages" is a real folder of real code.
+# Without this, Source/JavaScript/*/packages/ is silently dropped from every commit:
+# the build stays green locally because the files are on disk, and CI fails on a fresh clone.
+!Source/JavaScript/*/packages/**
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignorable files
diff --git a/Documentation/blueprint-components/add-a-template.md b/Documentation/blueprint-components/add-a-template.md
new file mode 100644
index 0000000..3e489b0
--- /dev/null
+++ b/Documentation/blueprint-components/add-a-template.md
@@ -0,0 +1,180 @@
+---
+title: Add a template of your own
+description: Write an Arc-bound page template that composes with the default blueprint's shell, and prove it does.
+---
+
+Sooner or later your application needs a page shape this blueprint does not ship — a queried list with an
+approval rail down the side, a command page that opens with a summary. This is the recipe.
+
+Two rules make the difference between a template that composes and one that only works where you first put
+it. Both are stated up front because both are easy to get wrong in a way that looks fine.
+
+## Rule one: never invent a slot name
+
+`fitsSlot` names the slot on your parent that you fill. It is resolved against *whatever contains you*, so
+the name has to be one your parent really declares. There are exactly two vocabularies:
+
+- `SlotName` — the default blueprint's layout regions: `topbar`, `sidebar`, `menu`, `breadcrumb`,
+ `content`, `footer`, `rightPanel`, `configPanel`, `aside`.
+- `TemplateSlotName` — what a screen template offers to what it contains: `header`, `body`, `sidePanel`,
+ `toolbar`, `actions`, `stats`, `primary`, `secondary`.
+
+A whole page fits `SlotName.Content`, because that is the only region a layout offers a screen. Anything
+nested fits one of its parent's `TemplateSlotName`s.
+
+## Rule two: within one chain, a fitted slot name has exactly one declarer
+
+`resolveScreenTemplates` places a template only when the containers in scope agree on **exactly one** home
+for it. Two containers declaring the same name is not resolved by preferring the nearer one — it is
+reported as unplaced, because guessing would put content in the wrong region, which is far harder to
+diagnose than being told the name is ambiguous.
+
+So a chain that reuses `body` at three levels reads perfectly and resolves to nothing. This blueprint's
+chain is built to avoid that: `body` is declared only by the module, `primary` only by the feature.
+
+```typescript
+export const dataModulePageTemplate: ScreenTemplate = {
+ name: 'DataModulePage',
+ fitsSlot: SlotName.Content,
+ slots: [{ name: TemplateSlotName.Header }, { name: TemplateSlotName.Body }, { name: TemplateSlotName.SidePanel }],
+ // ...
+};
+
+export const dataFeatureSectionTemplate: ScreenTemplate = {
+ name: 'DataFeatureSection',
+ fitsSlot: TemplateSlotName.Body,
+ slots: [{ name: TemplateSlotName.Toolbar }, { name: TemplateSlotName.Primary }, { name: TemplateSlotName.Secondary }],
+ // ...
+};
+
+export const commandSliceSectionTemplate: ScreenTemplate = {
+ name: 'CommandSliceSection',
+ fitsSlot: TemplateSlotName.Primary,
+ slots: [{ name: TemplateSlotName.Header }, { name: TemplateSlotName.Actions }],
+ // ...
+};
+```
+
+`header` appears twice and that is fine — nothing in the chain *fits* `header`, so it is never ambiguous.
+The constraint is only on names something fits.
+
+## Write the template
+
+Start from the element builders this package exports, so you are not rebuilding decisions it already made:
+
+```typescript
+import { ScreenTemplate } from '@cratis/scene.model';
+import { SlotName, TemplateSlotName } from '@cratis/scene.blueprint.default';
+import { arcPageHeader, dataPage, page, toolbar, toolbarButton } from '@cratis/scene.blueprint.components';
+
+export const purchaseOrderListTemplate: ScreenTemplate = {
+ name: 'PurchaseOrderList',
+ fitsSlot: SlotName.Content,
+ slots: [{ name: TemplateSlotName.Header }, { name: TemplateSlotName.Body }],
+ content: {
+ [TemplateSlotName.Header]: [
+ arcPageHeader('po-header', { title: 'Purchase orders', subtitle: 'Awaiting approval', section: 'Procurement', query: 'AllPurchaseOrders' }, [
+ toolbar('po-actions', [toolbarButton('po-approve', 'Approve the selected order', 'pi pi-check', 'Approve')]),
+ ]),
+ ],
+ [TemplateSlotName.Body]: [
+ dataPage('po-body', 'AllPurchaseOrders', { emptyMessage: 'No purchase orders yet', dataKey: 'purchaseOrderId', globalFilterFields: ['number', 'supplier'] }, [
+ { field: 'number', header: 'Number' },
+ { field: 'supplier', header: 'Supplier' },
+ { field: 'total', header: 'Total' },
+ ]),
+ ],
+ },
+ displayName: 'Purchase order list',
+ description: 'A dataPage bound to the purchase order query, with an approval action.',
+};
+```
+
+Three things in there are worth calling out.
+
+**The binding is a name, never a class.** A property bag carries plain values and named slots; there is no
+way to put a TypeScript class in one. `resolveElementBinding` reads the name back out at render time and
+looks it up in the binding registry — do not invent a second mechanism for reaching a backend, because the
+one that exists is the one every adapter uses.
+
+**`emptyMessage` and `dataKey` are required by `TableOptions` on purpose.** The library's adapters both
+default them to an empty string, and both defaults are quietly wrong in a shipped template: a table with no
+empty message reads as broken when the query legitimately returns nothing, and one with no data key loses
+its selection every time the query is re-performed.
+
+**The header carries the binding too.** Give it the same one the body is built around, and the heading, the
+trail and the design-time state all follow from saying it once — instead of three copies that drift.
+
+## Add an arrangement only when declaration order is wrong
+
+A template with no `arrangement` renders its slots in declaration order, which is right more often than
+not. Add one when the page has a real two-dimensional shape, and add the compact override in the same
+breath:
+
+```typescript
+arrangement: {
+ root: column([slotLeaf(TemplateSlotName.Header), grid([slotLeaf(TemplateSlotName.Primary, { span: 2 }), slotLeaf(TemplateSlotName.Secondary)], 3, 16)]),
+ overrides: [
+ {
+ width: WidthSizeClass.Compact,
+ root: column([slotLeaf(TemplateSlotName.Header), slotLeaf(TemplateSlotName.Primary), slotLeaf(TemplateSlotName.Secondary)], 16),
+ },
+ ],
+},
+```
+
+A leaf naming a slot you never declared positions nothing and reports nothing — the region simply is not
+there. No type can express "one of whatever this container declared", so prove it with a spec instead.
+
+## Prove it composes
+
+Three checks, and every one of them is a spec this package already runs against its own templates.
+
+**It finds exactly one home**, with the other blueprint's templates in scope too — because that is the
+situation a real profile is in:
+
+```typescript
+const resolution = resolveScreenTemplates(appShellLayout, [...(defaultBlueprint.screenTemplates ?? []), purchaseOrderListTemplate]);
+resolution.unplaced.filter(unplaced => unplaced.template === 'PurchaseOrderList').should.be.empty;
+```
+
+**Every component name it writes resolves**, against a catalog built from the real manifests:
+
+```typescript
+const names = distinctComponentNames(Object.values(purchaseOrderListTemplate.content ?? {}).flat());
+names.map(name => resolveComponentName(name, componentsBlueprintProfile, componentsBlueprintCatalog))
+ .filter(resolution => resolution === undefined)
+ .should.be.empty;
+```
+
+If a name does not resolve, fix the template rather than the spec. The default blueprint hit exactly this:
+it referenced `chart` and `fileUpload`, which neither library declares — two names that look obvious and
+render as dashed red boxes.
+
+**Its content is filed only under slots it declares:**
+
+```typescript
+const declared = new Set(purchaseOrderListTemplate.slots.map(slot => slot.name));
+Object.keys(purchaseOrderListTemplate.content ?? {}).filter(name => !declared.has(name)).should.be.empty;
+```
+
+## When to write a component instead
+
+Almost never. Prefer a template every time: a template is data a host can rearrange, and a component is
+code it cannot.
+
+The bar this blueprint holds itself to is that a content tree genuinely *cannot express* the composition —
+which in practice means the thing derives or looks something up at render time rather than holding it. One
+component in this package clears it, and it is worth reading
+[layering on another blueprint](layering-on-a-blueprint.md) for why. If you do register one, declare it in
+your manifest, or `validatePackageBundle` fails:
+
+```typescript
+validatePackageBundle(componentsBlueprint).should.be.empty;
+```
+
+## Where to go next
+
+- [The template catalogue](template-catalogue.md) — the shapes you may not need to write at all.
+- [Wire the binding registry](wiring-the-binding-registry.md) — supplying the class behind your name.
+- [Ship your own blueprint](../blueprints/ship-your-own-blueprint.md) — when a template set is not enough.
diff --git a/Documentation/blueprint-components/getting-started.md b/Documentation/blueprint-components/getting-started.md
new file mode 100644
index 0000000..8db8fb3
--- /dev/null
+++ b/Documentation/blueprint-components/getting-started.md
@@ -0,0 +1,134 @@
+---
+title: Use the Components blueprint
+description: Activate the blueprint, boot one of its pages through the real engine, and register the query behind it.
+---
+
+Let's take an Arc-bound page from "nothing" to "showing real invoices". Three steps: list the package,
+render a screen, register the query.
+
+## List it in your profile
+
+A blueprint is a packaged artifact, not a language construct — it never appears in a `.play` file. A
+`ui profile` lists it by name, exactly like a component library:
+
+```screenplay
+ui profile Desktop
+ target platform web
+ target size expanded
+
+ packages
+ core
+ Tailwind
+ PrimeReact
+ Cratis.Components
+ Cratis.Blueprint.Default
+ Cratis.Blueprint.Components
+```
+
+Declaration order is ascending override priority, and this order is the one the package itself resolves
+against:
+
+```typescript
+export const componentsBlueprintProfile: UiProfile = {
+ name: 'Arc pages',
+ targetPlatform: 'web',
+ packages: ['core', 'PrimeReact', cratisComponentsPackageManifest.name, defaultBlueprintName, componentsBlueprintName],
+};
+```
+
+`Cratis.Components` sits above `PrimeReact`, so a template naming `table` or `dialog` gets the Arc-aware
+one and PrimeReact's is recorded as shadowed rather than discarded. Both blueprints sit above both
+libraries, so `pageHeader` and `arcPageHeader` resolve to the packages that declare them.
+
+You do not have to write all six by hand. Declaring only the blueprint and letting the resolver expand it
+gives you the same list, in a valid order:
+
+```typescript
+const selection = resolvePackageDependencies(['Cratis.Blueprint.Components'], catalog);
+isPackageSelectionValid(selection).should.be.true;
+```
+
+If a dependency is missing from the catalog, you get told which one while the profile is being configured,
+rather than finding out when the page opens.
+
+## Render a page
+
+The package ships a `Screen` per template, so a host can boot one through the real engine with nothing else
+in place:
+
+```tsx
+import { GalleryScreenPreview } from '@cratis/scene.blueprint.components';
+
+
+```
+
+Nothing about that path is preview-only. The screen is a real `Screen`, its bare component names go
+through the real `resolveComponentName`, and it is rendered by the real `SceneElementView` against the real
+merged registry — the same path a shipped application takes.
+
+What you see is a complete application shell — topbar, sidebar, breadcrumb, footer, the configurator — with
+an invoice page inside it. The header reads:
+
+```text
+Billing › Invoices
+Invoices
+Every invoice, filterable and paged against the server
+No query registered as AllInvoices
+```
+
+…and where the table should be:
+
+```text
+Unresolved query binding 'AllInvoices' on Cratis.Components:dataPage
+```
+
+That last line is not an error to fix before continuing. It is the design-time state: the template carries
+a query *name*, and nothing has supplied the class behind it yet.
+
+## Register the query
+
+Only a host owns the generated Arc proxies, so only a host can close that gap. It registers every proxy a
+screen can name, once, at startup:
+
+```typescript
+import { registerQueries, registerCommands } from '@cratis/scene.components';
+import { AllInvoices, InvoiceById, InvoicesInFlight } from './Billing/proxies';
+import { RegisterInvoice, RecordAdjustment } from './Billing/commands';
+
+registerQueries({ AllInvoices, InvoiceById, InvoicesInFlight });
+registerCommands({ RegisterInvoice, RecordAdjustment });
+```
+
+Render the same screen again and two things change. The header now reads `Bound to query AllInvoices`, and
+the placeholder is gone — the `dataPage` has its class, performs the query, and pages against your backend.
+
+Object shorthand is the point of the bulk form: Stage generates a module exporting every proxy it produced,
+and handing that module's exports straight in stays correct as proxies are added and removed without anyone
+editing a list. [Wiring the binding registry](wiring-the-binding-registry.md) covers which names this
+blueprint's templates ask for.
+
+## Point a template at your own query
+
+The shipped names are defaults, not fixtures. A template ships bound to something so that it renders as a
+*page* rather than a diagram of one; an application replaces the name and nothing else about the template
+changes:
+
+```typescript
+const invoiceList: ScreenTemplate = {
+ ...dataListPageTemplate,
+ name: 'PurchaseOrderList',
+ content: {
+ [TemplateSlotName.Header]: [arcPageHeader('po-header', { title: 'Purchase orders', section: 'Procurement', query: 'AllPurchaseOrders' })],
+ [TemplateSlotName.Body]: [dataPage('po-body', 'AllPurchaseOrders', purchaseOrderTableOptions, purchaseOrderColumns)],
+ },
+};
+```
+
+At that point you are writing templates, not screens — which is the moment to read
+[add a template of your own](add-a-template.md).
+
+## Where to go next
+
+- [Wire the binding registry](wiring-the-binding-registry.md) — every name these templates ask for.
+- [The template catalogue](template-catalogue.md) — the other ten screen templates and the three dialogs.
+- [Add a template of your own](add-a-template.md) — the two rules that keep a new template composable.
diff --git a/Documentation/blueprint-components/index.md b/Documentation/blueprint-components/index.md
new file mode 100644
index 0000000..a08a9e3
--- /dev/null
+++ b/Documentation/blueprint-components/index.md
@@ -0,0 +1,110 @@
+---
+title: Cratis Components blueprint
+description: A blueprint of Arc-bound screen and dialog templates - whole pages built from the Cratis Components composites, for the default blueprint's shell.
+---
+
+You have picked the [default blueprint](../blueprints/index.md), so your application has a shell: a topbar, a
+sidebar with eight menu modes, a breadcrumb, a content region and two themes. You have listed
+[`Cratis.Components`](../components-package/index.md), so a screen can name `dataPage` and get a real
+list screen instead of a table it has to feed by hand.
+
+And then you build your first list page, and you make seven decisions: which query, which columns, what the
+empty message says, which property identifies a row, which fields the search box filters across, what the
+header says and where its actions go. Then you build the second one, and you make those seven decisions
+again — slightly differently, because it is a week later. By the tenth page an application has ten list
+screens that are almost the same, and the differences are all accidents.
+
+`Cratis.Blueprint.Components` is the answer to that. It ships those decisions already made, as templates:
+
+```typescript
+export const dataListPageTemplate: ScreenTemplate = {
+ name: 'DataListPage',
+ fitsSlot: SlotName.Content,
+ slots: [{ name: TemplateSlotName.Header }, { name: TemplateSlotName.Body }],
+ content: {
+ [TemplateSlotName.Header]: [arcPageHeader('data-list-header', { title: 'Invoices', section: 'Billing', query: SampleBindingName.AllInvoices })],
+ [TemplateSlotName.Body]: [dataPage('data-list-body', SampleBindingName.AllInvoices, invoiceTableOptions, invoiceColumns)],
+ },
+};
+```
+
+You pick the template and supply a query name. The page is done.
+
+## What is in the box
+
+Eleven screen templates and three dialog templates. Every one of them is a whole page or a whole dialog,
+not a fragment.
+
+| Template | What it is |
+|---|---|
+| `DataListPage` | A `dataPage` bound to one query, under a header stating the binding |
+| `ObservableDataListPage` | The live variant, over an observable query |
+| `DataListWithDetailPage` | The list, with the selected record's document and history beside it |
+| `MasterDetailPage` | A queried list in the larger column, the record in the narrower one |
+| `DashboardPage` | Four query-backed widgets, arranged the way people read a dashboard |
+| `CommandFormPage` | A generated command form with its own action bar |
+| `SchemaEditorPage` | An event type's schema, edited as a typed property tree |
+| `ObjectEditorPage` | One document against its schema, with its trail and version history |
+| `DataModulePage` · `DataFeatureSection` · `CommandSliceSection` | A worked three-level nesting chain |
+| `CommandDialog` · `ConfirmDialog` · `BusyDialog` | The three dialog shapes an Arc application repeats |
+
+Every one is in [the template catalogue](template-catalogue.md), with the slots it declares and the
+bindings it names.
+
+## How it differs from the default blueprint
+
+They are both blueprints, and they do opposite halves of the job.
+
+The default blueprint answers **"what does this application look like"**. It ships two layouts, sixteen
+shell components, twenty-three screen templates and two themes, and its templates are built from
+primitives — a `dataTable` handed rows, a form of `inputText` fields, a dialog assembled from a title, a
+message and two buttons. Those templates are about *shape*. They render fully with no backend at all,
+because there is no backend in them.
+
+This blueprint answers **"what does a page look like once it is bound to Arc"**. Its templates are built
+from the Cratis Components composites, so a list page performs a real query, pages against the server, and
+wires filtering and sorting back into it. They are about *behavior*, and the price of that is that most of a
+page is a placeholder until a host registers the bindings — which is the normal design-time state and not a
+shortcoming.
+
+| | Default blueprint | This blueprint |
+|---|---|---|
+| Layouts | Two application shells | **None** — it reuses the default's |
+| Themes | Two | **None** — it reuses the default's |
+| Templates built from | `core` and PrimeReact primitives | `Cratis.Components` Arc-bound composites |
+| Renders without a backend | Completely | Headers and editors do; queried regions are placeholders |
+| Components registered | Sixteen shell components | **One** |
+
+That "none" and that "one" are the design, not an unfinished list — see
+[layering on another blueprint](layering-on-a-blueprint.md) for why an empty `layouts` is the whole point,
+and [the template catalogue](template-catalogue.md) for the one component that earned its place.
+
+## What it needs underneath it
+
+```typescript
+dependencies: [{ name: 'Cratis.Blueprint.Default' }, { name: 'Cratis.Components' }],
+```
+
+This is the first blueprint in Scene that depends on another blueprint. `resolvePackageDependencies`
+expands that declaration transitively, so listing this package in a profile pulls in the default blueprint,
+Cratis Components, and everything those two need in turn:
+
+```typescript
+const selection = resolvePackageDependencies(['Cratis.Blueprint.Components'], catalog);
+// selection.added contains 'PrimeReact' and 'Tailwind'
+// 'Cratis.Blueprint.Default' and 'Cratis.Components' both come before 'Cratis.Blueprint.Components'
+```
+
+The ordering is not incidental. Declaration order in a profile is ascending override priority, so this
+package outranking both means a template of its own can shadow one of the default blueprint's if it ever
+needs to — and `Cratis.Components` outranking `PrimeReact` is what makes a template naming `table` resolve
+to the Arc-aware one.
+
+## Where to go next
+
+- [Use the Components blueprint](getting-started.md) — activate it, render a page, register a binding.
+- [Wire the binding registry](wiring-the-binding-registry.md) — what a host has to supply, and what happens
+ before it does.
+- [Add a template of your own](add-a-template.md) — the recipe, and the two rules that keep it composable.
+- [Layering on another blueprint](layering-on-a-blueprint.md) — why `layouts` is empty and what that buys.
+- [The template catalogue](template-catalogue.md) — every template, slot and binding.
diff --git a/Documentation/blueprint-components/layering-on-a-blueprint.md b/Documentation/blueprint-components/layering-on-a-blueprint.md
new file mode 100644
index 0000000..e8ba29f
--- /dev/null
+++ b/Documentation/blueprint-components/layering-on-a-blueprint.md
@@ -0,0 +1,144 @@
+---
+title: Layering on another blueprint
+description: Why this blueprint ships no layout and no theme, and what depending on another blueprint buys that shipping a rival shell would have cost.
+---
+
+Open this package's manifest and the first thing that looks wrong is an empty array:
+
+```typescript
+export const componentsBlueprintManifest: ScenePackage = {
+ name: componentsBlueprintName,
+ version: '1.0.0',
+ kind: PackageKind.Blueprint,
+ dependencies: [{ name: 'Cratis.Blueprint.Default' }, { name: 'Cratis.Components' }],
+ components: Object.values(ComponentName),
+ layouts: [],
+ // ...
+ themes: [],
+};
+```
+
+A blueprint is *the package that ships the shape of an application* — its layouts, the templates built on
+them, the components that fill their slots, the themes that color all of it. This one ships no layout at
+all. It is worth being clear that this is the design, because it is the first blueprint-on-blueprint
+dependency in Scene and the empty array is easy to read as an unfinished list.
+
+## An application activates one shell
+
+A **layout** is an application's base navigational shell, and an application has exactly one. That is not a
+limitation of the model, it is what the word means: the shell is the thing every page lives inside, and
+there cannot be two.
+
+So a blueprint that ships a layout is, in effect, asking to be the only blueprint. Two of them are mutually
+exclusive by construction, and choosing between them is choosing between everything each one holds.
+
+```mermaid
+flowchart TB
+ subgraph rival["If this package shipped its own shell"]
+ direction TB
+ R1["Cratis.Blueprint.Default AppShell · 8 modes · 2 themes"]
+ R2["Cratis.Blueprint.Components ArcShell · Arc pages"]
+ R3{"Application picks one"}
+ R1 --> R3
+ R2 --> R3
+ end
+ subgraph layered["What it does instead"]
+ direction TB
+ L1["Cratis.Blueprint.Default AppShell · 8 modes · 2 themes"]
+ L2["Cratis.Blueprint.Components Arc pages, fitsSlot: content"]
+ L3["Application gets both"]
+ L1 --> L3
+ L2 -->|"depends on"| L1
+ L2 --> L3
+ end
+```
+
+On the left, picking Arc-bound pages costs you the eight menu modes, the themes, the navigation
+aggregation and the sixteen shell components the default blueprint already does well. On the right it costs
+nothing. Layering is the only arrangement in which either package is worth having.
+
+## What the dependency actually does
+
+The dependency is not a formality. Almost everything structural on these pages comes from the other
+blueprint:
+
+- **Its slot vocabulary.** Every template's `fitsSlot` is `SlotName.Content` — the default blueprint's
+ `AppShell` slot — and every template's own slots are `TemplateSlotName` values. No slot name here is
+ invented, because a name this package made up would fit nothing.
+- **Its shell components.** The gallery's chrome is built from `topbar`, `sidebar`, `menu`, `menuItem`,
+ `breadcrumb`, `footer`, `configPanel`, `logo` and `userMenu` — all names the default blueprint declares.
+- **Its element builders.** `externalComponent` is imported rather than reimplemented, because
+ `ExternalComponent` has fifteen required members before the two a template author cares about, and a
+ second copy of those defaults is a second thing to keep right.
+- **Its composition rules.** `nestScreenTemplates`, `templateContentInLayout` and `composeScreenElement`
+ place a template inside the layout, and reimplementing them would mean re-deriving `fitsSlot` semantics.
+- **Its themes and its layout modes.** Which is why there is a story rendering an Arc page in the shell's
+ slim mode: an Arc page dropped into `content` gets all eight modes for free.
+
+The one thing this package brings that the other does not is Arc binding. That is a genuinely different
+axis, and it is why the two are separable packages rather than one.
+
+## Themes are empty for the same reason, smaller
+
+`@cratis/components` reads a `--cratis-*` variable layer that resolves whatever theme is active underneath
+it, so the library asserts no palette. The shell being themed is the default blueprint's. A theme shipped
+here would be this package asserting a look for a shell it does not own — so it ships none, and the
+configurator on every gallery screen offers the default blueprint's themes:
+
+```typescript
+function configPanel(): SceneElement {
+ return externalComponent(DefaultComponentName.ConfigPanel, DefaultComponentName.ConfigPanel, {
+ title: 'Settings',
+ themes: defaultBlueprintThemes.map(theme => ({ name: theme.name, label: theme.name, isDark: theme.isDark ?? false })),
+ });
+}
+```
+
+## One component, and why even that one
+
+The same reasoning runs down to the component registry, which has exactly one entry:
+
+```typescript
+export const componentsBlueprintComponents: ComponentRegistry = {
+ [componentRegistryKey(componentsBlueprintName, ComponentName.ArcPageHeader)]: ArcPageHeader,
+};
+```
+
+The rule this package holds itself to is: register a component only when a template's content tree
+genuinely cannot express the composition, and reach for a template every other time. A template is data a
+host can rearrange; a component is code it cannot. A blueprint whose value sat in its components rather
+than its templates would have misunderstood its job.
+
+`arcPageHeader` clears the bar because it *derives* rather than holds. Give it one binding name and it
+produces the heading, the breadcrumb trail and the design-time binding state:
+
+```typescript
+export function deriveArcHeading(properties: Record, binding: ElementBinding, kind: BindingKind): ArcHeading {
+ const title = stringProperty(properties, 'title') ?? (binding.name === undefined ? 'Untitled page' : humanizeBindingName(binding.name));
+ const section = stringProperty(properties, 'section');
+
+ return {
+ title,
+ subtitle: stringProperty(properties, 'subtitle'),
+ trail: section === undefined ? [title] : [section, title],
+ bindingName: binding.name,
+ isBound: binding.target !== undefined,
+ bindingLabel: bindingLabelFor(binding, kind),
+ };
+}
+```
+
+A tree can hold a title, a subtitle and a trail as three literals — and then those three literals drift the
+first time anything is renamed. It cannot derive them from one name, and it certainly cannot look that name
+up in the binding registry to report whether a host has wired it. Both are behavior at render time, not
+structure, which is exactly the line.
+
+Note also what it does *not* do: it registers `arcPageHeader`, not `pageHeader`. Shadowing the default
+blueprint's page header would silently change that blueprint's own screens, which is not a decision this
+package gets to make on its behalf.
+
+## Where to go next
+
+- [Add a template of your own](add-a-template.md) — the same rules, applied to your code.
+- [The template catalogue](template-catalogue.md) — every template and the slots it declares.
+- [Understanding blueprints](../blueprints/understanding-blueprints.md) — what a blueprint is in general.
diff --git a/Documentation/blueprint-components/template-catalogue.md b/Documentation/blueprint-components/template-catalogue.md
new file mode 100644
index 0000000..6e5a785
--- /dev/null
+++ b/Documentation/blueprint-components/template-catalogue.md
@@ -0,0 +1,154 @@
+---
+title: The template catalogue
+description: Every screen template, dialog template, element builder and component this blueprint ships, with the slots each declares and the bindings each names.
+---
+
+Every template here fills the default blueprint's `AppShell` layout. Names are as a `Screen` refers to
+them; slots are `TemplateSlotName` values.
+
+## Screen templates
+
+All eight page templates set `fitsSlot: SlotName.Content`, which is the only region a layout offers a
+screen. The three nesting-chain templates fit each other.
+
+| Template | Slots | Bindings | What it is |
+|---|---|---|---|
+| `DataListPage` | `header`, `body` | `AllInvoices` | A `dataPage` filling the body, under a header stating the binding |
+| `ObservableDataListPage` | `header`, `body` | `InvoicesInFlight` | The same shape over an observable query |
+| `DataListWithDetailPage` | `header`, `body`, `sidePanel` | `AllInvoices` | The list, plus the record's document, trail and history |
+| `MasterDetailPage` | `header`, `primary`, `secondary` | `AllInvoices` | A `dataTable` in the larger column, the record in the narrower |
+| `DashboardPage` | `header`, `stats`, `primary`, `secondary` | `RevenueByMonth`, `InvoicesInFlight`, `OpenTickets`, `AllInvoices`, `AllAdjustments` | Query-backed widgets over a wide and a narrow column |
+| `CommandFormPage` | `header`, `body`, `actions` | `RegisterInvoice` | A generated command form with its own action bar |
+| `SchemaEditorPage` | `header`, `toolbar`, `body` | none | An event type's schema as a typed property tree |
+| `ObjectEditorPage` | `header`, `toolbar`, `body`, `sidePanel` | `InvoiceById` | One document against its schema, with trail and history |
+| `DataModulePage` | `header`, `body`, `sidePanel` | none | Module level — fits the layout's `content` |
+| `DataFeatureSection` | `toolbar`, `primary`, `secondary` | `InvoicesInFlight` | Feature level — fits the module's `body` |
+| `CommandSliceSection` | `header`, `actions` | `RecordAdjustment` | Slice level — fits the feature's `primary` |
+
+### Which list template to reach for
+
+Three of them look alike and are not.
+
+`DataListPage` is a whole `dataPage` — its own title bar, menubar and filtering — and is what you want when
+the page *is* the list.
+
+`DataListWithDetailPage` is that same `dataPage` with a detail region added beside it.
+
+`MasterDetailPage` is a plain `dataTable` next to a detail region, and is right when the page's chrome comes
+from the feature around it rather than from the list itself. Reaching for the wrong one shows up as a page
+with two toolbars.
+
+### Which pages render without a backend
+
+`SchemaEditorPage` and `ObjectEditorPage` render completely with nothing registered, because the editors
+they are built from read their content out of the property bag. Every other page renders its header,
+toolbar and chrome, and shows a placeholder where the queried region will be. See
+[wiring the binding registry](wiring-the-binding-registry.md) for the full split.
+
+### The nesting chain
+
+Three templates that demonstrate `fitsSlot` composing to depth, and the slot names are chosen so each
+fitted name has exactly one declarer:
+
+```mermaid
+flowchart TB
+ L["AppShell layout declares content"]
+ M["DataModulePage fitsSlot: content declares header · body · sidePanel"]
+ F["DataFeatureSection fitsSlot: body declares toolbar · primary · secondary"]
+ S["CommandSliceSection fitsSlot: primary declares header · actions"]
+ L --> M --> F --> S
+```
+
+`resolveScreenTemplates` places all three, at depths 1, 2 and 3. A chain reusing `body` at every level would
+read perfectly and resolve to nothing — see [add a template of your own](add-a-template.md) for why.
+
+## Dialog templates
+
+A dialog template has no `fitsSlot`, because an overlay occupies no slot: it is summoned, not placed.
+
+| Template | Slots | Binding | What it is |
+|---|---|---|---|
+| `CommandDialog` | `body` | `RecordAdjustment` | A short capture whose confirm button is the command's execution |
+| `ConfirmDialog` | `body` | none | One question, with the consequence spelled out |
+| `BusyDialog` | `body` | none | The blocking spinner for a long-running command |
+
+Each declares exactly **one** slot, which is a deliberate departure from how the default blueprint builds
+its dialogs. That blueprint composes a dialog out of primitives — a title, a message, a row of buttons —
+because primitives are all it has. Here the library ships whole dialogs that resolve their result through
+Arc's dialog context, so the frame, the title bar and the buttons all belong to the composite. A template
+declaring `header` and `actions` slots would be offering regions that render *outside* the modal, which is
+worse than not offering them: a screen would fill them, and the content would appear somewhere nobody
+expected.
+
+`CommandDialog` places its fields by hand rather than generating them, which is the opposite choice from
+`CommandFormPage` and deliberate. A dialog is a *short* capture — three or four properties a person can
+answer in an overlay. Generating every property of the command would turn an overlay into a page, which is
+precisely the decision a dialog has already made.
+
+## Element builders
+
+Exported so an application can write its own templates from the same parts.
+
+| Builder | Produces |
+|---|---|
+| `arcPageHeader(id, options, actions?)` | This blueprint's page header |
+| `page(id, title, content, panel?)` | The library's `page` primitive wrapping content |
+| `dataPage(id, query, options, columns)` | The whole list-screen composite |
+| `dataTable(id, query, options, columns)` | Server-side querying without page chrome |
+| `observableDataTable(id, query, options, columns)` | The live variant |
+| `columns(id, definitions)` | PrimeReact `column` children for a table |
+| `commandForm(id, command, exclude?)` | A form generated from the command's own properties |
+| `inputTextField` · `numberField` · `textAreaField` · `calendarField` · `dropdownField` | One field bound to one command property |
+| `dialog(id, title, okLabel, cancelLabel, content)` | The Arc-aware dialog |
+| `commandDialog(id, command, title, okLabel, fields)` | A dialog that submits a command |
+| `busyIndicatorDialog(id, title, message)` | The blocking spinner |
+| `schemaEditor` · `objectContentEditor` · `objectNavigationalBar` · `timeMachine` · `filterPanel` | The Arc-free editors |
+| `toolbar` · `toolbarButton` · `toolbarGroup` · `toolbarSeparator` | The tool palette family |
+| `errorBoundary(id, content)` · `icon(id, iconName)` | Region isolation, and an icon |
+
+Bindings are passed as plain strings, so the same builders work with your own proxy names.
+`SampleBindingName` is an enum of the names the shipped templates use, not a closed set of everything a
+binding may be.
+
+## Components
+
+One, registered under `componentRegistryKey('Cratis.Blueprint.Components', 'arcPageHeader')`.
+
+| Name | Properties | Slots |
+|---|---|---|
+| `arcPageHeader` | `title`, `subtitle`, `section`, `query`, `command` | `actions` |
+
+It derives its heading from `title`, falling back to the binding name read as a sentence; its trail from
+`section` and the heading; and its design-time state from a binding-registry lookup:
+
+| Rendered | Meaning |
+|---|---|
+| `Bound to query AllInvoices` | A host registered a class under that name |
+| `No query registered as AllInvoices` | Named, but nothing registered |
+| `No binding` | The template names none, deliberately |
+
+It carries `data-scene-binding` and `data-scene-binding-state` attributes, so a preview surface can be
+scanned for what is still unwired.
+
+## The names these templates write
+
+Every bare name a template references resolves against a catalog built from the real manifests of `core`,
+`PrimeReact`, `Cratis.Components`, `Cratis.Blueprint.Default` and this package — and a spec proves it, so a
+name renamed upstream fails here rather than rendering as a dashed box in your application.
+
+- **From `Cratis.Components`:** `page`, `dataPage`, `dataTable`, `observableDataTable`, `commandForm`,
+ `inputTextField`, `numberField`, `textAreaField`, `dropdownField`, `calendarField`, `dialog`,
+ `commandDialog`, `busyIndicatorDialog`, `icon`, `dropdown`, `errorBoundary`, `objectContentEditor`,
+ `objectNavigationalBar`, `schemaEditor`, `timeMachine`, `filterPanel`, `toolbar`, `toolbarButton`,
+ `toolbarGroup`, `toolbarSeparator`.
+- **From `Cratis.Blueprint.Default`:** `appShell`, `topbar`, `sidebar`, `menu`, `menuItem`, `breadcrumb`,
+ `footer`, `configPanel`, `logo`, `userMenu`.
+- **From `PrimeReact`:** `column`, and only `column` — the library's tables take PrimeReact `Column`
+ children and declare no `column` name of their own.
+- **From this package:** `arcPageHeader`.
+
+## Where to go next
+
+- [Wire the binding registry](wiring-the-binding-registry.md) — supplying the classes these names want.
+- [Add a template of your own](add-a-template.md) — when none of the above is the shape you need.
+- [Layering on another blueprint](layering-on-a-blueprint.md) — why the component list is one entry long.
diff --git a/Documentation/blueprint-components/toc.yml b/Documentation/blueprint-components/toc.yml
new file mode 100644
index 0000000..dacad8e
--- /dev/null
+++ b/Documentation/blueprint-components/toc.yml
@@ -0,0 +1,24 @@
+- name: Overview
+ href: index.md
+
+- name: Getting started
+ items:
+ - name: Use the Components blueprint
+ href: getting-started.md
+
+- name: Guides
+ items:
+ - name: Wire the binding registry
+ href: wiring-the-binding-registry.md
+ - name: Add a template of your own
+ href: add-a-template.md
+
+- name: Understand
+ items:
+ - name: Layering on another blueprint
+ href: layering-on-a-blueprint.md
+
+- name: Reference
+ items:
+ - name: The template catalogue
+ href: template-catalogue.md
diff --git a/Documentation/blueprint-components/wiring-the-binding-registry.md b/Documentation/blueprint-components/wiring-the-binding-registry.md
new file mode 100644
index 0000000..f908101
--- /dev/null
+++ b/Documentation/blueprint-components/wiring-the-binding-registry.md
@@ -0,0 +1,118 @@
+---
+title: Wire the binding registry
+description: Which query and command names this blueprint's templates ask for, how a host supplies them, and what a page looks like before it does.
+---
+
+Every Arc-bound region on these pages is waiting on one thing: a class. The template carries a *name*, and
+a host registers the generated Arc proxy under it. This page is the recipe for the host's side.
+
+The mechanism itself belongs to `Cratis.Components` and is explained once, in
+[the binding registry](../components-package/binding-registry.md). Read that first if you have not — it
+covers why a name is the only thing that survives the trip from a `.play` document to a renderer. What
+follows assumes it.
+
+## Register at startup
+
+```typescript
+import { registerQueries, registerCommands } from '@cratis/scene.components';
+import * as queries from './Billing/queries';
+import * as commands from './Billing/commands';
+
+registerQueries(queries);
+registerCommands(commands);
+```
+
+Do it once, at your entry point, before the first screen renders. Registering the same name twice replaces
+the earlier registration, so a host can re-register on hot reload without unwinding the previous run.
+
+## The names these templates ask for
+
+The shipped templates are bound to an invoice model, so that the whole package reads as one small
+application rather than eleven unrelated demonstrations. These are the names they write:
+
+| Name | Kind | Which templates want it |
+|---|---|---|
+| `AllInvoices` | Query | `DataListPage`, `DataListWithDetailPage`, `MasterDetailPage`, `DashboardPage` |
+| `InvoicesInFlight` | Observable query | `ObservableDataListPage`, `DashboardPage`, `DataFeatureSection` |
+| `InvoiceById` | Query | `ObjectEditorPage` |
+| `RevenueByMonth` | Query | `DashboardPage` |
+| `OpenTickets` | Query | `DashboardPage` |
+| `AllAdjustments` | Query | `DashboardPage` |
+| `RegisterInvoice` | Command | `CommandFormPage` |
+| `RecordAdjustment` | Command | `CommandSliceSection`, `CommandDialog` |
+
+They are exported as an enum, so a host can register against the symbols rather than retyping strings:
+
+```typescript
+import { SampleBindingName } from '@cratis/scene.blueprint.components';
+
+registerQueries({ [SampleBindingName.AllInvoices]: AllInvoices });
+```
+
+`InvoicesInFlight` is the one to look at twice. It is named by `observableDataTable` rather than
+`dataTable`, and the difference lives in the proxy you register, not in how the element is configured: an
+observable query opens a subscription and the page re-renders when the read model changes on the server.
+Registering a plain query under that name will render, and will not be live.
+
+## What a page looks like before you register
+
+Three states, three different problems, three different fixes. Every page in this blueprint opens with an
+`arcPageHeader`, and it tells you which one you are in:
+
+| The header says | What it means | What to do |
+|---|---|---|
+| `Bound to query AllInvoices` | A host registered a class under that name | Nothing |
+| `No query registered as AllInvoices` | The template named it; nothing is registered | Register it, or fix the name |
+| `No binding` | The template named nothing, deliberately | Nothing — see below |
+
+The third is not a defect. `SchemaEditorPage` genuinely has no query behind it: a schema is design-time
+metadata about an event type, not a read model anyone queries, so its header says so rather than implying
+a wiring step that does not exist.
+
+Below the header, an unbound region names what it wanted:
+
+```text
+Unresolved query binding 'AllInvoices' on Cratis.Components:dataPage
+```
+
+A template that named nothing at all reads differently, because it is a different mistake:
+
+```text
+Missing query binding on Cratis.Components:dataPage
+```
+
+The first needs the host to register that name; the second needs the template edited.
+
+## Half of a page renders anyway
+
+Not everything on these pages is Arc-bound, and the split is worth knowing because it decides what design
+work you can do before a backend exists.
+
+**Waiting on a binding:** `dataPage`, `dataTable`, `observableDataTable`, `commandForm`, `commandDialog`.
+
+**Not waiting on anything:** `schemaEditor`, `objectContentEditor`, `objectNavigationalBar`, `timeMachine`,
+`filterPanel`, `toolbar` and the page chrome. These read their content out of the property bag, so they
+render fully with no host at all.
+
+That is why `SchemaEditorPage` and `ObjectEditorPage` are the two pages to open first when looking at this
+blueprint — they are complete without a backend. It is also why one unbound table costs one dashed box
+rather than the page: a design surface stays usable while the wiring is still missing, which is the whole
+reason a missing binding is a placeholder and never a throw.
+
+## The Arc runtime is the host's, too
+
+`@cratis/arc` and `@cratis/arc.react` are peer dependencies of `@cratis/components` that the host supplies,
+and a design surface is not a host. Every Arc-bound adapter therefore reaches them through a dynamic
+`import()`. A page built only from the Arc-free composites never pulls the Arc client in at all, and an
+unbound table never even reaches the import.
+
+When a binding *is* registered and the client is not installed, the chunk fails to load and the
+`ArcRuntimeBoundary` around it shows the library's own error boundary — one dashed-out region, not a blank
+screen. That is exactly what this package's Storybook shows in its `Bound` story, and it is honest rather
+than hidden: it is what a real host without Arc installed sees.
+
+## Where to go next
+
+- [The binding registry](../components-package/binding-registry.md) — the mechanism, in full.
+- [The template catalogue](template-catalogue.md) — which composite sits in which slot of which template.
+- [Add a template of your own](add-a-template.md) — how to carry a binding name in a template you write.
diff --git a/Documentation/blueprints/composing-screens.md b/Documentation/blueprints/composing-screens.md
new file mode 100644
index 0000000..42d1a32
--- /dev/null
+++ b/Documentation/blueprints/composing-screens.md
@@ -0,0 +1,107 @@
+---
+title: Composing screens from templates
+description: How a nested chain of screen templates is folded and placed into a layout's slots, and how an arrangement changes with the size class.
+---
+
+[Layouts](./layouts.md) and [screen templates](./screen-templates.md) explain what the two structures are
+and how `FitsSlot` makes them nest. This page is about what happens when something actually places one -
+the step between "this template fits the content slot" and "here is a screen on the screen".
+
+It matters because the obvious implementation is wrong in a way that produces no error at all.
+
+## Placing a single template
+
+A template that fits the layout's own `content` slot is the easy case. Its content is filed under its own
+slot names, and all of it flows into `content`.
+
+One nuance decides where each piece lands:
+
+```ts
+const target = layoutSlots.has(slotName) ? slotName : template.fitsSlot;
+```
+
+Content filed under a name the **layout** also declares stays under that name; everything else flows into
+the slot `fitsSlot` names.
+
+That first branch is not a special case, it is a feature. The `Login` template declares an `aside` slot, and
+so does the `FullPage` layout - so the branding half of a sign-in screen reaches the layout's `aside` region
+rather than being buried inside the form column. A template can reach a layout region directly by naming it.
+
+## Placing a chain
+
+Now the case that is easy to get wrong. `SliceSection` fits `FeatureSection`'s `body`, which fits
+`ModuleWorkspace`'s `body`, which fits the layout's `content`.
+
+The tempting implementation is to file the slice template's content under its own `fitsSlot` - `body` - at
+the top level. It reads correctly and it is wrong: the `AppShell` layout has no `body` slot, so the content
+lands in a region nothing renders. The screen comes up empty, no exception is thrown, and nothing points
+back at the cause.
+
+:::caution
+This is not hypothetical. It is the bug a spec over the gallery caught while the default blueprint was
+being written, and it is exactly why "every screen fills only slots its layout declares" is worth a spec.
+:::
+
+The correct move is to fold the chain from the inside out: place the slice in the feature, place that
+result in the module, and hand the module to the layout.
+
+```ts
+export function nestScreenTemplates(chain: ScreenTemplate[]): ScreenTemplate {
+ return chain.reduceRight((inner, outer) => (inner === outer ? outer : placeInside(outer, inner)));
+}
+```
+
+`placeInside` appends the inner template's content - already folded, so it carries everything below it -
+to the outer template's content under the inner one's `fitsSlot`. The result has the outermost template's
+slots and `fitsSlot`, so it is placeable in the layout by the ordinary single-template rule.
+
+Because the fold is one operation applied repeatedly, the chain can be any depth. A chain of one is
+returned unchanged, which is why almost every template pays nothing for a mechanism it does not use.
+
+## From a screen to an element tree
+
+A `Screen`'s `slotContent` is keyed by the layout's slot names, and a shell component's slots use the same
+vocabulary. So rendering is a one-for-one handover rather than a translation:
+
+```ts
+export function composeScreenElement(screen: Screen): ExternalComponent {
+ const shell = shellComponentForLayout(screen.layout);
+ if (!shell) {
+ throw new Error(`Screen '${screen.name}' names the layout '${screen.layout}', which this blueprint does not provide.`);
+ }
+
+ return externalComponent(`screen-${screen.name}`, shell, { screenName: screen.name }, screen.slotContent);
+}
+```
+
+Sharing one vocabulary across the layout definition, the templates and the shell components is the point.
+A slot filled under a name the shell never reads renders nothing *and reports nothing*, so the names are
+one enum and the rest is covered by specs.
+
+## What the size class does
+
+A layout's arrangement is a `FlowArrangement`: a tree whose leaves reference slots by name, plus overrides
+targeting a width size class, a height size class, or both. `evaluateFlowArrangement` picks the most
+specific match — an override naming both axes beats one naming a single axis.
+
+The `AppShell` layout declares three:
+
+| Override | What leaves the flow | Why |
+| --- | --- | --- |
+| Compact width | sidebar, right panel | There is no width at which an 18rem panel and a 20rem panel both fit beside content on a phone |
+| Compact height | breadcrumb, footer | A landscape phone has almost no vertical room, and two horizontal strips of chrome eat most of it |
+| Compact width **and** height | all four | The most specific match, so a phone in landscape gets its own answer |
+
+That third one is not redundant. Without it, a landscape phone would match both single-axis overrides,
+`evaluateFlowArrangement` would pick whichever was declared last, and the screen would keep a footer it has
+no room for — or a sidebar it has no width for, depending on declaration order. Targeting both axes is how
+you stop the answer depending on the order you happened to write them in.
+
+Leaving the flow is not the same as not being rendered. At a compact width the sidebar is still there —
+off-canvas, over the content, behind the mask — it simply no longer *occupies* anything. That is a fact
+about the arrangement, and the shell's [layout modes](./layout-modes.md) are what act on it.
+
+## Next
+
+- [Regions and slots](./regions-and-slots.md) — the region vocabulary this is arranging.
+- [The template set](./template-set.md) — the twenty-three templates, including the three-level chain.
diff --git a/Documentation/blueprints/getting-started.md b/Documentation/blueprints/getting-started.md
new file mode 100644
index 0000000..be5d708
--- /dev/null
+++ b/Documentation/blueprints/getting-started.md
@@ -0,0 +1,115 @@
+---
+title: Use the default blueprint
+description: Render a screen inside the application shell, switch its layout mode, and switch its theme - in about ten minutes.
+---
+
+By the end of this you will have the Cratis default blueprint rendering a real screen in a real
+application shell, with a working menu, a configurator that switches between eight layout modes, and a
+theme that swaps live without a reload.
+
+You need a React application, `@cratis/scene.react`, and the blueprint itself.
+
+## Install and load the stylesheet
+
+The blueprint's shell is hand-written CSS - PrimeReact 10 has no application-shell primitive to lean on -
+so the stylesheet has to be loaded once, at your entry point, along with PrimeReact's own.
+
+```ts
+import 'primereact/resources/primereact.min.css';
+import 'primereact/resources/themes/lara-light-indigo/theme.css';
+import 'primeicons/primeicons.css';
+import '@cratis/scene.blueprint.default/styles';
+```
+
+Load it once and never again. Every mode, every rail width and every transition in the shell comes from
+this file; without it you get an unstyled column of regions and no clue why.
+
+## Boot a gallery screen
+
+The fastest way to see the whole thing working is to render one of the screens the blueprint already
+ships. They are real `Screen` instances - not pictures - so this is the actual rendering path, not a demo
+one.
+
+```tsx
+import { GalleryScreenPreview } from '@cratis/scene.blueprint.default';
+
+export function App() {
+ return ;
+}
+```
+
+Open it. You have a fixed topbar, a docked sidebar with three menu sections, a breadcrumb, a dashboard of
+four stat cards over two columns of widgets, a footer, and a cog button floating against the right edge.
+
+What happened under the hood: `composeScreenElement` turned the screen into an `ExternalComponent` tree
+whose slots are the layout's slots, `resolveComponentName` decided which package owns each bare component
+name, and the real `SceneElementView` rendered the result against the real registry. Nothing on that path
+exists only for previews.
+
+:::note
+Widgets whose names belong to packages you have not loaded render as a dashed red box naming what is
+missing. That is deliberate - a blueprint previewed against half a profile should look visibly incomplete
+rather than quietly wrong.
+:::
+
+## Switch the layout mode
+
+Click the cog. The configurator has four axes; the second is **Menu mode**. Click **Slim** and the sidebar
+becomes a 5rem rail of circular icons with the content sliding in behind it. Click **Horizontal** and the
+sidebar stops being a sidebar - it flows into the topbar as a row, and submenus drop down instead of
+popping out sideways.
+
+Nothing about the screen changed. The only thing that changed is one class on the wrapper element, which
+is the whole point of the mode vocabulary: a screen cannot be written for one mode and broken in another.
+
+To start in a particular mode rather than clicking to it:
+
+```tsx
+import { GalleryScreenPreview, LayoutMode } from '@cratis/scene.blueprint.default';
+
+;
+```
+
+Now narrow the browser window past 991px. Every mode collapses to off-canvas, the mask appears behind the
+sidebar, and the mode buttons grey out with a sentence saying why. Widen it again and your chosen mode is
+back - it was kept, not overwritten.
+
+## Switch the theme
+
+Still in the configurator, the **Theme** section offers the two themes the blueprint ships. Click
+**Scene Default Dark**.
+
+The page changes color and nothing remounts. `SceneThemeProvider` writes the new token values onto the
+element that is already there, so scroll position, an open menu, and anything typed into a form all
+survive. That is what "live re-resolution" means, and it is why theme switching is fast enough to be a
+thing people actually do rather than a thing they do once.
+
+## Render your own screen instead
+
+The gallery is a starting point, not the destination. A screen of your own names the layout, names the
+template whose shape it fills, and provides the content:
+
+```ts
+import { Screen } from '@cratis/scene.model';
+import { LayoutName } from '@cratis/scene.blueprint.default';
+
+const invoices: Screen = {
+ name: 'Invoices',
+ layout: LayoutName.AppShell,
+ screenTemplate: 'CrudList',
+ slotContent: {},
+ forms: [],
+ contributions: [],
+};
+```
+
+Fill `slotContent` with the shell chrome and your content, keyed by the layout's slot names - `topbar`,
+`sidebar`, `menu`, `breadcrumb`, `content`, `footer`, `configPanel`. The
+[regions and slots reference](./regions-and-slots.md) lists all of them.
+
+## Where to go next
+
+- [Screen and dialog templates](./screen-templates.md) - the model behind what you just rendered, and how
+ templates nest.
+- [The template set](./template-set.md) - the other twenty-two shapes you did not have to build.
+- [Ship your own blueprint](./ship-your-own-blueprint.md) - when the default one is not the look you want.
diff --git a/Documentation/blueprints/index.md b/Documentation/blueprints/index.md
new file mode 100644
index 0000000..bf76b32
--- /dev/null
+++ b/Documentation/blueprints/index.md
@@ -0,0 +1,68 @@
+# Blueprints
+
+A blueprint is a package that ships the shape of an application: its layouts, the screen and dialog templates
+built on them, and the components that fill their slots.
+
+An application selects **one** blueprint. That is the point — a blueprint is a coherent set, designed
+together, rather than a layout from one place and templates from another that happen not to clash. In the
+application's settings you pick a blueprint, and everything it holds becomes available.
+
+A blueprint declares the component libraries it is built from, like any other package:
+
+```csharp
+new ScenePackage(
+ Name: "Cratis.Blueprint.Default",
+ Version: "1.0.0",
+ Kind: PackageKind.Blueprint,
+ Dependencies: [new PackageDependency("PrimeReact"), new PackageDependency("Cratis.Components")],
+ Components: ["appShell", "topbar", "sidebar", /* ... */],
+ Layouts: ["AppShell", "FullPage"],
+ ScreenTemplates: ["ModuleWorkspace", "FeatureList", /* ... */],
+ DialogTemplates: ["Confirm", /* ... */],
+ Themes: ["Daylight", "Midnight"]);
+```
+
+Because those dependencies are declared, "which blueprints can I use" is answerable from the packages a
+profile already has, rather than being something you find out by trying one.
+
+## Layout, template, screen
+
+These three are easy to run together and mean different things.
+
+A **[layout](layouts.md)** is the application's base navigational look — the shell, with its top bar,
+navigation and content region. An application has one.
+
+A **[screen template](screen-templates.md)** is a reusable shape that goes *inside* that shell, at module,
+feature or slice level. An application has many. Each declares which of its parent's slots it fills, so a
+module's template fits the layout, a feature's template fits the module's, and a slice's fits the feature's —
+the same rule at every level, nesting arbitrarily deep without a second mechanism.
+
+A **dialog template** is the same idea for content that opens *over* the application. It declares no parent
+slot, because it occupies none: a dialog is summoned, not placed.
+
+A **screen** is an instance. It names the structure it fills and supplies the content.
+
+Layouts and screen templates are structurally alike on purpose — both are slots plus an arrangement,
+evaluated by the same engine. They differ in role, and a screen template additionally says where it belongs.
+
+## What a blueprint is not
+
+A blueprint is a **packaged artifact, not a language construct**. It never appears in a `.play` file. A
+`ui profile` lists it by name in `packages` like anything else; the layouts and templates it provides are
+then resolvable by name, exactly as its components are.
+
+## The default blueprint
+
+`@cratis/scene.blueprint.default` is the one you get for free: two layouts, eight menu modes, twenty-three
+screen templates, three dialog templates and two themes.
+
+- **[Use the default blueprint](getting-started.md)** — render a screen, switch its mode, switch its theme.
+- **[Understanding blueprints](understanding-blueprints.md)** — how a blueprint differs from a component
+ library, and when it is the wrong fit.
+- **[Composing screens from templates](composing-screens.md)** — how a nested template chain is placed, and
+ what the size class does to an arrangement.
+- **[Regions and slots](regions-and-slots.md)** — every region the two layouts expose.
+- **[Layout modes](layout-modes.md)** — all eight modes, and the mobile regime nobody chooses.
+- **[The template set](template-set.md)** — every screen and dialog template it ships.
+- **[Theme tokens](theme-tokens.md)** — the thirteen shared tokens, and every theme's attribution.
+- **[Ship your own blueprint](ship-your-own-blueprint.md)** — when the default is not your look.
diff --git a/Documentation/blueprints/layout-modes.md b/Documentation/blueprints/layout-modes.md
new file mode 100644
index 0000000..8522198
--- /dev/null
+++ b/Documentation/blueprints/layout-modes.md
@@ -0,0 +1,127 @@
+---
+title: Layout modes
+description: All eight menu modes the default blueprint implements, the classes they map to, when to choose each, and the mobile regime nobody chooses.
+---
+
+The application shell has eight menu modes. Each is a `LayoutMode` value, each maps to one wrapper class,
+and the class vocabulary is PrimeTek's - unchanged, so anyone who has themed a PrimeReact application
+recognizes it.
+
+## The modes
+
+| Mode | `LayoutMode` | Wrapper class | Sidebar width | Choose it when |
+| --- | --- | --- | --- | --- |
+| Static | `Static` | `layout-static` | 18rem | Navigation is used constantly and screens are wide. The default. |
+| Overlay | `Overlay` | `layout-overlay` | 18rem, off-canvas | Content needs the full width and navigation is occasional. |
+| Slim | `Slim` | `layout-slim` | 5rem | The icons are learnable and every pixel of width matters. |
+| Slim+ | `SlimPlus` | `layout-slim-plus` | 7rem | Same, but the icons are not self-explanatory enough to go unlabeled. |
+| Compact | `Compact` | `layout-compact` | 5rem | The slim rail, with square buttons and a topbar shifted by the rail. |
+| Horizontal | `Horizontal` | `layout-horizontal` | none | Few top-level areas, and vertical space is the scarce one. |
+| Reveal | `Reveal` | `layout-reveal` | 4.25rem strip → 18rem | Full labels on demand without permanently paying for them. |
+| Drawer | `Drawer` | `layout-drawer` | 5.25rem → 18rem | Same as reveal, but the content should never be covered mid-hover. |
+
+## What each one actually does
+
+**Static** docks the sidebar and gives the content a matching `margin-left`. The topbar toggle slides the
+sidebar out and drops the margin. Content is pushed, never covered.
+
+**Overlay** parks the sidebar off-canvas. Opening it floats it over the content at a high z-index with a
+`.layout-mask` scrim behind it; clicking the scrim closes it.
+
+**Slim** collapses the sidebar to an icon-only rail with root items as circular buttons. Submenus pop out
+as a floating panel anchored at the rail's width.
+
+**Slim+** is the same idea at 7rem, with each icon's label stacked directly beneath it, and the submenu
+popup anchored at 7rem.
+
+**Compact** is the 5rem icon rail again with square rather than circular buttons, and it also shifts the
+topbar by the rail width - so the brand sits beside the rail rather than over it.
+
+**Horizontal** makes the sidebar `position: static` and flows it into the topbar strip as a nowrap row.
+Submenus become absolute drop-downs, and the content margin goes to zero.
+
+**Reveal** translates the full panel off-left, leaving a 4.25rem strip of icons. Hovering slides the whole
+panel in *over* the content. The pin button anchors it open, which switches it from covering the content to
+pushing it out to the full 18rem.
+
+**Drawer** is the same interaction with a different mechanic: a collapsed 5.25rem rail that *animates its
+width* to full on hover. Reveal slides; drawer grows. It pins the same way.
+
+## State classes
+
+Modes are not the only thing on the wrapper. `layoutWrapperClasses` also emits:
+
+| Class | Present when |
+| --- | --- |
+| `layout-static-inactive` | static, and the sidebar has been toggled closed |
+| `layout-overlay-active` | overlay, the sidebar is open, and the viewport is not mobile |
+| `layout-sidebar-active` | reveal or drawer, and the sidebar is currently out |
+| `layout-sidebar-anchored` | reveal or drawer, and the sidebar is pinned |
+| `layout-mobile` | the viewport is at or below the breakpoint |
+| `layout-mobile-active` | mobile, and the sidebar has been opened |
+| `layout-menu-light` / `-dark` / `-primary` | the chosen menu tint |
+| `layout-color-scheme-light` / `-dark` | the chosen color scheme |
+
+The mode class always reflects the **effective** mode, so the off-canvas rules a phone needs come from the
+same `layout-overlay` block a desktop overlay uses rather than a parallel mobile-only ruleset. The mode the
+user actually chose is emitted separately as `data-layout-mode`, so a configurator can show it even while
+the viewport overrides it.
+
+## Mobile is not a mode
+
+At or below **991px** every mode renders off-canvas. This is not user-selectable and never appears in the
+picker as a ninth option.
+
+```ts
+export function effectiveLayoutMode(state: LayoutConfigState): LayoutMode {
+ return state.isMobile ? LayoutMode.Overlay : state.mode;
+}
+```
+
+Deriving it rather than overwriting `mode` is what lets the chosen mode come back untouched when the
+viewport grows again. The mode picker disables its buttons and says so in a sentence rather than
+disappearing - a control that vanishes reads as a bug; a disabled control with an explanation reads as a
+decision.
+
+The breakpoint is exported as `mobileBreakpoint`, and the media query as `mobileMediaQuery`, so the resize
+listener and the stylesheet cannot disagree about where the boundary is.
+
+## Choosing the mode at runtime
+
+The mode lives in one place - `LayoutConfigState` - behind one provider and one hook.
+
+```tsx
+import { LayoutConfigProvider, LayoutMode, useLayoutConfig } from '@cratis/scene.blueprint.default';
+
+function ModeButton() {
+ const { config, setMode } = useLayoutConfig();
+ return (
+
+ );
+}
+```
+
+Wrapping the shell in `` is optional - the shell puts one around itself when a host
+has not, because a gallery preview hands the renderer one `appShell` element and nothing else. Provide one
+yourself when a control outside the shell needs to drive it.
+
+## What is remembered
+
+Mode, menu theme, color scheme, theme name and the pin persist to `localStorage` under
+`cratis.scene.blueprint.default`.
+
+Whether the sidebar happened to be open, whether the pointer was over it, and whether the viewport was
+narrow are deliberately **not** persisted. Those are facts about a moment rather than preferences, and
+restoring them produces a shell that opens in a state nobody chose.
+
+Everything read back is validated against the enums rather than trusted. Storage is shared with every other
+script on the origin and outlives the version of the package that wrote it, and an unrecognized mode left
+in place would put a class on the wrapper that no rule matches - a shell with no sidebar at all, and no
+error to explain it.
+
+## Next
+
+- [Regions and slots](./regions-and-slots.md) - what the modes are moving around.
+- [Theme tokens](./theme-tokens.md) - how the modes get their colors.
diff --git a/Documentation/blueprints/layouts.md b/Documentation/blueprints/layouts.md
new file mode 100644
index 0000000..2963df6
--- /dev/null
+++ b/Documentation/blueprints/layouts.md
@@ -0,0 +1,82 @@
+---
+title: Layouts
+description: An application's base navigational shell - its slots, how they are arranged, and how a layout differs from a screen template.
+---
+
+A layout is an application's base navigational look: the shell everything else renders inside.
+
+```csharp
+public record Layout(string Name, IReadOnlyList Slots, Arrangement? Arrangement = null);
+```
+
+Three things, and the third is optional. `Slots` are the named regions the shell offers — a top bar, a
+navigation area, a content region, a footer. `Arrangement` says how those slots sit relative to each other.
+With no arrangement, they are simply in declaration order.
+
+An application has **one** layout in force, and selects it — usually from a [blueprint](index.md).
+
+## Slots
+
+```csharp
+public record Slot(string Name, Arrangement? Arrangement = null);
+```
+
+A slot's own `Arrangement` is a different thing from the layout's, and the distinction matters:
+
+- The **layout's** arrangement positions the slots relative to each other. Its flow leaves are
+ `FlowSlotLeaf`, which reference a slot by name.
+- A **slot's** arrangement positions the content filling that one slot. Its flow leaves are `FlowLeaf`, which
+ carry a real element.
+
+A layout is not uniformly one arrangement mode. Flow for most slots and freeform for one is a valid
+combination, and the engine evaluates each independently.
+
+## Arrangement
+
+Two modes, both evaluated by `Cratis.Scene.Engine.Layouts` and its TypeScript twin.
+
+**Flow** is a tree of rows, columns and grids, with size-class overrides:
+
+```csharp
+new FlowArrangement(
+ Root: new FlowColumn
+ {
+ Children =
+ [
+ new FlowSlotLeaf("topbar"),
+ new FlowRow { Children = [new FlowSlotLeaf("sidebar"), new FlowSlotLeaf("content") { Grow = 1 }] },
+ new FlowSlotLeaf("footer")
+ ]
+ },
+ Overrides:
+ [
+ new FlowOverride(
+ Width: WidthSizeClass.Compact,
+ Height: null,
+ Root: new FlowColumn { Children = [new FlowSlotLeaf("topbar"), new FlowSlotLeaf("content"), new FlowSlotLeaf("footer")] })
+ ]);
+```
+
+The override drops the sidebar out of the flow on a compact width. `Width` and `Height` are independently
+nullable, so an override can key on either axis or both. When more than one override matches a concrete size
+class, the most specific wins — both dimensions beats one — and among equally specific matches, the last
+declared wins.
+
+**Freeform** is one variant per size-class combination, each placing slots at explicit coordinates. Selection
+is exact-match only: a size class with no matching variant returns nothing rather than falling back to a
+variant that was never designed for it. That is deliberate — "warn, don't silently pick".
+
+## Size classes
+
+Size classes are named, not pixel breakpoints: `Compact` or `Regular` on each of width and height. A narrow
+browser window and a phone in portrait are the *same* class, which is what lets one layout describe both.
+
+`SizeClassCalculator.Compute` (C#) and `computeSizeClass` (TypeScript) own the conversion from a real size,
+with a 600dip default breakpoint per axis. It lives in the engine, shared by every renderer, so a React
+renderer and a future native one agree exactly on when a boundary is crossed.
+
+## Layouts and screen templates
+
+A [screen template](screen-templates.md) has the same shape — slots plus an arrangement — and is evaluated by
+the same engine. The difference is role and one field: a screen template also declares which of its parent's
+slots it fills, and an application has many of them, where it has one layout.
diff --git a/Documentation/blueprints/regions-and-slots.md b/Documentation/blueprints/regions-and-slots.md
new file mode 100644
index 0000000..d6c6e19
--- /dev/null
+++ b/Documentation/blueprints/regions-and-slots.md
@@ -0,0 +1,83 @@
+---
+title: Regions and slots
+description: Every region the default blueprint's two layouts expose, what fills it, and what happens to it at each size class.
+---
+
+The default blueprint exposes nine regions across two layouts. A screen fills them by slot name; the shell
+components read the same names.
+
+## The AppShell layout
+
+| Slot | Region | Filled with | Rendered when |
+| --- | --- | --- | --- |
+| `topbar` | the fixed strip across the top | `topbar` | it has content |
+| `sidebar` | the sidebar's own chrome, inside the panel the shell positions | `sidebar` | it or `menu` has content |
+| `menu` | the navigation itself | one or more `menu` | it or `sidebar` has content |
+| `breadcrumb` | the trail above the content | `breadcrumb` | it has content |
+| `content` | the screen | a screen template's content | always |
+| `footer` | the strip below the content | `footer` | it has content |
+| `rightPanel` | the inspector column down the right edge | `rightPanel` | it has content |
+| `configPanel` | the floating configurator | `configPanel` | it has content |
+
+## The FullPage layout
+
+| Slot | Region | Filled with |
+| --- | --- | --- |
+| `aside` | the branding half of the split | any content |
+| `content` | the form, message or hero | any content |
+| `configPanel` | the floating configurator | `configPanel` |
+
+## Why two layouts and not one
+
+Sign-in, register, forgotten password, new password, verification, lock, error, access-denied, not-found
+and landing screens have no navigation state, no sidebar to remember and no breadcrumb to place. Hanging
+them off the application shell would mean every one of the [eight layout modes](./layout-modes.md) needs an
+answer for a page that has no menu.
+
+The split is structural in every PrimeTek template for the same reason. What survives into the full-page
+layout is the configurator, because a sign-in page still has to honor the chosen theme - it is very often
+the first page anyone sees.
+
+## Where the regions come from
+
+Sakai, PrimeTek's free template, establishes topbar, sidebar, menu, content and footer. The premium line -
+Diamond, Atlantis, Freya, Apollo, Ultima, Avalon, Verona - adds the breadcrumb and a right panel. Both sets
+are exposed here, because a blueprint covering only the free template's regions forces a fork on anyone who
+wants the others.
+
+`aside` is not from that line; it is the branding half of the split every premium sign-in page uses, made a
+slot rather than something a screen paints inside `content`.
+
+## What the size class does to them
+
+The `AppShell` arrangement declares three overrides, and `evaluateFlowArrangement` picks the most specific
+match.
+
+| Size class | Regions in the flow |
+| --- | --- |
+| Regular width, regular height | all eight |
+| Compact width | topbar, breadcrumb, content, footer, configPanel |
+| Compact height | topbar, sidebar, menu, content, rightPanel, configPanel |
+| Compact width **and** height | topbar, content, configPanel |
+
+At a compact width the sidebar leaves the *flow* - it is still rendered, off-canvas, over the content,
+behind the mask, but it no longer occupies anything. That is a fact about the arrangement, not about CSS.
+
+The `FullPage` arrangement has one override: at a compact width the branding aside drops out entirely
+rather than stacking above the form. A sign-in form pushed below the fold by decoration is the worst
+possible first screen.
+
+## One vocabulary, three consumers
+
+`SlotName` is a single enum used by the layout definitions, the screen templates and the shell components.
+That is deliberate: a slot filled under a name the shell never reads renders nothing *and reports nothing*.
+Sharing the enum makes that a compile error where it can be, and the blueprint's specs cover the rest -
+every gallery screen is checked against the slots its layout actually declares.
+
+## Nested slot names
+
+A screen template declares slots of its own, from a separate vocabulary (`TemplateSlotName`): `header`,
+`body`, `sidePanel`, `toolbar`, `actions`, `stats`, `primary`, `secondary`.
+
+They never collide with the layout's names, because a template's `fitsSlot` is resolved against its direct
+parent rather than globally - see [composing screens from templates](./composing-screens.md).
diff --git a/Documentation/blueprints/screen-templates.md b/Documentation/blueprints/screen-templates.md
new file mode 100644
index 0000000..64d7a80
--- /dev/null
+++ b/Documentation/blueprints/screen-templates.md
@@ -0,0 +1,114 @@
+---
+title: Screen and dialog templates
+description: The reusable shapes that go inside a layout - how fitsSlot composes them into a tree, and how a screen instantiates one.
+---
+
+A [layout](layouts.md) is the shell. A screen template is a reusable shape that goes *inside* it.
+
+```csharp
+public record ScreenTemplate(
+ string Name,
+ string? FitsSlot,
+ IReadOnlyList Slots,
+ Arrangement? Arrangement = null,
+ IReadOnlyDictionary>? Content = null,
+ string? DisplayName = null,
+ string? Description = null);
+```
+
+An application has one layout and many screen templates — typically one per module, feature or slice that
+needs a shape of its own.
+
+## `FitsSlot` is what makes them nest
+
+A template states where it belongs. It is not told by whatever happens to host it:
+
+```text
+AppShell (layout)
+ slots: topbar, sidebar, content, footer
+ │
+ └── ModuleWorkspace (screen template)
+ fitsSlot: "content"
+ slots: moduleNav, moduleContent
+ │
+ └── FeatureList (screen template)
+ fitsSlot: "moduleContent"
+ slots: list, details
+ │
+ └── SliceDetail (screen template)
+ fitsSlot: "details"
+```
+
+A module's template fits a slot on the application layout. A feature's template fits a slot the module's
+template declares. A slice's fits one the feature's declares. **The same rule at every level** — there is no
+separate mechanism for "module-level" versus "feature-level" nesting, and no depth limit falls out of the
+design.
+
+`FitsSlot` is nullable for a template placed explicitly rather than by declaration.
+
+## Qualifying a slot when the name is not enough
+
+`body` is a good name for a slot at every level of a chain, so several templates legitimately declare one.
+A bare `body` then has no single answer, and resolution reports it as unplaced with the candidates rather
+than guessing — putting a template in the wrong parent renders content in the wrong region, which is far
+harder to diagnose than being told the name is ambiguous.
+
+Qualify it with the container to settle it:
+
+```csharp
+new ScreenTemplate("FeatureSection", "ModuleWorkspace.body", [new Slot("body")]);
+new ScreenTemplate("SliceSection", "FeatureSection.body", []);
+```
+
+This is the same rule component names use: a bare name searches, a qualified one goes straight to what it
+names. Everything before the last `.` is the container, everything after is the slot. A qualifier naming a
+container that does not declare that slot is unplaced too — it is never quietly downgraded to a search.
+
+Note that a bare name only becomes ambiguous once *more than one other* container declares it. A template
+never competes with itself, so a two-level chain where both levels declare `body` still resolves.
+
+## Slots and content
+
+`Slots` are what this template offers to whatever it contains — the next level down. `Content` is what the
+template brings with it: the chrome that is part of the template rather than part of any screen based on it.
+A template with an empty `Content` is purely structural.
+
+That split is what makes a template reusable. Two features can share `FeatureList` and get the same
+structure, header and toolbar, while each supplies its own list and detail content.
+
+## Screens
+
+A screen is the instance:
+
+```csharp
+public record Screen(
+ string Name,
+ string Layout,
+ IReadOnlyDictionary> SlotContent,
+ IReadOnlyList