@veil-runtime/ui 0.1.0 → 0.4.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.
- package/CHANGELOG.md +43 -0
- package/README.md +18 -10
- package/dist/build-manifest.json +26 -12
- package/dist/composition.d.ts +43 -0
- package/dist/composition.js +227 -0
- package/dist/dom.d.ts +31 -0
- package/dist/dom.js +569 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/input-schema.d.ts +4 -2
- package/dist/interface-transaction.d.ts +9 -0
- package/dist/interface-transaction.js +14 -0
- package/dist/interface.d.ts +261 -0
- package/dist/interface.js +524 -0
- package/dist/presentation-composition.d.ts +1 -1
- package/dist/presentation-composition.js +3 -1
- package/docs/API.md +4 -2
- package/docs/COMPOSITION.md +67 -0
- package/docs/DECISIONS.md +1 -1
- package/docs/RELEASE-0.3.0-rc.1.md +25 -0
- package/docs/RELEASE-0.3.0-rc.2.md +9 -0
- package/docs/RELEASE-0.4.0.md +9 -0
- package/docs/THIRD_PARTY.md +1 -1
- package/docs/UNIVERSAL-INTERFACE.md +105 -0
- package/docs/v0.4.0.md +47 -0
- package/examples/composed/app.mjs +44 -0
- package/examples/composed/build.mjs +7 -0
- package/examples/composed/index.html +34 -0
- package/examples/universal/agent.mjs +52 -0
- package/examples/universal/application.mjs +16 -0
- package/examples/universal/build.mjs +6 -0
- package/examples/universal/human.mjs +16 -0
- package/examples/universal/index.html +4 -0
- package/examples/universal/main.mjs +11 -0
- package/examples/universal/validated.mjs +21 -0
- package/package.json +24 -3
|
@@ -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,9 @@
|
|
|
1
|
+
# Veil UI 0.4.0 — Developer Preview
|
|
2
|
+
|
|
3
|
+
Veil UI 0.4.0 adds bounded compound conditions, conditional visibility and Button disabling, validated multi-input submissions, Select, and Textarea to the universal interface API. Existing root, `/inspection`, `/composition`, `/interface`, and `/dom` entry points remain available. The [validated request example](../examples/universal/validated.mjs) demonstrates the new controls. [v0.4.0.md](v0.4.0.md) describes the contract and migration details.
|
|
4
|
+
|
|
5
|
+
The package is ESM. Node.js 24 or newer is the qualified server baseline; the DOM entry point is qualified in Chrome at mobile and desktop sizes. TypeScript 6.0.2 with strict NodeNext resolution is the qualified type baseline. UI has one runtime dependency, Zod 4.x. Core remains an optional peer with range `^0.4.0`: the interface and DOM APIs do not require it, while capability graph consumers can install Core. Published Core 0.4.0 is the supported and qualified integration baseline. Isolated 0.5.0 and 0.6.0 probes do not expand the declared peer range.
|
|
6
|
+
|
|
7
|
+
Hosts must bound raw bytes before parsing, allowlist actions, validate inputs again at the service boundary, and authorize effects. Trusted custom components own their external resources. Candidate preparation failures preserve controller state, revision, and mounted view. A commit-phase exception preserves controller state and revision, but cannot generally undo already applied DOM changes or external effects; renderer commits must be nonthrowing. Live-model reliability, Firefox, WebKit, and actual screen-reader behavior are outside the current qualification evidence.
|
|
8
|
+
|
|
9
|
+
Install the exact qualified tarball for review with `npm install /absolute/path/veil-runtime-ui-0.4.0.tgz`. Publication and the proposed `v0.4.0` Git tag require separate maintainer approval of the final source commit and artifact SHA-256. This document is release text, not a publication announcement.
|
package/docs/THIRD_PARTY.md
CHANGED
|
@@ -13,4 +13,4 @@ The npm tarball includes this library's compiled JavaScript/declarations, docume
|
|
|
13
13
|
|
|
14
14
|
MIT, Apache-2.0, ISC, BSD-2-Clause, BSD-3-Clause, and 0BSD are the reviewed permissive license categories accepted by the qualification inventory. The [Apache Software Foundation third-party policy, Category A](https://www.apache.org/legal/resolved.html#category-a) lists Apache, MIT/ISC, BSD, and 0BSD as acceptable for inclusion subject to their notice requirements. Here those packages remain external dependencies; their own grants/notices are retained. No compatibility exception or relicensing of dependencies is claimed.
|
|
15
15
|
|
|
16
|
-
The release gate checks the actual downloaded production graph,
|
|
16
|
+
The release gate checks the actual downloaded production graph in its Core consumer, as well as UI's runtime dependency independently, against this explicit license list and writes a versioned inventory. Core is an optional peer for UI, but is installed explicitly in that consumer. It also extracts the exact tarball and checks LICENSE and NOTICE byte-for-byte against the reviewed source files. Neither a package.json license label alone nor root lab dependency metadata is treated as proof of distribution contents.
|
|
@@ -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.
|
package/docs/v0.4.0.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# v0.4.0 Developer Preview
|
|
2
|
+
|
|
3
|
+
Version 0.4.0 extends the existing universal interface descriptor. Existing descriptors and controller APIs remain supported. No historical tag is changed.
|
|
4
|
+
|
|
5
|
+
## Fields and submission
|
|
6
|
+
|
|
7
|
+
Input, Select, and Textarea accept `validation: { required, minLength, maxLength, min, max }`. Every property is optional. Required is boolean; lengths are integers from 0 to 4096; numeric bounds are finite numbers. Reversed bounds are rejected. Length rules apply to text fields; numeric bounds require Input with inputType number. Checkbox required means true. String lengths count UTF-16 code units. Whitespace is text, not empty.
|
|
8
|
+
|
|
9
|
+
Missing, null, and empty string are empty. Optional empty fields pass. Nonempty values must match the field type; Select values must be declared options. Validation does not prevent editing invalid intermediate state. `getInterfaceValidation(snapshot)` returns field-keyed errors for visible fields, excluding hidden ancestors. Errors are recomputed after state, data, and definition changes without an extra revision or event.
|
|
10
|
+
|
|
11
|
+
A Button with `validate: true` validates all visible fields before dispatch. Invalid submission throws `Interface validation failed`, delivers no host event, and does not change state or revision. The DOM click handler isolates the error through onError. Buttons without this flag retain existing behavior, supporting cancel/navigation actions. Hosts must use validated buttons for submission and still authorize and validate requests at their service boundary.
|
|
12
|
+
|
|
13
|
+
Fields have native labels, required state, aria-invalid, and aria-describedby linked to live field feedback. Errors appear immediately, including initial required errors. Hidden field state is retained but not validated for submission. There is no asynchronous validation or executable rule language.
|
|
14
|
+
|
|
15
|
+
## Components
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{"key":"kind","component":"Select","label":"Kind","binding":{"source":"state","path":"kind"},"options":[{"value":"supplies","label":"Supplies"}],"validation":{"required":true}}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Select accepts 1–100 unique string values and labels, each at most 256 characters. Empty string denotes no selection. A native empty prompt precedes nonempty options.
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{"key":"notes","component":"Textarea","label":"Details","binding":{"source":"state","path":"notes"},"validation":{"maxLength":1000}}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Both components use scalar state bindings and the existing lifecycle/transaction machinery. State retention requires unchanged definition ID, field key, component, input type, binding path, and a default of the same scalar type. Group related fields with Stack and Heading; no competing form container is introduced.
|
|
28
|
+
|
|
29
|
+
## Conditions
|
|
30
|
+
|
|
31
|
+
Legacy `{ "binding": { "source":"state", "path":"name" }, "equals":"Ada" }` remains valid. Comparisons use `{binding, operator, value}` with ne, lt, lte, gt, gte. Numeric comparisons require finite numeric operands and never coerce strings. Missing values fail numeric comparisons; strict inequality against a scalar is true for missing values.
|
|
32
|
+
|
|
33
|
+
Compose conditions with `{all:[condition,...]}`, `{any:[condition,...]}`, or `{not:condition}`. Each condition tree permits depth 8, 64 nodes, and 1–16 operands per logical array. Unknown properties/operators are rejected. Conditions read own-property data/state bindings only.
|
|
34
|
+
|
|
35
|
+
All nodes accept visible; Buttons also accept disabled conditions. Hidden ancestors and disabled Buttons prevent direct controller dispatch as well as DOM interaction. Host allowedActions remains independent of conditions.
|
|
36
|
+
|
|
37
|
+
## Integration and boundaries
|
|
38
|
+
|
|
39
|
+
Import universal APIs from `@veil-runtime/ui/interface` and DOM APIs from `@veil-runtime/ui/dom`; no Core or AI provider is required. Capability APIs retain legitimate Core types. The optional peer range remains ^0.4.0; isolated 0.5.0/0.6.0 probes are evidence, not a claim that all integration contracts have been qualified.
|
|
40
|
+
|
|
41
|
+
The existing component allowlist, safe bindings, bounded data/tree limits, text-only rendering, revisions, and atomic candidate rejection remain in force. New fields do not execute actions or network calls. See `examples/universal/validated.mjs` for a validated form, conditional fields, multiple input types, and host-controlled submission. The repository-local universal playground includes it; no external website is changed.
|
|
42
|
+
|
|
43
|
+
## Compatibility and transaction qualification
|
|
44
|
+
|
|
45
|
+
The exported `InterfaceCondition` retains the v0.3.0 equality interface with required `binding` and `equals`. `InterfaceConditionExpression` is the expanded union used by visibility, disabling, and the evaluator. Existing interface inheritance and direct equality-binding access remain supported.
|
|
46
|
+
|
|
47
|
+
Candidate preparation failures leave controller state, revision, and the mounted DOM unchanged. Disposal during preparation or commit rejects controller state advancement and stops subsequent commits. A commit-phase exception preserves controller state/revision and discards prepared candidates in reverse order, but discard is resource cleanup, not a general rollback of already-applied DOM or external side effects. Renderer commits must therefore be nonthrowing; arbitrary commit failures cannot promise cross-renderer visual rollback. This limitation is explicitly tested rather than claimed as full transaction atomicity.
|
|
@@ -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,11 @@
|
|
|
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 };
|
|
8
|
+
|
|
9
|
+
import { mountValidated } from './validated.mjs';
|
|
10
|
+
const validatedTarget = document.createElement('section'); validatedTarget.id = 'validated'; document.body.append(validatedTarget);
|
|
11
|
+
globalThis.veilValidated = mountValidated(validatedTarget);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createMountedInterface } from '../../dist/dom.js';
|
|
2
|
+
export const validatedDefinition = {
|
|
3
|
+
id: 'request', actions: ['submit'], state: { name: '', kind: '', notes: '', quantity: 1 },
|
|
4
|
+
screen: { key: 'request', component: 'Stack', children: [
|
|
5
|
+
{ key: 'title', component: 'Heading', text: 'Request supplies' },
|
|
6
|
+
{ key: 'name', component: 'Input', label: 'Your name', binding: { source: 'state', path: 'name' }, validation: { required: true, minLength: 2, maxLength: 80 } },
|
|
7
|
+
{ key: 'kind', component: 'Select', label: 'Request type', binding: { source: 'state', path: 'kind' }, options: [{ value: 'supplies', label: 'Supplies' }, { value: 'other', label: 'Other' }], validation: { required: true } },
|
|
8
|
+
{ key: 'quantity', component: 'Input', inputType: 'number', label: 'Quantity', binding: { source: 'state', path: 'quantity' }, validation: { required: true, min: 1, max: 20 }, visible: { binding: { source: 'state', path: 'kind' }, equals: 'supplies' } },
|
|
9
|
+
{ key: 'notes', component: 'Textarea', label: 'Details', binding: { source: 'state', path: 'notes' }, validation: { required: true, maxLength: 1000 }, visible: { binding: { source: 'state', path: 'kind' }, equals: 'other' } },
|
|
10
|
+
{ key: 'submit', component: 'Button', text: 'Submit request', action: 'submit', validate: true },
|
|
11
|
+
{ key: 'feedback', component: 'Status', text: { source: 'data', path: 'message' } },
|
|
12
|
+
] },
|
|
13
|
+
};
|
|
14
|
+
export function mountValidated(target) {
|
|
15
|
+
const controller = createMountedInterface(target, validatedDefinition, {
|
|
16
|
+
allowedActions: ['submit'],
|
|
17
|
+
onEvent(event) { controller.updateData({ message: `Received request from ${event.state.name}.` }); },
|
|
18
|
+
onError() { /* Field feedback explains invalid submissions; hosts may also log errors. */ },
|
|
19
|
+
});
|
|
20
|
+
return controller;
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veil-runtime/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
7
|
-
"tag": "
|
|
7
|
+
"tag": "latest"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
-
"description": "Veil UI
|
|
10
|
+
"description": "Veil UI universal interface foundation and deterministic capability descriptions (Developer Preview).",
|
|
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
|
},
|