slexkit 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.slexkit.md +29 -0
- package/CHANGELOG.md +90 -0
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/README.zh-CN.md +165 -0
- package/dist/ai/llms-authoring.txt +44 -0
- package/dist/ai/llms-components.txt +669 -0
- package/dist/ai/llms-full.txt +6586 -0
- package/dist/ai/llms-runtime.txt +1475 -0
- package/dist/ai/llms-toolhost.txt +295 -0
- package/dist/ai/llms.txt +69 -0
- package/dist/ai/slexkit-ai-manifest.json +2922 -0
- package/dist/base.css +621 -0
- package/dist/chunks/accordion-5f0nvjjm.js +376 -0
- package/dist/chunks/accordion-830dw78f.js +221 -0
- package/dist/chunks/accordion-cfjyxw93.js +630 -0
- package/dist/chunks/accordion-cw5r75jm.js +424 -0
- package/dist/chunks/accordion-ehnhpeca.js +492 -0
- package/dist/chunks/accordion-hzyrngd6.js +2377 -0
- package/dist/chunks/accordion-nw12ytps.js +6823 -0
- package/dist/components/accordion.js +163 -0
- package/dist/components/badge.js +80 -0
- package/dist/components/button.css +114 -0
- package/dist/components/button.js +16 -0
- package/dist/components/callout.js +154 -0
- package/dist/components/card.js +95 -0
- package/dist/components/checkbox.js +114 -0
- package/dist/components/choice.css +165 -0
- package/dist/components/code-block.js +264 -0
- package/dist/components/collapsible.js +111 -0
- package/dist/components/column.js +49 -0
- package/dist/components/content.css +474 -0
- package/dist/components/disclosure.css +162 -0
- package/dist/components/display.css +259 -0
- package/dist/components/divider.js +98 -0
- package/dist/components/feedback.css +219 -0
- package/dist/components/grid.js +67 -0
- package/dist/components/index.js +13364 -0
- package/dist/components/input.css +1247 -0
- package/dist/components/input.js +384 -0
- package/dist/components/link.js +77 -0
- package/dist/components/progress.js +111 -0
- package/dist/components/radio-group.js +189 -0
- package/dist/components/row.js +200 -0
- package/dist/components/section.js +161 -0
- package/dist/components/select.css +260 -0
- package/dist/components/select.js +16 -0
- package/dist/components/slider.css +125 -0
- package/dist/components/slider.js +175 -0
- package/dist/components/specs.js +1090 -0
- package/dist/components/stat.js +178 -0
- package/dist/components/submit.css +9 -0
- package/dist/components/submit.js +77 -0
- package/dist/components/switch.css +114 -0
- package/dist/components/switch.js +114 -0
- package/dist/components/table.js +157 -0
- package/dist/components/tabs.css +192 -0
- package/dist/components/tabs.js +17 -0
- package/dist/components/text-input.css +245 -0
- package/dist/components/text.js +50 -0
- package/dist/components/toast.js +240 -0
- package/dist/components/tooling.css +1009 -0
- package/dist/components/tooling.js +48951 -0
- package/dist/runtime.cjs +3728 -0
- package/dist/runtime.js +3686 -0
- package/dist/slexkit.cjs +18539 -0
- package/dist/slexkit.css +4776 -0
- package/dist/slexkit.js +18497 -0
- package/dist/tooling.js +59141 -0
- package/dist/types/components/accordion.d.ts +2 -0
- package/dist/types/components/badge.d.ts +2 -0
- package/dist/types/components/button.d.ts +2 -0
- package/dist/types/components/callout.d.ts +2 -0
- package/dist/types/components/card.d.ts +2 -0
- package/dist/types/components/checkbox.d.ts +2 -0
- package/dist/types/components/code-block.d.ts +2 -0
- package/dist/types/components/collapsible.d.ts +2 -0
- package/dist/types/components/column.d.ts +2 -0
- package/dist/types/components/divider.d.ts +2 -0
- package/dist/types/components/entries/accordion.d.ts +3 -0
- package/dist/types/components/entries/badge.d.ts +3 -0
- package/dist/types/components/entries/button.d.ts +3 -0
- package/dist/types/components/entries/callout.d.ts +3 -0
- package/dist/types/components/entries/card.d.ts +3 -0
- package/dist/types/components/entries/checkbox.d.ts +3 -0
- package/dist/types/components/entries/code-block.d.ts +3 -0
- package/dist/types/components/entries/collapsible.d.ts +3 -0
- package/dist/types/components/entries/column.d.ts +3 -0
- package/dist/types/components/entries/divider.d.ts +3 -0
- package/dist/types/components/entries/grid.d.ts +3 -0
- package/dist/types/components/entries/input.d.ts +3 -0
- package/dist/types/components/entries/link.d.ts +3 -0
- package/dist/types/components/entries/progress.d.ts +3 -0
- package/dist/types/components/entries/radio-group.d.ts +3 -0
- package/dist/types/components/entries/row.d.ts +3 -0
- package/dist/types/components/entries/section.d.ts +3 -0
- package/dist/types/components/entries/select.d.ts +3 -0
- package/dist/types/components/entries/slider.d.ts +3 -0
- package/dist/types/components/entries/specs.d.ts +1 -0
- package/dist/types/components/entries/stat.d.ts +3 -0
- package/dist/types/components/entries/submit.d.ts +3 -0
- package/dist/types/components/entries/switch.d.ts +3 -0
- package/dist/types/components/entries/table.d.ts +3 -0
- package/dist/types/components/entries/tabs.d.ts +3 -0
- package/dist/types/components/entries/text.d.ts +3 -0
- package/dist/types/components/entries/toast.d.ts +3 -0
- package/dist/types/components/entries/tooling.d.ts +1 -0
- package/dist/types/components/grid.d.ts +2 -0
- package/dist/types/components/index.d.ts +6 -0
- package/dist/types/components/input.d.ts +2 -0
- package/dist/types/components/link.d.ts +2 -0
- package/dist/types/components/progress.d.ts +2 -0
- package/dist/types/components/radio-group.d.ts +2 -0
- package/dist/types/components/row.d.ts +2 -0
- package/dist/types/components/section.d.ts +2 -0
- package/dist/types/components/select.d.ts +2 -0
- package/dist/types/components/slider.d.ts +2 -0
- package/dist/types/components/spec-helpers.d.ts +23 -0
- package/dist/types/components/spec-registry.d.ts +12 -0
- package/dist/types/components/spec-schema.d.ts +74 -0
- package/dist/types/components/specs.d.ts +2 -0
- package/dist/types/components/stat.d.ts +2 -0
- package/dist/types/components/submit.d.ts +2 -0
- package/dist/types/components/svelte/adapter.d.ts +3 -0
- package/dist/types/components/svelte/bindProps.d.ts +2 -0
- package/dist/types/components/svelte/helpers.d.ts +33 -0
- package/dist/types/components/svelte/layout/balancedTiles.d.ts +14 -0
- package/dist/types/components/svelte/types.d.ts +12 -0
- package/dist/types/components/switch.d.ts +2 -0
- package/dist/types/components/table.d.ts +2 -0
- package/dist/types/components/tabs.d.ts +2 -0
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/components/toast.d.ts +2 -0
- package/dist/types/components/tooling.d.ts +2 -0
- package/dist/types/components-svelte.d.ts +5 -0
- package/dist/types/engine/component-scope.d.ts +14 -0
- package/dist/types/engine/component-state.d.ts +9 -0
- package/dist/types/engine/diagnostics.d.ts +24 -0
- package/dist/types/engine/engineering.d.ts +11 -0
- package/dist/types/engine/eval.d.ts +5 -0
- package/dist/types/engine/index.d.ts +26 -0
- package/dist/types/engine/markdown-runtime.d.ts +33 -0
- package/dist/types/engine/merge.d.ts +1 -0
- package/dist/types/engine/reactive.d.ts +11 -0
- package/dist/types/engine/registry.d.ts +4 -0
- package/dist/types/engine/renderer.d.ts +6 -0
- package/dist/types/engine/sandbox-runner.d.ts +2 -0
- package/dist/types/engine/secure-runtime.d.ts +214 -0
- package/dist/types/engine/store.d.ts +12 -0
- package/dist/types/engine/types.d.ts +58 -0
- package/dist/types/icons/manager.d.ts +17 -0
- package/dist/types/icons/phosphor.d.ts +45 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/runtime.d.ts +32 -0
- package/dist/types/toolhost/index.d.ts +78 -0
- package/dist/types/tooling-umd.d.ts +47 -0
- package/dist/types/version.d.ts +8 -0
- package/dist/umd/slexkit.tooling.umd.js +66553 -0
- package/dist/umd/slexkit.umd.js +18552 -0
- package/package.json +136 -0
- package/scripts/cli.mjs +47 -0
- package/skills/slexkit/SKILL.md +27 -0
- package/skills/slexkit-author/SKILL.md +50 -0
- package/skills/slexkit-host-integration/SKILL.md +33 -0
- package/skills/slexkit-secure-runtime/SKILL.md +31 -0
- package/skills/slexkit-toolhost/SKILL.md +38 -0
- package/skills/slexkit-update/SKILL.md +23 -0
- package/src/components/svelte/InlineIcon.svelte +66 -0
- package/src/components/svelte/adapter.ts +76 -0
- package/src/components/svelte/bindProps.ts +9 -0
- package/src/components/svelte/content/Badge.svelte +19 -0
- package/src/components/svelte/content/Callout.svelte +57 -0
- package/src/components/svelte/content/CodeBlock.svelte +130 -0
- package/src/components/svelte/content/Divider.svelte +21 -0
- package/src/components/svelte/content/Link.svelte +21 -0
- package/src/components/svelte/content/Section.svelte +24 -0
- package/src/components/svelte/content/Table.svelte +44 -0
- package/src/components/svelte/disclosure/Accordion.svelte +100 -0
- package/src/components/svelte/disclosure/Collapsible.svelte +45 -0
- package/src/components/svelte/display/Stat.svelte +102 -0
- package/src/components/svelte/display/Text.svelte +11 -0
- package/src/components/svelte/feedback/Progress.svelte +34 -0
- package/src/components/svelte/feedback/Toast.svelte +105 -0
- package/src/components/svelte/helpers.ts +148 -0
- package/src/components/svelte/input/Button.svelte +78 -0
- package/src/components/svelte/input/Checkbox.svelte +52 -0
- package/src/components/svelte/input/Input.svelte +202 -0
- package/src/components/svelte/input/RadioGroup.svelte +71 -0
- package/src/components/svelte/input/Select.svelte +220 -0
- package/src/components/svelte/input/Slider.svelte +96 -0
- package/src/components/svelte/input/Submit.svelte +32 -0
- package/src/components/svelte/input/Switch.svelte +53 -0
- package/src/components/svelte/input/Tabs.svelte +188 -0
- package/src/components/svelte/layout/Card.svelte +17 -0
- package/src/components/svelte/layout/Column.svelte +15 -0
- package/src/components/svelte/layout/Grid.svelte +26 -0
- package/src/components/svelte/layout/Row.svelte +105 -0
- package/src/components/svelte/layout/balancedTiles.ts +85 -0
- package/src/components/svelte/tooling/CodeMirror.svelte +91 -0
- package/src/components/svelte/tooling/Playground.svelte +765 -0
- package/src/components/svelte/tooling/PlaygroundMarkdown.svelte +26 -0
- package/src/components/svelte/tooling/PlaygroundSlexCode.svelte +76 -0
- package/src/components/svelte/types.ts +17 -0
- package/src/styles/animation.css +98 -0
- package/src/styles/components/button.css +114 -0
- package/src/styles/components/choice.css +165 -0
- package/src/styles/components/select.css +260 -0
- package/src/styles/components/slider.css +125 -0
- package/src/styles/components/submit.css +9 -0
- package/src/styles/components/switch.css +114 -0
- package/src/styles/components/tabs.css +192 -0
- package/src/styles/components/text-input.css +245 -0
- package/src/styles/content.css +474 -0
- package/src/styles/disclosure.css +162 -0
- package/src/styles/display.css +259 -0
- package/src/styles/entry.css +34 -0
- package/src/styles/feedback.css +219 -0
- package/src/styles/input.css +8 -0
- package/src/styles/layout.css +365 -0
- package/src/styles/theme.css +31 -0
- package/src/styles/tooling.css +1009 -0
|
@@ -0,0 +1,1475 @@
|
|
|
1
|
+
# SlexKit Runtime for LLMs
|
|
2
|
+
|
|
3
|
+
## Slex Specification
|
|
4
|
+
|
|
5
|
+
Raw Markdown: /docs/reference/spec.md
|
|
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`, `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
|
+
| Component state | e.g. `slider.value` | Named components |
|
|
112
|
+
| `api` | Host-injected object | If `api` option provided |
|
|
113
|
+
| `$event` | Event data | `on*` handlers only |
|
|
114
|
+
| `$item` | Current array item | `$for` context only |
|
|
115
|
+
| `$index` | Current array index | `$for` context only |
|
|
116
|
+
| `$key` | Current item key | `$for` context only |
|
|
117
|
+
| Named `$for` alias | e.g. `user` for `"card:user"` | `$for` context only |
|
|
118
|
+
|
|
119
|
+
Expression evaluation errors are caught and produce a warning with namespace and path information. The last known value is returned as a fallback.
|
|
120
|
+
|
|
121
|
+
## 6. Component instance state
|
|
122
|
+
|
|
123
|
+
Component registration declares a state mode:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
register(type, renderer, { state: "value" | "checked" | "enabled" | "readable" | "none" });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| Mode | Writable | Behavior |
|
|
130
|
+
| ---------- | ------------------ | ------------------------------------------------------------- |
|
|
131
|
+
| `value` | `value` | Input component; `value` writable from expressions and events |
|
|
132
|
+
| `checked` | `checked`, `value` | Checkbox-like boolean component; both synced and writable |
|
|
133
|
+
| `enabled` | `enabled` | Switch-like boolean component; enabled state is writable |
|
|
134
|
+
| `readable` | (none) | Readable from expressions; write emits console warning |
|
|
135
|
+
| `none` | (none) | No instance state exposed |
|
|
136
|
+
|
|
137
|
+
Input components (`value`/`checked`/`enabled` modes) sync `change` events to instance state automatically. Duplicate-named components share one namespace-level state instance.
|
|
138
|
+
|
|
139
|
+
`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.
|
|
140
|
+
|
|
141
|
+
## 7. `$if`
|
|
142
|
+
|
|
143
|
+
`$if` controls component existence:
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
"card:panel": {
|
|
147
|
+
"$if": "g.visible",
|
|
148
|
+
"text:body": { text: "Visible" }
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- **Truthy** -mounts the component and its subtree with enter animation if `$enter` is defined.
|
|
153
|
+
- **Falsy** -unmounts the component and subtree with leave animation if `$leave` is defined, then fires lifecycle hooks, disposers, and subtree cleanup.
|
|
154
|
+
|
|
155
|
+
## 8. `$for`
|
|
156
|
+
|
|
157
|
+
`$for` renders a component for each array element:
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
"text:item": {
|
|
161
|
+
"$for": "g.items",
|
|
162
|
+
"$key": "id",
|
|
163
|
+
"$content": "$item.label"
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Context variables: `$item`, `$index`, `$key`. Named components inject the current item as a same-name variable.
|
|
168
|
+
|
|
169
|
+
### `$key` strategy
|
|
170
|
+
|
|
171
|
+
| `$key` value | Behavior |
|
|
172
|
+
| ------------------------ | ------------------------------------------------------------------------------- |
|
|
173
|
+
| `"$value"` | Use the primitive item itself |
|
|
174
|
+
| `"id"` or other property | Read that property from object items |
|
|
175
|
+
| Omitted | Use `item.id` if available; otherwise fall back to index with a console warning |
|
|
176
|
+
|
|
177
|
+
Primitive arrays should always specify `$key: "$value"`.
|
|
178
|
+
|
|
179
|
+
### $for phases
|
|
180
|
+
|
|
181
|
+
1. **Delete** -Remove items whose keys are absent from the new array (with leave animation).
|
|
182
|
+
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>`.
|
|
183
|
+
3. **Trim** -Defensively remove excess children.
|
|
184
|
+
|
|
185
|
+
## 9. Lifecycle and cleanup
|
|
186
|
+
|
|
187
|
+
Convention hooks on `g`:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
g.onMount_<name>() -after component is appended to DOM
|
|
191
|
+
g.onUnmount_<name>() -before component is removed from DOM
|
|
192
|
+
g.onUpdate_<name>() -after $for item changes (index or item reference)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Component implementations can register resource cleanup:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
attachComponentDisposer(el, dispose);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Component disposal is triggered by normal unmount, `$if` toggle-off, `$for` item removal, root cleanup, and `disposeNamespace()`.
|
|
202
|
+
|
|
203
|
+
## 10. Public runtime API
|
|
204
|
+
|
|
205
|
+
| Function | Signature | Description |
|
|
206
|
+
| ----------------------------------- | ------------------------------------------- | -------------------------------------------------------------------- |
|
|
207
|
+
| `mount` | `(input, container, options?) => Cleanup` | Parse, merge state, render component tree |
|
|
208
|
+
| `ingest` | `(input) => boolean` | Ingest state-only Slex, no rendering |
|
|
209
|
+
| `boot` | `(options?) => void` | Enhance static page code blocks |
|
|
210
|
+
| `disposeNamespace` | `(namespace) => void` | Release namespace roots, store, and cache |
|
|
211
|
+
| `register` | `(type, renderer, options?) => void` | Register component type |
|
|
212
|
+
| `getRenderer` | `(type) => ComponentRenderer \| undefined` | Look up a registered renderer |
|
|
213
|
+
| `getIcon` | `(name, state?) => string` | Resolve a registered or bundled icon synchronously |
|
|
214
|
+
| `loadIcon` | `(name, state?) => Promise<string>` | Resolve an icon, using Iconify fallback when not bundled |
|
|
215
|
+
| `registerIcon` | `(name, svg, options?) => void` | Register one global SVG icon for all components with an `icon` field |
|
|
216
|
+
| `registerIcons` | `(icons, options?) => void` | Register multiple global SVG icons |
|
|
217
|
+
| `clearRegisteredIcons` | `() => void` | Clear all custom registered icons |
|
|
218
|
+
| `getRegisteredIcon` | `(name, state?) => string` | Look up only registered icons (no Phosphor fallback) |
|
|
219
|
+
| `normalizeIconName` | `(name) => string` | Normalize icon name to kebab-case with set prefix |
|
|
220
|
+
| `resolveIconWeight` | `(state?) => IconWeight` | Resolve icon weight from component state |
|
|
221
|
+
| `resolveIconifyIcon` | `(name, state?) => {prefix, name}` | Resolve to Iconify-compatible name pair |
|
|
222
|
+
| `iconifySvgUrl` | `(name, state?) => string` | Build full Iconify API SVG URL |
|
|
223
|
+
| `configureComponentScope` | `(options) => void` | Configure framework adapter flush |
|
|
224
|
+
| `attachComponentDisposer` | `(el, dispose) => void` | Bind cleanup to element lifecycle |
|
|
225
|
+
| `createSecureRuntime` | `(policy, adapter?) => SecureRuntimeHandle` | Create gated runtime instance |
|
|
226
|
+
| `mountSecureArtifact` | `(input, container, options) => Cleanup` | Mount in secure sandbox |
|
|
227
|
+
| `createSlexKitMarkdownRuntimeHost` | `(options?) => MarkdownRuntimeHost` | Create Markdown host instance |
|
|
228
|
+
| `getSlexKitMarkdownRuntimeHost` | `() => MarkdownRuntimeHost` | Get or create global Markdown host |
|
|
229
|
+
| `installSlexKitMarkdownRuntimeHost` | `(options?) => MarkdownRuntimeHost` | Install and return global Markdown host |
|
|
230
|
+
| `getSlexKitRuntimeUrl` | `() => string \| undefined` | Get default sandbox runtime URL |
|
|
231
|
+
| `setSlexKitRuntimeUrl` | `(url) => void` | Set default sandbox runtime URL |
|
|
232
|
+
| `diagnoseSlexKitSource` | `(source, error) => Diagnostic` | Locate syntax error in source |
|
|
233
|
+
| `parseSlexSource` | `(source) => ParseResult` | Parse Slex source to object |
|
|
234
|
+
| `formatSlexKitDiagnostic` | `(diagnostic) => string` | Format diagnostic to readable string |
|
|
235
|
+
|
|
236
|
+
## 11. Error types
|
|
237
|
+
|
|
238
|
+
### SlexKitSyntaxError
|
|
239
|
+
|
|
240
|
+
Thrown when Slex source parsing fails. Includes a `diagnostic` property with `message`, `line`, `column`, `detail`, and `excerpt`.
|
|
241
|
+
|
|
242
|
+
### SlexKitRuntimeError
|
|
243
|
+
|
|
244
|
+
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`.
|
|
245
|
+
|
|
246
|
+
## 12. Markdown language handling
|
|
247
|
+
|
|
248
|
+
SlexKit hosts must only process explicit fence language tags:
|
|
249
|
+
|
|
250
|
+
- `slex`
|
|
251
|
+
|
|
252
|
+
Plain JavaScript, JSON, or untagged code blocks must not be scanned or executed.
|
|
253
|
+
|
|
254
|
+
## 13. Secure runtime types
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
type SecureRuntimeHandle = {
|
|
258
|
+
api: SlexKitRuntimeApi;
|
|
259
|
+
dispose: () => void;
|
|
260
|
+
};
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
For full secure runtime types (`HostRuntimePolicy`, `HostRuntimeAdapter`, `SlexKitRuntimeApi`, `SecureFrameOptions`, `SecureMountOptions`, sandbox message types), see the [security runtime contract](/docs/reference/security).
|
|
264
|
+
|
|
265
|
+
## 14. ToolHost
|
|
266
|
+
|
|
267
|
+
ToolHost bridges AI tool calls to interactive UI that returns structured user input. It is separate from display-oriented `slex` fences.
|
|
268
|
+
|
|
269
|
+
**Public API:**
|
|
270
|
+
|
|
271
|
+
| Function | Signature | Description |
|
|
272
|
+
|----------|-----------|-------------|
|
|
273
|
+
| `renderToolCall` | `(call, container) => ToolRenderHandle` | Compile and mount tool UI, return promise |
|
|
274
|
+
| `registerToolTemplate` | `(name, compiler) => void` | Register a custom tool template compiler |
|
|
275
|
+
|
|
276
|
+
**Result type:**
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
type ToolResult =
|
|
280
|
+
| { toolCallId?: string; toolName: string; status: "submitted"; value: Record<string, unknown> }
|
|
281
|
+
| { toolCallId?: string; toolName: string; status: "ignored"; value: null };
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**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.
|
|
285
|
+
|
|
286
|
+
For full template reference, arguments, type definitions, and custom template development, see [ToolHost documentation](/docs/reference/toolhost).
|
|
287
|
+
|
|
288
|
+
## 15. Icon system
|
|
289
|
+
|
|
290
|
+
SlexKit includes a built-in icon system with Phosphor Icons, a custom registration API, and Iconify fallback support.
|
|
291
|
+
|
|
292
|
+
**Public API (10 functions):** `registerIcon`, `registerIcons`, `clearRegisteredIcons`, `getIcon`, `getRegisteredIcon`, `loadIcon`, `normalizeIconName`, `resolveIconWeight`, `resolveIconifyIcon`, `iconifySvgUrl`.
|
|
293
|
+
|
|
294
|
+
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.
|
|
295
|
+
|
|
296
|
+
For the full API reference, icon list, naming conventions, and custom icon registration, see [Icon system documentation](/docs/reference/icons).
|
|
297
|
+
|
|
298
|
+
## 16. Non-goals
|
|
299
|
+
|
|
300
|
+
- No public stable compatibility commitment (v0/beta).
|
|
301
|
+
- Not a pure JSON cross-platform protocol.
|
|
302
|
+
- No automatic security hardening of arbitrary browser APIs.
|
|
303
|
+
- No heuristic scanning of code blocks to guess whether to render.
|
|
304
|
+
- No implicit wrapping of display UI as function calls.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Slex Usage Reference
|
|
309
|
+
|
|
310
|
+
Raw Markdown: /docs/reference/usage.md
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
title: Slex Usage Reference
|
|
314
|
+
category: Reference
|
|
315
|
+
status: ready
|
|
316
|
+
order: 20
|
|
317
|
+
summary: "Reference for Slex source structure, props, directives, events, theming, custom components, and ToolHost boundaries."
|
|
318
|
+
slexkitRenderMode: component
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
# Slex Usage Reference
|
|
322
|
+
|
|
323
|
+
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).
|
|
324
|
+
|
|
325
|
+
## Installation
|
|
326
|
+
|
|
327
|
+
Most hosts install the root package:
|
|
328
|
+
|
|
329
|
+
```sh
|
|
330
|
+
npm install slexkit
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
import { mount } from "slexkit";
|
|
335
|
+
import "slexkit/style.css";
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Component-free bare runtime:
|
|
339
|
+
|
|
340
|
+
```sh
|
|
341
|
+
npm install slexkit @slexkit/runtime
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
import { mount, register } from "@slexkit/runtime";
|
|
346
|
+
import "@slexkit/components-svelte";
|
|
347
|
+
import "@slexkit/theme-shadcn/style.css";
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
For package boundaries and host-specific install commands, see [Package Boundaries](/docs/reference/packages).
|
|
351
|
+
|
|
352
|
+
## Slex source structure
|
|
353
|
+
|
|
354
|
+
A Slex source is a JavaScript object literal:
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
{
|
|
358
|
+
slex: "0.1",
|
|
359
|
+
namespace: "demo",
|
|
360
|
+
g: { count: 0 },
|
|
361
|
+
layout: {
|
|
362
|
+
"button:add": { text: "Add", onclick: "g.count++" },
|
|
363
|
+
"text:value": { "$content": "'Count: ' + g.count" }
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
- `slex` -optional Slex protocol marker; use `"0.1"` for the current public protocol.
|
|
369
|
+
- `namespace` -state domain identifier (defaults to `"default"`).
|
|
370
|
+
- `g` -reactive state and logic (functions, data).
|
|
371
|
+
- `layout` -component tree.
|
|
372
|
+
|
|
373
|
+
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`.
|
|
374
|
+
|
|
375
|
+
## Props
|
|
376
|
+
|
|
377
|
+
### Static props
|
|
378
|
+
|
|
379
|
+
Passed to the component as-is:
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
"text:title": { text: "Hello World" }
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Dynamic read-pipes (`$`)
|
|
386
|
+
|
|
387
|
+
A string value on a `$`-prefixed key is evaluated as a JavaScript expression. The result replaces the key stripped of the `$` prefix:
|
|
388
|
+
|
|
389
|
+
```js
|
|
390
|
+
"text:value": { "$content": "'Count: ' + g.count" }
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The `$` prefix is removed. The prop passed to the component is `content`. Re-evaluation is triggered automatically when any reactive dependency changes.
|
|
394
|
+
|
|
395
|
+
### Write-pipes (`on*`)
|
|
396
|
+
|
|
397
|
+
A string value on an `on*`-prefixed key is executed as a JavaScript statement:
|
|
398
|
+
|
|
399
|
+
```js
|
|
400
|
+
"button:add": { onclick: "g.count++" }
|
|
401
|
+
"input:name": { onchange: "g.name = String($event || '')" }
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The handler receives `$event` as the event data.
|
|
405
|
+
|
|
406
|
+
### Structural directives
|
|
407
|
+
|
|
408
|
+
`$if`, `$for`, and `$key` are structural directives -they are not passed to the component as props.
|
|
409
|
+
|
|
410
|
+
## `$if` -conditional rendering
|
|
411
|
+
|
|
412
|
+
Controls whether the component and its subtree are mounted:
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
"card:panel": {
|
|
416
|
+
"$if": "g.visible",
|
|
417
|
+
"text:body": { text: "I am visible" }
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
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).
|
|
422
|
+
|
|
423
|
+
## `$for` -array iteration
|
|
424
|
+
|
|
425
|
+
Renders a component for each item in an array:
|
|
426
|
+
|
|
427
|
+
```js
|
|
428
|
+
"text:item": {
|
|
429
|
+
"$for": "g.items",
|
|
430
|
+
"$key": "id",
|
|
431
|
+
"$content": "$item.label"
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
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).
|
|
436
|
+
|
|
437
|
+
### Key strategy
|
|
438
|
+
|
|
439
|
+
`$key` supports:
|
|
440
|
+
- `"$value"` -use the primitive item itself as key.
|
|
441
|
+
- `"id"` (or any property name) -read that property from object items.
|
|
442
|
+
- Omitted -uses `item.id` if available, otherwise falls back to index with a console warning.
|
|
443
|
+
|
|
444
|
+
Primitive array items should always specify `$key: "$value"`.
|
|
445
|
+
|
|
446
|
+
### $for update algorithm
|
|
447
|
+
|
|
448
|
+
1. **Delete phase** -remove items whose keys are no longer in the array (with leave animation).
|
|
449
|
+
2. **Add/update/reorder phase** -create new items, update retained items' context (index, item reference), and reorder DOM nodes to match the array.
|
|
450
|
+
3. **Trim phase** -defensively remove any excess children.
|
|
451
|
+
|
|
452
|
+
When an item's index or item reference changes, `onUpdate_<name>` is called.
|
|
453
|
+
|
|
454
|
+
## Events
|
|
455
|
+
|
|
456
|
+
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:
|
|
457
|
+
|
|
458
|
+
```js
|
|
459
|
+
"input:name": { onchange: "g.name = String($event || '')" }
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
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.
|
|
463
|
+
|
|
464
|
+
## Trusted vs secure mode
|
|
465
|
+
|
|
466
|
+
### Trusted mode (default)
|
|
467
|
+
|
|
468
|
+
Slex source executes in the host page realm. Use for application-generated content, repository-maintained Slex source, or already-reviewed snippets.
|
|
469
|
+
|
|
470
|
+
```js
|
|
471
|
+
import { mount } from "slexkit";
|
|
472
|
+
|
|
473
|
+
mount(script, container, { theme: "host-shadcn" });
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Secure mode
|
|
477
|
+
|
|
478
|
+
Untrusted or agent-generated Slex source runs in a sandbox iframe with opaque origin. Sensitive capabilities are gated behind host policy:
|
|
479
|
+
|
|
480
|
+
```js
|
|
481
|
+
import { mountSecureArtifact } from "slexkit";
|
|
482
|
+
|
|
483
|
+
mountSecureArtifact(script, container, {
|
|
484
|
+
policy: {},
|
|
485
|
+
frame: { runtimeUrl: "/slexkit.runtime.js" },
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
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.
|
|
490
|
+
|
|
491
|
+
## Theming
|
|
492
|
+
|
|
493
|
+
Theme mode is resolved from the `theme` option:
|
|
494
|
+
|
|
495
|
+
| Value | Behavior |
|
|
496
|
+
|-------|----------|
|
|
497
|
+
| `"auto"` | Checks container for known theme classes; falls back to `"uno"` |
|
|
498
|
+
| `"host-shadcn"` | shadcn/ui compatible |
|
|
499
|
+
| `"uno"` | Uno/Flowbite compatible |
|
|
500
|
+
| `"flowbite"` | Flowbite compatible |
|
|
501
|
+
|
|
502
|
+
Direction (`ltr`, `rtl`, `auto`) is resolved from the inherited `dir` attribute or the document element.
|
|
503
|
+
|
|
504
|
+
```js
|
|
505
|
+
mount(script, container, { theme: "host-shadcn", dir: "auto" });
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Custom components
|
|
509
|
+
|
|
510
|
+
Register a component type with a render function:
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
import { register } from "slexkit";
|
|
514
|
+
|
|
515
|
+
register("custom", (props, name, ctx) => {
|
|
516
|
+
const el = ctx.document.createElement("div");
|
|
517
|
+
el.textContent = String(props.label ?? name);
|
|
518
|
+
return el;
|
|
519
|
+
}, { state: "value" });
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
The `RenderContext` provides:
|
|
523
|
+
|
|
524
|
+
| Property | Type | Description |
|
|
525
|
+
|----------|------|-------------|
|
|
526
|
+
| `g` | reactive proxy | Global state |
|
|
527
|
+
| `api` | `Record<string, unknown>` | Host-injected capabilities |
|
|
528
|
+
| `dir` | `"ltr"` or `"rtl"` | Resolved direction |
|
|
529
|
+
| `labels` | `Partial<Record<string, string>>` | Runtime labels |
|
|
530
|
+
| `id` | `string \| null` | Component name |
|
|
531
|
+
| `emit` | `(event, data?) => void` | Event emitter |
|
|
532
|
+
| `children` | `Record<string, unknown>` | Nested component tree |
|
|
533
|
+
| `document` | `Document` | Owner document |
|
|
534
|
+
| `renderTree` | function | Recursive render helper |
|
|
535
|
+
|
|
536
|
+
Use `attachComponentDisposer(el, fn)` to bind cleanup to the component's DOM lifecycle.
|
|
537
|
+
|
|
538
|
+
## ToolHost
|
|
539
|
+
|
|
540
|
+
ToolHost handles UI that must return structured user input (confirmations, selections, forms). It is separate from display-oriented `slex` fences.
|
|
541
|
+
|
|
542
|
+
Built-in templates:
|
|
543
|
+
- `confirm-action` -yes/no confirmation
|
|
544
|
+
- `choose-options` -single or multi-select
|
|
545
|
+
- `option-list` -scrollable option list
|
|
546
|
+
- `fill-form` -structured form with submit
|
|
547
|
+
|
|
548
|
+
Templates compile to standard Slex source. The `submit` component serves as the completion boundary -only tool templates use it, not display fences.
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Runtime Model
|
|
553
|
+
|
|
554
|
+
Raw Markdown: /docs/reference/runtime.md
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
title: Runtime Model
|
|
558
|
+
category: Reference
|
|
559
|
+
status: ready
|
|
560
|
+
order: 30
|
|
561
|
+
summary: "Mounting, ingestion, boot, namespace store, lifecycle hooks, component state, and runtime APIs."
|
|
562
|
+
slexkitRenderMode: component
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
# Runtime Model
|
|
566
|
+
|
|
567
|
+
The core SlexKit runtime: entry points, namespace store, component state, lifecycle hooks, and expression evaluation.
|
|
568
|
+
|
|
569
|
+
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).
|
|
570
|
+
|
|
571
|
+
## Entry points
|
|
572
|
+
|
|
573
|
+
### `mount(input, container, options)`
|
|
574
|
+
|
|
575
|
+
Parses Slex source object or source string, merges state into the namespace store, renders the component tree into `container`, returns a root cleanup function.
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
function mount(
|
|
579
|
+
input: SlexExpression | string,
|
|
580
|
+
container: HTMLElement,
|
|
581
|
+
options?: MountOptions
|
|
582
|
+
): () => void;
|
|
583
|
+
|
|
584
|
+
type MountOptions = {
|
|
585
|
+
theme?: "auto" | "host-shadcn" | "uno" | "flowbite";
|
|
586
|
+
dir?: "ltr" | "rtl" | "auto";
|
|
587
|
+
labels?: Partial<Record<string, string>>;
|
|
588
|
+
api?: Record<string, unknown>;
|
|
589
|
+
};
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
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.
|
|
593
|
+
|
|
594
|
+
### `ingest(input)`
|
|
595
|
+
|
|
596
|
+
Ingests state-only Slex: updates `g` without rendering UI. Used by the Markdown runtime host for state-only fences. Returns `true` if parsing succeeded.
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
function ingest(input: SlexExpression | string): boolean;
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### `disposeNamespace(namespace)`
|
|
603
|
+
|
|
604
|
+
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.
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
function disposeNamespace(namespace: string): void;
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### `boot(options)`
|
|
611
|
+
|
|
612
|
+
Enhances static pages by auto-discovering explicitly marked Slex blocks (`<pre><code class="language-slex">`) and mounting live previews.
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
function boot(options?: BootOptions): void;
|
|
616
|
+
|
|
617
|
+
type BootOptions = {
|
|
618
|
+
selector?: string;
|
|
619
|
+
sourceControls?: boolean;
|
|
620
|
+
theme?: ThemeMode;
|
|
621
|
+
dir?: MountOptions["dir"];
|
|
622
|
+
labels?: MountOptions["labels"];
|
|
623
|
+
};
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Default selector covers: `language-slex`.
|
|
627
|
+
|
|
628
|
+
Hosts like React/Streamdown or Obsidian should typically use the Markdown runtime host directly rather than `boot()`.
|
|
629
|
+
|
|
630
|
+
### `register(type, renderer, options)`
|
|
631
|
+
|
|
632
|
+
Registers a component type with a render function and state mode.
|
|
633
|
+
|
|
634
|
+
```ts
|
|
635
|
+
function register(
|
|
636
|
+
type: string,
|
|
637
|
+
renderer: ComponentRenderer,
|
|
638
|
+
options?: ComponentRegistrationOptions
|
|
639
|
+
): void;
|
|
640
|
+
|
|
641
|
+
type ComponentRenderer = (
|
|
642
|
+
props: Record<string, unknown>,
|
|
643
|
+
name: string,
|
|
644
|
+
ctx: RenderContext
|
|
645
|
+
) => HTMLElement | void;
|
|
646
|
+
|
|
647
|
+
type ComponentRegistrationOptions = {
|
|
648
|
+
state?: "value" | "checked" | "enabled" | "readable" | "none";
|
|
649
|
+
};
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### `configureComponentScope(options)`
|
|
653
|
+
|
|
654
|
+
Configures a flush function for component scope -used by framework adapters to synchronize DOM after reactive updates.
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
function configureComponentScope(options: { flush?: () => void }): void;
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
## Namespace store
|
|
661
|
+
|
|
662
|
+
`namespace` is the state domain. Multiple mounts with the same namespace share one store:
|
|
663
|
+
|
|
664
|
+
- New `g` is deep-merged into the old `g` (functions overwrite, objects recursively merge, arrays replace, scalars overwrite).
|
|
665
|
+
- New `layout` replaces the current layout (no deep merge for layout).
|
|
666
|
+
- Component instance state is persisted within the namespace.
|
|
667
|
+
- Expression caches are managed per namespace.
|
|
668
|
+
|
|
669
|
+
This allows a document, message domain, or tool panel to update its UI incrementally while preserving state.
|
|
670
|
+
|
|
671
|
+
## Component instance state
|
|
672
|
+
|
|
673
|
+
Named components can expose instance state. Which prop is writable depends on the component's registered state mode:
|
|
674
|
+
|
|
675
|
+
| Mode | Writable prop | Behavior |
|
|
676
|
+
|------|---------------|----------|
|
|
677
|
+
| `value` | `value` | Writable from expressions and events |
|
|
678
|
+
| `checked` | `checked`, `value` | Both synced, writable |
|
|
679
|
+
| `enabled` | `enabled` | Switch enabled state, writable |
|
|
680
|
+
| `readable` | (none) | Readable from expressions, write emits a console warning |
|
|
681
|
+
| `none` | (none) | No state exposed |
|
|
682
|
+
|
|
683
|
+
```js
|
|
684
|
+
{
|
|
685
|
+
layout: {
|
|
686
|
+
"slider:threshold": { value: 42 },
|
|
687
|
+
"text:preview": { "$content": "'Threshold: ' + threshold.value" }
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Repeatedly named components share namespace-level state. `$for` items with the same component name also share one state instance.
|
|
693
|
+
|
|
694
|
+
## Lifecycle hooks
|
|
695
|
+
|
|
696
|
+
The runtime calls convention-based hooks on the `g` object:
|
|
697
|
+
|
|
698
|
+
```
|
|
699
|
+
g.onMount_<name>() -after component is appended to DOM
|
|
700
|
+
g.onUnmount_<name>() -before component is removed from DOM
|
|
701
|
+
g.onUpdate_<name>() -after $for item index or item reference changes
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
These hooks fire for normal components, `$if` branches, and `$for` slots. Root cleanup and `disposeNamespace()` trigger `onUnmount`.
|
|
705
|
+
|
|
706
|
+
## Component disposer
|
|
707
|
+
|
|
708
|
+
Framework components, event listeners, subscriptions, and external resources should bind their cleanup to the component DOM element:
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
import { register, attachComponentDisposer } from "slexkit/runtime";
|
|
712
|
+
|
|
713
|
+
register("custom", (props, name, ctx) => {
|
|
714
|
+
const el = ctx.document.createElement("div");
|
|
715
|
+
const stop = subscribeSomething();
|
|
716
|
+
attachComponentDisposer(el, stop);
|
|
717
|
+
return el;
|
|
718
|
+
});
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
The runtime calls the disposer when the element is unmounted. The official Svelte adapter uses this mechanism to destroy Svelte component instances.
|
|
722
|
+
|
|
723
|
+
## Expression evaluation context
|
|
724
|
+
|
|
725
|
+
Expressions in `$` read-pipes and statements in `on*` write-pipes can access these variables:
|
|
726
|
+
|
|
727
|
+
| Variable | Type | Availability |
|
|
728
|
+
|----------|------|--------------|
|
|
729
|
+
| `g` | reactive state proxy | always |
|
|
730
|
+
| `api` | host-injected capabilities | if `api` option passed to `mount()` |
|
|
731
|
+
| `$event` | event data | `on*` handlers only |
|
|
732
|
+
| `$item` | current array item | `$for` context only |
|
|
733
|
+
| `$index` | current array index | `$for` context only |
|
|
734
|
+
| `$key` | current item key | `$for` context only |
|
|
735
|
+
| named component state | e.g. `threshold.value` | named components |
|
|
736
|
+
|
|
737
|
+
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.
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
## Host Integration
|
|
742
|
+
|
|
743
|
+
Raw Markdown: /docs/reference/integration.md
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
title: Host Integration
|
|
747
|
+
category: Reference
|
|
748
|
+
status: ready
|
|
749
|
+
order: 40
|
|
750
|
+
summary: "MarkdownRuntimeHost, trusted and secure host integrations, Streamdown, Obsidian, and custom adapters."
|
|
751
|
+
slexkitRenderMode: component
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
# Host Integration
|
|
755
|
+
|
|
756
|
+
How to integrate SlexKit into Markdown renderers, chat hosts, document viewers, and custom platforms.
|
|
757
|
+
|
|
758
|
+
## Core concepts
|
|
759
|
+
|
|
760
|
+
### Artifact
|
|
761
|
+
|
|
762
|
+
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.
|
|
763
|
+
|
|
764
|
+
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.
|
|
765
|
+
|
|
766
|
+
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.
|
|
767
|
+
|
|
768
|
+
### Block
|
|
769
|
+
|
|
770
|
+
A block is a single Slex block: one renderable unit. It has:
|
|
771
|
+
- A source (Slex expression object or source string).
|
|
772
|
+
- A container element (where the rendered output goes).
|
|
773
|
+
- An optional `artifactId` (for grouping into an artifact).
|
|
774
|
+
|
|
775
|
+
### Cleanup
|
|
776
|
+
|
|
777
|
+
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()`.
|
|
778
|
+
|
|
779
|
+
## MarkdownRuntimeHost
|
|
780
|
+
|
|
781
|
+
The `SlexKitMarkdownRuntimeHost` is the recommended API for Markdown-based hosts. It handles mode selection, artifact management, and block lifecycle.
|
|
782
|
+
|
|
783
|
+
```ts
|
|
784
|
+
import {
|
|
785
|
+
createSlexKitMarkdownRuntimeHost,
|
|
786
|
+
getSlexKitMarkdownRuntimeHost,
|
|
787
|
+
installSlexKitMarkdownRuntimeHost
|
|
788
|
+
} from "slexkit";
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Interface
|
|
792
|
+
|
|
793
|
+
```ts
|
|
794
|
+
type SlexKitMarkdownRuntimeHost = {
|
|
795
|
+
configure(options: Partial<SlexKitMarkdownRuntimeOptions>): void;
|
|
796
|
+
getMode(): "trusted" | "secure";
|
|
797
|
+
mountBlock(block: SlexKitMarkdownBlock): () => void;
|
|
798
|
+
disposeBlock(container: HTMLElement): void;
|
|
799
|
+
disposeArtifact(artifactId: string): void;
|
|
800
|
+
disposeAll(): void;
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
type SlexKitMarkdownBlock = {
|
|
804
|
+
artifactId?: string;
|
|
805
|
+
blockId?: string;
|
|
806
|
+
source: SlexExpression | string;
|
|
807
|
+
container: HTMLElement;
|
|
808
|
+
stateOnly?: boolean;
|
|
809
|
+
theme?: ThemeMode;
|
|
810
|
+
dir?: MountOptions["dir"];
|
|
811
|
+
labels?: MountOptions["labels"];
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
type SlexKitMarkdownRuntimeOptions = {
|
|
815
|
+
mode?: "trusted" | "secure";
|
|
816
|
+
policy?: HostRuntimePolicy;
|
|
817
|
+
hostAdapter?: HostRuntimeAdapter;
|
|
818
|
+
secureFrame?: boolean | SecureFrameOptions;
|
|
819
|
+
theme?: ThemeMode;
|
|
820
|
+
dir?: MountOptions["dir"];
|
|
821
|
+
labels?: MountOptions["labels"];
|
|
822
|
+
};
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
### Global singleton
|
|
826
|
+
|
|
827
|
+
The module provides a global singleton for convenience:
|
|
828
|
+
|
|
829
|
+
```ts
|
|
830
|
+
// Install explicitly
|
|
831
|
+
const runtime = installSlexKitMarkdownRuntimeHost({
|
|
832
|
+
mode: "secure",
|
|
833
|
+
policy: { execution: { maxUnresponsiveMs: 30000 } },
|
|
834
|
+
secureFrame: { runtimeUrl: "/slexkit.runtime.js" }
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// Or use lazy global (auto-creates with defaults on first call)
|
|
838
|
+
const runtime = getSlexKitMarkdownRuntimeHost();
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
Use the singleton when the entire application shares one runtime configuration. Avoid it when different host contexts need different policies.
|
|
842
|
+
|
|
843
|
+
## Trusted mode integration
|
|
844
|
+
|
|
845
|
+
Trusted mode runs Slex source in the host page realm. Use for local documents, application-generated content, or reviewed Slex source.
|
|
846
|
+
|
|
847
|
+
```ts
|
|
848
|
+
const runtime = createSlexKitMarkdownRuntimeHost({
|
|
849
|
+
mode: "trusted",
|
|
850
|
+
theme: "host-shadcn"
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// Detect a slex fence at position <container>
|
|
854
|
+
const cleanup = runtime.mountBlock({
|
|
855
|
+
artifactId: "doc-1",
|
|
856
|
+
source: fenceSource,
|
|
857
|
+
container: fenceContainer
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// When the fence container is removed
|
|
861
|
+
runtime.disposeBlock(fenceContainer);
|
|
862
|
+
|
|
863
|
+
// When the document is closed
|
|
864
|
+
runtime.disposeArtifact("doc-1");
|
|
865
|
+
|
|
866
|
+
// When the plugin or page unloads
|
|
867
|
+
runtime.disposeAll();
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
In trusted mode, the runtime automatically scopes namespaces by artifact ID (`<artifactId>::<namespace>`) to prevent different documents from polluting each other's state.
|
|
871
|
+
|
|
872
|
+
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.
|
|
873
|
+
|
|
874
|
+
## Secure mode integration
|
|
875
|
+
|
|
876
|
+
Secure mode runs Slex source in a sandbox iframe. Use for untrusted or agent-generated Markdown.
|
|
877
|
+
|
|
878
|
+
```ts
|
|
879
|
+
const runtime = createSlexKitMarkdownRuntimeHost({
|
|
880
|
+
mode: "secure",
|
|
881
|
+
policy: {
|
|
882
|
+
execution: { maxUnresponsiveMs: 30000 }
|
|
883
|
+
},
|
|
884
|
+
secureFrame: {
|
|
885
|
+
runtimeUrl: "/slexkit.runtime.js"
|
|
886
|
+
},
|
|
887
|
+
theme: "host-shadcn"
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
const cleanup = runtime.mountBlock({
|
|
891
|
+
artifactId: "agent-msg-1",
|
|
892
|
+
source: agentGeneratedDsl,
|
|
893
|
+
container: fenceContainer
|
|
894
|
+
});
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
Omitted capability policies deny access by default. Add `network`, `timer`, `animation`, or `canvas` policy objects only when the host intentionally enables those capabilities.
|
|
898
|
+
|
|
899
|
+
### Artifact slot bridge
|
|
900
|
+
|
|
901
|
+
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.
|
|
902
|
+
|
|
903
|
+
```html
|
|
904
|
+
<!-- In Markdown, the first fence becomes the anchor -->
|
|
905
|
+
<div id="fence-1"><!-- anchor: iframe rendered here --></div>
|
|
906
|
+
<div id="fence-2"><!-- slot: height synced from iframe --></div>
|
|
907
|
+
<div id="fence-3"><!-- slot: height synced from iframe --></div>
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
This allows state sharing across fences within one artifact while keeping all execution confined to one sandbox.
|
|
911
|
+
|
|
912
|
+
### `runtimeUrl` requirements
|
|
913
|
+
|
|
914
|
+
The `runtimeUrl` must serve the SlexKit runtime as an ES module with:
|
|
915
|
+
|
|
916
|
+
```
|
|
917
|
+
Access-Control-Allow-Origin: *
|
|
918
|
+
Content-Type: text/javascript
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
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.
|
|
922
|
+
|
|
923
|
+
## Streamdown / React integration
|
|
924
|
+
|
|
925
|
+
The `@slexkit/streamdown` package provides a React/Streamdown custom renderer:
|
|
926
|
+
|
|
927
|
+
```tsx
|
|
928
|
+
import { Streamdown } from "streamdown";
|
|
929
|
+
import { slexkitRenderer } from "@slexkit/streamdown";
|
|
930
|
+
import "@slexkit/theme-shadcn/style.css";
|
|
931
|
+
import "@slexkit/streamdown/style.css";
|
|
932
|
+
|
|
933
|
+
export function Message({ markdown }: { markdown: string }) {
|
|
934
|
+
return (
|
|
935
|
+
<Streamdown plugins={{ renderers: [slexkitRenderer] }}>
|
|
936
|
+
{markdown}
|
|
937
|
+
</Streamdown>
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
The renderer handles `slex` fences. It supports both trusted and secure runtime modes and can delegate to a shared Markdown runtime host instance.
|
|
943
|
+
|
|
944
|
+
## Obsidian integration
|
|
945
|
+
|
|
946
|
+
The `@slexkit/obsidian` package registers the `slex` fenced code block processor in Obsidian:
|
|
947
|
+
|
|
948
|
+
```ts
|
|
949
|
+
// In the Obsidian plugin:
|
|
950
|
+
registerMarkdownCodeBlockProcessor("slex", (source, el, ctx) => { ... });
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
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.
|
|
954
|
+
|
|
955
|
+
**Important**: The Obsidian adapter uses trusted mode because it renders content from the user's login vault. It is not designed as a security boundary for untrusted or agent-generated Markdown.
|
|
956
|
+
|
|
957
|
+
## Writing a custom host adapter
|
|
958
|
+
|
|
959
|
+
To integrate SlexKit into a custom Markdown renderer or chat host:
|
|
960
|
+
|
|
961
|
+
### 1. Detect fence language
|
|
962
|
+
|
|
963
|
+
Only process fences tagged with `slex`. Never scan plain JavaScript, JSON, or untagged code blocks.
|
|
964
|
+
|
|
965
|
+
### 2. Create a runtime host
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
import { createSlexKitMarkdownRuntimeHost } from "slexkit";
|
|
969
|
+
|
|
970
|
+
const runtime = createSlexKitMarkdownRuntimeHost({
|
|
971
|
+
mode: "trusted", // or "secure"
|
|
972
|
+
theme: "host-shadcn"
|
|
973
|
+
});
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
### 3. Mount blocks
|
|
977
|
+
|
|
978
|
+
For each detected fence, create a container element and mount:
|
|
979
|
+
|
|
980
|
+
```ts
|
|
981
|
+
function processFence(source: string, fenceIndex: number) {
|
|
982
|
+
const container = document.createElement("div");
|
|
983
|
+
// Insert container at the fence position in the document
|
|
984
|
+
|
|
985
|
+
const cleanup = runtime.mountBlock({
|
|
986
|
+
artifactId: "message-42",
|
|
987
|
+
source,
|
|
988
|
+
container
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
return cleanup;
|
|
992
|
+
}
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### 4. Manage lifecycle
|
|
996
|
+
|
|
997
|
+
```ts
|
|
998
|
+
// When a single block is removed
|
|
999
|
+
runtime.disposeBlock(container);
|
|
1000
|
+
|
|
1001
|
+
// When the entire artifact (message/document) is removed
|
|
1002
|
+
runtime.disposeArtifact("message-42");
|
|
1003
|
+
|
|
1004
|
+
// When the plugin/page unloads
|
|
1005
|
+
runtime.disposeAll();
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
### 5. Handle secure mode
|
|
1009
|
+
|
|
1010
|
+
If using secure mode, serve `slexkit.runtime.js` as a public ES module with the correct CORS headers, and configure `secureFrame.runtimeUrl`.
|
|
1011
|
+
|
|
1012
|
+
## Fallback rendering
|
|
1013
|
+
|
|
1014
|
+
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.
|
|
1015
|
+
|
|
1016
|
+
---
|
|
1017
|
+
|
|
1018
|
+
## Security Runtime
|
|
1019
|
+
|
|
1020
|
+
Raw Markdown: /docs/reference/security.md
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
title: Security Runtime Contract
|
|
1024
|
+
category: Reference
|
|
1025
|
+
status: ready
|
|
1026
|
+
order: 50
|
|
1027
|
+
summary: "Threat model, sandbox iframe deployment, host policy, postMessage bridge, and fail-closed behavior."
|
|
1028
|
+
slexkitRenderMode: component
|
|
1029
|
+
---
|
|
1030
|
+
|
|
1031
|
+
# Security Runtime Contract
|
|
1032
|
+
|
|
1033
|
+
The secure runtime defines what untrusted Slex source can and cannot do, how the host authorizes capabilities, and how sandbox isolation works.
|
|
1034
|
+
|
|
1035
|
+
## Threat model
|
|
1036
|
+
|
|
1037
|
+
- The host page and host application are trusted.
|
|
1038
|
+
- Slex source may be untrusted.
|
|
1039
|
+
- Secure artifacts run inside a sandbox iframe.
|
|
1040
|
+
- The iframe uses an opaque origin by default (no `allow-same-origin`).
|
|
1041
|
+
- Slex source must not access the host DOM, cookies, `localStorage`, `IndexedDB`, or host global objects.
|
|
1042
|
+
|
|
1043
|
+
Secure mode confines expression execution to an isolated environment and consolidates sensitive capabilities under the host `policy` and `api.*`.
|
|
1044
|
+
|
|
1045
|
+
## Authorization source
|
|
1046
|
+
|
|
1047
|
+
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.
|
|
1048
|
+
|
|
1049
|
+
All capabilities are accessed through `api.*`:
|
|
1050
|
+
|
|
1051
|
+
```
|
|
1052
|
+
api.get(url, options)
|
|
1053
|
+
api.post(url, body, options)
|
|
1054
|
+
api.fetch(url, options)
|
|
1055
|
+
|
|
1056
|
+
api.setTimeout(fn, ms)
|
|
1057
|
+
api.clearTimeout(id)
|
|
1058
|
+
api.setInterval(fn, ms)
|
|
1059
|
+
api.clearInterval(id)
|
|
1060
|
+
|
|
1061
|
+
api.raf(fn)
|
|
1062
|
+
api.cancelRaf(id)
|
|
1063
|
+
|
|
1064
|
+
api.createCanvas(width, height)
|
|
1065
|
+
api.getCanvasContext(canvas, contextId, options)
|
|
1066
|
+
|
|
1067
|
+
api.onDispose(fn)
|
|
1068
|
+
api.now()
|
|
1069
|
+
|
|
1070
|
+
api.isTimeoutError(error)
|
|
1071
|
+
api.isNetworkError(error)
|
|
1072
|
+
api.isPolicyError(error)
|
|
1073
|
+
api.errorMessage(error)
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
Capabilities not exposed through `api.*` are unsupported.
|
|
1077
|
+
|
|
1078
|
+
## HostRuntimePolicy
|
|
1079
|
+
|
|
1080
|
+
```ts
|
|
1081
|
+
type HostRuntimePolicy = {
|
|
1082
|
+
network?: {
|
|
1083
|
+
enabled: boolean;
|
|
1084
|
+
methods: ("GET" | "POST")[];
|
|
1085
|
+
allowOrigins: string[];
|
|
1086
|
+
allowHeaders?: string[];
|
|
1087
|
+
allowContentTypes?: string[];
|
|
1088
|
+
credentials: "omit" | "same-origin" | "include";
|
|
1089
|
+
timeoutMs: number;
|
|
1090
|
+
maxBodyBytes: number;
|
|
1091
|
+
maxResponseBytes?: number;
|
|
1092
|
+
};
|
|
1093
|
+
timer?: {
|
|
1094
|
+
enabled: boolean;
|
|
1095
|
+
maxTimers: number;
|
|
1096
|
+
minIntervalMs: number;
|
|
1097
|
+
};
|
|
1098
|
+
animation?: {
|
|
1099
|
+
enabled: boolean;
|
|
1100
|
+
};
|
|
1101
|
+
canvas?: {
|
|
1102
|
+
enabled: boolean;
|
|
1103
|
+
maxCanvases?: number;
|
|
1104
|
+
maxPixels?: number;
|
|
1105
|
+
allowedContexts?: ("2d" | "webgl" | "webgl2" | "bitmaprenderer")[];
|
|
1106
|
+
};
|
|
1107
|
+
execution?: {
|
|
1108
|
+
heartbeatIntervalMs?: number;
|
|
1109
|
+
maxUnresponsiveMs?: number;
|
|
1110
|
+
};
|
|
1111
|
+
};
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### Network policy
|
|
1115
|
+
|
|
1116
|
+
Network is denied by default unless `policy.network.enabled` is `true`. The policy constrains:
|
|
1117
|
+
- HTTP method (only listed methods allowed)
|
|
1118
|
+
- Origin (supports `*`, exact match, `protocol://*` protocol wildcard, and `protocol://*.domain` subdomain wildcard)
|
|
1119
|
+
- Request headers (only listed headers pass; `Authorization`, `Cookie`, `Proxy-Authorization`, `Set-Cookie`, and Sec-Fetch headers are always blocked)
|
|
1120
|
+
- Credentials mode
|
|
1121
|
+
- Request body size
|
|
1122
|
+
- Request timeout
|
|
1123
|
+
- Response body size
|
|
1124
|
+
- Response content-type
|
|
1125
|
+
|
|
1126
|
+
`hostAdapter.fetch` can replace the actual request implementation. `hostAdapter.onNetworkLog` is observational only -it must not alter runtime behavior.
|
|
1127
|
+
|
|
1128
|
+
### Timer, animation, and canvas
|
|
1129
|
+
|
|
1130
|
+
- **Timer**: denied by default. When enabled, subject to `maxTimers` (total concurrent) and `minIntervalMs` (minimum delay). All timers and intervals are cleaned up on dispose.
|
|
1131
|
+
- **Animation**: denied by default. Controlled via `animation.enabled`; `api.raf` is the only animation primitive.
|
|
1132
|
+
- **Canvas**: denied by default. When enabled, subject to `maxCanvases`, `maxPixels`, and `allowedContexts`.
|
|
1133
|
+
|
|
1134
|
+
### Execution monitoring
|
|
1135
|
+
|
|
1136
|
+
`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.
|
|
1137
|
+
|
|
1138
|
+
## HostRuntimeAdapter
|
|
1139
|
+
|
|
1140
|
+
The adapter allows the host to override or observe runtime behavior:
|
|
1141
|
+
|
|
1142
|
+
```ts
|
|
1143
|
+
type HostRuntimeAdapter = {
|
|
1144
|
+
fetch?: (request: HostFetchRequest) => Promise<NetworkResult>;
|
|
1145
|
+
onNetworkLog?: (event: RuntimeNetworkLogEvent) => void;
|
|
1146
|
+
onRuntimeError?: (event: RuntimeErrorEvent) => void;
|
|
1147
|
+
now?: () => number;
|
|
1148
|
+
setTimeout?: (fn: () => void, ms: number) => TimerId;
|
|
1149
|
+
clearTimeout?: (id: TimerId) => void;
|
|
1150
|
+
setInterval?: (fn: () => void, ms: number) => TimerId;
|
|
1151
|
+
clearInterval?: (id: TimerId) => void;
|
|
1152
|
+
requestAnimationFrame?: (fn: (time: number) => void) => RafId;
|
|
1153
|
+
cancelAnimationFrame?: (id: RafId) => void;
|
|
1154
|
+
};
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
`onNetworkLog` and `onRuntimeError` are audit hooks -they must not change runtime behavior. Errors thrown within them are silently caught.
|
|
1158
|
+
|
|
1159
|
+
## Sandbox iframe deployment
|
|
1160
|
+
|
|
1161
|
+
The secure frame imports the main runtime module from a `runtimeUrl`:
|
|
1162
|
+
|
|
1163
|
+
```ts
|
|
1164
|
+
mountSecureArtifact(script, container, {
|
|
1165
|
+
frame: {
|
|
1166
|
+
runtimeUrl: "/slexkit.runtime.js"
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
This URL must serve as a public ES module and return:
|
|
1172
|
+
|
|
1173
|
+
```
|
|
1174
|
+
Access-Control-Allow-Origin: *
|
|
1175
|
+
Content-Type: text/javascript
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
This is server or deployment layer configuration -it cannot be set from frontend JavaScript.
|
|
1179
|
+
|
|
1180
|
+
### CSP
|
|
1181
|
+
|
|
1182
|
+
The sandbox iframe is served via `srcdoc` with a strict Content-Security-Policy:
|
|
1183
|
+
|
|
1184
|
+
```
|
|
1185
|
+
default-src 'none'
|
|
1186
|
+
script-src 'nonce-{random}' 'unsafe-eval' {runtimeOrigin}
|
|
1187
|
+
connect-src 'none'
|
|
1188
|
+
img-src data: blob:
|
|
1189
|
+
style-src 'unsafe-inline'
|
|
1190
|
+
font-src data:
|
|
1191
|
+
form-action 'none'
|
|
1192
|
+
base-uri 'none'
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
A random nonce is generated for each frame instance. `unsafe-eval` is required because Slex source expression evaluation uses `eval()` inside the sandbox.
|
|
1196
|
+
|
|
1197
|
+
### Sandbox attribute
|
|
1198
|
+
|
|
1199
|
+
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:
|
|
1200
|
+
|
|
1201
|
+
```ts
|
|
1202
|
+
frame: {
|
|
1203
|
+
unsafeAllowSameOrigin: true, // only with explicit host acceptance
|
|
1204
|
+
sandbox: "allow-scripts allow-same-origin"
|
|
1205
|
+
}
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
Do not add `allow-same-origin` to fix CORS or debugging issues.
|
|
1209
|
+
|
|
1210
|
+
## postMessage bridge protocol
|
|
1211
|
+
|
|
1212
|
+
The host and sandbox communicate via `window.postMessage`. All messages are tagged with `channel: "slexkit-secure"`.
|
|
1213
|
+
|
|
1214
|
+
### Host -Sandbox messages
|
|
1215
|
+
|
|
1216
|
+
| Type | Purpose |
|
|
1217
|
+
|------|---------|
|
|
1218
|
+
| `mount` | Sends Slex source, policy, theme to render |
|
|
1219
|
+
| `dispose` | Tells sandbox to tear down |
|
|
1220
|
+
| `fetch-result` | Returns fetch response or error |
|
|
1221
|
+
| `slots` | Synchronizes artifact slot positions |
|
|
1222
|
+
|
|
1223
|
+
### Sandbox -Host messages
|
|
1224
|
+
|
|
1225
|
+
| Type | Purpose |
|
|
1226
|
+
|------|---------|
|
|
1227
|
+
| `ready` | Runner module loaded and listening |
|
|
1228
|
+
| `mounted` | Artifact render confirmed |
|
|
1229
|
+
| `disposed` | Sandbox teardown acknowledged |
|
|
1230
|
+
| `heartbeat` | Periodic liveness signal |
|
|
1231
|
+
| `error` | Mount or runtime error |
|
|
1232
|
+
| `fetch` | Proxied network request |
|
|
1233
|
+
| `slot-size` | Artifact slot height report |
|
|
1234
|
+
|
|
1235
|
+
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.
|
|
1236
|
+
|
|
1237
|
+
Messages are verified: the sandbox checks `event.source === window.parent`; the host checks `event.source === iframe.contentWindow`.
|
|
1238
|
+
|
|
1239
|
+
## Artifact slot bridge
|
|
1240
|
+
|
|
1241
|
+
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.
|
|
1242
|
+
|
|
1243
|
+
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.
|
|
1244
|
+
|
|
1245
|
+
## Heartbeat watchdog
|
|
1246
|
+
|
|
1247
|
+
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.
|
|
1248
|
+
|
|
1249
|
+
## Fail-closed behavior
|
|
1250
|
+
|
|
1251
|
+
If the iframe cannot load the runtime, does not send a ready/mounted message, or the heartbeat times out, SlexKit:
|
|
1252
|
+
|
|
1253
|
+
1. Removes the unresponsive iframe.
|
|
1254
|
+
2. Renders a `role="alert"` diagnostic element with a description of the failure.
|
|
1255
|
+
3. Emits the same information via `console.error`.
|
|
1256
|
+
|
|
1257
|
+
Load timeout is configurable via `frame.loadTimeoutMs` (default: 8000ms).
|
|
1258
|
+
|
|
1259
|
+
## Escape hatches
|
|
1260
|
+
|
|
1261
|
+
### `unsafeInlineExecution`
|
|
1262
|
+
|
|
1263
|
+
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.
|
|
1264
|
+
|
|
1265
|
+
```ts
|
|
1266
|
+
mountSecureArtifact(script, container, {
|
|
1267
|
+
policy,
|
|
1268
|
+
hostAdapter,
|
|
1269
|
+
unsafeInlineExecution: true
|
|
1270
|
+
});
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
### `unsafeAllowSameOrigin`
|
|
1274
|
+
|
|
1275
|
+
Allows `allow-same-origin` in the sandbox attribute. **Reduces isolation strength** -only use when the host explicitly accepts the risk.
|
|
1276
|
+
|
|
1277
|
+
```ts
|
|
1278
|
+
frame: {
|
|
1279
|
+
sandbox: "allow-scripts allow-same-origin",
|
|
1280
|
+
unsafeAllowSameOrigin: true
|
|
1281
|
+
}
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
## Sandbox hardening
|
|
1285
|
+
|
|
1286
|
+
When the sandbox runner starts, it hardens the global scope:
|
|
1287
|
+
|
|
1288
|
+
**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.
|
|
1289
|
+
|
|
1290
|
+
**Blocked scheduling globals** -`setTimeout`, `setInterval`, `requestAnimationFrame` are replaced with functions that throw, directing code to `api.setTimeout()`, `api.setInterval()`, and `api.raf()`.
|
|
1291
|
+
|
|
1292
|
+
**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.
|
|
1293
|
+
|
|
1294
|
+
## Maintenance principles
|
|
1295
|
+
|
|
1296
|
+
- New capabilities must define a policy field first, then an `api.*` method, then the bridge.
|
|
1297
|
+
- Slex source declarations are never an authorization source.
|
|
1298
|
+
- Default to opaque origin.
|
|
1299
|
+
- Always fail closed.
|
|
1300
|
+
- Log and error hooks are observational -they must not alter runtime behavior.
|
|
1301
|
+
|
|
1302
|
+
---
|
|
1303
|
+
|
|
1304
|
+
## Package Boundaries
|
|
1305
|
+
|
|
1306
|
+
Raw Markdown: /docs/reference/packages.md
|
|
1307
|
+
|
|
1308
|
+
---
|
|
1309
|
+
title: Package Boundaries
|
|
1310
|
+
category: Reference
|
|
1311
|
+
status: ready
|
|
1312
|
+
order: 60
|
|
1313
|
+
summary: "Package relationships, installation matrix, publish contents, and release quality gates."
|
|
1314
|
+
slexkitRenderMode: component
|
|
1315
|
+
---
|
|
1316
|
+
|
|
1317
|
+
# Package Boundaries
|
|
1318
|
+
|
|
1319
|
+
SlexKit v0/beta npm packages, their relationships, and installation.
|
|
1320
|
+
|
|
1321
|
+
## Package relationship
|
|
1322
|
+
|
|
1323
|
+
```
|
|
1324
|
+
slexkit (root - real code)
|
|
1325
|
+
├── runtime entry
|
|
1326
|
+
├── Svelte component registrations
|
|
1327
|
+
├── ToolHost
|
|
1328
|
+
├── default styles
|
|
1329
|
+
└── secure iframe runner
|
|
1330
|
+
|
|
1331
|
+
@slexkit/runtime (thin wrapper) ─── re-exports slexkit/runtime
|
|
1332
|
+
@slexkit/components-svelte (thin wrapper) ─── re-exports slexkit/components-svelte
|
|
1333
|
+
@slexkit/theme-shadcn ─── CSS only
|
|
1334
|
+
@slexkit/streamdown ─── React/Streamdown renderer
|
|
1335
|
+
@slexkit/obsidian ─── Obsidian plugin
|
|
1336
|
+
@slexkit/mcp ─── read-only MCP server for AI agents
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
`@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.
|
|
1340
|
+
|
|
1341
|
+
## slexkit (root)
|
|
1342
|
+
|
|
1343
|
+
The actual implementation package. Contains the runtime engine, official Svelte components, ToolHost, and styles.
|
|
1344
|
+
|
|
1345
|
+
```sh
|
|
1346
|
+
npm install slexkit
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
```js
|
|
1350
|
+
import { mount, disposeNamespace, boot } from "slexkit";
|
|
1351
|
+
import "slexkit/style.css"; // default styles (includes all component CSS)
|
|
1352
|
+
import "slexkit/dist/style.css"; // same distributed CSS bundle via dist alias
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
Version helpers are exported from both the root and runtime entries:
|
|
1356
|
+
|
|
1357
|
+
```js
|
|
1358
|
+
import { SLEXKIT_VERSION, SLEX_PROTOCOL_VERSION, getSlexKitInfo } from "slexkit";
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
## @slexkit/runtime
|
|
1362
|
+
|
|
1363
|
+
Component-free runtime entry point. Does not auto-register any official Svelte components.
|
|
1364
|
+
|
|
1365
|
+
```sh
|
|
1366
|
+
npm install slexkit @slexkit/runtime
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
```js
|
|
1370
|
+
import { mount, register, createSecureRuntime } from "@slexkit/runtime";
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
Use this when you want to register your own component set instead of the bundled Svelte components.
|
|
1374
|
+
|
|
1375
|
+
## @slexkit/components-svelte
|
|
1376
|
+
|
|
1377
|
+
Side-effect import that registers all official Svelte components into the runtime registry.
|
|
1378
|
+
|
|
1379
|
+
```sh
|
|
1380
|
+
npm install slexkit @slexkit/runtime @slexkit/components-svelte
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
```js
|
|
1384
|
+
import { mount } from "@slexkit/runtime";
|
|
1385
|
+
import "@slexkit/components-svelte";
|
|
1386
|
+
```
|
|
1387
|
+
|
|
1388
|
+
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).
|
|
1389
|
+
|
|
1390
|
+
## @slexkit/theme-shadcn
|
|
1391
|
+
|
|
1392
|
+
CSS theme bundle (shadcn/ui compatible).
|
|
1393
|
+
|
|
1394
|
+
```sh
|
|
1395
|
+
npm install @slexkit/theme-shadcn
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
```js
|
|
1399
|
+
import "@slexkit/theme-shadcn/style.css";
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
## @slexkit/streamdown
|
|
1403
|
+
|
|
1404
|
+
React/Streamdown custom renderer for Markdown-hosted SlexKit fences.
|
|
1405
|
+
|
|
1406
|
+
```sh
|
|
1407
|
+
npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown streamdown react react-dom
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
```tsx
|
|
1411
|
+
import { Streamdown } from "streamdown";
|
|
1412
|
+
import { slexkitRenderer } from "@slexkit/streamdown";
|
|
1413
|
+
import "@slexkit/theme-shadcn/style.css";
|
|
1414
|
+
import "@slexkit/streamdown/style.css";
|
|
1415
|
+
|
|
1416
|
+
export function Message({ markdown }: { markdown: string }) {
|
|
1417
|
+
return (
|
|
1418
|
+
<Streamdown plugins={{ renderers: [slexkitRenderer] }}>
|
|
1419
|
+
{markdown}
|
|
1420
|
+
</Streamdown>
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
Processes `slex` fences. Supports both trusted and secure runtime modes.
|
|
1426
|
+
|
|
1427
|
+
## @slexkit/obsidian
|
|
1428
|
+
|
|
1429
|
+
Obsidian plugin adapter. Registers SlexKit fenced code block processors and renders local vault content in reading mode.
|
|
1430
|
+
|
|
1431
|
+
```sh
|
|
1432
|
+
npm install slexkit @slexkit/obsidian
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
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. Obsidian secure sandbox support is not part of the v0 adapter.
|
|
1436
|
+
|
|
1437
|
+
## @slexkit/mcp
|
|
1438
|
+
|
|
1439
|
+
Read-only MCP server for AI agents. It serves generated LLM docs, component metadata, examples, runtime docs, ToolHost docs, and Slex source validation.
|
|
1440
|
+
|
|
1441
|
+
```sh
|
|
1442
|
+
npx -y @slexkit/mcp
|
|
1443
|
+
```
|
|
1444
|
+
|
|
1445
|
+
The server does not modify project files. Use it when an agent needs current SlexKit component or runtime context.
|
|
1446
|
+
|
|
1447
|
+
## Installation matrix
|
|
1448
|
+
|
|
1449
|
+
| Use case | Install command |
|
|
1450
|
+
|----------|----------------|
|
|
1451
|
+
| Quick start, everything included | `npm install slexkit` |
|
|
1452
|
+
| Component-free, custom components | `npm install slexkit @slexkit/runtime` |
|
|
1453
|
+
| With Svelte components | `npm install slexkit @slexkit/runtime @slexkit/components-svelte` |
|
|
1454
|
+
| Add shadcn theme | `npm install @slexkit/theme-shadcn` |
|
|
1455
|
+
| React/Streamdown host | `npm install slexkit @slexkit/theme-shadcn @slexkit/streamdown streamdown react react-dom` |
|
|
1456
|
+
| Obsidian plugin | `npm install slexkit @slexkit/obsidian` |
|
|
1457
|
+
| AI agent MCP server | `npx -y @slexkit/mcp` |
|
|
1458
|
+
|
|
1459
|
+
## v0 packaging strategy
|
|
1460
|
+
|
|
1461
|
+
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.
|
|
1462
|
+
|
|
1463
|
+
## Release quality gate
|
|
1464
|
+
|
|
1465
|
+
All scoped packages are release-checked together:
|
|
1466
|
+
|
|
1467
|
+
```sh
|
|
1468
|
+
bun run build
|
|
1469
|
+
bun run test
|
|
1470
|
+
bun run smoke:release
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
The release smoke packs and installs every scoped package, verifies public entry points, verifies CSS subpath exports, loads the Obsidian CJS bundle with an Obsidian module mock, and starts the MCP stdio binary to check `initialize`, `tools/list`, and `slexkitValidate`.
|
|
1474
|
+
|
|
1475
|
+
---
|