@symbiotejs/symbiote 3.8.0 → 3.8.2
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/README.md +25 -120
- package/core/webmcp.js +60 -39
- package/docs/README.md +0 -6
- package/docs/animations.md +1 -1
- package/docs/attributes.md +2 -2
- package/docs/common-mistakes.md +63 -1
- package/docs/context.md +212 -73
- package/docs/ecosystem.md +0 -3
- package/docs/llms-index.md +0 -1
- package/docs/webmcp.md +5 -9
- package/llms-full.txt +308 -204
- package/llms.txt +0 -1
- package/package.json +1 -2
- package/scripts/build-llms.js +0 -2
- package/types/core/webmcp.d.ts +3 -3
- package/types/core/webmcp.d.ts.map +1 -1
- package/CHANGELOG.md +0 -372
- package/docs/lit-vs-symbiote.md +0 -200
- package/docs/migration-2x-to-3x.md +0 -171
package/README.md
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
[](https://github.com/symbiotejs/symbiote.js/actions/workflows/tests.yml)
|
|
2
2
|
[](https://www.npmjs.com/package/@symbiotejs/symbiote)
|
|
3
3
|
[](https://www.npmjs.com/package/@symbiotejs/symbiote)
|
|
4
|
-

|
|
5
|
-

|
|
6
4
|

|
|
7
5
|
|
|
8
6
|
# Symbiote.js
|
|
9
7
|
|
|
10
8
|
<img src="https://rnd-pro.com/svg/symbiote/index.svg" width="200" alt="Symbiote.js">
|
|
11
9
|
|
|
12
|
-
A lightweight, standards-first UI library built on Web Components. No virtual DOM, no compiler, no build step required - works directly in the browser. A bundler is recommended for production performance, but entirely optional.
|
|
10
|
+
A lightweight, standards-first UI library built on Web Components. No virtual DOM, no compiler, no black boxes, no excess repaints. No build step required - works directly in the browser. A bundler is recommended for production performance, but entirely optional.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Here are the three most important differences between Symbiote.js and other frameworks:
|
|
13
|
+
1. **Natural DOM Extension Philosophy** - designed to extend platform APIs, not to replace them
|
|
14
|
+
2. **Runtime-Agnostic HTML Templates** - outstanding flexibility for rendering strategies and further customization
|
|
15
|
+
3. **Powerful App-wide State Management** - combine data contexts without bloated boilerplate or external tools
|
|
15
16
|
|
|
16
|
-
## What's new?
|
|
17
|
+
## What's new in v3.x?
|
|
17
18
|
|
|
18
|
-
- **
|
|
19
|
+
- **WebMCP support** - expose live Symbiote UI actions as browser-native tools for agents. See the [WebMCP docs](https://github.com/symbiotejs/symbiote.js/blob/webmcp/docs/webmcp.md).
|
|
19
20
|
- **Server-Side Rendering** - render components to HTML with `SSR.processHtml()` or stream chunks with `SSR.renderToStream()`. Client-side hydration via `ssrMode` attaches bindings to existing DOM without re-rendering.
|
|
20
21
|
- **Isomorphic components** - `isoMode` flag makes components work in both SSR and client-only scenarios automatically. If server-rendered content exists, it hydrates; otherwise it renders the template from scratch. One component, zero conditional logic.
|
|
21
|
-
- **Computed properties** - reactive derived state with microtask batching.
|
|
22
|
+
- **Computed properties refined** - reactive derived state with microtask batching.
|
|
22
23
|
- **Path-based router** - optional `AppRouter` module with `:param` extraction, route guards, and lazy loading.
|
|
23
24
|
- **Exit animations** - `animateOut(el)` for CSS-driven exit transitions, integrated into itemize API.
|
|
24
25
|
- **Dev mode** - `Symbiote.devMode` enables verbose warnings; import `devMessages.js` for full human-readable messages.
|
|
25
26
|
- **DSD hydration** - `ssrMode` supports both light DOM and Declarative Shadow DOM.
|
|
26
27
|
- **Class property fallback** - binding keys not in `init$` fall back to own class properties/methods.
|
|
27
28
|
- **Lazy mode** - `lazyMode` flag defers component initialization and rendering based on viewport visibility. Can also be enabled via the `lazy` attribute on `itemize` containers to efficiently handle massive data sets.
|
|
28
|
-
- And [more](https://github.com/symbiotejs/symbiote.js/blob/webmcp/CHANGELOG.md).
|
|
29
29
|
|
|
30
30
|
## Quick start
|
|
31
31
|
|
|
@@ -63,57 +63,6 @@ npm i @symbiotejs/symbiote
|
|
|
63
63
|
import Symbiote, { html, css } from '@symbiotejs/symbiote';
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
## Isomorphic Web Components
|
|
67
|
-
|
|
68
|
-
One component. Server-rendered or client-rendered - automatically. Set `isoMode = true` and the component figures it out: if server-rendered content exists, it hydrates; otherwise it renders from template. No conditional logic, no separate server/client versions:
|
|
69
|
-
```js
|
|
70
|
-
class MyComponent extends Symbiote {
|
|
71
|
-
isoMode = true;
|
|
72
|
-
count = 0;
|
|
73
|
-
increment() {
|
|
74
|
-
this.$.count++;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
MyComponent.template = html`
|
|
79
|
-
<h2 ${{textContent: 'count'}}></h2>
|
|
80
|
-
<button ${{onclick: 'increment'}}>Click me!</button>
|
|
81
|
-
`;
|
|
82
|
-
MyComponent.reg('my-component');
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
This exact code runs **everywhere** - SSR on the server, hydration on the client, or pure client rendering. No framework split, no `'use client'` directives, no hydration mismatch errors.
|
|
86
|
-
|
|
87
|
-
### SSR - one class, zero config
|
|
88
|
-
|
|
89
|
-
Server rendering doesn't need a virtual DOM, a reconciler, or framework-specific packages:
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
import { SSR } from '@symbiotejs/symbiote/node/SSR.js';
|
|
93
|
-
|
|
94
|
-
await SSR.init(); // patches globals with linkedom
|
|
95
|
-
await import('./my-app.js'); // components register normally
|
|
96
|
-
|
|
97
|
-
let html = await SSR.processHtml('<my-app></my-app>');
|
|
98
|
-
SSR.destroy();
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
For large pages, stream HTML chunks with `SSR.renderToStream()` for faster TTFB. See [SSR docs](./docs/ssr.md) and [server setup recipes](./docs/ssr-server.md).
|
|
102
|
-
|
|
103
|
-
### How it compares
|
|
104
|
-
|
|
105
|
-
| | **Symbiote.js** | **Next.js (React)** | **Lit** (`@lit-labs/ssr`) |
|
|
106
|
-
|--|----------------|---------------------|----|
|
|
107
|
-
| **Isomorphic code** | Same code, `isoMode` auto-detects | Server Components vs Client Components split | Same code, but load-order constraints |
|
|
108
|
-
| **Hydration** | Binding-based - attaches to existing DOM, no diffing | `hydrateRoot()` - must produce identical output or errors | Requires `ssr-client` + hydrate support module |
|
|
109
|
-
| **Packages** | 1 module + `linkedom` peer dep | Full framework buy-in | 3 packages: `ssr`, `ssr-client`, `ssr-dom-shim` |
|
|
110
|
-
| **Streaming** | `renderToStream()` async generator | `renderToPipeableStream()` | Iterable `RenderResult` |
|
|
111
|
-
| **Mismatch handling** | Not needed - bindings attach to whatever DOM exists | Hard errors if server/client output differs | N/A |
|
|
112
|
-
| **Template output** | Clean HTML with `bind=` attributes | HTML with framework markers | HTML with `<!--lit-part-->` comment markers |
|
|
113
|
-
| **Lock-in** | None - standard Web Components | Full framework commitment | Lit-specific, but Web Components |
|
|
114
|
-
|
|
115
|
-
**Key insight:** There are no hydration mismatches because there's no diffing. The server produces HTML with binding attributes. The client reads those attributes and adds reactivity. That's it.
|
|
116
|
-
|
|
117
66
|
## Core concepts
|
|
118
67
|
|
|
119
68
|
### Reactive state
|
|
@@ -142,7 +91,7 @@ This makes it easy to control Symbiote-based widgets and microfrontends from any
|
|
|
142
91
|
|
|
143
92
|
### Templates
|
|
144
93
|
|
|
145
|
-
Templates are plain HTML strings -
|
|
94
|
+
Templates are plain HTML strings - runtime-agnostic, easy to test, easy to move between files:
|
|
146
95
|
|
|
147
96
|
```js
|
|
148
97
|
// Separate file: my-component.template.js
|
|
@@ -160,34 +109,24 @@ The `html` function supports two interpolation modes:
|
|
|
160
109
|
|
|
161
110
|
### Itemize (dynamic reactive lists)
|
|
162
111
|
|
|
163
|
-
Render lists from data arrays with efficient updates:
|
|
112
|
+
Render lists from data arrays or objects with efficient updates:
|
|
164
113
|
```js
|
|
165
114
|
class TaskList extends Symbiote {
|
|
166
115
|
tasks = [
|
|
167
116
|
{ name: 'Buy groceries' },
|
|
168
117
|
{ name: 'Write docs' },
|
|
169
118
|
];
|
|
170
|
-
init$ = {
|
|
171
|
-
// Needs to be defined in init$ for pop-up binding to work
|
|
172
|
-
onItemClick: () => {
|
|
173
|
-
console.log('clicked!');
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
119
|
}
|
|
177
120
|
|
|
178
121
|
TaskList.template = html`
|
|
179
|
-
<
|
|
122
|
+
<ul itemize="tasks">
|
|
180
123
|
<template>
|
|
181
|
-
<
|
|
124
|
+
<li>{{name}}</li>
|
|
182
125
|
</template>
|
|
183
|
-
</
|
|
126
|
+
</ul>
|
|
184
127
|
`;
|
|
185
128
|
```
|
|
186
129
|
|
|
187
|
-
Items have their own state scope. Use the **`^` prefix** to reach higher-level component properties and handlers - `'^onItemClick'` binds to the parent's `onItemClick`, not the item's. Properties referenced via `^` must be defined in the parent's `init$`.
|
|
188
|
-
|
|
189
|
-
> **Performance Tip:** For massive lists, add the `lazy` attribute to the container (`<div itemize="tasks" lazy>`). It defers component initialization until they enter the viewport and cleans them up when they leave, heavily optimizing memory and rendering performance.
|
|
190
|
-
|
|
191
130
|
### Pop-up binding (`^`)
|
|
192
131
|
|
|
193
132
|
The `^` prefix works in any nested component template - it walks up the DOM tree to find the nearest ancestor that has the property registered in its data context (`init$` or `add$()`):
|
|
@@ -200,14 +139,11 @@ The `^` prefix works in any nested component template - it walks up the DOM tree
|
|
|
200
139
|
<button ${{onclick: '^parentHandler'}}>Click</button>
|
|
201
140
|
```
|
|
202
141
|
|
|
203
|
-
> **Note:** Class property fallbacks are not checked by the `^` walk - the parent must define the property in `init$`.
|
|
204
|
-
|
|
205
142
|
### Named data contexts
|
|
206
143
|
|
|
207
144
|
Share state across components without prop drilling:
|
|
208
|
-
|
|
209
145
|
```js
|
|
210
|
-
import { PubSub } from '@symbiotejs/symbiote';
|
|
146
|
+
import { PubSub, html } from '@symbiotejs/symbiote';
|
|
211
147
|
|
|
212
148
|
PubSub.registerCtx({
|
|
213
149
|
user: 'Alex',
|
|
@@ -216,6 +152,9 @@ PubSub.registerCtx({
|
|
|
216
152
|
|
|
217
153
|
// Any component can read/write:
|
|
218
154
|
this.$['APP/user'] = 'New name';
|
|
155
|
+
|
|
156
|
+
// Any template can use property directly:
|
|
157
|
+
let template = html`<h2>{{APP/user}}</h2>`;
|
|
219
158
|
```
|
|
220
159
|
|
|
221
160
|
### Shared context (`*`)
|
|
@@ -248,11 +187,10 @@ class StatusBar extends Symbiote {
|
|
|
248
187
|
|
|
249
188
|
All three components access the same `*files` state - no parent component, no prop drilling, no global store boilerplate. Just set `ctx="gallery"` in HTML and use `*`-prefixed properties. This makes it trivial to build complex component relationships purely in markup, with ready-made components that don't need to know about each other.
|
|
250
189
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
### Routing (optional module)
|
|
190
|
+
### Application routing
|
|
254
191
|
|
|
255
192
|
```js
|
|
193
|
+
// Import optional module:
|
|
256
194
|
import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js';
|
|
257
195
|
|
|
258
196
|
AppRouter.initRoutingCtx('R', {
|
|
@@ -262,23 +200,7 @@ AppRouter.initRoutingCtx('R', {
|
|
|
262
200
|
});
|
|
263
201
|
```
|
|
264
202
|
|
|
265
|
-
###
|
|
266
|
-
|
|
267
|
-
CSS-driven transitions with zero JS animation code:
|
|
268
|
-
|
|
269
|
-
```css
|
|
270
|
-
task-item {
|
|
271
|
-
opacity: 1;
|
|
272
|
-
transition: opacity 0.3s;
|
|
273
|
-
|
|
274
|
-
@starting-style { opacity: 0; } /* enter */
|
|
275
|
-
&[leaving] { opacity: 0; } /* exit */
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
`animateOut(el)` sets `[leaving]`, waits for `transitionend`, then removes. Itemize uses this automatically.
|
|
280
|
-
|
|
281
|
-
### Styling
|
|
203
|
+
### CSS Styling
|
|
282
204
|
|
|
283
205
|
Shadow DOM is **optional** in Symbiote - use it when you need isolation, skip it when you don't. This gives full flexibility:
|
|
284
206
|
|
|
@@ -295,6 +217,8 @@ MyComponent.rootStyles = css`
|
|
|
295
217
|
`;
|
|
296
218
|
```
|
|
297
219
|
|
|
220
|
+
This style will be applied to nearest upper shadow root, if exists and to common document if not.
|
|
221
|
+
|
|
298
222
|
**Shadow DOM** - opt-in isolation when needed:
|
|
299
223
|
|
|
300
224
|
```js
|
|
@@ -308,9 +232,9 @@ Isolated.shadowStyles = css`
|
|
|
308
232
|
|
|
309
233
|
All native CSS features work as expected: CSS variables flow through shadow boundaries, `::part()` exposes internals, modern nesting, `@layer`, `@container` - no framework abstractions in the way. Mix light DOM and shadow DOM components freely in the same app.
|
|
310
234
|
|
|
311
|
-
### CSS Data
|
|
235
|
+
### CSS Data
|
|
312
236
|
|
|
313
|
-
Components can read CSS custom
|
|
237
|
+
Components can read CSS custom property values to initiate reactive state:
|
|
314
238
|
|
|
315
239
|
```css
|
|
316
240
|
my-widget {
|
|
@@ -326,8 +250,6 @@ MyWidget.template = html`
|
|
|
326
250
|
`;
|
|
327
251
|
```
|
|
328
252
|
|
|
329
|
-
CSS values are parsed automatically - quoted strings become strings, numbers become numbers. Call `this.updateCssData()` to re-read after runtime CSS changes. This enables CSS-driven configuration: theme values, layout parameters, or localized strings - all settable from CSS without touching JS.
|
|
330
|
-
|
|
331
253
|
## Best for
|
|
332
254
|
|
|
333
255
|
- **Complex widgets** embedded in any host application
|
|
@@ -336,32 +258,15 @@ CSS values are parsed automatically - quoted strings become strings, numbers bec
|
|
|
336
258
|
- **Reusable component libraries** - works in React, Vue, Angular, or plain HTML
|
|
337
259
|
- **SSR-powered apps** - lightweight server rendering without framework lock-in
|
|
338
260
|
- **Framework-agnostic solutions** - one codebase, any context
|
|
339
|
-
|
|
340
|
-
## Bundle size
|
|
341
|
-
|
|
342
|
-
| Library | Minified | Gzip | Brotli |
|
|
343
|
-
|---------|----------|------|--------|
|
|
344
|
-
| **Symbiote.js** (core) | 23.6 kb | 8.1 kb | **7.3 kb** |
|
|
345
|
-
| **Symbiote.js** (full, with AppRouter + WebMCP export) | 35.6 kb | 12.1 kb | **11.0 kb** |
|
|
346
|
-
| **Symbiote.js** (WebMCP extension) | 31.4 kb | 10.6 kb | **9.6 kb** |
|
|
347
|
-
| **Lit** 3.3 | 15.5 kb | 6.0 kb | **~5.1 kb** |
|
|
348
|
-
| **React 19 + ReactDOM** | ~186 kb | ~59 kb | **~50 kb** |
|
|
349
|
-
|
|
350
|
-
Symbiote and Lit have similar base sizes, but Symbiote's **7.3 kb** core includes more built-in features: global state management, lists (itemize API), exit animations, computed properties etc. Lit needs additional packages for comparable features. React is **~7× larger** before adding a router, state manager, or SSR framework.
|
|
351
|
-
|
|
352
|
-
## Browser support
|
|
353
|
-
|
|
354
|
-
All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
|
|
261
|
+
- **Modern AI-first web** - expose the application state to WebMCP tools automatically
|
|
355
262
|
|
|
356
263
|
## Docs & Examples
|
|
357
264
|
|
|
358
265
|
- [Documentation](https://github.com/symbiotejs/symbiote.js/blob/main/docs/README.md)
|
|
359
|
-
- [Lit vs Symbiote.js](https://github.com/symbiotejs/symbiote.js/blob/main/docs/lit-vs-symbiote.md) - Side-by-side comparison
|
|
360
266
|
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
361
267
|
- [JSDA-Kit](https://github.com/rnd-pro/jsda-kit) - All-in-one companion tool: server, SSG, bundling, import maps, and native Symbiote.js SSR integration
|
|
362
268
|
- [AI / llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
|
|
363
269
|
- [Full docs (single file)](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
|
|
364
|
-
- [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
|
|
365
270
|
|
|
366
271
|
## Related articles
|
|
367
272
|
|
package/core/webmcp.js
CHANGED
|
@@ -22,7 +22,7 @@ import { parseProp } from './parseProp.js';
|
|
|
22
22
|
* @property {string | ((owner?: any) => string)} [description]
|
|
23
23
|
* @property {Object | ((owner?: any) => Object)} [inputSchema]
|
|
24
24
|
* @property {(args?: Object, owner?: any, event?: Event) => any} [execute]
|
|
25
|
-
* @property {() => boolean} [when]
|
|
25
|
+
* @property {(owner?: any) => boolean} [when]
|
|
26
26
|
* @property {string[]} [deps]
|
|
27
27
|
* @property {string[]} [exposedTo]
|
|
28
28
|
* @property {Object} [annotations]
|
|
@@ -53,6 +53,7 @@ let installedClasses = new WeakSet();
|
|
|
53
53
|
let pendingOwnerSync = new WeakSet();
|
|
54
54
|
/** @type {WeakMap<object, number>} */
|
|
55
55
|
let ownerVersions = new WeakMap();
|
|
56
|
+
let _modelContext;
|
|
56
57
|
|
|
57
58
|
function isToolDescriptor(val) {
|
|
58
59
|
return !!val?.[DICT.MCP_TOOL_DESCRIPTOR_MARKER];
|
|
@@ -244,13 +245,16 @@ function ownerId(owner) {
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
function getModelContext() {
|
|
248
|
+
if (_modelContext) return _modelContext;
|
|
247
249
|
let docCtx = /** @type {any} */ (globalThis.document)?.modelContext;
|
|
248
250
|
if (docCtx?.registerTool) {
|
|
249
|
-
|
|
251
|
+
_modelContext = docCtx;
|
|
252
|
+
return _modelContext;
|
|
250
253
|
}
|
|
251
254
|
let navCtx = /** @type {any} */ (globalThis.navigator)?.modelContext;
|
|
252
255
|
if (navCtx?.registerTool) {
|
|
253
|
-
|
|
256
|
+
_modelContext = navCtx;
|
|
257
|
+
return _modelContext;
|
|
254
258
|
}
|
|
255
259
|
return null;
|
|
256
260
|
}
|
|
@@ -591,13 +595,16 @@ function scheduleOwnerSync(owner) {
|
|
|
591
595
|
}
|
|
592
596
|
|
|
593
597
|
export class ToolDescriptor {
|
|
598
|
+
/** @type {((args?: Object, owner?: any, event?: Event) => any) | undefined} */
|
|
599
|
+
#fn;
|
|
600
|
+
|
|
594
601
|
/** @param {ToolDescriptorOptions} options */
|
|
595
602
|
constructor(options = {}) {
|
|
596
603
|
this[DICT.MCP_TOOL_DESCRIPTOR_MARKER] = true;
|
|
597
604
|
this.name = options.name;
|
|
598
605
|
this.description = options.description || 'Symbiote WebMCP tool.';
|
|
599
606
|
this.inputSchema = options.inputSchema || emptySchema();
|
|
600
|
-
this
|
|
607
|
+
this.#fn = options.execute;
|
|
601
608
|
this.when = options.when;
|
|
602
609
|
this.deps = options.deps || [];
|
|
603
610
|
this.exposedTo = options.exposedTo;
|
|
@@ -611,15 +618,15 @@ export class ToolDescriptor {
|
|
|
611
618
|
* @returns {any}
|
|
612
619
|
*/
|
|
613
620
|
execute(args = {}, owner, event) {
|
|
614
|
-
if (typeof this
|
|
621
|
+
if (typeof this.#fn !== 'function') {
|
|
615
622
|
throw new Error('ToolDescriptor requires an execute function.');
|
|
616
623
|
}
|
|
617
|
-
return this
|
|
624
|
+
return this.#fn.call(owner, args || {}, owner, event);
|
|
618
625
|
}
|
|
619
626
|
}
|
|
620
627
|
|
|
621
628
|
export const webMCPRegistry = (() => {
|
|
622
|
-
let existing = PubSub.getCtx(REGISTRY_UID
|
|
629
|
+
let existing = PubSub.getCtx(REGISTRY_UID);
|
|
623
630
|
let root = existing || PubSub.registerCtx({}, REGISTRY_UID);
|
|
624
631
|
ensureNestedCtx(root, 'tools');
|
|
625
632
|
ensureNestedCtx(root, 'owners');
|
|
@@ -662,32 +669,23 @@ export function registerWebMCPTool(owner, key, descriptor) {
|
|
|
662
669
|
webMCPRegistry.store.tools.add(publicName, resolvedEntry, true);
|
|
663
670
|
}
|
|
664
671
|
return resolvedEntry;
|
|
672
|
+
}).catch((e) => {
|
|
673
|
+
setDiagnostic('lastError', e);
|
|
674
|
+
throw e;
|
|
665
675
|
});
|
|
666
676
|
}
|
|
667
677
|
webMCPRegistry.store.tools.add(publicName, entry, true);
|
|
668
678
|
return entry;
|
|
669
679
|
}
|
|
670
680
|
|
|
671
|
-
/**
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
let id = ownerId(owner);
|
|
680
|
-
let desired = collectDesiredTools(owner);
|
|
681
|
-
setupDeps(owner, desired);
|
|
682
|
-
let owners = webMCPRegistry.store.owners;
|
|
683
|
-
let ownerEntry = owners.has(id)
|
|
684
|
-
? owners.read(id)
|
|
685
|
-
: {
|
|
686
|
-
ownerId: id,
|
|
687
|
-
ownerType: ownerType(owner),
|
|
688
|
-
ownerName: ownerName(owner),
|
|
689
|
-
toolsByKey: {},
|
|
690
|
-
};
|
|
681
|
+
/**
|
|
682
|
+
* @param {any} owner
|
|
683
|
+
* @param {string} id
|
|
684
|
+
* @param {{ownerId: string, ownerType: string, ownerName: string, toolsByKey: Record<string, string>}} ownerEntry
|
|
685
|
+
* @param {Array<{key: string, baseName: string, descriptor: ToolDescriptor, executor: Function}>} desired
|
|
686
|
+
* @param {string} componentDescription
|
|
687
|
+
*/
|
|
688
|
+
function applyDesiredTools(owner, id, ownerEntry, desired, componentDescription) {
|
|
691
689
|
let nextKeys = new Set();
|
|
692
690
|
for (let item of desired) {
|
|
693
691
|
nextKeys.add(item.key);
|
|
@@ -703,17 +701,9 @@ export function syncWebMCPTools(owner) {
|
|
|
703
701
|
if (hasToolName(publicName)) {
|
|
704
702
|
unregisterToolName(publicName);
|
|
705
703
|
}
|
|
706
|
-
let entry =
|
|
704
|
+
let entry = makeEntryWithComponentDescription(publicName, item.key, item.descriptor, owner, item.executor, componentDescription);
|
|
707
705
|
ownerEntry.toolsByKey[item.key] = publicName;
|
|
708
|
-
|
|
709
|
-
entry.then((resolvedEntry) => {
|
|
710
|
-
if (isCurrentOwnerVersion(owner, version)) {
|
|
711
|
-
webMCPRegistry.store.tools.add(publicName, resolvedEntry, true);
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
} else {
|
|
715
|
-
webMCPRegistry.store.tools.add(publicName, entry, true);
|
|
716
|
-
}
|
|
706
|
+
webMCPRegistry.store.tools.add(publicName, entry, true);
|
|
717
707
|
}
|
|
718
708
|
for (let key in ownerEntry.toolsByKey) {
|
|
719
709
|
if (!nextKeys.has(key)) {
|
|
@@ -723,9 +713,40 @@ export function syncWebMCPTools(owner) {
|
|
|
723
713
|
}
|
|
724
714
|
if (Object.keys(ownerEntry.toolsByKey).length) {
|
|
725
715
|
updateOwnerEntry(id, ownerEntry);
|
|
726
|
-
} else if (owners.has(id)) {
|
|
727
|
-
owners.delete(id);
|
|
716
|
+
} else if (webMCPRegistry.store.owners.has(id)) {
|
|
717
|
+
webMCPRegistry.store.owners.delete(id);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** @param {any} owner */
|
|
722
|
+
export function syncWebMCPTools(owner) {
|
|
723
|
+
if (!owner) return;
|
|
724
|
+
if (isElementOwner(owner) && !owner.isConnected) {
|
|
725
|
+
unregisterWebMCPTools(owner);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
let version = bumpOwnerVersion(owner);
|
|
729
|
+
let id = ownerId(owner);
|
|
730
|
+
let desired = collectDesiredTools(owner);
|
|
731
|
+
setupDeps(owner, desired);
|
|
732
|
+
let owners = webMCPRegistry.store.owners;
|
|
733
|
+
let ownerEntry = owners.has(id)
|
|
734
|
+
? owners.read(id)
|
|
735
|
+
: {
|
|
736
|
+
ownerId: id,
|
|
737
|
+
ownerType: ownerType(owner),
|
|
738
|
+
ownerName: ownerName(owner),
|
|
739
|
+
toolsByKey: {},
|
|
740
|
+
};
|
|
741
|
+
let componentDescription = readComponentDescription(owner);
|
|
742
|
+
if (typeof componentDescription !== 'string') {
|
|
743
|
+
componentDescription.then((resolved) => {
|
|
744
|
+
if (!isCurrentOwnerVersion(owner, version)) return;
|
|
745
|
+
applyDesiredTools(owner, id, ownerEntry, desired, resolved);
|
|
746
|
+
}).catch((e) => setDiagnostic('lastError', e));
|
|
747
|
+
return;
|
|
728
748
|
}
|
|
749
|
+
applyDesiredTools(owner, id, ownerEntry, desired, componentDescription);
|
|
729
750
|
}
|
|
730
751
|
|
|
731
752
|
/** @param {any} owner */
|
package/docs/README.md
CHANGED
|
@@ -41,19 +41,13 @@
|
|
|
41
41
|
- [**TypeScript**](./typescript.md) — JSDoc types, TypeScript usage, and hybrid template checks
|
|
42
42
|
- [**Security**](./security.md) — Trusted Types and CSP compliance
|
|
43
43
|
|
|
44
|
-
## Migration
|
|
45
|
-
|
|
46
|
-
- [**2.x → 3.x Migration Guide**](./migration-2x-to-3x.md) — Breaking changes and upgrade path
|
|
47
|
-
|
|
48
44
|
## Additional Resources
|
|
49
45
|
|
|
50
|
-
- [Lit vs Symbiote.js](./lit-vs-symbiote.md) — Side-by-side comparison
|
|
51
46
|
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
52
47
|
- [Common Mistakes](./common-mistakes.md) — Patterns that frequently cause bugs
|
|
53
48
|
- [Examples](./examples.md) — 26 complete working examples
|
|
54
49
|
- [llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
|
|
55
50
|
- [llms-full.txt](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
|
|
56
|
-
- [Changelog](../CHANGELOG.md)
|
|
57
51
|
- [npm](https://www.npmjs.com/package/@symbiotejs/symbiote)
|
|
58
52
|
- [GitHub Discussions](https://github.com/symbiotejs/symbiote.js/discussions)
|
|
59
53
|
|
package/docs/animations.md
CHANGED
|
@@ -38,7 +38,7 @@ my-item {
|
|
|
38
38
|
|
|
39
39
|
## Itemize integration
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Itemize processors use `animateOut` automatically for item removal. Items with CSS `transition` + `[leaving]` styles will animate out before being removed from the DOM:
|
|
42
42
|
```css
|
|
43
43
|
user-card {
|
|
44
44
|
opacity: 1;
|
package/docs/attributes.md
CHANGED
|
@@ -22,7 +22,7 @@ class MyComponent extends Symbiote {}
|
|
|
22
22
|
MyComponent.template = html`<h1>{{@attribute-name}}</h1>`;
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Then use it as an attribute in markup:
|
|
25
|
+
Then use it as an component's attribute in markup:
|
|
26
26
|
```html
|
|
27
27
|
<my-component attribute-name="attribute value"></my-component>
|
|
28
28
|
```
|
|
@@ -46,7 +46,7 @@ MyComponent.observedAttributes = [
|
|
|
46
46
|
|
|
47
47
|
## `bindAttributes()` static method
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Reflect attribute values to property values:
|
|
50
50
|
```js
|
|
51
51
|
class MyComponent extends Symbiote {
|
|
52
52
|
|
package/docs/common-mistakes.md
CHANGED
|
@@ -243,7 +243,69 @@ Named external contexts (`CTX/prop`) are different: they are registered globally
|
|
|
243
243
|
|
|
244
244
|
---
|
|
245
245
|
|
|
246
|
-
## 10.
|
|
246
|
+
## 10. Using `{{prop}}` binding syntax inside tag definitions
|
|
247
|
+
|
|
248
|
+
`{{prop}}` is a **text node** binding — it only works inside element content. Any binding that targets a tag itself — attributes, DOM properties, event handlers, or CSS custom properties — must use `${{}}` or `bind=""` syntax inside the opening tag.
|
|
249
|
+
|
|
250
|
+
```html
|
|
251
|
+
<!-- WRONG — {{}} syntax does not work inside tag definitions -->
|
|
252
|
+
<div class="{{APP/theme}}">...</div>
|
|
253
|
+
<img src="{{imageUrl}}">
|
|
254
|
+
<button onclick="{{onAction}}">Go</button>
|
|
255
|
+
|
|
256
|
+
<!-- CORRECT — use ${{}} binding object inside the tag -->
|
|
257
|
+
<div ${{'@class': 'APP/theme'}}>...</div>
|
|
258
|
+
<img ${{'@src': 'imageUrl'}}>
|
|
259
|
+
<button ${{onclick: 'onAction'}}>Go</button>
|
|
260
|
+
<div ${{'style.color': 'colorProp'}}>...</div>
|
|
261
|
+
|
|
262
|
+
<!-- CORRECT — or use the bind attribute string syntax -->
|
|
263
|
+
<div bind="@class: APP/theme">...</div>
|
|
264
|
+
<img bind="@src: imageUrl">
|
|
265
|
+
<button bind="onclick: onAction">Go</button>
|
|
266
|
+
|
|
267
|
+
<!-- CORRECT — {{}} works fine in text node content -->
|
|
268
|
+
<h1>Hello, {{userName}}!</h1>
|
|
269
|
+
<p>Theme: {{APP/theme}}</p>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 11. Expecting two-way data binding
|
|
275
|
+
|
|
276
|
+
Symbiote.js bindings are **one-way by design** — from state to DOM. There is no `v-model`, `[(ngModel)]`, or `bind:value` equivalent. To react to user input, wire the event handler explicitly and write back to state yourself.
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
// WRONG — expecting the binding to also update state on user input
|
|
280
|
+
MyComponent.template = html`
|
|
281
|
+
<input ${{value: 'query'}}>
|
|
282
|
+
`;
|
|
283
|
+
|
|
284
|
+
// CORRECT — handle the input event and write back to state
|
|
285
|
+
class MyComponent extends Symbiote {
|
|
286
|
+
init$ = { query: '' }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
MyComponent.template = html`
|
|
290
|
+
<input
|
|
291
|
+
${{value: 'query', oninput: 'onInput'}}
|
|
292
|
+
>
|
|
293
|
+
`;
|
|
294
|
+
```
|
|
295
|
+
```js
|
|
296
|
+
class MyComponent extends Symbiote {
|
|
297
|
+
init$ = {
|
|
298
|
+
query: '',
|
|
299
|
+
onInput: (e) => { this.$.query = e.target.value; },
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
This is intentional — explicit event handling keeps data flow predictable and avoids the hidden side effects that two-way binding can introduce.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 12. Treating `init$` as a plain object
|
|
247
309
|
|
|
248
310
|
`init$` is processed once at connection time to populate the component's reactive context. Mutating it after the fact has no effect. Use `this.$` or `add$()` for runtime changes.
|
|
249
311
|
|