@slexkit/mcp 0.3.3 → 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.
@@ -4,306 +4,310 @@
4
4
 
5
5
  Raw Markdown: /docs/reference/spec.md
6
6
 
7
- ---
8
- title: Slex Specification v0.1
9
- category: Reference
10
- status: ready
11
- order: 10
12
- summary: "Public Slex expression envelope, component keys, props, directives, lifecycle, and runtime API contract."
13
- slexkitRenderMode: component
14
- ---
15
-
16
- # Slex Specification v0.1
17
-
18
- Slex expression envelope, component keys, props, directives, lifecycle, and runtime API contract — SlexKit v0's entire public protocol in one place. Source of truth for implementors, test authors, and host adapter authors.
19
-
20
- **v0/beta.** The current implementation may evolve, but the protocol version (v0.1) is independent of the SlexKit package version. The same protocol may remain stable across multiple package releases.
21
-
22
- ## 1. Slex expression envelope
23
-
24
- The canonical Slex expression is an object. Slex source is the JavaScript object literal string form of that expression:
25
-
26
- ```ts
27
- type SlexExpression = {
28
- slex?: "0.1";
29
- namespace: string;
30
- g: Record<string, unknown>;
31
- layout: Record<string, unknown>;
32
- };
33
- ```
34
-
35
- `slex`, `namespace`, `g`, and `layout` are the standard envelope fields. `slex: "0.1"` is an optional protocol marker for humans, agents, and validators; it does not affect namespace identity or state merging. The runtime also accepts a bare component tree as shorthand: if the input has component keys at the top level and does not define `namespace`, `g`, or `layout`, it is normalized to:
36
-
37
- ```js
38
- { namespace: "default", g: {}, layout: <input> }
39
- ```
40
-
41
- ## 2. Layout key
42
-
43
- Component keys use the format:
44
-
45
- ```
46
- ComponentKey = ComponentType ":" Identifier
47
- ```
48
-
49
- - `ComponentType` maps to a type in the component registry.
50
- - `Identifier` may be empty (e.g. `"box:"`).
51
- - Named components use `Identifier` for instance state and lifecycle hooks.
52
- - Keys without `:` are not rendered as component nodes.
53
-
54
- Reserved context names: `g`, `std`, `api`, `$event`, `$item`, `$index`, `$key`.
55
-
56
- ## 3. Props classification
57
-
58
- ### Static props
59
-
60
- Passed to the component unchanged:
61
-
62
- ```js
63
- "text:title": { text: "Hello" }
64
- ```
65
-
66
- ### `$` read-pipes
67
-
68
- A string value on a `$`-prefixed key (excluding `$if`, `$for`, `$key`) is evaluated as a JavaScript expression. The result is passed under the key with the `$` prefix removed:
69
-
70
- ```js
71
- "text:value": { "$content": "'Count: ' + g.count" }
72
- // Resolves to: content = "Count: <value of g.count>"
73
- ```
74
-
75
- ### `on*` write-pipes
76
-
77
- A string value on an `on*`-prefixed key is executed as a JavaScript statement:
78
-
79
- ```js
80
- "button:add": { onclick: "g.count++" }
81
- "input:name": { onchange: "g.name = String($event || '')" }
82
- ```
83
-
84
- The handler receives `$event` as the event data.
85
-
86
- ### Structural directives
87
-
88
- `$if`, `$for`, `$key` are structural directives and are never passed to the component as props. Children of `$if` and `$for` components are treated as the conditional/iterated subtree.
89
-
90
- ## 4. `g` merge
91
-
92
- When the same namespace is mounted or ingested again, the new `g` is merged into the existing store:
93
-
94
- | Value type | Merge behavior |
95
- | ------------------- | ------------------------ |
96
- | Function | Overwrites the old value |
97
- | Array | Replaces entirely |
98
- | Plain object | Recursively deep-merges |
99
- | Other scalar | Overwrites the old value |
100
- | Keys not in new `g` | Preserved from old `g` |
101
-
102
- The new `layout` always replaces the current layout. Layout is never deep-merged.
103
-
104
- ## 5. Expression context
105
-
106
- Expressions can access these variables:
107
-
108
- | Variable | Type | Scope |
109
- | ------------------ | ----------------------------- | ------------------------ |
110
- | `g` | Reactive state proxy | Always |
111
- | `std` | Pure SlexKit standard library | Always |
112
- | Component state | e.g. `slider.value` | Named components |
113
- | `api` | Host-injected object | If `api` option provided |
114
- | `$event` | Event data | `on*` handlers only |
115
- | `$item` | Current array item | `$for` context only |
116
- | `$index` | Current array index | `$for` context only |
117
- | `$key` | Current item key | `$for` context only |
118
- | Named `$for` alias | e.g. `user` for `"card:user"` | `$for` context only |
119
-
120
- `std` contains deterministic helpers for math, formatting, units, and small statistics. Sensitive capabilities stay under host-injected `api.*` and may require secure runtime policy.
121
-
122
- Expression evaluation errors are caught and produce a warning with namespace and path information. The last known value is returned as a fallback.
123
-
124
- ## 6. Component instance state
125
-
126
- Component registration declares a state mode:
127
-
128
- ```ts
129
- register(type, renderer, { state: "value" | "checked" | "enabled" | "readable" | "none" });
130
- ```
131
-
132
- | Mode | Writable | Behavior |
133
- | ---------- | ------------------ | ------------------------------------------------------------- |
134
- | `value` | `value` | Input component; `value` writable from expressions and events |
135
- | `checked` | `checked`, `value` | Checkbox-like boolean component; both synced and writable |
136
- | `enabled` | `enabled` | Switch-like boolean component; enabled state is writable |
137
- | `readable` | (none) | Readable from expressions; write emits console warning |
138
- | `none` | (none) | No instance state exposed |
139
-
140
- Input components (`value`/`checked`/`enabled` modes) sync `change` events to instance state automatically. Duplicate-named components share one namespace-level state instance.
141
-
142
- `input` with `type: "engineering"` is a value-mode component with additional parsed fields. `value` remains the raw input string, while `number`, `valid`, `prefix`, `unit`, `normalized`, and optional `error` expose the parsed engineering number. Supported v1 notation includes scientific notation plus SI prefixes (`p`, `n`, `u`, `µ`, `m`, `k`, `K`, `M`, `meg`, `G`, `T`) with an optional unit suffix. Units are captured but not dimension-converted.
143
-
144
- ## 7. `$if`
145
-
146
- `$if` controls component existence:
147
-
148
- ```js
149
- "card:panel": {
150
- "$if": "g.visible",
151
- "text:body": { text: "Visible" }
152
- }
153
- ```
154
-
155
- - **Truthy** -mounts the component and its subtree with enter animation if `$enter` is defined.
156
- - **Falsy** -unmounts the component and subtree with leave animation if `$leave` is defined, then fires lifecycle hooks, disposers, and subtree cleanup.
157
-
158
- ## 8. `$for`
159
-
160
- `$for` renders a component for each array element:
161
-
162
- ```js
163
- "text:item": {
164
- "$for": "g.items",
165
- "$key": "id",
166
- "$content": "$item.label"
167
- }
168
- ```
169
-
170
- Context variables: `$item`, `$index`, `$key`. Named components inject the current item as a same-name variable.
171
-
172
- ### `$key` strategy
173
-
174
- | `$key` value | Behavior |
175
- | ------------------------ | ------------------------------------------------------------------------------- |
176
- | `"$value"` | Use the primitive item itself |
177
- | `"id"` or other property | Read that property from object items |
178
- | Omitted | Use `item.id` if available; otherwise fall back to index with a console warning |
179
-
180
- Primitive arrays should always specify `$key: "$value"`.
181
-
182
- ### $for phases
183
-
184
- 1. **Delete** -Remove items whose keys are absent from the new array (with leave animation).
185
- 2. **Add/update/reorder** -Create new items for new keys; update retained items' `forCtx` (item reference, index); reorder DOM nodes to match array order. Updated items fire `onUpdate_<name>`.
186
- 3. **Trim** -Defensively remove excess children.
187
-
188
- ## 9. Lifecycle and cleanup
189
-
190
- Convention hooks on `g`:
191
-
192
- ```
193
- g.onMount_<name>() -after component is appended to DOM
194
- g.onUnmount_<name>() -before component is removed from DOM
195
- g.onUpdate_<name>() -after $for item changes (index or item reference)
196
- ```
197
-
198
- Component implementations can register resource cleanup:
199
-
200
- ```ts
201
- attachComponentDisposer(el, dispose);
202
- ```
203
-
204
- Component disposal is triggered by normal unmount, `$if` toggle-off, `$for` item removal, root cleanup, and `disposeNamespace()`.
205
-
206
- ## 10. Public runtime API
207
-
208
- | Function | Signature | Description |
209
- | ----------------------------------- | ------------------------------------------- | -------------------------------------------------------------------- |
210
- | `mount` | `(input, container, options?) => Cleanup` | Parse, merge state, render component tree |
211
- | `ingest` | `(input) => boolean` | Ingest state-only Slex, no rendering |
212
- | `boot` | `(options?) => void` | Enhance static page code blocks |
213
- | `disposeNamespace` | `(namespace) => void` | Release namespace roots, store, and cache |
214
- | `register` | `(type, renderer, options?) => void` | Register component type |
215
- | `getRenderer` | `(type) => ComponentRenderer \| undefined` | Look up a registered renderer |
216
- | `getIcon` | `(name, state?) => string` | Resolve a registered or bundled icon synchronously |
217
- | `loadIcon` | `(name, state?) => Promise<string>` | Resolve an icon, using Iconify fallback when not bundled |
218
- | `registerIcon` | `(name, svg, options?) => void` | Register one global SVG icon for all components with an `icon` field |
219
- | `registerIcons` | `(icons, options?) => void` | Register multiple global SVG icons |
220
- | `clearRegisteredIcons` | `() => void` | Clear all custom registered icons |
221
- | `getRegisteredIcon` | `(name, state?) => string` | Look up only registered icons (no Phosphor fallback) |
222
- | `normalizeIconName` | `(name) => string` | Normalize icon name to kebab-case with set prefix |
223
- | `resolveIconWeight` | `(state?) => IconWeight` | Resolve icon weight from component state |
224
- | `resolveIconifyIcon` | `(name, state?) => {prefix, name}` | Resolve to Iconify-compatible name pair |
225
- | `iconifySvgUrl` | `(name, state?) => string` | Build full Iconify API SVG URL |
226
- | `configureComponentScope` | `(options) => void` | Configure framework adapter flush |
227
- | `attachComponentDisposer` | `(el, dispose) => void` | Bind cleanup to element lifecycle |
228
- | `createSecureRuntime` | `(policy, adapter?) => SecureRuntimeHandle` | Create gated runtime instance |
229
- | `mountSecureArtifact` | `(input, container, options) => Cleanup` | Mount in secure sandbox |
230
- | `createSlexKitMarkdownRuntimeHost` | `(options?) => MarkdownRuntimeHost` | Create Markdown host instance |
231
- | `getSlexKitMarkdownRuntimeHost` | `() => MarkdownRuntimeHost` | Get or create global Markdown host |
232
- | `installSlexKitMarkdownRuntimeHost` | `(options?) => MarkdownRuntimeHost` | Install and return global Markdown host |
233
- | `getSlexKitRuntimeUrl` | `() => string \| undefined` | Get default sandbox runtime URL |
234
- | `setSlexKitRuntimeUrl` | `(url) => void` | Set default sandbox runtime URL |
235
- | `diagnoseSlexKitSource` | `(source, error) => Diagnostic` | Locate syntax error in source |
236
- | `parseSlexSource` | `(source) => ParseResult` | Parse Slex source to object |
237
- | `formatSlexKitDiagnostic` | `(diagnostic) => string` | Format diagnostic to readable string |
238
-
239
- ## 11. Error types
240
-
241
- ### SlexKitSyntaxError
242
-
243
- Thrown when Slex source parsing fails. Includes a `diagnostic` property with `message`, `line`, `column`, `detail`, and `excerpt`.
244
-
245
- ### SlexKitRuntimeError
246
-
247
- Thrown when a runtime operation violates policy or encounters a runtime failure. Has properties: `kind` (`"policy"` | `"network"` | `"timeout"`), `code` (specific error code string), `message`, `elapsedMs`.
248
-
249
- ## 12. Markdown language handling
250
-
251
- SlexKit hosts must only process explicit fence language tags:
252
-
253
- - `slex`
254
-
255
- Plain JavaScript, JSON, or untagged code blocks must not be scanned or executed.
256
-
257
- ## 13. Secure runtime types
258
-
259
- ```ts
260
- type SecureRuntimeHandle = {
261
- api: SlexKitRuntimeApi;
262
- dispose: () => void;
263
- };
264
- ```
265
-
266
- For full secure runtime types (`HostRuntimePolicy`, `HostRuntimeAdapter`, `SlexKitRuntimeApi`, `SecureFrameOptions`, `SecureMountOptions`, sandbox message types), see the [security runtime contract](/docs/reference/security).
267
-
268
- ## 14. ToolHost
269
-
270
- ToolHost bridges AI tool calls to interactive UI that returns structured user input. It is separate from display-oriented `slex` fences.
271
-
272
- **Public API:**
273
-
274
- | Function | Signature | Description |
275
- |----------|-----------|-------------|
276
- | `renderToolCall` | `(call, container) => ToolRenderHandle` | Compile and mount tool UI, return promise |
277
- | `registerToolTemplate` | `(name, compiler) => void` | Register a custom tool template compiler |
278
-
279
- **Result type:**
280
-
281
- ```ts
282
- type ToolResult =
283
- | { toolCallId?: string; toolName: string; status: "submitted"; value: Record<string, unknown> }
284
- | { toolCallId?: string; toolName: string; status: "ignored"; value: null };
285
- ```
286
-
287
- **Built-in templates:** `confirm-action`, `choose-options`, `option-list`, `fill-form`. Templates compile to standard Slex expressions using `card:tool` and `submit:actions` components. The `submit:actions` component serves as the completion boundary — it is only used by tool templates, not general display fences.
288
-
289
- For full template reference, arguments, type definitions, and custom template development, see [ToolHost documentation](/docs/reference/toolhost).
290
-
291
- ## 15. Icon system
292
-
293
- SlexKit includes a built-in icon system with Phosphor Icons, a custom registration API, and Iconify fallback support.
294
-
295
- **Public API (10 functions):** `registerIcon`, `registerIcons`, `clearRegisteredIcons`, `getIcon`, `getRegisteredIcon`, `loadIcon`, `normalizeIconName`, `resolveIconWeight`, `resolveIconifyIcon`, `iconifySvgUrl`.
296
-
297
- Icons are resolved through a three-tier chain: registered icons → bundled Phosphor (24 icons, 2 weights) → Iconify API fetch (async, `loadIcon` only). Components that accept an `icon` prop automatically display the resolved SVG.
298
-
299
- For the full API reference, icon list, naming conventions, and custom icon registration, see [Icon system documentation](/docs/reference/icons).
300
-
301
- ## 16. Non-goals
302
-
303
- - No public stable compatibility commitment (v0/beta).
304
- - Not a pure JSON cross-platform protocol.
305
- - No automatic security hardening of arbitrary browser APIs.
306
- - No heuristic scanning of code blocks to guess whether to render.
7
+ ---
8
+ title: Slex Specification v0.1
9
+ category: Reference
10
+ status: ready
11
+ order: 10
12
+ summary: "Public Slex expression envelope, component keys, props, directives, lifecycle, and runtime API contract."
13
+ slexkitRenderMode: component
14
+ ---
15
+
16
+ # Slex Specification v0.1
17
+
18
+ Slex expression envelope, component keys, props, directives, lifecycle, and runtime API contract — SlexKit v0's entire public protocol in one place. Canonical reference for implementors, test authors, and host adapter authors.
19
+
20
+ **v0/beta.** The current implementation may evolve, but the protocol version (v0.1) is independent of the SlexKit package version. The same protocol may remain stable across multiple package releases.
21
+
22
+ ## 1. Slex expression envelope
23
+
24
+ The canonical Slex expression is an object. Slex source is the JavaScript object literal string form of that expression:
25
+
26
+ ```ts
27
+ type SlexExpression = {
28
+ slex?: "0.1";
29
+ namespace: string;
30
+ g: Record<string, unknown>;
31
+ layout: Record<string, unknown>;
32
+ };
33
+ ```
34
+
35
+ `slex`, `namespace`, `g`, and `layout` are the standard envelope fields. `slex: "0.1"` is an optional protocol marker for humans, agents, and validators; it does not affect namespace identity or state merging. The runtime also accepts a bare component tree as shorthand: if the input has component keys at the top level and does not define `namespace`, `g`, or `layout`, it is normalized to:
36
+
37
+ ```js
38
+ { namespace: "default", g: {}, layout: <input> }
39
+ ```
40
+
41
+ ## 2. Layout key
42
+
43
+ Component keys use the format:
44
+
45
+ ```
46
+ ComponentKey = ComponentType ":" Identifier
47
+ ```
48
+
49
+ - `ComponentType` maps to a type in the component registry.
50
+ - `Identifier` may be empty (e.g. `"box:"`).
51
+ - Named components use `Identifier` for instance state and lifecycle hooks.
52
+ - Keys without `:` are not rendered as component nodes.
53
+
54
+ Reserved context names: `g`, `std`, `api`, `$event`, `$item`, `$index`, `$key`.
55
+
56
+ ## 3. Props classification
57
+
58
+ ### Static props
59
+
60
+ Passed to the component unchanged:
61
+
62
+ ```js
63
+ "text:title": { text: "Hello" }
64
+ ```
65
+
66
+ ### `$` read-pipes
67
+
68
+ A string value on a `$`-prefixed key (excluding `$if`, `$for`, `$key`) is evaluated as a JavaScript expression. The result is passed under the key with the `$` prefix removed:
69
+
70
+ ```js
71
+ "text:value": { "$content": "'Count: ' + g.count" }
72
+ // Resolves to: content = "Count: <value of g.count>"
73
+ ```
74
+
75
+ ### `on*` write-pipes
76
+
77
+ A string value on an `on*`-prefixed key is executed as a JavaScript statement:
78
+
79
+ ```js
80
+ "button:add": { onclick: "g.count++" }
81
+ "input:name": { onchange: "g.name = String($event || '')" }
82
+ ```
83
+
84
+ The handler receives `$event` as the event data.
85
+
86
+ ### Structural directives
87
+
88
+ `$if`, `$for`, `$key` are structural directives and are never passed to the component as props. Children of `$if` and `$for` components are treated as the conditional/iterated subtree.
89
+
90
+ ## 4. `g` merge
91
+
92
+ When the same namespace is mounted or ingested again, the new `g` is merged into the existing store:
93
+
94
+ | Value type | Merge behavior |
95
+ | ------------------- | ------------------------ |
96
+ | Function | Overwrites the old value |
97
+ | Array | Replaces entirely |
98
+ | Plain object | Recursively deep-merges |
99
+ | Other scalar | Overwrites the old value |
100
+ | Keys not in new `g` | Preserved from old `g` |
101
+
102
+ The new `layout` always replaces the current layout. Layout is never deep-merged.
103
+
104
+ ## 5. Expression context
105
+
106
+ Expressions can access these variables:
107
+
108
+ | Variable | Type | Scope |
109
+ | ------------------ | ----------------------------- | ------------------------ |
110
+ | `g` | Reactive state proxy | Always |
111
+ | `std` | Pure SlexKit standard library | Always |
112
+ | Component state | e.g. `slider.value` | Named components |
113
+ | `api` | Host-injected object | If `api` option provided |
114
+ | `$event` | Event data | `on*` handlers only |
115
+ | `$item` | Current array item | `$for` context only |
116
+ | `$index` | Current array index | `$for` context only |
117
+ | `$key` | Current item key | `$for` context only |
118
+ | Named `$for` alias | e.g. `user` for `"card:user"` | `$for` context only |
119
+
120
+ `std` contains deterministic helpers for math, formatting, units, and small statistics. Sensitive capabilities stay under host-injected `api.*` and may require secure runtime policy.
121
+
122
+ Expression evaluation errors are caught and produce a warning with namespace and path information. The last known value is returned as a fallback.
123
+
124
+ ## 6. Component instance state
125
+
126
+ Component registration declares a state mode:
127
+
128
+ ```ts
129
+ register(type, renderer, { state: "value" | "checked" | "enabled" | "readable" | "none" });
130
+ ```
131
+
132
+ | Mode | Writable | Behavior |
133
+ | ---------- | ------------------ | ------------------------------------------------------------- |
134
+ | `value` | `value` | Input component; `value` writable from expressions and events |
135
+ | `checked` | `checked`, `value` | Checkbox-like boolean component; both synced and writable |
136
+ | `enabled` | `enabled` | Switch-like boolean component; enabled state is writable |
137
+ | `readable` | (none) | Readable from expressions; write emits console warning |
138
+ | `none` | (none) | No instance state exposed |
139
+
140
+ Input components (`value`/`checked`/`enabled` modes) sync `change` events to instance state automatically. Duplicate-named components share one namespace-level state instance.
141
+
142
+ `input` with `type: "engineering"` is a value-mode component with additional parsed fields. `value` remains the raw input string, while `number`, `valid`, `prefix`, `unit`, `normalized`, and optional `error` expose the parsed engineering number. Supported v1 notation includes scientific notation plus SI prefixes (`p`, `n`, `u`, `µ`, `m`, `k`, `K`, `M`, `meg`, `G`, `T`) with an optional unit suffix. Units are captured but not dimension-converted.
143
+
144
+ ## 7. `$if`
145
+
146
+ `$if` controls component existence:
147
+
148
+ ```js
149
+ "card:panel": {
150
+ "$if": "g.visible",
151
+ "text:body": { text: "Visible" }
152
+ }
153
+ ```
154
+
155
+ - **Truthy** -mounts the component and its subtree with enter animation if `$enter` is defined.
156
+ - **Falsy** -unmounts the component and subtree with leave animation if `$leave` is defined, then fires lifecycle hooks, disposers, and subtree cleanup.
157
+
158
+ ## 8. `$for`
159
+
160
+ `$for` renders a component for each array element:
161
+
162
+ ```js
163
+ "text:item": {
164
+ "$for": "g.items",
165
+ "$key": "id",
166
+ "$content": "$item.label"
167
+ }
168
+ ```
169
+
170
+ Context variables: `$item`, `$index`, `$key`. Named components inject the current item as a same-name variable.
171
+
172
+ ### `$key` strategy
173
+
174
+ | `$key` value | Behavior |
175
+ | ------------------------ | ------------------------------------------------------------------------------- |
176
+ | `"$value"` | Use the primitive item itself |
177
+ | `"id"` or other property | Read that property from object items |
178
+ | Omitted | Use `item.id` if available; otherwise fall back to index with a console warning |
179
+
180
+ Primitive arrays should always specify `$key: "$value"`.
181
+
182
+ ### $for phases
183
+
184
+ 1. **Delete** -Remove items whose keys are absent from the new array (with leave animation).
185
+ 2. **Add/update/reorder** -Create new items for new keys; update retained items' `forCtx` (item reference, index); reorder DOM nodes to match array order. Updated items fire `onUpdate_<name>`.
186
+ 3. **Trim** -Defensively remove excess children.
187
+
188
+ ## 9. Lifecycle and cleanup
189
+
190
+ Convention hooks on `g`:
191
+
192
+ ```
193
+ g.onMount_<name>() -after component is appended to DOM
194
+ g.onUnmount_<name>() -before component is removed from DOM
195
+ g.onUpdate_<name>() -after $for item changes (index or item reference)
196
+ ```
197
+
198
+ Component implementations can register resource cleanup:
199
+
200
+ ```ts
201
+ attachComponentDisposer(el, dispose);
202
+ ```
203
+
204
+ Component disposal is triggered by normal unmount, `$if` toggle-off, `$for` item removal, root cleanup, and `disposeNamespace()`.
205
+
206
+ ## 10. Public runtime API
207
+
208
+ | Function | Signature | Description |
209
+ | ----------------------------------- | ------------------------------------------- | -------------------------------------------------------------------- |
210
+ | `mount` | `(input, container, options?) => Cleanup` | Parse, merge state, render component tree |
211
+ | `ingest` | `(input) => boolean` | Ingest state-only Slex, no rendering |
212
+ | `boot` | `(options?) => void` | Enhance static page code blocks |
213
+ | `disposeNamespace` | `(namespace) => void` | Release namespace roots, store, and cache |
214
+ | `register` | `(type, renderer, options?) => void` | Register component type |
215
+ | `getRenderer` | `(type) => ComponentRenderer \| undefined` | Look up a registered renderer |
216
+ | `getIcon` | `(name, state?) => string` | Resolve a registered or bundled icon synchronously |
217
+ | `loadIcon` | `(name, state?) => Promise<string>` | Resolve an icon, using Iconify fallback when not bundled |
218
+ | `registerIcon` | `(name, svg, options?) => void` | Register one global SVG icon for all components with an `icon` field |
219
+ | `registerIcons` | `(icons, options?) => void` | Register multiple global SVG icons |
220
+ | `clearRegisteredIcons` | `() => void` | Clear all custom registered icons |
221
+ | `getRegisteredIcon` | `(name, state?) => string` | Look up only registered icons (no Phosphor fallback) |
222
+ | `normalizeIconName` | `(name) => string` | Normalize icon name to kebab-case with set prefix |
223
+ | `resolveIconWeight` | `(state?) => IconWeight` | Resolve icon weight from component state |
224
+ | `resolveIconifyIcon` | `(name, state?) => {prefix, name}` | Resolve to Iconify-compatible name pair |
225
+ | `iconifySvgUrl` | `(name, state?) => string` | Build full Iconify API SVG URL |
226
+ | `configureComponentScope` | `(options) => void` | Configure framework adapter flush |
227
+ | `attachComponentDisposer` | `(el, dispose) => void` | Bind cleanup to element lifecycle |
228
+ | `createSecureRuntime` | `(policy, adapter?) => SecureRuntimeHandle` | Create gated runtime instance |
229
+ | `mountSecureArtifact` | `(input, container, options) => Cleanup` | Mount in secure sandbox |
230
+ | `createSlexKitMarkdownRuntimeHost` | `(options?) => MarkdownRuntimeHost` | Create Markdown host instance |
231
+ | `getSlexKitMarkdownRuntimeHost` | `() => MarkdownRuntimeHost` | Get or create global Markdown host |
232
+ | `installSlexKitMarkdownRuntimeHost` | `(options?) => MarkdownRuntimeHost` | Install and return global Markdown host |
233
+ | `getSlexKitRuntimeUrl` | `() => string \| undefined` | Get default sandbox runtime URL |
234
+ | `setSlexKitRuntimeUrl` | `(url) => void` | Set default sandbox runtime URL |
235
+ | `diagnoseSlexKitSource` | `(source, error) => Diagnostic` | Locate syntax error in source |
236
+ | `parseSlexSource` | `(source) => ParseResult` | Parse Slex source to object |
237
+ | `validateSlexSource` | `(source, options?) => ValidationResult` | Parse-first validation with versions, usage, and stable warning codes |
238
+ | `runSlexConformance` | `(options?) => ConformanceReport` | Run bundled standard conformance fixtures |
239
+ | `formatSlexKitDiagnostic` | `(diagnostic) => string` | Format diagnostic to readable string |
240
+
241
+ For full runtime behavior, see [Runtime Model](/docs/reference/runtime).
242
+
243
+ ## 11. Error types
244
+
245
+ ### SlexKitSyntaxError
246
+
247
+ Thrown when Slex source parsing fails. Includes a `diagnostic` property with `message`, `line`, `column`, `detail`, and `excerpt`.
248
+
249
+ ### SlexKitRuntimeError
250
+
251
+ Thrown when a runtime operation violates policy or encounters a runtime failure. Has properties: `kind` (`"policy"` | `"network"` | `"timeout"`), `code` (specific error code string), `message`, `elapsedMs`.
252
+
253
+ ## 12. Markdown language handling
254
+
255
+ SlexKit hosts must only process explicit fence language tags:
256
+
257
+ - `slex`
258
+
259
+ Plain JavaScript, JSON, or untagged code blocks must not be scanned or executed.
260
+
261
+ ## 13. Secure runtime types
262
+
263
+ ```ts
264
+ type SecureRuntimeHandle = {
265
+ api: SlexKitRuntimeApi;
266
+ dispose: () => void;
267
+ };
268
+ ```
269
+
270
+ For full secure runtime types (`HostRuntimePolicy`, `HostRuntimeAdapter`, `SlexKitRuntimeApi`, `SecureFrameOptions`, `SecureMountOptions`, sandbox message types), see the [security runtime contract](/docs/reference/security).
271
+
272
+ ## 14. ToolHost
273
+
274
+ ToolHost bridges AI tool calls to interactive UI that returns structured user input. It is separate from display-oriented `slex` fences.
275
+
276
+ **Public API:**
277
+
278
+ | Function | Signature | Description |
279
+ |----------|-----------|-------------|
280
+ | `renderToolCall` | `(call, container) => ToolRenderHandle` | Compile and mount tool UI, return promise |
281
+ | `registerToolTemplate` | `(name, compiler) => void` | Register a custom tool template compiler |
282
+
283
+ **Result type:**
284
+
285
+ ```ts
286
+ type ToolResult =
287
+ | { toolCallId?: string; toolName: string; status: "submitted"; value: Record<string, unknown> }
288
+ | { toolCallId?: string; toolName: string; status: "ignored"; value: null };
289
+ ```
290
+
291
+ **Built-in templates:** `confirm-action`, `choose-options`, `option-list`, `fill-form`. Templates compile to standard Slex expressions using `card:tool` and `submit:actions` components. The `submit:actions` component submits the tool result; it is only used by tool templates, not general display fences.
292
+
293
+ For full template reference, arguments, type definitions, and custom template development, see [ToolHost documentation](/docs/reference/toolhost).
294
+
295
+ ## 15. Icon system
296
+
297
+ SlexKit includes a built-in icon system with Phosphor Icons, a custom registration API, and Iconify fallback support.
298
+
299
+ **Public API (10 functions):** `registerIcon`, `registerIcons`, `clearRegisteredIcons`, `getIcon`, `getRegisteredIcon`, `loadIcon`, `normalizeIconName`, `resolveIconWeight`, `resolveIconifyIcon`, `iconifySvgUrl`.
300
+
301
+ Icons are resolved through a three-tier chain: registered icons → bundled Phosphor (24 icons, 2 weights) → Iconify API fetch (async, `loadIcon` only). Components that accept an `icon` prop automatically display the resolved SVG.
302
+
303
+ For the full API reference, icon list, naming conventions, and custom icon registration, see [Icon system documentation](/docs/reference/icons).
304
+
305
+ ## 16. Non-goals
306
+
307
+ - No public stable compatibility commitment (v0/beta).
308
+ - Not a pure JSON cross-platform protocol.
309
+ - No automatic security hardening of arbitrary browser APIs.
310
+ - No heuristic scanning of code blocks to guess whether to render.
307
311
  - No implicit wrapping of display UI as function calls.
