Apps
Apps
Tenant apps are Svelte components discovered from src/apps/**/+<lower_snake_case>.svelte. Each filename is the canonical app ID.
Apps compile into the client bundle and render 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="pod:icon" content="lucide:briefcase" />
<meta name="pod:thumbnail" content="https://cdn.example.com/operations-card.webp" />
<meta name="pod:banner" content="https://cdn.example.com/operations-banner.webp" />
</svelte:head> The optional pod:thumbnail image appears in the root app gallery. The optional pod:banner image appears above the app and scrolls with the app surface. Both values must
be static URLs.
Composition
The Pod shell owns the app region and document scroll. Compose app bodies with PageHeader and the layout primitives: Stack, Inline, Cluster, Split, Grid, Columns, Cover, Center, and Frame. Create a local scroll region only with an explicit Bound and Scroll pair.
Client access
<script lang="ts">
import { CollectionTable } from '@norbital-ai/ui/collection-table';
import { Stack } from '@norbital-ai/ui/layout';
import { PageHeader } from '@norbital-ai/ui/page-header';
</script>
<svelte:head>
<title>Operations</title>
<meta name="pod:icon" content="lucide:briefcase" />
</svelte:head>
<Stack gap="lg">
<PageHeader title="Operations" description="Manage daily operations." />
<CollectionTable collection="sites">
{#snippet columns({ Column })}
<Column name="name" />
<Column name="client_name" />
<Column name="house_type" />
{/snippet}
</CollectionTable>
</Stack> Every CollectionTable requires an explicit {#snippet columns({ Column })} — table UI does not auto-derive columns from the model. Schema-derived create and record forms are the
default; one collection-owned +representation.svelte overrides create, display, and
edit only when needed. It branches on a nullable record prop and imports generated RepresentationProps from its adjacent ./$types.js.
Keep server-only behavior in hooks, pipelines, integrations, automations, or remotes. App access is filtered by team policy, and collection operations use the same policy enforcement as other clients.