# Symbiote.js — AI Context Reference (v3.x)

> **Purpose**: Authoritative reference for AI code assistants. All information is derived from source code analysis of [symbiote.js](https://github.com/symbiotejs/symbiote.js).
> Current version: **3.4.7**. Zero dependencies. ~6.4 KB brotli / ~7.1 KB gzip.

---

## Installation & Import

```js
// NPM
import Symbiote, { html, css, PubSub, DICT } from '@symbiotejs/symbiote';

// CDN / HTTPS
import Symbiote, { html, css } from 'https://esm.run/@symbiotejs/symbiote';

// Individual module imports (tree-shaking)
import Symbiote from '@symbiotejs/symbiote/core/Symbiote.js';
import { PubSub } from '@symbiotejs/symbiote/core/PubSub.js';
import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js';
import { html } from '@symbiotejs/symbiote/core/html.js';
import { css } from '@symbiotejs/symbiote/core/css.js';
```

### Core exports (index.js)
`Symbiote` (default), `html`, `css`, `PubSub`, `DICT`, `animateOut`

### Utils exports (`@symbiotejs/symbiote/utils`)
`UID`, `setNestedProp`, `applyStyles`, `applyAttributes`, `create`, `kebabToCamel`, `reassignDictionary`

---

## Component Basics

Symbiote extends `HTMLElement`. Every component is a Custom Element.

```js
class MyComponent extends Symbiote {
  // Class properties as initial values (fallback)
  name = 'World';
  count = 0;

  onBtnClick() {
    this.$.count++;
  }
}

// Template — assigned via static property SETTER, outside the class body
MyComponent.template = html`
  <h1>Hello {{name}}!</h1>
  <p>Count: {{count}}</p>
  <button ${{onclick: 'onBtnClick'}}>Increment</button>
`;

// Register Custom Element tag
MyComponent.reg('my-component');
```

> **CRITICAL**: `template` is a **static property setter** on the `Symbiote` class, not a regular static class field.
> You **MUST** assign it **outside** the class body: `MyComponent.template = html\`...\``.
> Using `static template = html\`...\`` inside the class declaration **will NOT work**.
> Templates are plain HTML strings, NOT JSX. Use the `html` tagged template literal.

### Lifecycle callbacks (override in subclass)
| Method | When called |
|--------|------------|
| `initCallback()` | Once, after state initialized, before render (if `pauseRender=true`) or normally after render |
| `renderCallback()` | Once, after template is rendered and attached to DOM |
| `destroyCallback()` | On disconnect, after 100ms delay, only if `readyToDestroy=true` |

### Usage in HTML
```html
<my-component></my-component>
```

---

## Template Binding Syntax

Use the `html` tagged template literal for ergonomic binding syntax. It supports **two interpolation modes**:

- **`Object`** → converted to `bind="prop:key;"` attribute (reactive binding)
- **`string` / `number`** → concatenated as-is (native interpolation, useful for SSR page shells)

This dual-mode design means `html` works for both component templates and full-page SSR output — no separate "server-only template" function is needed.

### Text node binding
```html
<div>{{propName}}</div>
```
Binds `propName` from component state to the text content of a text node. Works inside any element. Multiple bindings in one text node are supported: `{{first}} - {{second}}`.

### Property binding (element's own properties)
```html
<button ${{onclick: 'handlerName'}}>Click</button>
<div>{{myProp}}</div>
```
The `${{key: 'value'}}` interpolation creates a `bind="key:value;"` attribute. Keys are DOM element property names. Values are component state property names (strings).

**Class property fallback (3.x):** For any binding key not found in `init$`, Symbiote checks own instance properties first (`Object.hasOwn` — safe from inherited `HTMLElement` collisions), then prototype methods. Functions are automatically `.bind()`-ed to the component instance:
```js
class MyComp extends Symbiote {
  // Approach 1: state property in init$
  init$ = { count: 0 };

  // Approach 2: class property / method (fallback)
  label = 'Click me';
  onSubmit() { console.log('submitted'); }
}
```

> **Recommendation:** Use class property fallback for **simple components** — keeps code compact. For **complex components** with many reactive properties, prefer `init$` to explicitly separate reactive state from regular class properties.

### Nested property binding
```html
<div ${{'style.color': 'colorProp'}}>Text</div>
```
Dot notation navigates nested properties on the element.

### Direct child component state binding
```html
<child-component ${{'$.innerProp': 'parentProp'}}></child-component>
```
The `$.` prefix accesses the child component's `$` state proxy directly.

### Attribute binding (`@` prefix)
```html
<div ${{'@hidden': 'isHidden'}}>Content</div>
<input ${{'@disabled': 'isDisabled'}}>
<div ${{'@data-value': 'myValue'}}></div>
```
The `@` prefix means "bind to HTML attribute" (not DOM property). For boolean attributes: `true` → attribute present, `false` → attribute removed. `@` is for binding syntax only, do NOT use it as a regular HTML attribute prefix.

### Type casting (`!` / `!!`)
```html
<div ${{'@hidden': '!showContent'}}>...</div>    <!-- inverted boolean -->
<div ${{'@contenteditable': '!!hasText'}}>...</div> <!-- double inversion = cast to boolean -->
```

### Loose-coupling alternative (plain HTML, no JS context needed)
```html
<div bind="textContent: myProp"></div>
<div bind="onclick: handler; @hidden: !flag"></div>
```
This is the raw form. The `html` helper generates it automatically.

---

## Property Token Prefixes

Prefixes control which data context a binding resolves to:

| Prefix | Meaning | Example | Description |
|--------|---------|---------|-------------|
| _(none)_ | Local state | `{{count}}` | Current component's local context |
| `^` | Pop-up | `{{^parentProp}}` | Walk up DOM ancestry to find nearest component that has this prop in its data context (`init$` / `add$()`) |
| `*` | Shared context | `{{*sharedProp}}` | Shared context scoped by `ctx` attribute or CSS `--ctx` |
| `/` | Named context | `{{APP/myProp}}` | Global named context identified by key before `/` |
| `--` | CSS Data | `{{--my-css-var}}` | Read value from CSS custom property |
| `+` | Computed | (in init$) `'+sum': () => ...` | Function recalculated when local dependencies change (auto-tracked) |

### Examples in init$
```js
init$ = {
  localProp: 'hello',           // local (prefer class properties for simple cases)
  '*sharedProp': 'shared value', // shared context (requires init$)
  'APP/globalProp': 42,          // named context (requires init$)
  '+computed': () => this.$.a + this.$.b, // local computed (requires init$)
};
```

### Computed properties (v3.x)

Computed props use the `+` prefix and are auto-tracked: dependencies are recorded when the function executes.

**Local computed** — reacts to local state changes automatically:
```js
init$ = {
  a: 1,
  b: 2,
  '+sum': () => this.$.a + this.$.b, // auto-tracks 'a' and 'b'
};
```

**Cross-context computed** — reacts to external named context changes via explicit deps:
```js
init$ = {
  local: 0,
  '+total': {
    deps: ['GAME/score'],
    fn: () => this.$['GAME/score'] + this.$.local,
  },
};
```

> **NOTE**: Computed values are recalculated asynchronously (via `queueMicrotask`), so subscribers are notified in the next microtask, not inline during `pub()`.
```

---

## State Management API

### `$` proxy (read/write state)
```js
this.$.count = 10;          // publish
let val = this.$.count;     // read
this.$['APP/prop'] = 'x';   // named context
this.$['^parentProp'] = 5;  // parent context
```

### `set$(kvObj, forcePrimitives?)` — bulk update
```js
this.set$({ name: 'Jane', count: 5 });
// forcePrimitives=true → triggers callbacks even if value unchanged (for primitives)
```

### `sub(prop, handler, init?)` — subscribe to changes
```js
this.sub('count', (val) => {
  console.log('count changed:', val);
});
// init defaults to true (handler called immediately with current value)
```

### `add(prop, val, rewrite?)` — add property to context
### `add$(obj, rewrite?)` — bulk add

### `has(prop)` — check if property exists in context
### `notify(prop)` — force notification to all subscribers

> **WARNING**: Property keys with nested dots (`prop.sub`) are NOT supported as state keys.
> Use flat names: `propSub` instead of `prop.sub`.

---

## PubSub (Standalone State Management)

```js
import { PubSub } from '@symbiotejs/symbiote';

// Register a named global context
const ctx = PubSub.registerCtx({
  userName: 'Anonymous',
  score: 0,
}, 'GAME'); // 'GAME' is the context key

// Read/write from any component
this.$['GAME/userName'] = 'Player 1';
console.log(this.$['GAME/score']);

// Subscribe from any component
this.sub('GAME/score', (val) => {
  console.log('Score:', val);
});

// Direct PubSub API
ctx.pub('score', 100);
ctx.read('score');
ctx.sub('score', callback);
ctx.multiPub({ score: 100, userName: 'Hero' });
```

### PubSub static methods
- `PubSub.registerCtx(schema, uid?)` → `PubSub` instance
- `PubSub.getCtx(uid, notify?)` → `PubSub` instance or null
- `PubSub.deleteCtx(uid)`

---

## Shared Context (`*` prefix)

Components grouped by the `ctx` HTML attribute (or `--ctx` CSS custom property) share a data context. Properties with `*` prefix are read/written in this shared context — inspired by native HTML `name` attribute grouping (like radio button groups):

```html
<upload-btn ctx="gallery"></upload-btn>
<file-list  ctx="gallery"></file-list>
```

```js
class UploadBtn extends Symbiote {
  init$ = { '*files': [] }

  onUpload() {
    this.$['*files'] = [...this.$['*files'], newFile];
  }
}

class FileList extends Symbiote {
  init$ = { '*files': [] }  // same shared prop — first-registered value wins
}
```

Both components access the same `*files` state — no parent component, no prop drilling, no global store. Just set `ctx="gallery"` in HTML and use `*`-prefixed properties.

### Context name resolution (first match wins)
1. `ctx="name"` HTML attribute
2. `--ctx` CSS custom property (inherited from ancestors)
3. No match → `*` props are **silently skipped** (dev mode warns)

> **WARNING**: `*` properties require an explicit `ctx` attribute or `--ctx` CSS variable. Without one, the shared context is not created and `*` props have no effect.

---

## Lifecycle & Instance Properties

### Lifecycle callbacks (override in subclass)
| Method | When called |
|--------|------------|
| `initCallback()` | Once, after state initialized, before render (if `pauseRender=true`) or normally after render |
| `renderCallback()` | Once, after template is rendered and attached to DOM |
| `destroyCallback()` | On disconnect, after 100ms delay, only if `readyToDestroy=true` |

### Constructor flags (set in constructor or as class fields)
| Property | Default | Description |
|----------|---------|-------------|
| `pauseRender` | `false` | Skip automatic rendering; call `this.render()` manually later |
| `renderShadow` | `false` | Render template into Shadow DOM |
| `readyToDestroy` | `true` | Allow cleanup on disconnect |
| `processInnerHtml` | `false` | Process existing inner HTML with template processors |
| `ssrMode` | `false` | **Client-only.** Hydrate server-rendered HTML: skips template injection, attaches bindings to existing DOM. Supports both light DOM and Declarative Shadow DOM. Ignored when `__SYMBIOTE_SSR` is active (server side) |
| `isoMode` | `false` | **Client-only.** Isomorphic mode: if component has children at connect time, behaves as `ssrMode = true` (hydrate). If no children, renders template normally. Same component works for both SSR and client-only scenarios |
| `allowCustomTemplate` | `false` | Allow `use-template="#selector"` attribute |
| `isVirtual` | `false` | Replace element with its template fragment |
| `allowTemplateInits` | `true` | Auto-add props found in template but not in init$ |

### Instance properties (available after render)
- `this.ref` — object map of `ref`-attributed elements
- `this.initChildren` — array of original child nodes (before template render)
- `this.$` — state proxy
- `this.allSubs` — Set of all subscriptions (for cleanup)
---

## Exit Animation (`animateOut`)

`animateOut(el)` sets `[leaving]` attribute, waits for CSS `transitionend`, then removes the element. If no CSS transition is defined, removes immediately. Available as standalone import or `Symbiote.animateOut`.

```js
import { animateOut } from '@symbiotejs/symbiote';
// or: Symbiote.animateOut(el)
```

### CSS pattern

```css
my-item {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.3s, transform 0.3s;

  /* Enter (CSS-native, no JS needed): */
  @starting-style {
    opacity: 0;
    transform: translateY(20px);
  }

  /* Exit (triggered by animateOut): */
  &[leaving] {
    opacity: 0;
    transform: translateY(-10px);
  }
}
```

### Itemize integration

Both itemize processors use `animateOut` automatically for item removal. Items with CSS `transition` + `[leaving]` styles will animate out before being removed from the DOM.

---

## Styling

### rootStyles (Light DOM, adopted stylesheets)
```js
MyComponent.rootStyles = css`
  my-component {
    display: block;
    color: var(--text-color);
  }
`;
```
Styles are added to the closest document root via `adoptedStyleSheets`. Use the custom tag name as selector.

### shadowStyles (Shadow DOM, auto-creates shadow root)
```js
MyComponent.shadowStyles = css`
  :host {
    display: block;
  }
  button {
    color: red;
  }
`;
```
Setting `shadowStyles` automatically creates a Shadow Root and uses `adoptedStyleSheets`.

### addRootStyles / addShadowStyles (append additional sheets)
```js
MyComponent.addRootStyles(anotherSheet);
MyComponent.addShadowStyles(anotherSheet);
```

### `css` tag function
Returns a `CSSStyleSheet` instance (constructable stylesheet). Supports processors:
```js
css.useProcessor((txt) => txt.replaceAll('$accent', '#ff0'));
```

---

## Element References

```js
MyComponent.template = html`
  <input ${{ref: 'nameInput'}}>
  <button ${{ref: 'submitBtn', onclick: 'onSubmit'}}>Submit</button>
`;

// In renderCallback:
this.ref.nameInput.focus();
this.ref.submitBtn.disabled = true;
```

Alternative HTML syntax: `<div ref="myRef"></div>`

---

## Itemize API (Dynamic Lists)

```js
class MyList extends Symbiote {
  init$ = {
    items: [
      { name: 'Alice', role: 'Admin' },
      { name: 'Bob', role: 'User' },
    ],
  }

  onItemClick(e) {
    console.log('clicked');
  }
}

MyList.template = html`
  <ul itemize="items">
    <template>
      <li>
        <span>{{name}}</span> - <span>{{role}}</span>
        <button ${{onclick: '^onItemClick'}}>Click</button>
      </li>
    </template>
  </ul>
`;
```

> **CRITICAL**: Inside itemize, each item is a Symbiote component with its own state scope.
> There are **two patterns** — they determine how event handlers are bound:
>
> **Pattern 1: Inline `<template>` (dumb items)** — items have no class definition, only data properties from the array.
> Any event handler or data must come from an **external context** — not from the item itself.
> All context prefixes work: `^` (parent pop-up), `APP/` (named), `*` (shared). The most common is `^`:
> ```html
> <ul itemize="items">
>   <template>
>     <li>{{name}} <button ${{onclick: '^onItemClick'}}>Click</button></li>
>   </template>
> </ul>
> ```
> Without a context prefix, the binding looks for the handler on the item itself — which doesn't have it, so it breaks.
>
> **Pattern 2: Custom `item-tag` (smart items)** — items are full components with their own class, templates, and methods.
> Handlers defined on the item component itself do **NOT** need `^`:
> ```html
> <div itemize="items" item-tag="my-item"></div>
> ```
> ```js
> class MyItem extends Symbiote {
>   onItemClick() { console.log(this.$.name); }
> }
> MyItem.template = html`<li>{{name}} <button ${{onclick: 'onItemClick'}}>Click</button></li>`;
> MyItem.reg('my-item');
> ```
> Here `onItemClick` is the item's own method — no `^` needed.
> You can still use `^` to reach the parent list component if needed.

### Custom item component
```html
<div itemize="items" item-tag="my-item"></div>
```
Then define `my-item` as a separate Symbiote component with its own template, methods, and state.

### Data formats
- **Array**: `[{prop: val}, ...]` — items rendered in order
- **Object**: `{key1: {prop: val}, ...}` — items get `_KEY_` property added

### Updating lists
Assign new array to trigger re-render:
```js
this.$.items = [...newItems]; // triggers update
```
Existing items are updated in-place via `set$`, new items appended, excess removed.

---

## Slots (Light DOM)

Slots work without Shadow DOM (processed by `slotProcessor`). Import and add manually since v2.x:

```js
import { slotProcessor } from '@symbiotejs/symbiote/core/slotProcessor.js';

class MyWrapper extends Symbiote {
  constructor() {
    super();
    this.templateProcessors.add(slotProcessor);
  }
}

MyWrapper.template = html`
  <header><slot name="header"></slot></header>
  <main><slot></slot></main>
`;
```

Usage:
```html
<my-wrapper>
  <h1 slot="header">Title</h1>
  <p>Default slot content</p>
</my-wrapper>
```

---

## Server-Side Rendering (SSR)

Import `node/SSR.js` to render components to HTML strings on the server. Requires `linkedom` (optional peer dependency).

### Basic usage — `processHtml`

```js
import { SSR } from '@symbiotejs/symbiote/node/SSR.js';

await SSR.init();                // patches globals with linkedom env
await import('./my-component.js'); // component reg() works normally

let html = await SSR.processHtml('<div><my-component></my-component></div>');
// => '<div><my-component><style>...</style><template shadowrootmode="open">...</template>content</my-component></div>'

SSR.destroy();                   // cleanup globals
```

`processHtml` takes any HTML string, renders all Symbiote components found within, and returns the processed HTML. If `SSR.init()` was already called, it reuses the existing environment; otherwise it auto-initializes (and auto-destroys after).

### Advanced — `renderToString` / `renderToStream`

```js
import { SSR } from '@symbiotejs/symbiote/node/SSR.js';

await SSR.init();
await import('./my-component.js');

let html = SSR.renderToString('my-component', { title: 'Hello' });
// => '<my-component title="Hello"><h1>Hello</h1></my-component>'

SSR.destroy();
```

### API

| Method | Description |
|--------|-------------|
| `SSR.init()` | `async` — creates linkedom document, polyfills CSSStyleSheet/NodeFilter/MutationObserver/adoptedStyleSheets, patches globals |
| `SSR.processHtml(html)` | `async` — parses HTML string, renders all custom elements, returns processed HTML. Auto-inits if needed |
| `SSR.renderToString(tagName, attrs?)` | Creates element, triggers `connectedCallback`, serializes to HTML string |
| `SSR.renderToStream(tagName, attrs?)` | Async generator — yields HTML chunks. Same output as `renderToString`, but streamed for lower TTFB |
| `SSR.destroy()` | Removes global patches, cleans up document |

### Styles in SSR output

- **rootStyles** → `<style>` tag as first child of the component (light DOM, deduplicated per constructor)
- **shadowStyles** → `<style>` inside the Declarative Shadow DOM `<template>`
- Both are supported simultaneously on the same component

### Streaming usage

```js
import http from 'node:http';
import { SSR } from '@symbiotejs/symbiote/node/SSR.js';

await SSR.init();
import './my-app.js';

http.createServer(async (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.write('<!DOCTYPE html><html><body>');
  for await (let chunk of SSR.renderToStream('my-app')) {
    res.write(chunk);
  }
  res.end('</body></html>');
}).listen(3000);
```

### Shadow DOM output

Shadow components produce Declarative Shadow DOM markup with styles inlined. Light DOM content is preserved alongside the DSD template:
```html
<my-shadow>
  <style>my-shadow { display: block; }</style>
  <template shadowrootmode="open">
    <style>:host { color: red; }</style>
    <h1>Content</h1>
    <slot></slot>
  </template>
  Light DOM content here
</my-shadow>
```

### SSR context detection

`SSR.init()` sets `globalThis.__SYMBIOTE_SSR = true`. This is separate from the instance `ssrMode` flag:

| Flag | Scope | Purpose |
|------|-------|-------|
| `__SYMBIOTE_SSR` | Server (global) | Preserves binding attributes (`bind`, `ref`, `itemize`) in HTML output. Bypasses `ssrMode` effects |
| `ssrMode` | Client (instance) | Skips template injection, hydrates existing DOM with bindings |

### Hydration flow

1. **Server**: `SSR.processHtml()` / `SSR.renderToString()` produces HTML with `bind=` / `itemize=` attributes preserved
2. **Client**: component with `ssrMode = true` skips template injection, attaches bindings to pre-rendered DOM
3. State mutations on client update DOM reactively

---

## Routing (AppRouter)

### Path-based routing (recommended)
```js
import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js';

const routerCtx = AppRouter.initRoutingCtx('R', {
  home:     { pattern: '/',            title: 'Home', default: true },
  user:     { pattern: '/users/:id',   title: 'User Profile' },
  settings: { pattern: '/settings',    title: 'Settings' },
  notFound: { pattern: '/404',         title: 'Not Found', error: true },
});

// Navigate programmatically
AppRouter.navigate('user', { id: '42' });
// URL becomes: /users/42

// React to route changes in any component
this.sub('R/route', (route) => console.log('Route:', route));
this.sub('R/options', (opts) => console.log('Params:', opts)); // { id: '42' }
```

### Query-string routing (legacy/alternative)
```js
// Routes WITHOUT `pattern` use query-string mode automatically
const routerCtx = AppRouter.initRoutingCtx('R', {
  home:  { title: 'Home', default: true },
  about: { title: 'About' },
});
AppRouter.navigate('about', { section: 'team' });
// URL becomes: ?about&section=team
```

### Route guards
```js
// Register guard — runs before every navigation
let unsub = AppRouter.beforeRoute((to, from) => {
  if (!isAuth && to.route === 'settings') {
    return 'login'; // redirect
  }
  // return false to cancel, nothing to proceed
});

unsub(); // remove guard
```

### Lazy loaded routes
```js
AppRouter.initRoutingCtx('R', {
  settings: {
    pattern: '/settings',
    title: 'Settings',
    load: () => import('./pages/settings.js'), // loaded once, cached
  },
});
```

### AppRouter API
- `AppRouter.initRoutingCtx(ctxName, routingMap)` → PubSub
- `AppRouter.navigate(route, options?)` — navigate and dispatch event
- `AppRouter.reflect(route, options?)` — update URL without triggering event
- `AppRouter.notify()` — read URL, run guards, lazy load, dispatch event
- `AppRouter.beforeRoute(fn)` — register guard, returns unsubscribe fn
- `AppRouter.setRoutingMap(map)` — extend routes
- `AppRouter.readAddressBar()` → `{ route, options }`
- `AppRouter.setSeparator(char)` — default `&` (query-string mode)
- `AppRouter.setDefaultTitle(title)`
- `AppRouter.removePopstateListener()`
- Mode auto-detected: routes with `pattern` → path-based, without → query-string

---

## Attribute Binding

```js
class MyComponent extends Symbiote {
  init$ = {
    '@name': '',  // reads from HTML attribute `name` automatically
  };
}
MyComponent.bindAttributes({
  'value': 'inputValue',  // maps HTML attr `value` → state prop `inputValue`
});
// observedAttributes is auto-populated
```

---

## CSS Data Binding

Read CSS custom property values into component state:

```js
class MyComponent extends Symbiote {
  cssInit$ = {
    '--accent-color': '#ff0',  // fallback value
  };
}
```

Or in template:
```html
<div>{{--my-css-prop}}</div>
```

Update with: `this.updateCssData()` / `this.dropCssDataCache()`.

---

## Component Registration

```js
// Explicit tag name
MyComponent.reg('my-component');

// Auto-generated tag (sym-1, sym-2, ...)
MyComponent.reg();

// Alias registration (creates a subclass)
MyComponent.reg('my-alias', true);

// Get tag name (auto-registers if needed)
const tag = MyComponent.is; // 'my-component'
```

---

## Utilities

```js
import { UID } from '@symbiotejs/symbiote/utils';
UID.generate('XXXXX-XXX'); // e.g. 'aB3kD-z9Q'

import { create, applyStyles, applyAttributes } from '@symbiotejs/symbiote/utils';
let el = create({ tag: 'div', attributes: { id: 'x' }, styles: { color: 'red' }, children: [...] });

import { reassignDictionary } from '@symbiotejs/symbiote/utils';
reassignDictionary({ BIND_ATTR: 'data-bind' }); // customize internal attribute names
```

---

## Security (Trusted Types)

Template `innerHTML` writes use a Trusted Types policy when the API is available:

```js
// Symbiote creates a passthrough policy automatically:
// trustedTypes.createPolicy('symbiote', { createHTML: (s) => s })
```

This makes Symbiote compatible with strict CSP headers:
```
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types symbiote
```

No sanitization is performed — templates are developer-authored, not user input. The policy name is `'symbiote'`.

---

## Dev Mode

Enable verbose warnings during development:
```js
Symbiote.devMode = true;
```

### Dev messages module

All warning/error strings are in an optional module. Without it, warnings print short codes like `[Symbiote W5]`. Import once to get full messages and auto-enable `devMode`:
```js
import '@symbiotejs/symbiote/core/devMessages.js';
```

**Always-on** (regardless of `devMode`):
- `W1` PubSub: cannot read/publish/subscribe — property not found
- `W3` context already registered, `W4` context not found
- `W5` custom template not found, `W8` tag already registered, `W9` CSS data parse error
- `W13`/`W14` AppRouter messages, `W16` itemize data type error
- `E15` `this` in template interpolation error (`html` tag detects `${this.x}` usage)

**Dev-only** (`devMode = true`):
- `W2` type change on publish, `W6` `*prop` without ctx, `W7` shared prop conflict
- `W10` CSS data binding in SSR, `W11` unresolved binding key, `W12` text-node binding in SSR/ISO

---

## Common Mistakes to Avoid

1. **DON'T** use `this` in template strings — templates are decoupled from component context
2. **DON'T** nest property keys with dots in state: `'obj.prop'` won't work as a state key
3. **DON'T** forget `^` prefix when referencing **parent** handlers from inline `<template>` itemize items (dumb items without their own class). Custom `item-tag` components with own methods bind directly without `^`
4. **DON'T** use `@` prefix directly in HTML — it's only for binding syntax (`${{'@attr': 'prop'}}`)
5. **DON'T** treat `init$` as a regular object — it's processed at connection time
6. **DON'T** define `template` inside the class body (`static template = html\`...\`` won't work) — it's a static property **setter**, assign it outside: `MyComponent.template = html\`...\``. Same applies to `rootStyles` and `shadowStyles`.
7. **DON'T** expect Shadow DOM by default — use `renderShadow = true` or `shadowStyles` to opt in
8. **DON'T** wrap Custom Elements in extra divs — the custom tag IS the wrapper
9. **DON'T** use CSS frameworks (Tailwind, etc.) — use native CSS with custom properties
10. **DON'T** use `require()` — ESM only (import/export)
11. **DON'T** use `*prop` without `ctx` attribute or `--ctx` CSS variable — shared context won't be created
12. **DON'T** rely on class property fallbacks for `^`-targeted properties — the `^` walk only checks the parent's data context (`init$` / `add$()`), not own class properties