308
312
 
309
313
  ---
@@ -312,244 +316,244 @@ For the full API reference, icon list, naming conventions, and custom icon regis
312
316
 
313
317
  Raw Markdown: /docs/reference/usage.md
314
318
 
315
- ---
316
- title: Slex Usage Reference
317
- category: Reference
318
- status: ready
319
- order: 20
320
- summary: "Reference for Slex source structure, props, directives, events, theming, custom components, and ToolHost boundaries."
321
- slexkitRenderMode: component
322
- ---
323
-
324
- # Slex Usage Reference
325
-
326
- Slex source shape and runtime-facing authoring rules: props, directives, events, theming, custom components, and ToolHost boundaries. For first-time setup, start with [Getting Started](/docs/guides/quick-start). For exact protocol compatibility, use the [Slex Specification](/docs/reference/spec).
327
-
328
- ## Installation
329
-
330
- Most hosts install the root package:
331
-
332
- ```sh
333
- npm install slexkit
334
- ```
335
-
336
- ```js
337
- import { mount } from "slexkit";
338
- import "slexkit/style.css";
339
- ```
340
-
341
- Component-free bare runtime:
342
-
343
- ```sh
344
- npm install slexkit @slexkit/runtime
345
- ```
346
-
347
- ```js
348
- import { mount, register } from "@slexkit/runtime";
349
- import "@slexkit/components-svelte";
350
- import "@slexkit/theme-shadcn/style.css";
351
- ```
352
-
353
- For package boundaries and host-specific install commands, see [Package Boundaries](/docs/reference/packages).
354
-
355
- ## Slex source structure
356
-
357
- A Slex source is a JavaScript object literal:
358
-
359
- ```js
360
- {
361
- slex: "0.1",
362
- namespace: "demo",
363
- g: { count: 0 },
364
- layout: {
365
- "button:add": { text: "Add", onclick: "g.count++" },
366
- "text:value": { "$content": "'Count: ' + g.count" }
367
- }
368
- }
369
- ```
370
-
371
- - `slex` -optional Slex protocol marker; use `"0.1"` for the current public protocol.
372
- - `namespace` -state domain identifier (defaults to `"default"`).
373
- - `g` -reactive state and logic (functions, data).
374
- - `layout` -component tree.
375
-
376
- As a convenience, a bare component tree (keys containing `:`) is normalized to `{ namespace: "default", g: {}, layout: <tree> }`. If the bare tree includes `slex: "0.1"`, that marker is preserved while component keys move under `layout`.
377
-
378
- ## Props
379
-
380
- ### Static props
381
-
382
- Passed to the component as-is:
383
-
384
- ```js
385
- "text:title": { text: "Hello World" }
386
- ```
387
-
388
- ### Dynamic read-pipes (`$`)
389
-
390
- A string value on a `$`-prefixed key is evaluated as a JavaScript expression. The result replaces the key stripped of the `$` prefix:
391
-
392
- ```js
393
- "text:value": { "$content": "'Count: ' + g.count" }
394
- ```
395
-
396
- The `$` prefix is removed. The prop passed to the component is `content`. Re-evaluation is triggered automatically when any reactive dependency changes.
397
-
398
- ### Write-pipes (`on*`)
399
-
400
- A string value on an `on*`-prefixed key is executed as a JavaScript statement:
401
-
402
- ```js
403
- "button:add": { onclick: "g.count++" }
404
- "input:name": { onchange: "g.name = String($event || '')" }
405
- ```
406
-
407
- The handler receives `$event` as the event data.
408
-
409
- ### Structural directives
410
-
411
- `$if`, `$for`, and `$key` are structural directives -they are not passed to the component as props.
412
-
413
- ## `$if` -conditional rendering
414
-
415
- Controls whether the component and its subtree are mounted:
416
-
417
- ```js
418
- "card:panel": {
419
- "$if": "g.visible",
420
- "text:body": { text: "I am visible" }
421
- }
422
- ```
423
-
424
- When the expression is truthy, the component mounts (with enter animation if `$enter` is defined). When falsy, it unmounts (with leave animation if `$leave` is defined, then cleanup).
425
-
426
- ## `$for` -array iteration
427
-
428
- Renders a component for each item in an array:
429
-
430
- ```js
431
- "text:item": {
432
- "$for": "g.items",
433
- "$key": "id",
434
- "$content": "$item.label"
435
- }
436
- ```
437
-
438
- Context variables available inside `$for`: `$item` (current item), `$index` (current index), `$key` (item key). Named components also inject the current item as a same-name variable (e.g. `"card:user"` makes `user` available).
439
-
440
- ### Key strategy
441
-
442
- `$key` supports:
443
- - `"$value"` -use the primitive item itself as key.
444
- - `"id"` (or any property name) -read that property from object items.
445
- - Omitted -uses `item.id` if available, otherwise falls back to index with a console warning.
446
-
447
- Primitive array items should always specify `$key: "$value"`.
448
-
449
- ### $for update algorithm
450
-
451
- 1. **Delete phase** -remove items whose keys are no longer in the array (with leave animation).
452
- 2. **Add/update/reorder phase** -create new items, update retained items' context (index, item reference), and reorder DOM nodes to match the array.
453
- 3. **Trim phase** -defensively remove any excess children.
454
-
455
- When an item's index or item reference changes, `onUpdate_<name>` is called.
456
-
457
- ## Events
458
-
459
- Event handlers are defined as `on*` write-pipes. The component's native change/input events are automatically wired to component instance state for writable components:
460
-
461
- ```js
462
- "input:name": { onchange: "g.name = String($event || '')" }
463
- ```
464
-
465
- The `$event` variable contains the event data. For change events on writable components (`value`, `checked`, or `enabled` mode), the component state is automatically synced before the handler runs.
466
-
467
- ## Trusted vs secure mode
468
-
469
- ### Trusted mode (default)
470
-
471
- Slex source executes in the host page realm. Use for application-generated content, repository-maintained Slex source, or already-reviewed snippets.
472
-
473
- ```js
474
- import { mount } from "slexkit";
475
-
476
- mount(script, container, { theme: "host-shadcn" });
477
- ```
478
-
479
- ### Secure mode
480
-
481
- Untrusted or agent-generated Slex source runs in a sandbox iframe with opaque origin. Sensitive capabilities are gated behind host policy:
482
-
483
- ```js
484
- import { mountSecureArtifact } from "slexkit";
485
-
486
- mountSecureArtifact(script, container, {
487
- policy: {},
488
- frame: { runtimeUrl: "/slexkit.runtime.js" },
489
- });
490
- ```
491
-
492
- Use [Secure Runtime Setup](/docs/guides/security-runtime) for the deployment checklist. Use the [Security Runtime Contract](/docs/reference/security) for the full policy model, sandbox behavior, bridge protocol, and fail-closed requirements.
493
-
494
- ## Theming
495
-
496
- Theme mode is resolved from the `theme` option:
497
-
498
- | Value | Behavior |
499
- |-------|----------|
500
- | `"auto"` | Checks container for known theme classes; falls back to `"uno"` |
501
- | `"host-shadcn"` | shadcn/ui compatible |
502
- | `"uno"` | Uno/Flowbite compatible |
503
- | `"flowbite"` | Flowbite compatible |
504
-
505
- Direction (`ltr`, `rtl`, `auto`) is resolved from the inherited `dir` attribute or the document element.
506
-
507
- ```js
508
- mount(script, container, { theme: "host-shadcn", dir: "auto" });
509
- ```
510
-
511
- ## Custom components
512
-
513
- Register a component type with a render function:
514
-
515
- ```ts
516
- import { register } from "slexkit";
517
-
518
- register("custom", (props, name, ctx) => {
519
- const el = ctx.document.createElement("div");
520
- el.textContent = String(props.label ?? name);
521
- return el;
522
- }, { state: "value" });
523
- ```
524
-
525
- The `RenderContext` provides:
526
-
527
- | Property | Type | Description |
528
- |----------|------|-------------|
529
- | `g` | reactive proxy | Global state |
530
- | `std` | `SlexKitStdlib` | Pure deterministic helpers |
531
- | `api` | `Record<string, unknown>` | Host-injected capabilities |
532
- | `dir` | `"ltr"` or `"rtl"` | Resolved direction |
533
- | `labels` | `Partial<Record<string, string>>` | Runtime labels |
534
- | `id` | `string \| null` | Component name |
535
- | `emit` | `(event, data?) => void` | Event emitter |
536
- | `children` | `Record<string, unknown>` | Nested component tree |
537
- | `document` | `Document` | Owner document |
538
- | `renderTree` | function | Recursive render helper |
539
-
540
- Use `attachComponentDisposer(el, fn)` to bind cleanup to the component's DOM lifecycle.
541
-
542
- ## ToolHost
543
-
544
- ToolHost handles UI that must return structured user input (confirmations, selections, forms). It is separate from display-oriented `slex` fences.
545
-
546
- Built-in templates:
547
- - `confirm-action` -yes/no confirmation
548
- - `choose-options` -single or multi-select
549
- - `option-list` -scrollable option list
550
- - `fill-form` -structured form with submit
551
-
552
- Templates compile to standard Slex source. The `submit` component serves as the completion boundary -only tool templates use it, not display fences.
319
+ ---
320
+ title: Slex Usage Reference
321
+ category: Reference
322
+ status: ready
323
+ order: 20
324
+ summary: "Slex source structure, props, directives, events, theming, custom components, and ToolHost usage."
325
+ slexkitRenderMode: component
326
+ ---
327
+
328
+ # Slex Usage Reference
329
+
330
+ Slex source authoring covers props, directives, events, theming, custom components, and ToolHost. For first-time setup, start with [Getting Started](/docs/guides/quick-start). Protocol compatibility is covered in the [Slex Specification](/docs/reference/spec).
331
+
332
+ ## Installation
333
+
334
+ Most hosts install the root package:
335
+
336
+ ```sh
337
+ npm install slexkit
338
+ ```
339
+
340
+ ```js
341
+ import { mount } from "slexkit";
342
+ import "slexkit/style.css";
343
+ ```
344
+
345
+ Component-free bare runtime:
346
+
347
+ ```sh
348
+ npm install slexkit @slexkit/runtime
349
+ ```
350
+
351
+ ```js
352
+ import { mount, register } from "@slexkit/runtime";
353
+ ```
354
+
355
+ This entry does not auto-register the official Svelte components. Use the root `slexkit` package, or explicitly import `@slexkit/components-svelte`, when you want the bundled components.
356
+
357
+ For package roles and host-specific install commands, see [Packages](/docs/reference/packages).
358
+
359
+ ## Slex source structure
360
+
361
+ A Slex source is a JavaScript object literal:
362
+
363
+ ```js
364
+ {
365
+ slex: "0.1",
366
+ namespace: "demo",
367
+ g: { count: 0 },
368
+ layout: {
369
+ "button:add": { text: "Add", onclick: "g.count++" },
370
+ "text:value": { "$content": "'Count: ' + g.count" }
371
+ }
372
+ }
373
+ ```
374
+
375
+ - `slex` - optional Slex protocol marker; use `"0.1"` for the current public protocol.
376
+ - `namespace` - state domain identifier (defaults to `"default"`).
377
+ - `g` - reactive state and logic (functions, data).
378
+ - `layout` - component tree.
379
+
380
+ As a convenience, a bare component tree (keys containing `:`) is normalized to `{ namespace: "default", g: {}, layout: <tree> }`. If the bare tree includes `slex: "0.1"`, that marker is preserved while component keys move under `layout`.
381
+
382
+ ## Props
383
+
384
+ ### Static props
385
+
386
+ Passed to the component as-is:
387
+
388
+ ```js
389
+ "text:title": { text: "Hello World" }
390
+ ```
391
+
392
+ ### Dynamic read-pipes (`$`)
393
+
394
+ A string value on a `$`-prefixed key is evaluated as a JavaScript expression. The result replaces the key stripped of the `$` prefix:
395
+
396
+ ```js
397
+ "text:value": { "$content": "'Count: ' + g.count" }
398
+ ```
399
+
400
+ The `$` prefix is removed. The prop passed to the component is `content`. Re-evaluation is triggered automatically when any reactive dependency changes.
401
+
402
+ ### Write-pipes (`on*`)
403
+
404
+ A string value on an `on*`-prefixed key is executed as a JavaScript statement:
405
+
406
+ ```js
407
+ "button:add": { onclick: "g.count++" }
408
+ "input:name": { onchange: "g.name = String($event || '')" }
409
+ ```
410
+
411
+ The handler receives `$event` as the event data.
412
+
413
+ ### Structural directives
414
+
415
+ `$if`, `$for`, and `$key` are structural directives - they are not passed to the component as props.
416
+
417
+ ## `$if` - conditional rendering
418
+
419
+ Controls whether the component and its subtree are mounted:
420
+
421
+ ```js
422
+ "card:panel": {
423
+ "$if": "g.visible",
424
+ "text:body": { text: "I am visible" }
425
+ }
426
+ ```
427
+
428
+ When the expression is truthy, the component mounts (with enter animation if `$enter` is defined). When falsy, it unmounts (with leave animation if `$leave` is defined, then cleanup).
429
+
430
+ ## `$for` - array iteration
431
+
432
+ Renders a component for each item in an array:
433
+
434
+ ```js
435
+ "text:item": {
436
+ "$for": "g.items",
437
+ "$key": "id",
438
+ "$content": "$item.label"
439
+ }
440
+ ```
441
+
442
+ Context variables available inside `$for`: `$item` (current item), `$index` (current index), `$key` (item key). Named components also inject the current item as a same-name variable (e.g. `"card:user"` makes `user` available).
443
+
444
+ ### Key strategy
445
+
446
+ `$key` supports:
447
+ - `"$value"` - use the primitive item itself as key.
448
+ - `"id"` (or any property name) - read that property from object items.
449
+ - Omitted - uses `item.id` if available, otherwise falls back to index with a console warning.
450
+
451
+ Primitive array items should always specify `$key: "$value"`.
452
+
453
+ ### $for update algorithm
454
+
455
+ 1. **Delete phase** - remove items whose keys are no longer in the array (with leave animation).
456
+ 2. **Add/update/reorder phase** - create new items, update retained items' context (index, item reference), and reorder DOM nodes to match the array.
457
+ 3. **Trim phase** - defensively remove any excess children.
458
+
459
+ When an item's index or item reference changes, `onUpdate_<name>` is called.
460
+
461
+ ## Events
462
+
463
+ Event handlers are defined as `on*` write-pipes. The component's native change/input events are automatically wired to component instance state for writable components:
464
+
465
+ ```js
466
+ "input:name": { onchange: "g.name = String($event || '')" }
467
+ ```
468
+
469
+ The `$event` variable contains the event data. For change events on writable components (`value`, `checked`, or `enabled` mode), the component state is automatically synced before the handler runs.
470
+
471
+ ## Trusted vs secure mode
472
+
473
+ ### Trusted mode (default)
474
+
475
+ Slex source executes in the host page realm. Use for application-generated content, repository-maintained Slex source, or already-reviewed snippets.
476
+
477
+ ```js
478
+ import { mount } from "slexkit";
479
+
480
+ mount(script, container, { theme: "host-shadcn" });
481
+ ```
482
+
483
+ ### Secure mode
484
+
485
+ Untrusted or agent-generated Slex source runs in a sandbox iframe with opaque origin. Sensitive capabilities are gated behind host policy:
486
+
487
+ ```js
488
+ import { mountSecureArtifact } from "slexkit";
489
+
490
+ mountSecureArtifact(script, container, {
491
+ policy: {},
492
+ frame: { runtimeUrl: "/slexkit.runtime.js" },
493
+ });
494
+ ```
495
+
496
+ Use [Secure Runtime Setup](/docs/guides/security-runtime) for deployment steps. The [Security Runtime Contract](/docs/reference/security) covers policy, sandbox behavior, bridge protocol, and fail-closed requirements.
497
+
498
+ ## Theming
499
+
500
+ Theme mode is resolved from the `theme` option:
501
+
502
+ | Value | Behavior |
503
+ |-------|----------|
504
+ | `"auto"` | Checks container for known theme classes; falls back to `"uno"` |
505
+ | `"host-shadcn"` | shadcn/ui compatible |
506
+ | `"uno"` | Uno/Flowbite compatible |
507
+ | `"flowbite"` | Flowbite compatible |
508
+
509
+ Direction (`ltr`, `rtl`, `auto`) is resolved from the inherited `dir` attribute or the document element.
510
+
511
+ ```js
512
+ mount(script, container, { theme: "host-shadcn", dir: "auto" });
513
+ ```
514
+
515
+ ## Custom components
516
+
517
+ Register a component type with a render function:
518
+
519
+ ```ts
520
+ import { register } from "slexkit";
521
+
522
+ register("custom", (props, name, ctx) => {
523
+ const el = ctx.document.createElement("div");
524
+ el.textContent = String(props.label ?? name);
525
+ return el;
526
+ }, { state: "value" });
527
+ ```
528
+
529
+ The `RenderContext` provides:
530
+
531
+ | Property | Type | Description |
532
+ |----------|------|-------------|
533
+ | `g` | reactive proxy | Global state |
534
+ | `std` | `SlexKitStdlib` | Pure deterministic helpers |
535
+ | `api` | `Record<string, unknown>` | Host-injected capabilities |
536
+ | `dir` | `"ltr"` or `"rtl"` | Resolved direction |
537
+ | `labels` | `Partial<Record<string, string>>` | Runtime labels |
538
+ | `id` | `string \| null` | Component name |
539
+ | `emit` | `(event, data?) => void` | Event emitter |
540
+ | `children` | `Record<string, unknown>` | Nested component tree |
541
+ | `document` | `Document` | Owner document |
542
+ | `renderTree` | function | Recursive render helper |
543
+
544
+ Use `attachComponentDisposer(el, fn)` to bind cleanup to the component's DOM lifecycle.
545
+
546
+ ## ToolHost
547
+
548
+ ToolHost handles UI that must return structured user input (confirmations, selections, forms). It is separate from display-oriented `slex` fences.
549
+
550
+ Built-in templates:
551
+ - `confirm-action` - yes/no confirmation
552
+ - `choose-options` - single or multi-select
553
+ - `option-list` - scrollable option list
554
+ - `fill-form` - structured form with submit
555
+
556
+ Templates compile to standard Slex source. The `submit` component submits the result; only tool templates use it, not display fences.
553
557
 
