@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
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
# 0.3.0-rc.2 — review remediation candidate
|
|
2
|
+
|
|
3
|
+
- Reject inherited composition field names and stage bounded state changes before acceptance.
|
|
4
|
+
- Apply independent resource limits to interface definition, data, and state snapshot copies.
|
|
5
|
+
- Preserve conditional layout visibility, numeric edit spelling, focus and selection, current accessibility labels, and the previous screen when replacement mounting fails.
|
|
6
|
+
- Route form fallback submissions locally, keep field/error IDs unique, and exercise only visible actions in the model qualification harness.
|
|
7
|
+
- Add controller, browser, harness, and adversarial boundary regressions for all review findings.
|
|
8
|
+
|
|
9
|
+
# 0.3.0-rc.1 — Developer Preview release candidate
|
|
10
|
+
|
|
11
|
+
- Prepare the universal interface engine for independent qualification and external consumption.
|
|
12
|
+
- Add a provider-neutral six-scenario model harness with deterministic fixtures; live model reliability remains unverified until provider access is available.
|
|
13
|
+
- Preserve the v0.1.0 and v0.2.0 APIs. Publication remains subject to maintainer approval of the exact candidate artifact.
|
|
14
|
+
|
|
15
|
+
# 0.3.0-dev.0 — dynamic universal interface work
|
|
16
|
+
|
|
17
|
+
- Add a single-call DOM mount helper with one teardown, a JSON-safe built-in capability manifest, and a strict TypeScript type for built-in screens.
|
|
18
|
+
- Add Row, Heading, List, and Status primitives; reject unsupported properties on built-ins and validate bound collections before mounting.
|
|
19
|
+
- Exercise six workflows from an independently installed tarball in TypeScript and JavaScript; update the three browser demonstrations to use the public helper and manifest.
|
|
20
|
+
- Make controller and mounted DOM acceptance transactional for generated definition, data, and local state updates; failed preparation preserves the accepted revision and view.
|
|
21
|
+
- Prepare detached custom replacements before commit and dispose rejected candidates; restore rejected input edits in the DOM.
|
|
22
|
+
- Retain compatible input state and DOM identity across definition updates, including keyed reordering and focus/selection.
|
|
23
|
+
- Add optional custom DOM component update/dispose hooks and an abort signal for owned resources.
|
|
24
|
+
- Stage new DOM subtrees before structural changes; reject invalid definitions without changing the current controller.
|
|
25
|
+
- Reject dispatch to hidden actions and expand the simulated agent and browser lifecycle regressions.
|
|
26
|
+
- Keep the development version unpublished; the 0.2.0-rc.1 checkpoint remains at `c65e752`.
|
|
27
|
+
|
|
28
|
+
# 0.2.0-rc.1 — application composition foundation (release candidate)
|
|
29
|
+
|
|
30
|
+
- Added a bounded one-screen composition schema, reactive controller, typed action result, and native DOM renderer.
|
|
31
|
+
- Added `/composition` and `/dom` subpaths and an independently installable example without Core.
|
|
32
|
+
- Marked Core as an optional peer for the new API; preserved the existing capability graph APIs and `/inspection` export.
|
|
33
|
+
- Hardened field metadata and child validation; added controller disposal, late-result suppression, DOM listener cleanup on remount, external field synchronization, and focus on invalid submission.
|
|
34
|
+
- This is a release candidate for maintainer review. No package has been published from this branch.
|
|
35
|
+
|
|
1
36
|
# Proposed v0.1.0 release notes — release candidate
|
|
2
37
|
|
|
3
38
|
Veil UI — Deterministic Composition Engine (Developer Preview).
|
|
@@ -13,3 +48,11 @@ Veil UI — Deterministic Composition Engine (Developer Preview).
|
|
|
13
48
|
Behavior changes from the private lab extraction: older Core peer ranges are removed; unknown outputs are errors rather than accepted extras; registry updates require a new registry; presentation callbacks cannot execute the supplied capability; bounded JSON-like data and real calendars are enforced. Callers must adapt service results to public DTOs. Optional presentation APIs remain experimental.
|
|
14
49
|
|
|
15
50
|
This package supplies descriptions, not a React renderer, execution host, authentication service, workflow engine, or resumable-operation protocol. Historical lab UI/Emergence limitations remain outside the supported preview. Publication requires explicit maintainer approval of the exact qualified tarball and release action; these are proposed notes, not an announcement of availability.
|
|
51
|
+
|
|
52
|
+
## 0.4.0 — Developer Preview
|
|
53
|
+
|
|
54
|
+
- Add bounded compound conditions and runtime-enforced conditional Button disabling.
|
|
55
|
+
- Add declarative field validation, accessible feedback, and opt-in validated submission.
|
|
56
|
+
- Add Select and Textarea with strict descriptors and retained state.
|
|
57
|
+
- Add a validated conditional request example and isolated Core compatibility probes.
|
|
58
|
+
- Correct development metadata following the v0.3.0 tag. See docs/v0.4.0.md for semantics and migration.
|
package/README.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Veil UI — Deterministic Composition Engine (Developer Preview)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The v0.4.0 Developer Preview's framework-neutral interface engine is documented in [UNIVERSAL-INTERFACE.md](docs/UNIVERSAL-INTERFACE.md) and [v0.4.0.md](docs/v0.4.0.md). It supports bounded JSON definitions, validated multi-input forms, conditional controls, keyed dynamic updates, local state retention, trusted component lifecycles, and host-controlled actions without Veil Core. See [RELEASE-0.4.0.md](docs/RELEASE-0.4.0.md) for release scope and limits.
|
|
4
|
+
|
|
5
|
+
The one-screen composition API and DOM renderer are documented in [COMPOSITION.md](docs/COMPOSITION.md), with a working [stock treatment example](examples/composed). The root capability-description API remains available alongside the newer interface APIs.
|
|
6
|
+
|
|
7
|
+
The root API derives and validates interface descriptions from governed capability contracts. It is framework-neutral: it returns graphs, fields, surfaces, and interaction descriptors. Your host renders those descriptions and executes authorized operations through Core.
|
|
4
8
|
|
|
5
9
|
**Developer Preview: publication requires explicit maintainer approval of the exact qualified tarball and release action.**
|
|
6
10
|
|
|
7
|
-
## Install and run
|
|
11
|
+
## Install and run
|
|
8
12
|
|
|
9
|
-
Requires Node.js 24 or newer and npm. The supported TypeScript baseline is 6.0.2, ESM with NodeNext resolution. Strict consumer checks use `ES2022` and `DOM` libraries plus Node types because public Core/MCP declarations reference web transport types.
|
|
13
|
+
Requires Node.js 24 or newer for server-side use and npm. The supported TypeScript baseline is 6.0.2, ESM with NodeNext resolution. Strict Core consumer checks use `ES2022` and `DOM` libraries plus Node types because public Core/MCP declarations reference web transport types. The `/dom` entry point requires a browser DOM and should be bundled for browser use; UI has no compiled Core runtime import. Direct CommonJS `require()` is unsupported; use ESM imports or dynamic `import()` from a CommonJS host.
|
|
10
14
|
|
|
11
15
|
From this repository:
|
|
12
16
|
|
|
@@ -15,13 +19,13 @@ npm ci
|
|
|
15
19
|
npm run qualify:ui
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
Qualification prints the
|
|
22
|
+
Qualification prints the artifact and evidence directory. It builds and tests a source-only snapshot, installs the tarball into a new consumer with **published Core 0.4.0**, runs the standalone example, checks declarations and browser bundling, compares repeated tarballs, audits production dependencies, and inspects legal files. It never publishes.
|
|
19
23
|
|
|
20
24
|
In a separate application, use the printed absolute tarball path:
|
|
21
25
|
|
|
22
26
|
```sh
|
|
23
27
|
npm init -y
|
|
24
|
-
npm install /absolute/path/veil-runtime-ui-0.
|
|
28
|
+
npm install /absolute/path/veil-runtime-ui-0.4.0.tgz
|
|
25
29
|
```
|
|
26
30
|
|
|
27
31
|
Then copy [examples/standalone/index.mjs](examples/standalone/index.mjs) into that application and run:
|
|
@@ -30,7 +34,7 @@ Then copy [examples/standalone/index.mjs](examples/standalone/index.mjs) into th
|
|
|
30
34
|
node index.mjs
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
The example prints derived input controls, rejects invalid input, demonstrates a policy-denied execution without calling the service, and displays an approved result. It uses a tiny host-owned terminal renderer, a local identity fixture, an existing-service-style operation, and authoritative Core authorization. No model, Emergence, React, or Sandbox is involved. The included example `package.json` intentionally omits the
|
|
37
|
+
For the current interface quick start, use [UNIVERSAL-INTERFACE.md](docs/UNIVERSAL-INTERFACE.md) and [examples/universal](examples/universal). The legacy capability example below also needs `@veil-runtime/core@0.4.0`; install that peer separately when running it. The example prints derived input controls, rejects invalid input, demonstrates a policy-denied execution without calling the service, and displays an approved result. It uses a tiny host-owned terminal renderer, a local identity fixture, an existing-service-style operation, and authoritative Core authorization. No model, Emergence, React, or Sandbox is involved. The included example `package.json` intentionally omits the UI dependency: install the qualified tarball explicitly using the command above.
|
|
34
38
|
|
|
35
39
|
## Minimal derivation
|
|
36
40
|
|
|
@@ -63,9 +67,9 @@ validateRichCapabilityInput(inputSchema, { name: 'Amina' });
|
|
|
63
67
|
|
|
64
68
|
## Public contract
|
|
65
69
|
|
|
66
|
-
Supported exports are the root `@veil-runtime/ui
|
|
70
|
+
Supported exports are the root `@veil-runtime/ui`, plus `/inspection`, `/composition`, `/interface`, and `/dom`. Internal dist subpaths are not API. See [API.md](docs/API.md) for the inventory and [DECISIONS.md](docs/DECISIONS.md) for compatibility decisions.
|
|
67
71
|
|
|
68
|
-
The
|
|
72
|
+
The root capability API supports injected registry/graph validation; recursive inputs; form, table, detail, aggregate, time-series, and category-series descriptions; relationship bindings/conditions; interaction and human-input payload resolution; descriptive workflow/success-transition metadata; semantic value formatting; and composition inspection. It does not mount controls or enforce domain transitions. The separate `/dom` API mounts controls.
|
|
69
73
|
|
|
70
74
|
`createUIRegistry()` snapshots definitions. Returned metadata can be edited without changing subsequent derivations. To change descriptions or capability versions, create a new registry and refresh the host's view/context lifecycle explicitly. There is no subscription or live-update protocol.
|
|
71
75
|
|
|
@@ -88,10 +92,14 @@ npm --workspace @veil-runtime/ui run check
|
|
|
88
92
|
npm test
|
|
89
93
|
```
|
|
90
94
|
|
|
91
|
-
The build targets ES2022, emits ESM/declarations without dangling source maps, and fingerprints inputs and emitted files. `verify:dist` rejects missing, stale, altered, or extra files; `npm pack` rebuilds and verifies first. A failing `prepublishOnly` hook blocks publication from a source directory. Only the exact qualified tarball may be published after maintainer approval of its SHA-256 and the release action. npm tarball publication does not run directory lifecycle hooks; no script bypass is needed. Publication defaults to public access on `https://registry.npmjs.org/` under the `
|
|
95
|
+
The build targets ES2022, emits ESM/declarations without dangling source maps, and fingerprints inputs and emitted files. `verify:dist` rejects missing, stale, altered, or extra files; `npm pack` rebuilds and verifies first. A failing `prepublishOnly` hook blocks publication from a source directory. Only the exact qualified tarball may be published after maintainer approval of its SHA-256 and the release action. npm tarball publication does not run directory lifecycle hooks; no script bypass is needed. Publication defaults to public access on `https://registry.npmjs.org/` under the `latest` tag. Qualification verifies source-directory refusal and an exact-tarball publication dry-run; it never publishes. Neither qualification nor CI authorizes a release.
|
|
92
96
|
|
|
93
97
|
The laboratory React UI and Emergence remain historical experiments outside this release contract. The capability lab is at `/lab`; `/` opens the separate Emergence experiment. Known lab-only issues include mobile overflow, decimal input stepping, fixed/permissive demo identity/policy, process-local stores, constructor/singleton integration friction, and unavailable Core Emergence imports in its broad client/server build. These do not become supported package features through this preview. See the release qualification report for separate laboratory dependency findings.
|
|
94
98
|
|
|
95
99
|
## License
|
|
96
100
|
|
|
97
|
-
Apache License 2.0, approved by the maintainer for this package. [LICENSE](LICENSE) and [NOTICE](NOTICE) are included in the
|
|
101
|
+
Apache License 2.0, approved by the maintainer for this package. [LICENSE](LICENSE) and [NOTICE](NOTICE) are included in the qualified tarball. External dependencies retain their own licenses; see [THIRD_PARTY.md](docs/THIRD_PARTY.md). Root laboratory metadata remains separate.
|
|
102
|
+
|
|
103
|
+
### v0.4.0
|
|
104
|
+
|
|
105
|
+
See [validation, conditions, and new inputs](docs/v0.4.0.md) and the [validated request example](examples/universal/validated.mjs). Submission validation is opt-in with `validate: true` on a Button; hosts retain execution and authorization responsibility.
|
package/dist/build-manifest.json
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"inputs": {
|
|
3
|
-
"package.json": "
|
|
3
|
+
"package.json": "4ebc32386b4b6e35fa6ae6eb80c6d0140803d6ff65d2026b2e1638fd8c715754",
|
|
4
4
|
"scripts/artifact.mjs": "92d2ffd8836d0e14742528da9ddc96e0834ef873ffa81097a3ebae58d825aace",
|
|
5
|
-
"scripts/build.mjs": "
|
|
6
|
-
"scripts/check-boundary.mjs": "
|
|
5
|
+
"scripts/build.mjs": "54ca91e728925b1efefa61e5aeeb9e5bf549d25c2faa8ceb618009c214a7e03c",
|
|
6
|
+
"scripts/check-boundary.mjs": "5c09c118f7340f3a111e3947c2d98579f73903728ded9d604ac13b93077a5b29",
|
|
7
7
|
"scripts/publication-hold.mjs": "2c7a00f8f23ac34d888813e76c4ab8f5bb112526002fd6be15993825dff118ca",
|
|
8
|
+
"scripts/verify-core-matrix.mjs": "69614723a540ddc6167d9d87a25718d1f1ce36f2a51f8fcd767a7ac0f1c6259c",
|
|
8
9
|
"scripts/verify-dist.mjs": "668d5b105b7762cc2fd812091dba24f6985597da4519f72d572d5b7e63b880c0",
|
|
10
|
+
"scripts/verify-universal-consumer.mjs": "f637b99540831089f05c71d113ac958bf5dfb2a316358b75de3e0c1f29e34bd5",
|
|
9
11
|
"src/application.ts": "18cee45849d0ef9858d673b35aa42b77c4469b637669a0ab8a1ffc1f2c980d5d",
|
|
10
12
|
"src/capability-view-derivation.ts": "07367b08e115c4022c873d2d01888d0d2c26ab58ac7518500fd95331301bbaa4",
|
|
11
13
|
"src/capability-view.ts": "fda1db85ffcb26b45b60890b3a20b875ee4aff76bd3c35ce5d41018b22a64de7",
|
|
12
14
|
"src/composed-surface.ts": "b2c2fc4cfbc8c5a07048f5482f9a2807ff936f62a99cd93c5a794133d6a31175",
|
|
13
15
|
"src/composition-inspection.ts": "a9265c68bab785ba9e38ea83899997daec9322de10b98fdafaa44bc523ca0c3a",
|
|
16
|
+
"src/composition.ts": "4d06b8fe4ed61b2e0a4daf07770be708b2f0167a35ae41140b4047b7cd8fbd78",
|
|
14
17
|
"src/derive-input-fields.ts": "cf8c7ee9f98ed0838dbf299fb4653814c3e70004fc121d8c99752aa137331efd",
|
|
15
18
|
"src/derive-output-view.ts": "81d622d5430f7b9333a41c33794d8d22e3e539e861e04830ce089d83a6790a8e",
|
|
19
|
+
"src/dom.ts": "a7580ecc8599de9df2554390beaf430ac5217b0adb7e7e9145f0caaedc39e3f1",
|
|
16
20
|
"src/execution-request.ts": "775c3beba47aae482acbc2a48461ab8f947ca7b773c71a69077af387557ae344",
|
|
17
21
|
"src/experience-orchestration.ts": "f156497d99828d15da2b42f3155e4ec0b77fc72ad6e6d846bb9bbea4d6791fa4",
|
|
18
22
|
"src/format-semantic-value.ts": "2192cd723c5a9d64164287ddae99c0aa105024e1d0778a597d0b71a2ea8ab9dd",
|
|
19
|
-
"src/index.ts": "
|
|
20
|
-
"src/input-schema.ts": "
|
|
23
|
+
"src/index.ts": "403cb5e536fe1df95c4911de7fe221dcf5d118f097cbd56bbdf7d19f6da1b7c7",
|
|
24
|
+
"src/input-schema.ts": "3f40e707882efc81f5e537c8ad8a2cdc4fd279e6362d32ee6fb55a09ac298ee7",
|
|
21
25
|
"src/interaction-resolution.ts": "e9a75d501ded82fe773f03d2dcb1e2fa8b396a98d9c2902888a3f6b3cbf8f55e",
|
|
26
|
+
"src/interface-transaction.ts": "fe415c5318d0f0df78760dc374968dcfee42b0327e8796f3195aef07ccd19e91",
|
|
27
|
+
"src/interface.ts": "ea35aa01aac62478cf218b1980823200063c79451bf44542b1a1e5541b0fef4f",
|
|
22
28
|
"src/output-presentation-suggestion-schema.ts": "2421d9f152cb4ce5e4d560f8ebe1d4029147dcab01a187c67008f36a3dfd6bf4",
|
|
23
29
|
"src/output-schema.ts": "68f7f49fed5badafb5ca509365802dec6526a56c23d2bf2837b350852823688b",
|
|
24
|
-
"src/presentation-composition.ts": "
|
|
30
|
+
"src/presentation-composition.ts": "0116ae011d577396b5eff5b11a4c6c58b196b7610e71f0f3a0898e63de6457f0",
|
|
25
31
|
"src/presentation-context.ts": "34c2796cb23c6086e6ee9474b756a03fb69d713aca28c0cdb7d6b0bd60f5c248",
|
|
26
32
|
"src/presentation-suggestion-schema.ts": "f618db5e8caf439e0ae758a74a38edf8db57485662f1ddad29614e68425b1d41",
|
|
27
33
|
"src/registry.ts": "de906003b8098202c905532cfd497ab0e2f9092226be2d9f469c5cf883891a8c",
|
|
@@ -29,7 +35,7 @@
|
|
|
29
35
|
"src/validate-application.ts": "f76874d49955d7ade1d0f8c5f995b63a6026102173b3e50b710517ccbc1442f4",
|
|
30
36
|
"src/validation-safety.ts": "e0bcc379269e3f51a0021dd4797c422f813b41141274125705b425d2b1b1eb2b",
|
|
31
37
|
"src/workflow.ts": "6c5b31242474c0a37b1a9f85717eebf3b1d4b373c17c8d47861804e28d3302e5",
|
|
32
|
-
"tsconfig.json": "
|
|
38
|
+
"tsconfig.json": "d4ec334afc2daeccd982d4c3410b53f39df080f3cd1b761557f8e46dc96ab8aa"
|
|
33
39
|
},
|
|
34
40
|
"outputs": {
|
|
35
41
|
"application.d.ts": "2c3091787abcd5a8005bfe951dc012056219b5ba6d70e5d8ad275281cf481d86",
|
|
@@ -42,28 +48,36 @@
|
|
|
42
48
|
"composed-surface.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881",
|
|
43
49
|
"composition-inspection.d.ts": "dca099a4a9776626a3b0fffe16f65aeecf4de100eab84287906c03f94c059941",
|
|
44
50
|
"composition-inspection.js": "29527de641d75b0ea6710aa5407f9d0362ef5c61595694ced3745dd2ace92b72",
|
|
51
|
+
"composition.d.ts": "b76e23e686ca428378274bf74d55986ccb0bc91b95edc32beb6f82427e7a9c8b",
|
|
52
|
+
"composition.js": "6840719dac8b9239a67c647961fe32552b6fa98d30422bf3438edca993267147",
|
|
45
53
|
"derive-input-fields.d.ts": "417aa8a884416e1a679a69d938592908488611fb453cb4e84b3666d351ca1045",
|
|
46
54
|
"derive-input-fields.js": "de940148e3c029b786fec95a520a3d8a8d1eef1d361436fa607b2990b63f2adc",
|
|
47
55
|
"derive-output-view.d.ts": "9ca3c32ac65215bb85ae74b75121c55c2ac82d04d7754d38ebc0de94d3fa898a",
|
|
48
56
|
"derive-output-view.js": "e54de4b2ece567a518d346cfd8d96fe2179f5a1fcf76cc6306c5222184e19b73",
|
|
57
|
+
"dom.d.ts": "ae12ba88b9bf43bdc99a9c2ca8923764cf28e77d55b132a05dc5965cba34a6a5",
|
|
58
|
+
"dom.js": "decdde492af176b865643e0c11992724e33c4146b155f9b9f9ca10f889769b6f",
|
|
49
59
|
"execution-request.d.ts": "f8b68cf456bfbeb63a968378e5ccaef5f55a908022e323901668ca99ebe75a3a",
|
|
50
60
|
"execution-request.js": "f32bd2e9de70dd9f243d08d0fce4898e847fa3d34d77d3376a1b3c5839edb611",
|
|
51
61
|
"experience-orchestration.d.ts": "45fa9770a37acccaeb6e0f267e0a566c6d6cda6133d6e1077589859478effd77",
|
|
52
62
|
"experience-orchestration.js": "3ffccbcdc94bb2083f8e2227755651825d279f93b48f0c71d32d595c297b3fc2",
|
|
53
63
|
"format-semantic-value.d.ts": "8ecb9dff99d819af71251a909e307e11e07f5c76bec1e8796a8cefcd0692a921",
|
|
54
64
|
"format-semantic-value.js": "dd36b084312a57949a0a6e104e1c84a92ba334488e5e4dc7555f5d16a7eea7aa",
|
|
55
|
-
"index.d.ts": "
|
|
56
|
-
"index.js": "
|
|
57
|
-
"input-schema.d.ts": "
|
|
65
|
+
"index.d.ts": "403cb5e536fe1df95c4911de7fe221dcf5d118f097cbd56bbdf7d19f6da1b7c7",
|
|
66
|
+
"index.js": "403cb5e536fe1df95c4911de7fe221dcf5d118f097cbd56bbdf7d19f6da1b7c7",
|
|
67
|
+
"input-schema.d.ts": "e3cf583d925bc65d54250cfa3c1c3230b5df15dcb7be300a5de5fd9f0f0ab81d",
|
|
58
68
|
"input-schema.js": "f5747d9b6acdced514c1c8c80d7a8a36e972d703fc234e1ac266f02dfd19c045",
|
|
59
69
|
"interaction-resolution.d.ts": "fc3af3c0405e78e85bacf67262d7566f1b9f9ea6263bc4c200d0318795b805cf",
|
|
60
70
|
"interaction-resolution.js": "498ea435bbb9ba0c814728664652491ba6c4be5c3407233984df4cf4f16103d7",
|
|
71
|
+
"interface-transaction.d.ts": "e93b9188ebfd497a4ef180e31fb93b48052a25c5905bf94a6b018a41538f33ac",
|
|
72
|
+
"interface-transaction.js": "9bf1a2bf576576ccb25d993581980e06280fa35107c96e49f962c47a72c1dbb3",
|
|
73
|
+
"interface.d.ts": "f7e780ea478aff594de1fcbc34bed3f2239fd3f5bb8f8b6da289b86463f5cee2",
|
|
74
|
+
"interface.js": "02885e7c3293fd2c9924472844bc5e5b88391ecf747c53ee756ce8be834bf24e",
|
|
61
75
|
"output-presentation-suggestion-schema.d.ts": "88072dc37afeaab8ac05182b5d49fdf52099bbb18518ce2485feea476ea85895",
|
|
62
76
|
"output-presentation-suggestion-schema.js": "708d2f821132978ba44a2e01812e41e3eec70266e5dbeb7053f55f88142ad634",
|
|
63
77
|
"output-schema.d.ts": "8dbf090a79ab489e068e4a290dccd57e222c377f111d38e990308db8a3debca8",
|
|
64
78
|
"output-schema.js": "29345dd93ccc27fafbdc29c66fbd35e38ab9d382392ca40fcdf643dbedefc662",
|
|
65
|
-
"presentation-composition.d.ts": "
|
|
66
|
-
"presentation-composition.js": "
|
|
79
|
+
"presentation-composition.d.ts": "e2b80f670230104e6b29e6447d842d0962c2ebcbde6dd064620d5194c2cd4689",
|
|
80
|
+
"presentation-composition.js": "c3d1666f55173f4e9f839e2b6e69acd2172a086f7e002e8332fde0b7edc8aec4",
|
|
67
81
|
"presentation-context.d.ts": "f3565bd1666882a0ddd6041cb379270fd3fcb2c172a8e011b9c07ff189a404a9",
|
|
68
82
|
"presentation-context.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881",
|
|
69
83
|
"presentation-suggestion-schema.d.ts": "9fdd12079f822a608c9a3f2f3330b953fc247c65c5b6181a595ddc0b4440f37c",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { UIField } from "./capability-view.js";
|
|
2
|
+
import { type RichCapabilityInputSchema } from "./input-schema.js";
|
|
3
|
+
export interface ComponentNode {
|
|
4
|
+
component: string;
|
|
5
|
+
field?: string;
|
|
6
|
+
action?: string;
|
|
7
|
+
text?: string;
|
|
8
|
+
children?: ComponentNode[];
|
|
9
|
+
}
|
|
10
|
+
export interface ComposedApplication {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
fields: RichCapabilityInputSchema;
|
|
14
|
+
initialState?: Record<string, unknown>;
|
|
15
|
+
screen: ComponentNode;
|
|
16
|
+
actions: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export type SubmissionStatus = "idle" | "submitting" | "success" | "error";
|
|
19
|
+
export interface CompositionSnapshot {
|
|
20
|
+
values: Record<string, unknown>;
|
|
21
|
+
errors: Record<string, string>;
|
|
22
|
+
status: SubmissionStatus;
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
export type ActionResult = {
|
|
26
|
+
ok: true;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
};
|
|
30
|
+
export type ActionHandler = (input: Readonly<Record<string, unknown>>) => Promise<ActionResult> | ActionResult;
|
|
31
|
+
export type ActionHandlers = Readonly<Record<string, ActionHandler>>;
|
|
32
|
+
export declare function defineComposedApplication<const T extends ComposedApplication>(definition: T): T;
|
|
33
|
+
export declare function validateComposedApplication(definition: ComposedApplication, customComponents?: readonly string[]): void;
|
|
34
|
+
export interface CompositionController<Action extends string = string, Field extends string = string> {
|
|
35
|
+
readonly definition: ComposedApplication;
|
|
36
|
+
snapshot(): CompositionSnapshot;
|
|
37
|
+
field(name: Field): UIField;
|
|
38
|
+
setField(name: Field, value: unknown): void;
|
|
39
|
+
submit(action: Action): Promise<boolean>;
|
|
40
|
+
subscribe(listener: (snapshot: CompositionSnapshot) => void): () => void;
|
|
41
|
+
dispose(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createCompositionController<const T extends ComposedApplication>(definition: T, handlers: Readonly<Record<T["actions"][number], ActionHandler>>, customComponents?: readonly string[]): CompositionController<T["actions"][number], keyof T["fields"] & string>;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { deriveUIField } from "./derive-input-fields.js";
|
|
2
|
+
import { validateRichCapabilityInput, validateRichInputSchema, validateRichInputValue } from "./input-schema.js";
|
|
3
|
+
import { assertBoundedData, assertSafeKey, copyData } from "./validation-safety.js";
|
|
4
|
+
const builtins = new Set(["Stack", "Form", "Field", "Submit", "Text", "Status"]);
|
|
5
|
+
const identifier = /^[A-Za-z][A-Za-z0-9._-]{0,127}$/;
|
|
6
|
+
export function defineComposedApplication(definition) {
|
|
7
|
+
return definition;
|
|
8
|
+
}
|
|
9
|
+
export function validateComposedApplication(definition, customComponents = []) {
|
|
10
|
+
assertBoundedData(definition);
|
|
11
|
+
if (typeof definition.id !== "string" || !identifier.test(definition.id) || typeof definition.title !== "string" || !definition.title.trim())
|
|
12
|
+
throw new Error("Invalid application identity");
|
|
13
|
+
if (!definition.fields || typeof definition.fields !== "object" || Array.isArray(definition.fields))
|
|
14
|
+
throw new Error("Invalid field schema");
|
|
15
|
+
validateRichInputSchema(definition.fields);
|
|
16
|
+
for (const [name, field] of Object.entries(definition.fields)) {
|
|
17
|
+
if (!identifier.test(name) || typeof field.required !== "boolean" || typeof field.description !== "string")
|
|
18
|
+
throw new Error(`Invalid field contract: ${name}`);
|
|
19
|
+
if (field.enum !== undefined && (!Array.isArray(field.enum) || field.enum.length === 0 || new Set(field.enum).size !== field.enum.length))
|
|
20
|
+
throw new Error(`Invalid enum: ${name}`);
|
|
21
|
+
if (field.format !== undefined && !["date", "date-time", "multiline"].includes(field.format))
|
|
22
|
+
throw new Error(`Invalid format: ${name}`);
|
|
23
|
+
if (field.semantic !== undefined && field.semantic !== "currency")
|
|
24
|
+
throw new Error(`Invalid semantic: ${name}`);
|
|
25
|
+
if (field.currency !== undefined && (typeof field.currency !== "string" || !/^[A-Z]{3}$/.test(field.currency)))
|
|
26
|
+
throw new Error(`Invalid currency: ${name}`);
|
|
27
|
+
for (const key of ["minLength", "maxLength", "minItems", "maxItems"]) {
|
|
28
|
+
const value = field[key];
|
|
29
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0))
|
|
30
|
+
throw new Error(`Invalid ${key}: ${name}`);
|
|
31
|
+
}
|
|
32
|
+
for (const key of ["minimum", "maximum"]) {
|
|
33
|
+
const value = field[key];
|
|
34
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value)))
|
|
35
|
+
throw new Error(`Invalid ${key}: ${name}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!Array.isArray(definition.actions) || new Set(definition.actions).size !== definition.actions.length || definition.actions.some((id) => typeof id !== "string" || !identifier.test(id)))
|
|
39
|
+
throw new Error("Invalid action list");
|
|
40
|
+
for (const name of customComponents) {
|
|
41
|
+
if (!identifier.test(name))
|
|
42
|
+
throw new Error(`Invalid component name: ${name}`);
|
|
43
|
+
assertSafeKey(name);
|
|
44
|
+
}
|
|
45
|
+
const known = new Set([...builtins, ...customComponents]);
|
|
46
|
+
if (known.size !== builtins.size + customComponents.length)
|
|
47
|
+
throw new Error("Component names must be unique and cannot replace built-ins");
|
|
48
|
+
const fields = new Set();
|
|
49
|
+
const actions = new Set();
|
|
50
|
+
function visit(node, insideForm) {
|
|
51
|
+
if (!node || typeof node !== "object" || !known.has(node.component))
|
|
52
|
+
throw new Error(`Unknown component: ${String(node?.component)}`);
|
|
53
|
+
if (node.children !== undefined && !Array.isArray(node.children))
|
|
54
|
+
throw new Error("Component children must be an array");
|
|
55
|
+
if (node.component === "Form" && insideForm)
|
|
56
|
+
throw new Error("Nested forms are unsupported");
|
|
57
|
+
if (node.component === "Field") {
|
|
58
|
+
if (!insideForm || !node.field || !Object.hasOwn(definition.fields, node.field) || fields.has(node.field))
|
|
59
|
+
throw new Error(`Invalid or duplicate field: ${node.field}`);
|
|
60
|
+
const type = definition.fields[node.field]?.type;
|
|
61
|
+
if (type === "object" || type === "array")
|
|
62
|
+
throw new Error(`Structured field requires a custom component: ${node.field}`);
|
|
63
|
+
fields.add(node.field);
|
|
64
|
+
}
|
|
65
|
+
else if (node.field !== undefined)
|
|
66
|
+
throw new Error("Only Field nodes accept a field binding");
|
|
67
|
+
if (node.component === "Submit") {
|
|
68
|
+
if (!insideForm || !node.action || !definition.actions.includes(node.action) || actions.has(node.action))
|
|
69
|
+
throw new Error(`Invalid or duplicate action: ${node.action}`);
|
|
70
|
+
actions.add(node.action);
|
|
71
|
+
}
|
|
72
|
+
else if (node.action !== undefined)
|
|
73
|
+
throw new Error("Only Submit nodes accept an action");
|
|
74
|
+
if (node.text !== undefined && (typeof node.text !== "string" || node.text.length > 4096))
|
|
75
|
+
throw new Error("Invalid component text");
|
|
76
|
+
if (node.component === "Field" || node.component === "Submit" || node.component === "Text" || node.component === "Status") {
|
|
77
|
+
if (node.children?.length)
|
|
78
|
+
throw new Error(`${node.component} cannot contain children`);
|
|
79
|
+
}
|
|
80
|
+
for (const child of node.children ?? [])
|
|
81
|
+
visit(child, insideForm || node.component === "Form");
|
|
82
|
+
}
|
|
83
|
+
visit(definition.screen, false);
|
|
84
|
+
if (fields.size !== Object.keys(definition.fields).length || actions.size !== definition.actions.length)
|
|
85
|
+
throw new Error("All fields and actions must appear in the screen");
|
|
86
|
+
if (definition.initialState !== undefined) {
|
|
87
|
+
if (!definition.initialState || typeof definition.initialState !== "object" || Array.isArray(definition.initialState))
|
|
88
|
+
throw new Error("Invalid initial state");
|
|
89
|
+
for (const [key, value] of Object.entries(definition.initialState)) {
|
|
90
|
+
const field = Object.hasOwn(definition.fields, key) ? definition.fields[key] : undefined;
|
|
91
|
+
if (!field)
|
|
92
|
+
throw new Error(`Unknown initial field: ${key}`);
|
|
93
|
+
validateRichInputValue(value, { ...field, required: false }, key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function createCompositionController(definition, handlers, customComponents = []) {
|
|
98
|
+
validateComposedApplication(definition, customComponents);
|
|
99
|
+
const source = copyData(definition);
|
|
100
|
+
for (const action of source.actions)
|
|
101
|
+
if (!Object.hasOwn(handlers, action) || typeof handlers[action] !== "function")
|
|
102
|
+
throw new Error(`Missing action handler: ${action}`);
|
|
103
|
+
const listeners = new Set();
|
|
104
|
+
const values = Object.fromEntries(Object.keys(source.fields).map((name) => [name, source.initialState && Object.hasOwn(source.initialState, name) ? source.initialState[name] : (source.fields[name]?.type === "boolean" ? false : "")]));
|
|
105
|
+
let errors = {};
|
|
106
|
+
let status = "idle";
|
|
107
|
+
let message = "";
|
|
108
|
+
let disposed = false;
|
|
109
|
+
const snapshot = () => copyData({ values, errors, status, message });
|
|
110
|
+
const assertSnapshot = (nextValues, nextErrors, nextStatus, nextMessage) => {
|
|
111
|
+
assertBoundedData({ values: nextValues, errors: nextErrors, status: nextStatus, message: nextMessage });
|
|
112
|
+
};
|
|
113
|
+
const errorBudget = Object.fromEntries(Object.entries(source.fields).map(([name, field]) => [name, `Check ${deriveUIField(name, field).label}`]));
|
|
114
|
+
const assertValuesResource = (nextValues) => {
|
|
115
|
+
assertSnapshot(nextValues, errorBudget, "error", "Unable to save. Please try again.");
|
|
116
|
+
};
|
|
117
|
+
assertValuesResource(values);
|
|
118
|
+
const emit = () => { for (const listener of listeners) {
|
|
119
|
+
try {
|
|
120
|
+
listener(snapshot());
|
|
121
|
+
}
|
|
122
|
+
catch { /* Host listener failures cannot change the transaction. */ }
|
|
123
|
+
} };
|
|
124
|
+
return {
|
|
125
|
+
get definition() { return copyData(source); },
|
|
126
|
+
snapshot,
|
|
127
|
+
field(name) {
|
|
128
|
+
const field = Object.hasOwn(source.fields, name) ? source.fields[name] : undefined;
|
|
129
|
+
if (!field)
|
|
130
|
+
throw new Error(`Unknown field: ${name}`);
|
|
131
|
+
return deriveUIField(name, field);
|
|
132
|
+
},
|
|
133
|
+
setField(name, value) {
|
|
134
|
+
if (disposed)
|
|
135
|
+
return;
|
|
136
|
+
const field = Object.hasOwn(source.fields, name) ? source.fields[name] : undefined;
|
|
137
|
+
if (!field)
|
|
138
|
+
throw new Error(`Unknown field: ${name}`);
|
|
139
|
+
if (status === "submitting")
|
|
140
|
+
return;
|
|
141
|
+
assertBoundedData(value);
|
|
142
|
+
const nextValues = { ...values, [name]: copyData(value) };
|
|
143
|
+
const next = { ...errors };
|
|
144
|
+
try {
|
|
145
|
+
validateRichInputValue(value, field, name);
|
|
146
|
+
delete next[name];
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
next[name] = `Check ${deriveUIField(name, field).label}`;
|
|
150
|
+
}
|
|
151
|
+
assertValuesResource(nextValues);
|
|
152
|
+
Object.assign(values, nextValues);
|
|
153
|
+
errors = next;
|
|
154
|
+
status = "idle";
|
|
155
|
+
message = "";
|
|
156
|
+
emit();
|
|
157
|
+
},
|
|
158
|
+
async submit(action) {
|
|
159
|
+
if (disposed)
|
|
160
|
+
return false;
|
|
161
|
+
if (!source.actions.includes(action))
|
|
162
|
+
throw new Error(`Unknown action: ${action}`);
|
|
163
|
+
if (status === "submitting")
|
|
164
|
+
return false;
|
|
165
|
+
const next = {};
|
|
166
|
+
for (const [name, field] of Object.entries(source.fields)) {
|
|
167
|
+
try {
|
|
168
|
+
validateRichInputValue(values[name], field, name);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
next[name] = `Check ${deriveUIField(name, field).label}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
assertSnapshot(values, next, Object.keys(next).length ? "error" : "submitting", Object.keys(next).length ? "Review the highlighted fields." : "Submitting…");
|
|
175
|
+
errors = next;
|
|
176
|
+
if (Object.keys(next).length) {
|
|
177
|
+
status = "error";
|
|
178
|
+
message = "Review the highlighted fields.";
|
|
179
|
+
emit();
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const input = copyData(values);
|
|
183
|
+
validateRichCapabilityInput(source.fields, input);
|
|
184
|
+
status = "submitting";
|
|
185
|
+
message = "Submitting…";
|
|
186
|
+
emit();
|
|
187
|
+
try {
|
|
188
|
+
const result = await handlers[action](input);
|
|
189
|
+
if (disposed)
|
|
190
|
+
return false;
|
|
191
|
+
assertBoundedData(result);
|
|
192
|
+
if (!result || typeof result !== "object" || Object.keys(result).length !== 1 || (result.ok !== true && result.ok !== false))
|
|
193
|
+
throw new Error("Invalid action result");
|
|
194
|
+
if (!result.ok)
|
|
195
|
+
throw new Error("Action failed");
|
|
196
|
+
assertSnapshot(values, errors, "success", "Saved successfully.");
|
|
197
|
+
status = "success";
|
|
198
|
+
message = "Saved successfully.";
|
|
199
|
+
emit();
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
if (disposed)
|
|
204
|
+
return false;
|
|
205
|
+
assertSnapshot(values, errors, "error", "Unable to save. Please try again.");
|
|
206
|
+
status = "error";
|
|
207
|
+
message = "Unable to save. Please try again.";
|
|
208
|
+
emit();
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
subscribe(listener) {
|
|
213
|
+
if (disposed)
|
|
214
|
+
throw new Error("Composition controller is disposed");
|
|
215
|
+
listeners.add(listener);
|
|
216
|
+
try {
|
|
217
|
+
listener(snapshot());
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
listeners.delete(listener);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
return () => { listeners.delete(listener); };
|
|
224
|
+
},
|
|
225
|
+
dispose() { disposed = true; listeners.clear(); },
|
|
226
|
+
};
|
|
227
|
+
}
|
package/dist/dom.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentNode, CompositionController } from "./composition.js";
|
|
2
|
+
import { type InterfaceController, type InterfaceDefinition, type InterfaceEvent, type InterfaceNode, type InterfaceSnapshot } from "./interface.js";
|
|
3
|
+
export type DOMComponent = (node: Readonly<ComponentNode>, context: {
|
|
4
|
+
controller: CompositionController;
|
|
5
|
+
renderChildren: () => HTMLElement[];
|
|
6
|
+
}) => HTMLElement;
|
|
7
|
+
export type DOMComponentRegistry = Readonly<Record<string, DOMComponent>>;
|
|
8
|
+
/** Mount one composed screen. Caller owns the controller, mount point, and disposal. */
|
|
9
|
+
export declare function mountComposedApplication(target: HTMLElement, controller: CompositionController, components?: DOMComponentRegistry): () => void;
|
|
10
|
+
export interface InterfaceDOMComponentInstance {
|
|
11
|
+
element: HTMLElement;
|
|
12
|
+
update?: (node: Readonly<InterfaceNode>, snapshot: InterfaceSnapshot) => void;
|
|
13
|
+
dispose?: () => void;
|
|
14
|
+
}
|
|
15
|
+
export type InterfaceDOMComponent = (node: Readonly<InterfaceNode>, context: {
|
|
16
|
+
renderChildren: () => HTMLElement[];
|
|
17
|
+
snapshot: InterfaceSnapshot;
|
|
18
|
+
signal: AbortSignal;
|
|
19
|
+
}) => HTMLElement | InterfaceDOMComponentInstance;
|
|
20
|
+
export type InterfaceDOMRegistry = Readonly<Record<string, InterfaceDOMComponent>>;
|
|
21
|
+
/** Own a controller and one DOM mount with a single teardown. Component factories remain trusted host code. */
|
|
22
|
+
export declare function createMountedInterface(target: HTMLElement, definition: InterfaceDefinition, options?: {
|
|
23
|
+
components?: InterfaceDOMRegistry;
|
|
24
|
+
allowedActions?: readonly string[];
|
|
25
|
+
onEvent?: (event: InterfaceEvent) => void | Promise<void>;
|
|
26
|
+
onError?: (error: unknown) => void;
|
|
27
|
+
}): InterfaceController & {
|
|
28
|
+
unmount(): void;
|
|
29
|
+
};
|
|
30
|
+
/** Mount a framework-neutral interface. The caller owns and disposes the controller separately. */
|
|
31
|
+
export declare function mountInterface(target: HTMLElement, controller: InterfaceController, components?: InterfaceDOMRegistry, onError?: (error: unknown) => void): () => void;
|