@symbiotejs/symbiote 3.8.0-webmcp.2 → 3.8.1

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 CHANGED
@@ -1,24 +1,27 @@
1
1
  [![Tests](https://github.com/symbiotejs/symbiote.js/actions/workflows/tests.yml/badge.svg)](https://github.com/symbiotejs/symbiote.js/actions/workflows/tests.yml)
2
2
  [![npm version](https://img.shields.io/npm/v/@symbiotejs/symbiote)](https://www.npmjs.com/package/@symbiotejs/symbiote)
3
3
  [![npm downloads](https://img.shields.io/npm/dm/@symbiotejs/symbiote)](https://www.npmjs.com/package/@symbiotejs/symbiote)
4
- ![bundle size](https://img.shields.io/badge/brotli-7.3_kb-blue)
5
- ![types](https://img.shields.io/badge/types-JSDoc+d.ts-blue)
6
4
  ![license](https://img.shields.io/badge/license-MIT-green)
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. **~7.3kb** brotli / **~8.1kb** gzip.
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
- Symbiote.js gives you the convenience of a modern framework while staying close to the native platform - HTML, CSS, and DOM APIs. Components are real custom elements that work everywhere: in any framework, in plain HTML, or in a micro-frontend architecture. And with **isomorphic mode**, the same component code works on the server and the client - server-rendered pages hydrate automatically, no diffing, no mismatch errors.
12
+ Symbiote.js gives you the convenience of a modern framework while staying close to the native platform - HTML, CSS, and DOM APIs. Components are custom elements that work everywhere: in any framework, in plain HTML, in a micro-frontend architecture. And with **isomorphic mode**, the same component code works on the server and the client - server-rendered pages hydrate automatically, no diffing, no mismatch errors.
15
13
 
16
- ## What's new?
14
+ Here are the three most important differences between Symbiote.js and other frameworks:
15
+ 1. Natural DOM Extension Philosophy - designed to extend platform, not to replace it
16
+ 2. Runtime-Agnostic HTML Templates - outstanding flexibility for rendering strategies and further customization
17
+ 3. Powerful App-wide State Management - combine data contexts without bloated boilerplate or external tools
17
18
 
18
- - **Experimental 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
+ ## What's new in v3.x?
20
+
21
+ - **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
22
  - **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
23
  - **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.
24
+ - **Computed properties refined** - reactive derived state with microtask batching.
22
25
  - **Path-based router** - optional `AppRouter` module with `:param` extraction, route guards, and lazy loading.
23
26
  - **Exit animations** - `animateOut(el)` for CSS-driven exit transitions, integrated into itemize API.
24
27
  - **Dev mode** - `Symbiote.devMode` enables verbose warnings; import `devMessages.js` for full human-readable messages.
@@ -336,18 +339,7 @@ CSS values are parsed automatically - quoted strings become strings, numbers bec
336
339
  - **Reusable component libraries** - works in React, Vue, Angular, or plain HTML
337
340
  - **SSR-powered apps** - lightweight server rendering without framework lock-in
338
341
  - **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.
342
+ - **Modern AI-first web** - expose the application state to WebMCP tools automatically
351
343
 
352
344
  ## Browser support
353
345
 
@@ -359,7 +351,8 @@ All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
359
351
  - [Lit vs Symbiote.js](https://github.com/symbiotejs/symbiote.js/blob/main/docs/lit-vs-symbiote.md) - Side-by-side comparison
360
352
  - [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
361
353
  - [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
- - [AI Reference](https://github.com/symbiotejs/symbiote.js/blob/main/AI_REFERENCE.md)
354
+ - [AI / llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
355
+ - [Full docs (single file)](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
363
356
  - [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
364
357
 
365
358
  ## Related articles
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
- return docCtx;
251
+ _modelContext = docCtx;
252
+ return _modelContext;
250
253
  }
251
254
  let navCtx = /** @type {any} */ (globalThis.navigator)?.modelContext;
252
255
  if (navCtx?.registerTool) {
253
- return navCtx;
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.fn = options.execute;
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.fn !== 'function') {
621
+ if (typeof this.#fn !== 'function') {
615
622
  throw new Error('ToolDescriptor requires an execute function.');
616
623
  }
617
- return this.fn.call(owner, args || {}, owner, event);
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, false);
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
- /** @param {any} owner */
672
- export function syncWebMCPTools(owner) {
673
- if (!owner) return;
674
- if (isElementOwner(owner) && !owner.isConnected) {
675
- unregisterWebMCPTools(owner);
676
- return;
677
- }
678
- let version = bumpOwnerVersion(owner);
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 = makeEntry(publicName, item.key, item.descriptor, owner, item.executor);
704
+ let entry = makeEntryWithComponentDescription(publicName, item.key, item.descriptor, owner, item.executor, componentDescription);
707
705
  ownerEntry.toolsByKey[item.key] = publicName;
708
- if (entry instanceof Promise) {
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 ADDED
@@ -0,0 +1,62 @@
1
+ # Symbiote.js Documentation
2
+
3
+ > **Version 3.x** — A lightweight, standards-first UI library built on Web Components.
4
+ > Zero dependencies. ~7.3 KB brotli / ~8.1 KB gzip. No virtual DOM, no compiler, no build step required.
5
+
6
+ ## Getting Started
7
+
8
+ - [**Get Started**](./get-started.md) — Installation, first component, platform specs
9
+ - [**Ecosystem**](./ecosystem.md) — IDE setup, build tools, code sharing
10
+
11
+ ## Core Concepts
12
+
13
+ - [**Templates**](./templates.md) — HTML string templates, binding syntax, slots, element references
14
+ - [**Properties**](./properties.md) — State initialization, `$` proxy, subscriptions, computed properties
15
+ - [**Context**](./context.md) — Local, named, pop-up, and shared data contexts
16
+ - [**List Rendering**](./list-rendering.md) — Itemize API for dynamic lists
17
+ - [**Lifecycle**](./lifecycle.md) — Component lifecycle callbacks and destruction
18
+ - [**Flags**](./flags.md) — Component configuration flags
19
+ - [**Attributes**](./attributes.md) — HTML attribute binding and observation
20
+
21
+ ## State Management
22
+
23
+ - [**PubSub**](./pubsub.md) — Standalone state management, named contexts
24
+
25
+ ## Styling
26
+
27
+ - [**Styling**](./styling.md) — rootStyles, shadowStyles, CSS processing
28
+ - [**CSS Data**](./css-data.md) — CSS custom properties as reactive state
29
+
30
+ ## Features
31
+
32
+ - [**WebMCP Experimental**](./webmcp.md) — Expose live UI state and handlers as browser-native tools
33
+ - [**Routing**](./routing.md) — SPA routing with path patterns, guards, lazy loading
34
+ - [**SSR**](./ssr.md) — Server-side rendering, streaming, hydration
35
+ - [**SSR and Your Server Setup**](./ssr-server.md) — Static build, streaming, Express or Fastify
36
+ - [**Animations**](./animations.md) — CSS-driven exit transitions
37
+
38
+ ## Development
39
+
40
+ - [**Dev Mode**](./dev-mode.md) — Verbose warnings for debugging
41
+ - [**TypeScript**](./typescript.md) — JSDoc types, TypeScript usage, and hybrid template checks
42
+ - [**Security**](./security.md) — Trusted Types and CSP compliance
43
+
44
+ ## Migration
45
+
46
+ - [**2.x → 3.x Migration Guide**](./migration-2x-to-3x.md) — Breaking changes and upgrade path
47
+
48
+ ## Additional Resources
49
+
50
+ - [Lit vs Symbiote.js](./lit-vs-symbiote.md) — Side-by-side comparison
51
+ - [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
52
+ - [Common Mistakes](./common-mistakes.md) — Patterns that frequently cause bugs
53
+ - [Examples](./examples.md) — 26 complete working examples
54
+ - [llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
55
+ - [llms-full.txt](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
56
+ - [Changelog](../CHANGELOG.md)
57
+ - [npm](https://www.npmjs.com/package/@symbiotejs/symbiote)
58
+ - [GitHub Discussions](https://github.com/symbiotejs/symbiote.js/discussions)
59
+
60
+ ---
61
+
62
+ © [rnd-pro.com](https://rnd-pro.com) — MIT License
@@ -0,0 +1,61 @@
1
+ # Animations
2
+
3
+ Symbiote.js provides CSS-driven exit transitions with zero JS animation code.
4
+
5
+ ## `animateOut`
6
+
7
+ `animateOut(el)` sets the `[leaving]` attribute on an element, waits for CSS `transitionend`, then removes the element from the DOM. If no CSS transition is defined, removes immediately.
8
+
9
+ ```js
10
+ import { animateOut } from '@symbiotejs/symbiote';
11
+
12
+ // or as a static method:
13
+ Symbiote.animateOut(el);
14
+ ```
15
+
16
+ ## CSS pattern
17
+
18
+ Use `@starting-style` for enter animations and `[leaving]` for exit:
19
+ ```css
20
+ my-item {
21
+ opacity: 1;
22
+ transform: translateY(0);
23
+ transition: opacity 0.3s, transform 0.3s;
24
+
25
+ /* Enter (CSS-native, no JS needed): */
26
+ @starting-style {
27
+ opacity: 0;
28
+ transform: translateY(20px);
29
+ }
30
+
31
+ /* Exit (triggered by animateOut): */
32
+ &[leaving] {
33
+ opacity: 0;
34
+ transform: translateY(-10px);
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## Itemize integration
40
+
41
+ Both itemize processors use `animateOut` automatically for item removal. Items with CSS `transition` + `[leaving]` styles will animate out before being removed from the DOM:
42
+ ```css
43
+ user-card {
44
+ opacity: 1;
45
+ transition: opacity 0.3s;
46
+
47
+ @starting-style {
48
+ opacity: 0;
49
+ }
50
+
51
+ &[leaving] {
52
+ opacity: 0;
53
+ }
54
+ }
55
+ ```
56
+
57
+ No additional JavaScript is required — just define the CSS transitions and Symbiote handles the rest.
58
+
59
+ ---
60
+
61
+ Next: [CSS Data →](./css-data.md)
@@ -0,0 +1,85 @@
1
+ # Attributes
2
+
3
+ Like all regular DOM elements, Symbiote components can have their own HTML attributes. And like standard Custom Elements, they can react to dynamic attribute changes.
4
+
5
+ ## Attribute connection
6
+
7
+ The simplest way to connect a local state property to an attribute value is with the `@` token:
8
+ ```js
9
+ class MyComponent extends Symbiote {
10
+
11
+ init$ = {
12
+ '@attribute-name': 'initial value',
13
+ }
14
+
15
+ }
16
+ ```
17
+
18
+ Or initiate from the component's template directly:
19
+ ```js
20
+ class MyComponent extends Symbiote {}
21
+
22
+ MyComponent.template = html`<h1>{{@attribute-name}}</h1>`;
23
+ ```
24
+
25
+ Then use it as an attribute in markup:
26
+ ```html
27
+ <my-component attribute-name="attribute value"></my-component>
28
+ ```
29
+
30
+ ## Attribute change reaction
31
+
32
+ Using a property accessor:
33
+ ```js
34
+ class MyComponent extends Symbiote {
35
+
36
+ set 'my-attribute'(val) {
37
+ console.log(val);
38
+ }
39
+
40
+ }
41
+
42
+ MyComponent.observedAttributes = [
43
+ 'my-attribute',
44
+ ];
45
+ ```
46
+
47
+ ## `bindAttributes()` static method
48
+
49
+ Bind attributes to property values directly:
50
+ ```js
51
+ class MyComponent extends Symbiote {
52
+
53
+ init$ = {
54
+ myProp: '',
55
+ }
56
+
57
+ }
58
+
59
+ // Map the attribute name to corresponding property key:
60
+ MyComponent.bindAttributes({
61
+ 'my-attribute': 'myProp',
62
+ });
63
+
64
+ // observedAttributes is auto-populated
65
+
66
+ MyComponent.template = html`
67
+ <div>{{myProp}}</div>
68
+ `;
69
+ ```
70
+
71
+ ## Reserved attribute names
72
+
73
+ These attribute names are used internally by Symbiote.js:
74
+
75
+ - `bind`
76
+ - `ctx`
77
+ - `ref`
78
+ - `itemize`
79
+ - `item-tag`
80
+ - `use-template`
81
+ - `skip-text-nodes`
82
+
83
+ ---
84
+
85
+ Next: [PubSub →](./pubsub.md)