554
558
  ---
555
559
 
@@ -557,189 +561,210 @@ Templates compile to standard Slex source. The `submit` component serves as the
557
561
 
558
562
  Raw Markdown: /docs/reference/runtime.md
559
563
 
560
- ---
561
- title: Runtime Model
562
- category: Reference
563
- status: ready
564
- order: 30
565
- summary: "Mounting, ingestion, boot, namespace store, lifecycle hooks, component state, and runtime APIs."
566
- slexkitRenderMode: component
567
- ---
568
-
569
- # Runtime Model
570
-
571
- The core SlexKit runtime: entry points, namespace store, component state, lifecycle hooks, and expression evaluation.
572
-
573
- Slex source syntax is covered in the [protocol specification](/docs/reference/spec). Secure mode isolation is covered in the [security runtime contract](/docs/reference/security).
574
-
575
- ## Entry points
576
-
577
- ### `mount(input, container, options)`
578
-
579
- Parses Slex source object or source string, merges state into the namespace store, renders the component tree into `container`, returns a root cleanup function.
580
-
581
- ```ts
582
- function mount(
583
- input: SlexExpression | string,
584
- container: HTMLElement,
585
- options?: MountOptions
586
- ): () => void;
587
-
588
- type MountOptions = {
589
- theme?: "auto" | "host-shadcn" | "uno" | "flowbite";
590
- dir?: "ltr" | "rtl" | "auto";
591
- labels?: Partial<Record<string, string>>;
592
- api?: Record<string, unknown>;
593
- };
594
- ```
595
-
596
- Calling `mount()` again on the same `container` clears the old root first, then appends a new root. The returned cleanup only unmounts the current root; it does not delete the namespace store.
597
-
598
- Every expression receives `std`, SlexKit's pure deterministic standard library. Hosts can still inject capability objects through `api`, but network, timers, animation, and canvas should remain policy-gated in secure mode.
599
-
600
- ### `ingest(input)`
601
-
602
- Ingests state-only Slex: updates `g` without rendering UI. Used by the Markdown runtime host for state-only fences. Returns `true` if parsing succeeded.
603
-
604
- ```ts
605
- function ingest(input: SlexExpression | string): boolean;
606
- ```
607
-
608
- ### `disposeNamespace(namespace)`
609
-
610
- Permanently releases all roots, cleanups, store entries, and expression caches for a namespace. Call this when a document, message domain, or page section is permanently removed. Root cleanup is not equivalent to namespace disposal.
611
-
612
- ```ts
613
- function disposeNamespace(namespace: string): void;
614
- ```
615
-
616
- ### `boot(options)`
617
-
618
- Enhances static pages by auto-discovering explicitly marked Slex blocks (`<pre><code class="language-slex">`) and mounting live previews.
619
-
620
- ```ts
621
- function boot(options?: BootOptions): void;
622
-
623
- type BootOptions = {
624
- selector?: string;
625
- sourceControls?: boolean;
626
- theme?: ThemeMode;
627
- dir?: MountOptions["dir"];
628
- labels?: MountOptions["labels"];
629
- };
630
- ```
631
-
632
- Default selector covers: `language-slex`.
633
-
634
- Hosts like React/Streamdown or Obsidian should typically use the Markdown runtime host directly rather than `boot()`.
635
-
636
- ### `register(type, renderer, options)`
637
-
638
- Registers a component type with a render function and state mode.
639
-
640
- ```ts
641
- function register(
642
- type: string,
643
- renderer: ComponentRenderer,
644
- options?: ComponentRegistrationOptions
645
- ): void;
646
-
647
- type ComponentRenderer = (
648
- props: Record<string, unknown>,
649
- name: string,
650
- ctx: RenderContext
651
- ) => HTMLElement | void;
652
-
653
- type ComponentRegistrationOptions = {
654
- state?: "value" | "checked" | "enabled" | "readable" | "none";
655
- };
656
- ```
657
-
658
- ### `configureComponentScope(options)`
659
-
660
- Configures a flush function for component scope -used by framework adapters to synchronize DOM after reactive updates.
661
-
662
- ```ts
663
- function configureComponentScope(options: { flush?: () => void }): void;
664
- ```
665
-
666
- ## Namespace store
667
-
668
- `namespace` is the state domain. Multiple mounts with the same namespace share one store:
669
-
670
- - New `g` is deep-merged into the old `g` (functions overwrite, objects recursively merge, arrays replace, scalars overwrite).
671
- - New `layout` replaces the current layout (no deep merge for layout).
672
- - Component instance state is persisted within the namespace.
673
- - Expression caches are managed per namespace.
674
-
675
- This allows a document, message domain, or tool panel to update its UI incrementally while preserving state.
676
-
677
- ## Component instance state
678
-
679
- Named components can expose instance state. Which prop is writable depends on the component's registered state mode:
680
-
681
- | Mode | Writable prop | Behavior |
682
- |------|---------------|----------|
683
- | `value` | `value` | Writable from expressions and events |
684
- | `checked` | `checked`, `value` | Both synced, writable |
685
- | `enabled` | `enabled` | Switch enabled state, writable |
686
- | `readable` | (none) | Readable from expressions, write emits a console warning |
687
- | `none` | (none) | No state exposed |
688
-
689
- ```js
690
- {
691
- layout: {
692
- "slider:threshold": { value: 42 },
693
- "text:preview": { "$content": "'Threshold: ' + threshold.value" }
694
- }
695
- }
696
- ```
697
-
698
- Repeatedly named components share namespace-level state. `$for` items with the same component name also share one state instance.
699
-
700
- ## Lifecycle hooks
701
-
702
- The runtime calls convention-based hooks on the `g` object:
703
-
704
- ```
705
- g.onMount_<name>() -after component is appended to DOM
706
- g.onUnmount_<name>() -before component is removed from DOM
707
- g.onUpdate_<name>() -after $for item index or item reference changes
708
- ```
709
-
710
- These hooks fire for normal components, `$if` branches, and `$for` slots. Root cleanup and `disposeNamespace()` trigger `onUnmount`.
711
-
712
- ## Component disposer
713
-
714
- Framework components, event listeners, subscriptions, and external resources should bind their cleanup to the component DOM element:
715
-
716
- ```ts
717
- import { register, attachComponentDisposer } from "slexkit/runtime";
718
-
719
- register("custom", (props, name, ctx) => {
720
- const el = ctx.document.createElement("div");
721
- const stop = subscribeSomething();
722
- attachComponentDisposer(el, stop);
723
- return el;
724
- });
725
- ```
726
-
727
- The runtime calls the disposer when the element is unmounted. The official Svelte adapter uses this mechanism to destroy Svelte component instances.
728
-
729
- ## Expression evaluation context
730
-
731
- Expressions in `$` read-pipes and statements in `on*` write-pipes can access these variables:
732
-
733
- | Variable | Type | Availability |
734
- |----------|------|--------------|
735
- | `g` | reactive state proxy | always |
736
- | `api` | host-injected capabilities | if `api` option passed to `mount()` |
737
- | `$event` | event data | `on*` handlers only |
738
- | `$item` | current array item | `$for` context only |
739
- | `$index` | current array index | `$for` context only |
740
- | `$key` | current item key | `$for` context only |
741
- | named component state | e.g. `threshold.value` | named components |
742
-
564
+ ---
565
+ title: Runtime Model
566
+ category: Reference
567
+ status: ready
568
+ order: 30
569
+ summary: "Mounting, ingestion, boot, namespace store, lifecycle hooks, component state, and runtime APIs."
570
+ slexkitRenderMode: component
571
+ ---
572
+
573
+ # Runtime Model
574
+
575
+ The core SlexKit runtime: entry points, namespace store, component state, lifecycle hooks, and expression evaluation.
576
+
577
+ Slex source syntax is covered in the [protocol specification](/docs/reference/spec). Secure mode isolation is covered in the [security runtime contract](/docs/reference/security).
578
+
579
+ ## Entry points
580
+
581
+ ### `mount(input, container, options)`
582
+
583
+ Parses Slex source object or source string, merges state into the namespace store, renders the component tree into `container`, returns a root cleanup function.
584
+
585
+ ```ts
586
+ function mount(
587
+ input: SlexExpression | string,
588
+ container: HTMLElement,
589
+ options?: MountOptions
590
+ ): () => void;
591
+
592
+ type MountOptions = {
593
+ theme?: "auto" | "host-shadcn" | "uno" | "flowbite";
594
+ dir?: "ltr" | "rtl" | "auto";
595
+ labels?: Partial<Record<string, string>>;
596
+ api?: Record<string, unknown>;
597
+ };
598
+ ```
599
+
600
+ Calling `mount()` again on the same `container` clears the old root first, then appends a new root. The returned cleanup only unmounts the current root; it does not delete the namespace store.
601
+
602
+ Every expression receives `std`, SlexKit's pure deterministic standard library. Hosts can still inject capability objects through `api`, but network, timers, animation, and canvas should remain policy-gated in secure mode.
603
+
604
+ ### `ingest(input)`
605
+
606
+ Ingests state-only Slex: updates `g` without rendering UI. Used by the Markdown runtime host for state-only fences. Returns `true` if parsing succeeded.
607
+
608
+ ```ts
609
+ function ingest(input: SlexExpression | string): boolean;
610
+ ```
611
+
612
+ ### `disposeNamespace(namespace)`
613
+
614
+ Permanently releases all roots, cleanups, store entries, and expression caches for a namespace. Call this when a document, message domain, or page section is permanently removed. Root cleanup is not equivalent to namespace disposal.
615
+
616
+ ```ts
617
+ function disposeNamespace(namespace: string): void;
618
+ ```
619
+
620
+ ### `boot(options)`
621
+
622
+ Enhances static pages by auto-discovering explicitly marked Slex blocks (`<pre><code class="language-slex">`) and mounting live previews.
623
+
624
+ ```ts
625
+ function boot(options?: BootOptions): void;
626
+
627
+ type BootOptions = {
628
+ selector?: string;
629
+ sourceControls?: boolean;
630
+ theme?: ThemeMode;
631
+ dir?: MountOptions["dir"];
632
+ labels?: MountOptions["labels"];
633
+ };
634
+ ```
635
+
636
+ Default selector covers: `language-slex`.
637
+
638
+ Hosts like React/Streamdown or Obsidian should typically use the Markdown runtime host directly rather than `boot()`.
639
+
640
+ ### `register(type, renderer, options)`
641
+
642
+ Registers a component type with a render function and state mode.
643
+
644
+ ```ts
645
+ function register(
646
+ type: string,
647
+ renderer: ComponentRenderer,
648
+ options?: ComponentRegistrationOptions
649
+ ): void;
650
+
651
+ type ComponentRenderer = (
652
+ props: Record<string, unknown>,
653
+ name: string,
654
+ ctx: RenderContext
655
+ ) => HTMLElement | void;
656
+
657
+ type ComponentRegistrationOptions = {
658
+ state?: "value" | "checked" | "enabled" | "readable" | "none";
659
+ };
660
+ ```
661
+
662
+ ### `configureComponentScope(options)`
663
+
664
+ Configures a flush function for component scope. Framework adapters use it to synchronize DOM after reactive updates.
665
+
666
+ ```ts
667
+ function configureComponentScope(options: { flush?: () => void }): void;
668
+ ```
669
+
670
+ ## Validation and conformance
671
+
672
+ ### `validateSlexSource(source, options)`
673
+
674
+ Validates Slex source after parsing. The result includes `schemaVersion`, `protocolVersion`, `logicProfileVersion`, usage lists, and warning codes. Syntax failures return a diagnostic.
675
+
676
+ ```ts
677
+ function validateSlexSource(
678
+ source: string,
679
+ options?: { mode?: "trusted" | "secure" }
680
+ ): ValidationResult;
681
+ ```
682
+
683
+ ### `runSlexConformance(options)`
684
+
685
+ Runs the bundled standard fixtures against the current validator. Pass `fixtureId` to run one fixture.
686
+
687
+ ```ts
688
+ function runSlexConformance(options?: { fixtureId?: string }): ConformanceReport;
689
+ ```
690
+
691
+ ## Namespace store
692
+
693
+ `namespace` is the state domain. Multiple mounts with the same namespace share one store:
694
+
695
+ - New `g` is deep-merged into the old `g` (functions overwrite, objects recursively merge, arrays replace, scalars overwrite).
696
+ - New `layout` replaces the current layout (no deep merge for layout).
697
+ - Component instance state is persisted within the namespace.
698
+ - Expression caches are managed per namespace.
699
+
700
+ This allows a document, message domain, or tool panel to update its UI incrementally while preserving state.
701
+
702
+ ## Component instance state
703
+
704
+ Named components can expose instance state. Which prop is writable depends on the component's registered state mode:
705
+
706
+ | Mode | Writable prop | Behavior |
707
+ |------|---------------|----------|
708
+ | `value` | `value` | Writable from expressions and events |
709
+ | `checked` | `checked`, `value` | Both synced, writable |
710
+ | `enabled` | `enabled` | Switch enabled state, writable |
711
+ | `readable` | (none) | Readable from expressions, write emits a console warning |
712
+ | `none` | (none) | No state exposed |
713
+
714
+ ```js
715
+ {
716
+ layout: {
717
+ "slider:threshold": { value: 42 },
718
+ "text:preview": { "$content": "'Threshold: ' + threshold.value" }
719
+ }
720
+ }
721
+ ```
722
+
723
+ Repeatedly named components share namespace-level state. `$for` items with the same component name also share one state instance.
724
+
725
+ ## Lifecycle hooks
726
+
727
+ The runtime calls convention-based hooks on the `g` object:
728
+
729
+ ```
730
+ g.onMount_<name>() // after component is appended to DOM
731
+ g.onUnmount_<name>() // before component is removed from DOM
732
+ g.onUpdate_<name>() // after $for item index or item reference changes
733
+ ```
734
+
735
+ These hooks fire for normal components, `$if` branches, and `$for` slots. Root cleanup and `disposeNamespace()` trigger `onUnmount`.
736
+
737
+ ## Component disposer
738
+
739
+ Framework components, event listeners, subscriptions, and external resources should bind their cleanup to the component DOM element:
740
+
741
+ ```ts
742
+ import { register, attachComponentDisposer } from "slexkit/runtime";
743
+
744
+ register("custom", (props, name, ctx) => {
745
+ const el = ctx.document.createElement("div");
746
+ const stop = subscribeSomething();
747
+ attachComponentDisposer(el, stop);
748
+ return el;
749
+ });
750
+ ```
751
+
752
+ The runtime calls the disposer when the element is unmounted. The official Svelte adapter uses this mechanism to destroy Svelte component instances.
753
+
754
+ ## Expression evaluation context
755
+
756
+ Expressions in `$` read-pipes and statements in `on*` write-pipes can access these variables:
757
+
758
+ | Variable | Type | Availability |
759
+ |----------|------|--------------|
760
+ | `g` | reactive state proxy | always |
761
+ | `api` | host-injected capabilities | if `api` option passed to `mount()` |
762
+ | `$event` | event data | `on*` handlers only |
763
+ | `$item` | current array item | `$for` context only |
764
+ | `$index` | current array index | `$for` context only |
765
+ | `$key` | current item key | `$for` context only |
766
+ | named component state | e.g. `threshold.value` | named components |
767
+
743
768
  Expression evaluation uses `new Function()` in trusted mode. Evaluation errors are caught and produce a console warning with namespace and path information; the last known value is used as a fallback.
