{"id":50620150,"url":"https://github.com/rnd-pro/symbiote-ui","last_synced_at":"2026-06-06T10:30:52.240Z","repository":{"id":362380343,"uuid":"1258714914","full_name":"rnd-pro/symbiote-ui","owner":"rnd-pro","description":"Symbiote Web Components, provider catalogs, layout contracts, and WebMCP metadata","archived":false,"fork":false,"pushed_at":"2026-06-04T02:00:26.000Z","size":1022,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-04T02:03:48.656Z","etag":null,"topics":["agentic-ui","custom-elements","design-tokens","javascript","npm-package","ssr","symbiote","ui-components","web-components","webmcp"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/symbiote-ui","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rnd-pro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-03T21:08:32.000Z","updated_at":"2026-06-04T02:00:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rnd-pro/symbiote-ui","commit_stats":null,"previous_names":["rnd-pro/symbiote-ui"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rnd-pro/symbiote-ui","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnd-pro%2Fsymbiote-ui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnd-pro%2Fsymbiote-ui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnd-pro%2Fsymbiote-ui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnd-pro%2Fsymbiote-ui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rnd-pro","download_url":"https://codeload.github.com/rnd-pro/symbiote-ui/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rnd-pro%2Fsymbiote-ui/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33979274,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-ui","custom-elements","design-tokens","javascript","npm-package","ssr","symbiote","ui-components","web-components","webmcp"],"created_at":"2026-06-06T10:30:51.766Z","updated_at":"2026-06-06T10:30:52.234Z","avatar_url":"https://github.com/rnd-pro.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# symbiote-ui\n\n`symbiote-ui` owns the browser-facing and agent-facing UI contracts for Symbiote provider systems.\n\nIt is built for agents that construct components, data views, and surrounding layouts dynamically. A chat agent can choose a component descriptor, bind data, compose a layout, and let the browser hydrate interactive Web Components without restarting the server.\n\nSee [Agent UI Construction Principles](./docs/agent-ui-principles.md) for the\nUX scenarios and workspace rules that guide agent-built interfaces.\n\n## Install\n\n```sh\nnpm install symbiote-ui @symbiotejs/symbiote@3.8.0-webmcp.2\n```\n\nFor SSR integration tests or JSDA-based hosts, install the integration dependencies in the host project:\n\n```sh\nnpm install jsda-kit linkedom\n```\n\n`jsda-kit` is intentionally not a runtime dependency of `symbiote-ui`.\n\n## Entry Points\n\n- `symbiote-ui` - Node-safe core primitives.\n- `symbiote-ui/core` - graph editor data primitives.\n- `symbiote-ui/layout` - SSR-safe layout helpers.\n- `symbiote-ui/graph` - provider graph normalization and projection helpers.\n- `symbiote-ui/manifest` - component, schema, rule, theme, and provider catalogs.\n- `symbiote-ui/runtime` - Node-safe agent UI construction helpers.\n- `symbiote-ui/ui` - browser Web Component registration and UI runtime.\n- `symbiote-ui/webmcp` - WebMCP descriptor helpers and registration utilities.\n- `symbiote-ui/xr` - WebXR provider helpers, spatial algorithms, 3D graph layout, and multi-view coordination.\n- `symbiote-ui/locale` - Node-safe locale catalogs and translation helpers.\n- `symbiote-ui/discover` - provider discovery JSON API used by the CLI.\n- `symbiote-ui/custom-elements.json` - Custom Elements manifest.\n- `symbiote-ui/schemas/*`, `symbiote-ui/tokens/*`, `symbiote-ui/rules/*` - machine-readable provider contracts.\n- `symbiote-ui/display/*` - reusable display utilities exposed by package export map.\n\nFor the complete export map and provider catalog, run:\n\n```sh\nsymbiote-ui discover\n```\n\nUse `symbiote-node` only as the terminal migration facade for older consumers.\n\n## Related Packages\n\n- [`symbiote-engine`](https://github.com/RND-PRO/symbiote-engine) - runtime execution, CLI commands, server helpers, persistence, and handlers.\n- [`symbiote-node`](https://github.com/RND-PRO/symbiote-node) - terminal migration facade for older imports.\n- [Package split guide](https://github.com/RND-PRO/symbiote-node/blob/main/docs/package-split.md)\n- [Agent contract index](https://github.com/RND-PRO/symbiote-node/blob/main/docs/agent-contracts.md)\n\n## Browser Registration\n\n```js\nimport { defineModule, listModules } from 'symbiote-ui/ui';\n\ndefineModule('chat-composer');\ndefineModule('cascade-theme-editor');\n\nconsole.log(listModules());\n```\n\nThe root package and Node-safe entry points must import without creating DOM globals. Import safety does not mean every exported helper is useful without host data, a DOM adapter, browser hydration, or runtime-provided objects. Browser-only custom elements and module definition helpers belong behind `symbiote-ui/ui`.\n\n## Agent UI Construction\n\nAgents should start from the public manifest, choose a component by its\nagent-facing description, then ask the host to bind data and insert the\ncomponent into an approved layout surface:\n\n```js\nimport { listAgentComponentDescriptions } from 'symbiote-ui/manifest';\n\nlet catalog = listAgentComponentDescriptions();\nlet graphSurface = catalog.find((item) =\u003e item.tagName === 'node-canvas');\nconsole.log(graphSurface.componentDescription);\nconsole.log(graphSurface.webmcp.toolNames);\n```\n\nBrowser hosts register custom elements through the public UI entrypoint:\n\n```js\nimport { defineModule } from 'symbiote-ui/ui';\n\ndefineModule('panel-layout');\ndefineModule('chat-sidebar-shell');\ndefineModule('chat-composer');\ndefineModule('graph-explorer-shell');\ndefineModule('node-canvas');\ndefineModule('graph-node');\n```\n\n`NodeCanvas` is the low-level constructor surface for node/edge previews.\nWhen a host uses it directly, it must also register `graph-node`; this keeps\nthe canvas reusable while node rendering stays in its own component contract.\nUse `symbiote-ui/core` for the host-owned editor model and `symbiote-ui/ui` for\nbrowser registration, not unexported package file paths.\n\n`canvas-graph` is not the primary node/edge constructor surface. Use it when an\nagent needs a read/overview graph renderer with semantic clusters, focus,\nselection, and layout snapshots. Use `node-canvas` when the agent is actively\nconstructing editable nodes, sockets, edges, frames, or document-flow previews.\nBoth components keep graph data host-owned and emit intent events; hosts own\nnavigation, persistence, and permission policy.\n\n```js\nimport { Node, NodeEditor, Output, Socket } from 'symbiote-ui/core';\nimport { defineModule } from 'symbiote-ui/ui';\n\ndefineModule('node-canvas');\ndefineModule('graph-node');\n\nlet editor = new NodeEditor();\nlet socket = new Socket('flow');\nlet node = new Node('Generated view', { id: 'generated-view', type: 'agent' });\nnode.addOutput('next', new Output(socket, 'next'));\neditor.addNode(node);\n\nlet canvas = document.querySelector('node-canvas');\ncanvas.setEditor(editor);\ncanvas.setPathStyle('pcb');\ncanvas.setFlowLayout({ nodeIds: [node.id], direction: 'vertical', scroll: true });\n```\n\n`panel-layout` owns reusable split and panel behavior only. Product routes,\ntransport, persistence, and permission checks remain host policy:\n\n```js\nlayout.registerPanelType('chat', {\n  title: 'Chat',\n  icon: 'forum',\n  component: 'chat-composer',\n});\nlayout.openPanel('chat', {\n  direction: 'horizontal',\n  ratio: 0.42,\n  uiInvoked: true,\n  source: 'agent-constructor',\n});\n```\n\nTheme updates should mutate the cascade target once and let components inherit\ntokens:\n\n```js\nimport { applyCascadeTheme, createCascadeTheme } from 'symbiote-ui';\n\nlet theme = createCascadeTheme({ mode: 'dark', brightness: 8, contrast: 64 });\napplyCascadeTheme(document.documentElement, theme.state);\n```\n\nFor chat construction, `chat-sidebar-shell` owns sidebar presentation and emits\nselection/collapse/width intents. `chat-composer` owns composer presentation,\nfooter controls, and voice-control intents. The host owns actual chat\ntransport, model/provider policy, speech recognition, and storage.\nUse `setFooterControls()` for structured provider/model/agent/resource/settings\ncontrols; `chat-composer-footer-control` and\n`chat-composer-footer-control-change` report product-neutral intents back to the\nhost. Voice controls emit `chat-composer-permission-intent`,\n`chat-composer-recorder-intent`, and `chat-composer-transcription-intent` so\nhosts can own microphone permission, recorder lifecycle, and transcription\nproviders without those policies leaking into the component. `setFooterHtml()`\nremains available only for trusted host-rendered footer markup.\n`extractChatTitleFromAgentText()` provides a product-neutral parser for\nstandalone `\u003cchat-title\u003e...\u003c/chat-title\u003e` responses; any prompt instruction that\nasks an agent to produce that tag remains host policy.\n\nFor live agent construction, `symbiote-ui/runtime` provides the Node-safe\n`runtime-ui-v1` adapter:\n\n```js\nimport {\n  applyRuntimeLayoutAction,\n  createRuntimeUiController,\n} from 'symbiote-ui/runtime';\n\nlet controller = createRuntimeUiController({\n  document,\n  allowedMethods: ['setData'],\n  onIntent(intent) {\n    hostActions.dispatch(intent.action, intent.detail);\n  },\n});\n\nlet panel = controller.create({\n  id: 'agent-kpi-panel',\n  component: 'sn-data-table',\n  state: {\n    methods: {\n      setData: [{\n        columns: [{ key: 'metric', label: 'Metric' }],\n        rows: [{ metric: 'Latency' }],\n      }],\n    },\n  },\n  events: {\n    'row-open': 'metrics.open',\n  },\n});\n\ntarget.append(panel.element);\ncontroller.update('agent-kpi-panel', {\n  props: { density: 'compact' },\n  attrs: { 'data-live': true },\n});\n\napplyRuntimeLayoutAction(layout, {\n  type: 'open-panel',\n  panelType: 'agent-kpi-panel',\n  options: { uiInvoked: true, source: 'agent-constructor' },\n});\n```\n\nThe runtime adapter never stores layouts, chat history, theme presets, or\nautomation results. Hosts persist the `runtime-ui-v1` tree, constructed\ncomponent state, intent mappings, and layout snapshots in their own project\nstore. `destroy()` tears down listeners and removes dynamic elements when the\nhost closes or removes UI-invoked panels. Hosts should pass `allowedMethods` or\n`allowMethod()` when applying agent-authored `state.methods`; use the component\nregistry or a project policy allowlist to approve method calls.\n\n## Cascade Theme\n\n`symbiote-ui` exposes a reusable cascade theme contract for agent-built UI, graph canvases, layouts, scrollbars, and VR-ready panels:\n\n```js\nimport { applyCascadeTheme, createCascadeTheme } from 'symbiote-ui';\n\nlet theme = createCascadeTheme({\n  mode: 'dark',\n  brightness: 0,\n  contrast: 58,\n  chroma: 89,\n  hue: 218,\n  outline: 38,\n  type: 100,\n  heading: 100,\n  density: 100,\n  motion: 100,\n});\n\napplyCascadeTheme(document.documentElement, theme.state);\n```\n\nApply the cascade once at `:root`, an app shell, or a subtree boundary. Components inherit `--sn-*` tokens; host projects should not duplicate the formulas in app-local CSS or JS.\n\nThe contract writes both low-level controls such as `--sn-theme-bg-lightness`,\n`--sn-theme-outline-strength`, `--sn-theme-type-scale`, and\n`--sn-theme-heading-scale`, `--sn-theme-density`, and\n`--sn-theme-motion-scale`, and public component aliases such as `--sn-bg`,\n`--sn-text`, `--sn-node-bg`, `--sn-panel-bg`, `--sn-ctx-bg`,\n`--sn-button-bg`, and `--sn-field-control-bg`. Motion also exposes\n`--sn-motion-enabled`, `--sn-animation-play-state`,\n`--sn-animation-duration-scale`, and `--sn-transition-easing`; disabled\nmotion sets transition durations to zero and pauses cascade-driven animations.\n\n`createCascadeTheme()` also derives readable foreground tokens for colored\ncontrols. The same Node-safe formula is exposed as `getReadableTextForHsl()`;\nagents should use it when they construct custom accent surfaces instead of\nhard-coding light or dark button text. Tab and content-group accents rotate\nthrough `--sn-tab-accent-0` ... `--sn-tab-accent-5`, which lets hosts separate\nlayout groups while still inheriting the same root cascade.\n\nRuntime hosts can set `data-engine-state=\"idle\"`, `\"running\"`, `\"success\"`,\nor `\"error\"` on a reusable surface. The default provider maps those states to\n`--sn-engine-state-color`, `--sn-engine-state-bg`, and\n`--sn-engine-state-border`; running state animation uses\n`--sn-animation-play-state` so reduced or disabled motion pauses it without\ncomponent-local JavaScript.\n\nPreset composition is available through `ThemeFactory` exports. Agents can use\n`resolveThemePresetsForTask('chat' | 'editor' | 'monitor' | 'terminal')` for\ntask defaults, then call `applyThemePresets(element, { color, skin, motion })`.\nThe motion preset comes from the same `Motion.js` definitions as direct\n`applyMotion()` usage.\n\nBrowser hosts can mount the reusable editor module inside a layout panel:\n\n```html\n\u003ccascade-theme-editor\n  storage-key=\"my-app:cascade-theme\"\n  target-selector=\"#app-shell\"\n\u003e\u003c/cascade-theme-editor\u003e\n```\n\nThe editor reuses the same bounded cascade controls, auto-saves normalized\nparameters to `localStorage`, can reset to defaults, copies the current\nparameter JSON, and emits `cascade-theme-change` after applying tokens. The\nlayout owns where the module is shown; `panel-layout` can register it as a\npanel type while keeping the panel menu closed by default.\nTemporary UI-invoked panels use the built-in `Close` action and\n`closeUiPanel()` contract; close marks the panel closed/collapsed so the layout\nsurface remains recoverable. `removeUiPanel()` is the destructive operation that\nphysically removes a temporary panel and may restore the captured host layout\nwhen the last temporary panel is removed. Persistent host layout panels use\n`Remove` when the host deliberately edits the split tree.\n\n## Layout Behavior\n\n`symbiote-ui/layout` exposes SSR-safe layout behavior helpers for hosts and\nagents that compose dynamic workspaces:\n\n```js\nimport {\n  LayoutTree,\n  resolveLayoutMinSize,\n  resolveResponsiveLayoutState,\n} from 'symbiote-ui/layout';\n\nlet root = LayoutTree.createSplit(\n  'horizontal',\n  LayoutTree.createPanel('graph', {}, { importance: 90, minInlineSize: 420 }),\n  LayoutTree.createPanel('chat', {}, { importance: 40, minInlineSize: 320 }),\n  0.58\n);\n\nlet minSize = resolveLayoutMinSize(root);\nlet state = resolveResponsiveLayoutState(\n  { collapse: 'auto', overflow: 'scroll-inline', responsiveMode: 'scroll-inline' },\n  { inlineSize: 520, blockSize: 420, layoutMinSize: minSize }\n);\n```\n\n`panel-layout` uses the same contract at runtime. Root `layoutBehavior`\nis host-applied responsive policy and is not persisted into the saved layout\ntree; per-panel or per-branch `behavior` belongs on layout nodes and is\npersisted with the tree. `importance` decides auto-collapse order, minimum\ninline/block sizes decide when panels no longer fit, `collapse` controls\nwhether a panel may auto-collapse, `overflow` selects collapse versus\nhorizontal/vertical scroll fallback, and `responsiveMode` selects mobile\npreserve, vertical stack, or horizontal scroll behavior. Minimum footprint\nresolution accounts for split ratios, so a skewed split still reserves enough\nscrollable space for both child branches.\nAt runtime `panel-layout` exposes resolved scroll axes through\n`scroll-inline-active` and `scroll-block-active` attributes so CSS, browser\nsmoke tests, and agents can distinguish requested policy from active fallback.\n`layout-sidebar` owns only its sidebar configuration and width persistence; its\nreset control clears that state and emits `layout-sidebar-reset` for host-owned\nlayout resets instead of clearing host storage or reloading the page.\n\n## Spatial Algorithms\n\n`symbiote-ui/xr` includes a set of dependency-free spatial primitives for 3D\ngraph visualization, force-directed layout, spatial indexing, and multi-view\ncoordination. All algorithmic cores are pure JavaScript, renderer-neutral, and\nNode-safe. Three.js and WebXR adapters accept runtime dependencies through\ninjection, never as hard imports.\n\n### Octree / Spatial Index\n\nHigh-performance 3D octree for spatial queries and Barnes-Hut force\napproximation:\n\n```js\nimport { createOctree } from 'symbiote-ui/xr/spatial-index';\n\nlet tree = createOctree();\ntree.insertAll([\n  { x: 1, y: 2, z: 3 },\n  { x: 4, y: 5, z: 6 },\n  { x: 0, y: 0, z: 0 },\n]);\n\nlet nearest = tree.nearest(1, 1, 1);\nlet box = tree.queryBox(0, 0, 0, 3, 3, 3);\n\ntree.visit((node, x0, y0, z0, x1, y1, z1) =\u003e {\n  // depth-first traversal of octants\n});\n```\n\n### Spatial Graph Model\n\nPure functions to create and query a renderer-agnostic 3D graph model\nconforming to the `spatial-graph-v1` contract:\n\n```js\nimport {\n  createSpatialGraphModel,\n  updateSpatialNodePosition,\n  selectSpatialNode,\n  focusSpatialNode,\n  pinSpatialNode,\n  unpinSpatialNode,\n} from 'symbiote-ui/xr/spatial-graph';\n\nlet model = createSpatialGraphModel({\n  nodes: [\n    { id: 'a', label: 'Module A', type: 'module', position: [0, 0, 0] },\n    { id: 'b', label: 'Module B', type: 'module' },\n  ],\n  links: [\n    { source: 'a', target: 'b', type: 'dependency' },\n  ],\n});\n\nmodel = updateSpatialNodePosition(model, 'a', [1, 2, 3]);\nmodel = selectSpatialNode(model, 'a');\nmodel = focusSpatialNode(model, 'b');\nmodel = pinSpatialNode(model, 'a', [1, 2, 3]);\nmodel = unpinSpatialNode(model, 'a');\n```\n\nAll functions are pure and return new model objects. Host metadata is preserved\nin `node.metadata` without product-specific fields leaking into the contract.\n\n### Force-Directed Layout\n\n3D force simulation with Barnes-Hut many-body repulsion via the built-in\noctree. No external dependencies:\n\n```js\nimport {\n  createSimulation,\n  forceCenter3D,\n  forceLink3D,\n  forceManyBody3D,\n  forceCluster3D,\n} from 'symbiote-ui/xr/force-layout';\n\nlet nodes = [\n  { id: 'a', x: 0, y: 0, z: 0, category: 'core' },\n  { id: 'b', x: 1, y: 0, z: 0, category: 'core' },\n  { id: 'c', x: 0, y: 1, z: 0, category: 'util' },\n];\n\nlet links = [\n  { source: 'a', target: 'b' },\n  { source: 'b', target: 'c' },\n];\n\nlet sim = createSimulation(nodes);\nsim.force('center', forceCenter3D(0, 0, 0));\nsim.force('charge', forceManyBody3D().strength(-3.5));\nsim.force('link', forceLink3D(links));\nsim.force('cluster', forceCluster3D().strength(0.15));\n\nfor (let i = 0; i \u003c 300; i++) sim.tick();\n// nodes[0].x, .y, .z now contain settled positions\n```\n\nThe `createForceLayoutAdapter` bridge connects a `NodeEditor` to the 3D\nsimulation:\n\n```js\nimport { createForceLayoutAdapter } from 'symbiote-ui/xr/force-layout-adapter';\n\nlet adapter = createForceLayoutAdapter(editor, {\n  strength: -4,\n  distance: 1.5,\n  useCluster: true,\n});\n\nadapter.tick(); // one simulation step, syncs coordinates back to editor nodes\n```\n\n### Spherical Layout\n\nDeterministic 3D layout using Fibonacci spiral distribution. Units are meters\nfor XR compatibility:\n\n```js\nimport { createSphericalGraphLayout } from 'symbiote-ui/xr/spherical-layout';\n\nlet layout = createSphericalGraphLayout(\n  [\n    { id: 'a', type: 'module' },\n    { id: 'b', type: 'module' },\n    { id: 'c', type: 'util' },\n  ],\n  [{ source: 'a', target: 'b' }],\n  {\n    mode: 'clustered-shell', // 'sphere' | 'shell' | 'clustered-shell'\n    radius: 1.6,\n    center: [0, 1.55, 0],\n    category: (d) =\u003e d.type,\n  }\n);\n\n// layout.nodes[i].position → [x, y, z]\n// layout.bounds → { min: [x,y,z], max: [x,y,z] }\n```\n\nFixed nodes are preserved in place. Output is fully deterministic for stable\nagent-authored layouts.\n\n### Spatial Drag Controller\n\nPure ray-sphere intersection math and pointer drag projection. No DOM, Three.js,\nor WebXR dependencies:\n\n```js\nimport {\n  intersectRaySphere,\n  hitTestSpatialNode,\n  projectPointerToDragPlane,\n  createSpatialDragController,\n} from 'symbiote-ui/xr/spatial-drag-controller';\n\n// Ray-sphere hit test\nlet dist = intersectRaySphere(\n  [0, 0, 5],     // ray origin\n  [0, 0, -1],    // ray direction\n  [0, 0, 0],     // sphere center\n  0.08            // sphere radius\n);\n\n// Hit test against spatial graph nodes\nlet hit = hitTestSpatialNode(model.nodes, {\n  origin: [0, 0, 5],\n  direction: [0, 0, -1],\n});\n\n// Full drag controller\nlet drag = createSpatialDragController();\nlet startEvent = drag.startDrag(hit.node, pointer);\nlet moveEvent = drag.moveDrag(pointer);\nlet endEvent = drag.endDrag();\n// Events follow the spatial-node-drag contract: { type, nodeId, phase, position }\n```\n\n### Panel Auto-Tiling\n\nDistributes workspace panels in 3D space with arc, grid, and sphere layouts:\n\n```js\nimport { autoTileXRPanels } from 'symbiote-ui/xr';\n\nlet poses = autoTileXRPanels(\n  [\n    { id: 'chat', width: 0.6, height: 0.8 },\n    { id: 'graph', width: 0.8, height: 0.6 },\n    { id: 'inspector', width: 0.4, height: 0.6 },\n  ],\n  { layout: 'arc', comfortDistance: 1.2 }\n);\n\n// poses[i] → { id, position, rotation, ... }\n```\n\n### Three.js Adapter\n\nOptional renderer adapter. The host supplies the `THREE` instance to avoid\nmaking Three.js a mandatory dependency:\n\n```js\nimport { createThreeSpatialGraph } from 'symbiote-ui/xr/three-spatial-graph';\n\nlet graph3D = createThreeSpatialGraph(THREE, model);\nscene.add(graph3D.group);\n\n// On model change:\ngraph3D.setModel(updatedModel);\n\n// Cleanup:\ngraph3D.destroy();\n```\n\nRenders spherical nodes, link lines, and selection/focus color states.\nMeshes are created, updated, and disposed automatically.\n\n### Dual View Controller\n\nCoordinates state between 2D canvas, 3D desktop preview, and XR immersive\nmodes:\n\n```js\nimport { createDualViewController } from 'symbiote-ui/xr/dual-view-controller';\n\nlet dualView = createDualViewController({ initialMode: '2d' });\n\nlet unsubscribe = dualView.subscribe((state) =\u003e {\n  console.log(state.mode, state.activeNodeId, state.focusedNodeId);\n});\n\ndualView.enter3DPreview();\ndualView.focusNode('node-a');\ndualView.selectNode('node-b');\ndualView.updateNodePosition('node-a', [1, 2, 3]);\n\n// Cleanup\ndualView.destroy();\n```\n\nState is serializable. The controller does not own product routing, project\nloading, or engine execution.\n\n### Standalone Subpath Imports\n\nEach spatial module is available as a standalone subpath export:\n\n| Subpath | Module |\n| --- | --- |\n| `symbiote-ui/xr/spatial-index` | Octree spatial index |\n| `symbiote-ui/xr/spatial-graph` | 3D graph model (spatial-graph-v1) |\n| `symbiote-ui/xr/force-layout` | Force-directed 3D simulation |\n| `symbiote-ui/xr/force-layout-adapter` | NodeEditor ↔ force-layout bridge |\n| `symbiote-ui/xr/spherical-layout` | Fibonacci spherical layout |\n| `symbiote-ui/xr/spatial-drag-controller` | Ray/sphere drag math |\n| `symbiote-ui/xr/dual-view-controller` | 2D/3D/XR state bridge |\n| `symbiote-ui/xr/three-spatial-graph` | Optional Three.js renderer adapter |\n\nAll modules are also re-exported from the barrel `symbiote-ui/xr`.\n\n## Demos\n\n- [`demo/cascade-theme-lab.html`](./demo/cascade-theme-lab.html) - agent workspace showcase with project-type top tabs, per-tab sidebar menus, a right collapsed page-agent chat layout panel, project file tree, source editor, markdown/source viewer, editable node canvas, canvas graph overview, chat and voice controls, runtime UI contracts, spatial node preview, component surfaces, and cascade theme editing.\n- [`demo/pcb-router-stress.html`](./demo/pcb-router-stress.html) - animated PCB route diagnostics with orbit metrics, keyframes, and agent-readable JSON samples.\n\n## WebMCP\n\nComponent metadata uses `component-descriptor-v2` with bounded agent-facing contracts:\n\n- `componentDescription` gives agents a stable explanation of what the component represents in the current UI contract.\n- `agent.semanticRole`, `agent.usage`, `agent.dataOwnership`, and `agent.webmcp` describe when to use a component, who owns its data, and which explicit tools exist.\n- `contract.ssr.mode` classifies SSR safety as `node-safe`, `ssr-entry-safe`, `jsda-ssr-renderable`, `hydrate-only`, or `client-only`.\n- `contract.webmcp.tools[]` documents explicit tool descriptors with `name`, `description`, `inputSchema`, `annotations`, visibility, and permission hints.\n- WebMCP exposure is explicit-first. The package does not enable global `Symbiote.mcpToolMode` by default.\n\nUse `listAgentComponentDescriptions()` from `symbiote-ui/manifest` or\n`symbiote-ui discover` when an agent needs to understand which component is\nwhich before composing a layout or choosing a tool.\n\n`symbiote-ui/webmcp` also exposes `createComponentToolDescriptor(component,\ntool)`, which appends the component context to explicit tool descriptions while\npreserving the typed `ToolDescriptor` shape.\n\nThe RND-PRO overview of Symbiote WebMCP support is:\n\nhttps://rnd-pro.com/pulse/symbiote-webmcp-support/\n\nThe upstream Symbiote WebMCP reference is:\n\nhttps://github.com/symbiotejs/symbiote.js/blob/webmcp/docs/webmcp.md\n\n## JSDA SSR\n\nHosts that use JSDA SSR should provide their own SSR runtime and DOM adapter:\n\n```js\nimport { parseHTML } from 'linkedom';\nimport 'symbiote-ui/ui';\n```\n\n`jsda-ssr-renderable` components are expected to import safely in a JSDA SSR fixture with `ssr.enabled`, `ssr.imports`, `linkedom`, and Web Component SSR enabled by the host.\n\n## Package Boundary\n\n`symbiote-ui` owns Web Components, UI/layout primitives, manifests, schemas, rules, tokens, themes, locale helpers, provider-facing graph metadata, WebMCP metadata, and `custom-elements.json`.\n\nRuntime workflow execution, persistence, server commands, handler packs, and process lifecycle belong in `symbiote-engine`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frnd-pro%2Fsymbiote-ui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frnd-pro%2Fsymbiote-ui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frnd-pro%2Fsymbiote-ui/lists"}