Apps
Apps
An app is the Svelte page operators actually use. Apps are discovered from src/apps/**/+<lower_snake_case>.svelte , each filename is the canonical app ID, and each compiles into the client bundle with the signed-in user’s policy context.
Source layout
src/apps/
├── +operations.svelte
└── finance/
├── +group.ts
└── +payroll.svelte Group metadata is optional. A group directory owns the group ID; each app filename owns its app ID. No registration module is authored.
Static metadata
Each app declares literal metadata in <svelte:head> :
<svelte:head>
<title>Operations</title>
<meta name="description" content="Manage daily operations" />
<meta name="bolt:icon" content="lucide:briefcase" />
<meta name="bolt:thumbnail" content="https://cdn.example.com/operations-card.webp" />
<meta name="bolt:banner" content="https://cdn.example.com/operations-banner.webp" />
<meta name="bolt:kiosk" content="true" />
</svelte:head> The optional bolt:thumbnail image appears in the root app gallery. The optional bolt:banner image appears above the app as its media header. Both values must be static URLs. bolt:kiosk makes the app full-screen — no sidebar, finder, agent trigger, or banner. Identity may also be declared on <AppShell> (icon, title, description, banner, thumbnail), which carries translated strings and emits the same head tags.
Composition
The Bolt shell owns the app region and document scroll, and renders the app’s translated heading from its declared identity — an app should not repeat the heading inside its body. Compose app bodies with PageHeader and the layout primitives: Stack , Inline , Cluster , Split , Grid , Columns , Cover , Center , and Frame . Every app follows the same body contract — one Cover , one body region, one scroll owner. See Layout for the full rules; they are the authoring contract every surface is reviewed against.
Live data in apps
Reads in tenant apps go through the live data layer — client.db queries are live over the sync HTTP/SSE session, not one-off fetches. CollectionTable and custom views inherit that behavior: filters, sorts, and pagination stay reactive without cache invalidation in app code.
Client access
<script lang="ts">
import { client } from '$bolt/client';
import { getCollectionClientForSurface } from '@norbital-ai/ui/collection-runtime';
import { CollectionTable } from '@norbital-ai/ui/collection-table';
import { Bound, Cover } from '@norbital-ai/ui/layout';
const workspaceClient = getCollectionClientForSurface(client, 'sites');
</script>
<svelte:head>
<title>Operations</title>
<meta name="bolt:icon" content="lucide:briefcase" />
</svelte:head>
<Cover as="main">
<Bound size="full" inset>
<CollectionTable client={workspaceClient} collection="sites">
{#snippet columns({ Column })}
<Column name="name" />
<Column name="client_name" />
<Column name="house_type" />
{/snippet}
</CollectionTable>
</Bound>
</Cover> Every CollectionTable requires an explicit {#snippet columns({ Column })} — table UI does not auto-derive columns from the model. Wire the client with getCollectionClientForSurface(client, …) and pass it as the client prop. One collection-owned +representation.svelte overrides them only when needed. See UI components .
Keep server-only behavior in write contracts, pipelines, integrations, automations, or functions. App access is filtered by team policy, and collection operations use the same policy enforcement as other clients.