744
769
 
745
770
  ---
@@ -748,275 +773,334 @@ Expression evaluation uses `new Function()` in trusted mode. Evaluation errors a
748
773
 
749
774
  Raw Markdown: /docs/reference/integration.md
750
775
 
751
- ---
752
- title: Host Integration
753
- category: Reference
754
- status: ready
755
- order: 40
756
- summary: "MarkdownRuntimeHost, trusted and secure host integrations, Streamdown, Obsidian, and custom adapters."
757
- slexkitRenderMode: component
758
- ---
759
-
760
- # Host Integration
761
-
762
- How to integrate SlexKit into Markdown renderers, chat hosts, document viewers, and custom platforms.
763
-
764
- ## Core concepts
765
-
766
- ### Artifact
767
-
768
- An artifact is a group of Slex blocks belonging to the same document, message, or note. Hosts identify an artifact by an `artifactId` and group related blocks into one runtime domain.
769
-
770
- In **trusted mode**, the runtime prefixes each block's namespace with the artifact ID to prevent cross-document state pollution. `disposeArtifact()` releases all namespace stores for that artifact.
771
-
772
- In **secure mode**, all fences in one artifact are combined into a single sandbox iframe. The runtime maintains artifact slots, syncing rendered heights back to the original Markdown placeholder containers.
773
-
774
- ### Block
775
-
776
- A block is a single Slex block: one renderable unit. It has:
777
- - A source (Slex expression object or source string).
778
- - A container element (where the rendered output goes).
779
- - An optional `artifactId` (for grouping into an artifact).
780
-
781
- ### Cleanup
782
-
783
- Every block mount returns a cleanup function. The host must call it when the block is removed. For artifact-level cleanup, call `disposeArtifact(artifactId)`. For global cleanup, call `disposeAll()`.
784
-
785
- ## MarkdownRuntimeHost
786
-
787
- The `SlexKitMarkdownRuntimeHost` is the recommended API for Markdown-based hosts. It handles mode selection, artifact management, and block lifecycle.
788
-
789
- ```ts
790
- import {
791
- createSlexKitMarkdownRuntimeHost,
792
- getSlexKitMarkdownRuntimeHost,
793
- installSlexKitMarkdownRuntimeHost
794
- } from "slexkit";
795
- ```
796
-
797
- ### Interface
798
-
799
- ```ts
800
- type SlexKitMarkdownRuntimeHost = {
801
- configure(options: Partial<SlexKitMarkdownRuntimeOptions>): void;
802
- getMode(): "trusted" | "secure";
803
- mountBlock(block: SlexKitMarkdownBlock): () => void;
804
- disposeBlock(container: HTMLElement): void;
805
- disposeArtifact(artifactId: string): void;
806
- disposeAll(): void;
807
- };
808
-
809
- type SlexKitMarkdownBlock = {
810
- artifactId?: string;
811
- blockId?: string;
812
- source: SlexExpression | string;
813
- container: HTMLElement;
814
- stateOnly?: boolean;
815
- theme?: ThemeMode;
816
- dir?: MountOptions["dir"];
817
- labels?: MountOptions["labels"];
818
- };
819
-
820
- type SlexKitMarkdownRuntimeOptions = {
821
- mode?: "trusted" | "secure";
822
- policy?: HostRuntimePolicy;
823
- hostAdapter?: HostRuntimeAdapter;
824
- secureFrame?: boolean | SecureFrameOptions;
825
- theme?: ThemeMode;
826
- dir?: MountOptions["dir"];
827
- labels?: MountOptions["labels"];
828
- };
829
- ```
830
-
831
- ### Global singleton
832
-
833
- The module provides a global singleton for convenience:
834
-
835
- ```ts
836
- // Install explicitly
837
- const runtime = installSlexKitMarkdownRuntimeHost({
838
- mode: "secure",
839
- policy: { execution: { maxUnresponsiveMs: 30000 } },
840
- secureFrame: { runtimeUrl: "/slexkit.runtime.js" }
841
- });
842
-
843
- // Or use lazy global (auto-creates with defaults on first call)
844
- const runtime = getSlexKitMarkdownRuntimeHost();
845
- ```
846
-
847
- Use the singleton when the entire application shares one runtime configuration. Avoid it when different host contexts need different policies.
848
-
849
- ## Trusted mode integration
850
-
851
- Trusted mode runs Slex source in the host page realm. Use for local documents, application-generated content, or reviewed Slex source.
852
-
853
- ```ts
854
- const runtime = createSlexKitMarkdownRuntimeHost({
855
- mode: "trusted",
856
- theme: "host-shadcn"
857
- });
858
-
859
- // Detect a slex fence at position <container>
860
- const cleanup = runtime.mountBlock({
861
- artifactId: "doc-1",
862
- source: fenceSource,
863
- container: fenceContainer
864
- });
865
-
866
- // When the fence container is removed
867
- runtime.disposeBlock(fenceContainer);
868
-
869
- // When the document is closed
870
- runtime.disposeArtifact("doc-1");
871
-
872
- // When the plugin or page unloads
873
- runtime.disposeAll();
874
- ```
875
-
876
- In trusted mode, the runtime automatically scopes namespaces by artifact ID (`<artifactId>::<namespace>`) to prevent different documents from polluting each other's state.
877
-
878
- State-only blocks (no `layout`, only `g` updates) are detected automatically and ingested via `ingest()`. Subsequent renderable blocks in the same artifact can read that state.
879
-
880
- ## Secure mode integration
881
-
882
- Secure mode runs Slex source in a sandbox iframe. Use for untrusted or agent-generated Markdown.
883
-
884
- ```ts
885
- const runtime = createSlexKitMarkdownRuntimeHost({
886
- mode: "secure",
887
- policy: {
888
- execution: { maxUnresponsiveMs: 30000 }
889
- },
890
- secureFrame: {
891
- runtimeUrl: "/slexkit.runtime.js"
892
- },
893
- theme: "host-shadcn"
894
- });
895
-
896
- const cleanup = runtime.mountBlock({
897
- artifactId: "agent-msg-1",
898
- source: agentGeneratedDsl,
899
- container: fenceContainer
900
- });
901
- ```
902
-
903
- Omitted capability policies deny access by default. Add `network`, `timer`, `animation`, or `canvas` policy objects only when the host intentionally enables those capabilities.
904
-
905
- ### Artifact slot bridge
906
-
907
- When multiple secure blocks belong to the same artifact, they share one sandbox iframe. The first block (in document order) becomes the iframe anchor. Other blocks act as slots -their containers receive position and height updates from the sandbox via the postMessage bridge.
908
-
909
- ```html
910
- <!-- In Markdown, the first fence becomes the anchor -->
911
- <div id="fence-1"><!-- anchor: iframe rendered here --></div>
912
- <div id="fence-2"><!-- slot: height synced from iframe --></div>
913
- <div id="fence-3"><!-- slot: height synced from iframe --></div>
914
- ```
915
-
916
- This allows state sharing across fences within one artifact while keeping all execution confined to one sandbox.
917
-
918
- ### `runtimeUrl` requirements
919
-
920
- The `runtimeUrl` must serve the SlexKit runtime as an ES module with:
921
-
922
- ```
923
- Access-Control-Allow-Origin: *
924
- Content-Type: text/javascript
925
- ```
926
-
927
- The build output includes `dist/runtime.js` for this purpose. The `slex copy-runtime` command copies that module to `public/slexkit.runtime.js` by default so existing secure-frame URLs can stay stable. Configure your CDN or static file server to serve it with the correct headers.
928
-
929
- ## Streamdown / React integration
930
-
931
- The `@slexkit/streamdown` package provides a React/Streamdown custom renderer:
932
-
933
- ```tsx
934
- import { Streamdown } from "streamdown";
935
- import { slexkitRenderer } from "@slexkit/streamdown";
936
- import "@slexkit/theme-shadcn/style.css";
937
- import "@slexkit/streamdown/style.css";
938
-
939
- export function Message({ markdown }: { markdown: string }) {
940
- return (
941
- <Streamdown plugins={{ renderers: [slexkitRenderer] }}>
942
- {markdown}
943
- </Streamdown>
944
- );
945
- }
946
- ```
947
-
948
- The renderer handles `slex` fences. It supports both trusted and secure runtime modes and can delegate to a shared Markdown runtime host instance.
949
-
950
- ## Obsidian integration
951
-
952
- The official Obsidian plugin lives in <https://github.com/slexkit/obsidian-slexkit> and registers the `slex` fenced code block processor:
953
-
954
- ```ts
955
- // In the plugin:
956
- registerMarkdownCodeBlockProcessor("slex", (source, el, ctx) => { ... });
957
- ```
958
-
959
- The adapter renders blocks in **reading mode only** and does not write back to the vault. Blocks within the same note share a trusted artifact runtime.
960
-
961
- **Important**: The Obsidian adapter uses trusted mode because it renders content from the user's local vault. It is not designed as a security boundary for untrusted or agent-generated Markdown.
962
-
963
- ## Writing a custom host adapter
964
-
965
- To integrate SlexKit into a custom Markdown renderer or chat host:
966
-
967
- ### 1. Detect fence language
968
-
969
- Only process fences tagged with `slex`. Never scan plain JavaScript, JSON, or untagged code blocks.
970
-
971
- ### 2. Create a runtime host
972
-
973
- ```ts
974
- import { createSlexKitMarkdownRuntimeHost } from "slexkit";
975
-
976
- const runtime = createSlexKitMarkdownRuntimeHost({
977
- mode: "trusted", // or "secure"
978
- theme: "host-shadcn"
979
- });
980
- ```
981
-
982
- ### 3. Mount blocks
983
-
984
- For each detected fence, create a container element and mount:
985
-
986
- ```ts
987
- function processFence(source: string, fenceIndex: number) {
988
- const container = document.createElement("div");
989
- // Insert container at the fence position in the document
990
-
991
- const cleanup = runtime.mountBlock({
992
- artifactId: "message-42",
993
- source,
994
- container
995
- });
996
-
997
- return cleanup;
998
- }
999
- ```
1000
-
1001
- ### 4. Manage lifecycle
1002
-
1003
- ```ts
1004
- // When a single block is removed
1005
- runtime.disposeBlock(container);
1006
-
1007
- // When the entire artifact (message/document) is removed
1008
- runtime.disposeArtifact("message-42");
1009
-
1010
- // When the plugin/page unloads
1011
- runtime.disposeAll();
1012
- ```
1013
-
1014
- ### 5. Handle secure mode
1015
-
1016
- If using secure mode, serve `slexkit.runtime.js` as a public ES module with the correct CORS headers, and configure `secureFrame.runtimeUrl`.
1017
-
1018
- ## Fallback rendering
1019
-
776
+ ---
777
+ title: Host Integration
778
+ category: Reference
779
+ status: ready
780
+ order: 40
781
+ summary: "MarkdownRuntimeHost, trusted and secure host integrations, Svelte custom hosts, Streamdown, Tiptap, Obsidian, and custom adapters."
782
+ slexkitRenderMode: component
783
+ ---
784
+
785
+ # Host Integration
786
+
787
+ How to integrate SlexKit into Markdown renderers, chat hosts, document viewers, and custom platforms.
788
+
789
+ ## Core concepts
790
+
791
+ ### Artifact
792
+
793
+ An artifact is a group of Slex blocks belonging to the same document, message, or note. Hosts identify an artifact by an `artifactId` and group related blocks into one runtime domain.
794
+
795
+ In **trusted mode**, the runtime prefixes each block's namespace with the artifact ID to prevent cross-document state pollution. `disposeArtifact()` releases all namespace stores for that artifact.
796
+
797
+ In **secure mode**, all fences in one artifact are combined into a single sandbox iframe. The runtime uses artifact slots to sync rendered heights back to the original Markdown placeholder containers.
798
+
799
+ ### Block
800
+
801
+ A block is a single Slex block: one renderable unit. It has:
802
+ - A source (Slex expression object or source string).
803
+ - A container element (where the rendered output goes).
804
+ - An optional `artifactId` (for grouping into an artifact).
805
+
806
+ ### Cleanup
807
+
808
+ Every block mount returns a cleanup function. The host must call it when the block is removed. For artifact-level cleanup, call `disposeArtifact(artifactId)`. For global cleanup, call `disposeAll()`.
809
+
810
+ ## MarkdownRuntimeHost
811
+
812
+ The `SlexKitMarkdownRuntimeHost` is the recommended API for Markdown-based hosts. It handles mode selection, artifact management, and block lifecycle.
813
+
814
+ ```ts
815
+ import {
816
+ createSlexKitMarkdownRuntimeHost,
817
+ getSlexKitMarkdownRuntimeHost,
818
+ installSlexKitMarkdownRuntimeHost
819
+ } from "slexkit";
820
+ ```
821
+
822
+ ### Interface
823
+
824
+ ```ts
825
+ type SlexKitMarkdownRuntimeHost = {
826
+ configure(options: Partial<SlexKitMarkdownRuntimeOptions>): void;
827
+ getMode(): "trusted" | "secure";
828
+ mountBlock(block: SlexKitMarkdownBlock): () => void;
829
+ disposeBlock(container: HTMLElement): void;
830
+ disposeArtifact(artifactId: string): void;
831
+ disposeAll(): void;
832
+ };
833
+
834
+ type SlexKitMarkdownBlock = {
835
+ artifactId?: string;
836
+ blockId?: string;
837
+ source: SlexExpression | string;
838
+ container: HTMLElement;
839
+ stateOnly?: boolean;
840
+ theme?: ThemeMode;
841
+ dir?: MountOptions["dir"];
842
+ labels?: MountOptions["labels"];
843
+ executionMode?: MountOptions["executionMode"];
844
+ };
845
+
846
+ type SlexKitMarkdownRuntimeOptions = {
847
+ mode?: "trusted" | "secure";
848
+ policy?: HostRuntimePolicy;
849
+ hostAdapter?: HostRuntimeAdapter;
850
+ secureFrame?: boolean | SecureFrameOptions;
851
+ theme?: ThemeMode;
852
+ dir?: MountOptions["dir"];
853
+ labels?: MountOptions["labels"];
854
+ executionMode?: MountOptions["executionMode"];
855
+ };
856
+ ```
857
+
858
+ `executionMode: "preview"` is for speculative renders of repaired streaming prefixes. In preview mode, trusted mounts render readable UI but freeze write handlers, component emits, lifecycle hooks, and `api.*` capabilities. Blocks can override a host-level default by passing `executionMode` directly to `mountBlock()`.
859
+
860
+ ### Global singleton
861
+
862
+ The module provides a global singleton for convenience:
863
+
864
+ ```ts
865
+ // Install explicitly
866
+ const runtime = installSlexKitMarkdownRuntimeHost({
867
+ mode: "secure",
868
+ policy: { execution: { maxUnresponsiveMs: 30000 } },
869
+ secureFrame: { runtimeUrl: "/slexkit.runtime.js" }
870
+ });
871
+
872
+ // Or use lazy global (auto-creates with defaults on first call)
873
+ const runtime = getSlexKitMarkdownRuntimeHost();
874
+ ```
875
+
876
+ Use the singleton when the entire application shares one runtime configuration. Avoid it when different host contexts need different policies.
877
+
878
+ ## Trusted mode integration
879
+
880
+ Trusted mode runs Slex source in the host page realm. Use for local documents, application-generated content, or reviewed Slex source.
881
+
882
+ ```ts
883
+ const runtime = createSlexKitMarkdownRuntimeHost({
884
+ mode: "trusted",
885
+ theme: "host-shadcn"
886
+ });
887
+
888
+ // Detect a slex fence at position <container>
889
+ const cleanup = runtime.mountBlock({
890
+ artifactId: "doc-1",
891
+ source: fenceSource,
892
+ container: fenceContainer
893
+ });
894
+
895
+ // When the fence container is removed
896
+ runtime.disposeBlock(fenceContainer);
897
+
898
+ // When the document is closed
899
+ runtime.disposeArtifact("doc-1");
900
+
901
+ // When the plugin or page unloads
902
+ runtime.disposeAll();
903
+ ```
904
+
905
+ In trusted mode, the runtime automatically scopes namespaces by artifact ID (`<artifactId>::<namespace>`) to prevent different documents from polluting each other's state.
906
+
907
+ State-only blocks (no `layout`, only `g` updates) are detected automatically and ingested via `ingest()`. Subsequent renderable blocks in the same artifact can read that state.
908
+
909
+ ## Secure mode integration
910
+
911
+ Secure mode runs Slex source in a sandbox iframe. Use for untrusted or agent-generated Markdown.
912
+
913
+ ```ts
914
+ const runtime = createSlexKitMarkdownRuntimeHost({
915
+ mode: "secure",
916
+ policy: {
917
+ execution: { maxUnresponsiveMs: 30000 }
918
+ },
919
+ secureFrame: {
920
+ runtimeUrl: "/slexkit.runtime.js"
921
+ },
922
+ theme: "host-shadcn"
923
+ });
924
+
925
+ const cleanup = runtime.mountBlock({
926
+ artifactId: "agent-msg-1",
927
+ source: agentGeneratedDsl,
928
+ container: fenceContainer
929
+ });
930
+ ```
931
+
932
+ Omitted capability policies deny access by default. Add `network`, `timer`, `animation`, or `canvas` policy objects only when the host intentionally enables those capabilities.
933
+
934
+ ### Artifact slot bridge
935
+
936
+ When multiple secure blocks belong to the same artifact, they share one sandbox iframe. The first block (in document order) becomes the iframe anchor. Other blocks act as slots; their containers receive position and height updates from the sandbox via the postMessage bridge.
937
+
938
+ ```html
939
+ <!-- In Markdown, the first fence becomes the anchor -->
940
+ <div id="fence-1"><!-- anchor: iframe rendered here --></div>
941
+ <div id="fence-2"><!-- slot: height synced from iframe --></div>
942
+ <div id="fence-3"><!-- slot: height synced from iframe --></div>
943
+ ```
944
+
945
+ This allows state sharing across fences within one artifact while keeping all execution confined to one sandbox.
946
+
947
+ ### `runtimeUrl` requirements
948
+
949
+ The `runtimeUrl` must serve the SlexKit runtime as an ES module with:
950
+
951
+ ```
952
+ Access-Control-Allow-Origin: *
953
+ Content-Type: text/javascript
954
+ ```
955
+
956
+ The build output includes `dist/runtime.js` for this purpose. The `slex copy-runtime` command copies that module to `public/slexkit.runtime.js` by default so existing secure-frame URLs can stay stable. Configure the CDN or static file server to serve it with the correct headers.
957
+
958
+ ## Svelte custom Markdown host
959
+
960
+ The SlexKit website uses Svelte plus a custom Markdown renderer instead of a packaged Svelte Markdown adapter. The public contract is still `createSlexKitMarkdownRuntimeHost`; the Svelte pieces are host code.
961
+
962
+ ```js
963
+ import { mount, unmount } from "svelte";
964
+ import { createSlexKitMarkdownRuntimeHost } from "slexkit";
965
+ import MarkdownRenderer from "./MarkdownRenderer.svelte";
966
+
967
+ export function renderMarkdown(content, container, options = {}) {
968
+ const runtimeHost = options.slexkitRuntimeHost ?? createSlexKitMarkdownRuntimeHost({
969
+ mode: options.runtimeMode ?? "trusted",
970
+ theme: "host-shadcn"
971
+ });
972
+
973
+ const app = mount(MarkdownRenderer, {
974
+ target: container,
975
+ props: {
976
+ content,
977
+ runtimeHost,
978
+ artifactId: options.artifactId,
979
+ slexkitRenderMode: options.slexkitRenderMode ?? "component"
980
+ }
981
+ });
982
+
983
+ return () => unmount(app);
984
+ }
985
+ ```
986
+
987
+ Use this as an implementation reference when the host owns its Markdown AST or Svelte renderer. Do not treat `site/markdown/*` as a separately versioned package API.
988
+
989
+ ## Streamdown / React integration
990
+
991
+ The `@slexkit/streamdown` package provides a React/Streamdown custom renderer:
992
+
993
+ ```tsx
994
+ import { Streamdown } from "streamdown";
995
+ import { slexkitRenderer } from "@slexkit/streamdown";
996
+ import "@slexkit/theme-shadcn/style.css";
997
+ import "@slexkit/streamdown/style.css";
998
+
999
+ export function Message({ markdown }: { markdown: string }) {
1000
+ return (
1001
+ <Streamdown plugins={{ renderers: [slexkitRenderer] }}>
1002
+ {markdown}
1003
+ </Streamdown>
1004
+ );
1005
+ }
1006
+ ```
1007
+
1008
+ The renderer handles `slex` fences. It supports both trusted and secure runtime modes and can delegate to a shared Markdown runtime host instance.
1009
+
1010
+ During token streaming, `streaming="repair"` is the default for trusted renders. A parseable source mounts immediately; a source missing only EOF closing tokens mounts as preview; uncertain prefixes stay on the placeholder; clear syntax errors still render diagnostics. Use `streaming="stable"` to render only already-parseable prefixes, or `streaming={false}` to wait for the closing fence. Secure runtime and secure runtime-host renders do not execute repaired prefixes in the host realm.
1011
+
1012
+ ## Tiptap integration
1013
+
1014
+ The `@slexkit/tiptap` package provides a framework-free Tiptap `CodeBlock` extension:
1015
+
1016
+ ```ts
1017
+ import StarterKit from "@tiptap/starter-kit";
1018
+ import { Markdown } from "@tiptap/markdown";
1019
+ import { createSlexKitTiptapExtension } from "@slexkit/tiptap";
1020
+ import "@slexkit/theme-shadcn/style.css";
1021
+ import "@slexkit/tiptap/style.css";
1022
+
1023
+ const extensions = [
1024
+ StarterKit.configure({ codeBlock: false }),
1025
+ Markdown,
1026
+ createSlexKitTiptapExtension({ artifactId: "doc-1" })
1027
+ ];
1028
+ ```
1029
+
1030
+ The adapter only takes over `codeBlock` nodes with `language === "slex"`. Non-Slex code blocks remain ordinary Tiptap code blocks, and Markdown import/export keeps standard ` ```slex ` fences through Tiptap's Markdown extension.
1031
+
1032
+ Each editor instance gets one trusted Markdown runtime host by default. Blocks in that editor share an artifact ID, so state-only fences can seed later renderable fences. Pass `runtimeHost` or `artifactId` when the host needs explicit lifecycle ownership.
1033
+
1034
+ ## Obsidian integration
1035
+
1036
+ The official Obsidian plugin is available at <https://github.com/slexkit/obsidian-slexkit> and registers the `slex` fenced code block processor:
1037
+
1038
+ ```ts
1039
+ // In the plugin:
1040
+ registerMarkdownCodeBlockProcessor("slex", (source, el, ctx) => { ... });
1041
+ ```
1042
+
1043
+ The adapter renders blocks in **reading mode only** and does not write back to the vault. Blocks within the same note share a trusted artifact runtime.
1044
+
1045
+ **Important**: The Obsidian adapter uses trusted mode because it renders content from the user's local vault. It is not designed as a security boundary for untrusted or agent-generated Markdown.
1046
+
1047
+ ## Writing a custom host adapter
1048
+
1049
+ To integrate SlexKit into a custom Markdown renderer or chat host:
1050
+
1051
+ ### 1. Detect fence language
1052
+
1053
+ Only process fences tagged with `slex`. Never scan plain JavaScript, JSON, or untagged code blocks.
1054
+
1055
+ ### 2. Create a runtime host
1056
+
1057
+ ```ts
1058
+ import { createSlexKitMarkdownRuntimeHost } from "slexkit";
1059
+
1060
+ const runtime = createSlexKitMarkdownRuntimeHost({
1061
+ mode: "trusted", // or "secure"
1062
+ theme: "host-shadcn"
1063
+ });
1064
+ ```
1065
+
1066
+ ### 3. Mount blocks
1067
+
1068
+ For each detected fence, create a container element and mount:
1069
+
1070
+ ```ts
1071
+ function processFence(source: string, fenceIndex: number) {
1072
+ const container = document.createElement("div");
1073
+ // Insert container at the fence position in the document
1074
+
1075
+ const cleanup = runtime.mountBlock({
1076
+ artifactId: "message-42",
1077
+ source,
1078
+ container
1079
+ });
1080
+
1081
+ return cleanup;
1082
+ }
1083
+ ```
1084
+
1085
+ ### 4. Manage lifecycle
1086
+
1087
+ ```ts
1088
+ // When a single block is removed
1089
+ runtime.disposeBlock(container);
1090
+
1091
+ // When the entire artifact (message/document) is removed
1092
+ runtime.disposeArtifact("message-42");
1093
+
1094
+ // When the plugin/page unloads
1095
+ runtime.disposeAll();
1096
+ ```
1097
+
1098
+ ### 5. Handle secure mode
1099
+
1100
+ If using secure mode, serve `slexkit.runtime.js` as a public ES module with the correct CORS headers, and configure `secureFrame.runtimeUrl`.
1101
+
1102
+ ## Fallback rendering
1103
+
1020
1104
  SlexKit-capable hosts should still include the raw fence content or a plain text fallback in the DOM for environments that don't support SlexKit. The runtime replaces the container children, so fallback text is only visible before mount or after disposal.
1021
1105
 
1022
1106
  ---
@@ -1025,456 +1109,643 @@ SlexKit-capable hosts should still include the raw fence content or a plain text
1025
1109
 
1026
1110
  Raw Markdown: /docs/reference/security.md
1027
1111
 
1028
- ---
1029
- title: Security Runtime Contract
1030
- category: Reference
1031
- status: ready
1032
- order: 50
1033
- summary: "Threat model, sandbox iframe deployment, host policy, postMessage bridge, and fail-closed behavior."
1034
- slexkitRenderMode: component
1035
- ---
1036
-
1037
- # Security Runtime Contract
1038
-
1039
- The secure runtime defines what untrusted Slex source can and cannot do, how the host authorizes capabilities, and how sandbox isolation works.
1040
-
1041
- ## Threat model
1042
-
1043
- - The host page and host application are trusted.
1044
- - Slex source may be untrusted.
1045
- - Secure artifacts run inside a sandbox iframe.
1046
- - The iframe uses an opaque origin by default (no `allow-same-origin`).
1047
- - Slex source must not access the host DOM, cookies, `localStorage`, `IndexedDB`, or host global objects.
1048
-
1049
- Secure mode confines expression execution to an isolated environment and consolidates sensitive capabilities under the host `policy` and `api.*`.
1050
-
1051
- ## Authorization source
1052
-
1053
- The sole authorization source is the `HostRuntimePolicy` provided by the host. Slex source fields such as `capabilities`, `permissions`, `api`, or other top-level declarations cannot grant themselves authority.
1054
-
1055
- All capabilities are accessed through `api.*`:
1056
-
1057
- ```
1058
- api.get(url, options)
1059
- api.post(url, body, options)
1060
- api.fetch(url, options)
1061
-
1062
- api.setTimeout(fn, ms)
1063
- api.clearTimeout(id)
1064
- api.setInterval(fn, ms)
1065
- api.clearInterval(id)
1066
-
1067
- api.raf(fn)
1068
- api.cancelRaf(id)
1069
-
1070
- api.createCanvas(width, height)
1071
- api.getCanvasContext(canvas, contextId, options)
1072
-
1073
- api.onDispose(fn)
1074
- api.now()
1075
-
1076
- api.isTimeoutError(error)
1077
- api.isNetworkError(error)
1078
- api.isPolicyError(error)
1079
- api.errorMessage(error)
1080
- ```
1081
-
1082
- Capabilities not exposed through `api.*` are unsupported.
1083
-
1084
- ## HostRuntimePolicy
1085
-
1086
- ```ts
1087
- type HostRuntimePolicy = {
1088
- network?: {
1089
- enabled: boolean;
1090
- methods: ("GET" | "POST")[];
1091
- allowOrigins: string[];
1092
- allowHeaders?: string[];
1093
- allowContentTypes?: string[];
1094
- credentials: "omit" | "same-origin" | "include";
1095
- timeoutMs: number;
1096
- maxBodyBytes: number;
1097
- maxResponseBytes?: number;
1098
- };
1099
- timer?: {
1100
- enabled: boolean;
1101
- maxTimers: number;
1102
- minIntervalMs: number;
1103
- };
1104
- animation?: {
1105
- enabled: boolean;
1106
- };
1107
- canvas?: {
1108
- enabled: boolean;
1109
- maxCanvases?: number;
1110
- maxPixels?: number;
1111
- allowedContexts?: ("2d" | "webgl" | "webgl2" | "bitmaprenderer")[];
1112
- };
1113
- execution?: {
1114
- heartbeatIntervalMs?: number;
1115
- maxUnresponsiveMs?: number;
1116
- };
1117
- };
1118
- ```
1119
-
1120
- ### Network policy
1121
-
1122
- Network is denied by default unless `policy.network.enabled` is `true`. The policy constrains:
1123
- - HTTP method (only listed methods allowed)
1124
- - Origin (supports `*`, exact match, `protocol://*` protocol wildcard, and `protocol://*.domain` subdomain wildcard)
1125
- - Request headers (only listed headers pass; `Authorization`, `Cookie`, `Proxy-Authorization`, `Set-Cookie`, and Sec-Fetch headers are always blocked)
1126
- - Credentials mode
1127
- - Request body size
1128
- - Request timeout
1129
- - Response body size
1130
- - Response content-type
1131
-
1132
- `hostAdapter.fetch` can replace the actual request implementation. `hostAdapter.onNetworkLog` is observational only -it must not alter runtime behavior.
1133
-
1134
- ### Timer, animation, and canvas
1135
-
1136
- - **Timer**: denied by default. When enabled, subject to `maxTimers` (total concurrent) and `minIntervalMs` (minimum delay). All timers and intervals are cleaned up on dispose.
1137
- - **Animation**: denied by default. Controlled via `animation.enabled`; `api.raf` is the only animation primitive.
1138
- - **Canvas**: denied by default. When enabled, subject to `maxCanvases`, `maxPixels`, and `allowedContexts`.
1139
-
1140
- ### Execution monitoring
1141
-
1142
- `execution.heartbeatIntervalMs` controls how often the sandbox sends a heartbeat to the host. `execution.maxUnresponsiveMs` defines the maximum silence before the sandbox is terminated as unresponsive.
1143
-
1144
- ## HostRuntimeAdapter
1145
-
1146
- The adapter allows the host to override or observe runtime behavior:
1147
-
1148
- ```ts
1149
- type HostRuntimeAdapter = {
1150
- fetch?: (request: HostFetchRequest) => Promise<NetworkResult>;
1151
- onNetworkLog?: (event: RuntimeNetworkLogEvent) => void;
1152
- onRuntimeError?: (event: RuntimeErrorEvent) => void;
1153
- now?: () => number;
1154
- setTimeout?: (fn: () => void, ms: number) => TimerId;
1155
- clearTimeout?: (id: TimerId) => void;
1156
- setInterval?: (fn: () => void, ms: number) => TimerId;
1157
- clearInterval?: (id: TimerId) => void;
1158
- requestAnimationFrame?: (fn: (time: number) => void) => RafId;
1159
- cancelAnimationFrame?: (id: RafId) => void;
1160
- };
1161
- ```
1162
-
1163
- `onNetworkLog` and `onRuntimeError` are audit hooks -they must not change runtime behavior. Errors thrown within them are silently caught.
1164
-
1165
- ## Sandbox iframe deployment
1166
-
1167
- The secure frame imports the main runtime module from a `runtimeUrl`:
1168
-
1169
- ```ts
1170
- mountSecureArtifact(script, container, {
1171
- frame: {
1172
- runtimeUrl: "/slexkit.runtime.js"
1173
- }
1174
- });
1175
- ```
1176
-
1177
- This URL must serve as a public ES module and return:
1178
-
1179
- ```
1180
- Access-Control-Allow-Origin: *
1181
- Content-Type: text/javascript
1182
- ```
1183
-
1184
- This is server or deployment layer configuration -it cannot be set from frontend JavaScript.
1185
-
1186
- ### CSP
1187
-
1188
- The sandbox iframe is served via `srcdoc` with a strict Content-Security-Policy:
1189
-
1190
- ```
1191
- default-src 'none'
1192
- script-src 'nonce-{random}' 'unsafe-eval' {runtimeOrigin}
1193
- connect-src 'none'
1194
- img-src data: blob:
1195
- style-src 'unsafe-inline'
1196
- font-src data:
1197
- form-action 'none'
1198
- base-uri 'none'
1199
- ```
1200
-
1201
- A random nonce is generated for each frame instance. `unsafe-eval` is required because Slex source expression evaluation uses `eval()` inside the sandbox.
1202
-
1203
- ### Sandbox attribute
1204
-
1205
- The iframe requires `allow-scripts`. `allow-same-origin` is **blocked by default** -it would weaken the opaque origin isolation. Only set `unsafeAllowSameOrigin: true` explicitly if the host accepts the risk:
1206
-
1207
- ```ts
1208
- frame: {
1209
- unsafeAllowSameOrigin: true, // only with explicit host acceptance
1210
- sandbox: "allow-scripts allow-same-origin"
1211
- }
1212
- ```
1213
-
1214
- Do not add `allow-same-origin` to fix CORS or debugging issues.
1215
-
1216
- ## postMessage bridge protocol
1217
-
1218
- The host and sandbox communicate via `window.postMessage`. All messages are tagged with `channel: "slexkit-secure"`.
1219
-
1220
- ### Host -Sandbox messages
1221
-
1222
- | Type | Purpose |
1223
- |------|---------|
1224
- | `mount` | Sends Slex source, policy, theme to render |
1225
- | `dispose` | Tells sandbox to tear down |
1226
- | `fetch-result` | Returns fetch response or error |
1227
- | `slots` | Synchronizes artifact slot positions |
1228
-
1229
- ### Sandbox -Host messages
1230
-
1231
- | Type | Purpose |
1232
- |------|---------|
1233
- | `ready` | Runner module loaded and listening |
1234
- | `mounted` | Artifact render confirmed |
1235
- | `disposed` | Sandbox teardown acknowledged |
1236
- | `heartbeat` | Periodic liveness signal |
1237
- | `error` | Mount or runtime error |
1238
- | `fetch` | Proxied network request |
1239
- | `slot-size` | Artifact slot height report |
1240
-
1241
- Every host→sandbox message includes an `id` and `token`. The token is opaque, cryptographically random, and scoped to a single mount instance. Messages with mismatched tokens are rejected.
1242
-
1243
- Messages are verified: the sandbox checks `event.source === window.parent`; the host checks `event.source === iframe.contentWindow`.
1244
-
1245
- ## Artifact slot bridge
1246
-
1247
- Multiple Markdown fences belonging to one artifact share a single sandbox iframe. The host sends slot rectangles to the sandbox, which renders each fence's output inside the corresponding slot container. The sandbox reports each slot's rendered height back via `slot-size` messages.
1248
-
1249
- A `ResizeObserver` on the host side and inside the sandbox keeps positions and heights synchronized. This allows visual continuity across fence boundaries while keeping all execution confined to one isolation context.
1250
-
1251
- ## Heartbeat watchdog
1252
-
1253
- When `execution.maxUnresponsiveMs` is set, the host monitors the sandbox heartbeat interval. If no heartbeat arrives within the threshold, the iframe is terminated, a diagnostic alert (`role="alert"`) is rendered, and a `console.error` is emitted.
1254
-
1255
- ## Fail-closed behavior
1256
-
1257
- If the iframe cannot load the runtime, does not send a ready/mounted message, or the heartbeat times out, SlexKit:
1258
-
1259
- 1. Removes the unresponsive iframe.
1260
- 2. Renders a `role="alert"` diagnostic element with a description of the failure.
1261
- 3. Emits the same information via `console.error`.
1262
-
1263
- Load timeout is configurable via `frame.loadTimeoutMs` (default: 8000ms).
1264
-
1265
- ## Escape hatches
1266
-
1267
- ### `unsafeInlineExecution`
1268
-
1269
- Allows a secure artifact to execute inline in the host page with an injected secure runtime API. **Intended for testing or host-trusted content only.** Not recommended for untrusted paths.
1270
-
1271
- ```ts
1272
- mountSecureArtifact(script, container, {
1273
- policy,
1274
- hostAdapter,
1275
- unsafeInlineExecution: true
1276
- });
1277
- ```
1278
-
1279
- ### `unsafeAllowSameOrigin`
1280
-
1281
- Allows `allow-same-origin` in the sandbox attribute. **Reduces isolation strength** -only use when the host explicitly accepts the risk.
1282
-
1283
- ```ts
1284
- frame: {
1285
- sandbox: "allow-scripts allow-same-origin",
1286
- unsafeAllowSameOrigin: true
1287
- }
1288
- ```
1289
-
1290
- ## Sandbox hardening
1291
-
1292
- When the sandbox runner starts, it hardens the global scope:
1293
-
1294
- **Blocked network globals** -`fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `Worker`, `SharedWorker`, and `navigator.sendBeacon` are replaced with functions that throw, ensuring all network traffic must go through the bridge.
1295
-
1296
- **Blocked scheduling globals** -`setTimeout`, `setInterval`, `requestAnimationFrame` are replaced with functions that throw, directing code to `api.setTimeout()`, `api.setInterval()`, and `api.raf()`.
1297
-
1298
- **Canvas prototype wrapping** -`HTMLCanvasElement.prototype.getContext` is wrapped to enforce canvas policy on context creation. `OffscreenCanvas` is replaced with a subclass that validates dimensions on construction.
1299
-
1300
- ## Maintenance principles
1301
-
1302
- - New capabilities must define a policy field first, then an `api.*` method, then the bridge.
1303
- - Slex source declarations are never an authorization source.
1304
- - Default to opaque origin.
1305
- - Always fail closed.
1306
- - Log and error hooks are observational -they must not alter runtime behavior.
1112
+ ---
1113
+ title: Security Runtime Contract
1114
+ category: Reference
1115
+ status: ready
1116
+ order: 50
1117
+ summary: "Threat model, sandbox iframe deployment, host policy, postMessage bridge, and fail-closed behavior."
1118
+ slexkitRenderMode: component
1119
+ ---
1120
+
1121
+ # Security Runtime Contract
1122
+
1123
+ The secure runtime defines what untrusted Slex source can and cannot do, how the host authorizes capabilities, and how sandbox isolation works.
1124
+
1125
+ ## Threat model
1126
+
1127
+ - The host page and host application are trusted.
1128
+ - Slex source may be untrusted.
1129
+ - Secure artifacts run inside a sandbox iframe.
1130
+ - The iframe uses an opaque origin by default (no `allow-same-origin`).
1131
+ - Slex source must not access the host DOM, cookies, `localStorage`, `IndexedDB`, or host global objects.
1132
+
1133
+ Secure mode confines expression execution to an isolated environment and consolidates sensitive capabilities under the host `policy` and `api.*`.
1134
+
1135
+ ## Authorization source
1136
+
1137
+ The sole authorization source is the `HostRuntimePolicy` provided by the host. Slex source fields such as `capabilities`, `permissions`, `api`, or other top-level declarations cannot grant themselves authority.
1138
+
1139
+ All capabilities are accessed through `api.*`:
1140
+
1141
+ ```
1142
+ api.get(url, options)
1143
+ api.post(url, body, options)
1144
+ api.fetch(url, options)
1145
+
1146
+ api.setTimeout(fn, ms)
1147
+ api.clearTimeout(id)
1148
+ api.setInterval(fn, ms)
1149
+ api.clearInterval(id)
1150
+
1151
+ api.raf(fn)
1152
+ api.cancelRaf(id)
1153
+
1154
+ api.createCanvas(width, height)
1155
+ api.getCanvasContext(canvas, contextId, options)
1156
+
1157
+ api.onDispose(fn)
1158
+ api.now()
1159
+
1160
+ api.isTimeoutError(error)
1161
+ api.isNetworkError(error)
1162
+ api.isPolicyError(error)
1163
+ api.errorMessage(error)
1164
+ ```
1165
+
1166
+ Capabilities not exposed through `api.*` are unsupported.
1167
+
1168
+ ## HostRuntimePolicy
1169
+
1170
+ ```ts
1171
+ type HostRuntimePolicy = {
1172
+ network?: {
1173
+ enabled: boolean;
1174
+ methods: ("GET" | "POST")[];
1175
+ allowOrigins: string[];
1176
+ allowHeaders?: string[];
1177
+ allowContentTypes?: string[];
1178
+ credentials: "omit" | "same-origin" | "include";
1179
+ timeoutMs: number;
1180
+ maxBodyBytes: number;
1181
+ maxResponseBytes?: number;
1182
+ };
1183
+ timer?: {
1184
+ enabled: boolean;
1185
+ maxTimers: number;
1186
+ minIntervalMs: number;
1187
+ };
1188
+ animation?: {
1189
+ enabled: boolean;
1190
+ };
1191
+ canvas?: {
1192
+ enabled: boolean;
1193
+ maxCanvases?: number;
1194
+ maxPixels?: number;
1195
+ allowedContexts?: ("2d" | "webgl" | "webgl2" | "bitmaprenderer")[];
1196
+ };
1197
+ execution?: {
1198
+ heartbeatIntervalMs?: number;
1199
+ maxUnresponsiveMs?: number;
1200
+ };
1201
+ };
1202
+ ```
1203
+
1204
+ ### Network policy
1205
+
1206
+ Network is denied by default unless `policy.network.enabled` is `true`. The policy constrains:
1207
+ - HTTP method (only listed methods allowed)
1208
+ - Origin (supports `*`, exact match, `protocol://*` protocol wildcard, and `protocol://*.domain` subdomain wildcard)
1209
+ - Request headers (only listed headers pass; `Authorization`, `Cookie`, `Proxy-Authorization`, `Set-Cookie`, and Sec-Fetch headers are always blocked)
1210
+ - Credentials mode
1211
+ - Request body size
1212
+ - Request timeout
1213
+ - Response body size
1214
+ - Response content-type
1215
+
1216
+ `hostAdapter.fetch` can replace the actual request implementation. `hostAdapter.onNetworkLog` is observational only; it must not alter runtime behavior.
1217
+
1218
+ ### Timer, animation, and canvas
1219
+
1220
+ - **Timer**: denied by default. When enabled, subject to `maxTimers` (total concurrent) and `minIntervalMs` (minimum delay). All timers and intervals are cleaned up on dispose.
1221
+ - **Animation**: denied by default. Controlled via `animation.enabled`; `api.raf` is the only animation primitive.
1222
+ - **Canvas**: denied by default. When enabled, subject to `maxCanvases`, `maxPixels`, and `allowedContexts`.
1223
+
1224
+ ### Execution monitoring
1225
+
1226
+ `execution.heartbeatIntervalMs` controls how often the sandbox sends a heartbeat to the host. `execution.maxUnresponsiveMs` defines the maximum silence before the sandbox is terminated as unresponsive.
1227
+
1228
+ ## HostRuntimeAdapter
1229
+
1230
+ The adapter allows the host to override or observe runtime behavior:
1231
+
1232
+ ```ts
1233
+ type HostRuntimeAdapter = {
1234
+ fetch?: (request: HostFetchRequest) => Promise<NetworkResult>;
1235
+ onNetworkLog?: (event: RuntimeNetworkLogEvent) => void;
1236
+ onRuntimeError?: (event: RuntimeErrorEvent) => void;
1237
+ now?: () => number;
1238
+ setTimeout?: (fn: () => void, ms: number) => TimerId;
1239
+ clearTimeout?: (id: TimerId) => void;
1240
+ setInterval?: (fn: () => void, ms: number) => TimerId;
1241
+ clearInterval?: (id: TimerId) => void;
1242
+ requestAnimationFrame?: (fn: (time: number) => void) => RafId;
1243
+ cancelAnimationFrame?: (id: RafId) => void;
1244
+ };
1245
+ ```
1246
+
1247
+ `onNetworkLog` and `onRuntimeError` are audit hooks; they must not change runtime behavior. Errors thrown within them are silently caught.
1248
+
1249
+ ## Sandbox iframe deployment
1250
+
1251
+ The secure frame imports the main runtime module from a `runtimeUrl`:
1252
+
1253
+ ```ts
1254
+ mountSecureArtifact(script, container, {
1255
+ frame: {
1256
+ runtimeUrl: "/slexkit.runtime.js"
1257
+ }
1258
+ });
1259
+ ```
1260
+
1261
+ This URL must serve as a public ES module and return:
1262
+
1263
+ ```
1264
+ Access-Control-Allow-Origin: *
1265
+ Content-Type: text/javascript
1266
+ ```
1267
+
1268
+ This is server or deployment layer configuration; it cannot be set from frontend JavaScript.
1269
+
1270
+ ### CSP
1271
+
1272
+ The sandbox iframe is served via `srcdoc` with a strict Content-Security-Policy:
1273
+
1274
+ ```
1275
+ default-src 'none'
1276
+ script-src 'nonce-{random}' 'unsafe-eval' {runtimeOrigin}
1277
+ connect-src 'none'
1278
+ img-src data: blob:
1279
+ style-src 'unsafe-inline'
1280
+ font-src data:
1281
+ form-action 'none'
1282
+ base-uri 'none'
1283
+ ```
1284
+
1285
+ A random nonce is generated for each frame instance. `unsafe-eval` is required because Slex source expression evaluation uses `eval()` inside the sandbox.
1286
+
1287
+ ### Sandbox attribute
1288
+
1289
+ The iframe requires `allow-scripts`. `allow-same-origin` is **blocked by default** because it would weaken the opaque origin isolation. Only set `unsafeAllowSameOrigin: true` explicitly if the host accepts the risk:
1290
+
1291
+ ```ts
1292
+ frame: {
1293
+ unsafeAllowSameOrigin: true, // only with explicit host acceptance
1294
+ sandbox: "allow-scripts allow-same-origin"
1295
+ }
1296
+ ```
1297
+
1298
+ Do not add `allow-same-origin` to fix CORS or debugging issues.
1299
+
1300
+ ## postMessage bridge protocol
1301
+
1302
+ The host and sandbox communicate via `window.postMessage`. All messages are tagged with `channel: "slexkit-secure"`.
1303
+
1304
+ ### Host -Sandbox messages
1305
+
1306
+ | Type | Purpose |
1307
+ |------|---------|
1308
+ | `mount` | Sends Slex source, policy, theme to render |
1309
+ | `dispose` | Tells sandbox to tear down |
1310
+ | `fetch-result` | Returns fetch response or error |
1311
+ | `slots` | Synchronizes artifact slot positions |
1312
+
1313
+ ### Sandbox -Host messages
1314
+
1315
+ | Type | Purpose |
1316
+ |------|---------|
1317
+ | `ready` | Runner module loaded and listening |
1318
+ | `mounted` | Artifact render confirmed |
1319
+ | `disposed` | Sandbox teardown acknowledged |
1320
+ | `heartbeat` | Periodic liveness signal |
1321
+ | `error` | Mount or runtime error |
1322
+ | `fetch` | Proxied network request |
1323
+ | `slot-size` | Artifact slot height report |
1324
+
1325
+ Every host→sandbox message includes an `id` and `token`. The token is opaque, cryptographically random, and scoped to a single mount instance. Messages with mismatched tokens are rejected.
1326
+
1327
+ Messages are verified: the sandbox checks `event.source === window.parent`; the host checks `event.source === iframe.contentWindow`.
1328
+
1329
+ ## Artifact slot bridge
1330
+
1331
+ Multiple Markdown fences belonging to one artifact share a single sandbox iframe. The host sends slot rectangles to the sandbox, which renders each fence's output inside the corresponding slot container. The sandbox reports each slot's rendered height back via `slot-size` messages.
1332
+
1333
+ A `ResizeObserver` on the host side and inside the sandbox keeps positions and heights synchronized. This preserves visual continuity across fences while keeping all execution confined to one isolation context.
1334
+
1335
+ ## Heartbeat watchdog
1336
+
1337
+ When `execution.maxUnresponsiveMs` is set, the host monitors the sandbox heartbeat interval. If no heartbeat arrives within the threshold, the iframe is terminated, a diagnostic alert (`role="alert"`) is rendered, and a `console.error` is emitted.
1338
+
1339
+ ## Fail-closed behavior
1340
+
1341
+ If the iframe cannot load the runtime, does not send a ready/mounted message, or the heartbeat times out, SlexKit:
1342
+
1343
+ 1. Removes the unresponsive iframe.
1344
+ 2. Renders a `role="alert"` diagnostic element with a description of the failure.
1345
+ 3. Emits the same information via `console.error`.
1346
+
1347
+ Load timeout is configurable via `frame.loadTimeoutMs` (default: 8000ms).
1348
+
1349
+ ## Escape hatches
1350
+
1351
+ ### `unsafeInlineExecution`
1352
+
1353
+ Allows a secure artifact to execute inline in the host page with an injected secure runtime API. **Intended for testing or host-trusted content only.** Not recommended for untrusted paths.
1354
+
1355
+ ```ts
1356
+ mountSecureArtifact(script, container, {
1357
+ policy,
1358
+ hostAdapter,
1359
+ unsafeInlineExecution: true
1360
+ });
1361
+ ```
1362
+
1363
+ ### `unsafeAllowSameOrigin`
1364
+
1365
+ Allows `allow-same-origin` in the sandbox attribute. **Reduces isolation strength**; only use when the host explicitly accepts the risk.
1366
+
1367
+ ```ts
1368
+ frame: {
1369
+ sandbox: "allow-scripts allow-same-origin",
1370
+ unsafeAllowSameOrigin: true
1371
+ }
1372
+ ```
1373
+
1374
+ ## Sandbox hardening
1375
+
1376
+ When the sandbox runner starts, it hardens the global scope:
1377
+
1378
+ **Blocked network globals** -`fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `Worker`, `SharedWorker`, and `navigator.sendBeacon` are replaced with functions that throw, ensuring all network traffic must go through the bridge.
1379
+
1380
+ **Blocked scheduling globals** -`setTimeout`, `setInterval`, `requestAnimationFrame` are replaced with functions that throw, directing code to `api.setTimeout()`, `api.setInterval()`, and `api.raf()`.
1381
+
1382
+ **Canvas prototype wrapping** -`HTMLCanvasElement.prototype.getContext` is wrapped to enforce canvas policy on context creation. `OffscreenCanvas` is replaced with a subclass that validates dimensions on construction.
1383
+
1384
+ ## Maintenance principles
1385
+
1386
+ - New capabilities must define a policy field first, then an `api.*` method, then the bridge.
1387
+ - Slex source declarations are never an authorization source.
1388
+ - Default to opaque origin.
1389
+ - Always fail closed.
1390
+ - Log and error hooks are observational; they must not alter runtime behavior.
1307
1391
 
1308
1392
  ---
1309
1393
 
1310
- ## Package Boundaries
1394
+ ## Packages
1311
1395
 
1312
1396
  Raw Markdown: /docs/reference/packages.md
1313
1397
 
1314
- ---
1315
- title: Package Boundaries
1316
- category: Reference
1317
- status: ready
1318
- order: 60
1319
- summary: "Package relationships, installation matrix, publish contents, and release quality gates."
1320
- slexkitRenderMode: component
1321
- ---
1322
-
1323
- # Package Boundaries
1324
-
1325
- SlexKit v0/beta npm packages, their relationships, and installation.
1326
-
1327
- ## Package relationship
1328
-
1329
- ```
1330
- slexkit (root - real code)
1331
- ├── runtime entry
1332
- ├── Svelte component registrations
1333
- ├── ToolHost
1334
- ├── default styles
1335
- └── secure iframe runner
1336
-
1337
- @slexkit/runtime (thin wrapper) ─── re-exports slexkit/runtime
1338
- @slexkit/components-svelte (thin wrapper) ─── re-exports slexkit/components-svelte
1339
- @slexkit/theme-shadcn ─── CSS only
1340
- @slexkit/streamdown ─── React/Streamdown renderer
1341
- @slexkit/mcp ─── read-only MCP server for AI agents
1342
- ```
1343
-
1344
- `@slexkit/runtime` and `@slexkit/components-svelte` are thin wrappers that re-export from the root `slexkit` package. They are not standalone physical packages; installing them still requires installing `slexkit`. `@slexkit/theme-shadcn` is CSS-only and contains no runtime implementation.
1345
-
1346
- ## slexkit (root)
1347
-
1348
- The actual implementation package. Contains the runtime engine, official Svelte components, ToolHost, and styles.
1349
-
1350
- ```sh
1351
- npm install slexkit
1352
- ```
1353
-
1354
- ```js
1355
- import { mount, disposeNamespace, boot } from "slexkit";
1356
- import "slexkit/style.css"; // default styles (includes all component CSS)
1357
- import "slexkit/dist/style.css"; // same distributed CSS bundle via dist alias
1358
- ```
1359
-
1360
- Version helpers are exported from both the root and runtime entries:
1361
-
1362
- ```js
1363
- import { SLEXKIT_VERSION, SLEX_PROTOCOL_VERSION, getSlexKitInfo } from "slexkit";
1364
- ```
1365
-
1366
- ## @slexkit/runtime
1367
-
1368
- Component-free runtime entry point. Does not auto-register any official Svelte components.
1369
-
1370
- ```sh
1371
- npm install slexkit @slexkit/runtime
1372
- ```
1373
-
1374
- ```js
1375
- import { mount, register, createSecureRuntime } from "@slexkit/runtime";
1376
- ```
1377
-
1378
- Use this when you want to register your own component set instead of the bundled Svelte components.
1379
-
1380
- ## @slexkit/components-svelte
1381
-
1382
- Side-effect import that registers all official Svelte components into the runtime registry.
1383
-
1384
- ```sh
1385
- npm install slexkit @slexkit/runtime @slexkit/components-svelte
1386
- ```
1387
-
1388
- ```js
1389
- import { mount } from "@slexkit/runtime";
1390
- import "@slexkit/components-svelte";
1391
- ```
1392
-
1393
- Public component specs: action (2), component capability (1), content (6), data (1), disclosure (2), display (2), feedback (2), input (6), layout (4), navigation (1), tooling (1).
1398
+ ---
1399
+ title: Packages
1400
+ category: Reference
1401
+ status: ready
1402
+ order: 60
1403
+ summary: "SlexKit npm package roles, install combinations, publish contents, and release checks."
1404
+ slexkitRenderMode: component
1405
+ ---
1406
+
1407
+ # Packages
1408
+
1409
+ SlexKit v0/beta package references list npm packages, install commands, and release checks.
1410
+
1411
+ ## Package Map
1412
+
1413
+ ```
1414
+ slexkit (root package)
1415
+ ├── runtime entry
1416
+ ├── Svelte component registrations
1417
+ ├── ToolHost
1418
+ ├── default styles
1419
+ └── secure iframe runner
1420
+
1421
+ @slexkit/runtime ─── re-exports slexkit/runtime
1422
+ @slexkit/components-svelte ─── re-exports slexkit/components-svelte
1423
+ @slexkit/theme-shadcn ─── CSS only
1424
+ @slexkit/streamdown ─── React/Streamdown renderer
1425
+ @slexkit/assistant-ui ─── assistant-ui Streamdown text wrapper
1426
+ @slexkit/tiptap ─── framework-free Tiptap NodeView adapter
1427
+ @slexkit/mcp ─── read-only MCP server for AI agents
1428
+ ```
1429
+
1430
+ `@slexkit/runtime` and `@slexkit/components-svelte` are published npm packages, but their code wraps the root `slexkit` package. They are not independent implementation packages; installing them still requires installing `slexkit`. `@slexkit/theme-shadcn` is CSS-only and contains no runtime implementation.
1431
+
1432
+ ## slexkit (root)
1433
+
1434
+ The main implementation package. It contains the runtime engine, official Svelte components, ToolHost, and styles.
1435
+
1436
+ ```sh
1437
+ npm install slexkit
1438
+ ```
1439
+
1440
+ ```js
1441
+ import { mount, disposeNamespace, boot } from "slexkit";
1442
+ import "slexkit/style.css"; // default styles (includes all component CSS)
1443
+ ```
1444
+
1445
+ `slexkit/dist/style.css` is a compatibility alias for the same distributed CSS bundle; do not import both paths.
1446
+
1447
+ Version helpers are exported from both the root and runtime entries:
1448
+
1449
+ ```js
1450
+ import { SLEXKIT_VERSION, SLEX_PROTOCOL_VERSION, getSlexKitInfo } from "slexkit";
1451
+ ```
1452
+
1453
+ The root package also ships the `slex` CLI:
1454
+
1455
+ ```sh
1456
+ slex copy-runtime public/slexkit.runtime.js
1457
+ slex validate ./artifact.slex --mode secure
1458
+ slex validate --standard
1459
+ ```
1460
+
1461
+ `slex validate --standard` runs the bundled Slex conformance fixtures against the validator shipped with the package. Use `--json` for CI or agent consumption.
1462
+
1463
+ ## @slexkit/runtime
1464
+
1465
+ Component-free runtime entry point. Does not auto-register any official Svelte components.
1466
+
1467
+ ```sh
1468
+ npm install slexkit @slexkit/runtime
1469
+ ```
1470
+
1471
+ ```js
1472
+ import { mount, register, createSecureRuntime } from "@slexkit/runtime";
1473
+ ```
1474
+
1475
+ Use this to register a custom component set instead of the bundled Svelte components.
1476
+
1477
+ ## @slexkit/components-svelte
1478
+
1479
+ Side-effect import that registers all official Svelte components into the runtime registry.
1480
+
1481
+ ```sh
1482
+ npm install slexkit @slexkit/runtime @slexkit/components-svelte
1483
+ ```
1484
+
1485
+ ```js
1486
+ import { mount } from "@slexkit/runtime";
1487
+ import "@slexkit/components-svelte";
1488
+ ```
1489
+
1490
+ Public component specs: action (1), component (1), content (6), data (1), disclosure (2), display (3), feedback (2), input (6), layout (4), navigation (1), tooling (3).
1491
+
1492
+ ## @slexkit/theme-shadcn
1493
+
1494
+ CSS theme bundle (shadcn/ui compatible).
1495
+
1496
+ ```sh
1497
+ npm install @slexkit/theme-shadcn
1498
+ ```
1499
+
1500
+ ```js
1501
+ import "@slexkit/theme-shadcn/style.css";
1502
+ ```
1503
+
1504
+ ## @slexkit/streamdown
1505
+
1506
+ React/Streamdown custom renderer for Markdown-hosted SlexKit fences.
1507
+
1508
+ ```sh
1509
+ npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown streamdown react react-dom
1510
+ ```
1511
+
1512
+ ```tsx
1513
+ import { Streamdown } from "streamdown";
1514
+ import { slexkitRenderer } from "@slexkit/streamdown";
1515
+ import "@slexkit/theme-shadcn/style.css";
1516
+ import "@slexkit/streamdown/style.css";
1517
+
1518
+ export function Message({ markdown }: { markdown: string }) {
1519
+ return (
1520
+ <Streamdown plugins={{ renderers: [slexkitRenderer] }}>
1521
+ {markdown}
1522
+ </Streamdown>
1523
+ );
1524
+ }
1525
+ ```
1526
+
1527
+ Processes `slex` fences. Supports both trusted and secure runtime modes.
1528
+
1529
+ ## @slexkit/assistant-ui
1530
+
1531
+ assistant-ui message text wrapper for SlexKit fences. It delegates Markdown rendering to `@assistant-ui/react-streamdown` and only overrides the `slex` language block with `@slexkit/streamdown`.
1532
+
1533
+ ```sh
1534
+ npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown @slexkit/assistant-ui @assistant-ui/react @assistant-ui/react-streamdown streamdown react react-dom
1535
+ ```
1536
+
1537
+ ```tsx
1538
+ import { MessagePrimitive } from "@assistant-ui/react";
1539
+ import { SlexKitAssistantStreamdownText } from "@slexkit/assistant-ui";
1540
+ import "@slexkit/theme-shadcn/style.css";
1541
+ import "@slexkit/assistant-ui/style.css";
1542
+
1543
+ export function AssistantMessage() {
1544
+ return (
1545
+ <MessagePrimitive.Parts>
1546
+ {({ part }) =>
1547
+ part.type === "text" ? (
1548
+ <SlexKitAssistantStreamdownText
1549
+ artifactId="message-1"
1550
+ secureFrame={{ runtimeUrl: "/slexkit.runtime.js" }}
1551
+ />
1552
+ ) : null
1553
+ }
1554
+ </MessagePrimitive.Parts>
1555
+ );
1556
+ }
1557
+ ```
1558
+
1559
+ The runtime defaults to secure. assistant-ui tool calls and ToolHost flows still use their own integration layers.
1560
+
1561
+ ## @slexkit/tiptap
1562
+
1563
+ Tiptap extension for rendering explicit `slex` code blocks as SlexKit previews while preserving normal fenced code block Markdown roundtrip.
1564
+
1565
+ ```sh
1566
+ npm install slexkit @slexkit/theme-shadcn @slexkit/tiptap @tiptap/core @tiptap/pm @tiptap/starter-kit @tiptap/extension-code-block @tiptap/markdown
1567
+ ```
1568
+
1569
+ ```ts
1570
+ import StarterKit from "@tiptap/starter-kit";
1571
+ import { createSlexKitTiptapExtension } from "@slexkit/tiptap";
1572
+ import "@slexkit/theme-shadcn/style.css";
1573
+ import "@slexkit/tiptap/style.css";
1574
+
1575
+ const extensions = [
1576
+ StarterKit.configure({ codeBlock: false }),
1577
+ createSlexKitTiptapExtension({ artifactId: "doc-1" })
1578
+ ];
1579
+ ```
1580
+
1581
+ The extension only takes over code blocks whose language is `slex`; ordinary code blocks stay native to Tiptap. It uses a trusted Markdown runtime host unless configured otherwise. Add `@tiptap/markdown` when loading or exporting Markdown.
1582
+
1583
+ ## Obsidian plugin
1584
+
1585
+ The official Obsidian plugin lives in a separate release repository: <https://github.com/slexkit/obsidian-slexkit>.
1586
+
1587
+ Install **SlexKit** through Obsidian Community Plugins for normal vault use. Use BRAT or manual GitHub release assets only when testing unreleased builds from `slexkit/obsidian-slexkit`.
1588
+
1589
+ The community plugin is marked desktop-only and compatible with Obsidian 1.5.0+.
1590
+
1591
+ The plugin treats the user's local vault as trusted and uses trusted runtime mode. Do not use it as a sandbox for third-party or agent-generated Markdown; the v0 adapter does not include secure sandbox support.
1592
+
1593
+ ## @slexkit/mcp
1594
+
1595
+ Read-only MCP server for AI agents. It serves generated LLM docs, component metadata, examples, runtime docs, ToolHost docs, and Slex source validation.
1596
+
1597
+ ```sh
1598
+ npx -y @slexkit/mcp
1599
+ ```
1600
+
1601
+ The server does not modify project files. Use it when an agent needs SlexKit component or runtime context.
1602
+
1603
+ ## Installation matrix
1604
+
1605
+ | Use case | Install command |
1606
+ |----------|----------------|
1607
+ | Quick start, everything included | `npm install slexkit` |
1608
+ | Component-free, custom components | `npm install slexkit @slexkit/runtime` |
1609
+ | With Svelte components | `npm install slexkit @slexkit/runtime @slexkit/components-svelte` |
1610
+ | Add shadcn theme | `npm install @slexkit/theme-shadcn` |
1611
+ | React/Streamdown host | `npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown streamdown react react-dom` |
1612
+ | assistant-ui host | `npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown @slexkit/assistant-ui @assistant-ui/react @assistant-ui/react-streamdown streamdown react react-dom` |
1613
+ | Tiptap editor host | `npm install slexkit @slexkit/theme-shadcn @slexkit/tiptap @tiptap/core @tiptap/pm @tiptap/starter-kit @tiptap/extension-code-block @tiptap/markdown` |
1614
+ | Obsidian plugin | Install **SlexKit** from Obsidian Community Plugins |
1615
+ | AI agent MCP server | `npx -y @slexkit/mcp` |
1616
+
1617
+ ## v0 Package Layout
1618
+
1619
+ In v0, the root `slexkit` package carries the main implementation. Scoped `@slexkit/*` packages provide clearer install entry points. If these become physical packages later, source code, build output, and publish workflows need to split together.
1620
+
1621
+ ## Release Check
1622
+
1623
+ Check all scoped packages together before publishing:
1624
+
1625
+ ```sh
1626
+ bun run build
1627
+ bun run test
1628
+ bun run lint
1629
+ bun run smoke:release
1630
+ npm pack --dry-run --json
1631
+ slex validate --standard --json
1632
+ ```
1633
+
1634
+ The release smoke packs and installs every scoped package in this repository, verifies public entry points, verifies CSS subpath exports, runs the installed `slex validate --standard --json`, and starts the MCP stdio binary to check `initialize`, `tools/list`, and `slexkitValidate`.
1635
+
1636
+ Before publishing, check that `npm pack --dry-run --json` includes `dist/standard/*` and `scripts/cli.mjs`. Standard artifacts must match `package.json`, `SLEX_PROTOCOL_VERSION`, and the bundled conformance fixtures.
1394
1637
 
1395
- ## @slexkit/theme-shadcn
1396
-
1397
- CSS theme bundle (shadcn/ui compatible).
1398
-
1399
- ```sh
1400
- npm install @slexkit/theme-shadcn
1401
- ```
1402
-
1403
- ```js
1404
- import "@slexkit/theme-shadcn/style.css";
1405
- ```
1406
-
1407
- ## @slexkit/streamdown
1408
-
1409
- React/Streamdown custom renderer for Markdown-hosted SlexKit fences.
1410
-
1411
- ```sh
1412
- npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown streamdown react react-dom
1413
- ```
1414
-
1415
- ```tsx
1416
- import { Streamdown } from "streamdown";
1417
- import { slexkitRenderer } from "@slexkit/streamdown";
1418
- import "@slexkit/theme-shadcn/style.css";
1419
- import "@slexkit/streamdown/style.css";
1420
-
1421
- export function Message({ markdown }: { markdown: string }) {
1422
- return (
1423
- <Streamdown plugins={{ renderers: [slexkitRenderer] }}>
1424
- {markdown}
1425
- </Streamdown>
1426
- );
1427
- }
1428
- ```
1429
-
1430
- Processes `slex` fences. Supports both trusted and secure runtime modes.
1431
-
1432
- ## Obsidian plugin
1433
-
1434
- The official Obsidian plugin lives in a separate release repository: <https://github.com/slexkit/obsidian-slexkit>.
1435
-
1436
- Install **SlexKit** through Obsidian Community Plugins for normal vault use. Use BRAT or manual GitHub release assets only when testing unreleased builds from `slexkit/obsidian-slexkit`.
1437
-
1438
- The community plugin is currently desktop-only and compatible with Obsidian 1.5.0+. Mobile support should be enabled only after real mobile vault testing.
1439
-
1440
- The adapter uses trusted runtime mode - it renders content from the user's local vault and is not designed as a sandbox for third-party or agent-generated Markdown. Secure sandbox support is not part of the v0 adapter.
1441
-
1442
- ## @slexkit/mcp
1443
-
1444
- Read-only MCP server for AI agents. It serves generated LLM docs, component metadata, examples, runtime docs, ToolHost docs, and Slex source validation.
1445
-
1446
- ```sh
1447
- npx -y @slexkit/mcp
1448
- ```
1449
-
1450
- The server does not modify project files. Use it when an agent needs current SlexKit component or runtime context.
1451
-
1452
- ## Installation matrix
1453
-
1454
- | Use case | Install command |
1455
- |----------|----------------|
1456
- | Quick start, everything included | `npm install slexkit` |
1457
- | Component-free, custom components | `npm install slexkit @slexkit/runtime` |
1458
- | With Svelte components | `npm install slexkit @slexkit/runtime @slexkit/components-svelte` |
1459
- | Add shadcn theme | `npm install @slexkit/theme-shadcn` |
1460
- | React/Streamdown host | `npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown streamdown react react-dom` |
1461
- | Obsidian plugin | Install **SlexKit** from Obsidian Community Plugins |
1462
- | AI agent MCP server | `npx -y @slexkit/mcp` |
1463
-
1464
- ## v0 packaging strategy
1465
-
1466
- The current approach keeps the root `slexkit` package as the real code carrier. Scoped `@slexkit/*` wrappers exist to define future package boundaries. If physical package splitting happens in the future, it will involve splitting source code, build output, and publishing workflows.
1467
-
1468
- ## Release quality gate
1469
-
1470
- All scoped packages are release-checked together:
1471
-
1472
- ```sh
1473
- bun run build
1474
- bun run test
1475
- bun run smoke:release
1476
- ```
1638
+ ---
1477
1639
 
1478
- The release smoke packs and installs every scoped package in this repository, verifies public entry points, verifies CSS subpath exports, and starts the MCP stdio binary to check `initialize`, `tools/list`, and `slexkitValidate`.
1640
+ ## Slex Standard Artifacts
1641
+
1642
+ Raw Markdown: /docs/reference/standard.md
1643
+
1644
+ ---
1645
+ title: Slex Standard Artifacts
1646
+ category: Reference
1647
+ status: ready
1648
+ order: 65
1649
+ summary: "JSON artifacts for the Slex envelope, component catalog, logic profile, capabilities, conformance fixtures, and manifest."
1650
+ slexkitRenderMode: component
1651
+ ---
1652
+
1653
+ # Slex Standard Artifacts
1654
+
1655
+ SlexKit provides machine-readable JSON files for agents, host runtimes, MCP servers, and package users. These files describe Slex as a Markdown-embedded UI artifact with local state and logic, not as a pure JSON card catalog.
1656
+
1657
+ The JSON files are generated from the TypeScript runtime registry, component specs, runtime version constants, expression capability metadata, and conformance fixtures.
1658
+
1659
+ ## Files
1660
+
1661
+ - [`/standard/slex-standard-manifest.json`](/standard/slex-standard-manifest.json): version, protocol, logic profile, artifact paths, and hashes.
1662
+ - [`/standard/slex-expression.schema.json`](/standard/slex-expression.schema.json): JSON Schema for the Slex envelope, `namespace`, `g`, `layout`, component keys, and directive fields.
1663
+ - [`/standard/slex-component-catalog.json`](/standard/slex-component-catalog.json): public component props, dynamic flags, generated prop JSON Schema, state modes, children, examples, docs, and per-component hashes.
1664
+ - [`/standard/slex-logic-profile.json`](/standard/slex-logic-profile.json): `$` read-pipes, `on*` write-pipes, `$if`, `$for`, `$key`, context variables, reserved names, component state, and secure-mode native capability policy.
1665
+ - [`/standard/slex-capabilities.catalog.json`](/standard/slex-capabilities.catalog.json): deterministic `std.*` functions and policy-gated `api.*` secure runtime capabilities.
1666
+ - [`/standard/slex-conformance.json`](/standard/slex-conformance.json): valid, warning, and invalid fixtures with stable expected diagnostic codes, paths, and values.
1667
+
1668
+ ## Validation Model
1669
+
1670
+ Validation is parse-first:
1671
+
1672
+ 1. Parse the JavaScript object literal source.
1673
+ 2. Validate the Slex envelope and component-key shape.
1674
+ 3. Compare component names and props against the generated component catalog.
1675
+ 4. Scan logic strings and source text against the logic profile.
1676
+ 5. Return stable diagnostic and warning codes.
1677
+
1678
+ `validateSlexSource()` keeps its previous structured output and adds `schemaVersion`, `protocolVersion`, and `logicProfileVersion`. Secure-mode diagnostics should guide authors toward policy-gated `api.*` capabilities instead of treating all logic as forbidden.
1679
+
1680
+ Warnings are path-aware after parsing. For example, an unknown `std.*` call in `layout.text:value.$text` and a native `fetch()` call in `g.load` produce different stable paths, so tools can point to one expression instead of rewriting the whole artifact.
1681
+
1682
+ ## Run Conformance
1683
+
1684
+ Use the bundled conformance runner to verify that SlexKit's validator still matches the published standard fixtures:
1685
+
1686
+ ```sh
1687
+ slex validate --standard
1688
+ slex validate --standard --json
1689
+ slex validate --standard --fixture valid-full-envelope
1690
+ ```
1691
+
1692
+ Use file validation for a single Slex source:
1693
+
1694
+ ```sh
1695
+ slex validate ./artifact.slex --mode secure
1696
+ slex validate ./artifact.slex --mode trusted --strict
1697
+ ```
1698
+
1699
+ Conformance validates source shape, logic profile diagnostics, capability checks, and warning stability. It is not a visual renderer screenshot test.
1700
+
1701
+ ## Diagnostic Codes
1702
+
1703
+ Clients use `code`, `path`, and `value` for program logic. `message` is for display.
1704
+
1705
+ | Code | Severity | Meaning |
1706
+ |---|---|---|
1707
+ | `syntax` | error | JavaScript object literal parsing failed. Returned as `diagnostic.code`, not a warning. |
1708
+ | `unsupported_protocol` | warning | The optional `slex` marker does not match the supported protocol version. |
1709
+ | `invalid_component_key` | warning | A component key does not match the `type:identifier` shape. |
1710
+ | `invalid_directive_type` | warning | A structural directive such as `$if`, `$for`, or `$key` has an invalid value type. |
1711
+ | `unknown_component` | warning | The component type is not present in the generated component catalog. |
1712
+ | `unknown_prop` | warning | A component prop is not declared by that component's public spec. |
1713
+ | `unknown_std_member` | warning | A logic expression references a `std.*` helper outside the published capability catalog. |
1714
+ | `unknown_api_member` | warning | A logic expression references an `api.*` member outside the secure runtime capability catalog. |
1715
+ | `native_secure_capability` | warning | Secure mode source uses native browser capability names such as `fetch`, timers, or `WebSocket`; use policy-gated `api.*` instead. |
1716
+ | `reserved_context_shadowing` | warning | `g` keys or component identifiers shadow reserved expression context names. |
1717
+
1718
+ Paths refer to the parsed source tree, for example `g.load` or `layout.text:value.$text`. If parsing fails, validation can still return source-level usage warnings, but not parsed-tree paths.
1719
+
1720
+ ## Conformance Fixture Contract
1721
+
1722
+ `slex-conformance.json` contains `valid`, `warning`, and `invalid` fixtures. Each fixture has an `id`, `mode`, source text, and an `expected` object.
1723
+
1724
+ - `expected.ok` is the validator success state.
1725
+ - `expected.warnings` is the warning set for the fixture. The runner fails if an expected warning is missing or if an extra warning appears.
1726
+ - Warning matching uses `code`, and when present also `path` and `value`.
1727
+ - `expected.diagnostic` is the expected parse diagnostic code for invalid fixtures.
1728
+ - Fixture IDs stay stable. If behavior changes, add a new fixture ID.
1729
+
1730
+ The conformance suite checks source validation semantics only. It does not assert component screenshots, browser layout, CSS output, or host adapter lifecycle behavior.
1731
+
1732
+ ## Versioning Policy
1733
+
1734
+ SlexKit exposes separate version fields:
1735
+
1736
+ - `packageVersion`: npm package version, from `package.json`.
1737
+ - `protocolVersion`: accepted Slex source protocol marker, `0.1`.
1738
+ - `schemaVersion`: generated artifact schema generation, using date-style values.
1739
+ - `logicProfileVersion`: expression, context, stdlib, and secure capability profile version.
1740
+
1741
+ Compatible package releases may update catalog hashes, add components or props, add examples, or improve messages without changing `protocolVersion`.
1742
+
1743
+ Changes that alter source interpretation, remove or rename diagnostic codes, change expression semantics, or change secure capability behavior require a protocol or logic profile review. Artifact hashes are for cache invalidation, not semantic versioning.
1744
+
1745
+ ## Difference From A2UI
1746
+
1747
+ A2UI describes cross-platform declarative UI. SlexKit describes Markdown-embedded, stateful, executable UI artifacts; the host chooses trusted runtime or secure runtime.
1748
+
1749
+ JSON Schema describes the envelope and catalog. The JavaScript expression profile is part of the standard because local logic and state are part of the artifact.
1479
1750
 
1480
1751
  ---