From one view to a complete page
Run one component, a three-output workspace, and a registered page through the same application facade.
Use one mounted Region for a result view, a workspace, or an entire registered page. The host supplies the allowed goals, data, views, and layout patterns. The same app.render call evaluates and commits each request.
Varendo 0.1.0 is the published stable release. Install all Varendo packages at the same exact version.
Try the progression#
Open the Playground and choose Browse people in the People scenario, then Analytical workspace and Entire page in Guided demos. The workspace joins a summary, a daily trend, and an employee breakdown. The page adds a registered header and navigation around that workspace. Open Inspect to see the task, result outputs, and presentation nodes.
| Level | What the host registers | What the Region displays |
|---|---|---|
| Component | A resource and its allowed views | One table, card collection, form, or other eligible view |
| Workspace | A goal with several required outputs and a matching pattern | One validated tree containing summary, trend, and breakdown |
| Page | The workspace plus structural views and a page pattern | Header, navigation, and workspace in one validated tree |
A Region can fill the page content area. Your application still owns the URL, authentication, navigation destinations, and effects. The example below uses synthetic attendance records and makes no model call.
1. Create the local project#
Create an empty Vite project and install matching package versions:
mkdir attendance-ui
cd attendance-ui
npm init -y
npm pkg set type=module scripts.dev=vite
npm install --save-dev --save-exact vite@8.2.2 typescript@7.0.2
npm install --save-exact @varendo/core@0.1.0 @varendo/runtime@0.1.0 @varendo/web@0.1.0 lit@3.3.3 zod@4.5.4
mkdir src
The complete source entry lives in examples/vnext/page-goal/ in the repository.
The package versions shown here must be available on npm before this installation works. The Playground also runs directly from the repository while a release is being prepared.
2. Register the attendance goal#
Create src/goal.ts from this complete source. It registers the attendance resource, the reviewed attendance.present-total measure, and attendance.overview. The custom request accepts exactly { team: 'Engineering' }.
The compiler declares three required outputs: summary, trend, and breakdown. Each output groups the same authorized observations at its declared grain. Two Engineering observations produce a summary of 2 present employee-days, daily values of 1 and 1, and per-person values of 1 for Ada and Sam. This fixture does not calculate unique headcount or infer attendance anomalies.
import { z } from 'zod';
import { type PresentationPlan, type QuerySpec, type ResultRef, type Task } from '@varendo/core';
import { createIntentCompilerRegistry } from '@varendo/core/app';
import { defineDataFeature } from '@varendo/core/features';
import type { PresentationPatternManifest } from '@varendo/core/presentation';
export const REGION = 'attendance-workspace';
export const GOAL = { id: 'attendance.overview', revision: '1' } as const;
export const PATTERN = { id: 'attendance.overview-pattern', revision: '1' } as const;
export const METRIC = { id: 'attendance.present-total', revision: '1' } as const;
const ANALYZE = { id: 'data.analyze', revision: '1' } as const;
const READ = { id: 'data.read', revision: '1' } as const;
export const ROLES = ['summary', 'trend', 'breakdown'] as const;
export const REF = {
workspace: { id: 'layout.stack', revision: '1' },
summary: { id: 'data.metric', revision: '1' },
trend: { id: 'data.trend', revision: '1' },
breakdown: { id: 'data.table', revision: '1' },
} as const;
export const feature = defineDataFeature({
id: 'attendance',
presentation: { allowedViews: ['table', 'trend', 'data.metric'] },
schema: z.object({
id: z.string(),
employee: z.string(),
day: z.iso.date(),
team: z.string(),
present: z.number().int(),
}),
identity: ['id'],
fields: {
employee: { role: 'dimension' },
day: { role: 'time' },
team: { role: 'dimension' },
present: { role: 'measure' },
},
meanings: [
{
...METRIC,
label: 'Present employees',
explanation: 'Sum of registered present observations.',
output: { value: 'integer', nullable: false, grain: [] },
implementation: {
kind: 'expression',
expression: {
kind: 'call',
function: { id: 'core.aggregate.sum', revision: '1' },
arguments: [{ kind: 'field', ref: 'present' }],
},
},
dependencies: [],
functionRegistryDigest: 'core-query-2',
origin: 'manual',
lifecycle: 'active',
scope: 'workspace',
authority: 'approved',
aggregation: 'additive',
aggregationDimensions: [],
missingPolicy: 'reject',
},
],
});
function query(groupBy: readonly string[]): QuerySpec {
return {
entity: feature.entity.id,
fields: groupBy,
measures: [METRIC],
relations: [],
groupBy,
population: { kind: 'all-authorized' },
where: { op: 'compare', field: 'team', comparison: 'eq', value: 'Engineering' },
order: [],
};
}
function groupFor(role: (typeof ROLES)[number]): string {
if (role === 'trend') return 'day';
if (role === 'breakdown') return 'employee';
return 'team';
}
function presentationRole(role: (typeof ROLES)[number]): 'metric' | 'trend' | 'table' {
if (role === 'summary') return 'metric';
if (role === 'breakdown') return 'table';
return 'trend';
}
function configFor(role: (typeof ROLES)[number]) {
if (role === 'summary') return { field: METRIC.id, identityValues: { team: 'Engineering' } };
if (role === 'trend') return { labelField: 'day', series: [{ field: METRIC.id }] };
return {};
}
export function goalRegistry() {
const registered = createIntentCompilerRegistry([
{
ref: GOAL,
schema: z.object({ team: z.literal('Engineering') }),
capabilities: ['data.read', 'data.analyze'],
compile(_input, context) {
const output = (role: (typeof ROLES)[number]) => ({
id: role,
kind: 'query' as const,
query: query([groupFor(role)]),
dependsOn: [],
delivery: 'eager' as const,
});
const task: Task = {
version: '1',
id: 'attendance-overview-task',
revision: context.taskRevision,
catalogRevision: context.resource.catalog.revision,
functionRegistryDigest: context.resource.catalog.functionRegistryDigest,
regionId: context.regionId,
kind: 'data',
goal: 'Engineering attendance overview',
assumptions: [],
outputs: [output('summary'), output('trend'), output('breakdown')],
needs: ROLES.map((role) => ({
id: role,
operation: role === 'summary' ? READ : ANALYZE,
outputId: role,
fields: role === 'summary' ? [METRIC.id] : [role === 'trend' ? 'day' : 'employee', METRIC.id],
required: true,
simultaneousGroup: 'overview',
})),
};
return { ok: true as const, value: task };
},
},
]);
if (!registered.ok) throw new Error(registered.diagnostics[0].message);
return registered.value;
}
function sameResult(actual: ResultRef | undefined, expected: ResultRef | undefined): boolean {
if (actual === undefined || expected === undefined) return actual === expected;
return (['id', 'revision', 'outputId', 'queryDigest', 'scopeDigest', 'sourceLineage'] as const).every(
(key) => actual[key] === expected[key],
);
}
function sameOverviewNode(
actual: PresentationPlan['nodes'][number],
expected: PresentationPlan['nodes'][number],
): boolean {
return (
actual.id === expected.id &&
actual.role === expected.role &&
actual.representation.id === expected.representation.id &&
actual.representation.revision === expected.representation.revision &&
actual.config.schema.id === expected.config.schema.id &&
actual.config.schema.revision === expected.config.schema.revision &&
actual.children.length === expected.children.length &&
actual.children.every((child, index) => child === expected.children[index]) &&
sameResult(actual.result, expected.result)
);
}
export function overviewPattern(): PresentationPatternManifest {
const pattern: PresentationPatternManifest = {
ref: PATTERN,
expand(request) {
const matches = ROLES.map((role) => request.context.results.filter((result) => result.ref.outputId === role));
if (
request.context.task.id !== 'attendance-overview-task' ||
!ROLES.every((role) => request.context.task.needs.some((need) => need.id === role && need.required)) ||
matches.some((results) => results.length !== 1)
)
return {
ok: false,
diagnostics: [
{
code: 'attendance.pattern-input',
message: 'The registered goal needs three exact results.',
retryable: false,
},
],
};
return {
ok: true,
value: {
id: request.id,
revision: request.revision,
rootId: 'workspace',
preconditions: request.preconditions,
nodes: [
{
id: 'workspace',
role: 'structure',
representation: REF.workspace,
config: { schema: { id: 'layout.stack.config', revision: '1' }, values: {} },
children: [...ROLES],
},
...ROLES.map((role, index) => ({
id: role,
role: presentationRole(role),
representation: REF[role],
result: matches[index]![0]!.ref,
config: {
schema: { id: `${REF[role].id}.config`, revision: '1' },
values: configFor(role),
},
children: [],
})),
],
links: [],
coverage: ROLES.map((role) => ({
needId: role,
nodeIds: [role],
operations: [role === 'summary' ? READ : ANALYZE],
})),
stateTransfer: [],
diagnostics: [],
},
};
},
matches(plan, context) {
const expanded = pattern.expand({
id: plan.id,
revision: plan.revision,
preconditions: plan.preconditions,
context,
});
if (!expanded.ok || plan.rootId !== expanded.value.rootId || plan.nodes.length !== expanded.value.nodes.length)
return false;
return expanded.value.nodes.every(
(expected) => plan.nodes.filter((actual) => sameOverviewNode(actual, expected)).length === 1,
);
},
};
return pattern;
}
overviewPattern() binds each child to the exact result for its output ID. An unknown goal, missing required output, or incompatible result is rejected before publication. The runnable single-goal entry is also available in examples/vnext/workspace-goal/main.ts.
3. Register the complete page#
Create src/page.ts from the source below. Its ./goal.js import points to the file from step 2; the remaining imports are public package exports.
import type { Intent, PresentationPlan, VersionRef } from '@varendo/core';
import type { PresentationPatternManifest, PresentationStateMappingManifest } from '@varendo/core/presentation';
import { defineView } from '@varendo/web/recipes';
import { html } from 'lit';
import { feature, GOAL, PATTERN, goalRegistry, overviewPattern } from './goal.js';
const PAGE_GOAL = { id: 'attendance.page', revision: '1' } as const;
const PAGE_PATTERN = { id: 'attendance.page-pattern', revision: '1' } as const;
const PAGE_TASK = 'attendance-page-task';
const SHELL = { id: 'demo.page-shell', revision: '1' } as const;
const HEADER = { id: 'demo.page-header', revision: '1' } as const;
const SIDEBAR = { id: 'demo.page-sidebar', revision: '1' } as const;
const BODY = { id: 'demo.page-body', revision: '1' } as const;
export const WORKSPACE_RESOURCE = feature.resource;
export const WORKSPACE_RECORDS = [
{ id: 'a', employee: 'Ada', day: '2026-09-01', team: 'Engineering', present: 1 },
{ id: 'b', employee: 'Sam', day: '2026-09-02', team: 'Engineering', present: 1 },
{ id: 'c', employee: 'Lee', day: '2026-09-02', team: 'Design', present: 1 },
];
const overviewGoal = goalRegistry().definitions[0]!;
export const LAYOUT_INTENTS = [
overviewGoal,
{
...overviewGoal,
ref: PAGE_GOAL,
compile(input: unknown, context: Parameters<typeof overviewGoal.compile>[1]) {
const compiled = overviewGoal.compile(input, context);
if (!compiled.ok) return compiled;
return { ok: true as const, value: { ...compiled.value, id: PAGE_TASK, goal: 'Attendance overview page' } };
},
},
];
export function layoutIntent(kind: 'workspace' | 'page'): Intent {
return {
version: '1',
id: `show-${kind}`,
kind: 'custom',
resource: feature.id,
intent: kind === 'page' ? PAGE_GOAL : GOAL,
input: { team: 'Engineering' },
};
}
function structuralNode(id: string, ref: VersionRef, children: string[]): PresentationPlan['nodes'][number] {
return {
id,
representation: ref,
role: 'structure',
children,
config: { schema: { id: `${ref.id}.config`, revision: '1' }, values: {} },
};
}
function pageStructure(): PresentationPlan['nodes'] {
return [
structuralNode('page', SHELL, ['page-header', 'page-body']),
structuralNode('page-header', HEADER, []),
structuralNode('page-body', BODY, ['page-sidebar', 'workspace']),
structuralNode('page-sidebar', SIDEBAR, []),
];
}
function matchesStructure(
actual: PresentationPlan['nodes'][number],
expected: PresentationPlan['nodes'][number],
): boolean {
return (
actual.id === expected.id &&
actual.role === 'structure' &&
actual.result === undefined &&
actual.representation.id === expected.representation.id &&
actual.representation.revision === expected.representation.revision &&
actual.config.schema.id === expected.config.schema.id &&
actual.config.schema.revision === expected.config.schema.revision &&
actual.children.length === expected.children.length &&
actual.children.every((child, index) => child === expected.children[index])
);
}
const baseOverview = overviewPattern();
const STRUCTURAL_REFS = [SHELL, HEADER, BODY, SIDEBAR];
export const LAYOUT_STATE_MAPPINGS: readonly PresentationStateMappingManifest[] = STRUCTURAL_REFS.map((from) => ({
ref: { id: `${from.id}.archive`, revision: '1' },
from,
to: { id: 'layout.stack', revision: '1' },
fromRole: 'structure',
toRole: 'structure',
kind: 'archive',
}));
function stateTransfers(
previous: PresentationPlan | undefined,
nodes: PresentationPlan['nodes'],
): PresentationPlan['stateTransfer'] {
return (previous?.nodes ?? []).map((node) => {
if (nodes.some((next) => next.id === node.id))
return { fromNode: node.id, toNode: node.id, mapping: { id: 'aeliqo.state.identity', revision: '1' } };
return {
fromNode: node.id,
toNode: 'workspace',
mapping: { id: `${node.representation.id}.archive`, revision: '1' },
};
});
}
const overview: PresentationPatternManifest = {
...baseOverview,
expand(request) {
const expanded = baseOverview.expand(request);
if (!expanded.ok) return expanded;
const plan = expanded.value as PresentationPlan;
return { ok: true, value: { ...plan, stateTransfer: stateTransfers(request.context.incumbent, plan.nodes) } };
},
};
const pagePattern: PresentationPatternManifest = {
ref: PAGE_PATTERN,
expand(request) {
if (request.context.task.id !== PAGE_TASK)
return {
ok: false,
diagnostics: [{ code: 'demo.page-intent', message: 'This pattern requires the page goal.', retryable: false }],
};
const expanded = baseOverview.expand({
...request,
context: {
...request.context,
task: { ...request.context.task, id: 'attendance-overview-task' },
},
});
if (!expanded.ok) return expanded;
const plan = expanded.value as PresentationPlan;
const nodes = [...pageStructure(), ...plan.nodes];
return {
ok: true,
value: { ...plan, rootId: 'page', nodes, stateTransfer: stateTransfers(request.context.incumbent, nodes) },
};
},
matches(plan, context) {
if (context.task.id !== PAGE_TASK || plan.rootId !== 'page' || plan.nodes.length !== 8) return false;
const structure = pageStructure();
if (!structure.every((expected) => plan.nodes.filter((actual) => matchesStructure(actual, expected)).length === 1))
return false;
return baseOverview.matches(
{
...plan,
rootId: 'workspace',
nodes: plan.nodes.filter((node) => !structure.some((item) => item.id === node.id)),
},
{ ...context, task: { ...context.task, id: 'attendance-overview-task' } },
);
},
};
export const LAYOUT_PATTERNS = [overview, pagePattern];
export const LAYOUT_DISCOVERY = [
{ ref: PATTERN, intent: GOAL, outputs: ['summary', 'trend', 'breakdown'] },
{ ref: PAGE_PATTERN, intent: PAGE_GOAL, outputs: ['summary', 'trend', 'breakdown'] },
];
function structuralView(ref: VersionRef, children: number, render: Parameters<typeof defineView>[0]['render']) {
return defineView({
ref,
render,
manifest: {
ref,
configSchema: { id: `${ref.id}.config`, revision: '1' },
roles: ['structure'],
operations: [],
result: 'none',
children: { min: children, max: children },
visibility: children === 0 ? 'leaf' : 'simultaneous',
extension: true,
resolveConfig: () => ({ ok: true, value: { values: {}, fields: [], ports: [], operations: [] } }),
},
});
}
export const LAYOUT_VIEWS = [
structuralView(
SHELL,
2,
({ children }) =>
html`<section class="demo-page" aria-label="Registered attendance page">
<style>
.demo-page {
container-type: inline-size;
min-width: 0;
}
.demo-page-header {
padding: 1rem;
border-bottom: 1px solid var(--aeliqo-color-border);
}
.demo-page-header h3 {
margin: 0;
}
.demo-page-body {
display: grid;
grid-template-columns: minmax(8rem, 1fr) minmax(0, 4fr);
gap: 1rem;
}
.demo-page-body > *,
.demo-page-body [data-aeliqo-node-id] {
min-width: 0;
}
.demo-page-nav {
padding: 1rem;
}
.demo-page-nav a {
display: block;
padding: 0.75rem;
}
@container (max-width: 600px) {
.demo-page-body {
grid-template-columns: minmax(0, 1fr);
}
.demo-page-nav {
display: flex;
flex-wrap: wrap;
}
}</style
>${children()}
</section>`,
),
structuralView(
HEADER,
0,
() =>
html`<header class="demo-page-header">
<h3 id="attendance-overview">Attendance overview</h3>
<p>Engineering · September 2026</p>
</header>`,
),
structuralView(BODY, 2, ({ children }) => html`<div class="demo-page-body">${children()}</div>`),
structuralView(
SIDEBAR,
0,
() =>
html`<nav class="demo-page-nav" aria-label="Registered page navigation">
<a href="#attendance-overview" aria-current="page">Overview</a
><a href="https://hermawan22.github.io/varendo/guides/workspace/">Workspace guide</a>
</nav>`,
),
];
The page goal reuses the three registered data outputs. Its pattern wraps the existing workspace with four stable structural nodes: page, page-header, page-body, and page-sidebar. Each structural view declares its allowed child count and accepts no result of its own. children() renders the validated children supplied by the framework.
The host supplies the header text, navigation links, and responsive CSS. Replace the demonstration links with destinations your application owns. Below 600 pixels, the supplied page container stacks its sidebar and workspace. This CSS changes placement while keeping the registered child tree.
4. Wire the same facade for all three levels#
Create src/main.ts from this runnable entry. Change its ../workspace/page.js import to ./page.js for the local file layout:
import type { Intent } from '@varendo/core';
import { createIntentCompilerRegistry } from '@varendo/core/app';
import { createQueryFunctionRegistry } from '@varendo/core/expressions';
import { createLocalDataService } from '@varendo/runtime/data';
import { createAeliqoApp } from '@varendo/web/app';
import {
LAYOUT_INTENTS,
LAYOUT_STATE_MAPPINGS,
LAYOUT_PATTERNS,
LAYOUT_VIEWS,
WORKSPACE_RESOURCE,
WORKSPACE_RECORDS,
layoutIntent,
} from '../workspace/page.js';
const functions = createQueryFunctionRegistry({ version: '2' });
const intents = createIntentCompilerRegistry(LAYOUT_INTENTS);
if (!functions.ok || !intents.ok) throw new Error('Registration failed.');
const resource = WORKSPACE_RESOURCE;
const data = createLocalDataService({
snapshot: {
catalog: resource.catalog,
sourceRevision: 'attendance-1',
records: { attendance: WORKSPACE_RECORDS },
},
functionRegistry: functions.value,
authorize: ({ context }) =>
context.principal === 'attendance-user'
? { ok: true, value: { scopeDigest: 'attendance-scope', policyRevision: '1' } }
: { ok: false, diagnostics: [{ code: 'attendance.denied', message: 'Read denied.', retryable: false }] },
});
const status = document.querySelector<HTMLElement>('#status');
const target = document.querySelector<HTMLElement>('#workspace');
if (!status || !target) throw new Error('Missing host elements.');
const app = createAeliqoApp({
resources: [{ resource, data }],
intents: intents.value,
patterns: LAYOUT_PATTERNS,
stateMappings: LAYOUT_STATE_MAPPINGS,
views: LAYOUT_VIEWS,
authority: {
read: () => ({
ok: true,
value: {
principalKey: 'attendance-user',
scopeDigest: 'attendance-scope',
policyRevision: '1',
experienceRevision: '1',
grants: ['catalog.read', 'task.evaluate', 'result.inspect'],
readContext: { principal: 'attendance-user' },
},
}),
},
onPresentation(receipt) {
status!.textContent = `${receipt.status}: ${receipt.presentation.plan.rootId}`;
},
});
const mounted = app.mount({ target, regionId: 'attendance-workspace', resourceId: resource.id });
if (!mounted.ok) throw new Error(mounted.diagnostics[0].message);
const browse: Intent = {
version: '1',
id: 'attendance-rows',
kind: 'browse',
resource: resource.id,
fields: ['employee', 'day', 'present'],
filter: { op: 'compare', field: 'team', comparison: 'eq', value: 'Engineering' },
};
async function show(intent: Intent): Promise<void> {
const receipt = await app.render({ regionId: 'attendance-workspace', intent });
if (receipt.status !== 'renderer-ready')
status!.textContent = `${receipt.status}: ${receipt.diagnostics[0]?.message ?? 'Try another request.'}`;
}
document.querySelector('#component')!.addEventListener('click', () => void show(browse));
document.querySelector('#overview')!.addEventListener('click', () => void show(layoutIntent('workspace')));
document.querySelector('#page')!.addEventListener('click', () => void show(layoutIntent('page')));
window.addEventListener('pagehide', () => app.dispose(), { once: true });
await show(browse);
This authority is a synthetic demo identity. Replace it with your application's current identity and permission checks before connecting private data. The selected goal never supplies grants.
Replace index.html with:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Attendance UI</title>
<style>
body {
margin: 0;
font: 16px/1.5 system-ui;
}
main {
max-width: 72rem;
margin: auto;
padding: 1rem;
}
button {
min-height: 44px;
font: inherit;
margin: 0.25rem;
}
#workspace {
min-width: 0;
}
</style>
</head>
<body>
<main>
<h1 id="pg-result-title">Attendance UI</h1>
<button id="component">One component</button>
<button id="overview">Workspace</button>
<button id="page">Entire page</button>
<p id="status" role="status">Loading</p>
<div id="workspace"></div>
</main>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Run npm run dev and open the printed address. The three buttons send different registered intents to the same mounted Region. One component displays two Engineering observations. Workspace displays the three required outputs. Entire page adds the registered header and sidebar around those outputs.
5. Keep state and recover from failures#
- Resize the same result: eligible presentations reuse current results and validated state mappings. Resizing does not request new data or call a model.
- The shared page registration includes
LAYOUT_STATE_MAPPINGSfor explicit identity transfers and for archiving structural page state when returning to a workspace. Register these mappings with its patterns. - Stable child identities keep renderer ownership predictable. Selection and other semantic values transfer only when their fields, ports, result identity, and registered mappings remain compatible. A different query does not automatically inherit a selection from old results.
- Before leaving a dirty form,
onDraftExitlets the host offer Save, Discard, or Stay. Without a handler, replacement returnsneeds-input. See the application API. - An unsupported pattern, failed mandatory output, cancelled request, or renderer failure keeps the previous authorized UI and canonical Region. Forced revocation clears inaccessible content.
renderer-readyreports the Region renderer publication boundary. It does not promise that every asynchronous custom child has finished loading or that the browser has painted.
For the failure journey, run the repository's workspace-goal example: Request anomaly rejects an unregistered goal; Fail breakdown update rejects a missing mandatory output while retaining the successful overview. A save already completed by your host remains a business effect even if a later presentation fails.
