@veil-runtime/ui 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ # Application composition foundation (v0.2.0-rc.1)
2
+
3
+ ## Architecture assessment
4
+
5
+ **Confirmed in v0.1.0:** `ApplicationManifest` and `createUIRegistry` describe Core capability graphs. `deriveUIFields`, rich input schemas, and `validateRichCapabilityInput` supply field metadata and bounded validation. Relationships, actions, transitions, and workflow metadata describe possible interactions. The package does not render, own client state, or call a host operation. The React laboratory is outside the published package. The v0.1.0 Git tag exists and `npm view @veil-runtime/ui@0.1.0 version` confirms npm publication; package metadata and documentation call it a Developer Preview. The root exports and `/inspection` remain supported.
6
+
7
+ **New in this branch:** `ComposedApplication` defines one screen tree with a form, field bindings, action IDs, and optional initial values. `createCompositionController` owns one instance's state and submission lifecycle. `mountComposedApplication` renders a native HTML form and subscribes to that controller. Registered DOM components can wrap or extend the tree. These are separate from `ApplicationManifest`; neither replaces the capability graph.
8
+
9
+ The new architecture is deliberately one-screen and one-level of form state. A composed definition is plain bounded data. It holds no executable code; handlers and custom components are registered separately by trusted host code. The controller snapshots the definition, validates all fields before action execution, passes a copied input to a handler, prevents duplicate submissions, and exposes copied state snapshots. The host owns the handler's authorization, transport, persistence, result reconciliation, and server validation. The mount disposer removes DOM listeners and its subscription. Call `controller.dispose()` when the application instance is finished; pending handler results are ignored, and the host remains responsible for cancelling its own network request if needed.
10
+
11
+ ## Install and use
12
+
13
+ `@veil-runtime/core` is an optional peer for this API. New composition consumers may install only `@veil-runtime/ui`; existing Core-based graph APIs continue to work when Core is installed. The package remains ESM with Node 24+ for server-side use. Use a browser bundler for the DOM entry point.
14
+
15
+ ```ts
16
+ import { createCompositionController, defineComposedApplication } from '@veil-runtime/ui/composition';
17
+ import { mountComposedApplication } from '@veil-runtime/ui/dom';
18
+
19
+ const definition = defineComposedApplication({
20
+ id: 'entry', title: 'Treatment entry',
21
+ fields: { lot: { type: 'string', required: true, description: 'Lot', minLength: 2 } },
22
+ actions: ['save'],
23
+ screen: { component: 'Form', children: [
24
+ { component: 'Field', field: 'lot' },
25
+ { component: 'Submit', action: 'save', text: 'Save' },
26
+ { component: 'Status' },
27
+ ] },
28
+ });
29
+ const controller = createCompositionController(definition, {
30
+ save: async (input) => { await myAuthorizedOperation(input); return { ok: true }; },
31
+ });
32
+ const dispose = mountComposedApplication(document.getElementById('app')!, controller);
33
+ // When finished with this instance:
34
+ dispose();
35
+ controller.dispose();
36
+ ```
37
+
38
+ The complete browser example is in [`examples/composed`](../examples/composed). From the repository root, build the package, run `node packages/ui/examples/composed/build.mjs`, then serve `packages/ui/examples/composed` with any static HTTP server. Its action is mocked: lot `FAIL` returns `{ ok: false }`, while other valid entries succeed. The bundle is generated and is not source control input. With Chrome installed, `npm --workspace @veil-runtime/ui run test:browser` checks mobile layout, field errors, Enter-key submission, success, and failure.
39
+
40
+ ## Contracts
41
+
42
+ Built-in components are `Stack`, `Form`, `Field`, `Submit`, `Text`, and `Status`. The schema requires every declared field and action to appear once. Fields and submits must be inside a form; nested forms are rejected. `Field` uses the existing rich input field vocabulary for labels, constraints, and control choice. Primitive fields, dates, multiline text, and finite enums are supported. Structured object and array fields require future work; they are rejected in this renderer. `setField` updates one value and emits a snapshot. `submit` validates the complete input and invokes only its registered handler. The handler must return exactly `{ ok: true }` or `{ ok: false }`; malformed results, rejection, and thrown errors fail safely. States are `idle`, `submitting`, `success`, and `error`. Failures produce a generic public message, without leaking handler details.
43
+
44
+ To register an application-specific component, include its name when creating the controller and supply the same name to the DOM mount:
45
+
46
+ ```ts
47
+ const components = {
48
+ Card(_node, { renderChildren }) {
49
+ const card = document.createElement('section');
50
+ card.className = 'card';
51
+ card.append(...renderChildren());
52
+ return card;
53
+ },
54
+ };
55
+ const controller = createCompositionController(definition, handlers, Object.keys(components));
56
+ mountComposedApplication(target, controller, components);
57
+ ```
58
+
59
+ The DOM renderer writes strings with `textContent`, uses native label/control association, native submit behavior (including Enter), visible field errors, `aria-invalid`, and a polite live status region. On invalid submission it focuses the first invalid field. It uses a single-column layout with full-width controls. Mounting a new screen into the same target disposes the previous mount after the replacement renders successfully; a failed replacement leaves the prior screen mounted. The caller still owns the controller and must dispose it separately. Host CSS controls theme and may strengthen contrast and focus styles. Custom renderers are trusted code and must implement their own accessibility and escaping.
60
+
61
+ Untrusted definitions and inputs must be byte-limited before decoding. The library then applies the v0.1.0 bounded-data rules and rejects unknown components, invalid bindings, duplicate fields/actions, unsafe keys, and invalid initial values. Schema validation is not authorization or server-side business validation. Avoid rendering or running untrusted custom component code.
62
+
63
+ ## Core boundary and compatibility
64
+
65
+ Core integration is an action-handler choice. A host may call its authorized Core operation in `save`; the composition package does not import Core at runtime, construct Core capabilities, or change Core's policies. The server must revalidate input and enforce authorization independently. Existing v0.1.0 root exports, `/inspection`, graph behavior, and deterministic validation remain available. The new `/composition` and `/dom` subpaths let an independent TypeScript consumer avoid Core declarations. The root declaration surface still includes legacy Core-based types and therefore still requires Core for strict type-checking of legacy imports.
66
+
67
+ This foundation does not provide navigation, nested or repeating forms, asynchronous field validation, conditional visibility, persistence, a drag-and-drop builder, backend APIs, or automatic Core mapping. Chromium mobile and desktop smoke checks cover labels, focus, errors, keyboard submission, escaping and remounting. Firefox, WebKit, and assistive technology behavior remain unverified. A release candidate requires maintainer review of compatibility and security evidence.
@@ -0,0 +1,25 @@
1
+ # Veil UI 0.3.0-rc.1 Developer Preview candidate
2
+
3
+ This is a review candidate, not a publication announcement. Install the qualified tarball by its absolute path with `npm install /absolute/path/veil-runtime-ui-0.3.0-rc.1.tgz`. Node.js 24 or newer and ESM imports are supported. The universal interface engine does not require Veil Core, a frontend framework, or an AI provider. The old capability description APIs use Core types and may use Core in the host.
4
+
5
+ ## Quick start and examples
6
+
7
+ [UNIVERSAL-INTERFACE.md](UNIVERSAL-INTERFACE.md) contains the smallest TypeScript and JavaScript examples. The packaged [human authored example](../examples/universal/human.mjs) mounts an input, button, and status. The [agent JSON example](../examples/universal/agent.mjs) validates simulated generated definitions and applies controlled updates. The [application composition example](../examples/universal/application.mjs) switches between two screens. These are executable demonstrations, not live model reliability evidence.
8
+
9
+ ## Supported contract
10
+
11
+ The `/interface` export validates bounded JSON definitions and creates a framework neutral controller. The `/dom` export mounts built-in controls and trusted host components. Built-ins are Stack, Row, Text, Heading, Status, Input, Button, Table, and List. `getInterfaceCapabilities()` describes their names, properties, events, and limits. State is local scalar input state; data is host supplied. Bindings read own-property dot paths. Equality conditions hide nodes. Buttons emit declared action IDs, source keys, state copies, and revision numbers to the host. Definition and data updates preserve compatible state and keyed DOM nodes; failed renderer preparation leaves the accepted revision and view in place. `createMountedInterface()` combines one controller and mount with one disposal call.
12
+
13
+ The root and `/inspection` exports preserve v0.1.0 capability descriptions. `/composition` and the composition portion of `/dom` preserve v0.2.0 form composition. See [API.md](API.md) for the public inventory and [COMPOSITION.md](COMPOSITION.md) for the form contract. The candidate does not remove those APIs.
14
+
15
+ ## Host responsibilities and limits
16
+
17
+ Generated definitions are untrusted data. Cap raw model response bytes before JSON parsing; validate each definition; pass an explicit `allowedActions` list; authenticate and authorize every external effect in the host; validate inputs again at the service boundary. Definitions cannot supply JavaScript, custom factory code, raw HTML, URLs, or arbitrary attributes. The DOM adapter writes text through `textContent`. Trusted custom factories execute host code and must clean up timers, subscriptions, and network work using the abort signal and disposal hook. The transaction cannot undo external effects performed by trusted factories during preparation.
18
+
19
+ The interface vocabulary has no row actions, sorting, repeating templates, navigation router, or form-level validation. Heading is a fixed `h2`; List and Table have bounded simple scalar content. The package is not an autonomous application generator or a production-ready AI framework. Live model reliability, Firefox, WebKit, and screen-reader behavior require separate evidence when those environments are available.
20
+
21
+ ## Migration and release order
22
+
23
+ Existing v0.1.0 imports remain valid. Hosts using v0.2.0 composition can keep their form controller and add `/interface` separately; the interface tree is a separate contract, not a schema replacement. New agent hosts should send the capability manifest and an explicit list of allowed actions, then validate returned JSON. Direct CommonJS `require()` remains unsupported; dynamic `import()` works from CommonJS.
24
+
25
+ If this RC passes maintainer review, skip a public v0.2.0 release and make v0.3.0 the next public preview. v0.2.0-rc.1 is qualified but unpublished; publishing it first adds an extra migration and npm version without a user benefit when v0.3.0 retains its APIs. Keep the v0.2.0 checkpoint for provenance and explain in release communication that the composition API first ships publicly with v0.3.0. Do not publish, tag, or merge from this candidate automatically.
@@ -0,0 +1,9 @@
1
+ # Veil UI 0.3.0-rc.2 Developer Preview candidate
2
+
3
+ This is a review candidate, not a publication announcement. Install only the newly qualified tarball by its absolute path with `npm install /absolute/path/veil-runtime-ui-0.3.0-rc.2.tgz`. The rc.1 checksum does not qualify this corrected source.
4
+
5
+ The supported public surface and release strategy remain those described for rc.1: Node.js 24+, ESM, the framework-neutral `/interface` engine, `/dom` mounting, the v0.2 composition APIs, and the v0.1 capability-description APIs. Veil Core remains optional for the new interface and composition entry points. See [UNIVERSAL-INTERFACE.md](UNIVERSAL-INTERFACE.md), [COMPOSITION.md](COMPOSITION.md), and [API.md](API.md).
6
+
7
+ This candidate remediates the pull-request review findings without adding public APIs. It makes composition field access own-property-only and state changes atomic under the snapshot budget; treats definition, data, and state as independently bounded interface resources; corrects conditional layout display; preserves numeric editing, focus, selection, and accessibility updates; keeps IDs and form action routing local and unique; makes replacement mounting failure-safe; and limits model-harness interaction to visible controls.
8
+
9
+ Generated definitions remain untrusted data. Hosts must cap bytes before parsing, pass explicit action allowlists, authorize effects, validate service inputs, and treat custom components as trusted host code. Live-model reliability, Firefox, WebKit, and actual screen-reader behavior remain outside the available qualification evidence. Do not publish, tag, or merge from this candidate automatically.
@@ -0,0 +1,7 @@
1
+ # Veil UI 0.3.0
2
+
3
+ Stable release prepared for maintainer approval; publication has not occurred. Intended npm dist-tag: `latest`. After publication: `npm install @veil-runtime/ui@0.3.0`. Before publication, install the exact qualified tarball.
4
+
5
+ Includes the universal interface engine, DOM mounting, composition, and capability-description APIs from the merged candidate. All 13 review remediations are retained, including bounded notification feedback when onError updates controller state. No public API changes were made during stable preparation. Node.js 24+, ESM; TypeScript baseline 6.0.2. Core 0.4.x is optional for interface/composition and required for capability execution.
6
+
7
+ See [UNIVERSAL-INTERFACE.md](UNIVERSAL-INTERFACE.md) and [API.md](API.md). Live-model reliability, Firefox, WebKit, and actual screen-reader behavior remain unverified. Custom components are trusted host code.
@@ -0,0 +1,105 @@
1
+ # Universal interface foundation (0.3.0-rc.2 Developer Preview candidate)
2
+
3
+ ## Smallest working interface
4
+
5
+ For a single DOM mount, `createMountedInterface` owns the controller and mount together. Its `dispose()` unmounts the DOM and disposes the controller. The lower-level controller and `mountInterface` exports remain available for multiple mounts or another renderer.
6
+
7
+ ```ts
8
+ import { type BuiltinInterfaceDefinition } from '@veil-runtime/ui/interface';
9
+ import { createMountedInterface } from '@veil-runtime/ui/dom';
10
+
11
+ const definition = { id: 'hello', actions: ['greet'], state: { name: '' }, data: { message: '' },
12
+ screen: { key: 'root', component: 'Stack', children: [
13
+ { key: 'title', component: 'Heading', text: 'Greeting' },
14
+ { key: 'name', component: 'Input', label: 'Name', binding: { source: 'state', path: 'name' } },
15
+ { key: 'greet', component: 'Button', text: 'Greet', action: 'greet' },
16
+ { key: 'message', component: 'Status', text: { source: 'data', path: 'message' } },
17
+ ] } } satisfies BuiltinInterfaceDefinition;
18
+ const ui = createMountedInterface(document.getElementById('app')!, definition, {
19
+ allowedActions: ['greet'],
20
+ onEvent(event) { ui.updateData({ message: `Hello ${event.state.name}` }, event.revision); },
21
+ });
22
+ // On teardown: ui.dispose();
23
+ ```
24
+
25
+ JavaScript uses the same calls without `satisfies` or the non-null assertion. `allowedActions` is a host policy; keep it explicit for untrusted definitions. `components` is a map of trusted host factories, so the helper derives the registry names from its keys. Definitions cannot provide factory code.
26
+
27
+ Definitions are plain JSON data. Import the neutral runtime through `/interface` and the optional native DOM adapter through `/dom`.
28
+
29
+ ```ts
30
+ import { createInterfaceController, defineInterface } from '@veil-runtime/ui/interface';
31
+ import { mountInterface } from '@veil-runtime/ui/dom';
32
+
33
+ const definition = defineInterface({
34
+ id: 'hello', actions: ['greet'], state: { name: '' }, data: { message: '' },
35
+ screen: { key: 'root', component: 'Stack', children: [
36
+ { key: 'name', component: 'Input', label: 'Name', binding: { source: 'state', path: 'name' } },
37
+ { key: 'greet', component: 'Button', text: 'Greet', action: 'greet' },
38
+ { key: 'message', component: 'Text', text: { source: 'data', path: 'message' } },
39
+ ] },
40
+ });
41
+ const controller = createInterfaceController(definition, {
42
+ allowedActions: ['greet'],
43
+ onEvent(event) {
44
+ if (event.action === 'greet') controller.updateData(
45
+ { message: `Hello ${event.state.name}` }, event.revision,
46
+ );
47
+ },
48
+ });
49
+ const unmount = mountInterface(document.getElementById('app')!, controller);
50
+ // On teardown:
51
+ unmount();
52
+ controller.dispose();
53
+ ```
54
+
55
+ The host owns the controller, mount point, component implementations, event callback, and teardown. Use `updateData` to refresh host data and `updateDefinition` to replace a view. Both accept an optional expected revision and return `false` if it is stale or disposed. Invalid or unrenderable updates throw; the controller and mounted view keep their last accepted revision. `setState` changes local scalar state and throws on rejection. `snapshot` returns isolated copies. The same imports work in JavaScript; omit the TypeScript non-null assertion on `getElementById`.
56
+
57
+ Every update follows one acceptance path: validate and copy the proposed definition/data/state; derive compatible local state; prepare candidate DOM and custom components for every mount; discard all candidates if any mount fails; otherwise commit DOM, controller values, and revision; finally notify ordinary subscribers. A candidate is not visible through `snapshot()` or subscriptions during preparation. A synchronous reentrant update is rejected; asynchronous responses should pass their expected revision. A rejected update does not increment the revision. Mounted renderers use the controller's internal preparation bridge; hosts do not need a rollback call.
58
+
59
+ ## Architecture and alternatives
60
+
61
+ The RC's `ComposedApplication` is a good focused form controller: it validates field schemas, submits one action at a time, and protects late results. Its required `Form`/`Field`/`Submit` nesting cannot express arbitrary UI. `InterfaceDefinition` keeps the separation of plain data and trusted host callbacks, and adds a small node tree instead of extending the form schema with unrelated cases. This is a separate public API for broader experiences; composition and v0.1 capability graph APIs remain unchanged.
62
+
63
+ Nodes have stable unique keys, a component name, and optional children. The built-ins are `Stack`, `Row`, `Text`, `Heading`, `Status`, `Input`, `Button`, `Table`, and `List`. `Stack` and wrapping `Row` nest layouts. `Heading` renders an `h2`; `Status` has a polite live region; `List` renders up to 100 scalar items. `Table` presents up to 100 rows and 20 columns. Text, list, and table values use one path binding from `data` or `state`. An optional equality condition controls visibility. `Input` writes scalar state. `Button` emits `{ action, source, state, revision }`. Host code registers trusted DOM components by passing their names to the controller and implementations to `mountInterface`. Registrations are local to an instance.
64
+
65
+ `getInterfaceCapabilities()` returns an isolated JSON-safe manifest: component names, allowed and required properties, child composition, events, property types, binding semantics, and limits. The built-in validator uses the same component property lists. An agent host can serialize the manifest alongside its own allowed action IDs, ask a provider-neutral agent for JSON, cap bytes before parsing, and call `validateInterfaceDefinition` before `createMountedInterface` or `updateDefinition`. Component properties that do not belong to a built-in are rejected. `BuiltinInterfaceDefinition` supports TypeScript `satisfies` checks for built-in-only authored screens; the runtime validator remains mandatory for received JSON. The manifest intentionally excludes trusted custom factories and privileged actions. A host may add a trusted component after receiving the generated definition, as the agent demonstration does with `Activity`.
66
+
67
+ The DOM adapter retains built-in elements by key and component within the same parent. It reorders them without remounting and updates text, labels, tables and visibility in place. An `Input` also requires the same binding path and type; a `Button` requires the same action. This preserves focus and selection on retained inputs. Reparenting, replacing a key, or changing component identity remounts it. The controller retains local input values when the interface id, input key, binding path, and type match and the new state declares a compatible default. A new interface id or removed input resets those values. Host data is replaced by each definition's `data` or by `updateData`; it is never merged with local state. Another adapter can consume snapshots and subscriptions without importing `/dom`.
68
+
69
+ Custom DOM factories may return an `HTMLElement` for the original contract, or `{ element, update?, dispose? }`. For an update, the adapter prepares a **new detached custom instance**, calls its optional `update` with the candidate snapshot, and swaps it in only after every renderer prepares successfully. This isolates a throwing `update` from the displayed instance. It remounts custom components on accepted updates; built-in keyed nodes remain in place. The factory receives `{ snapshot, renderChildren, signal }`. The signal aborts before `dispose` when a candidate is discarded, an instance is replaced, or the mount is torn down. Use it to cancel subscriptions, timers, fetches, and delayed callbacks. `dispose` must release resources owned by the instance. `renderChildren` may be called once. Trusted factory and update code must validate their own assumptions.
70
+
71
+ ```js
72
+ const components = {
73
+ Clock(node, { signal }) {
74
+ const element = document.createElement('span');
75
+ const timer = setInterval(() => { if (!signal.aborted) element.textContent = new Date().toLocaleTimeString(); }, 1000);
76
+ return { element, update(next, snapshot) { /* refresh trusted props */ }, dispose() { clearInterval(timer); } };
77
+ },
78
+ };
79
+ const controller = createInterfaceController(definition, { components: ['Clock'], allowedActions: ['refresh'], onEvent });
80
+ const unmount = mountInterface(target, controller, components);
81
+ // Later: controller.updateData(data); controller.updateDefinition(nextDefinition);
82
+ // Teardown: unmount(); controller.dispose();
83
+ ```
84
+
85
+ Alternatives considered: extending the form schema in place would weaken its focused validation and compatibility; accepting HTML or generated framework code would grant definitions executable behavior; a full virtual DOM or application framework adds scope before use cases justify it. This foundation chooses a bounded tree and explicit event boundary. More complex layout, row actions, repeat templates, validation vocabulary, and navigation state need later designs.
86
+
87
+ ## Trust boundary and limits
88
+
89
+ Treat model responses as untrusted bytes. The host must cap bytes before `JSON.parse` (the example uses 64 KiB). The runtime rejects cyclic data, accessors, unsafe property names, unsupported values, unknown node properties and components, undeclared actions, excessive depth and size, malformed bindings, table rows over 100, and invalid input state. Interface trees are limited to 512 nodes and depth 16; the underlying transport limit is 1 MiB. Text is assigned with `textContent`, never `innerHTML`. `allowedActions` narrows action IDs accepted from a definition. These checks do not grant authority: the host must authenticate, authorize, validate inputs on the server, and execute external effects. Trusted component code is outside the untrusted definition boundary.
90
+
91
+ Subscriptions and DOM listeners are removed on disposal. Disposed controllers ignore later data updates; revision checks help hosts reject stale asynchronous responses. Events are delivered synchronously in interaction order with a copy of the current state. Reentrant dispatch is ignored, but repeated later clicks are separate events. The host must deduplicate or serialize effects, check revisions when applying asynchronous results, and authorize every capability independently. Hidden actions cannot be dispatched. Listener feedback is capped at 32 passes per update. Failed DOM preparation calls the mount's `onError` and causes the initiating update to throw. Errors from ordinary subscribers are reported through the controller's `onError` after acceptance and do not reject the update. An input edit rejected during rendering restores its displayed value from accepted state.
92
+
93
+ The transaction covers Veil-owned controller data and DOM operations under normal DOM ownership. It cannot reverse effects that arbitrary trusted component code performs outside its candidate element. Factories should attach cleanup to their abort signal as soon as they acquire a resource; if a factory throws before returning an instance, Veil can abort the signal but cannot call a `dispose` it never received. Components must not mutate the mounted tree or perform privileged effects during preparation. External code that removes or monkey-patches Veil-owned DOM nodes can break commit assumptions. Report such failures as integration defects.
94
+
95
+ ## Three public API demonstrations
96
+
97
+ `examples/universal/human.mjs` defines an interactive interface with the single-call mount helper. `agent.mjs` reads the built-in manifest, parses simulated JSON responses, rejects an unknown component, filters data, continues to a summary, and starts over. The host adds a trusted Activity component after parsing; the agent never supplies its implementation. It retains the query and focus, and disposes Activity on removal. `application.mjs` switches between reusable home and detail definitions and displays a list. Build with `node packages/ui/examples/universal/build.mjs`; open `examples/universal/index.html`. `npm --workspace @veil-runtime/ui run test:browser` exercises all three in Chromium at mobile and desktop widths.
98
+
99
+ ## External consumer assessment
100
+
101
+ `npm --workspace @veil-runtime/ui run verify:consumer` packs twice, compares tarball hashes, installs the packed artifact into separate TypeScript and JavaScript projects offline, and runs six headless scenarios: information, form, structured data, JSON acceptance, event update, and disposal. Core is not installed; the only production dependency is Zod. Before this increment, a one-mount app called `createInterfaceController`, `mountInterface`, then both `unmount()` and `controller.dispose()`; custom component names had to be listed separately from the factory map. The helper reduces these to one creation call, one `dispose()`, and one registry map. The host still supplies an action allowlist, event handling, pre-parse byte limit, and domain authorization. There is no bundled AI provider or framework.
102
+
103
+ ## Release assessment
104
+
105
+ This is a 0.3.0 release candidate that preserves the qualified 0.2.0 RC APIs. Chromium coverage is available; Firefox, WebKit, screen readers, and framework-specific adapters remain unverified. The `Heading` primitive has a fixed h2 level; richer heading hierarchy, row controls, table sorting, list item templates, and form-level validation are outside this increment. The old RC commit and its tarball remain the reproducible 0.2.0 checkpoint. Publication requires maintainer approval of the exact qualified artifact.
@@ -0,0 +1,44 @@
1
+ import { createCompositionController, defineComposedApplication } from '@veil-runtime/ui/composition';
2
+ import { mountComposedApplication } from '@veil-runtime/ui/dom';
3
+
4
+ const definition = defineComposedApplication({
5
+ id: 'stock-treatment', title: 'Stock treatment entry',
6
+ fields: {
7
+ lot: { type: 'string', required: true, description: 'Stock lot', minLength: 2, maxLength: 40 },
8
+ treatment: { type: 'string', required: true, description: 'Treatment', enum: ['Clean', 'Inspect', 'Spray'] },
9
+ quantity: { type: 'number', required: true, description: 'Quantity treated', minimum: 0.01, maximum: 100000 },
10
+ date: { type: 'string', required: true, description: 'Treatment date', format: 'date' },
11
+ notes: { type: 'string', required: false, description: 'Notes', format: 'multiline', maxLength: 500 },
12
+ },
13
+ actions: ['record'],
14
+ screen: { component: 'Card', children: [
15
+ { component: 'Form', children: [
16
+ { component: 'Text', text: 'Fields marked * are required.' },
17
+ { component: 'Field', field: 'lot' },
18
+ { component: 'Field', field: 'treatment' },
19
+ { component: 'Field', field: 'quantity' },
20
+ { component: 'Field', field: 'date' },
21
+ { component: 'Field', field: 'notes' },
22
+ { component: 'Submit', action: 'record', text: 'Record treatment' },
23
+ { component: 'Status' },
24
+ ] },
25
+ ] },
26
+ });
27
+
28
+ const components = {
29
+ Card(_node, { renderChildren }) {
30
+ const card = document.createElement('section');
31
+ card.className = 'card';
32
+ card.append(...renderChildren());
33
+ return card;
34
+ },
35
+ };
36
+
37
+ const controller = createCompositionController(definition, {
38
+ async record(input) {
39
+ await new Promise((resolve) => setTimeout(resolve, 500));
40
+ return { ok: input.lot !== 'FAIL' };
41
+ },
42
+ }, Object.keys(components));
43
+
44
+ mountComposedApplication(document.getElementById('app'), controller, components);
@@ -0,0 +1,7 @@
1
+ import { build } from 'esbuild';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const here = dirname(fileURLToPath(import.meta.url));
6
+ await build({ entryPoints: [join(here, 'app.mjs')], outfile: join(here, 'app.bundle.js'), bundle: true, format: 'esm', platform: 'browser', target: 'es2022' });
7
+ console.log('Built examples/composed/app.bundle.js');
@@ -0,0 +1,34 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Veil UI · Treatment entry</title>
7
+ <style>
8
+ :root { font: 16px system-ui, sans-serif; color: #172b25; background: #f2f6f2; }
9
+ * { box-sizing: border-box; }
10
+ body { margin: 0; padding: clamp(1rem, 4vw, 3rem); }
11
+ main { max-width: 40rem; margin: auto; }
12
+ h1 { margin: 0 0 .5rem; font-size: clamp(1.65rem, 5vw, 2.4rem); }
13
+ .intro { margin: 0 0 1.5rem; color: #42584e; }
14
+ .card { background: white; border: 1px solid #d5e2d6; border-radius: 1rem; padding: clamp(1rem, 4vw, 2rem); box-shadow: 0 10px 30px #1b3b2310; }
15
+ input, select, textarea { border: 1px solid #8ca69a; border-radius: .5rem; padding: .65rem .75rem; background: white; color: inherit; }
16
+ input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible { outline: 3px solid #89b9ed; outline-offset: 2px; }
17
+ input[aria-invalid="true"], select[aria-invalid="true"] { border-color: #af3737; }
18
+ .veil-error { color: #9c2929; min-height: 1em; }
19
+ button { border: 0; border-radius: .55rem; padding: .7rem 1rem; background: #126b43; color: white; font-weight: 700; cursor: pointer; }
20
+ button:disabled { opacity: .65; cursor: wait; }
21
+ .veil-status[data-status="error"] { color: #9c2929; }
22
+ .veil-status[data-status="success"] { color: #126b43; }
23
+ .hint { font-size: .875rem; color: #42584e; }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <main>
28
+ <h1>Record stock treatment</h1>
29
+ <p class="intro">A small declarative Veil UI application. Enter lot <strong>FAIL</strong> to see the mocked operation fail.</p>
30
+ <div id="app"></div>
31
+ </main>
32
+ <script type="module" src="./app.bundle.js"></script>
33
+ </body>
34
+ </html>
@@ -0,0 +1,52 @@
1
+ import { getInterfaceCapabilities } from '@veil-runtime/ui/interface';
2
+ import { createMountedInterface } from '@veil-runtime/ui/dom';
3
+
4
+ const rows = [{ name: 'Alpha', status: 'Open' }, { name: 'Beta', status: 'Open' }];
5
+ const query = { key: 'query', component: 'Input', label: 'Filter requests', binding: { source: 'state', path: 'query' } };
6
+ const table = { key: 'results', component: 'Table', binding: { source: 'data', path: 'rows' }, columns: [{ key: 'name', label: 'Request' }, { key: 'status', label: 'Status' }] };
7
+ const title = { key: 'title', component: 'Text', text: { source: 'data', path: 'title' } };
8
+ function generated(stage, filtered = rows) {
9
+ const children = stage === 0
10
+ ? [title, query, { key: 'filter', component: 'Button', text: 'Filter', action: 'filter' }, table]
11
+ : stage === 1
12
+ ? [title, query, { key: 'filter', component: 'Button', text: 'Filter again', action: 'filter' }, table,
13
+ { key: 'advance', component: 'Button', text: 'Continue', action: 'advance' }]
14
+ : [title, query, { key: 'summary', component: 'Text', text: 'Review complete' },
15
+ { key: 'reset', component: 'Button', text: 'Start over', action: 'reset' }];
16
+ return { id: 'agent', actions: ['filter', 'advance', 'reset'], state: { query: '' },
17
+ data: { title: stage === 2 ? 'Request summary' : stage === 1 ? 'Filtered requests' : 'Open requests', rows: filtered },
18
+ screen: { key: 'root', component: 'Stack', children } };
19
+ }
20
+
21
+ export function mountAgent(target) {
22
+ const metrics = { events: [], rejected: 0, mounted: 0, disposed: 0, ticks: 0 };
23
+ const capabilities = getInterfaceCapabilities();
24
+ if (!capabilities.components.Table || !capabilities.components.Input || !capabilities.events.action) throw new Error('Agent vocabulary unavailable');
25
+ // Simulate an agent transport: cap the response before parsing, then let Veil validate it.
26
+ const parse = (value) => { const response = JSON.stringify(value); if (response.length > 65536) throw new Error('Agent response too large'); return JSON.parse(response); };
27
+ // Trusted host decoration is separate from the JSON vocabulary given to the agent.
28
+ const decorate = (definition) => ({ ...definition, screen: { ...definition.screen, children: [...definition.screen.children.slice(0, -1), { key: 'activity', component: 'Activity' }, definition.screen.children.at(-1)] } });
29
+ const controller = createMountedInterface(target, parse(generated(0)), {
30
+ allowedActions: ['filter', 'advance', 'reset'],
31
+ components: {
32
+ Activity() {
33
+ metrics.mounted++;
34
+ const element = document.createElement('p'); element.textContent = 'Agent activity';
35
+ const timer = setInterval(() => { metrics.ticks++; }, 1000);
36
+ return { element, update() {}, dispose() { clearInterval(timer); metrics.disposed++; } };
37
+ },
38
+ },
39
+ onEvent(event) {
40
+ metrics.events.push(event.action);
41
+ if (event.action === 'filter') {
42
+ // A rejected generated definition leaves the last valid view interactive.
43
+ try { controller.updateDefinition(parse({ ...generated(1), screen: { key: 'root', component: 'Script' } }), event.revision); }
44
+ catch { metrics.rejected++; }
45
+ const filtered = rows.filter((row) => row.name.toLowerCase().includes(String(event.state.query).toLowerCase()));
46
+ controller.updateDefinition(decorate(parse(generated(1, filtered))), event.revision);
47
+ } else if (event.action === 'advance') controller.updateDefinition(parse(generated(2)), event.revision);
48
+ else if (event.action === 'reset') controller.updateDefinition(parse(generated(0)), event.revision);
49
+ },
50
+ });
51
+ return { controller, metrics, capabilities, dispose() { controller.dispose(); } };
52
+ }
@@ -0,0 +1,16 @@
1
+ import { createMountedInterface } from '@veil-runtime/ui/dom';
2
+
3
+ export function mountApplication(target) {
4
+ const home = { id: 'application', actions: ['details'], data: { steps: ['Open details', 'Return home'] }, screen: { key: 'root', component: 'Stack', children: [
5
+ { key: 'heading', component: 'Heading', text: 'Application home' },
6
+ { key: 'steps', component: 'List', binding: { source: 'data', path: 'steps' } },
7
+ { key: 'details', component: 'Button', action: 'details', text: 'View details' },
8
+ ] } };
9
+ const details = { id: 'application', actions: ['back'], screen: { key: 'root', component: 'Stack', children: [
10
+ { key: 'heading', component: 'Heading', text: 'Item details' },
11
+ { key: 'description', component: 'Text', text: 'A bounded two-view workflow.' },
12
+ { key: 'back', component: 'Button', action: 'back', text: 'Back' },
13
+ ] } };
14
+ const controller = createMountedInterface(target, home, { allowedActions: ['details', 'back'], onEvent(event) { controller.updateDefinition(event.action === 'details' ? details : home, event.revision); } });
15
+ return { controller, dispose() { controller.dispose(); } };
16
+ }
@@ -0,0 +1,6 @@
1
+ import { build } from 'esbuild';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+ const here = dirname(fileURLToPath(import.meta.url));
5
+ await build({ entryPoints: [join(here, 'main.mjs')], outfile: join(here, 'main.bundle.js'), bundle: true, format: 'esm', platform: 'browser', target: 'es2022' });
6
+ console.log('Built examples/universal/main.bundle.js');
@@ -0,0 +1,16 @@
1
+ import { defineInterface } from '@veil-runtime/ui/interface';
2
+ import { createMountedInterface } from '@veil-runtime/ui/dom';
3
+
4
+ export function mountHuman(target) {
5
+ const definition = defineInterface({
6
+ id: 'human', actions: ['show'], state: { selected: 'All' }, data: { summary: '3 items available' },
7
+ screen: { key: 'root', component: 'Stack', children: [
8
+ { key: 'heading', component: 'Heading', text: 'Human-authored inventory' },
9
+ { key: 'selection', component: 'Input', label: 'Category', binding: { source: 'state', path: 'selected' } },
10
+ { key: 'show', component: 'Button', action: 'show', text: 'Show category' },
11
+ { key: 'summary', component: 'Status', text: { source: 'data', path: 'summary' } },
12
+ ] },
13
+ });
14
+ const controller = createMountedInterface(target, definition, { allowedActions: ['show'], onEvent(event) { controller.updateData({ summary: `Showing ${event.state.selected}` }, event.revision); } });
15
+ return { controller, dispose() { controller.dispose(); } };
16
+ }
@@ -0,0 +1,4 @@
1
+ <!doctype html>
2
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Veil UI universal foundation</title>
3
+ <style>body{font:16px system-ui;max-width:64rem;margin:auto;padding:1rem;color:#172b25}h1{overflow-wrap:anywhere}main{display:grid;gap:1.5rem;min-width:0}.card,.demo{border:1px solid #bbcbbf;border-radius:.7rem;padding:1rem;min-width:0;overflow-wrap:anywhere}button,input{font:inherit;padding:.6rem;max-width:100%;box-sizing:border-box}button:focus-visible,input:focus-visible{outline:3px solid #2675b8;outline-offset:2px}table{border-collapse:collapse;width:100%;max-width:100%}td,th{text-align:left;border-bottom:1px solid #bbcbbf;padding:.5rem}@media(max-width:480px){body{padding:.5rem}}</style></head>
4
+ <body><h1>Veil UI: one foundation, three experiences</h1><main><section class="demo"><h2>Human authored</h2><div id="human"></div></section><section class="demo"><h2>Simulated agent</h2><div id="agent"></div></section><section class="demo"><h2>Application composition</h2><div id="application"></div></section></main><script type="module" src="./main.bundle.js"></script></body></html>
@@ -0,0 +1,7 @@
1
+ import { mountHuman } from './human.mjs';
2
+ import { mountAgent } from './agent.mjs';
3
+ import { mountApplication } from './application.mjs';
4
+ const human = mountHuman(document.getElementById('human'));
5
+ const agent = mountAgent(document.getElementById('agent'));
6
+ const application = mountApplication(document.getElementById('application'));
7
+ globalThis.veilExamples = { human, agent, application };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@veil-runtime/ui",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/",
7
- "tag": "preview"
7
+ "tag": "latest"
8
8
  },
9
9
  "type": "module",
10
- "description": "Veil UI \u2014 Deterministic Composition Engine (Developer Preview). Framework-neutral interface descriptions from governed capabilities.",
10
+ "description": "Veil UI universal interface foundation and deterministic capability descriptions.",
11
11
  "files": [
12
12
  "dist",
13
13
  "README.md",
@@ -15,6 +15,8 @@
15
15
  "SECURITY.md",
16
16
  "docs",
17
17
  "examples/standalone",
18
+ "examples/composed",
19
+ "examples/universal",
18
20
  "LICENSE",
19
21
  "NOTICE"
20
22
  ],
@@ -28,6 +30,18 @@
28
30
  "./inspection": {
29
31
  "types": "./dist/composition-inspection.d.ts",
30
32
  "import": "./dist/composition-inspection.js"
33
+ },
34
+ "./dom": {
35
+ "types": "./dist/dom.d.ts",
36
+ "import": "./dist/dom.js"
37
+ },
38
+ "./composition": {
39
+ "types": "./dist/composition.d.ts",
40
+ "import": "./dist/composition.js"
41
+ },
42
+ "./interface": {
43
+ "types": "./dist/interface.d.ts",
44
+ "import": "./dist/interface.js"
31
45
  }
32
46
  },
33
47
  "sideEffects": false,
@@ -35,9 +49,11 @@
35
49
  "build": "node scripts/build.mjs",
36
50
  "build:no-dom": "node scripts/build.mjs",
37
51
  "test": "node --import tsx --test --test-isolation=none test/*.test.ts test/*.test.mjs",
52
+ "test:browser": "node examples/composed/build.mjs && node test/browser-composition.mjs && node examples/universal/build.mjs && node test/browser-interface.mjs",
38
53
  "check:boundary": "node scripts/check-boundary.mjs",
39
54
  "typecheck:test": "tsc -p tsconfig.test.json",
40
55
  "verify:dist": "node scripts/verify-dist.mjs",
56
+ "verify:consumer": "node scripts/verify-universal-consumer.mjs",
41
57
  "prepack": "npm run build && npm run verify:dist && npm run check:boundary",
42
58
  "prepublishOnly": "node scripts/publication-hold.mjs",
43
59
  "check": "npm run build && npm run typecheck:test && npm test && npm run verify:dist && npm run check:boundary"
@@ -45,6 +61,11 @@
45
61
  "peerDependencies": {
46
62
  "@veil-runtime/core": "^0.4.0"
47
63
  },
64
+ "peerDependenciesMeta": {
65
+ "@veil-runtime/core": {
66
+ "optional": true
67
+ }
68
+ },
48
69
  "dependencies": {
49
70
  "zod": "^4.6.6"
50
71
  },