{
  "__meta": {
    "name": "Sercrod man.json",
    "version": "0.1.27",
    "language": "en",
    "purpose": "Detailed runtime manual and AI instruction file for Sercrod.",
    "format": "mixed-string-and-structured",
    "primary_audience": [
      "human developers",
      "AI assistants",
      "documentation tools",
      "runtime inspection tools"
    ],
    "notes": [
      "String entries contain detailed human-readable manual text.",
      "Structured entries beginning with __ai_ provide guidance for AI assistants and tools.",
      "When a detailed string entry and an AI guide entry describe the same feature, the detailed string entry is the source of behavioral truth.",
      "AI guide entries should be used to avoid common misunderstandings and to choose safer implementation patterns.",
      "Final stage includes AI guide blocks, per-entry AI notes, improvement coverage, JSON validation, citation cleanup, and ASCII-safe punctuation cleanup.",
      "Stage 4.5 aligns this manual with Sercrod v0.1.27 key resolution for *case.break, *man, and debug manual entries.",
      "Stage 4.6 adds keyboard event documentation for key modifiers, .window keyboard actions, prevent/preventDefault aliases, and Custom Element lifecycle cleanup.",
      "Stage 4.7 adds Shadow DOM bridge documentation for *shadow, *host, host aliases, standard slot behavior, CSS scope, and AI guidance."
    ]
  },
  "__ai_policy": {
    "title": "AI policy for Sercrod",
    "summary": "Rules that AI assistants should follow when generating, editing, or explaining Sercrod templates.",
    "must_follow": [
      "Treat Sercrod as an attribute-first HTML runtime, not as a virtual DOM framework.",
      "Read behavior from the template whenever possible: data sources, bindings, event attributes, request directives, and output directives are visible in HTML attributes.",
      "Prefer small, readable attribute expressions. Move long or reusable logic into named JavaScript functions.",
      "Keep server response shapes aligned with the Sercrod template paths that read them.",
      "Use button for action-only behavior unless link navigation is intentionally required.",
      "Use *fetch for simple JSON loading and *api for API-style operations with method, body, upload, or *into handling.",
      "Use *for when the element itself represents one repeated item.",
      "Use *each when the element is a container and its children should repeat.",
      "Treat Sercrod expressions as Sercrod-managed expressions, not as ordinary strict-mode JavaScript modules.",
      "Do not execute arbitrary external text as a Sercrod expression. External content should normally be treated as data.",
      "Use `@keydown` as the primary form for keyboard shortcuts and movement controls; mention `@keyup` mainly for release-timing behavior.",
      "When using window-level keyboard behavior, write it explicitly with `.window` and keep the action scoped to the nearest `<serc-rod>` host.",
      "Do not treat arbitrary non-modifier key combinations such as `a+b` as supported shortcut conditions unless the detailed keyboard manual says so.",
      "Treat *shadow and *host as a Shadow DOM bridge, not as a custom component renderer.",
      "Use `<template *shadow=\"name\">` to define the visible shadow-side structure and `*host=\"name\"` to connect a host element to it.",
      "Keep standard `<slot>` and `slot=\"...\"` behavior assigned to the browser. Sercrod should only connect the shadow template and host.",
      "Use Sercrod data and binding directives inside the shadow template when the generated content should live inside the shadow root; use Light DOM directives for slotted page-owned content.",
      "When documenting CSS separation for *shadow / *host, state that CSS scoping comes from standard Shadow DOM behavior, not from Sercrod's own CSS isolation layer.",
      "State that slotted content remains Light DOM content and outer page CSS applies to it normally."
    ],
    "must_not": [
      "Do not assume that Sercrod behaves exactly like Vue, React, Alpine, or a server-side template engine.",
      "Do not assume that every input update should immediately redraw the parent template.",
      "Do not assume that *lazy means the value is ignored.",
      "Do not assume that non_mutating events can never change data. It means they do not automatically trigger the normal update flow by default.",
      "Do not place *each and *include or *import on the same element.",
      "Do not change a server JSON response root without changing the Sercrod template paths that read it.",
      "Do not mix multiple network control directives on the same element unless the detailed manual explicitly says the combination is supported.",
      "Do not rely on undocumented behavior when the detailed directive entry gives a specific execution model.",
      "Do not write *shadow on the host element when defining the Shadow DOM bridge. Use `<template *shadow=\"...\">` for the shadow structure and `*host` on the host.",
      "Do not claim that Sercrod moves Light DOM children into Shadow DOM.",
      "Do not claim that Sercrod reimplements slot assignment or CSS isolation.",
      "Do not treat *shadow or *host as ordinary nested directives inside a shadow template; ordinary non-bridge directives may render there.",
      "Do not treat value-less *host as the recommended form. It is allowed as a shorthand, but explicit names are preferred."
    ],
    "when_uncertain": [
      "Prefer the detailed directive entry over short summaries.",
      "Prefer explicit data paths over inferred paths.",
      "Prefer preserving the existing template structure over rewriting it into another framework style.",
      "When request timing is unclear, check whether the directive is on a clickable element or a non-clickable element.",
      "When rendering behavior is unclear, inspect whether the directive is structural, output-oriented, event-oriented, or network-oriented.",
      "For Shadow DOM bridge behavior, prefer explicit names such as `<template *shadow=\"messagebox\">` and `<message-box *host=\"messagebox\">`.",
      "When CSS isolation is discussed, distinguish Shadow DOM internal CSS from slotted Light DOM content."
    ]
  },
  "__index": {
    "title": "Sercrod manual index",
    "purpose": "Routing guide for humans and AI assistants. Read this first, then jump to the smallest relevant entry.",
    "routing_principles": [
      "Use this index before rereading the full runtime manual.",
      "For directive behavior, jump to that directive entry first.",
      "For persistence questions, read save/load plus storage-backends and adapters.",
      "Use dist/sercrod.js only when implementation details are required."
    ],
    "common_routes": [
      {
        "id": "save-load-persistence",
        "question": "How should Sercrod save or load local state?",
        "read": [
          "save",
          "load",
          "storage-backends",
          "adapters"
        ],
        "stop_when": "You understand the directive merge behavior and the chosen storage backend boundary."
      },
      {
        "id": "browser-storage-backends",
        "question": "Where do IndexedDB and OPFS fit?",
        "read": [
          "storage-backends",
          "load",
          "save",
          "adapters"
        ],
        "stop_when": "You can explain what remains in host data and what is stored externally."
      }
    ]
  },
  "__ai_runtime_guide": {
    "title": "Runtime behavior guide for AI assistants",
    "summary": "High-level runtime rules that help AI assistants understand how Sercrod pages behave.",
    "sections": [
      {
        "id": "html-attributes-define-behavior",
        "title": "HTML attributes define behavior",
        "summary": "Sercrod makes UI behavior visible in HTML attributes. Attributes show where data is read, where values are written, where events happen, and where server responses are stored.",
        "ai_notes": [
          "Do not treat attributes as mere decoration. In Sercrod, many attributes are behavior declarations.",
          "Inline expressions should stay readable. Complex logic should be moved to functions and called from attributes.",
          "A Sercrod template should make the data flow inspectable from the HTML."
        ],
        "related_keys": [
          "attributes",
          "print",
          "input",
          "api",
          "fetch"
        ]
      },
      {
        "id": "reading-data-flow",
        "title": "Reading the data flow from the template",
        "summary": "A Sercrod template can be read by tracing which attribute writes data, which expression reads it, and which element renders the result.",
        "ai_notes": [
          "When editing a template, keep the write path and read path aligned.",
          "When renaming a data path, update all bindings that reference that path.",
          "Do not invent new response keys or data paths unless the template is updated to match them."
        ],
        "related_keys": [
          "input",
          "print",
          "textContent",
          "attributes"
        ]
      },
      {
        "id": "input-update-timing",
        "title": "Input update timing",
        "summary": "*input, *lazy, and *eager differ mainly by update timing and user interaction behavior.",
        "ai_notes": [
          "Normal *input commits the edited value through the normal input commit flow.",
          "*lazy keeps the value available while avoiding immediate parent template refresh caused by each edit.",
          "*eager is appropriate for live search, live filtering, live preview, counters, and immediate validation.",
          "Do not explain *lazy as simply ignoring input or as a simple blur-only update.",
          "With normal `*input`, when a text edit is committed and the parent template refreshes, the first click after typing may primarily commit the input value rather than run the intended button action.",
          "In form flows where the user types and then clicks Send or Submit, explain whether the first click is expected to commit input, run the action, or both.",
          "When natural type-then-send behavior is required, consider `*lazy` so the input edit does not force a parent refresh before the intended action can run.",
          "For form flows where the user types and then clicks Send or Submit, `*lazy` helps avoid a parent template refresh caused by input commit from consuming the first click as a focus-out or commit step.",
          "Use `*lazy` to support natural type-then-action flows such as message send, contact submit, staged apply, WebSocket send, or API submit.",
          "When documenting `*lazy`, explicitly contrast it with normal `*input` in button-after-input flows."
        ],
        "related_keys": [
          "input",
          "lazy",
          "eager"
        ]
      },
      {
        "id": "fetch-api-trigger-timing",
        "title": "*fetch and *api trigger timing",
        "summary": "*fetch and *api can behave as initial loading or as user-triggered actions depending on the element they are attached to.",
        "ai_notes": [
          "On non-clickable elements, request directives are commonly used for initial loading.",
          "On clickable elements, request directives are commonly used as user-triggered actions.",
          "*fetch is usually for simple JSON loading.",
          "*api is the general HTTP primitive for method, body, upload, events, status flags, and *into."
        ],
        "related_keys": [
          "fetch",
          "api",
          "post",
          "upload",
          "download"
        ]
      },
      {
        "id": "clickable-elements",
        "title": "Clickable elements and action timing",
        "summary": "Some directives change behavior when placed on clickable elements.",
        "ai_notes": [
          "Clickable triggers include button, a without download, input[type=button], input[type=submit], and input[type=reset].",
          "For *fetch, input[type=image] may also act as a clickable trigger.",
          "Use button for action-only behavior.",
          "Use a only when link-like behavior or navigation semantics are intentional."
        ],
        "related_keys": [
          "api",
          "fetch",
          "post",
          "prevent-default"
        ]
      },
      {
        "id": "event-update-behavior",
        "title": "Event attributes and update behavior",
        "summary": "Event attributes use a configurable event prefix. High-frequency events may be excluded from the normal automatic update flow through the non_mutating event list.",
        "ai_notes": [
          "The default event prefix is @, such as @click, @input, and @submit.",
          "non_mutating does not mean data cannot change.",
          "non_mutating means the event is not treated as a normal automatic update trigger by default.",
          "High-frequency events such as pointer movement, mouse movement, scrolling, touch movement, and drag-over behavior should be handled carefully.",
          "Keyboard events are part of the @event family, but `@keydown` and `@keyup` can use key-name modifiers such as `.arrowup` and combinations such as `.arrowup+shift`.",
          "For shortcuts and movement controls, prefer `@keydown` examples over `@keyup` examples.",
          "Use `.window` only when the keyboard action should be observed at the window level."
        ],
        "related_keys": [
          "events",
          "prevent-default",
          "updated",
          "event-keydown",
          "event-keyup",
          "keyboard-events"
        ]
      },
      {
        "id": "keyboard-events",
        "title": "Keyboard events and window key actions",
        "summary": "Keyboard shortcuts use @keydown and @keyup. Key names may be written as modifiers, modifier-key combinations use +, and .window declares window-level observation scoped to the nearest Sercrod host.",
        "ai_notes": [
          "Use `@keydown` as the main form for shortcuts, arrow-key movement, Enter actions, Escape closing behavior, and Ctrl/Shift combinations.",
          "`@keyup` is supported with the same key syntax, but should usually be described as release timing or cleanup.",
          "Key names are matched case-insensitively. Use lowercase in examples, such as `arrowup`, `enter`, `escape`, and `shift`.",
          "Accept official `KeyboardEvent.key` names such as `ArrowUp`, `Enter`, `Escape`, and `Shift` by normalizing both the attribute token and `event.key` before comparison.",
          "The order of tokens in modifier-key combinations does not matter. `arrowup+shift` and `shift+arrowup` are equivalent.",
          "Support modifier-key combinations such as `arrowup+shift`, `s+ctrl`, and `enter+ctrl+shift`.",
          "Do not treat arbitrary non-modifier combinations such as `a+b` as supported in the initial specification. They should produce `[Sercrod warn]`.",
          "When `.window` is present, the declaration element is not the event target. The key event is observed on `window`, and the action runs in the nearest `<serc-rod>` context.",
          "`input type=\"hidden\"` may be used as an invisible declaration location for `.window` key actions.",
          "When the owning `<serc-rod>` is removed from the DOM, window key registrations should be cleaned up through the Custom Element lifecycle.",
          "Treat `.prevent` and `.preventDefault` as equivalent for key events. Prefer `.prevent` in examples.",
          "`preventDefault()` should run only when the key condition matches."
        ],
        "related_keys": [
          "events",
          "event-keydown",
          "event-keyup",
          "prevent",
          "prevent-default"
        ]
      },
      {
        "id": "for-and-each",
        "title": "*for and *each",
        "summary": "*for repeats the element itself. *each keeps the container and repeats its children.",
        "ai_notes": [
          "Use *for when the element itself represents one item.",
          "Use *each when the element is a container for repeated children.",
          "Do not place *each and *include or *import on the same element.",
          "When reusable parts are needed inside repeated content, put the include or import on a child element or wrapper."
        ],
        "related_keys": [
          "for",
          "each",
          "include",
          "import"
        ]
      },
      {
        "id": "expression-scope",
        "title": "Sercrod-managed expression scope",
        "summary": "Sercrod expressions are evaluated in a Sercrod-managed scope that can include data paths, loop variables, events, element references, parent/root data, and methods.",
        "ai_notes": [
          "Do not judge Sercrod expressions as ordinary strict-mode JavaScript module code.",
          "Data paths such as title, items, and form.email can be read directly in template expressions.",
          "Loop-local variables such as item or post are available inside the repeated scope.",
          "Special values such as $data, $root, $parent, $event, $e, el, and $el may be available depending on context."
        ],
        "related_keys": [
          "methods",
          "for",
          "each",
          "let",
          "global"
        ]
      },
      {
        "id": "methods-and-scope-functions",
        "title": "*methods and scope functions",
        "summary": "*methods can bring named JavaScript functions into the Sercrod expression scope. This can affect how this is resolved inside the function.",
        "ai_notes": [
          "A function exposed through *methods should be treated as a scope function.",
          "A function not exposed through *methods may still be resolved externally, but it is not the same as a Sercrod scope function.",
          "When this matters, explain whether the function is called as part of the Sercrod expression scope or as an external function."
        ],
        "related_keys": [
          "methods"
        ]
      },
      {
        "id": "template-ast-hooks",
        "title": "Template AST hooks",
        "summary": "Sercrod can expose a lightweight AST of the template structure for tools, editors, validators, documentation helpers, and AI-assisted workflows.",
        "ai_notes": [
          "The Sercrod AST is not a JavaScript AST.",
          "It represents HTML-oriented template structure: elements, attributes, text nodes, comments, and children.",
          "AST hooks should usually support the runtime, not replace it.",
          "A useful pattern is to send the template AST to an external tool and receive supporting JSON data back."
        ],
        "related_keys": [
          "template",
          "include",
          "import"
        ]
      },
      {
        "id": "frontend-server-contract",
        "title": "Frontend/server response contract",
        "summary": "Sercrod templates and server JSON responses must agree on response shape and data paths.",
        "ai_notes": [
          "*api with *into=\"result\" stores the response under result.",
          "If the template reads result.message, the server response should provide a message key under that object.",
          "*fetch=\"/api/items.json:items\" stores the fetched result under items.",
          "Do not change the server response root without changing the template paths.",
          "Stable response shapes help humans, tests, documentation, and AI tools."
        ],
        "related_keys": [
          "api",
          "fetch",
          "post",
          "into"
        ]
      },
      {
        "id": "debugging-hooks",
        "title": "Debugging hooks",
        "summary": "Sercrod may expose runtime events such as sercrod-change to help inspect data changes and compare them with rendered DOM changes.",
        "ai_notes": [
          "Use runtime inspection hooks when debugging data update problems.",
          "Compare data changes with DOM inspection to identify whether the problem is input handling, data update timing, action timing, or rendering.",
          "Do not treat debug hooks as application logic unless the detailed manual explicitly documents that usage.",
          "For manual display, `*man=\"debug\"` maps to the internal `__debug` manual entry."
        ],
        "related_keys": [
          "events",
          "updated",
          "updated-propagate",
          "log",
          "__debug",
          "man"
        ]
      },
      {
        "id": "shadow-dom-bridge",
        "title": "Shadow DOM bridge",
        "summary": "*shadow defines a visible shadow template, and *host connects a host element to that template. Sercrod connects the two and leaves standard slot assignment and CSS scoping to the browser.",
        "ai_notes": [
          "Use `<template *shadow=\"name\">` to define the Shadow DOM structure.",
          "Use `*host=\"name\"` on the host element that should receive the shadow template.",
          "Do not use `*shadow` on the host element.",
          "Do not explain the feature as moving Light DOM children into Shadow DOM. Slotted children remain Light DOM nodes.",
          "Do not explain the feature as a Sercrod slot implementation. Standard `<slot>` and `slot=\"...\"` are handled by the browser.",
          "Use explicit names in examples. Value-less `*host` is allowed, but not recommended.",
          "Treat `*shadow-host`, `n-host`, and `n-shadow-host` as aliases of `*host`.",
          "Describe CSS scoping as standard Shadow DOM behavior, not as Sercrod's own CSS isolation feature.",
          "Mention that CSS written inside the shadow template does not leak out, and outer CSS does not select shadow-root internal elements with ordinary selectors.",
          "State that slotted Light DOM content is still selected by outer page CSS normally."
        ],
        "related_keys": [
          "shadow",
          "host",
          "shadow-host",
          "n-host",
          "n-shadow-host"
        ]
      }
    ]
  },
  "__ai_confusion_guards": {
    "title": "Common misunderstanding guards",
    "summary": "Short rules that prevent AI assistants from applying incorrect assumptions from other frameworks.",
    "items": [
      {
        "id": "not-vdom",
        "wrong_assumption": "Sercrod is a virtual DOM framework.",
        "correct_view": "Sercrod is an attribute-first HTML runtime using Web Components and runtime rendering rules."
      },
      {
        "id": "not-template-only",
        "wrong_assumption": "Sercrod attributes are just static documentation.",
        "correct_view": "Sercrod attributes can be executable behavior declarations."
      },
      {
        "id": "lazy-not-ignore",
        "wrong_assumption": "*lazy ignores the input value until later.",
        "correct_view": "*lazy keeps the value usable while avoiding immediate parent template refresh caused by each edit."
      },
      {
        "id": "eager-not-default",
        "wrong_assumption": "Inputs should always update the surrounding UI immediately.",
        "correct_view": "Immediate updates are appropriate for *eager use cases such as live search or live preview, but not every input needs that behavior."
      },
      {
        "id": "fetch-api-trigger",
        "wrong_assumption": "*fetch and *api always fire in the same way.",
        "correct_view": "Trigger timing depends on whether the directive is attached to a clickable or non-clickable element."
      },
      {
        "id": "for-each-difference",
        "wrong_assumption": "*for and *each are interchangeable loop names.",
        "correct_view": "*for repeats the element itself. *each keeps the container and repeats its children."
      },
      {
        "id": "non-mutating",
        "wrong_assumption": "non_mutating events cannot change data.",
        "correct_view": "non_mutating means the event is not treated as a normal automatic update trigger by default."
      },
      {
        "id": "scope",
        "wrong_assumption": "Sercrod expressions must follow ordinary strict JavaScript module scope.",
        "correct_view": "Sercrod expressions run in a Sercrod-managed expression scope."
      },
      {
        "id": "server-response",
        "wrong_assumption": "The server can return any JSON shape and Sercrod will adapt automatically.",
        "correct_view": "The Sercrod template and server response shape must agree on paths such as items, result.message, or posts.items."
      },
      {
        "id": "ast",
        "wrong_assumption": "Template AST hooks parse JavaScript expressions deeply.",
        "correct_view": "Sercrod AST hooks expose lightweight HTML-oriented template structure, not a full JavaScript AST."
      },
      {
        "id": "case-break-key",
        "wrong_assumption": "The external man.json key for *case.break is only case-break.",
        "correct_view": "Sercrod resolves *case.break to the key case.break. case-break may be kept only as a compatibility alias."
      },
      {
        "id": "man-runtime-key",
        "wrong_assumption": "*man is only an internal index and does not need its own manual entry.",
        "correct_view": "When requested as *man, the runtime resolves it to the key man, so a man entry is useful in external man.json."
      },
      {
        "id": "keyboard-window-target",
        "wrong_assumption": "`@keydown.window.arrowup` means the declaration element itself receives keyboard focus or keydown events.",
        "correct_view": "With `.window`, the element is only a declaration location. The key event is observed on `window`, then executed in the nearest `<serc-rod>` context."
      },
      {
        "id": "keyboard-keyup-primary",
        "wrong_assumption": "Keyboard shortcuts should usually be explained with keyup because mouse actions often use mouseup or click.",
        "correct_view": "Keyboard shortcuts and movement controls should usually be explained with keydown. keyup is supported but mainly for release timing."
      },
      {
        "id": "keyboard-arbitrary-multikey",
        "wrong_assumption": "`@keydown.a+b` is supported in the same way as `@keydown.arrowup+shift`.",
        "correct_view": "The initial keyboard specification supports modifier-key combinations, but arbitrary non-modifier combinations such as `a+b` should produce `[Sercrod warn]`."
      },
      {
        "id": "shadow-not-host-side",
        "wrong_assumption": "`*shadow` should be written on the host element to make that element a shadow component.",
        "correct_view": "`*shadow` belongs on `<template>` and defines the shadow-side structure. The host element uses `*host` to connect to that template."
      },
      {
        "id": "shadow-not-slot-reimplementation",
        "wrong_assumption": "Sercrod implements its own slot assignment for *shadow / *host.",
        "correct_view": "Sercrod does not reimplement slots. Standard `<slot>` and `slot=\"...\"` assignment is handled by the browser."
      },
      {
        "id": "shadow-not-moving-children",
        "wrong_assumption": "Sercrod moves Light DOM children into Shadow DOM when *host is used.",
        "correct_view": "Light DOM children are not physically moved into Shadow DOM. They are displayed through the browser's standard slot mechanism."
      },
      {
        "id": "shadow-css-not-sercrod-isolation",
        "wrong_assumption": "Sercrod implements CSS isolation for *shadow / *host by itself.",
        "correct_view": "CSS scoping comes from standard Shadow DOM behavior. Sercrod only connects the shadow template and host."
      },
      {
        "id": "shadow-slotted-content-css",
        "wrong_assumption": "Slotted content is fully isolated from outer page CSS because it appears inside a shadow layout.",
        "correct_view": "Slotted elements remain Light DOM nodes, so outer page CSS applies to them normally."
      }
    ]
  },
  "__ai_entry_notes": {
    "title": "Per-entry AI notes for Sercrod manual",
    "summary": "AI-oriented notes attached to existing manual keys, special internal keys, and compatibility keys. These notes do not replace the detailed manual text.",
    "usage": {
      "read_order": [
        "Read __ai_policy first.",
        "Read __ai_runtime_guide for high-level behavior.",
        "Read the detailed manual entry for the specific directive or feature.",
        "Use __ai_entry_notes for additional cautions and generation guidance."
      ],
      "source_of_truth": "The detailed manual entry remains the behavioral source of truth. These AI notes are guidance for interpretation and generation."
    },
    "coverage": {
      "original_top_level_key_count": 71,
      "noted_entry_count": 83,
      "all_original_keys_are_covered": true,
      "covered_original_keys": [
        "__data",
        "api",
        "apply",
        "attribute-action",
        "attribute-class",
        "attribute-formaction",
        "attribute-href",
        "attribute-src",
        "attribute-style",
        "attribute-value",
        "attribute-xlink-href",
        "attributes",
        "break",
        "case-break",
        "case",
        "compose",
        "default",
        "download",
        "each",
        "eager",
        "else",
        "elseif",
        "event-blur",
        "event-change",
        "event-click",
        "event-focus",
        "event-input",
        "event-keydown",
        "event-keyup",
        "keyboard-events",
        "event-submit",
        "events",
        "fetch",
        "for",
        "global",
        "if",
        "import",
        "include",
        "innerHTML",
        "input",
        "into",
        "lazy",
        "let",
        "literal",
        "load",
        "log",
        "methods",
        "post",
        "prevent-default",
        "prevent",
        "print",
        "rem",
        "restore",
        "save",
        "stage",
        "switch",
        "template",
        "textContent",
        "unwrap",
        "updated-propagate",
        "updated",
        "upload",
        "websocket",
        "ws-send",
        "ws-to",
        "shadow",
        "host",
        "shadow-host",
        "n-host",
        "n-shadow-host",
        "save.store",
        "load.store"
      ]
    },
    "entries": {
      "__data": {
        "summary": "Data, scope, and *let behavior require careful distinction between local scope and host data.",
        "ai_notes": [
          "Distinguish host data from local expression scope.",
          "Do not assume that every assignment in *let permanently updates host data.",
          "Top-level assignments to names already present in host data can be local shadows rather than committed updates.",
          "Nested assignments to existing objects may mutate shared object references and therefore affect host data.",
          "Use *let for local derived values and light transformations.",
          "Use *global, event handlers, or external code when the intent is to explicitly update host data as application state."
        ],
        "related_keys": [
          "let",
          "global",
          "stage"
        ]
      },
      "__debug": {
        "summary": "__debug documents the sercrod-change runtime inspection hook.",
        "ai_notes": [
          "Use `sercrod-change` when debugging observed data mutations through the Proxy layer.",
          "Compare event detail with DOM inspection to locate whether a problem is in input handling, data update timing, action timing, request timing, or rendering.",
          "The event detail includes `host`, `parent`, `key`, `old`, and `new`.",
          "Do not treat debug hooks as ordinary application behavior unless the project explicitly chooses that pattern.",
          "Use `<pre *man=\"debug\"></pre>` to show the debug manual entry."
        ],
        "related_keys": [
          "events",
          "log",
          "updated",
          "updated-propagate",
          "man"
        ]
      },
      "api": {
        "summary": "*api is the general HTTP primitive for API-style requests.",
        "ai_notes": [
          "Use *api when the request needs method, body, payload, upload behavior, status flags, events, or *into.",
          "Do not assume *api always auto-runs. Trigger timing depends on the element.",
          "On non-clickable elements, *api is commonly used for initial loading.",
          "On clickable elements, *api is commonly used for user-triggered actions.",
          "Use *into when the response should be stored under a named data key.",
          "Keep server response shape aligned with the template paths that read the response.",
          "Use $pending and $error for loading and error display.",
          "Avoid stacking *api with other network control directives on the same element unless the detailed manual explicitly supports that combination.",
          "If the template reads `result.message`, the response assigned to `result` should include a `message` property.",
          "If the template reads `posts.items`, the response assigned to `posts` should include an `items` property.",
          "Manual click-triggered requests should not be confused with non-clickable auto-run requests."
        ],
        "related_keys": [
          "fetch",
          "post",
          "upload",
          "download",
          "into"
        ]
      },
      "apply": {
        "summary": "*apply commits staged data back to the host data.",
        "ai_notes": [
          "Use *apply only with hosts that use *stage.",
          "Treat *apply as a commit control, not as an expression directive.",
          "Do not rely on dynamic children inside the same *apply element unless the detailed manual says they are rendered.",
          "Use a dedicated button for commit behavior.",
          "Pair *apply with *restore when the user needs a cancel or discard path.",
          "For staged form UI, make clear whether the apply button commits staged data only or also triggers external side effects through separate directives."
        ],
        "related_keys": [
          "stage",
          "restore"
        ]
      },
      "attribute-action": {
        "summary": ":action binds the form action endpoint.",
        "ai_notes": [
          "Use :action primarily on form elements.",
          "Keep endpoint expressions simple and inspectable.",
          "Coordinate :action with native submit behavior, *post, *api, and prevent-default behavior.",
          "Use URL filtering or server-side validation when endpoints may be influenced by data.",
          "Return null or false when the action attribute should be removed."
        ],
        "related_keys": [
          "attribute-formaction",
          "post",
          "api",
          "prevent-default"
        ]
      },
      "attribute-class": {
        "summary": ":class computes the element class list from data.",
        "ai_notes": [
          "Treat :class as owning the class list for that render.",
          "Do not rely on a static class attribute to remain authoritative when :class overwrites it.",
          "Include required base classes inside the :class expression.",
          "Use array or object syntax for conditional classes when that is clearer than string concatenation."
        ],
        "related_keys": [
          "attributes"
        ]
      },
      "attribute-formaction": {
        "summary": ":formaction binds per-button submit endpoints.",
        "ai_notes": [
          "Use :formaction on submit controls when a button should override the parent form action.",
          "Do not use :formaction as a generic action directive.",
          "Coordinate it with native form behavior and any Sercrod event or post handling.",
          "Return null or false when the button should fall back to the parent form action."
        ],
        "related_keys": [
          "attribute-action",
          "post",
          "prevent-default"
        ]
      },
      "attribute-href": {
        "summary": ":href binds a URL-like value to href.",
        "ai_notes": [
          "Use :href for navigation or link targets.",
          "If the element is used as an action trigger, decide whether native navigation should remain.",
          "Use URL filtering or validation for externally influenced URLs.",
          "Return null or false when the link should not have an href."
        ],
        "related_keys": [
          "attributes",
          "prevent-default"
        ]
      },
      "attribute-src": {
        "summary": ":src binds a URL-like value to src.",
        "ai_notes": [
          "Use :src for resources such as images, media, iframes, or script sources.",
          "Return null or false when no resource should be loaded.",
          "Do not use an empty string when the intended behavior is no src at all.",
          "Use URL filtering or validation for externally influenced resource URLs."
        ],
        "related_keys": [
          "attributes"
        ]
      },
      "attribute-style": {
        "summary": ":style writes inline style from an expression.",
        "ai_notes": [
          "Prefer short style strings or precomputed style values.",
          "Do not use :style for large styling systems when CSS classes would be clearer.",
          "Be aware of whether :style or *style owns the final inline style on the element.",
          "Use filters or project-level rules when style strings may come from external data."
        ],
        "related_keys": [
          "attributes"
        ]
      },
      "attribute-value": {
        "summary": ":value writes from data to a form control value. It is not the same as *input.",
        "ai_notes": [
          ":value is data-to-DOM.",
          "*input is DOM-to-data.",
          "Use both only when you intentionally want a controlled value plus write-back behavior.",
          "Do not explain :value as two-way binding by itself.",
          "For editable controls, decide whether :value, *input, or both are appropriate.",
          "In form documentation, explicitly distinguish `:value` as data-to-DOM from `*input` as DOM-to-data."
        ],
        "related_keys": [
          "input",
          "lazy",
          "eager"
        ]
      },
      "attribute-xlink-href": {
        "summary": ":xlink:href binds SVG xlink references.",
        "ai_notes": [
          "Use mainly for SVG elements that support xlink:href.",
          "Prefer simple symbol references or precomputed values.",
          "Use URL or reference filtering if external data can influence SVG references.",
          "Do not confuse :xlink:href with normal HTML navigation."
        ],
        "related_keys": [
          "attributes"
        ]
      },
      "attributes": {
        "summary": "Colon attributes bind data to DOM attributes.",
        "ai_notes": [
          "Use :name bindings for one-way data-to-DOM attribute output.",
          "Do not assume colon attributes write back to data.",
          "Use dedicated entries for special attributes such as :class, :style, :value, :href, :src, :action, :formaction, and :xlink:href.",
          "When binding boolean-like attributes, understand whether false or null removes the attribute and true creates it.",
          "If a dynamic binding owns an attribute, treat it as the source of truth for that attribute.",
          "Attribute bindings are one-way data-to-DOM unless the detailed entry says otherwise. Do not confuse them with input write-back directives."
        ],
        "related_keys": [
          "attribute-class",
          "attribute-style",
          "attribute-value"
        ]
      },
      "break": {
        "summary": "*break stops switch fallthrough where the detailed manual defines it.",
        "ai_notes": [
          "Use *break primarily in *switch branches.",
          "Do not assume *break stops *for or *each loops unless the detailed manual for the current version says so.",
          "For match-and-stop behavior in switches, prefer *case.break when that is clearer.",
          "Do not attach meaning to the value of *break when the runtime treats it as a presence flag."
        ],
        "related_keys": [
          "switch",
          "case",
          "case-break"
        ]
      },
      "case-break": {
        "summary": "*case.break combines a switch case with a break point.",
        "ai_notes": [
          "Use *case.break for the common match-and-stop switch pattern.",
          "Do not add a redundant *break to the same branch unless you intentionally want explicit duplication.",
          "Keep case expressions simple and close to the switch value.",
          "Remember that *case.break is meaningful only inside a *switch context."
        ],
        "related_keys": [
          "switch",
          "case",
          "break"
        ]
      },
      "case.break": {
        "summary": "*case.break is the runtime key form for the case-break manual entry.",
        "ai_notes": [
          "Use `case.break` as the actual external man.json key for `*case.break`.",
          "Keep `case-break` only as a compatibility or editorial alias if it already exists.",
          "The behavior is the same as `*case=\"expr\" *break` on the same branch.",
          "Do not describe `*case.break` as a generic loop break. It belongs to `*switch` branch control."
        ],
        "related_keys": [
          "switch",
          "case",
          "break",
          "case-break"
        ]
      },
      "case": {
        "summary": "*case defines a branch in a *switch block.",
        "ai_notes": [
          "Use *case only as a direct child branch of a *switch host.",
          "Remember that Sercrod switch behavior may allow fallthrough until a break point.",
          "Use *case.break or *break when only one branch should render.",
          "Do not place branch markers deeply inside wrappers if the switch engine only reads direct children."
        ],
        "related_keys": [
          "switch",
          "case-break",
          "default",
          "break"
        ]
      },
      "compose": {
        "summary": "*compose writes HTML content through the HTML filter.",
        "ai_notes": [
          "Treat *compose and *innerHTML as security-sensitive because they write HTML.",
          "Do not use *compose for untrusted external content unless the HTML filter sanitizes or controls it.",
          "Use *print or text output when plain text is sufficient.",
          "Use *compose when the project intentionally composes trusted HTML fragments or filtered HTML output."
        ],
        "related_keys": [
          "innerHTML",
          "print",
          "textContent"
        ]
      },
      "default": {
        "summary": "*default defines the fallback branch for *switch.",
        "ai_notes": [
          "Use *default only inside a *switch block.",
          "Keep at most one default branch per switch unless fallthrough behavior is intentionally documented.",
          "If the default branch should not fall through, pair it with *break when supported.",
          "Do not use *default as a general conditional outside switch contexts."
        ],
        "related_keys": [
          "switch",
          "case",
          "break"
        ]
      },
      "download": {
        "summary": "*download handles download-oriented network behavior.",
        "ai_notes": [
          "Use *download for download-specific flows rather than forcing *api into every network case.",
          "Keep download triggers explicit and user-understandable.",
          "Expose loading and error state when the detailed manual provides status fields.",
          "Do not combine *download with *api or *upload on the same element unless the detailed manual says that combination is supported.",
          "Only successful GET downloads are persisted; non-GET download requests are never stored because they may have server-side side effects."
        ],
        "related_keys": [
          "api",
          "upload"
        ]
      },
      "each": {
        "summary": "*each keeps the container and repeats its children.",
        "ai_notes": [
          "Use *each when the element is a stable container and its child template should repeat.",
          "Do not treat *each as interchangeable with *for.",
          "Do not place *each and *include or *import on the same element.",
          "If each repeated item needs an included part, place *include or *import on a child wrapper inside the repeated area.",
          "Use *each carefully with containers such as ul, tbody, section, or div where the container should remain singular."
        ],
        "related_keys": [
          "for",
          "include",
          "import"
        ]
      },
      "eager": {
        "summary": "*eager is for input-driven interfaces that must update while the user types.",
        "ai_notes": [
          "Use *eager with *input when another part of the template should react immediately to typing.",
          "Good examples include live search, live filtering, live preview, realtime counters, slug previews, and immediate validation messages.",
          "A useful *eager example should include another element that reads the same data path.",
          "Do not use *eager for ordinary message forms or simple submit forms where the user types and then clicks a button.",
          "Use *eager carefully in large templates because frequent host updates may become expensive.",
          "When a button-after-input flow should not redraw on every edit, do not solve it with `*eager`; use `*lazy` or a more explicit commit/action design instead."
        ],
        "related_keys": [
          "input",
          "lazy"
        ]
      },
      "else": {
        "summary": "*else is part of the conditional chain.",
        "ai_notes": [
          "Use *else only as part of an *if / *elseif / *else chain.",
          "Do not treat *else as a standalone condition.",
          "Keep the conditional chain adjacent and readable.",
          "When generating examples, make the true and fallback branches semantically parallel."
        ],
        "related_keys": [
          "if",
          "elseif"
        ]
      },
      "elseif": {
        "summary": "*elseif adds intermediate branches to an *if chain.",
        "ai_notes": [
          "Use *elseif between *if and *else.",
          "Keep each condition simple enough to inspect in HTML.",
          "Prefer moving complex branch logic into a named helper or derived data.",
          "Do not reorder conditional siblings without checking the intended branch priority."
        ],
        "related_keys": [
          "if",
          "else"
        ]
      },
      "event-blur": {
        "summary": "@blur handles focus leaving an element.",
        "ai_notes": [
          "Use blur for commit, validation, or cleanup after an input loses focus.",
          "Do not confuse blur timing with live input timing.",
          "When blur interacts with buttons, consider whether input commit timing affects the next action.",
          "Use *lazy or explicit action design when blur-driven refresh interferes with natural clicks."
        ],
        "related_keys": [
          "events",
          "input",
          "lazy"
        ]
      },
      "event-change": {
        "summary": "@change handles value changes committed by form controls.",
        "ai_notes": [
          "Use change when the control's committed value matters.",
          "Do not use change when every keystroke must update the UI. Use input with *eager for that.",
          "For selects, checkboxes, and radios, change may be more appropriate than input.",
          "Keep change handlers small and readable."
        ],
        "related_keys": [
          "events",
          "input",
          "eager"
        ]
      },
      "event-click": {
        "summary": "@click handles user click actions.",
        "ai_notes": [
          "Use click for explicit user actions such as toggles, saves, sends, and selection.",
          "Use button for action-only click behavior.",
          "When using a link for click behavior, decide whether href navigation should remain or be prevented.",
          "Do not hide major data flow in click handlers when a Sercrod directive can express it visibly."
        ],
        "related_keys": [
          "events",
          "prevent-default",
          "api",
          "fetch"
        ]
      },
      "event-focus": {
        "summary": "@focus handles focus entering an element.",
        "ai_notes": [
          "Use focus for UI state such as highlighting or preparing interaction.",
          "Do not use focus as a substitute for value change handling.",
          "Keep focus side effects light because focus can occur during normal keyboard navigation.",
          "Coordinate focus behavior with accessibility expectations."
        ],
        "related_keys": [
          "events",
          "event-blur"
        ]
      },
      "event-input": {
        "summary": "@input handles ongoing input events.",
        "ai_notes": [
          "Use input events when typed or edited values matter during editing.",
          "If the UI should update live, consider *input with *eager.",
          "If the user should type first and act later, consider *lazy.",
          "Avoid heavy work on every input event in large templates."
        ],
        "related_keys": [
          "events",
          "input",
          "lazy",
          "eager"
        ]
      },
      "event-keydown": {
        "summary": "@keydown handles key press start events and is the preferred event for keyboard shortcuts and movement controls.",
        "ai_notes": [
          "Use keydown for keyboard shortcuts or navigation logic.",
          "Do not assume a keydown event means a form value has already changed.",
          "For text value updates, use input or change timing rather than keydown alone.",
          "When using keydown outside inputs, ensure the target can receive focus.",
          "Use `@keydown` as the primary form for keyboard shortcuts and arrow-key movement.",
          "Keyboard-specific modifiers may include key names such as `.arrowup`, `.enter`, and `.escape`.",
          "Modifier-key combinations such as `arrowup+shift`, `s+ctrl`, and `enter+ctrl+shift` are supported and order-independent.",
          "The `.window` modifier observes key events on `window` while executing the action in the nearest `<serc-rod>` context.",
          "Without `.window`, the event follows normal DOM focus and bubbling behavior.",
          "Do not use `@keydown.a+b` for arbitrary non-modifier multi-key shortcuts in the initial specification; it should warn.",
          "Use `.prevent` or `.preventDefault` to prevent the default browser behavior only when the key condition matches."
        ],
        "related_keys": [
          "events",
          "event-keyup",
          "input",
          "keyboard-events",
          "prevent",
          "prevent-default"
        ]
      },
      "event-keyup": {
        "summary": "@keyup handles key release events and supports the same key syntax as @keydown, but it is usually secondary to keydown for shortcuts.",
        "ai_notes": [
          "Use keyup when behavior should occur after a key is released.",
          "Do not rely on keyup alone for robust text input across IME and composition cases.",
          "Prefer input/change events for value binding.",
          "Keep keyboard behavior accessible and avoid trapping normal navigation keys unexpectedly.",
          "Use `@keyup` when behavior should happen after a key is released.",
          "For keyboard shortcuts and movement controls, prefer `@keydown` in examples and documentation.",
          "Keyboard-specific modifiers and `.window` use the same syntax as `@keydown`.",
          "Modifier-key conditions on `keyup` depend on the modifier still being pressed when the target key is released.",
          "For example, `@keyup.arrowup+shift` matches only if Shift is still pressed when ArrowUp is released.",
          "Do not rely on `@keyup` alone for robust text input across IME and composition cases."
        ],
        "related_keys": [
          "events",
          "event-keydown",
          "input",
          "keyboard-events",
          "prevent",
          "prevent-default"
        ]
      },
      "event-submit": {
        "summary": "@submit handles form submission events.",
        "ai_notes": [
          "Use submit for form-level validation or routing.",
          "Decide whether native form submission, *post, *api, or custom JavaScript owns the final submission.",
          "Use prevent-default deliberately when Sercrod or JavaScript will handle the request.",
          "Keep frontend validation aligned with server-side validation."
        ],
        "related_keys": [
          "events",
          "post",
          "api",
          "prevent-default"
        ]
      },
      "events": {
        "summary": "Event attributes connect DOM events to Sercrod expressions.",
        "ai_notes": [
          "The default event prefix is @, such as @click, @input, and @submit.",
          "The event prefix may be configurable, so do not hard-code @ as the only possible form in conceptual documentation.",
          "Use event attributes for user actions that change data or call functions.",
          "Be careful with high-frequency events such as mousemove, pointermove, scroll, touchmove, and dragover.",
          "non_mutating does not mean the event cannot change data.",
          "non_mutating means the event is not treated as a normal automatic update trigger by default.",
          "For debugging, runtime events should be interpreted together with DOM inspection rather than as proof that rendering has completed correctly.",
          "Keyboard events have a dedicated manual entry because `@keydown` and `@keyup` support key-name modifiers and `.window` observation.",
          "When explaining key shortcuts, link to `keyboard-events` rather than treating them as ordinary fallback `@name` events.",
          "Do not confuse `.window` key declarations with normal element-local event bindings."
        ],
        "related_keys": [
          "prevent-default",
          "updated",
          "updated-propagate",
          "keyboard-events"
        ]
      },
      "fetch": {
        "summary": "*fetch is for simple JSON loading and placement into data.",
        "ai_notes": [
          "Use *fetch for simple JSON loading when the response can be placed directly into a data path.",
          "Use URL:prop style consistently when the fetched value should be stored in a named data property.",
          "Do not use *fetch when the request needs method, JSON body, upload handling, or more API-like control. Use *api for that.",
          "Check whether *fetch is on a clickable element or a non-clickable element before explaining its timing.",
          "On non-clickable elements, explain it as initial loading.",
          "On clickable elements, explain it as user-triggered loading.",
          "Keep the server response shape aligned with the template path that reads the fetched data.",
          "If the template reads `items`, the fetched response assigned to `items` must have the shape the loop or output expects.",
          "Do not add method or body assumptions to `*fetch`; move to `*api` when the request is no longer a simple fetch."
        ],
        "related_keys": [
          "api",
          "into",
          "post"
        ]
      },
      "for": {
        "summary": "*for repeats the element itself.",
        "ai_notes": [
          "Use *for when the element with the directive represents one repeated item.",
          "Good targets include li, article, tr, option, card wrappers, and repeated components.",
          "Do not use *for when the element should remain a single container. Use *each for that.",
          "When generating examples, make the repeated element semantically match the repeated data item.",
          "Keep loop-local variable names short and clear, such as item, post, user, or row."
        ],
        "related_keys": [
          "each"
        ]
      },
      "global": {
        "summary": "*global is for explicit host or global state effects.",
        "ai_notes": [
          "Use *global only when a real stateful side effect is intended.",
          "Prefer *let for local derived values.",
          "Be clear whether an assignment targets host data or globalThis.",
          "Do not use *global to hide ordinary data flow that could be expressed with bindings or event handlers.",
          "When documenting examples, state the expected side effect explicitly."
        ],
        "related_keys": [
          "let",
          "__data"
        ]
      },
      "if": {
        "summary": "*if conditionally renders an element or branch.",
        "ai_notes": [
          "Use *if for presence, not merely for visual hiding.",
          "Keep conditions readable and template-local.",
          "Use *elseif and *else for adjacent branch chains.",
          "Do not assume hidden elements still run child bindings if the structural condition removes them.",
          "When examples require persistent DOM with toggled visibility, consider class or style binding instead."
        ],
        "related_keys": [
          "elseif",
          "else",
          "attributes"
        ]
      },
      "import": {
        "summary": "*import brings external or reusable content into a template.",
        "ai_notes": [
          "Use *import for reusable template or external fragment behavior as defined by the detailed manual.",
          "Keep import sources explicit and predictable.",
          "Do not combine *import with *each on the same element unless the detailed manual supports it.",
          "When repeated imported parts are needed, place import on a child wrapper inside the loop area.",
          "Do not use import to bypass the frontend/server contract for data-driven content.",
          "When include/import is used inside repeated content, place it inside the repeated child structure rather than on the same element as `*each`.",
          "Successful imports may be stored under source key import:<url>.",
          "For *import, network-first still preserves the v15-compatible warmed-memory-cache-first behavior; do not force network in a way that changes render timing."
        ],
        "related_keys": [
          "include",
          "each",
          "template",
          "storage-backends",
          "adapters"
        ]
      },
      "include": {
        "summary": "*include includes reusable content or partials.",
        "ai_notes": [
          "Use *include when a reusable part should be inserted into the template.",
          "Do not place *include and *each on the same element.",
          "When including inside repeated data, put the include on a child wrapper within the repeated body.",
          "Keep included content compatible with the current Sercrod scope.",
          "Document whether included content is static, template-driven, or data-driven.",
          "When include/import is used inside repeated content, place it inside the repeated child structure rather than on the same element as `*each`.",
          "`*include` must remain synchronous and registry-only; persistent templates must be preloaded into `_template_registry` before include runs."
        ],
        "related_keys": [
          "import",
          "each"
        ]
      },
      "innerHTML": {
        "summary": "*innerHTML writes HTML content and should be handled with the same caution as *compose.",
        "ai_notes": [
          "Prefer text output for untrusted content.",
          "Pass HTML through the project HTML filter.",
          "Do not generate examples that place user-provided strings directly into *innerHTML without warning.",
          "Use this feature only when HTML output is intentional.",
          "Prefer *print or *textContent when plain text is enough."
        ],
        "related_keys": [
          "compose",
          "print",
          "textContent"
        ]
      },
      "input": {
        "summary": "*input writes form control values back to a data path. The path must be assignable.",
        "ai_notes": [
          "The value of *input must be an assignable target such as form.email or user.name.",
          "Do not use optional chaining such as user?.name as the write target of *input because optional chaining cannot be assigned to.",
          "Do not assume the HTML name attribute controls the Sercrod data binding. *input controls the Sercrod write path.",
          "Use the HTML name attribute only when browser form submission or server-side form handling needs it.",
          "Initialize expected data types where possible, especially for numbers, checkbox groups, radio groups, selects, and multi-selects.",
          "Separate the write path from the display path when explaining examples.",
          "With normal `*input`, when a text edit is committed and the parent template refreshes, the first click after typing may primarily commit the input value rather than run the intended button action.",
          "In form flows where the user types and then clicks Send or Submit, explain whether the first click is expected to commit input, run the action, or both.",
          "When natural type-then-send behavior is required, consider `*lazy` so the input edit does not force a parent refresh before the intended action can run."
        ],
        "related_keys": [
          "lazy",
          "eager",
          "attribute-value"
        ]
      },
      "into": {
        "summary": "*into names where a response or external value should be stored.",
        "ai_notes": [
          "Treat *into as part of the frontend/server contract.",
          "When changing *into, update every template path that reads the old destination.",
          "Prefer descriptive names such as result, profile, items, posts, or upload_result.",
          "Do not assume *into dynamically evaluates to a nested path unless the detailed manual for that directive says so.",
          "Avoid reusing the same *into key for unrelated requests on the same host unless overwriting is intentional."
        ],
        "related_keys": [
          "api",
          "fetch",
          "post",
          "upload",
          "websocket"
        ]
      },
      "lazy": {
        "summary": "*lazy tunes input-driven update behavior without ignoring the input value.",
        "ai_notes": [
          "*lazy does not mean the value is ignored.",
          "*lazy does not simply mean blur-only update.",
          "*lazy is not lazy loading.",
          "Use *lazy when the user is expected to type first and then move focus, click Send, click Submit, or trigger a later action.",
          "Use *lazy when an immediate parent template refresh may interfere with natural focus or click behavior.",
          "When generating examples, pair *lazy with a later action such as @click, *post, *api, *ws-send, or a submit/send button.",
          "Do not use *lazy for live search, live filtering, live preview, realtime counters, or immediate validation. Use *eager for those cases.",
          "For form flows where the user types and then clicks Send or Submit, `*lazy` helps avoid a parent template refresh caused by input commit from consuming the first click as a focus-out or commit step.",
          "Use `*lazy` to support natural type-then-action flows such as message send, contact submit, staged apply, WebSocket send, or API submit.",
          "When documenting `*lazy`, explicitly contrast it with normal `*input` in button-after-input flows."
        ],
        "related_keys": [
          "input",
          "eager"
        ]
      },
      "let": {
        "summary": "*let creates local derived values and may promote new names according to runtime rules.",
        "ai_notes": [
          "Use *let for local helper values, derived labels, temporary calculations, and light transformations.",
          "Do not assume *let is a general state mutation tool.",
          "Explain whether a *let assignment creates a new name, shadows an existing top-level data field, or mutates a nested object.",
          "Avoid relying on loop variable promotion into host data.",
          "Use *global or explicit event logic for intentional state updates."
        ],
        "related_keys": [
          "__data",
          "global"
        ]
      },
      "literal": {
        "summary": "*literal prevents Sercrod interpretation for content that should remain literal.",
        "ai_notes": [
          "Use *literal when template-like syntax must be shown as text or preserved.",
          "This is important for documentation pages that display Sercrod examples.",
          "Do not put active Sercrod directives inside a literal area expecting them to execute.",
          "Use literal handling instead of fragile escaping when examples contain directive syntax."
        ],
        "related_keys": [
          "rem",
          "print",
          "textContent"
        ]
      },
      "load": {
        "summary": "*load.file, *load.session, and *load.store are explicit load forms; *load remains legacy-compatible shorthand.",
        "ai_notes": [
          "Use *load.file, *load.session, or *load.store plus *keys and *into in new examples.",
          "*into is the destination key; *keys selects data from the loaded JSON.",
          "Old *load=\"a b\" remains supported as legacy key selection.",
          "Do not confuse *load with HTTP loading; use *fetch or *api for network data."
        ],
        "related_keys": [
          "load.file",
          "load.session",
          "load.store",
          "keys",
          "into",
          "save",
          "save.file",
          "save.session",
          "save.store",
          "fetch",
          "api"
        ]
      },
      "log": {
        "summary": "*log is for inspection and debugging output.",
        "ai_notes": [
          "Use *log for debugging and runtime inspection, not for application behavior.",
          "Keep log expressions clear and low-cost.",
          "Remove or disable noisy logs in public examples unless the example is specifically about debugging.",
          "Use logs to verify data paths, event timing, and update behavior.",
          "Use logging as inspection support, not as a replacement for clear template data flow.",
          "When documenting warnings, keep Sercrod-specific warning prefixes consistent."
        ],
        "related_keys": [
          "events",
          "updated"
        ]
      },
      "man": {
        "summary": "*man displays or logs Sercrod manual entries and can load detailed text from man.json.",
        "ai_notes": [
          "Treat `*man` as documentation and inspection support, not application logic.",
          "On `<pre>`, `*man` writes manual text to `textContent`.",
          "On non-`pre` elements, `*man` logs short help to the console and does not modify the DOM.",
          "Use prefixed keys such as `*post`, `@click`, and `:style` when asking for a specific feature.",
          "The alias `debug` maps to `__debug`, and `directives` maps to the directive list.",
          "Prefer detailed external `man.json` entries over built-in short help when both are available."
        ],
        "related_keys": [
          "__debug",
          "log",
          "events"
        ]
      },
      "methods": {
        "summary": "*methods exposes functions into the Sercrod expression scope.",
        "ai_notes": [
          "A function exposed through *methods should be treated as a Sercrod scope function.",
          "Do not treat every callable function as equivalent to a *methods function.",
          "When this is used inside a function, explain whether the function is called as part of the Sercrod expression scope or as an external function.",
          "Use *methods when the function should operate with the current Sercrod block's data and scope.",
          "Use external functions when they are intentionally shared by the wider page and do not depend on Sercrod scope.",
          "Prefer named functions for reusable logic rather than long inline attribute expressions.",
          "When generating examples with `this`, include enough context to show whether `this` resolves through Sercrod scope or ordinary external function context."
        ],
        "related_keys": [
          "__data"
        ]
      },
      "post": {
        "summary": "*post is a submit/write helper whose response shape must match the template.",
        "ai_notes": [
          "Use *post for submit-like JSON write operations when its simpler contract is sufficient.",
          "Use *api instead when method, body, payload, upload behavior, or more explicit control is needed.",
          "Keep the response shape stable and documented.",
          "If the template writes the response to a target such as result, make sure the server returns keys that the template actually reads.",
          "Do not present reference server samples as production-ready endpoints without validation, authentication, permissions, and error handling.",
          "For forms that involve typing followed by submit, consider whether the input update timing should use normal `*input`, `*lazy`, or a more explicit staged commit flow."
        ],
        "related_keys": [
          "api",
          "fetch",
          "into"
        ]
      },
      "prevent-default": {
        "summary": "*prevent-default suppresses native browser behavior for event-driven elements.",
        "ai_notes": [
          "Use prevent-default deliberately when an element has native behavior such as form submission or link navigation.",
          "Do not add prevent-default blindly to every clickable element.",
          "When using a as an action trigger, decide explicitly whether navigation should remain or be prevented.",
          "When using forms, decide whether native submit, Sercrod *post, Sercrod *api, or a custom event handler owns the submission."
        ],
        "related_keys": [
          "prevent",
          "events",
          "post",
          "api"
        ]
      },
      "prevent": {
        "summary": "*prevent is the short form or related form of prevent-default behavior.",
        "ai_notes": [
          "Treat *prevent as part of the same family as *prevent-default when the detailed manual defines it that way.",
          "Use it to suppress native browser behavior only when that is necessary.",
          "Do not use prevent behavior to hide unclear ownership of form submission or link navigation.",
          "Document whether Sercrod or the browser owns the final action."
        ],
        "related_keys": [
          "prevent-default",
          "events"
        ]
      },
      "print": {
        "summary": "*print renders a value as text output.",
        "ai_notes": [
          "Use *print for plain text output.",
          "Prefer *print over *innerHTML or *compose when HTML output is not needed.",
          "Keep expressions readable and side-effect-free.",
          "Do not use *print for values that should be bound to attributes. Use colon attribute bindings instead.",
          "When output can be missing, handle empty or null values deliberately."
        ],
        "related_keys": [
          "textContent",
          "compose",
          "innerHTML"
        ]
      },
      "rem": {
        "summary": "*rem is for remarks or non-rendered documentation according to runtime rules.",
        "ai_notes": [
          "Use *rem for comments or notes intended for Sercrod-aware processing.",
          "Do not rely on *rem content for visible UI.",
          "Keep documentation comments close to the directive they explain.",
          "For literal visible examples, use literal handling rather than comments."
        ],
        "related_keys": [
          "literal"
        ]
      },
      "restore": {
        "summary": "*restore restores staged data from the committed snapshot.",
        "ai_notes": [
          "Use *restore with *stage to discard staged edits.",
          "Pair *restore with *apply in editable forms where cancel is needed.",
          "Use a dedicated button for restore behavior.",
          "Do not combine *restore with other control directives on the same element unless the detailed manual supports it.",
          "In form examples, distinguish restoring staged data from clearing user input or submitting data."
        ],
        "related_keys": [
          "stage",
          "apply"
        ]
      },
      "save": {
        "summary": "*save.file, *save.session, and *save.store are explicit save forms; *save remains legacy-compatible shorthand.",
        "ai_notes": [
          "Use *save.file, *save.session, or *save.store plus *keys in new examples.",
          "The *save.file value is a filename, not a data-key list.",
          "The *save.session and *save.store values are storage keys.",
          "Use *keys for selective top-level data export.",
          "Old *save=\"a b\" remains supported as legacy key selection.",
          "Do not confuse local browser persistence with server-side persistence; use *post or *api for server writes."
        ],
        "related_keys": [
          "save.file",
          "save.session",
          "save.store",
          "keys",
          "load",
          "load.file",
          "load.session",
          "load.store",
          "post",
          "api"
        ]
      },
      "stage": {
        "summary": "*stage creates a staged editing buffer separate from committed data.",
        "ai_notes": [
          "Use *stage when edits should be previewed before being committed.",
          "Use *apply to commit staged values.",
          "Use *restore to discard staged edits.",
          "Explain whether a value shown in the UI is staged or committed.",
          "Do not use *stage for ordinary simple input forms unless commit/cancel behavior is actually needed.",
          "When staged forms also have text inputs, explain whether edits are committed to the stage immediately, lazily, or only through apply/restore flow."
        ],
        "related_keys": [
          "apply",
          "restore"
        ]
      },
      "switch": {
        "summary": "*switch selects branches based on a switch value.",
        "ai_notes": [
          "Use *switch for branch sets where a single value controls which branch starts rendering.",
          "Remember that Sercrod switch behavior may include fallthrough until a break point.",
          "Use *case.break or *break for no-fallthrough behavior.",
          "Keep switch branches as direct children when required by the detailed manual.",
          "Use *default for fallback behavior."
        ],
        "related_keys": [
          "case",
          "case-break",
          "default",
          "break"
        ]
      },
      "template": {
        "summary": "*template defines or handles reusable template content according to the detailed manual.",
        "ai_notes": [
          "Use template features for reusable or delayed-rendering structures, not for ordinary visible content.",
          "Keep template boundaries explicit.",
          "Do not assume template content renders immediately unless the detailed manual says so.",
          "When template content is reused with data, make the expected scope clear.",
          "Template tooling may inspect Sercrod HTML structure, but should not assume it has a full JavaScript AST for expressions."
        ],
        "related_keys": [
          "include",
          "import"
        ]
      },
      "textContent": {
        "summary": "*textContent writes text content.",
        "ai_notes": [
          "Use *textContent for explicit textContent-style output.",
          "Prefer text output for untrusted content.",
          "Do not use HTML-writing directives when text is enough.",
          "Keep text expressions side-effect-free and readable.",
          "When comparing with *print, follow the detailed manual for exact differences."
        ],
        "related_keys": [
          "print",
          "innerHTML",
          "compose"
        ]
      },
      "unwrap": {
        "summary": "*unwrap removes or bypasses a wrapper according to runtime rules.",
        "ai_notes": [
          "Use *unwrap when the wrapper is needed for template organization but should not remain in the rendered output.",
          "Do not use *unwrap when the wrapper has semantic or accessibility meaning.",
          "Check how unwrapping affects CSS selectors, layout, and event delegation.",
          "Keep unwrapped structures simple so the final DOM remains inspectable."
        ],
        "related_keys": [
          "template",
          "include"
        ]
      },
      "updated-propagate": {
        "summary": "*updated-propagate is an old compatible alias for *update.",
        "ai_notes": [
          "Prefer *update in new templates and documentation examples.",
          "Keep *updated-propagate behavior available when reading or maintaining older templates.",
          "The value is the same literal routing spec used by *update.",
          "Do not present *updated-propagate as a *updated hook form."
        ],
        "related_keys": [
          "update",
          "updated"
        ]
      },
      "updated": {
        "summary": "*updated runs post-update handler code after a Sercrod host or element updates.",
        "ai_notes": [
          "Use *updated for post-render handler code and local integration work.",
          "Do not use *updated as target routing. Parenthesized values are now ordinary handler expressions, not selector targets.",
          "Use *update for target selection and forced host updates.",
          "Keep lifecycle side effects small and convergent."
        ],
        "related_keys": [
          "update",
          "events",
          "log"
        ]
      },
      "upload": {
        "summary": "*upload handles upload-oriented network behavior.",
        "ai_notes": [
          "Use *upload or file-oriented *api behavior when the user is sending files.",
          "Expose pending, success, and error states when available.",
          "Keep server upload response shape aligned with the template path that reads it.",
          "Do not combine upload and unrelated network directives on the same element unless the detailed manual supports it.",
          "Do not present example upload handlers as production-ready without validation, size limits, permissions, and storage controls."
        ],
        "related_keys": [
          "api",
          "download",
          "into"
        ]
      },
      "websocket": {
        "summary": "*websocket opens and manages a WebSocket connection; *into stores received messages.",
        "ai_notes": [
          "Use *websocket when the UI needs live communication rather than request/response HTTP.",
          "Use *into for receive-side data placement.",
          "Use *websocket.send plus *keys for new send examples.",
          "Keep old *ws-send and *ws-to only as compatibility syntax.",
          "For message-input flows, pair input timing with send timing so the sent value matches the visible input."
        ],
        "related_keys": [
          "websocket.send",
          "keys",
          "into",
          "ws-send",
          "ws-to"
        ]
      },
      "ws-send": {
        "summary": "*ws-send is the old compatible WebSocket send spelling.",
        "ai_notes": [
          "Prefer *websocket.send plus *keys in new examples.",
          "In old syntax, *ws-send is the payload expression and *ws-to is the target URL template.",
          "Keep examples of *ws-send in compatibility sections unless the user asks about old code."
        ],
        "related_keys": [
          "websocket.send",
          "websocket",
          "ws-to",
          "keys"
        ]
      },
      "ws-to": {
        "summary": "*ws-to is the old compatible target helper for *ws-send.",
        "ai_notes": [
          "Prefer *websocket.send plus *keys in new examples.",
          "*ws-to selects the outgoing connection in old *ws-send syntax; it is not the receive-side destination.",
          "For receive-side placement, use *into on *websocket."
        ],
        "related_keys": [
          "websocket.send",
          "websocket",
          "ws-send",
          "into"
        ]
      },
      "keyboard-events": {
        "summary": "Keyboard events describe @keydown and @keyup key-name modifiers, modifier-key combinations, .window observation, and cleanup behavior.",
        "ai_notes": [
          "Use `@keydown` as the primary documentation form for keyboard shortcuts, arrow-key movement, Enter actions, Escape actions, and modifier-key combinations.",
          "Use `@keyup` only when release timing matters, and warn that modifier-key conditions on keyup depend on which key is released first.",
          "Key names are matched case-insensitively. Prefer lowercase in examples.",
          "`ArrowUp`, `arrowup`, `ARROWUP`, and `arrowUp` should be treated as the same key after normalization.",
          "Modifier-key combinations using `+` are order-independent. `arrowup+shift` and `shift+arrowup` are equivalent.",
          "Support modifier keys `shift`, `ctrl`, `alt`, and `meta`.",
          "Do not support arbitrary non-modifier combinations such as `a+b` in the initial specification. Produce `[Sercrod warn]`.",
          "When `.window` is present, the declaration element is not the event target. It is a declaration location whose action runs in the nearest `<serc-rod>` context.",
          "A hidden input may be used as an invisible `.window` declaration location.",
          "Clean up window key registrations when the nearest `<serc-rod>` host is removed from the DOM through the Custom Element lifecycle.",
          "Treat `.prevent` and `.preventDefault` as equivalent, with `.prevent` recommended."
        ],
        "related_keys": [
          "events",
          "event-keydown",
          "event-keyup",
          "prevent",
          "prevent-default"
        ]
      },
      "shadow": {
        "summary": "*shadow defines a visible shadow template for the Shadow DOM bridge.",
        "ai_notes": [
          "Write `*shadow` on a `<template>` element.",
          "Do not write `*shadow` on the host element.",
          "Pair `*shadow=\"name\"` with `*host=\"name\"` on a host element.",
          "Use explicit connection names in examples; avoid recommending value-less host shorthand.",
          "Keep the shadow template focused on structure, style, and standard slots.",
          "Do not assume Sercrod directives inside the shadow template are processed in the initial specification.",
          "Do not describe slot behavior as Sercrod behavior. The browser handles standard slot assignment.",
          "Do not claim Light DOM children are moved into Shadow DOM.",
          "Mention that CSS inside the shadow template is scoped by standard Shadow DOM behavior.",
          "Mention that slotted content remains Light DOM content and outer page CSS applies to it normally.",
          "Warn on duplicate `*shadow` names using the Sercrod warning style, with first definition wins."
        ],
        "related_keys": [
          "host",
          "shadow-host",
          "n-host",
          "n-shadow-host"
        ]
      },
      "host": {
        "summary": "*host connects a host element to a named *shadow template.",
        "ai_notes": [
          "Use `*host=\"name\"` on the host element and `<template *shadow=\"name\">` for the shadow-side structure.",
          "Treat `*shadow-host`, `n-host`, and `n-shadow-host` as aliases of `*host`.",
          "Use `*host` as the recommended form in most examples.",
          "Value-less `*host` is allowed, but not recommended; explicit names are easier for humans and AI tools to read.",
          "When `*host` has no value, derive a constructor-style connection name from the host localName, such as `message-box` to `MessageBox`.",
          "If the matching `*shadow` template is missing, warn with `[Sercrod warn] *host requires a matching *shadow template: name`.",
          "If the host already has a shadowRoot, do not overwrite it.",
          "Use open Shadow DOM only in the initial specification.",
          "Do not treat `*host` as a replacement for `customElements.define()`.",
          "Do not explain `*host` as moving Light DOM children into Shadow DOM."
        ],
        "related_keys": [
          "shadow",
          "shadow-host",
          "n-host",
          "n-shadow-host"
        ]
      },
      "shadow-host": {
        "summary": "*shadow-host is a descriptive alias for *host.",
        "ai_notes": [
          "Treat `*shadow-host` exactly like `*host`.",
          "Prefer `*host` in most examples unless the descriptive alias improves clarity.",
          "Do not describe `*shadow-host` as a separate behavior from `*host`.",
          "Link or refer to the `host` entry for full behavior."
        ],
        "related_keys": [
          "host",
          "shadow"
        ]
      },
      "n-host": {
        "summary": "n-host is a namespace-style alias for *host.",
        "ai_notes": [
          "Treat `n-host` exactly like `*host`.",
          "Prefer `*host` in general documentation unless a namespace-style form is intentionally used.",
          "Do not describe `n-host` as a separate behavior from `*host`.",
          "Link or refer to the `host` entry for full behavior."
        ],
        "related_keys": [
          "host",
          "shadow"
        ]
      },
      "n-shadow-host": {
        "summary": "n-shadow-host is a namespace-style descriptive alias for *host.",
        "ai_notes": [
          "Treat `n-shadow-host` exactly like `*host`.",
          "Prefer `*host` in general documentation unless a namespace-style descriptive form is intentionally used.",
          "Do not describe `n-shadow-host` as a separate behavior from `*host`.",
          "Link or refer to the `host` entry for full behavior."
        ],
        "related_keys": [
          "host",
          "shadow",
          "shadow-host",
          "n-host"
        ]
      },
      "storage-backends": {
        "summary": "Browser and platform storage choices behind save/load flows.",
        "ai_notes": [
          "Use this entry when a question is about IndexedDB, OPFS, Capacitor Filesystem, or local persistence design.",
          "Use the action family documented by the detailed manual, such as *save.file, *load.file, *save.store, and *load.store; do not invent backend-named directives.",
          "Use IndexedDB by default for metadata, JSON snapshots, key-value records, source text, and indexes.",
          "Use OPFS for obvious image payloads and suspiciously large Blob/File values, with IndexedDB records holding metadata and OPFS paths.",
          "Host data should remain JSON-like and should store keys and metadata for external payloads."
        ],
        "related_keys": [
          "load",
          "save",
          "save.store",
          "load.store",
          "adapters"
        ]
      },
      "adapters": {
        "summary": "Adapters bridge Sercrod directives to external environments and storage backends.",
        "ai_notes": [
          "Adapters own external environment communication; Sercrod owns directive behavior and host data merge semantics.",
          "Return false when browser fallback should continue.",
          "For file role adapters, *save and *load are the main user-facing directives."
        ],
        "related_keys": [
          "load",
          "save",
          "storage-backends"
        ]
      },
      "save.file": {
        "summary": "*save.file saves host data or selected *keys as a JSON file.",
        "ai_notes": [
          "Prefer *save.file in new documentation and examples.",
          "Use *keys to select data; omit *keys to save the whole host data or stage.",
          "Keep old *save syntax only in compatibility notes."
        ],
        "related_keys": [
          "save",
          "keys",
          "load.file",
          "save.session",
          "save.store"
        ]
      },
      "save.session": {
        "summary": "*save.session saves host data or selected *keys into browser sessionStorage.",
        "ai_notes": [
          "The *save.session value is the sessionStorage key.",
          "Use *keys to select data; omit *keys to save the whole host data or stage.",
          "This is session-scoped browser storage, not reload-independent persistent store."
        ],
        "related_keys": [
          "save",
          "keys",
          "load.session",
          "save.file",
          "save.store"
        ]
      },
      "load.file": {
        "summary": "*load.file loads JSON from a browser-selected file and applies it with optional *keys and *into.",
        "ai_notes": [
          "Omit *keys and *into to merge the loaded JSON into the root data or stage.",
          "Use *keys without *into to copy selected top-level keys into root data or stage.",
          "Use *into to place the whole or selected loaded value under one destination key."
        ],
        "related_keys": [
          "load",
          "keys",
          "into",
          "save.file",
          "load.session",
          "load.store"
        ]
      },
      "load.session": {
        "summary": "*load.session loads JSON from browser sessionStorage and applies it with optional *keys and *into.",
        "ai_notes": [
          "The *load.session value is the sessionStorage key.",
          "Omit *keys and *into to merge the stored JSON into the root data or stage.",
          "Use *into to place the whole or selected stored value under one destination key."
        ],
        "related_keys": [
          "load",
          "keys",
          "into",
          "save.session",
          "load.file",
          "load.store"
        ]
      },
      "keys": {
        "summary": "*keys selects top-level host data keys for action directives.",
        "ai_notes": [
          "Use *keys for data selection in new save/load/WebSocket send examples.",
          "Do not overload destination/source directive values as key lists in new syntax.",
          "Keys are whitespace-separated top-level names."
        ],
        "related_keys": [
          "save.file",
          "load.file",
          "save.session",
          "load.session",
          "save.store",
          "load.store",
          "websocket.send"
        ]
      },
      "websocket.send": {
        "summary": "*websocket.send sends selected *keys through a WebSocket connection.",
        "ai_notes": [
          "*websocket.send resolves to the target WebSocket URL.",
          "*keys selects the payload from host data or stage.",
          "One key sends that key value; multiple keys send an object; no keys sends the whole host data or stage.",
          "It does not open the connection; *websocket must create or reuse it."
        ],
        "related_keys": [
          "websocket",
          "keys",
          "ws-send",
          "ws-to"
        ]
      },
      "save.store": {
        "summary": "*save.store saves host data or selected *keys into persistent browser storage backed by IndexedDB.",
        "ai_notes": [
          "The *save.store value is the persistent storage key.",
          "Use *keys to select data; omit *keys to save the whole host data or stage.",
          "This is browser-local persistent storage, not a server write and not large-blob storage."
        ],
        "related_keys": [
          "save",
          "keys",
          "load.store",
          "save.file",
          "save.session",
          "storage-backends"
        ]
      },
      "load.store": {
        "summary": "*load.store loads JSON from persistent browser storage backed by IndexedDB and applies it with optional *keys and *into.",
        "ai_notes": [
          "The *load.store value is the persistent storage key.",
          "Omit *keys and *into to merge the stored JSON into the root data or stage.",
          "Use *into to place the whole or selected stored value under one destination key.",
          "Missing keys, unavailable IndexedDB, and parse errors should warn and leave data unchanged."
        ],
        "related_keys": [
          "load",
          "keys",
          "into",
          "save.store",
          "load.file",
          "load.session",
          "storage-backends"
        ]
      },
      "update": {
        "summary": "*update routes a forced update to one target Sercrod host.",
        "ai_notes": [
          "Use *update when the value is a literal target spec such as root, a numeric depth, or a selector.",
          "Do not treat *update values as JavaScript expressions.",
          "Use *updated for post-render handler execution.",
          "*updated-propagate is an old compatible alias for *update."
        ],
        "related_keys": [
          "updated",
          "updated-propagate"
        ]
      }
    }
  },
  "__ai_improvement_coverage": {
    "title": "Coverage of discussion-driven improvements",
    "summary": "Checklist of improvements added during documentation work. This block is intended to help AI assistants verify that the detailed manual, runtime guide, and entry notes cover the agreed Sercrod documentation concerns.",
    "items": [
      {
        "id": "directive-catalog-classification",
        "title": "Directive catalog classification",
        "coverage": "Sercrod directives are grouped conceptually so AI can distinguish control, data/model, output, network/API, lifecycle, hooks, storage, WebSocket, and utility behavior.",
        "related_blocks": [
          "__ai_policy",
          "__ai_runtime_guide",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "if",
          "for",
          "each",
          "switch",
          "case",
          "default",
          "input",
          "fetch",
          "api",
          "post",
          "upload",
          "download",
          "websocket",
          "updated"
        ]
      },
      {
        "id": "how-it-works-runtime-overview",
        "title": "How-it-works runtime overview",
        "coverage": "AI should understand Sercrod as an attribute-first runtime whose behavior is readable from templates, data paths, events, request directives, expression scope, hooks, and server contracts.",
        "related_blocks": [
          "__ai_runtime_guide"
        ],
        "related_entries": [
          "attributes",
          "input",
          "print",
          "api",
          "fetch",
          "events"
        ]
      },
      {
        "id": "input-lazy-eager-update-timing",
        "title": "Input, lazy, and eager update timing",
        "coverage": "Normal input commit, lazy type-then-action behavior, eager live-update behavior, and form click timing are explicitly documented for AI interpretation.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "input",
          "lazy",
          "eager"
        ]
      },
      {
        "id": "fetch-api-trigger-timing",
        "title": "Fetch and API trigger timing",
        "coverage": "Request timing depends on whether the directive is attached to a clickable or non-clickable element. Fetch is simple JSON loading; API is the general HTTP primitive.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "fetch",
          "api",
          "post",
          "upload",
          "download",
          "into"
        ]
      },
      {
        "id": "clickable-elements-and-action-timing",
        "title": "Clickable elements and action timing",
        "coverage": "AI notes identify button-like elements, link cautions, download link exclusion, and action-only use of button.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "api",
          "fetch",
          "post",
          "prevent-default",
          "prevent"
        ]
      },
      {
        "id": "event-attributes-and-non-mutating",
        "title": "Event attributes and non_mutating behavior",
        "coverage": "Event prefix, high-frequency events, non_mutating meaning, and drop-as-final-action distinction are captured for AI generation and debugging.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "events",
          "event-click",
          "event-input",
          "event-submit",
          "event-keydown",
          "event-keyup",
          "prevent-default",
          "updated"
        ]
      },
      {
        "id": "for-each-distinction",
        "title": "*for and *each distinction",
        "coverage": "*for repeats the element itself; *each keeps the container and repeats its children. Include/import placement cautions are included.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "for",
          "each",
          "include",
          "import"
        ]
      },
      {
        "id": "sercrod-managed-expression-scope",
        "title": "Sercrod-managed expression scope",
        "coverage": "Sercrod expressions are documented as runtime-managed expressions, not strict JavaScript module scope. Data paths, loop locals, events, element references, and parent/root data are included.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "__data",
          "let",
          "global",
          "methods",
          "for",
          "each",
          "events"
        ]
      },
      {
        "id": "methods-scope-functions",
        "title": "Methods and scope functions",
        "coverage": "*methods functions are distinguished from external functions, including this-binding cautions.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "methods"
        ]
      },
      {
        "id": "template-ast-hooks",
        "title": "Template AST hooks",
        "coverage": "AST hooks are described as lightweight HTML-oriented template structure hooks for tools, not JavaScript AST parsing or runtime replacement.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "template",
          "include",
          "import"
        ]
      },
      {
        "id": "frontend-server-response-contract",
        "title": "Frontend/server response contract",
        "coverage": "Response destinations such as *into and URL:prop must match template read paths. Stable JSON shapes are recommended for tests, docs, and AI tools.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "api",
          "fetch",
          "post",
          "into",
          "download",
          "upload"
        ]
      },
      {
        "id": "debug-runtime-inspection-hooks",
        "title": "Debug and runtime inspection hooks",
        "coverage": "Runtime inspection guidance covers sercrod-change, data-vs-DOM comparison, update hooks, and separating input, data, action, request, and rendering causes.",
        "related_blocks": [
          "__ai_runtime_guide",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "events",
          "updated",
          "updated-propagate",
          "log"
        ]
      },
      {
        "id": "man-json-role",
        "title": "man.json role",
        "coverage": "man.json is positioned as a shared manual for humans, AI assistants, documentation tools, and runtime inspection tools. Detailed entries remain behavioral source of truth; AI entries are interpretation guidance.",
        "related_blocks": [
          "__meta",
          "__ai_policy",
          "__ai_runtime_guide",
          "__ai_entry_notes"
        ],
        "related_entries": []
      },
      {
        "id": "keyboard-events-window-actions",
        "title": "Keyboard events and window actions",
        "coverage": "Keyboard actions cover keydown-first shortcut guidance, keyup release timing, key-name modifiers, modifier-key combinations, .window declarations, hidden declaration elements, prevent/preventDefault aliases, and Custom Element lifecycle cleanup.",
        "related_blocks": [
          "__ai_policy",
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "keyboard-events",
          "event-keydown",
          "event-keyup",
          "events",
          "prevent",
          "prevent-default"
        ]
      },
      {
        "id": "shadow-dom-bridge",
        "title": "Shadow DOM bridge",
        "coverage": "The manual covers *shadow, *host, host aliases, visible shadow templates, standard slot assignment, Light DOM directive placement, CSS scope, slotted-content cautions, duplicate templates, missing templates, existing shadowRoot behavior, and customElements.define() cautions.",
        "related_blocks": [
          "__ai_policy",
          "__ai_runtime_guide",
          "__ai_confusion_guards",
          "__ai_entry_notes"
        ],
        "related_entries": [
          "shadow",
          "host",
          "shadow-host",
          "n-host",
          "n-shadow-host"
        ]
      }
    ]
  },
  "__data": "### Data and *let\n\n#### Summary\n\nThis reference explains in detail how `*let` interacts with Sercrod's data:\n\n- How `*let` creates new variables.\n- When existing data is left unchanged.\n- How nested assignments behave.\n- What happens inside loops.\n- How `*let` differs from `*global` in terms of side effects.\n\nIt focuses on the actual behavior of the runtime, pattern by pattern, so that you can predict when Sercrod's host data (`data` on `<serc-rod>`) is modified and when it is not.\n\n\n### 1. Data model recap\n\nBefore looking at patterns, it helps to separate three layers:\n\n1. **Host data (`_data`)**\n\n   - Each `<serc-rod>` keeps a data object (from its `data=\"...\"` or `data='{...}'` attribute).\n   - This is the base object used to build scopes.\n   - When we say \"host data changes\", we mean this object is mutated or receives new properties.\n\n2. **Effective scope (`effScope`)**\n\n   - A plain object that represents \"what expressions see\" at a given element.\n   - It starts from the host data.\n   - Sercrod adds extra entries (loop variables like `item`, `index` and so on).\n   - For each element, `effScope` may be replaced or extended (for example by `*let`).\n\n3. **Local *let scope (`letScope`)**\n\n   - When an element has `*let`, Sercrod builds\n\n     - `letScope = Object.assign(Object.create(effScope), effScope)`\n\n   - The `*let` code runs against this `letScope` via a sandbox.\n   - After execution:\n\n     - `effScope` for this element and its children becomes `letScope`.\n     - New names from `letScope` may be copied (\"promoted\") into the host data.\n\nPromotion rule (current implementation):\n\n- After `*let` finishes, Sercrod checks each property name in `letScope`.\n- For every key `k`:\n\n  - If `k` is already in the host data, it is not overwritten.\n  - If `k` is not in the host data, `this._data[k] = letScope[k]` is assigned.\n\nThis rule is the core of how `*let` affects data.\n\n\n### 2. Pattern reference\n\nThis section lists concrete patterns: initial data, `*let` code, and what happens to both the local scope and the host data.\n\n\n#### 2.1 Pattern A: New top-level variables\n\nCase A1: Create a new helper from existing data.\n\n```html\n<serc-rod id=\"invoice\" data='{\"price\": 1200, \"qty\": 3}'>\n  <p *let=\"total = price * qty\">\n    <span *print=\"total\"></span>\n  </p>\n</serc-rod>\n```\n\n- Initial host data:\n\n  ```json\n  { \"price\": 1200, \"qty\": 3 }\n  ```\n\n- `*let` execution:\n\n  - `total` does not exist yet in `effScope` or host data.\n  - The sandbox writes `total` into `letScope`.\n\n- Promotion:\n\n  - After `*let`, the runtime sees `total` in `letScope`.\n  - `total` is not in the host data, so host data becomes:\n\n    ```json\n    { \"price\": 1200, \"qty\": 3, \"total\": 3600 }\n    ```\n\n- Visibility:\n\n  - Inside the `<p>` and its children, `total` is available via `effScope` (which is `letScope`).\n  - Sibling elements of `<p>` can use `total` as a normal data field as well.\n\n\nCase A2: Create multiple new names at once.\n\n```html\n<serc-rod data='{\"a\": 2, \"b\": 3}'>\n  <p *let=\"\n    sum = a + b;\n    diff = a - b;\n  \">\n    <span *print=\"sum\"></span>\n    <span *print=\"diff\"></span>\n  </p>\n</serc-rod>\n```\n\n- New names: `sum`, `diff`.\n- Data after first render:\n\n  ```json\n  { \"a\": 2, \"b\": 3, \"sum\": 5, \"diff\": -1 }\n  ```\n\n- On subsequent renders, `sum` and `diff` are already in host data, so only their local values in `letScope` are updated. The host data entries stay at the first values unless something else updates them.\n\n\n#### 2.2 Pattern B: Overwriting existing top-level properties\n\nCase B1: Reassign a field that already exists in data.\n\n```html\n<serc-rod id=\"priceBox\" data='{\"price\": 100}'>\n  <p *let=\"price = price * 1.1\">\n    <span *print=\"price\"></span>\n  </p>\n  <p>\n    Original price: <span *print=\"$data.price\"></span>\n  </p>\n</serc-rod>\n```\n\n- Initial host data:\n\n  ```json\n  { \"price\": 100 }\n  ```\n\n- `*let` execution on `<p>`:\n\n  - Before `*let`, `effScope.price` comes from host data (100).\n  - `letScope` is created as a shallow copy of `effScope`.\n  - The sandbox runs `price = price * 1.1`:\n\n    - Reads `price` from `letScope` (100).\n    - Writes `price` (110) back into `letScope`.\n\n- Promotion:\n\n  - When promoting, the runtime sees `price` in `letScope`.\n  - Since `price` already exists in the host data, it is not overwritten.\n\n- Results:\n\n  - Inside the first `<p>` (which uses `letScope` as `effScope`), `price` is 110.\n  - In the host data and for other elements (such as the second `<p>`), `price` remains 100.\n  - So `*let` can shadow existing values for the current element and its subtree without changing the original data.\n\n\nCase B2: Incrementing an existing field with `+=`\n\n```html\n<serc-rod id=\"counter\" data='{\"count\": 0}'>\n  <p *let=\"count += 1\">\n    <span *print=\"count\"></span>\n  </p>\n</serc-rod>\n```\n\n- First render:\n\n  - `effScope.count` is 0 from host data.\n  - `letScope.count` becomes 1.\n  - Promotion sees `count` already in data, so it does not overwrite it.\n  - Host data remains `{ \"count\": 0 }`.\n  - Inside `<p>`, `count` is 1 (from `letScope`).\n\n- Second render:\n\n  - Host data still has `count: 0`, so the same process repeats.\n  - `letScope.count` becomes 1 again.\n  - Host data remains 0.\n\nConsequence:\n\n- `*let` is not suitable for permanent accumulation on existing top-level fields using `+=`.\n- The local value changes per render, but the host data does not track those changes.\n- If you need to truly update host data, prefer `*global` or update data outside templates.\n\n\n#### 2.3 Pattern C: Creating new nested objects\n\nCase C1: Creating a nested object from scratch.\n\n```html\n<serc-rod id=\"userBox\" data='{}'>\n  <p *let=\"user.name = 'Ann'\">\n    <span *print=\"user.name\"></span>\n  </p>\n</serc-rod>\n```\n\n- Initial host data:\n\n  ```json\n  { }\n  ```\n\n- `*let` execution:\n\n  - `user` does not exist in the scope.\n  - When `user` is first read, the sandbox returns an internal \"hole\" object.\n  - When `user.name = 'Ann'` is executed:\n\n    - The \"hole\" captures the intended path `[\"user\",\"name\"]`.\n    - Sercrod calls an internal helper that ensures `letScope.user` is an object, then sets `user.name` to `\"Ann\"`.\n\n- Promotion:\n\n  - Host data initially lacks `user`.\n  - Promotion copies `user` from `letScope` into host data.\n\n- Resulting data:\n\n  ```json\n  { \"user\": { \"name\": \"Ann\" } }\n  ```\n\nThis is the standard way `*let` creates nested objects for you when you assign to a previously unknown path.\n\n\n#### 2.4 Pattern D: Updating existing nested structures\n\nCase D1: Updating a nested field on an existing object.\n\n```html\n<serc-rod id=\"profile\" data='{\"user\": { \"name\": \"Ann\", \"age\": 30 }}'>\n  <p *let=\"user.name = 'Bob'\">\n    <span *print=\"user.name\"></span>\n  </p>\n  <p>\n    Outside: <span *print=\"$data.user.name\"></span>\n  </p>\n</serc-rod>\n```\n\n- Initial host data:\n\n  ```json\n  { \"user\": { \"name\": \"Ann\", \"age\": 30 } }\n  ```\n\n- `*let` execution:\n\n  - `effScope.user` refers directly to `data.user` object.\n  - `letScope.user` is a shallow copy of the reference, so it points to the same object.\n  - When `user.name = 'Bob'` runs, it mutates that shared object in place.\n\n- Promotion:\n\n  - `user` already exists in host data.\n  - No new top-level names are added, and promotion does not overwrite `user`.\n  - However, the nested object has already been mutated; host data now has `user.name: \"Bob\"`.\n\n- Result:\n\n  - Both inside the `<p>` and wherever `user` is read later, `user.name` is `\"Bob\"`.\n  - This is an example where `*let` has a global effect through shared references, even though it is \"meant\" to be local.\n\nKey point:\n\n- Assigning to nested properties of existing data (for example `user.name`, `settings.theme`) mutates the underlying object and therefore affects the host data globally.\n\n\n#### 2.5 Pattern E: Unknown variables with operators like `+=`\n\nCase E1: Incrementing a variable that does not exist yet.\n\n```html\n<serc-rod id=\"box\" data='{}'>\n  <p *let=\"count += 1\">\n    <span *print=\"count\"></span>\n  </p>\n</serc-rod>\n```\n\n- First render:\n\n  - `count` is not in scope, so reading it returns a special placeholder (hole).\n  - `count += 1` uses that placeholder:\n\n    - It behaves as if the previous value was `0`.\n    - The result becomes `1`, which is then written into the local scope.\n\n  - After `*let`:\n\n    - `letScope.count` is `1`.\n    - Host data does not yet have `count`, so promotion adds it.\n\n- Data after first render:\n\n  ```json\n  { \"count\": 1 }\n  ```\n\n- Second render:\n\n  - Now `count` exists in host data (value 1).\n  - The situation becomes identical to Pattern B2:\n\n    - `letScope.count` becomes `2` inside `<p>`.\n    - Host data remains `1`.\n\nConsequences:\n\n- For the **first** render, an unknown name used with `+=` behaves like a fresh counter.\n- From the **second** render onward, `count` is considered an existing data field, so the `+=` happens only in the local `*let` scope without updating host data.\n- Do not rely on `*let` for long-lived counters; treat this as an advanced detail and use other mechanisms for stateful counters.\n\n\n#### 2.6 Pattern F: Inside loops (*for / *each)\n\n`*let` inside loops is evaluated for each iteration. The scope for each iteration is a plain object that includes:\n\n- The host data.\n- Loop variables (such as `item`, `index`, `key`, `value`).\n- Any earlier `*let` values on ancestors.\n\nExample: `*let` inside `*each`.\n\n```html\n<serc-rod id=\"list\" data='{\"items\":[{\"name\":\"A\"},{\"name\":\"B\"}]}'>\n  <ul *each=\"item of items\">\n    <li *let=\"label = item.name + '!'\">\n      <span *print=\"label\"></span>\n    </li>\n  </ul>\n</serc-rod>\n```\n\nPer iteration:\n\n- `effScope` includes `items` and `item`.\n- `letScope` is built from that `effScope`.\n- `label` is created and used by this `<li>` and its children.\n- Post-`*let`, `label` is also added to host data (if it did not exist yet), because it is a new name.\n- `item` is a loop variable:\n\n  - It is part of `effScope`, so `letScope` sees it as well.\n  - During promotion, implementation walks through all properties of `letScope`.\n  - If `item` is not in host data yet, it may also be added as a new property of the host data.\n  - Subsequent renders will not keep this top-level `item` in sync with the loop; it is essentially a snapshot.\n\nPractical guidance:\n\n- It is safe and common to use `*let` to create helper variables from loop variables (for example `label = item.name + '!'`).\n- Do not rely on promotion of loop variables like `item`, `index`, `key`, `value` into host data; treat that as an internal detail.\n- If you want stable top-level fields derived from loops, compute them outside the template or use a dedicated `*let` on a parent element that runs once.\n\n\n#### 2.7 Pattern G: Using $parent inside *let\n\nInside `*let`, Sercrod injects `$parent` as a non-enumerable property:\n\n- `$parent` refers to the data of the nearest ancestor `<serc-rod>`.\n- Because it is non-enumerable, it is not copied into host data during promotion.\n- However, the object behind `$parent` is shared and can be mutated.\n\nExample:\n\n```html\n<serc-rod id=\"root\" data='{\"currency\":\"JPY\"}'>\n  <serc-rod id=\"child\" data='{\"price\": 500}'>\n    <button *let=\"$parent.total = ($parent.total || 0) + price\">\n      Add to total\n    </button>\n    <p>Total: <span *print=\"$parent.total\"></span> {{currency}}</p>\n  </serc-rod>\n</serc-rod>\n```\n\n- `*let` runs in the child, but `\"$parent\"` points to the root host's data.\n- The line\n\n  - `$parent.total = ($parent.total || 0) + price`\n\n  mutates the parent data directly.\n\n- Since `$parent` is non-enumerable:\n\n  - Promotion does not create `$parent` as a data key.\n  - Only `total` inside the parent's data changes.\n\nThis is an advanced pattern for updating parent data from a nested component via `*let`.\n\n\n### 3. Comparison with *global\n\n`*let` and `*global` both execute arbitrary JavaScript, but they target different kinds of side effects.\n\n- `*let` (this document):\n\n  - Writes into a local scope first.\n  - After execution, new names are copied into host data if they did not exist.\n  - Existing host data fields are not overwritten by top-level assignments.\n  - Nested assignments can mutate existing objects through shared references.\n  - It always schedules a re-render after execution.\n\n- `*global`:\n\n  - Uses a different sandbox.\n  - For simple assignments:\n\n    - If a name exists in host data, write into host data.\n    - Otherwise, write into `globalThis`.\n\n  - For nested assignments, it uses a scoped resolver that prefers host data when possible.\n  - It is intended for explicit, \"I know I am changing global or host state\" updates.\n\nRule of thumb:\n\n- Use `*let` for local derived values and light transformations, with occasional controlled promotion of new helper names into data.\n- Use `*global` (or external code) when you truly want to update host data fields as part of application logic.\n\n\n### 4. Cheat sheet\n\n- New simple variable (`x = expr`) where `x` does not exist in data:\n\n  - `*let` creates `x` in the local scope.\n  - After promotion, host data gains a new field `x`.\n\n- Reassign existing variable (`x = expr`) where `x` already exists in data:\n\n  - `*let` updates `x` only in the local scope.\n  - Host data keeps the original `x`.\n\n- New nested path (`user.name = 'Ann'`) where `user` does not exist:\n\n  - `*let` creates a nested object under `user` in the local scope.\n  - Promotion adds `user` into host data.\n\n- Nested update on existing object (`user.name = 'Bob'` where `user` exists):\n\n  - The underlying object is mutated in place.\n  - Host data reflects the new nested value everywhere.\n\n- Unknown name with `+=`:\n\n  - First render acts like a fresh counter and adds a new field.\n  - Later renders only update the local `*let` scope; host data stays at the first value.\n\n- Inside loops:\n\n  - `*let` per iteration can create helper variables for that iteration.\n  - New helper names can be promoted to host data; loop variables may leak into data but are not kept in sync.\n\n- With `$parent`:\n\n  - `*let` can mutate ancestor data through shared references.\n  - `$parent` itself is not promoted into host data.\n\nUnderstanding these patterns makes it much easier to predict when `*let` is local, when it behaves like a helper for derived values, and when it effectively mutates your data structures in a global way.\n",
  "__debug": "### Sercrod debug hook\n\n#### Summary\n\nSercrod exposes the `sercrod-change` event as a runtime inspection hook.\n\nUse this hook when debugging Sercrod pages and when comparing data changes with the rendered DOM.\n\nThe event is dispatched when an observed data property is changed through the Sercrod Proxy layer.\n\nThis helps identify whether a problem is caused by input handling, data updates, action timing, request timing, or DOM rendering.\n\n#### Event detail\n\nThe event detail contains:\n\n- `host` - the Sercrod element that owns the data.\n- `parent` - the object whose property was changed.\n- `key` - the changed property name.\n- `old` - the previous value.\n- `new` - the new value.\n\n#### Example\n\n```js\ndocument.addEventListener(\"sercrod-change\", (event)=>{\n\tconst detail = event.detail;\n\n\tconsole.log(\"[Sercrod change]\", {\n\t\thost: detail.host,\n\t\tparent: detail.parent,\n\t\tkey: detail.key,\n\t\told: detail.old,\n\t\tnew: detail.new\n\t});\n});\n```\n\n#### Usage with *man\n\nThe built-in alias `debug` maps to the internal manual key `__debug`.\n\n```html\n<pre *man=\"debug\"></pre>\n```\n\n#### Notes\n\n- This hook is for inspection and debugging.\n- Do not use it as ordinary application logic unless the project explicitly chooses that pattern.\n- When debugging, compare this event with DOM inspection and request logs.\n- Separate input handling, data update timing, action timing, request timing, and rendering before changing the template.\n",
  "api": "### *api / n-api\n\n#### Summary\n\n`*api` / `n-api` is the low-level HTTP gateway directive in Sercrod.\n\nIt is responsible for:\n\n- Building an HTTP request from the current scope (URL, method, optional JSON body).\n- Sending the request via `fetch` (or `FormData` upload for file inputs).\n- Updating shared status flags on the host:\n  - `$pending` - whether a request is in flight.\n  - `$error` - last error object for this host.\n  - `$download` - last value from GET-like requests.\n  - `$upload` - last value from non-GET or file uploads.\n- Optionally writing the response into a named data slot via `*into` / `n-into`.\n- Dispatching events (`sercrod-api`, `sercrod-error`) for external observers.\n- Automatically firing once for non-clickable elements, with deduplication.\n\nHigher level helpers such as `*download` or `*upload` share the same `$pending` / `$error` / `$download` / `$upload` convention, but `*api` is the single general-purpose primitive for ad-hoc HTTP calls on normal elements.\n\n\n#### Basic example\n\nA simple GET that populates `user` and exposes status to children:\n\n```html\n<serc-rod id=\"app\" data='{\"user\": null}'>\n  <section\n    *api=\"/api/user.json\"\n    *into=\"user\">\n\n    <p *if=\"$pending\">Loading user...</p>\n\n    <p *if=\"$error\" class=\"error\">\n      <span *print=\"$error.message\"></span>\n    </p>\n\n    <pre *if=\"user\" *print=\"JSON.stringify(user, null, 2)\"></pre>\n  </section>\n</serc-rod>\n```\n\nKey points in this example:\n\n- The `section` element owns the HTTP call through `*api`.\n- The response is written as-is to `user` (because of `*into=\"user\"`).\n- `$pending` and `$error` are shared on the host and can be read from any child.\n- `user` starts as `null` but is explicitly initialized by `*api` if missing.\n\n\n#### Behavior\n\n##### Core behavior\n\nWhen Sercrod finds `*api` or `n-api` on an element during rendering:\n\n1. It clones the element (shallow clone, without children).\n2. It reads the relevant attributes:\n   - `*api` / `n-api` - URL template string.\n   - `method` - HTTP method, default `\"GET\"`.\n   - `body` or `payload` - expression for request body (non-GET only).\n   - `*into` / `n-into` - optional destination key in the host data.\n3. It detects file uploads:\n\n   - `isFile` is `true` when the cloned element is `<input type=\"file\">`.\n\n4. It initializes status fields on `this._data` if they are missing:\n   - `$pending` - `false`\n   - `$error` - `null`\n   - `$download` - `null`\n   - `$upload` - `null`\n   - `into` key - when provided and not present, it is created and set to `null`.\n5. It appends the clone to the parent.\n6. It wires up request logic depending on the element type:\n   - `<input type=\"file\">` - prepare upload on `change`.\n   - Button-like elements - trigger JSON-like request on `click`.\n   - Other elements - schedule a one-shot automatic request.\n7. It renders the original children into the clone with the current scope, so that children can read `$pending`, `$error`, `$download`, `$upload`, and the `*into` variable.\n\n\n#### Request URL and placeholders\n\n##### URL source\n\nThe URL template is taken exactly from:\n\n- `*api` attribute, or\n- `n-api` attribute, when `*api` is not present.\n\nThe raw string is passed to an internal helper `_expand_text(urlRaw, scope, work)`, which performs placeholder expansion based on the global delimiters.\n\nBy default, the delimiters are:\n\n- `start: \"%\"`\n- `end: \"%\"`\n\nSo a URL such as:\n\n```html\n<section\n  *api=\"/api/users/%userId%?ts=%Date.now()%\"\n  *into=\"user\">\n</section>\n```\n\nis turned into a concrete URL by evaluating each expression between the delimiters in the current scope, then substituting the result.\n\nNotes:\n\n- If the expression throws, the placeholder is replaced with an empty string by the placeholder filter.\n- Placeholder expansion is performed at each request, not just once, so dynamic values (for example timestamps or tokens) are evaluated at call time.\n\n\n#### HTTP method and body\n\n##### Method\n\nThe HTTP method is read from the `method` attribute on the same element, then uppercased:\n\n- `method=\"GET\"` (default when omitted).\n- `method=\"POST\"`, `method=\"PUT\"`, `method=\"PATCH\"`, and so on - passed through unchanged.\n\nFor `<input type=\"file\" *api>`, GET is automatically converted to POST during the upload, but the original `method` string is still used in the non-file path and in event payloads for JSON-like requests.\n\n##### JSON body (non-file elements)\n\nFor non-file elements, `*api` can optionally send a JSON body when the method is not GET.\n\nThe body expression is taken from:\n\n- `body` attribute, or, if missing,\n- `payload` attribute.\n\nWhen `bodyExp` is non-empty and `method !== \"GET\"`:\n\n1. Sercrod evaluates the expression with `eval_expr(bodyExp, scope, { el: work, mode: \"body\" })`.\n2. If evaluation succeeds and the result is not `null` or `undefined`, it builds:\n   - `headers: { \"Content-Type\": \"application/json\" }`\n   - `body: JSON.stringify(value)`\n3. If evaluation of the body expression throws, the error is ignored, and the request is sent without a body.\n\nFor `method === \"GET\"`:\n\n- The body expression is ignored.\n- No request body is sent.\n\n##### Parsing the response\n\nAfter the request completes:\n\n- Sercrod inspects the response `Content-Type` header (lowercased).\n- If it contains `application/json`:\n  - It calls `res.json()` and uses the resulting value.\n- Otherwise:\n  - It calls `res.text()`.\n  - It then attempts `JSON.parse(text)`.\n    - On success, the parsed value is used.\n    - On failure, the original text string is kept.\n\nArrays, objects, and primitive values are all stored as-is.\n\n\n#### File uploads with `<input type=\"file\" *api>`\n\nWhen `*api` is placed on an `<input type=\"file\">`:\n\n```html\n<serc-rod id=\"uploader\">\n  <input\n    type=\"file\"\n    name=\"files[]\"\n    *api=\"/upload\">\n</serc-rod>\n```\n\nthe behavior is:\n\n- `isFile` is detected from the element type.\n- The element gets a `change` listener.\n- On `change`:\n  - Sercrod collects all chosen files into a `FormData` instance.\n  - The form field name is:\n    - `name` attribute value if present, otherwise\n    - `\"files[]\"`.\n  - The HTTP method is either:\n    - `method` attribute value if provided and not `\"GET\"`, or\n    - `\"POST\"` when `method` is `\"GET\"` or omitted.\n  - The request is sent using `fetch(url, { method, body: formData })`.\n\nThe response parsing and placement are the same as for JSON-like requests, with these differences:\n\n- `$upload` is always updated with the response value.\n- `sercrod-api` is dispatched with `method: \"POST(FORMDATA)\"`.\n- On error:\n  - `$error` is set to `{ code: \"UPLOAD\", message: String(err) }`.\n  - `sercrod-error` is dispatched with `detail: { url, method: \"POST(FORMDATA)\", into, error: String(err) }`.\n\nThere is no automatic request on page load for file inputs. Uploads only happen when the user selects files.\n\n\n#### Shared state flags and *into\n\n##### Status flags\n\n`*api` ensures that the following properties exist on the host data object:\n\n- `$pending` - `boolean`\n  - `true` while a request is in flight.\n  - `false` once the request finishes (success or error).\n- `$error` - `null` or `{ code, message }`\n  - `null` before the first request and after each successful request.\n  - An error object when a request throws (network error, fetch error, JSON failure, and so on).\n- `$download` - `any`\n  - Last value from GET-like JSON requests.\n  - Shared across all `*api` and related network helpers on the same host.\n- `$upload` - `any`\n  - Last value from non-GET JSON requests or file uploads.\n  - Shared across the host.\n\nThese flags are created even when `*into` is not set, so you can always:\n\n- show a spinner while `$pending` is true.\n- show an error area when `$error` is non-null.\n- inspect `$download` or `$upload` directly for diagnostics.\n\n##### `*into` / `n-into`\n\n`*into` and `n-into` allow you to additionally store the response into a named data property:\n\n- The attribute value is treated as a plain string key.\n- It is not evaluated as an expression.\n- When the key is non-empty and not yet present, Sercrod initializes it with `null`.\n- On success, `this._data[into]` is set to the final response value.\n\nExample:\n\n```html\n<serc-rod id=\"app\" data='{\"profile\": null, \"logs\": []}'>\n  <section\n    *api=\"/api/profile\"\n    *into=\"profile\">\n\n    <p *if=\"$pending\">Loading profile...</p>\n    <p *if=\"$error\" *print=\"$error.message\"></p>\n\n    <h2 *if=\"profile\" *print=\"profile.name\"></h2>\n  </section>\n</serc-rod>\n```\n\nImportant notes:\n\n- Because the key is literal, `*into=\"user\"` always writes to `data.user`, not to a dynamic path.\n- If you reuse the same `*into` key in several `*api` blocks on the same host, the last successful request overwrites the previous value.\n- Internally, Sercrod also tracks `*into` names in an internal `_intos` list, used by other features (for example update hooks). This is an implementation detail, but explains why `*into` is cheap to use even when you do not immediately read the value.\n\n\n#### Events\n\n`*api` dispatches two CustomEvents on the element that owns the directive.\n\n##### `sercrod-api`\n\nDispatched after a successful request, with `detail` containing:\n\n- For JSON-like (non-file) requests:\n  - `url` - resolved URL at call time.\n  - `method` - the HTTP method (for example `\"GET\"`, `\"POST\"`).\n  - `status` - numeric HTTP status code.\n  - `into` - resolved `*into` key or empty string.\n  - `value` - parsed response value (JSON, parsed text, or raw text).\n\n- For file uploads:\n  - `url` - resolved URL at call time.\n  - `method` - the fixed string `\"POST(FORMDATA)\"`.\n  - `status` - numeric HTTP status code.\n  - `into` - resolved `*into` key or empty string.\n  - `value` - parsed response value.\n\nThe event is configured with:\n\n- `bubbles: true`\n- `composed: true`\n\nso ancestor elements and outer frameworks can listen for it.\n\n##### `sercrod-error`\n\nDispatched when the request, parsing, or internal processing throws. Details differ slightly:\n\n- For JSON-like (non-file) requests:\n  - `$error` is set to `{ code: \"API\", message: String(err) }`.\n  - The event detail is `{ url, method, into, error: String(err) }`.\n\n- For file uploads:\n  - `$error` is set to `{ code: \"UPLOAD\", message: String(err) }`.\n  - The event detail is `{ url, method: \"POST(FORMDATA)\", into, error: String(err) }`.\n\n\n#### Evaluation timing and scope\n\n##### Scope used for URL and body\n\nInside `_renderElement(node, scope, parent)`, Sercrod maintains:\n\n- `scope` - the scope object passed into this element from its parent (already includes ancestor `*let` effects and loop variables).\n- `effScope` - the effective scope that may be further modified by `*let` on this element before children are rendered.\n\nFor `*api`:\n\n- URL placeholders and body expressions are evaluated with `scope`, not `effScope`.\n\nThis means:\n\n- Ancestor `*let` expressions are visible inside `*api`, because parents call `renderNode(child, effScope, parent)` with their updated effective scope.\n- Loop variables (`*for`, `*each`) are visible, because they are part of the incoming `scope`.\n- Additional variables introduced by `*let` on the same element are not visible to `*api` in the current implementation. They are only visible to children that are rendered after `*api`.\n\nIf you need to prepare derived values for `*api`, prefer one of these patterns:\n\n- Define them in the host `data` up front.\n- Use `*let` on an ancestor wrapper element.\n- Compute them inside the body expression by referring directly to the base data.\n\n##### Child rendering order\n\nThe order around `*api` is:\n\n1. Status fields (`$pending`, `$error`, `$download`, `$upload`, and `into` key) are created if missing.\n2. The host clone is appended to the DOM.\n3. Event handlers and auto-run logic are registered.\n4. Children are rendered into the clone, using the current effective scope.\n\nThis guarantees that:\n\n- Children can safely read `$pending`, `$error`, `$download`, `$upload`, and the `*into` variable even before the first request has completed.\n- `$pending` begins as `false` and is only toggled to `true` once the actual request starts.\n\n\n#### Execution model and triggers\n\n##### Non-file elements\n\nFor non-file elements, `*api` chooses the trigger based on the element type:\n\n- Clickable elements:\n  - `<button>`\n  - `<a>` without a `download` attribute\n  - `<input type=\"button\">`, `<input type=\"submit\">`, `<input type=\"reset\">`\n\n  These get a `click` listener that calls the JSON-like request function.\n\n  - Each click sends a new request.\n  - There is no deduplication for manual clicks.\n\n- Non-clickable elements:\n  - Any element that is not considered clickable and not a file input (for example `section`, `div`, `span`, and so on).\n\n  These get an automatic one-time request after the next animation frame.\n\n##### Deduplication key for auto-run\n\nFor non-clickable, non-file elements, `*api` builds an automatic deduplication key and uses an internal `__apiOnce` set:\n\n- Sercrod computes a body hash once at render time:\n  - It evaluates the body expression in the same way as `runJsonLike`.\n  - It then `JSON.stringify`s the result.\n  - On any error, the hash is an empty string.\n- It defines `resolveUrl()` that expands placeholders using `_expand_text`.\n- It then builds a URL-only string for deduplication:\n  - It tries `new URL(resolveUrl(), location.href)`.\n  - If that succeeds:\n    - It removes the `ts` query parameter, if present.\n    - It uses `pathname` plus the remaining query string.\n  - If creating a `URL` fails:\n    - It falls back to a simple string replace that strips `ts` and trailing `?` or `&`.\n\nThe final once-key is:\n\n- `method + \" \" + dedupPathAndQueryWithoutTs + \" :: \" + into + \" :: \" + bodyHash`\n\nAt render time:\n\n- If `__apiOnce` does not contain the key:\n  - The key is added to `__apiOnce`.\n  - `requestAnimationFrame(runJsonLike)` is scheduled to execute once.\n- If `__apiOnce` already contains the key:\n  - No automatic request is scheduled.\n\nAs a result:\n\n- Changing the URL (except for the `ts` parameter) causes a new automatic request.\n- Changing the HTTP method causes a new automatic request.\n- Changing the literal `*into` key causes a new automatic request.\n- Changing the body expression result causes a new automatic request.\n- Re-rendering the same element with the same URL (ignoring `ts`), method, `into`, and body result produces no additional automatic requests.\n\n##### File inputs\n\nFor `<input type=\"file\" *api>`, there is:\n\n- No auto-run.\n- No deduplication based on `__apiOnce`.\n- Each `change` event sends an upload request with the current selection.\n\n\n#### Use with conditionals and loops\n\n##### Showing loading and errors\n\nA typical pattern is:\n\n```html\n<serc-rod id=\"users\" data='{\"items\": [], \"selectedId\": null}'>\n  <section *api=\"/api/users\" *into=\"items\">\n    <p *if=\"$pending\">Loading users...</p>\n\n    <p *if=\"$error\" class=\"error\">\n      <span *print=\"$error.message\"></span>\n    </p>\n\n    <ul *if=\"!$pending && !$error && items\">\n      <li *for=\"user of items\">\n        <button\n          @click=\"selectedId = user.id\"\n          *print=\"user.name\">\n        </button>\n      </li>\n    </ul>\n  </section>\n</serc-rod>\n```\n\nBecause `*api` fires automatically on the non-clickable `section`, this:\n\n- Shows \"Loading users...\" while the request is in flight.\n- Shows an error message if `$error` is non-null.\n- Renders the list when `items` has been populated.\n\n##### Inside loops\n\nYou can place `*api` inside `*for` or `*each` loops, but keep in mind:\n\n- Loop variables are visible to `*api` expressions, because they are part of the incoming `scope`.\n- The `*into` key is literal, so reusing the same `*into` inside a loop will cause each iteration to overwrite the same data property on the host.\n\nFor independent state per iteration, consider:\n\n- Having a dedicated `*api` host per entity (for example one `<section>` per user ID, each with a different literal `*into` key).\n- Structuring your API to return all needed data at once and iterating purely on the client side.\n\n\n#### Use with other directives\n\n##### `*into`\n\n`*into` is designed to be used with `*api` (and some related directives such as `*websocket` and upload helpers). When combined with `*api`:\n\n- `*into` controls where the response is stored.\n- `$download` and `$upload` are always updated irrespective of `*into`.\n\n##### Other network helpers\n\nAlthough `*api`, `*download`, and `*upload` are related conceptually, they consume the element in different ways:\n\n- Each expects to own the element and its children.\n- In the rendering pipeline, only one of these directives is applied per element.\n\nIn the current implementation:\n\n- If `*api` or `n-api` is present on an element, it takes precedence for that element.\n- Other network helpers on the same element are effectively ignored because `_renderElement` returns after handling `*api`.\n\nFor clarity and future-proofing, it is recommended to:\n\n- Use at most one of `*api`, `*download`, `*upload`, or similar network primitives on a single element.\n- Split different responsibilities across separate wrapper elements when needed.\n\n##### Event handlers (`@click` and others)\n\n`*api` coexists with event directives such as `@click`, `@change`, and others:\n\n- Sercrod simply adds another listener for `click` or `change` on the same element.\n- You can safely add your own handlers alongside `*api` to update state or log events.\n\nBe aware that:\n\n- For clickable elements, every `click` triggers the `*api` call. If your own handler also changes data, it will run in addition to the HTTP request.\n\n\n#### Server-side contract and recommended API shape\n\nBecause `*post`, `*fetch`, and `*api` all treat HTTP communication as \"JSON in, JSON out\" and share the same state flags, it is natural to standardize server-side handlers around this contract.\n\nRecommended approach on the server:\n\n- Treat Sercrod-driven endpoints as JSON endpoints:\n\n  - Always accept a JSON request body for write operations.\n  - Always return a JSON response for both success and application-level errors.\n  - Use a stable envelope shape so that `URL[:prop]` and `*into` can be wired consistently.\n\n- Reuse the same processing pipeline:\n\n  - Parse JSON.\n  - Run validation, authentication, business logic, and logging in a shared middleware.\n  - Produce a JSON object that Sercrod can store as-is into `data[prop]`, `data[base][key]`, or a target selected by `*into`.\n\nBenefits for server-side code:\n\n- You can implement a \"Sercrod API style\" once and reuse it across multiple endpoints.\n- Monitoring and logging become easier because every Sercrod request and response has the same structure.\n- Frontend and backend teams can agree on a single JSON contract instead of negotiating many small variations.\n\nPosition in Sercrod's design:\n\n- Sercrod does not force this server-side style, but the runtime is optimized around it:\n  - `*post` and `*fetch` share the `URL[:prop]` rule and write values back without further transformation.\n  - `*api` writes the raw response into the variable named by `*into`.\n  - All of them update `$pending`, `$error`, `$download`, and `$upload` in a consistent way.\n- For new projects that adopt Sercrod end to end, designing server APIs to follow this unified JSON contract is strongly recommended.\n- For existing APIs, you can:\n  - Use `*api` to integrate with legacy endpoints as they are.\n  - Gradually introduce Sercrod-style JSON endpoints for new features and move existing endpoints toward the same contract when possible.\n\n\n#### Best practices\n\n- Prefer literal URLs in `*api` and use placeholder expansion for dynamic parts:\n  - `*api=\"/api/users/%userId%\"` is usually clearer than concatenating strings in the attribute.\n\n- Always choose a descriptive `*into` key when you intend to use the value later:\n  - For example `*into=\"profile\"`, `*into=\"items\"`, `*into=\"result\"`.\n\n- Use `$pending` and `$error` for control flow:\n  - Render loading states.\n  - Hide or disable buttons while `$pending` is true.\n  - Show `$error.message` in a dedicated area.\n\n- For one-off initial loads, prefer non-clickable hosts so automatic firing performs the first request for you.\n\n- For actions like \"Save\", \"Retry\", or \"Load more\", prefer clickable hosts (buttons, links) so the user is in control.\n\n- When you need cache busting with timestamps, put them in a `ts` query parameter:\n  - `*api=\"/api/users?ts=%Date.now()%\"`\n\n  `*api` ignores `ts` when computing the automatic deduplication key so the first automatic request per configuration is still only sent once.\n\n- Handle file uploads through `<input type=\"file\" *api>`, and show progress or final results by reading `$upload` or the `*into` variable.\n\n\n#### Examples\n\n##### GET with ignored body\n\nBecause `*api` only uses the body expression for non-GET methods, the following does not send a body:\n\n```html\n<section\n  *api=\"/api/search\"\n  method=\"GET\"\n  body=\"{ query: term }\"\n  *into=\"results\">\n</section>\n```\n\n- URL is `/api/search` (plus any placeholder expansions).\n- The body expression is ignored, and no body is sent.\n- The response still populates `results` and `$download`.\n\nTo send JSON, change to `method=\"POST\"`:\n\n```html\n<section\n  *api=\"/api/search\"\n  method=\"POST\"\n  body=\"{ query: term }\"\n  *into=\"results\">\n</section>\n```\n\n##### Button-triggered POST\n\n```html\n<serc-rod id=\"formHost\" data='{\"form\": {\"name\": \"\", \"email\": \"\"}, \"saved\": null}'>\n  <input type=\"text\"\n         :value=\"form.name\"\n         @input=\"form.name = $event.target.value\">\n\n  <input type=\"email\"\n         :value=\"form.email\"\n         @input=\"form.email = $event.target.value\">\n\n  <button\n    *api=\"/api/submit\"\n    method=\"POST\"\n    body=\"form\"\n    *into=\"saved\">\n    Save\n  </button>\n\n  <p *if=\"$pending\">Saving...</p>\n  <p *if=\"$error\" *print=\"$error.message\"></p>\n  <p *if=\"saved\" *print=\"'Saved as id ' + saved.id\"></p>\n</serc-rod>\n```\n\n- The request is only sent when the button is clicked.\n- The entire `form` object is serialized and sent as JSON.\n- The response is placed into `saved` and `$upload`.\n\n##### Simple file upload with preview\n\n```html\n<serc-rod id=\"avatarHost\" data='{\"avatarResult\": null}'>\n  <input\n    type=\"file\"\n    name=\"avatar\"\n    accept=\"image/*\"\n    *api=\"/api/avatar\"\n    *into=\"avatarResult\">\n\n  <p *if=\"$pending\">Uploading...</p>\n  <p *if=\"$error\" *print=\"$error.message\"></p>\n\n  <p *if=\"avatarResult && avatarResult.url\">\n    <img :src=\"avatarResult.url\" alt=\"Avatar\">\n  </p>\n</serc-rod>\n```\n\n- Selecting a file sends it via `FormData` to `/api/avatar`.\n- The parsed response is written into `avatarResult` and `$upload`.\n- Errors are surfaced through `$error` and the `sercrod-error` event.\n\n\n#### Notes\n\n- `*api` is the single low-level primitive for HTTP calls on normal elements. Other helpers reuse the same status fields but provide different ergonomics.\n- Responses are not transformed beyond JSON parsing. If you need special handling, do it in your template or in `@` event handlers listening for `sercrod-api`.\n- The `*into` key is literal for now. There is no special syntax for nested paths or dynamic property names on this directive.\n- Auto-run deduplication is intentionally conservative. If you need to force a new automatic request without user action, change one of the stable components of the key (URL except for `ts`, method, `*into`, or the body expression).\n- Future versions of Sercrod may refine the interaction between `*let` and `*api`. The current behavior is that `*api` sees ancestor `*let` effects and loop variables but not new names introduced by `*let` on the same element.\n",
  "apply": "### *apply / n-apply\n\n#### Summary\n\n*apply is the commit counterpart to *stage. On a staged host it copies the current staged data back into the host data object and triggers a redraw, so that edited values become the new committed state. The alias n-apply behaves identically.\n\n\n#### Description\n\nWhen a Sercrod host uses *stage, the runtime keeps a separate buffer object called the stage for rendering and user edits, while the original data object remains the committed source of truth. During update, the visible scope for the host is taken from the stage buffer when it exists, and falls back to the data object when it does not.\n\n*apply is a simple event-style directive that finalises a staged edit session. When the element is clicked, the contents of the stage buffer are merged into the host data and the host is updated. Immediately after applying, Sercrod takes a deep snapshot of the committed data so that *restore can later rebuild the stage from that snapshot.\n\nThe attribute value of *apply or n-apply is not used. Presence of the attribute is all that matters; any value is ignored.\n\n\n#### Basic example\n\nA minimal save or cancel form with staging:\n\n```html\n<serc-rod data=\"{ profile: { name: 'Alice' } }\" *stage>\n  <label>\n    Name:\n    <input type=\"text\" *input=\"profile.name\">\n  </label>\n\n  <p>Preview (staged): %profile.name%</p>\n\n  <button type=\"button\" *apply>Save</button>\n  <button type=\"button\" *restore>Cancel</button>\n</serc-rod>\n```\n\nIn this pattern:\n\n- The host has *stage, so user edits go into the staged buffer.\n- Clicking the Save button commits the staged values into the host data.\n- Clicking the Cancel button discards staged edits and reconstructs the stage from the last committed snapshot.\n\n\n#### Behavior\n\n- Presence only\n  - *apply and n-apply are presence based. The attribute value, if any, is ignored by the runtime.\n\n- Host selection\n  - The directive always talks to the nearest Sercrod host that owns the template being rendered. The click handler is bound with that host as the internal target, and it uses that host data and stage.\n\n- Click handling\n  - On click, if the host has a non null stage buffer, the runtime performs a shallow merge from the stage to the host data at the top level using an assignment equivalent to Object.assign(data, stage).\n  - Nested objects are copied by reference at the top level, so changes to nested properties in the stage are reflected when the containing object is assigned back to the data.\n\n- Redraw and snapshot\n  - After merging, the host update method is called to re render based on the new committed data or staged view.\n  - The committed data is then deep cloned into a private snapshot field used by *restore, using structured cloning where available and falling back to a JSON round trip.\n\n- No stage, no effect\n  - If the host does not have a stage buffer, clicking a *apply or n-apply element does nothing. There is no error and no update is triggered.\n\n- Children and other directives inside the element\n  - The renderer handles *apply as a control directive: it clones the original element, wires the click handler on the clone, appends that clone to the output, and returns without sending the element through the normal attribute and text processing pipeline.\n  - As a consequence, Sercrod specific features inside the same element are not processed:\n    - No text expansion of percent expressions inside the button label.\n    - No *print, *textContent, *compose, or other directives on the same element.\n    - No @event attributes handled by Sercrod on that element; only the internal click handler is attached.\n  - Plain HTML attributes, tag name, and static text in the element are preserved as in the template but stay static.\n\n- Aliases\n  - n-apply is a direct alias of *apply. Both names go through the same runtime path and have identical behavior.\n\n\n#### Evaluation timing\n\n- Template render\n  - During rendering, Sercrod scans each node for control directives after structural directives such as *if, *switch, and *each have been processed. *apply is one of these control directives.\n  - If *apply or n-apply is found on the working node, the engine:\n    - Clones the node.\n    - Attaches the click listener to the clone.\n    - Appends the clone to the output parent.\n    - Returns without further processing for that node.\n\n- User interaction\n  - The runtime does not evaluate any expression for *apply itself; the only evaluation occurs when the user clicks the element, and the operation is a direct object merge from stage into data followed by an update.\n\n- Re rendering\n  - On each host update, the template is re rendered and new clones of the *apply elements are created with fresh handlers. Old DOM is discarded as part of the normal update flow.\n\n\n#### Execution model\n\n- Synchronous behavior\n  - The commit operation is fully synchronous: merging the staged object into the data, taking a snapshot, and calling update all happen in the same call stack of the click handler, before the event finishes bubbling.\n\n- Interaction with other lifecycle features\n  - After update, normal lifecycle hooks run as usual, including logging hooks, *updated, and any data observation and change detection that operate on the host data.\n  - If the host is running with observation enabled, the assignments produced by *apply go through the same proxy layer used for other writes and therefore produce the same change events.\n\n- Error handling\n  - The *apply implementation does not wrap its assignments in explicit error handling. If the data object is writable, the operation should succeed under normal circumstances.\n\n\n#### Variable creation\n\n- *apply does not define any new variables or bindings in the template scope.\n- It does not introduce special variables like a loop index or branch state.\n- The only internal data it maintains is the host private snapshot used by *restore, which is not injected into expression scopes.\n\n\n#### Scope layering\n\n- Host data versus stage buffer\n  - Hosts with *stage or n-stage maintain:\n    - A committed data object used as a durable source of truth and as the base for observation.\n    - A stage buffer used for rendering and user edits.\n  - The visible scope during update is taken from the stage buffer when it exists, falling back to the data object otherwise.\n\n- Impact of apply on scopes\n  - *apply copies properties from the stage buffer into the committed data object at the top level. After the merge:\n    - New or changed top level properties and their nested contents are committed.\n    - Top level keys present only in the data object and removed from the stage buffer are not automatically deleted.\n  - Expressions that read from the host data (for example through reserved variables such as the root data of the host) observe the new committed values after the update triggered by *apply.\n\n- External scopes\n  - *apply does not alter parent hosts or global scopes directly. Any effect on parent or root scopes is indirect, through the object graph shared by the data and how it was originally constructed.\n\n\n#### Parent access\n\n- Because *apply has no expression, it does not directly read or write parent or root variables.\n- The only data it modifies is the host data object associated with the Sercrod element that owns the template.\n- If the host data was constructed to inherit from a parent object through the prototype chain, parent level reads may see committed values after apply, but this is determined by how the data structure itself is set up rather than by the directive.\n\n\n#### Use with conditionals and loops\n\n- Inside *if and related branches\n  - *apply can be combined with *if, *elseif, and *else on the same element. Structural directives are processed first, and the chosen branch is cloned with the structural attributes removed. The cloned element still carries *apply and is processed as an apply button.\n\n- Inside *switch and *each\n  - It is safe to use *apply or n-apply inside elements that are part of a *switch block or that are repeated via *each or *for. Each cloned instance renders its own button, and all of them talk to the same host stage and data.\n\n- Multiple control directives on one element\n  - The renderer handles control directives in a fixed order and returns as soon as it matches one of them. Because of this:\n    - If you put *apply together with *restore, *save, *load, *post, *api, *upload, or *download on the same element, only the first directive that the runtime checks will be applied and the rest will be ignored.\n  - In practice, you should treat these as mutually exclusive on a single element and place them on separate elements instead.\n\n- Children with directives\n  - Child elements inside a *apply element are not processed as Sercrod templates for this element, because the renderer returns after wiring the apply handler and does not recurse into children for this particular path.\n  - If you need dynamic labels or additional Sercrod logic near a commit button, use separate sibling elements rather than nesting those features inside the same *apply button.\n\n\n#### Best practices\n\n- Always pair with *stage or n-stage\n  - Use *apply only in hosts that are configured with staging. Without a stage buffer the button is effectively inert.\n\n- Use a dedicated element for committing\n  - Place *apply or n-apply on a dedicated element, typically a button with type set to button to avoid unintended form submission when used inside forms.\n\n- Keep labels static inside the apply element\n  - Because Sercrod does not process directives or text expansions inside a *apply element, keep its contents static and move any dynamic information to nearby elements.\n\n- Coordinate with *restore\n  - For a smooth editing experience:\n    - Provide a *restore button to allow users to discard staged edits and rebuild the stage from the last committed snapshot.\n    - Ensure that both buttons are clearly labelled and grouped together.\n\n- Avoid stacking control directives\n  - Do not mix *apply with other control directives like *save, *load, *post, or *api on the same element. Use separate elements to keep behavior predictable.\n\n- Drive external side effects from committed data\n  - For external side effects, such as sending a request or syncing with other widgets, read from the committed data after apply has run or trigger those effects from lifecycle hooks that see the committed state.\n\n\n#### Examples\n\n##### Dialog style form with staged editing and commit\n\n```html\n<serc-rod\n  data=\"{\n    profile: { name: 'Alice', email: 'alice@example.com' },\n    editing: false\n  }\"\n  *stage\n>\n  <button type=\"button\" @click=\"editing = true\">Edit profile</button>\n\n  <div *if=\"editing\">\n    <label>\n      Name:\n      <input type=\"text\" *input=\"profile.name\">\n    </label>\n\n    <label>\n      Email:\n      <input type=\"email\" *input=\"profile.email\">\n    </label>\n\n    <button type=\"button\" *apply>Save changes</button>\n    <button type=\"button\" *restore @click=\"editing = false\">Cancel</button>\n  </div>\n\n  <h2>Current profile</h2>\n  <p>Name: %profile.name%</p>\n  <p>Email: %profile.email%</p>\n</serc-rod>\n```\n\nKey points:\n\n- Edits are made against the stage buffer while the dialog is open.\n- Saving applies all staged changes to the committed data and triggers a redraw.\n- Cancel uses *restore to rebuild the stage from the last committed snapshot and additionally clears the editing flag via a separate event handler on the cancel button.\n- The apply button itself only commits; any additional state changes in the UI are handled by other directives and events on other elements.\n\n##### Multiple apply buttons in different places\n\nYou can place several apply buttons in the same staged host to offer alternative commit affordances:\n\n```html\n<serc-rod data=\"{ title: 'Draft', body: '' }\" *stage>\n  <header>\n    <input type=\"text\" *input=\"title\">\n    <button type=\"button\" *apply>Save</button>\n  </header>\n\n  <main>\n    <textarea *input=\"body\"></textarea>\n  </main>\n\n  <footer>\n    <button type=\"button\" *restore>Discard changes</button>\n    <button type=\"button\" *apply>Save and stay</button>\n  </footer>\n</serc-rod>\n```\n\nAll apply buttons commit the same staged buffer into the host data when clicked. Users can choose whichever button is more convenient in the layout, but the semantics are identical.\n\n\n#### Notes\n\n- *apply and n-apply are purely host level commit controls; they do not evaluate expressions or accept configuration.\n- The directive relies on the staging mechanism; without *stage or n-stage on the host, it has no effect.\n- Because the contents of a *apply element are not processed by Sercrod, you must not rely on nested directives or percent expressions inside the same element. Use separate elements for dynamic information or attach additional behavior via standard DOM event listeners from user code.\n- On a single element, only one of the action oriented control directives such as *apply, *restore, *save, *load, *post, *api, *upload, or *download will be honored; additional directives of this kind on the same element are ignored by design.\n",
  "attribute-action": "### :action\n\n#### Summary\n\n:action is an attribute binding directive that controls the action attribute of a form element.\nIt evaluates a Sercrod expression and writes the result into the form action attribute, optionally passing the value through a configurable url filter.\nThe binding is one way: data updates change the DOM attribute, but changes to the DOM attribute do not update data.\n\n:action is part of the generic colon attribute family and shares the same evaluation rules as other colon bindings such as :href and :src.\n\n\n#### Basic example\n\nA simple dynamic form endpoint:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"endpoint\": \"/api/contact\"\n}'>\n  <form method=\"post\" :action=\"endpoint\">\n    <label>\n      Name:\n      <input type=\"text\" name=\"name\">\n    </label>\n    <button type=\"submit\">Send</button>\n  </form>\n</serc-rod>\n```\n\nBehavior:\n\n- The Sercrod host receives data with endpoint set to \"/api/contact\".\n- When the form is rendered, Sercrod evaluates the expression endpoint in the current scope.\n- The result is converted to a string and written into the form action attribute.\n- If the data changes and the form is re rendered, the action attribute is updated to match the latest value.\n\n\n#### Behavior\n\nCore rules:\n\n- Target attribute\n  :action targets the standard HTML action attribute. In practice it is meant for form elements, but Sercrod does not enforce the tag name. If used on a non form element, it simply writes an action attribute on that element.\n\n- Expression evaluation\n  Sercrod reads the attribute value (for example :action=\"endpoint\" or :action=\"isEdit ? editUrl : createUrl\").\n  It evaluates the expression in the current scope using the attribute binding mode attr:action.\n\n- Value interpretation\n  After evaluation:\n\n  - If the value is strictly false or is null or undefined, Sercrod removes the action attribute from the element.\n  - Otherwise the value is converted to a string and passed through the url filter.\n  - If the url filter returns a truthy value, Sercrod sets action to that filtered value.\n\n- Error handling\n  If evaluating the :action expression throws an error, Sercrod falls back to a safe state by setting an empty action attribute on the element.\n\n- Url filter\n  For :action (as for :href, :src, :formaction, and xlink:href), Sercrod calls the url filter:\n\n  - The filter signature is url(raw, attr, ctx).\n  - For :action, raw is the stringified expression result, attr is \"action\", and ctx contains el and scope.\n  - By default, the built in url filter simply returns raw unchanged.\n  - Projects can override url at startup via window.__Sercrod_filter to perform validation, rewriting, or blocking of unsafe URLs.\n\n- Cleanup\n  If Sercrod configuration sets cleanup.handlers to a truthy value, the original :action attribute is removed from the output DOM after it has been processed.\n  In that case, only the final action attribute remains visible in the rendered HTML.\n\n\n#### Evaluation timing\n\n:action is evaluated during the element rendering phase, together with other colon style attribute bindings.\n\nMore precisely:\n\n- Structural directives such as *if, *for, *each, *switch, *include, and others decide first whether the element is present and what its children look like.\n- Once Sercrod has decided to keep the element and has a working element instance for rendering, it walks through its attributes.\n- For every attribute whose name starts with a colon, Sercrod dispatches to the colon binding path.\n- For :action, this happens in the same pass as :href and :src.\n- If the Sercrod host updates due to data changes, the form is re rendered and :action is re evaluated with the latest scope.\n\nThere is no separate scheduling or delay specific to :action. It participates in the normal render update cycle of the host.\n\n\n#### Execution model\n\nConceptually, when Sercrod renders an element with :action, the steps are:\n\n1. Sercrod encounters a node that has an attribute named :action.\n2. It reads the expression string from that attribute, for example endpoint or isEdit ? editUrl : createUrl.\n3. It evaluates the expression with eval_expr, using the current scope and a context that sets mode to attr:action and el to the current element.\n4. It inspects the result:\n\n   - If the result is strictly false, or is null or undefined, Sercrod removes the action attribute from the element and stops.\n   - Otherwise, it converts the result to a string and passes it to the url filter, along with the attribute name \"action\" and a context with the element and scope.\n\n5. If the url filter returns a truthy string, Sercrod sets the element attribute action to that string.\n6. If evaluation throws an exception at any point, Sercrod falls back to setting action to an empty string.\n7. If cleanup.handlers is enabled in the configuration, Sercrod removes the original :action attribute from the element, leaving only the concrete action attribute in the output DOM.\n\n\n#### Use with structural directives and loops\n\nBecause :action is only an attribute binding, it composes freely with structural directives around it.\n\nTypical combinations:\n\n- Conditional forms:\n\n  ```html\n  <form *if=\"mode === 'edit'\" method=\"post\" :action=\"editEndpoint\">\n    <!-- fields -->\n  </form>\n\n  <form *if=\"mode === 'create'\" method=\"post\" :action=\"createEndpoint\">\n    <!-- fields -->\n  </form>\n  ```\n\n  Each form has its own :action and the relevant one is evaluated only when its *if condition is true.\n\n- Forms inside loops:\n\n  ```html\n  <div *each=\"user of users\">\n    <form method=\"post\" :action=\"user.endpoint\">\n      <input type=\"hidden\" name=\"id\" :value=\"user.id\">\n      <button type=\"submit\">Save {{%user.name%}}</button>\n    </form>\n  </div>\n  ```\n\n  For each iteration, :action is evaluated using that iteration scope, so each form can post to a user specific endpoint.\n\nThere are no special restrictions on combining :action with structural directives such as *if, *for, or *each, because those operate at the element or container level, while :action only binds an attribute.\n\n\n#### Best practices\n\n- Use on form elements\n  Use :action primarily on form elements, because the action attribute has standard meaning there.\n  Sercrod does not prevent using it on other elements, but it is rarely useful outside forms.\n\n- Keep expressions simple\n  Prefer expressions like endpoint, user.formAction, or config.apiBase + \"/users\" over large, complex inline logic.\n  If you need elaborate branching or construction, compute the endpoint in data or in a helper function and bind that result.\n\n- Use null or false to remove the attribute\n  When you want to explicitly remove action and fall back to the browser default (current URL), return null, undefined, or false from the expression.\n  Sercrod will remove the attribute in these cases.\n\n- Leverage the url filter for safety\n  If your project accepts dynamic or user influenced endpoints, consider overriding the url filter to reject or normalise unsafe URLs, for example by blocking unsupported protocols or rewriting relative paths.\n\n- Combine with other bindings deliberately\n  You can freely use :action alongside other colon bindings on the same element, such as :method or :target, as long as the attribute names are distinct.\n  Each binding is evaluated independently in the same attribute pass.\n\n\n#### Additional examples\n\nDynamic endpoint based on mode:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"mode\": \"create\",\n  \"createEndpoint\": \"/api/users/create\",\n  \"editEndpoint\": \"/api/users/edit\"\n}'>\n  <form method=\"post\"\n        :action=\"mode === 'edit' ? editEndpoint : createEndpoint\">\n    <!-- fields -->\n  </form>\n</serc-rod>\n```\n\nWhen mode is \"create\", the form posts to /api/users/create. When mode switches to \"edit\" and the host updates, :action changes the action attribute to /api/users/edit.\n\nSwitching between environments:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"env\": \"staging\",\n  \"endpoints\": {\n    \"staging\": \"https://staging.example.com/api/submit\",\n    \"production\": \"https://example.com/api/submit\"\n  }\n}'>\n  <form method=\"post\" :action=\"endpoints[env]\">\n    <!-- fields -->\n  </form>\n</serc-rod>\n```\n\nThe same template can be reused across staging and production by changing env in data, without touching the markup.\n\n\n#### Notes\n\n- :action is a colon style attribute binding that targets the action attribute and shares the same mechanics as :href, :src, :formaction, and xlink:href.\n- The expression result is treated as read only from the perspective of Sercrod. It is never written back into data.\n- Falsy values of false, null, or undefined remove the attribute, while other values are stringified and written.\n- The final URL passes through the url filter before being assigned, which allows applications to plug in custom validation or rewriting logic at startup.\n- If cleanup.handlers is enabled, the original :action attribute will not appear in the rendered HTML, only the concrete action attribute remains.\n",
  "attribute-class": "### :class\n\n#### Summary\n\n:class is an attribute binding directive that computes the class attribute from a Sercrod expression.\nIt writes directly to the element's className property and supports three main value shapes:\n\n- string: used as the class list as is,\n- array: filtered and joined as space separated class tokens,\n- object: keys included when their corresponding values are truthy.\n\nThe binding is one way: Sercrod updates the element's classes from data, but changes to className in the DOM are not written back to data.\n\n\n#### Basic example\n\nA simple conditional class:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"isActive\": true,\n  \"isDisabled\": false\n}'>\n  <button :class=\"[\n    'btn',\n    isActive && 'btn-active',\n    isDisabled && 'btn-disabled'\n  ]\">\n    Click me\n  </button>\n</serc-rod>\n```\n\nBehavior:\n\n- The expression is evaluated in the current scope.\n- The array is filtered for truthy entries, then joined by spaces.\n- With isActive true and isDisabled false, the resulting className is \"btn btn-active\".\n- If the data changes and the host re renders, the className is recomputed.\n\n\n#### Behavior\n\nCore rules for :class:\n\n- The expression is evaluated with mode attr:class and has access to the current scope and element.\n- The result is inspected by type:\n\n  - string:\n    - The string is assigned directly to el.className.\n  - array:\n    - Falsy values are removed using filter(Boolean).\n    - The remaining items are joined with a single space.\n    - JavaScript toString is used for coercion, so non string items are converted to strings.\n  - object:\n    - Object.keys(val) is taken.\n    - Keys whose values are truthy (Boolean coercion) are kept.\n    - The remaining keys are joined with a single space.\n  - anything else:\n    - el.className is set to an empty string.\n\n- On evaluation error:\n  - If evaluating the :class expression throws, Sercrod sets el.className to an empty string as a safe fallback.\n\n- Cleanup:\n  - If Sercrod configuration sets cleanup.handlers to a truthy value, the original :class attribute is removed from the output DOM after it has been processed.\n  - In that case, only the effective class attribute remains externally visible.\n\n\n#### Value forms in detail\n\nString form:\n\n```html\n<button :class=\"'btn btn-primary'\">Save</button>\n```\n\n- The expression returns a string.\n- Sercrod sets el.className to that exact string.\n- Any existing classes on the element are replaced.\n\nArray form:\n\n```html\n<button :class=\"[\n  'btn',\n  size === 'large' && 'btn-lg',\n  variant === 'danger' && 'btn-danger'\n]\">\n  Delete\n</button>\n```\n\n- The expression returns an array.\n- Falsy entries (false, null, undefined, 0, \"\", NaN) are removed.\n- The remaining entries are joined with a space.\n- Typical usage is to write conditions like isActive && \"is-active\" so that a class is included only when the condition is truthy.\n\nObject form:\n\n```html\n<button :class=\"{\n  'btn': true,\n  'btn-active': active,\n  'btn-disabled': disabled\n}\">\n  Submit\n</button>\n```\n\n- The expression returns an object mapping class names to flags.\n- Sercrod includes a key in the class list when its value is truthy under Boolean coercion.\n- This form is well suited for declarative toggling of many classes at once.\n\n\n#### Evaluation timing\n\n:class is evaluated in the attribute binding phase of element rendering.\n\nRough order:\n\n- Structural directives such as *if, *for, *each, *switch, *include, and others decide whether the element stays in the DOM and how its children look.\n- Once the element is confirmed for rendering, Sercrod iterates over its attributes.\n- For every attribute whose name starts with a colon, Sercrod runs the colon binding logic.\n- :text and :html are handled by their dedicated text and HTML binding path; other colon attributes, including :class, are handled by the generic :attr binding group.\n- When the host re renders because its data or stage changes, :class is re evaluated using the updated scope.\n\nThere is no separate scheduling for :class. It participates in the normal render update process like other colon bindings.\n\n\n#### Execution model\n\nConceptually, when Sercrod processes an element with :class, the steps are:\n\n1. It encounters an attribute with name :class and reads its value as an expression string.\n2. It evaluates that expression using eval_expr with the current scope, and a context where:\n   - el is the source template node for this element,\n   - mode is \"attr:class\".\n3. It inspects the type of the returned value:\n   - string, array, or object are handled as described above.\n   - Any other type yields an empty className.\n4. It writes the computed class list into el.className, replacing any previous className on that element.\n5. If evaluation throws, Sercrod sets el.className to an empty string as a defensive default.\n6. If cleanup.handlers is enabled, Sercrod removes the original :class attribute from the rendered element.\n\nImportant detail:\n\n- :class always overwrites the element's entire className, including any classes defined in the original markup or set by scripts, for that render pass.\n- If you need to keep certain classes permanently, include them in the expression itself rather than relying on a static class attribute.\n\n\n#### Scope and data access\n\nThe :class expression is evaluated in the same scope as other Sercrod expressions:\n\n- It can access data from the root or the current host.\n- It can see any iteration variables from *for or *each that surround the element.\n- It can access helper methods and special values such as $root or $parent where applicable.\n\nThere is no special scope for :class beyond what the element would normally see in its context.\n\n\n#### Use with static class attributes and other directives\n\nStatic class attributes:\n\n```html\n<button class=\"btn\" :class=\"isPrimary ? 'btn btn-primary' : 'btn btn-secondary'\">\n  Save\n</button>\n```\n\n- Even if the template has class=\"btn\", the :class binding will overwrite className entirely.\n- After Sercrod's first render, the static class attribute is no longer authoritative for that element.\n\nBest practice:\n\n- Treat :class as the single source of truth for the element's classes whenever you use it.\n- If you need persistent static classes, include them in the :class expression, for example:\n  - :class=\"['btn', isPrimary ? 'btn-primary' : 'btn-secondary']\".\n\nInteraction with other directives:\n\n- :class can be freely combined with:\n\n  - structural directives on the same element such as *if, *for, *each, *switch,\n  - other colon bindings on the same element (for example :title, :aria-label),\n  - event bindings such as @click,\n  - data bindings like n-input on form controls.\n\n- There is no special restriction specific to :class. The main behavior to remember is that :class controls the entire className for that render.\n\n\n#### Best practices\n\n- Prefer object or array syntax for conditional classes\n  Object syntax is often clearer for toggling many classes:\n\n  ```html\n  :class=\"{\n    'is-open': open,\n    'is-disabled': disabled,\n    'has-error': hasError\n  }\"\n  ```\n\n  or, using array syntax:\n\n  ```html\n  :class=\"[\n    'panel',\n    open && 'is-open',\n    disabled && 'is-disabled',\n    hasError && 'has-error'\n  ]\"\n  ```\n\n- Keep string literals for static base classes\n  Use strings as the base and layers of variability:\n\n  ```html\n  :class=\"size === 'large' ? 'btn btn-lg' : 'btn'\"\n  ```\n\n- Avoid relying on the static class attribute once :class is present\n  Any static class defined directly in markup is overwritten by :class.\n  For predictable behavior, consider treating :class as the only definition of classes for that element.\n\n- Use simple, readable expressions\n  If your :class expression becomes long or complex, consider moving the computation into data or a helper method, then binding the single result:\n\n  ```html\n  :class=\"buttonClass\"\n  ```\n\n  where buttonClass is computed in data or methods.\n\n\n#### Additional examples\n\nClasses driven by item state in a loop:\n\n```html\n<serc-rod id=\"list\" data='{\n  \"items\": [\n    { \"label\": \"Alpha\", \"active\": true,  \"disabled\": false },\n    { \"label\": \"Beta\",  \"active\": false, \"disabled\": false },\n    { \"label\": \"Gamma\", \"active\": false, \"disabled\": true  }\n  ]\n}'>\n  <ul>\n    <li *each=\"item of items\"\n        :class=\"{\n          'item': true,\n          'item-active': item.active,\n          'item-disabled': item.disabled\n        }\">\n      <span :text=\"item.label\"></span>\n    </li>\n  </ul>\n</serc-rod>\n```\n\nResponsive classes:\n\n```html\n<serc-rod id=\"layout\" data='{\n  \"compact\": true\n}'>\n  <div :class=\"compact ? 'layout layout-compact' : 'layout layout-wide'\">\n    <!-- content -->\n  </div>\n</serc-rod>\n```\n\n\n#### Notes\n\n- :class is a specialized colon binding for the class attribute. It does not go through the generic attr filter; instead, it writes directly to el.className.\n- The expression result is never written back into data; the binding is data to DOM only.\n- If evaluation fails, Sercrod falls back to an empty className for safety.\n- When cleanup.handlers is enabled, the original :class attribute is stripped from the final HTML, leaving only the effective class list on the element.\n",
  "attribute-formaction": "### :formaction\n\n#### Summary\n\n:formaction is an attribute binding directive that controls the formaction attribute on form submit controls such as button and input type=\"submit\".\nIt evaluates a Sercrod expression and writes the result into the formaction attribute, optionally passing the value through a configurable url filter.\nThe binding is one way: data updates change the DOM attribute, but changes to the DOM attribute do not update data.\n\n:formaction is part of the generic colon attribute family and shares the same evaluation rules as other colon bindings such as :href, :src, and :action.\n\n\n#### Basic example\n\nA form with per button endpoints:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"saveEndpoint\": \"/api/save\",\n  \"deleteEndpoint\": \"/api/delete\"\n}'>\n  <form method=\"post\" action=\"/api/default\">\n    <button type=\"submit\" :formaction=\"saveEndpoint\">\n      Save\n    </button>\n    <button type=\"submit\" :formaction=\"deleteEndpoint\">\n      Delete\n    </button>\n  </form>\n</serc-rod>\n```\n\nBehavior:\n\n- The form has a default action attribute \"/api/default\".\n- Each button has its own :formaction binding.\n- Sercrod evaluates saveEndpoint and deleteEndpoint and writes the results into each button's formaction attribute.\n- When the user clicks Save, the browser submits to /api/save; when they click Delete, it submits to /api/delete, following standard HTML semantics for formaction.\n\n\n#### Behavior\n\nCore rules:\n\n- Target attribute\n  :formaction targets the standard HTML formaction attribute on submit controls.\n  In normal HTML, formaction overrides the parent form action for submissions triggered by that control.\n  Sercrod does not implement that override itself; it only sets the attribute. The browser provides the actual behavior.\n\n- Expression evaluation\n  Sercrod reads the attribute value from :formaction (for example :formaction=\"endpoint\" or :formaction=\"mode === 'archive' ? archiveUrl : saveUrl\").\n  It evaluates the expression in the current scope using the attribute binding mode attr:formaction.\n\n- Value interpretation\n  After evaluation:\n\n  - If the value is strictly false, or is null or undefined, Sercrod removes the formaction attribute from the element.\n  - Otherwise the value is converted to a string and passed through the url filter.\n  - If the url filter returns a truthy value, Sercrod sets formaction to that filtered value.\n\n- Error handling\n  If evaluating the :formaction expression throws an error, Sercrod falls back to a safe state by setting an empty formaction attribute on the element.\n\n- Url filter\n  For :formaction (as for :href, :src, :action, and xlink:href), Sercrod calls the url filter:\n\n  - The filter signature is url(raw, attr, ctx).\n  - For :formaction, raw is the stringified expression result, attr is \"formaction\", and ctx contains el and scope.\n  - By default, the built in url filter simply returns raw unchanged.\n  - Projects can override url at startup via window.__Sercrod_filter to perform validation, rewriting, or blocking of unsafe URLs.\n\n- Cleanup\n  If Sercrod configuration sets cleanup.handlers to a truthy value, the original :formaction attribute is removed from the output DOM after it has been processed.\n  In that case, only the final formaction attribute remains visible in the rendered HTML.\n\n\n#### Evaluation timing\n\n:formaction is evaluated during the element rendering phase, together with other colon style attribute bindings.\n\nMore precisely:\n\n- Structural directives such as *if, *for, *each, *switch, *include, and others run first and decide whether the element is present and what its children look like.\n- When Sercrod decides to keep the element and is ready to render it, it walks through its attributes.\n- For every attribute whose name starts with a colon, Sercrod dispatches to the colon binding path.\n- For :formaction, this happens in the same pass as :href, :src, and :action.\n- If the Sercrod host updates due to data changes, the host re renders and :formaction is re evaluated with the latest scope.\n\nThere is no separate scheduling or delay specific to :formaction. It participates in the normal render update cycle of the host.\n\n\n#### Execution model\n\nConceptually, when Sercrod renders an element with :formaction, the steps are:\n\n1. Sercrod encounters a node that has an attribute named :formaction.\n2. It reads the expression string from that attribute, for example endpoint or mode === 'archive' ? archiveUrl : saveUrl.\n3. It evaluates the expression with eval_expr, using the current scope and a context that sets mode to attr:formaction and el to the current element.\n4. It inspects the result:\n\n   - If the result is strictly false, or is null or undefined, Sercrod removes the formaction attribute from the element and stops.\n   - Otherwise, it converts the result to a string and passes it to the url filter, along with the attribute name \"formaction\" and a context with the element and scope.\n\n5. If the url filter returns a truthy string, Sercrod sets the element attribute formaction to that string.\n6. If evaluation throws an exception at any point, Sercrod falls back to setting formaction to an empty string.\n7. If cleanup.handlers is enabled in the configuration, Sercrod removes the original :formaction attribute from the element, leaving only the concrete formaction attribute in the output DOM.\n\n\n#### Use with forms and actions\n\nBecause :formaction is a pure attribute binding, it composes naturally with the rest of the form and with :action:\n\n- Default action plus per button overrides:\n\n  ```html\n  <serc-rod id=\"app\" data='{\n    \"defaultAction\": \"/api/default\",\n    \"specialAction\": \"/api/special\"\n  }'>\n    <form method=\"post\" :action=\"defaultAction\">\n      <button type=\"submit\">\n        Default submit\n      </button>\n      <button type=\"submit\" :formaction=\"specialAction\">\n        Special submit\n      </button>\n    </form>\n  </serc-rod>\n  ```\n\n  - When the first button is used, the browser submits to defaultAction.\n  - When the second button is used, the browser submits to specialAction.\n\n- Combined with validation and event handlers:\n\n  ```html\n  <form method=\"post\" :action=\"endpoint\" @submit=\"validateAndMaybeSubmit($event)\">\n    <button type=\"submit\" :formaction=\"dangerEndpoint\">\n      Dangerous action\n    </button>\n  </form>\n  ```\n\n  In this pattern, :formaction sets the DOM attribute and the event handler decides whether to let the browser submit, use Sercrod *post, or prevent the submission entirely.\n\n\n#### Use with structural directives and loops\n\n:formaction is evaluated at the element level and does not interfere with container level structural directives.\n\nExamples:\n\n- Conditional submit controls:\n\n  ```html\n  <form method=\"post\" :action=\"defaultEndpoint\">\n    <button type=\"submit\" *if=\"canSave\" :formaction=\"saveEndpoint\">\n      Save\n    </button>\n    <button type=\"submit\" *if=\"canDelete\" :formaction=\"deleteEndpoint\">\n      Delete\n    </button>\n  </form>\n  ```\n\n  Each button is only rendered when its *if condition is true, and its :formaction is evaluated inside that conditional scope.\n\n- Submit controls inside loops:\n\n  ```html\n  <serc-rod id=\"app\" data='{\n    \"items\": [\n      { \"id\": 1, \"endpoint\": \"/api/items/1/action\" },\n      { \"id\": 2, \"endpoint\": \"/api/items/2/action\" }\n    ]\n  }'>\n    <ul *each=\"item of items\">\n      <li>\n        <form method=\"post\">\n          <input type=\"hidden\" name=\"id\" :value=\"item.id\">\n          <button type=\"submit\" :formaction=\"item.endpoint\">\n            Run action for {{%item.id%}}\n          </button>\n        </form>\n      </li>\n    </ul>\n  </serc-rod>\n  ```\n\n  The :formaction is evaluated separately for each iteration, using that iteration's item as part of the scope.\n\n\n#### Best practices\n\n- Use on submit controls\n  Use :formaction on button and input elements that can trigger a form submission.\n  This matches the standard meaning of formaction in HTML and avoids confusing markup.\n\n- Keep expressions simple\n  Prefer expressions like item.endpoint or routes[dangerLevel] over long inline branches.\n  If you need more complex logic, compute a prepared URL in data or in a helper method and bind that.\n\n- Use null or false to remove the attribute\n  When you do not want a per button override, return null, undefined, or false from the expression.\n  Sercrod will remove the formaction attribute and the browser will fall back to the parent form action.\n\n- Use url filter for central control\n  If you want to validate or rewrite all submission endpoints in one place, implement a custom url filter.\n  Since :formaction uses the url filter with attr set to \"formaction\", you can distinguish it from other URL bindings if necessary.\n\n- Combine with :action deliberately\n  A typical pattern is:\n\n  - Use :action on the form to define the default endpoint.\n  - Use :formaction on specific buttons to override that endpoint when needed.\n\n  This mirrors native HTML behavior and keeps intent clear in the markup.\n\n\n#### Additional examples\n\nSwitching endpoint based on environment:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"env\": \"staging\",\n  \"endpoints\": {\n    \"staging\": \"https://staging.example.com/api/bulk\",\n    \"production\": \"https://example.com/api/bulk\"\n  }\n}'>\n  <form method=\"post\" action=\"/noop\">\n    <button type=\"submit\" :formaction=\"endpoints[env]\">\n      Run bulk job\n    </button>\n  </form>\n</serc-rod>\n```\n\nHere, changing env in data (for example from \"staging\" to \"production\") changes the target endpoint for submissions triggered by the button.\n\n\n#### Notes\n\n- :formaction is a colon style attribute binding that targets formaction and shares its mechanics with :href, :src, :action, and xlink:href.\n- Sercrod only sets the attribute; the semantics of how the form is submitted with formaction are provided by the browser.\n- The expression result is never written back into data. It is read only from the perspective of Sercrod.\n- Falsy values of false, null, or undefined remove the formaction attribute, while other values are stringified and written.\n- The final URL passes through the url filter before being assigned, which allows applications to plug in custom validation or rewriting.\n- If cleanup.handlers is enabled, the original :formaction attribute will not appear in the rendered HTML, only the concrete formaction attribute remains.\n",
  "attribute-href": "### :href\n\n#### Summary\n\n:href is an attribute binding directive that controls the href attribute of a link or resource element.\nIt evaluates a Sercrod expression and writes the result into the href attribute, optionally passing the value through a configurable url filter.\nThe binding is one way: data updates change the DOM attribute, but changes to the DOM attribute do not update data.\n\n:href is part of the generic colon attribute family and shares the same evaluation rules as other colon bindings such as :src and :action.\n\n\n#### Basic example\n\nA simple data driven link:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"url\": \"https://example.com/docs\"\n}'>\n  <a :href=\"url\">Open documentation</a>\n</serc-rod>\n```\n\nBehavior:\n\n- The Sercrod host receives data with url set to \"https://example.com/docs\".\n- When the link is rendered, Sercrod evaluates the expression url in the current scope.\n- The result is converted to a string, passed through the url filter, and written into the a element's href attribute.\n- If data.url changes and the host is updated, the href attribute is updated to match the latest value.\n\n\n#### Behavior\n\nCore rules:\n\n- Target attribute\n  :href targets the standard HTML href attribute. It is typically used on a elements, but can also be used on other href capable elements such as area and link. Sercrod does not enforce any restriction on the tag name.\n\n- Expression evaluation\n  Sercrod reads the attribute value (for example :href=\"url\" or :href=\"base + '/users/' + userId\").\n  It evaluates the expression in the current scope using the attribute binding mode attr:href.\n\n- Value interpretation\n  After evaluation:\n\n  - If the value is strictly false or is null or undefined, Sercrod removes the href attribute from the element.\n  - Otherwise, the value is converted to a string and passed through the url filter.\n  - If the url filter returns a truthy value, Sercrod sets href to that filtered value.\n\n- Error handling\n  If evaluating the :href expression throws an error, Sercrod falls back to a safe state by setting an empty href attribute on the element.\n\n- Url filter\n  For :href (as for :src, :action, :formaction, and xlink:href), Sercrod calls the url filter:\n\n  - The filter signature is url(raw, attr, ctx).\n  - For :href, raw is the stringified expression result, attr is \"href\", and ctx contains el and scope.\n  - By default, the built in url filter simply returns raw unchanged.\n  - Projects can override url at startup via window.__Sercrod_filter to perform validation, rewriting, or blocking of unsafe URLs.\n\n- Cleanup\n  If Sercrod configuration sets cleanup.handlers to a truthy value, the original :href attribute is removed from the output DOM after it has been processed.\n  In that case, only the final href attribute remains visible in the rendered HTML.\n\n\n#### Evaluation timing\n\n:href is evaluated during the element rendering phase, together with other colon style attribute bindings.\n\nMore precisely:\n\n- Structural directives such as *if, *for, *each, *switch, *include, and others decide first whether the element is present and what its children look like.\n- Once Sercrod has decided to keep the element and has a working element instance for rendering, it walks through its attributes.\n- For every attribute whose name starts with a colon, Sercrod dispatches to the colon binding path.\n- For :href, this happens in the same pass as :src and :action.\n- If the Sercrod host updates due to data changes, the link is re rendered and :href is re evaluated with the latest scope.\n\nThere is no separate scheduling or delay specific to :href. It participates in the normal render update cycle of the host.\n\n\n#### Execution model\n\nConceptually, when Sercrod renders an element with :href, the steps are:\n\n1. Sercrod encounters a node that has an attribute named :href.\n2. It reads the expression string from that attribute, for example url or base + \"/users/\" + userId.\n3. It evaluates the expression with eval_expr, using the current scope and a context that sets mode to attr:href and el to the current element.\n4. It inspects the result:\n\n   - If the result is strictly false, or is null or undefined, Sercrod removes the href attribute from the element and stops.\n   - Otherwise, it converts the result to a string and passes it to the url filter, along with the attribute name \"href\" and a context with the element and scope.\n\n5. If the url filter returns a truthy string, Sercrod sets the element attribute href to that string.\n6. If evaluation throws an exception at any point, Sercrod falls back to setting href to an empty string.\n7. If cleanup.handlers is enabled in the configuration, Sercrod removes the original :href attribute from the element, leaving only the concrete href attribute in the output DOM.\n\n\n#### Use with structural directives and loops\n\nBecause :href is only an attribute binding, it composes freely with structural directives around it.\n\nTypical combinations:\n\n- Conditional links:\n\n  ```html\n  <a *if=\"isLoggedIn\" :href=\"profileUrl\">Your profile</a>\n  <span *if=\"!isLoggedIn\">Please log in</span>\n  ```\n\n  Only when isLoggedIn is true is the link rendered, and only then is :href evaluated.\n\n- Links inside loops:\n\n  ```html\n  <ul *each=\"item of items\">\n    <li>\n      <a :href=\"item.url\" *print=\"item.label\"></a>\n    </li>\n  </ul>\n  ```\n\n  For each iteration, :href is evaluated using that iteration scope, so each link points to a different URL.\n\n- Links with events:\n\n  ```html\n  <a :href=\"item.url\"\n     @click=\"trackClick(item)\">\n    {{%item.label%}}\n  </a>\n  ```\n\n  :href controls navigation, while @click allows tracking or other side effects. If you add *prevent-default on the same element, the browser navigation will be suppressed even if href is set.\n\n\n#### Best practices\n\n- Use on link and resource elements\n  Use :href primarily on elements where href is meaningful, such as a, area, and link. Sercrod does not prevent using it on other elements, but it is rarely useful there.\n\n- Keep expressions simple\n  Prefer expressions like url, item.url, or base + path segments over very long inline logic.\n  If you need elaborate branching or construction, compute the URL in data or in a helper function and bind that result.\n\n- Use null or false to disable navigation\n  When you want to temporarily disable a link or fall back to a non navigational element, return null, undefined, or false from the :href expression.\n  Sercrod will remove the href attribute in these cases, leaving the element clickable only for any attached events.\n\n- Leverage the url filter for safety\n  If your project accepts or constructs dynamic URLs from user influenced data, consider overriding the url filter to reject or normalise unsafe URLs, for example by blocking unsupported protocols or rewriting to a safe base.\n\n- Combine with events deliberately\n  You can freely use :href alongside event bindings such as @click or structural helpers like *prevent-default.\n  Decide explicitly whether navigation should occur (no *prevent-default) or whether the link is only a styled trigger for a JavaScript action (*prevent-default).\n\n- Combine with other colon bindings carefully\n  You can use :href on the same element as other colon bindings (for example :class or :title), as long as they target different attributes.\n  Each binding is evaluated independently in the same attribute pass.\n\n\n#### Additional examples\n\nBuilding URLs from parameters:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"userId\": 42,\n  \"base\": \"/users\"\n}'>\n  <a :href=\"base + '/' + userId\">View profile</a>\n</serc-rod>\n```\n\nLocalised links:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"locale\": \"en\",\n  \"docs\": {\n    \"en\": \"/docs/en/guide\",\n    \"ja\": \"/docs/ja/guide\"\n  }\n}'>\n  <a :href=\"docs[locale]\">Read guide</a>\n</serc-rod>\n```\n\nLinks that can be disabled:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"item\": { \"url\": \"/pay\", \"enabled\": false }\n}'>\n  <a :href=\"item.enabled ? item.url : null\"\n     @click=\"item.enabled && pay(item)\">\n    Proceed to payment\n  </a>\n</serc-rod>\n```\n\nWhen item.enabled is false, :href removes the href attribute, and only the click handler (if not guarded) will run.\n\n\n#### Notes\n\n- :href is a colon style attribute binding that targets the href attribute and shares the same mechanics as :src, :action, :formaction, and xlink:href.\n- The expression result is treated as read only from the perspective of Sercrod. It is never written back into data.\n- Falsy values of false, null, or undefined remove the attribute, while other values are stringified and written.\n- The final URL passes through the url filter before being assigned, which allows applications to plug in custom validation or rewriting logic at startup.\n- If cleanup.handlers is enabled, the original :href attribute will not appear in the rendered HTML, only the concrete href attribute remains.\n",
  "attribute-src": "### :src\n\n#### Summary\n\n:src is an attribute binding directive that controls the src attribute of an image, script, or media element.\nIt evaluates a Sercrod expression and writes the result into the src attribute, passing the value through a configurable url filter.\nThe binding is one way: data changes update the DOM attribute, but changes to the DOM attribute do not update data.\n\n:src belongs to the URL binding family together with :href, :action, :formaction, and xlink:href.\n\n\n#### Basic example\n\nSimple image binding:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"imageUrl\": \"/assets/logo.png\"\n}'>\n  <img :src=\"imageUrl\" alt=\"Logo\">\n</serc-rod>\n```\n\nBehavior:\n\n- Sercrod evaluates the expression imageUrl in the current scope.\n- The result \"/assets/logo.png\" is stringified and passed through the url filter for the \"src\" attribute.\n- The filtered value is assigned to the src attribute of the img element.\n- When imageUrl changes in data and the host re renders, Sercrod re evaluates the expression and updates src.\n\n\n#### Behavior\n\nCore rules:\n\n- Target attribute\n  :src targets the standard HTML src attribute. Sercrod does not enforce the tag name; it is designed for elements such as img, script, iframe, video, audio, and source, but any element can technically receive a src attribute.\n\n- Expression evaluation\n  Sercrod reads the attribute value (for example :src=\"imageUrl\" or :src=\"cdnBase + path\").\n  It evaluates this string as a Sercrod expression in the current scope, using an evaluation mode of attr:src.\n\n- Value interpretation\n  After evaluation:\n\n  - If the value is strictly false, or is null or undefined, Sercrod removes the src attribute from the element.\n  - Otherwise, the value is converted to a string and passed through the url filter (see below).\n  - If the url filter returns a truthy string, Sercrod sets src to that string.\n\n- Error handling\n  If evaluating the :src expression throws an error, Sercrod falls back to a safe, minimal state by setting src to an empty string.\n  The exact browser behavior for an empty src is defined by HTML and may vary, but Sercrod does not attempt to interpret it further.\n\n- Url filter\n  For :src, as for :href, :action, :formaction, and xlink:href, Sercrod calls the url filter:\n\n  - The filter signature is url(raw, attr, ctx).\n  - For :src, raw is the stringified expression result, attr is \"src\", and ctx contains at least el and scope.\n  - By default, the built in url filter simply returns raw unchanged.\n  - Applications can override url at startup by defining window.__Sercrod_filter.url. Sercrod merges this into its internal filter map at initialization time.\n\n- Cleanup\n  If Sercrod configuration sets cleanup.handlers to a truthy value, the original :src attribute is removed from the output DOM after processing.\n  The rendered HTML then contains only the resolved src attribute, with no :src visible.\n\n\n#### Evaluation timing\n\n:src is evaluated during the element rendering phase alongside other colon style attribute bindings.\n\nEvaluation order, simplified:\n\n- Structural directives such as *if, *for, *each, *switch, *include, and similar determine whether the element itself is rendered and what its children look like.\n- Once Sercrod decides to keep the element, it constructs a working element instance and evaluates its attributes.\n- In this attribute pass, all colon attributes (names starting with \":\") are processed.\n- For :src, Sercrod calls eval_expr with mode attr:src and applies the url filter.\n- If the Sercrod host updates due to data changes, the affected elements re render and :src is re evaluated with the updated scope.\n\nThere is no special delay, scheduling, or async behavior associated specifically with :src. It participates in the normal synchronous render cycle of the host.\n\n\n#### Execution model\n\nConceptually, the runtime behaves as follows when it encounters a node with :src:\n\n1. Sercrod detects an attribute whose name starts with \":\" and whose key slice is \"src\".\n2. It reads the attribute value string, for example imageUrl or cdnBase + \"/images/\" + fileName.\n3. It evaluates this string as an expression with eval_expr, using the current scope and a mode of attr:src, with el set to the current element.\n4. It inspects the evaluated result:\n\n   - If the result is strictly false, or is null or undefined, Sercrod removes the src attribute from the element and stops.\n   - Otherwise, the result is stringified.\n\n5. Sercrod calls the url filter with the string value, the attribute name \"src\", and a context including the element and scope.\n6. If the filter returns a truthy value, Sercrod assigns that value to the element's src attribute.\n7. If any exception occurs during evaluation or filtering, Sercrod sets src to an empty string as a fallback.\n8. If cleanup.handlers is enabled, Sercrod removes the :src attribute from the element's attribute list, leaving only the resolved src.\n\nThis process repeats on subsequent renders when data changes cause the element to be re rendered.\n\n\n#### Use with structural directives and loops\n\nBecause :src is purely an attribute binding, it composes naturally with structural directives around it.\n\nExamples:\n\n- Conditional image:\n\n  ```html\n  <img *if=\"showLogo\"\n       :src=\"logoUrl\"\n       alt=\"Company logo\">\n  ```\n\n  Here, *if decides whether the img element exists at all.\n  If showLogo is truthy, :src is evaluated and applied; otherwise, neither the element nor its src appears in the DOM.\n\n- Image galleries in loops:\n\n  ```html\n  <serc-rod id=\"gallery\" data='{\n    \"images\": [\n      { \"src\": \"/img/a.jpg\", \"alt\": \"Photo A\" },\n      { \"src\": \"/img/b.jpg\", \"alt\": \"Photo B\" }\n    ]\n  }'>\n    <div class=\"gallery\">\n      <figure *each=\"item of images\">\n        <img :src=\"item.src\" :alt=\"item.alt\">\n        <figcaption *print=\"item.alt\"></figcaption>\n      </figure>\n    </div>\n  </serc-rod>\n  ```\n\n  Each iteration gets its own copy of the img element and its own :src evaluation with the iteration's scope.\n\nThere are no special restrictions on combining :src with *if, *for, *each, *switch, or template directives. They operate at different layers: structural directives control presence and shape of elements, while :src binds a single attribute on each element.\n\n\n#### Best practices\n\n- Use for resource URLs\n  Use :src for URLs that load external resources, such as images, scripts, and embedded content.\n  For other attributes that are not URLs, prefer either the generic attribute binding (for example :data-id) or a dedicated attribute directive if one exists.\n\n- Keep expressions short and clear\n  Prefer expressions like image.src, imageUrl, or cdnBase + path over long, deeply nested expressions.\n  If you need complex logic (for example, selecting among many sizes or CDNs), compute the final URL in data or in a helper method and bind that single value to :src.\n\n- Use null or false to disable loading\n  When you want to prevent a resource from loading entirely, return null, undefined, or false from the :src expression.\n  Sercrod will remove the src attribute, and the browser will not attempt to load from src.\n\n- Be deliberate about empty strings\n  If the evaluated value or fallback results in an empty string, Sercrod will set src to \"\".\n  In HTML, an empty src may cause a request related to the current page; if you want no src at all, return null or false instead.\n\n- Leverage the url filter for safety and rewriting\n  If your project accepts or stores URLs that may require validation or normalization, override the url filter via window.__Sercrod_filter.url.\n  You can, for example, strip unsafe protocols, prepend a CDN base path, or block URLs that do not match allowed patterns.\n\n- Combine with other colon bindings carefully\n  You can combine :src with other colon bindings on the same element (for example :alt, :title), as long as each attribute has a unique name.\n  Each binding is evaluated independently during the same attribute pass.\n\n\n#### Additional examples\n\nResponsive image selection:\n\n```html\n<serc-rod id=\"hero\" data='{\n  \"small\": \"/img/hero-small.jpg\",\n  \"large\": \"/img/hero-large.jpg\",\n  \"isMobile\": false\n}'>\n  <img :src=\"isMobile ? small : large\" alt=\"Hero\">\n</serc-rod>\n```\n\nWhen isMobile is true, :src resolves to small; when false, to large. A data update followed by a re render switches the image.\n\nScripts based on environment:\n\n```html\n<serc-rod id=\"analytics\" data='{\n  \"env\": \"production\",\n  \"scripts\": {\n    \"staging\": \"https://staging.example.com/analytics.js\",\n    \"production\": \"https://cdn.example.com/analytics.js\"\n  }\n}'>\n  <script :src=\"scripts[env]\"></script>\n</serc-rod>\n```\n\nBy changing env in data, you can switch which analytics script is loaded without modifying markup.\n\n\n#### Notes\n\n- :src is part of the URL binding family in Sercrod. It uses the same url filter as :href, :action, :formaction, and xlink:href.\n- The expression result is never written back into data. :src is strictly a data to DOM attribute binding.\n- Values of false, null, or undefined remove the src attribute. Other values, including empty strings and zero, are converted to strings and assigned.\n- On evaluation error, Sercrod sets src to an empty string. The browser then applies its own rules for that value.\n- If cleanup.handlers is enabled in configuration, the colon attribute :src is removed after processing, leaving only the resolved src in the rendered DOM.\n- For attributes that do not have their own dedicated manual (such as :aria-label or :data-*), see the generic attribute bindings documentation. :src is documented separately because it participates in the URL filter pipeline.\n",
  "attribute-style": "### :style\n\n#### Summary\n\n:style is an attribute binding directive that controls an element's inline style through a Sercrod expression.\nIt evaluates the expression and assigns the result directly to element.style.cssText.\nThe binding is one way: changes in data update the inline styles, but changing styles in the DOM does not modify data.\n\nUnlike *style or n-style, :style does not use the style filter.\nIt is a thin, direct bridge between your expression and the element's inline CSS string.\n\n\n#### Basic example\n\nSimple inline style binding:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"highlight\": true\n}'>\n  <p :style=\"highlight ? 'color: red; font-weight: bold;' : ''\">\n    This paragraph is red when highlight is true.\n  </p>\n</serc-rod>\n```\n\nBehavior:\n\n- When highlight is true, :style sets the inline styles to color: red; font-weight: bold;.\n- When highlight becomes false and the host re renders, :style sets style.cssText to the empty string, removing the inline styles.\n- The rest of the element's attributes and children are not affected by :style.\n\n\n#### Behavior\n\n:style follows the general colon attribute-binding pipeline, with :style specific handling:\n\n- Attribute detection\n  :style is recognized when the element has an attribute whose name is exactly :style.\n\n- Expression evaluation\n  Sercrod reads the attribute value (for example :style=\"expr\") and evaluates expr in the current scope with mode attr:style and el set to the current element.\n\n- Value handling\n  On successful evaluation:\n\n  - Sercrod takes the raw expression result as val.\n  - It assigns element.style.cssText = val || \"\".\n  - If val is a non empty string, the inline style is applied as written.\n  - If val is falsy (for example \"\", 0, null, undefined, or false), the inline style string becomes \"\", effectively clearing inline styles.\n\n- Error handling\n  If the expression throws during evaluation:\n\n  - Sercrod forces a safe fallback and sets element.style.cssText = \"\".\n  - No custom filter is applied in this path.\n\n- Cleanup\n  After processing the attribute:\n\n  - If Sercrod configuration _config.cleanup.handlers is truthy, Sercrod removes the original :style attribute from the element.\n  - The resulting DOM only contains the computed style attribute (as produced by style.cssText), not the binding syntax.\n\n\n#### Evaluation timing\n\n:style is evaluated as part of the element render pass, alongside other colon bindings:\n\n- Structural directives (such as *if, *for, *each, *include, *switch, and others) decide first whether the element is present and what its child structure is.\n- After structural processing, Sercrod walks through the element's attributes.\n- All attributes whose name starts with : are handled by the colon-binding logic.\n- For key === \"style\", Sercrod runs the dedicated :style branch and assigns style.cssText accordingly.\n- If the Sercrod host re renders because data changed or an explicit update was requested, :style is re evaluated with the new scope and the inline style is updated.\n\n\n#### Execution model\n\nConceptually, Sercrod's execution model for :style looks like this:\n\n1. Detect the colon attribute:\n   - When rendering an element node, inspect its attributes.\n   - For each attribute with name starting with :, compute key = name.slice(1).\n   - If key is \"style\", treat it as a :style binding.\n\n2. Evaluate the expression:\n   - Let exprSource be the attribute value string (for example \"highlight ? 'color: red;' : ''\").\n   - Call eval_expr(exprSource, scope, { el: node, mode: \"attr:style\" }).\n   - If this call throws, jump to the error path.\n\n3. Apply the result:\n   - Let val be the result of evaluation.\n   - Set element.style.cssText = val || \"\".\n\n4. Error path:\n   - If evaluation throws, set element.style.cssText = \"\".\n\n5. Cleanup of the binding attribute:\n   - If _config.cleanup.handlers is enabled, remove the :style attribute from the element after it has been processed.\n\nThere is no additional filter step and no special handling for objects or arrays in :style; the expression result is used directly as the style string (subject to val || \"\").\n\n\n#### Interaction with *style and n-style\n\nSercrod also provides *style and n-style directives that assign inline styles using a separate, filter aware pipeline:\n\n- *style / n-style:\n  - These directives evaluate an expression and then run the result through the style filter.\n  - The result of style filter is assigned to element.style.cssText.\n  - They are processed later in the render pipeline than the generic colon bindings.\n\nOrder and precedence on the same element:\n\n- If an element has both :style and *style or n-style:\n\n  - :style runs earlier as part of the generic colon attribute pass.\n  - *style / n-style runs later in a dedicated block for n-class / n-style.\n  - The final value of style.cssText is whatever *style / n-style assigns last.\n\nThis means:\n\n- Sercrod does not prohibit combining :style with *style or n-style on the same element, but *style / n-style will overwrite the effect of :style when both are present.\n- For clarity and predictability, it is recommended to choose either :style or *style / n-style per element, rather than using both at once.\n\n\n#### Use with conditionals and loops\n\nBecause :style only binds an attribute, it composes freely with structural directives and loops around it:\n\n- Conditional elements:\n\n  ```html\n  <p *if=\"error\" :style=\"'color: red; font-weight: bold;'\">\n    An error has occurred.\n  </p>\n  ```\n\n  The paragraph exists only when error is truthy; :style is evaluated only when the element is rendered.\n\n- Inside *each or *for:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\"\n        :style=\"item.done ? 'text-decoration: line-through;' : ''\">\n      <span *print=\"item.label\"></span>\n    </li>\n  </ul>\n  ```\n\n  Each repetition gets its own style based on the iteration data.\n\n- Combined with other colon bindings:\n\n  ```html\n  <button :class=\"primary ? 'btn btn-primary' : 'btn'\"\n          :style=\"disabled ? 'opacity: 0.5; pointer-events: none;' : ''\"\n          :disabled=\"disabled\">\n    Submit\n  </button>\n  ```\n\n  :class, :style, and :disabled are evaluated independently in the same attribute pass.\n\n\n#### Best practices\n\n- Prefer strings as the expression result\n  :style assigns the expression result directly to style.cssText.\n  For predictable behavior, ensure your expression always evaluates to a string.\n  Avoid returning objects or arrays from :style expressions.\n\n- Use empty string to clear inline styles\n  Returning \"\" (or any falsy value) will reset style.cssText to the empty string, removing inline styles.\n  This is useful for toggling highlight states without leaving stale CSS on the element.\n\n- Keep style expressions simple\n  Inline style strings can become hard to maintain.\n  Prefer short expressions and factor complex logic into helpers or computed properties:\n\n  - Good: :style=\"isActive ? activeStyle : ''\"\n  - Better: :style=\"styles.card\"\n\n- Coordinate with *style / n-style\n  Decide per element whether you want:\n\n  - Fine grained control with :style on a single element, or\n  - A more structured style expression using *style / n-style plus the style filter.\n\n  Avoid mixing both on the same element unless you intentionally want *style / n-style to override :style.\n\n- Defer complex formatting to the style filter when using *style\n  Remember that :style does not use the style filter.\n  If you need complex formatting, canonicalisation, or security checks on style strings, implement them in the style filter for *style / n-style rather than relying on :style.\n\n\n#### Additional examples\n\nUsing a computed style string in data:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"styles\": {\n    \"warning\": \"color: #b45309; background-color: #fffbeb; padding: 0.5rem;\",\n    \"normal\": \"\"\n  },\n  \"mode\": \"warning\"\n}'>\n  <p :style=\"styles[mode]\">\n    This paragraph style is controlled via data.styles[mode].\n  </p>\n</serc-rod>\n```\n\nCombining multiple conditions into one style string:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"isActive\": true,\n  \"isDisabled\": false\n}'>\n  <button :style=\"(isActive ? 'border: 2px solid #2563eb;' : '') +\n                  (isDisabled ? ' opacity: 0.5; pointer-events: none;' : '')\">\n    Click me\n  </button>\n</serc-rod>\n```\n\n\n#### Notes\n\n- :style is a dedicated colon style attribute binding that writes directly to element.style.cssText.\n- It does not use the style filter; only *style / n-style do.\n- The expression result is treated as read only from Sercrod's perspective and is not written back into data.\n- Falsy values (including \"\", 0, null, undefined, and false) clear style.cssText by setting it to the empty string.\n- If _config.cleanup.handlers is enabled, the original :style attribute will be removed from the rendered DOM, leaving only the actual style attribute content that results from style.cssText.\n- When :style and *style / n-style are present on the same element, :style is applied first and *style / n-style wins last; prefer choosing one per element to avoid confusion.\n",
  "attribute-value": "### :value\n\n#### Summary\n\n:value is an attribute binding directive that controls the value property and value attribute of a form control.\nIt evaluates a Sercrod expression and writes the result into both the DOM property el.value and the value attribute for form elements, keeping the control's visible value in sync with data.\n\nFor non form elements, :value behaves like a normal colon attribute binding and only updates the value attribute.\n\n\n#### Basic example\n\nPrefilling a text input from data:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"form\": { \"name\": \"Alice\" }\n}'>\n  <form method=\"post\" action=\"/submit\">\n    <label>\n      Name:\n      <input type=\"text\" name=\"name\" :value=\"form.name\">\n    </label>\n    <button type=\"submit\">Send</button>\n  </form>\n</serc-rod>\n```\n\nBehavior:\n\n- The Sercrod host provides form.name = \"Alice\".\n- When the template renders, Sercrod evaluates the expression form.name.\n- Because the input element is a form control, Sercrod assigns that value to:\n  - el.value (the DOM property).\n  - The value attribute of the input.\n- If data changes and the host re renders, the input's value is updated accordingly.\n\n:value is one way: the DOM property and attribute are driven by data, but editing the input does not automatically update data unless other directives such as *input or n-input are used.\n\n\n#### Behavior\n\nCore rules:\n\n- Target attribute and property\n  :value targets the value attribute. For form controls, it also updates the value property.\n\n  - If the element is INPUT, SELECT, TEXTAREA, or OPTION, Sercrod:\n    - Sets el.value = String(result).\n    - Then runs the generic attribute pipeline to update the value attribute.\n  - For other tags, only the value attribute is updated.\n\n- Expression evaluation\n  Sercrod reads the expression from :value (for example form.name, user.email, or condition ? a : b).\n  It evaluates this expression in the current scope with mode attr:value and el set to the current element.\n\n- Value interpretation\n  After evaluation:\n\n  - If the result is strictly false, or is null or undefined, Sercrod removes the value attribute from the element.\n    - For form controls, the DOM property el.value is not explicitly reset in this branch; the attribute is simply removed.\n  - Otherwise, the result is converted to a string and passed to the generic attr filter.\n\n- Attribute filter\n  For :value, Sercrod does not use the url filter. Instead, it calls the attr filter:\n\n  - The filter signature is attr(name, value, ctx).\n  - For :value, name is \"value\", value is the expression result, and ctx contains el and scope.\n  - The filter must return an object { name, value } or a compatible pair.\n  - If pair.value is not null, Sercrod sets pair.name to either:\n    - An empty string if pair.value is true.\n    - String(pair.value) for any other value.\n\n  By default, attr returns the name and value unchanged, but applications can override it at startup to rewrite or normalise attribute names and values.\n\n- Error handling\n  If evaluating the :value expression throws an error, Sercrod falls back to a safe state:\n\n  - It sets an empty value attribute on the element.\n  - The property el.value is not changed in the error handler.\n\n- Cleanup\n  If configuration sets cleanup.handlers to a truthy value, Sercrod removes the original :value attribute from the DOM after processing it.\n  The rendered HTML then only contains the plain value attribute, not the colon binding.\n\n\n#### Evaluation timing\n\n:value is evaluated during element rendering, as part of the colon attribute binding pass.\n\nHigh level order:\n\n- Structural directives on the same node (such as *if, *for, *each, *switch, *include, and others) run first and determine whether the element is rendered and what its children are.\n- Once the element is kept for output, Sercrod creates a fresh el node from the template node.\n- Sercrod then walks all attributes that start with : (except :text and :html, which are handled elsewhere).\n- For each such attribute, including :value, the associated expression is evaluated and the element is updated.\n- After attribute bindings and event bindings, the element is appended to the parent.\n\nWhen the host re renders due to data changes, :value is re evaluated with the updated scope, so the control's value reflects the latest data.\n\n\n#### Execution model\n\nConceptually, when Sercrod renders an element that has :value:\n\n1. Sercrod discovers an attribute named :value on the template node.\n2. It extracts the expression string, such as form.name or user.profile.displayName.\n3. It evaluates the expression using eval_expr with:\n   - scope as the current scope.\n   - ctx containing el (the working element) and mode \"attr:value\".\n4. It inspects the result:\n\n   - If the result is strictly false, null, or undefined:\n     - It removes the value attribute from the element.\n     - For form controls, el.value is left as is by this branch.\n   - Otherwise:\n     - If the element is INPUT, SELECT, TEXTAREA, or OPTION, it sets el.value = String(result).\n     - It passes result to the attr filter as attr(\"value\", result, {el, scope}).\n     - If the filter returns a pair where pair.value is not null, it sets the attribute:\n       - pair.name as the attribute name.\n       - A stringified value (empty string if pair.value is true, or String(pair.value) otherwise).\n\n5. If an exception occurs during expression evaluation, Sercrod sets an empty value attribute as a fallback.\n6. If cleanup.handlers is enabled, Sercrod removes the :value attribute from el, leaving only the concrete value attribute.\n\n\n#### Use with structural directives and loops\n\n:value is an attribute binding and therefore composes naturally with structural directives that act at the element or container level.\n\nExamples:\n\n- Conditional fields:\n\n  ```html\n  <input type=\"text\"\n         name=\"nickname\"\n         *if=\"user.allowNickname\"\n         :value=\"user.nickname\">\n  ```\n\n  The input is only rendered when user.allowNickname is truthy.\n  When present, :value controls the initial and updated content of the input.\n\n- Lists of form controls:\n\n  ```html\n  <div *each=\"field of fields\">\n    <label>\n      {{%field.label%}}\n      <input type=\"text\"\n             :value=\"field.value\"\n             :name=\"field.name\">\n    </label>\n  </div>\n  ```\n\n  For each iteration, :value is evaluated in that iteration scope, so each input receives the correct value for its field.\n\n\n#### Best practices\n\n- Use on form controls when you want to set the visible value\n  Use :value on INPUT, SELECT, TEXTAREA, and OPTION when you need the control's value to follow data.\n  Sercrod updates both the DOM property and the attribute, so the control behaves as if you had set value directly in HTML.\n\n- Keep expressions simple\n  Prefer bindings like form.name, user.email, or config.defaults.city over very complex inline expressions.\n  If you need elaborate logic, compute a derived field in data or in a helper method and bind :value to that field.\n\n- Use null or false to remove the attribute\n  When you explicitly want to remove the value attribute, return false, null, or undefined from the expression.\n  For many browsers, removing the value attribute on a form control leaves the property value unchanged until the next time it is set, so use this pattern when you specifically want the attribute omitted.\n\n- Use attr filter for project wide normalisation\n  If your project needs to normalise or escape values going into attributes, implement a custom attr filter.\n  :value automatically participates in this pipeline, so all form values can pass through your normalisation step.\n\n- Combine with other colon attributes on the same element\n  It is safe to use :value alongside other colon bindings on the same form control, such as :name, :placeholder, or :class.\n  Each attribute is processed independently in the same colon binding pass.\n\n\n#### Additional examples\n\nHidden ID field:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"user\": { \"id\": 42, \"name\": \"Alice\" }\n}'>\n  <form method=\"post\" action=\"/users/save\">\n    <input type=\"hidden\" name=\"id\" :value=\"user.id\">\n    <input type=\"text\" name=\"name\" :value=\"user.name\">\n    <button type=\"submit\">Save</button>\n  </form>\n</serc-rod>\n```\n\nUsing :value on OPTION inside SELECT:\n\n```html\n<serc-rod id=\"country-form\" data='{\n  \"countries\": [\n    { \"code\": \"US\", \"label\": \"United States\" },\n    { \"code\": \"JP\", \"label\": \"Japan\" }\n  ],\n  \"selected\": \"JP\"\n}'>\n  <select name=\"country\">\n    <option value=\"\">Select a country</option>\n    <option *each=\"c of countries\" :value=\"c.code\">\n      {{%c.label%}}\n    </option>\n  </select>\n</serc-rod>\n```\n\nIn this example, :value ensures that each option's value attribute matches c.code.\nThe selected option still depends on standard browser behaviour or on other directives for selection state.\n\n\n#### Notes\n\n- :value is a colon style attribute binding with special handling for form controls: it updates both the value property and the value attribute on INPUT, SELECT, TEXTAREA, and OPTION.\n- For other elements, :value behaves as a normal attribute binding, updating only the value attribute.\n- The binding is one way from data to DOM. Changing the value in the browser does not update data by itself; two way binding, if desired, is provided by separate form binding directives.\n- :value participates in the generic attribute filter pipeline, allowing applications to plug in custom normalisation or escaping at startup.\n- If cleanup.handlers is enabled, the original :value attribute is removed from the output DOM after processing, leaving a clean HTML surface with only the concrete value attribute.\n",
  "attribute-xlink-href": "### :xlink:href\n\n#### Summary\n\n:xlink:href is an attribute binding directive that controls the xlink:href attribute of an SVG element.\nIt evaluates a Sercrod expression and writes the result into xlink:href, passing the value through the configurable url filter.\nThe binding is one way: data updates change the DOM attribute, but changes to the DOM attribute do not update data.\n\n:xlink:href is part of the generic colon attribute family and shares the same evaluation rules as :href, :src, :action, and :formaction.\n\n\n#### Basic example\n\nReferencing an SVG symbol by id:\n\n```html\n<serc-rod id=\"icons\" data='{\n  \"iconRef\": \"#icon-check\"\n}'>\n  <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <defs>\n      <symbol id=\"icon-check\" viewBox=\"0 0 24 24\">\n        <path d=\"M3 12l6 6L21 4\"></path>\n      </symbol>\n    </defs>\n\n    <use :xlink:href=\"iconRef\"></use>\n  </svg>\n</serc-rod>\n```\n\nBehavior:\n\n- Sercrod evaluates the expression iconRef in the current scope.\n- The string \"#icon-check\" is written into the xlink:href attribute of the use element.\n- If iconRef changes and the host re renders, xlink:href is updated accordingly.\n\n\n#### Behavior\n\nCore rules:\n\n- Target attribute\n  :xlink:href targets the xlink:href attribute commonly used in SVG 1.1 to reference other SVG content.\n  Sercrod does not enforce that the element is SVG, but the intended use is on elements such as use, image, and other SVG linking elements.\n\n- Expression evaluation\n  Sercrod reads the attribute value (for example :xlink:href=\"iconRef\" or :xlink:href=\"base + '#' + name\").\n  It evaluates the expression in the current scope using the attribute binding mode attr:xlink:href.\n\n- Value interpretation\n  After evaluation:\n\n  - If the value is strictly false or is null or undefined, Sercrod removes the xlink:href attribute from the element.\n  - Otherwise, the value is converted to a string and passed through the url filter.\n  - If the url filter returns a truthy value, Sercrod sets xlink:href on the element to that filtered value.\n\n- Error handling\n  If evaluating the :xlink:href expression throws an error, Sercrod falls back to a safe state by setting an empty xlink:href attribute on the element.\n\n- Url filter\n  For :xlink:href (as for :href, :src, :action, and :formaction), Sercrod calls the url filter:\n\n  - The filter signature is url(raw, attr, ctx).\n  - For :xlink:href, raw is the stringified expression result, attr is \"xlink:href\", and ctx contains el and scope.\n  - By default, the built in url filter simply returns raw unchanged.\n  - Projects can override url at startup via window.__Sercrod_filter to perform validation, rewriting, or blocking of unsafe URLs.\n\n- Cleanup\n  If Sercrod configuration sets cleanup.handlers to a truthy value, the original :xlink:href attribute is removed from the output DOM after it has been processed.\n  The final DOM then contains only the concrete xlink:href attribute and no colon attribute bindings.\n\n\n#### Evaluation timing\n\n:xlink:href is evaluated during the element rendering phase, together with other colon style attribute bindings.\n\nMore precisely:\n\n- Structural directives such as *if, *for, *each, *switch, *include, and others decide first whether the element is present and what its children look like.\n- Once Sercrod has decided to keep the element and has a working element instance for rendering, it walks through its attributes.\n- For every attribute whose name starts with a colon, Sercrod dispatches to the colon binding path.\n- :xlink:href is processed in the same pass as :href, :src, :action, and :formaction.\n- If the Sercrod host updates due to data changes, the element is re rendered and :xlink:href is re evaluated with the latest scope.\n\nThere is no dedicated scheduling or delay specific to :xlink:href. It participates in the normal render update cycle of the host.\n\n\n#### Execution model\n\nConceptually, when Sercrod renders an element with :xlink:href, the steps are:\n\n1. Sercrod encounters a node that has an attribute named :xlink:href.\n2. It reads the expression string from that attribute, for example iconRef or spriteBase + '#' + name.\n3. It evaluates the expression with eval_expr, using the current scope and a context that sets mode to attr:xlink:href and el to the current element.\n4. It inspects the result:\n\n   - If the result is strictly false, or is null or undefined, Sercrod removes the xlink:href attribute and stops.\n   - Otherwise, it converts the result to a string and passes it to the url filter, along with the attribute name \"xlink:href\" and a context with the element and scope.\n\n5. If the url filter returns a truthy string, Sercrod sets the element attribute xlink:href to that string.\n6. If evaluation throws an exception, Sercrod falls back to setting xlink:href to an empty string.\n7. If cleanup.handlers is enabled in the configuration, Sercrod removes the original :xlink:href attribute from the element, leaving only the concrete xlink:href attribute in the output DOM.\n\n\n#### Use with structural directives and loops\n\n:xlink:href is an attribute binding and composes freely with structural directives around it.\n\nTypical combinations:\n\n- Conditional symbol reference:\n\n  ```html\n  <use *if=\"hasIcon\" :xlink:href=\"iconRef\"></use>\n  ```\n\n  If hasIcon is truthy, Sercrod evaluates iconRef and writes the result into xlink:href.\n  If hasIcon is falsy, the entire use element is removed.\n\n- Sprite based icons in a loop:\n\n  ```html\n  <serc-rod id=\"nav\" data='{\n    \"spriteBase\": \"#icon-\",\n    \"items\": [\n      { \"id\": \"home\",  \"label\": \"Home\"  },\n      { \"id\": \"about\", \"label\": \"About\" }\n    ]\n  }'>\n    <nav>\n      <a *each=\"item of items\" href=\"#\">\n        <svg aria-hidden=\"true\">\n          <use :xlink:href=\"spriteBase + item.id\"></use>\n        </svg>\n        <span *print=\"item.label\"></span>\n      </a>\n    </nav>\n  </serc-rod>\n  ```\n\n  Each iteration uses a different xlink:href built from spriteBase and the item id.\n\nThere are no special restrictions on combining :xlink:href with structural directives such as *if, *for, or *each, because those operate at the element or container level, while :xlink:href only binds an attribute.\n\n\n#### Best practices\n\n- Use on SVG elements that support xlink:href\n  Use :xlink:href on SVG elements where xlink:href has meaning, such as use or image inside an SVG.\n\n- Keep expressions simple\n  Prefer expressions like iconRef, spriteBase + id, or map[name] over long inline logic.\n  If you need complex routing or environment dependent URLs, compute the reference string in data or in a helper function and bind that result.\n\n- Use null or false to remove the attribute\n  When you want to remove xlink:href entirely, return null, undefined, or false from the expression.\n  Sercrod will remove the attribute in these cases, which can be useful for optional icons.\n\n- Leverage the url filter for safety and rewriting\n  If your project composes URLs or symbolic references dynamically, consider overriding the url filter to validate or normalise them.\n  For example, you might restrict which sprite identifiers are allowed or ensure that the string always starts with \"#\".\n\n- Coordinate with :href when needed\n  Some projects use both href and xlink:href on SVG links or hybrid elements.\n  Sercrod allows :xlink:href and :href on the same element, and processes them independently.\n  For clarity and maintainability, prefer a single binding per semantic target unless you have a concrete reason to assign both.\n\n\n#### Additional examples\n\nUsing a mapping table:\n\n```html\n<serc-rod id=\"icons\" data='{\n  \"icons\": {\n    \"success\": \"#icon-check\",\n    \"error\":   \"#icon-cross\"\n  },\n  \"status\": \"success\"\n}'>\n  <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <use :xlink:href=\"icons[status]\"></use>\n  </svg>\n</serc-rod>\n```\n\nHere, changing status between \"success\" and \"error\" automatically switches the icon used by the use element.\n\nOptional icon:\n\n```html\n<serc-rod id=\"maybe-icon\" data='{\n  \"showIcon\": false,\n  \"iconRef\": \"#icon-info\"\n}'>\n  <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <use :xlink:href=\"showIcon ? iconRef : null\"></use>\n  </svg>\n</serc-rod>\n```\n\nWhen showIcon is false, :xlink:href removes the attribute and the use element no longer references any symbol.\n\n\n#### Notes\n\n- :xlink:href is a colon style attribute binding that targets the xlink:href attribute and shares the same mechanics as :href, :src, :action, and :formaction.\n- The expression result is treated as read only from the perspective of Sercrod. It is never written back into data.\n- Falsy values of false, null, or undefined remove the attribute, while other values are stringified and written.\n- The final reference string passes through the url filter before being assigned, which allows applications to plug in custom validation or rewriting logic with full knowledge of the attribute name \"xlink:href\".\n- If cleanup.handlers is enabled, the original :xlink:href attribute will not appear in the rendered HTML, only the concrete xlink:href attribute remains.\n",
  "attributes": "### Attribute bindings (fallback for :name)\n\n#### Summary\n\nThis page describes the general behavior of Sercrod's attribute bindings that **do not** have their own dedicated manual.\n\nAny attribute whose name starts with `:` (for example `:title`, `:aria-label`, `:data-id`) participates in this mechanism.\nThe expression to the right of `=` is evaluated, and the result is mapped to a plain HTML attribute on the rendered element.\n\nTypical examples that use this fallback:\n\n- `:title`\n- `:aria-label`, `:aria-current`, `:aria-describedby`, `:aria-*`\n- `:data-id`, `:data-status`, `:data-role`, `:data-*`\n- `:role`\n- `:tabindex`\n- other custom attributes (`:data-*`, `:foo-bar`, and so on)\n\nAttributes such as:\n\n- `:class`\n- `:style`\n- `:value`\n- `:href`\n- `:src`\n- `:action`\n- `:formaction`\n- `:xlink:href`\n- `:disabled`\n- `:readonly`\n- `:checked`\n\nhave (or may have) their own dedicated manuals that describe additional details.\nHowever, they still share the generic evaluation pipeline described here.\n\nReserved keys:\n\n- `:text` and `:html` are reserved for text/HTML bindings and are **explicitly skipped** by this fallback.\n  They are not processed here and are handled separately (or may be unimplemented in this version).\n\n\n#### Basic example\n\nA typical use of fallback attribute bindings:\n\n```html\n<serc-rod id=\"user-card\" data='{\n  \"user\": {\n    \"id\": \"u-123\",\n    \"name\": \"Alice\",\n    \"role\": \"admin\",\n    \"active\": true\n  }\n}'>\n  <div\n    :data-id=\"user.id\"\n    :aria-label=\"user.name\"\n    :role=\"user.role\"\n    :tabindex=\"user.active ? 0 : -1\"\n  >\n    <span *print=\"user.name\"></span>\n  </div>\n</serc-rod>\n```\n\nBehavior:\n\n- All attributes beginning with `:` are evaluated against the current scope.\n- When the expression produces a value, the underlying attribute name is the part after the colon (for example `data-id`, `aria-label`).\n- Falsy and boolean results map to attributes in a predictable way (explained below).\n- The `*print` directive controls the content; `:data-*`, `:aria-*`, `:role`, and `:tabindex` control accessibility and behavior.\n\n\n#### Behavior\n\nAt render time, Sercrod scans each element for attributes that start with `:`:\n\n- For each `:name=\"expr\"`:\n\n  - Sercrod evaluates `expr` in the current scope.\n  - The result is mapped to the underlying attribute name `name` (for example `title`, `aria-label`, `data-id`).\n  - The scope is never modified by attribute bindings; they are pure read operations.\n\nAttribute name:\n\n- The underlying attribute name is the attribute name without the leading `:`.\n- For example:\n\n  - `:title=\"expr\"` -> `title` attribute.\n  - `:data-id=\"expr\"` -> `data-id` attribute.\n  - `:aria-label=\"expr\"` -> `aria-label` attribute.\n\nSpecial cases in the fallback pipeline:\n\n- `:class` and `:style` have dedicated logic for combining strings, arrays, and objects.\n- `:value` interacts with form controls and assigns to both `el.value` and the `value` attribute.\n- URL-like attributes (`:href`, `:src`, `:action`, `:formaction`, `:xlink:href`) are passed through a URL filter hook for safety.\n- All other attribute names share the generic rules described in the next section.\n\nReserved and excluded:\n\n- `:text` and `:html` are explicitly skipped by the fallback implementation and are not handled here.\n  They are reserved for text/HTML bindings.\n\n\n#### Value mapping\n\nThe generic fallback follows a small set of rules to decide how to apply values:\n\n- The expression is evaluated once for each render, with context `{ el, scope, mode: \"attr:name\" }`, where `name` is the attribute name without `:`.\n\nFor non-special attribute names (everything except `class`, `style`, `value` and the URL-like list):\n\n- If the evaluated value is `false`, `null`, or `undefined`:\n\n  - The attribute is **removed** from the element (as if it had never been set).\n\n- If the evaluated value is `true`:\n\n  - The attribute is created and set to an empty string (`\"\"`).\n  - This matches the common HTML convention for boolean attributes.\n\n- For any other non-null, non-false value:\n\n  - The value is passed through the attribute filter hook.\n  - The final string (or boolean) returned by the filter is converted into a real HTML attribute.\n\nFor URL-like attributes:\n\n- When the attribute name is one of:\n\n  - `href`\n  - `src`\n  - `action`\n  - `formaction`\n  - `xlink:href`\n\n- The raw string is passed to the URL filter hook.\n- Only if the filter returns a truthy string is the attribute set.\n- Otherwise, the attribute is not written, even if the original expression returned a value.\n\nFor `:value` on form controls:\n\n- When the underlying attribute name is `value` and the element is an `INPUT`, `SELECT`, `TEXTAREA`, or `OPTION`:\n\n  - The fallback not only sets the `value` attribute, but also assigns to `el.value = String(val)`.\n  - This keeps the DOM property and attribute in sync on the rendered element.\n\n\n#### Filters and customization\n\nAttribute bindings use the following hooks on the `Sercrod` class:\n\n- `Sercrod._filters.url(raw, attrName, ctx)`:\n\n  - Used for URL-like attributes (`href`, `src`, `action`, `formaction`, `xlink:href`).\n  - By default, it just returns the raw string.\n  - Projects can override it to sanitize or rewrite URLs.\n\n- `Sercrod._filters.attr(name, value, ctx)`:\n\n  - Used for all other attributes that go through the generic path.\n  - By default, it returns `{ name, value }`.\n  - Projects can override it to:\n    - Rename attributes.\n    - Normalize boolean flags.\n    - Drop disallowed attributes by returning `null` or `{ name, value: null }`.\n\nThe fallback implementation behaves like this after the filter:\n\n- If the filter returns `null` or `undefined`, the attribute is not written.\n- If the filter returns an object `{ name, value }` and `value` is `null` or `undefined`, the attribute is not written.\n- If `value` is exactly `true`, a boolean-style attribute is written with an empty string.\n- Otherwise, the attribute with name `name` is set to `String(value)`.\n\n\n#### Error handling\n\nIf evaluation of the attribute expression throws an error:\n\n- For `:class`:\n\n  - The element's `className` is set to an empty string.\n\n- For `:style`:\n\n  - The element's inline `style` is cleared (`cssText = \"\"`).\n\n- For other attributes:\n\n  - A \"safe default\" attribute is written with an empty string value.\n\nThis behavior ensures that failures during attribute evaluation do not break the render process; instead, the element falls back to a neutral state.\n\n\n#### Evaluation timing\n\nAttribute bindings run as part of `_renderElement`, after structural directives have decided whether the element will be rendered.\n\nIn broad order:\n\n1. Structural directives (`*if`, `*for`, `*each`, `*switch`, `*template`, `*include`, and similar) decide:\n\n   - Whether the element is rendered at all.\n   - How many copies (if any) are created.\n   - Which children will be present.\n\n2. For each concrete element being rendered, Sercrod evaluates attribute bindings:\n\n   - `:class`, `:style`, `:value`, and all other `:name` attributes (except `:text` / `:html`) are processed.\n   - Each attribute is evaluated once for the current scope.\n\n3. Event bindings and other element-level behaviors are attached.\n\n4. Text content, `%expr%` expansions, inner HTML, and children are rendered.\n\nImplications:\n\n- Attribute bindings do not run for elements that were removed by `*if`, `*switch`, or other structural directives.\n- For elements generated by `*for` or `*each`, the bindings are evaluated once per iteration, with the iteration's scope.\n\n\n#### Execution model\n\nFor the fallback path, you can think of `:name=\"expr\"` as:\n\n1. **Parse**:\n\n   - Collect attributes whose names start with `:` on the template node.\n   - For each attribute, compute `name = attr.name.slice(1)`.\n\n2. **Evaluate**:\n\n   - Evaluate `expr` using the current scope with a context `{ el: templateNode, mode: \"attr:\"+name }`.\n\n3. **Apply**:\n\n   - If `name === \"class\"`:\n     - Use the class-specific rules (string, array, object).\n   - Else if `name === \"style\"`:\n     - Use the style-specific rule and `Sercrod._filters.style`.\n   - Else if `name === \"value\"` and the element is a form control:\n     - Assign to both `el.value` and the `value` attribute.\n   - Else if `name` is URL-like:\n     - Pass through `Sercrod._filters.url`.\n   - Otherwise:\n     - Pass through `Sercrod._filters.attr` and set or remove the attribute accordingly.\n\n4. **Cleanup (optional)**:\n\n   - If the runtime is configured with `cleanup.handlers = true`, the original `:name` attributes are removed from the output DOM.\n\n\n#### Variable creation and scope layering\n\nAttribute bindings **do not** create any new variables.\n\n- The expression on a `:name` attribute sees the same scope as any other Sercrod expression on that element.\n- No new loop variables, aliases, or helpers are introduced.\n- The scope is never mutated by attribute bindings; they always read from data, never write back to it.\n\nAvailable inside the expression:\n\n- The data bound to the surrounding Sercrod host (`data` or any other root object).\n- Special helpers:\n\n  - `$data` - the current host's data.\n  - `$root` - the top-level host's data.\n  - `$parent` - the nearest ancestor host's data.\n\n- Methods and helpers injected by `*methods` or related configuration.\n\n\n#### Parent access\n\nAttribute expressions can freely refer to outer data:\n\n- `:title=\"$root.appTitle\"` to reference root-level data.\n- `:data-parent-id=\"$parent.id\"` to reflect an ancestor's identifier.\n- `:aria-label=\"user.name + ' (' + parentLabel + ')'\"` if `parentLabel` is part of the scope.\n\nThe fallback implementation does not alter how scope resolution works; it simply evaluates the expression under the usual rules.\n\n\n#### Use with conditionals and loops\n\nAttribute bindings are compatible with conditionals and loops:\n\n- With `*if`:\n\n  - The `*if` gate runs before attributes are evaluated.\n  - If `*if` is falsy, the element and its attributes are not rendered.\n\n- With `*for`:\n\n  - Each repeated instance of the element gets its attributes evaluated in its own per-iteration scope.\n\n  ```html\n  <ul>\n    <li\n      *for=\"user of users\"\n      :data-id=\"user.id\"\n      :aria-label=\"user.name\"\n    >\n      <span *print=\"user.name\"></span>\n    </li>\n  </ul>\n  ```\n\n- With `*each`:\n\n  - If `*each` is on a container, the container's attributes are evaluated once.\n  - Attributes on child elements inside the loop body are evaluated for each iteration.\n\n  ```html\n  <table>\n    <tbody *each=\"row of rows\">\n      <tr :data-row-id=\"row.id\">\n        <td *print=\"row.name\"></td>\n      </tr>\n    </tbody>\n  </table>\n  ```\n\nAs a rule of thumb:\n\n- Structural directives (`*if`, `*for`, `*each`, `*switch`) decide *which* elements exist.\n- Attribute bindings decide *what attributes those existing elements have*.\n\n\n#### Best practices\n\n- Prefer specific manuals when available:\n\n  - For `:class`, `:style`, `:value`, `:href`, `:src`, `:action`, `:formaction`, `:xlink:href`, `:disabled`, `:readonly`, `:checked`, consult their dedicated pages for additional details.\n\n- Keep expressions simple:\n\n  - Attribute expressions should focus on shaping the attribute's final value.\n  - Complex computation is often better performed in data or helper methods.\n\n- Use booleans for boolean-like attributes:\n\n  - Write `:disabled=\"form.submitting\"` instead of `:disabled=\"form.submitting ? true : false\"`.\n  - The fallback will remove the attribute when the value is falsy and create it otherwise.\n\n- Be explicit with `:data-*` and `:aria-*`:\n\n  - These attributes are ideal for accessibility and automation hooks.\n  - Use clear names like `:data-test-id`, `:aria-label`, and `:aria-describedby` to make intent obvious.\n\n- Avoid mixing multiple notations for the same attribute on one element:\n\n  - For example, combining `:class` with `*class` or `n-class` is technically possible, but the last one applied wins and may overwrite the others.\n  - As a best practice, stick to **one** class-binding style per element (`:class` *or* `*class` / `n-class`, not both).\n\n\n#### Examples\n\nUsing `:aria-*` and `:data-*` for accessibility and diagnostics:\n\n```html\n<serc-rod id=\"menu\" data='{\n  \"items\": [\n    { \"id\": \"home\", \"label\": \"Home\", \"active\": true },\n    { \"id\": \"settings\", \"label\": \"Settings\", \"active\": false }\n  ]\n}'>\n  <nav aria-label=\"Main menu\">\n    <ul>\n      <li\n        *for=\"item of items\"\n        :data-id=\"item.id\"\n        :aria-current=\"item.active ? 'page' : null\"\n        :tabindex=\"item.active ? 0 : -1\"\n      >\n        <button\n          type=\"button\"\n          :aria-pressed=\"item.active\"\n        >\n          <span *print=\"item.label\"></span>\n        </button>\n      </li>\n    </ul>\n  </nav>\n</serc-rod>\n```\n\nUsing `:role` and `:data-*` together:\n\n```html\n<div\n  :role=\"isDialogOpen ? 'dialog' : null\"\n  :data-state=\"isDialogOpen ? 'open' : 'closed'\"\n>\n  <p *print=\"message\"></p>\n</div>\n```\n\nIn these examples:\n\n- Falsy results (`null`, `false`) remove the attribute.\n- Truthy non-boolean values are converted to strings.\n- Boolean `true` produces a boolean-style attribute with an empty string when passed through the generic filter.\n\n\n#### Notes\n\n- Fallback attribute bindings are one-way: they read from data and write to the DOM, but never modify data.\n- URL-like attributes are always routed through the URL filter hook before being applied.\n- The `cleanup.handlers` configuration controls whether the `:name` attributes themselves are removed from the output DOM.\n- `:text` and `:html` are reserved keys and explicitly skipped by this fallback implementation; they are not covered by this page.\n- When a dedicated manual exists for a specific attribute binding, treat that manual as the primary source; this page describes the shared baseline behavior for all `:name` attributes.\n",
  "break": "### *break\n\n#### Summary\n\n`*break` is a control directive used inside `*switch` blocks.\nIt stops the `*switch` fallthrough after the current branch has been rendered.\nThe alias `n-break` behaves the same.\n\nIn addition, `*case.break` is a shorthand that combines `*case` and `*break` on a single element:\n\n- `*case.break=\"expr\"` is equivalent to `*case=\"expr\" *break`.\n\n\n#### Basic example\n\nA typical `*switch` with a breaking case:\n\n```html\n<serc-rod id=\"app\" data='{\"status\":\"ready\"}'>\n  <div *switch=\"status\">\n    <p *case=\"'idle'\">Idle...</p>\n\n    <!-- Match \"ready\" and then stop evaluating later branches -->\n    <p *case.break=\"'ready'\">Ready</p>\n\n    <!-- Never rendered, because the previous branch breaks -->\n    <p *case=\"'ready'\">Also ready (not reached)</p>\n\n    <!-- Never reached in this example -->\n    <p *default>Unknown status</p>\n  </div>\n</serc-rod>\n```\n\nConceptually:\n\n- `*case.break=\"'ready'\"` matches when `status === \"ready\"`.\n- That branch is rendered.\n- Because it is a breaking branch, the `*switch` stops and later siblings are not evaluated or rendered.\n\n\n#### Behavior\n\n`*break` and `*case.break` are interpreted only in the context of `*switch`:\n\n- Location:\n\n  - Only direct child elements of a `*switch` host are considered.\n  - Text nodes and non-element nodes are ignored.\n\n- Branch types:\n\n  - `*case` or `n-case` define a normal case.\n  - `*case.break` or `n-case.break` define a case that also breaks after rendering.\n  - `*default` or `n-default` define the fallback branch if no case matched.\n\n- Breaking rules:\n\n  - If a branch element has `*break` or `n-break`, the `*switch` stops after rendering that branch.\n  - If a branch element uses `*case.break` or `n-case.break`, the `*switch` stops after rendering that branch.\n  - Later siblings in the `*switch` body are not evaluated or rendered.\n\nImportant limitations:\n\n- `*break` does not evaluate its attribute value in `*switch` blocks.\n  - `*break=\"expr\"` is treated the same as `*break` without a value.\n- As of the current implementation, `*break` does not stop `*for` or `*each` loops.\n  - The internal short text may mention loops, but loop control is not wired to `*break`.\n  - Placing `*break` inside a `*for` or `*each` body has no special effect on those loops.\n\nAliases:\n\n- `*break` and `n-break` are equivalent.\n- `*case.break` and `n-case.break` are equivalent.\n\n\n#### Evaluation timing\n\n`*break` participates in the evaluation of `*switch` as follows:\n\n- The `*switch` host evaluates its expression once and stores the result in `$switch` for children.\n- Sercrod walks the direct child elements of the `*switch` host from top to bottom.\n- It decides when to start \"falling through\" (rendering) by matching `*case` and `*case.break` against `$switch`, or by reaching `*default`.\n- Once falling has started:\n\n  - Each child element in the fallthrough range is rendered in order.\n  - After rendering each such branch, Sercrod checks the original branch element for:\n    - `*break` or `n-break`\n    - `*case.break` or `n-case.break`\n  - If any of those are present, the walk stops and no further children of the `*switch` are processed.\n\nNotes:\n\n- The check for `*break` and `*case.break` happens after rendering the branch, but it is based on the original element's attributes.\n- The clone used for rendering has control attributes removed so they do not leak into the final HTML.\n\n\n#### Execution model\n\nConceptually, for a `*switch` host:\n\n1. Evaluate the `*switch` expression and store it as `$switch` for children.\n2. Collect the direct child elements.\n3. Iterate over those children in DOM order.\n\n   - Determine whether each child is:\n     - A `*case` or `n-case` branch.\n     - A `*case.break` or `n-case.break` branch.\n     - A `*default` or `n-default` branch.\n     - Something else (ignored by the switch controller).\n\n4. Until the first matching branch is found, nothing is rendered.\n5. Once a matching branch (or `*default`) is found, enter \"fallthrough\" mode:\n\n   - For each branch in fallthrough mode:\n     - Clone the original node.\n     - Strip control attributes (`*case`, `*default`, `*case.break`, `*break`, and their `n-` aliases).\n     - Render the clone with the child scope (which includes `$switch`).\n     - Inspect the original node for breaking attributes:\n       - `*break`, `n-break`, `*case.break`, or `n-case.break`.\n     - If any breaking attribute is present, stop the loop.\n\n6. Other non-branch child nodes (such as comments or text) are ignored by the switch controller.\n\nThere is no separate execution path for `*break` outside of `*switch`; other directives do not consult it.\n\n\n#### Variable creation\n\n`*break` does not create any variables:\n\n- It does not introduce new names into the scope.\n- It does not affect `$switch`, `$data`, `$root`, or `$parent`.\n- It is purely a structural control flag for `*switch`.\n\nSimilarly, `*case.break` uses the same expression as `*case`:\n\n- The value of `*case.break=\"expr\"` is evaluated by the same mechanism that handles `*case=\"expr\"`.\n- No additional variables are created by using `.break`.\n\n\n#### Scope layering\n\nIn a `*switch` block:\n\n- Child branches are evaluated with a scope that merges:\n\n  - The parent scope at the point where the `*switch` appears.\n  - A `$switch` property that holds the evaluated value of the `*switch` expression.\n\n- `*break` and `*case.break` do not change the scope.\n- Using `*break` does not affect variable visibility or lifetime; it only shortens which branches are processed.\n\n\n#### Parent access\n\n`*break` does not alter the way parents are accessed:\n\n- Branch bodies can still access:\n\n  - Host data via whatever property names you used in `data`.\n  - `$root` for the root Sercrod host's data.\n  - `$parent` for the nearest ancestor Sercrod host's data.\n  - Methods injected via `*methods` or configuration.\n\n- `*break` does not expose any special information about the `*switch` beyond what `$switch` already provides.\n\n\n#### Use with conditionals and loops\n\nWithin `*switch` bodies:\n\n- You can combine `*break` or `*case.break` with normal conditionals and content:\n\n  - `*if` inside the branch body is independent of `*break`.\n  - Nested `*switch` blocks inside the body work as usual; their own `*break` only affects the inner switch.\n\nExample pattern with an inner `*if`:\n\n```html\n<div *switch=\"status\">\n  <p *case.break=\"'ready'\">\n    <span *if=\"$root.showLabel\">Ready</span>\n  </p>\n  <p *default>Other status</p>\n</div>\n```\n\nInteraction with `*for` and `*each`:\n\n- As of the current implementation:\n\n  - `*break` has no special effect on `*for` or `*each` loops.\n  - Loops do not consult `*break` when iterating.\n  - Writing `*break=\"expr\"` inside a `*for` or `*each` body does not stop the loop.\n\n- If you need to stop rendering items based on a condition, you must express that either by:\n\n  - Pre-filtering the collection.\n  - Using conditionals (`*if`) inside the loop body.\n  - Or reorganizing your data so that the loop naturally contains only the items you want to show.\n\n\n#### Best practices\n\n- Prefer `*case.break` for \"match and break\":\n\n  - Use `*case.break=\"expr\"` when you want:\n    - The branch to act like a case.\n    - The switch to stop after that branch.\n\n- Use `*break` on a branch only when you need to:\n\n  - Separate the matching logic from the breaking logic.\n  - For example, when the decision to break is expressed structurally rather than by choosing `.break`.\n\n- Keep `*break` close to `*case`:\n\n  - Treat `*case=\"expr\" *break` as equivalent to `*case.break=\"expr\"`.\n  - Choose one style per codebase for readability.\n\n- Do not rely on `*break` in loops:\n\n  - Even though the short text mentions loops, there is no loop control implemented for `*break` at this time.\n  - Express loop cutoffs using data and conditionals instead of `*break`.\n\n- Limit `*break` to child elements of `*switch`:\n\n  - Placing `*break` on elements that are not direct children of a `*switch` host has no special effect.\n  - In such locations, it behaves as a non-interpreted attribute.\n\n\n#### Examples\n\nUnconditional break with `*break`:\n\n```html\n<serc-rod id=\"app\" data='{\"status\":\"processing\"}'>\n  <div *switch=\"status\">\n    <p *case=\"'processing'\" *break>Processing...</p>\n    <p *case=\"'processing'\">Also processing (not reached)</p>\n    <p *default>Fallback (not reached)</p>\n  </div>\n</serc-rod>\n```\n\n- The first `*case` matches and renders.\n- Because it also has `*break`, the following branches are skipped.\n\nMixed fallthrough with one breaking branch:\n\n```html\n<serc-rod id=\"app\" data='{\"status\":\"multi\"}'>\n  <div *switch=\"status\">\n    <p *case=\"'multi'\">First line</p>\n    <p *case=\"'multi'\">Second line</p>\n    <p *case.break=\"'multi'\">Third line, then break</p>\n    <p *case=\"'multi'\">Fourth line (not reached)</p>\n    <p *default>Default (not reached)</p>\n  </div>\n</serc-rod>\n```\n\n- All three `*case` branches for `\"multi\"` are rendered in order until the `.break` case.\n- After the `.break` branch, the `*switch` stops and no further siblings are evaluated.\n\n\n#### Notes\n\n- `*break` and `n-break` are aliases that only have meaning inside `*switch` children.\n- `*case.break` and `n-case.break` are equivalent shorthands for `*case + *break`.\n- The current implementation:\n\n  - Removes control attributes (`*case`, `*default`, `*case.break`, `*break`, and their `n-` aliases) from the clones that are actually rendered, so these do not appear in the final HTML.\n  - Checks for breaking attributes on the original nodes only.\n  - Does not connect `*break` to `*for` or `*each` loops.\n\n- If you need loop-level early termination, use data modeling and conditionals rather than `*break`.\n",
  "case-break": "### *case.break\n\n#### Summary\n\n`*case.break` is a variant of `*case` used inside `*switch` or `n-switch`.\nIt behaves like `*case` when matching against the switch value, and in addition it stops evaluating all later `*case` and `*default` branches in the same `*switch` block.\nIt is effectively syntactic sugar for `*case=\"expr\" *break` on the same element.\n\nAlias:\n\n- `*case.break`\n- `n-case.break`\n\nBoth names share the same behavior.\n\n\n#### Basic example\n\nA simple status switch that does not fall through past the matching branch:\n\n```html\n<serc-rod id=\"app\" data='{\"status\": \"ready\"}'>\n  <div *switch=\"$data.status\">\n    <p *case=\"'pending'\">Pending...</p>\n    <p *case.break=\"'ready'\">Ready</p>\n    <p *default>Unknown status</p>\n  </div>\n</serc-rod>\n```\n\nBehavior:\n\n- The host `<div *switch>` evaluates `$data.status` as the switch value.\n- The second branch `*case.break=\"'ready'\"` matches that value.\n- That branch is rendered.\n- Because it is a `*case.break` branch, Sercrod stops there and does not render the `*default` branch.\n\n\n#### Behavior\n\n`*case.break` participates only as a child of a `*switch` or `n-switch` host:\n\n- It is recognized only on direct element children of a node with `*switch` or `n-switch`.\n- Outside of a `*switch` context, `*case.break` and `n-case.break` are not interpreted by the switch logic and have no special effect.\n\nWithin a `*switch` block:\n\n- Sercrod walks child elements in DOM order.\n- It finds the first branch that should start rendering:\n  - A matching `*case` or `*case.break`.\n  - Or a `*default` when no earlier case has matched.\n- Once rendering has started, Sercrod is in a fallthrough mode:\n  - Every subsequent `*case` or `*default` branch is also rendered, until a break-type directive is seen.\n- `*case.break` is both:\n  - A case branch (its expression is matched against the switch value).\n  - A break marker (it stops processing any later branches in this `*switch`).\n\nConceptually:\n\n- `*case=\"expr\"` means \"start here if `expr` matches; then continue rendering later branches.\"\n- `*case.break=\"expr\"` means \"start here if `expr` matches; render this branch; then stop, with no further fallthrough.\"\n\n\n#### Evaluation timing\n\nWithin the host element that has `*switch` or `n-switch`:\n\n1. Sercrod evaluates the switch expression once:\n\n   - It reads the attribute value from `*switch` or `n-switch`.\n   - It evaluates that expression and stores the result as the switch value.\n   - The value is then exposed to children as `$switch`.\n\n2. Sercrod iterates over child elements in DOM order.\n\n3. For each child:\n\n   - If it does not have `*case`, `n-case`, `*case.break`, `n-case.break`, `*default`, or `n-default`, it is ignored by the switch logic and never rendered by this `*switch`.\n   - If no branch has started yet:\n     - `*case` / `n-case` / `*case.break` / `n-case.break`:\n       - Sercrod evaluates the case expression and compares it to the switch value.\n       - If it matches, rendering starts from this branch.\n     - `*default` / `n-default`:\n       - If no earlier branch has matched, rendering starts from this default.\n\n4. Once a starting branch is chosen (including `*case.break`):\n\n   - Sercrod clones that branch, removes control attributes, and renders it with the augmented child scope (including `$switch`).\n   - It then continues in fallthrough mode (see below) unless a break is detected.\n\n\n#### Case expression semantics\n\nThe expression given to `*case.break` is interpreted in the same way as for `*case`.\n\nGiven:\n\n- `switchVal` as the evaluated switch value.\n- `raw` as the attribute string from `*case.break=\"raw\"`.\n\nSercrod applies the following rules:\n\n1. It first tries to evaluate the case expression as a normal Sercrod expression, with `$switch` injected into the scope:\n\n   - If the result is a function, Sercrod calls `fn(switchVal, scope)` and uses the truthiness of the return value.\n   - If the result is a `RegExp`, Sercrod tests it against `String(switchVal)`.\n   - If the result is an array, Sercrod checks whether any element is strictly equal to `switchVal` (using `Object.is` semantics).\n   - If the result is an object with a `has` method (for example a `Set`), Sercrod calls `set.has(switchVal)` and uses the result.\n   - If the result is a boolean, the boolean itself decides the match.\n   - If the result is a string, number, or bigint, Sercrod compares it to `switchVal` using strict identity.\n   - For other result types, this step does not produce a match.\n\n2. If evaluation throws, or if you intentionally keep the expression as a simple string, Sercrod falls back to a token list:\n\n   - It splits the raw string on commas or pipes: `\"a|b,c\"` becomes tokens like `\"a\"`, `\"b\"`, `\"c\"`.\n   - It trims empty tokens.\n   - For each token `t`:\n     - It tries to evaluate `t` as an expression.\n     - If that fails, it uses `t` itself as a string literal.\n     - It compares the result to `switchVal` using strict identity.\n   - If any token matches, the case matches.\n\nUseful patterns:\n\n- Direct equality:\n\n  - `*case.break=\"'ready'\"` matches when `$switch` is strictly `\"ready\"`.\n\n- Membership in a small set:\n\n  - `*case.break=\"'pending' | 'queued'\"`\n    Matches when `$switch` is `\"pending\"` or `\"queued\"`.\n\n- Function-based matching:\n\n  - `*case.break=\"(val) => val > 0\"`\n    Matches if the function returns a truthy value when called with `switchVal` and the current scope.\n\n- Regular expression matching:\n\n  - `*case.break=\"/^ok-/\"` (assuming the expression is parsed to a RegExp)\n    Matches when the string form of `$switch` starts with `ok-`.\n\n\n#### Scope and `$switch`\n\nInside a `*switch` block, and inside a `*case.break` branch:\n\n- The child scope is created as a shallow copy of the parent scope plus `$switch`.\n- The case expression for `*case.break` is evaluated with `$switch` available.\n- The branch content is rendered with the same scope:\n\n  - All original data (for example `$data`, `$root`, `$parent`) remain available.\n  - `$switch` holds the value of the `*switch` expression.\n  - `*case.break` itself does not create new variables; it only controls whether and where this branch starts and whether execution stops afterward.\n\nYou can freely refer to `$switch` inside the branch body:\n\n```html\n<div *switch=\"status\">\n  <p *case.break=\"'error'\">\n    Error: <span *print=\"$switch\"></span>\n  </p>\n  <p *default>OK</p>\n</div>\n```\n\n\n#### Relationship to *case, *default and *break\n\nWithin a `*switch` block:\n\n- `*case` and `n-case`:\n  - Define branches that start rendering when their expression matches `$switch`.\n  - Do not stop fallthrough by themselves.\n\n- `*default` and `n-default`:\n  - Define a branch that starts rendering only if no earlier `*case` or `*case.break` has matched.\n  - Do not stop fallthrough by themselves.\n\n- `*break` and `n-break` on a branch:\n  - If placed together with `*case` or `*default`, they cause the switch to stop after rendering that branch.\n  - This is equivalent to \"case + break\" or \"default + break\" in JavaScript.\n\n- `*case.break` and `n-case.break`:\n  - Combine `*case` and `*break` into a single directive.\n  - They match using the same rules as `*case`.\n  - When the branch is rendered, they also act as a break marker, stopping the switch from evaluating later branches.\n\nSyntactic equivalence:\n\n- The following two branches are equivalent in behavior:\n\n  - `*case.break=\"expr\"`\n\n  - `*case=\"expr\" *break`\n\nSercrod implementation treats both forms in the same way:\n\n- For matching, it uses the `*case` / `n-case` / `*case.break` / `n-case.break` expression.\n- For breaking, it checks for `*break`, `n-break`, `*case.break`, or `n-case.break` on the original branch element.\n\n\n#### Fallthrough and break behavior\n\n`*switch` in Sercrod uses a DOM-ordered, fallthrough model similar to JavaScript:\n\n- Sercrod locates the first branch that should start rendering.\n- From that branch onward, it renders every subsequent `*case` / `*default` branch until a break is detected.\n- A break is detected when the original branch element has any of:\n  - `*break`\n  - `n-break`\n  - `*case.break`\n  - `n-case.break`\n\n`*case.break` is therefore the most concise way to express:\n\n- \"Start rendering here when this condition holds.\"\n- \"Do not allow any further fallthrough.\"\n\nExample showing fallthrough vs break:\n\n```html\n<div *switch=\"state\">\n  <p *case=\"'warm'\">Warm</p>\n  <p *case=\"'hot'\">Hot</p>\n  <p *default>Default</p>\n</div>\n\n<div *switch=\"state\">\n  <p *case=\"'warm'\">Warm</p>\n  <p *case.break=\"'hot'\">Hot only</p>\n  <p *default>Default</p>\n</div>\n```\n\n- If `state` is `\"hot\"`:\n  - In the first block:\n    - The `*case=\"'hot'\"` branch starts rendering.\n    - There is no break, so the `*default` branch also renders (fallthrough).\n  - In the second block:\n    - The `*case.break=\"'hot'\"` branch starts rendering and also stops the switch.\n    - The `*default` branch does not render.\n\n\n#### Best practices\n\n- Use `*case.break` when you explicitly want \"no fallthrough\":\n\n  - It is clearer than writing `*case=\"expr\" *break`.\n  - It makes it obvious that this branch is terminal inside the switch.\n\n- Keep case expressions simple:\n\n  - Prefer direct comparisons or small membership sets.\n  - Move complex logic to a helper function and call it from the case expression.\n\n- Use `$switch` consistently:\n\n  - When writing more advanced logic, prefer using `$switch` inside the expression rather than repeating the switch expression.\n  - For example, `*case.break=\"(v) => v > 100\"` can be easier to read than rewriting the whole expression.\n\n- Avoid redundant `*break` attributes:\n\n  - `*case.break=\"expr\" *break` is allowed but redundant; the break is already implied by `*case.break`.\n  - For clarity, choose either `*case=\"expr\" *break` or `*case.break=\"expr\"`, not both.\n\n- Do not rely on non-case children inside `*switch`:\n\n  - Elements without any of `*case`, `n-case`, `*case.break`, `n-case.break`, `*default`, or `n-default` are ignored by the switch logic.\n  - Wrap branch content in dedicated case/default elements.\n\n\n#### Additional examples\n\nSimple status mapping with no fallthrough:\n\n```html\n<serc-rod id=\"status-app\" data='{\"status\":\"error\"}'>\n  <div *switch=\"$data.status\">\n    <p *case=\"'ok'\">All good</p>\n    <p *case.break=\"'error'\">Something went wrong</p>\n    <p *default>Unknown status: <span *print=\"$switch\"></span></p>\n  </div>\n</serc-rod>\n```\n\nUsing a function as a case expression:\n\n```html\n<serc-rod id=\"range-switch\" data='{\"value\": 42}'>\n  <div *switch=\"$data.value\">\n    <p *case=\"(v) => v < 0\">Negative</p>\n    <p *case=\"(v) => v === 0\">Zero</p>\n    <p *case.break=\"(v) => v > 0\">Positive (and stop)</p>\n    <p *default>Unreachable default</p>\n  </div>\n</serc-rod>\n```\n\nMembership via list syntax:\n\n```html\n<serc-rod id=\"role-switch\" data='{\"role\":\"admin\"}'>\n  <div *switch=\"$data.role\">\n    <p *case=\"'guest' | 'anonymous'\">Guest mode</p>\n    <p *case.break=\"'user' | 'admin'\">Signed-in user</p>\n    <p *default>Other role: <span *print=\"$switch\"></span></p>\n  </div>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*case.break` and `n-case.break` are only meaningful under a `*switch` or `n-switch` host.\n- The case expression of `*case.break` is evaluated with access to `$switch` and all outer scope variables.\n- `*case.break` uses exactly the same matching rules as `*case`; it only differs by also acting as a break.\n- A branch with `*case.break` will stop fallthrough even if it appears before `*default`, so that default branch will not run.\n- For loop control inside `*for` or `*each`, use `*break` rather than `*case.break`; they are separate directives with different purposes.\n",
  "case.break": "### *case.break\n\n#### Summary\n\n`*case.break` is a variant of `*case` used inside `*switch` or `n-switch`.\nIt behaves like `*case` when matching against the switch value, and in addition it stops evaluating all later `*case` and `*default` branches in the same `*switch` block.\nIt is effectively syntactic sugar for `*case=\"expr\" *break` on the same element.\n\nAlias:\n\n- `*case.break`\n- `n-case.break`\n\nBoth names share the same behavior.\n\n\n#### Basic example\n\nA simple status switch that does not fall through past the matching branch:\n\n```html\n<serc-rod id=\"app\" data='{\"status\": \"ready\"}'>\n  <div *switch=\"$data.status\">\n    <p *case=\"'pending'\">Pending...</p>\n    <p *case.break=\"'ready'\">Ready</p>\n    <p *default>Unknown status</p>\n  </div>\n</serc-rod>\n```\n\nBehavior:\n\n- The host `<div *switch>` evaluates `$data.status` as the switch value.\n- The second branch `*case.break=\"'ready'\"` matches that value.\n- That branch is rendered.\n- Because it is a `*case.break` branch, Sercrod stops there and does not render the `*default` branch.\n\n\n#### Behavior\n\n`*case.break` participates only as a child of a `*switch` or `n-switch` host:\n\n- It is recognized only on direct element children of a node with `*switch` or `n-switch`.\n- Outside of a `*switch` context, `*case.break` and `n-case.break` are not interpreted by the switch logic and have no special effect.\n\nWithin a `*switch` block:\n\n- Sercrod walks child elements in DOM order.\n- It finds the first branch that should start rendering:\n  - A matching `*case` or `*case.break`.\n  - Or a `*default` when no earlier case has matched.\n- Once rendering has started, Sercrod is in a fallthrough mode:\n  - Every subsequent `*case` or `*default` branch is also rendered, until a break-type directive is seen.\n- `*case.break` is both:\n  - A case branch (its expression is matched against the switch value).\n  - A break marker (it stops processing any later branches in this `*switch`).\n\nConceptually:\n\n- `*case=\"expr\"` means \"start here if `expr` matches; then continue rendering later branches.\"\n- `*case.break=\"expr\"` means \"start here if `expr` matches; render this branch; then stop, with no further fallthrough.\"\n\n\n#### Evaluation timing\n\nWithin the host element that has `*switch` or `n-switch`:\n\n1. Sercrod evaluates the switch expression once:\n\n   - It reads the attribute value from `*switch` or `n-switch`.\n   - It evaluates that expression and stores the result as the switch value.\n   - The value is then exposed to children as `$switch`.\n\n2. Sercrod iterates over child elements in DOM order.\n\n3. For each child:\n\n   - If it does not have `*case`, `n-case`, `*case.break`, `n-case.break`, `*default`, or `n-default`, it is ignored by the switch logic and never rendered by this `*switch`.\n   - If no branch has started yet:\n     - `*case` / `n-case` / `*case.break` / `n-case.break`:\n       - Sercrod evaluates the case expression and compares it to the switch value.\n       - If it matches, rendering starts from this branch.\n     - `*default` / `n-default`:\n       - If no earlier branch has matched, rendering starts from this default.\n\n4. Once a starting branch is chosen (including `*case.break`):\n\n   - Sercrod clones that branch, removes control attributes, and renders it with the augmented child scope (including `$switch`).\n   - It then continues in fallthrough mode (see below) unless a break is detected.\n\n\n#### Case expression semantics\n\nThe expression given to `*case.break` is interpreted in the same way as for `*case`.\n\nGiven:\n\n- `switchVal` as the evaluated switch value.\n- `raw` as the attribute string from `*case.break=\"raw\"`.\n\nSercrod applies the following rules:\n\n1. It first tries to evaluate the case expression as a normal Sercrod expression, with `$switch` injected into the scope:\n\n   - If the result is a function, Sercrod calls `fn(switchVal, scope)` and uses the truthiness of the return value.\n   - If the result is a `RegExp`, Sercrod tests it against `String(switchVal)`.\n   - If the result is an array, Sercrod checks whether any element is strictly equal to `switchVal` (using `Object.is` semantics).\n   - If the result is an object with a `has` method (for example a `Set`), Sercrod calls `set.has(switchVal)` and uses the result.\n   - If the result is a boolean, the boolean itself decides the match.\n   - If the result is a string, number, or bigint, Sercrod compares it to `switchVal` using strict identity.\n   - For other result types, this step does not produce a match.\n\n2. If evaluation throws, or if you intentionally keep the expression as a simple string, Sercrod falls back to a token list:\n\n   - It splits the raw string on commas or pipes: `\"a|b,c\"` becomes tokens like `\"a\"`, `\"b\"`, `\"c\"`.\n   - It trims empty tokens.\n   - For each token `t`:\n     - It tries to evaluate `t` as an expression.\n     - If that fails, it uses `t` itself as a string literal.\n     - It compares the result to `switchVal` using strict identity.\n   - If any token matches, the case matches.\n\nUseful patterns:\n\n- Direct equality:\n\n  - `*case.break=\"'ready'\"` matches when `$switch` is strictly `\"ready\"`.\n\n- Membership in a small set:\n\n  - `*case.break=\"'pending' | 'queued'\"`\n    Matches when `$switch` is `\"pending\"` or `\"queued\"`.\n\n- Function-based matching:\n\n  - `*case.break=\"(val) => val > 0\"`\n    Matches if the function returns a truthy value when called with `switchVal` and the current scope.\n\n- Regular expression matching:\n\n  - `*case.break=\"/^ok-/\"` (assuming the expression is parsed to a RegExp)\n    Matches when the string form of `$switch` starts with `ok-`.\n\n\n#### Scope and `$switch`\n\nInside a `*switch` block, and inside a `*case.break` branch:\n\n- The child scope is created as a shallow copy of the parent scope plus `$switch`.\n- The case expression for `*case.break` is evaluated with `$switch` available.\n- The branch content is rendered with the same scope:\n\n  - All original data (for example `$data`, `$root`, `$parent`) remain available.\n  - `$switch` holds the value of the `*switch` expression.\n  - `*case.break` itself does not create new variables; it only controls whether and where this branch starts and whether execution stops afterward.\n\nYou can freely refer to `$switch` inside the branch body:\n\n```html\n<div *switch=\"status\">\n  <p *case.break=\"'error'\">\n    Error: <span *print=\"$switch\"></span>\n  </p>\n  <p *default>OK</p>\n</div>\n```\n\n\n#### Relationship to *case, *default and *break\n\nWithin a `*switch` block:\n\n- `*case` and `n-case`:\n  - Define branches that start rendering when their expression matches `$switch`.\n  - Do not stop fallthrough by themselves.\n\n- `*default` and `n-default`:\n  - Define a branch that starts rendering only if no earlier `*case` or `*case.break` has matched.\n  - Do not stop fallthrough by themselves.\n\n- `*break` and `n-break` on a branch:\n  - If placed together with `*case` or `*default`, they cause the switch to stop after rendering that branch.\n  - This is equivalent to \"case + break\" or \"default + break\" in JavaScript.\n\n- `*case.break` and `n-case.break`:\n  - Combine `*case` and `*break` into a single directive.\n  - They match using the same rules as `*case`.\n  - When the branch is rendered, they also act as a break marker, stopping the switch from evaluating later branches.\n\nSyntactic equivalence:\n\n- The following two branches are equivalent in behavior:\n\n  - `*case.break=\"expr\"`\n\n  - `*case=\"expr\" *break`\n\nSercrod implementation treats both forms in the same way:\n\n- For matching, it uses the `*case` / `n-case` / `*case.break` / `n-case.break` expression.\n- For breaking, it checks for `*break`, `n-break`, `*case.break`, or `n-case.break` on the original branch element.\n\n\n#### Fallthrough and break behavior\n\n`*switch` in Sercrod uses a DOM-ordered, fallthrough model similar to JavaScript:\n\n- Sercrod locates the first branch that should start rendering.\n- From that branch onward, it renders every subsequent `*case` / `*default` branch until a break is detected.\n- A break is detected when the original branch element has any of:\n  - `*break`\n  - `n-break`\n  - `*case.break`\n  - `n-case.break`\n\n`*case.break` is therefore the most concise way to express:\n\n- \"Start rendering here when this condition holds.\"\n- \"Do not allow any further fallthrough.\"\n\nExample showing fallthrough vs break:\n\n```html\n<div *switch=\"state\">\n  <p *case=\"'warm'\">Warm</p>\n  <p *case=\"'hot'\">Hot</p>\n  <p *default>Default</p>\n</div>\n\n<div *switch=\"state\">\n  <p *case=\"'warm'\">Warm</p>\n  <p *case.break=\"'hot'\">Hot only</p>\n  <p *default>Default</p>\n</div>\n```\n\n- If `state` is `\"hot\"`:\n  - In the first block:\n    - The `*case=\"'hot'\"` branch starts rendering.\n    - There is no break, so the `*default` branch also renders (fallthrough).\n  - In the second block:\n    - The `*case.break=\"'hot'\"` branch starts rendering and also stops the switch.\n    - The `*default` branch does not render.\n\n\n#### Best practices\n\n- Use `*case.break` when you explicitly want \"no fallthrough\":\n\n  - It is clearer than writing `*case=\"expr\" *break`.\n  - It makes it obvious that this branch is terminal inside the switch.\n\n- Keep case expressions simple:\n\n  - Prefer direct comparisons or small membership sets.\n  - Move complex logic to a helper function and call it from the case expression.\n\n- Use `$switch` consistently:\n\n  - When writing more advanced logic, prefer using `$switch` inside the expression rather than repeating the switch expression.\n  - For example, `*case.break=\"(v) => v > 100\"` can be easier to read than rewriting the whole expression.\n\n- Avoid redundant `*break` attributes:\n\n  - `*case.break=\"expr\" *break` is allowed but redundant; the break is already implied by `*case.break`.\n  - For clarity, choose either `*case=\"expr\" *break` or `*case.break=\"expr\"`, not both.\n\n- Do not rely on non-case children inside `*switch`:\n\n  - Elements without any of `*case`, `n-case`, `*case.break`, `n-case.break`, `*default`, or `n-default` are ignored by the switch logic.\n  - Wrap branch content in dedicated case/default elements.\n\n\n#### Additional examples\n\nSimple status mapping with no fallthrough:\n\n```html\n<serc-rod id=\"status-app\" data='{\"status\":\"error\"}'>\n  <div *switch=\"$data.status\">\n    <p *case=\"'ok'\">All good</p>\n    <p *case.break=\"'error'\">Something went wrong</p>\n    <p *default>Unknown status: <span *print=\"$switch\"></span></p>\n  </div>\n</serc-rod>\n```\n\nUsing a function as a case expression:\n\n```html\n<serc-rod id=\"range-switch\" data='{\"value\": 42}'>\n  <div *switch=\"$data.value\">\n    <p *case=\"(v) => v < 0\">Negative</p>\n    <p *case=\"(v) => v === 0\">Zero</p>\n    <p *case.break=\"(v) => v > 0\">Positive (and stop)</p>\n    <p *default>Unreachable default</p>\n  </div>\n</serc-rod>\n```\n\nMembership via list syntax:\n\n```html\n<serc-rod id=\"role-switch\" data='{\"role\":\"admin\"}'>\n  <div *switch=\"$data.role\">\n    <p *case=\"'guest' | 'anonymous'\">Guest mode</p>\n    <p *case.break=\"'user' | 'admin'\">Signed-in user</p>\n    <p *default>Other role: <span *print=\"$switch\"></span></p>\n  </div>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*case.break` and `n-case.break` are only meaningful under a `*switch` or `n-switch` host.\n- The case expression of `*case.break` is evaluated with access to `$switch` and all outer scope variables.\n- `*case.break` uses exactly the same matching rules as `*case`; it only differs by also acting as a break.\n- A branch with `*case.break` will stop fallthrough even if it appears before `*default`, so that default branch will not run.\n- For loop control inside `*for` or `*each`, use `*break` rather than `*case.break`; they are separate directives with different purposes.\n",
  "case": "### *case / *case.break\n\n#### Summary\n\n`*case` marks a branch inside a `*switch` / `n-switch` block.\nThe branch is rendered when the `*switch` expression matches the case condition.\n`*case.break` is sugar for \"case plus break\": it behaves like `*case`, and additionally stops evaluating later branches.\n\nKey points:\n\n- `*case` and `*case.break` only have an effect as direct children of an element with `*switch` or `n-switch`.\n- When a case matches, rendering starts at that branch and continues for later siblings (\"fallthrough\") until a break is encountered.\n- Branches without any `*case`, `*case.break`, `*default`, or `n-default` are ignored by the switch engine.\n\n\n#### Basic example\n\nSimple switch over a status value:\n\n```html\n<serc-rod id=\"app\" data='{\"status\":\"ready\"}'>\n  <div *switch=\"status\">\n    <p *case=\"'idle'\">Idle</p>\n    <p *case=\"'ready'\">Ready</p>\n    <p *default>Unknown</p>\n  </div>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<div *switch=\"status\">` itself is not rendered.\n- `status` is evaluated once, and its value is exposed to children as `$switch`.\n- `*case=\"'ready'\"` matches the value `\"ready\"`, so that `<p>` is rendered.\n- No `*break` is present, but there are no later branches in this example, so nothing else is rendered.\n\n\n#### Behavior\n\nInside a `*switch` / `n-switch` host:\n\n- Only direct element children that carry one of:\n  - `*case`\n  - `n-case`\n  - `*case.break`\n  - `n-case.break`\n  - `*default`\n  - `n-default`\n  are treated as switch branches.\n- Other children (elements without these attributes, or deeper descendants) are ignored by the switch engine.\n\nSelection and fallthrough:\n\n- The switch host evaluates its own `*switch` / `n-switch` expression to produce a value `switchVal`.\n- It then scans its direct children in DOM order.\n- The first child that satisfies either:\n  - a matching `*case` / `*case.break` / `n-case` / `n-case.break`, or\n  - a `*default` / `n-default` when no previous branch has matched,\n  becomes the starting point.\n- That starting branch and all later branches are rendered (\"fallthrough\") until a break is hit.\n\nCloning and attributes:\n\n- For each branch that is rendered, the original child element is cloned.\n- On the clone, control attributes are removed:\n  - `*case`, `n-case`, `*case.break`, `n-case.break`\n  - `*default`, `n-default`\n  - `*break`, `n-break`\n- The clone is then passed to Sercrod's normal rendering pipeline with a scope that includes `$switch`.\n\n\n#### Case expression semantics\n\n`*case` and `*case.break` both use the same matching engine.\nThey evaluate their attribute value in the current scope (augmented with `$switch`) and compare it to the `*switch` value.\n\nThe raw attribute string is processed as follows:\n\n1. Sercrod first tries to evaluate the full expression:\n\n   - The expression is evaluated using `eval_expr(raw, {...scope, $switch: switchVal})`.\n   - If evaluation succeeds, the resulting value `v` is matched by type.\n   - If evaluation throws, Sercrod falls back to a string-based list mechanism (see below).\n\n2. If evaluation succeeded, the result `v` is matched according to its type:\n\n   - Function:\n\n     - If `typeof v === \"function\"`, Sercrod calls `v(switchVal, scope)`.\n     - If the call returns a truthy value, the case matches.\n\n   - Regular expression:\n\n     - If `v` is a `RegExp`, Sercrod tests `v.test(String(switchVal))`.\n     - If the test returns `true`, the case matches.\n\n   - Array:\n\n     - If `Array.isArray(v)` is true, the case matches when any array element is strictly equal to `switchVal` (using `Object.is`).\n\n   - Objects with `has`:\n\n     - If `v` is an object and has a `has` method (for example a `Set`), Sercrod calls `v.has(switchVal)`.\n     - If `has` returns truthy, the case matches.\n\n   - Boolean:\n\n     - If `v` is a boolean, its value is used directly.\n     - `true` means the case matches; `false` means it does not.\n\n   - Primitive string / number / bigint:\n\n     - For `typeof v` in `{ \"string\", \"number\", \"bigint\" }`, Sercrod compares `v` and `switchVal` with `Object.is`.\n     - `Object.is` is used rather than `===`, so `NaN` matches `NaN`, and `+0` and `-0` are distinguished.\n\n   - Any other type:\n\n     - If `v` does not fit any of the above categories, the case does not match.\n\n3. If evaluation failed (expression threw), Sercrod falls back to a token list:\n\n   - The raw string is split on commas and pipes: `/[,|]/`.\n   - Each token is trimmed; empty tokens are ignored.\n   - For each token `t`:\n     - Sercrod tries to evaluate `t` as an expression.\n     - If that evaluation fails, it falls back to treating `t` as a string literal.\n     - The token value `vv` is then compared to `switchVal` with `Object.is`.\n     - If any `vv` matches `switchVal`, the case matches.\n\nThis means you can write all of the following:\n\n- Simple literal:\n\n  ```html\n  <p *case=\"'ready'\">Ready</p>\n  ```\n\n- Multiple values with an array:\n\n  ```html\n  <p *case=\"['ready','done']\">Finished</p>\n  ```\n\n- Regular expression:\n\n  ```html\n  <p *case=\"/^admin-/\">Admin section</p>\n  ```\n\n- Predicate function:\n\n  ```html\n  <p *case=\"(value, scope) => value > 10\">Large</p>\n  ```\n\n- Comma- or pipe-separated list:\n\n  ```html\n  <p *case=\"'ready' | 'done'\">Finished</p>\n  <p *case=\"'draft, pending'\">Not final</p>\n  ```\n\n\n#### Evaluation timing\n\nWithin the host that has `*switch` / `n-switch`:\n\n- The `*switch` expression is evaluated once to obtain `switchVal`.\n- This happens as part of the structural rendering phase, after any host-level `*if` / `*elseif` / `*else` chain has been resolved.\n- The `*switch` host does not render itself; only its children (cases and default) are rendered.\n- For each candidate case branch, the case expression is evaluated when needed:\n  - Cases are evaluated in DOM order, stopping at the first branch that matches or the first default that is reached with no matches.\n  - Once a starting branch is found, later cases are not evaluated for matching; they participate only in fallthrough rendering.\n\n\n#### Execution model\n\nAt a high level, the switch engine works like this for `*case` and `*case.break`:\n\n1. Evaluate `switchVal` from the host's `*switch` / `n-switch` expression.\n2. Prepare a child scope:\n   - Clone the current scope.\n   - Inject `$switch = switchVal`.\n3. Scan the host's direct element children in DOM order.\n4. For each child `c`:\n   - Determine whether it is:\n     - a `*case` / `n-case` / `*case.break` / `n-case.break` branch, or\n     - a `*default` / `n-default` branch, or\n     - neither.\n   - Ignore children that are neither case nor default.\n5. Before rendering has started (no branch selected yet):\n   - If `c` is a default branch:\n     - Start rendering from this branch (`falling = true`).\n   - Else if `c` is a case branch:\n     - Evaluate the case expression with `$switch` in scope.\n     - If the match succeeds, start rendering from this branch (`falling = true`).\n     - If it does not match, skip this child.\n6. After rendering has started (`falling = true`):\n   - Clone `c`.\n   - Strip control attributes (`*case`, `n-case`, `*case.break`, `n-case.break`, `*default`, `n-default`, `*break`, `n-break`) from the clone.\n   - Render the clone with the child scope (which includes `$switch`).\n   - Check for break:\n     - If the original child `c` has `*break`, `n-break`, `*case.break`, or `n-case.break`, stop the switch here and do not render later branches.\n\nAs a result:\n\n- `*case` without `*break` participates in fallthrough: later branches are also rendered.\n- `*case.break` behaves as `*case` plus an implicit `*break`.\n- A branch with `*break` (or `n-break`) stops rendering after that branch, regardless of the value of the `*break` attribute.\n\n\n#### Variable creation and scope layering\n\n`*case` and `*case.break` do not create new local variables by themselves.\nInstead, they rely on the scope prepared by the switch host:\n\n- `$switch` is injected by the switch host and is available in:\n  - case expressions.\n  - default branches.\n  - the body content of all rendered branches.\n- All existing scope variables (from the Sercrod host and outer scopes) remain available.\n- Case expressions may introduce additional values via normal Sercrod expressions (for example, calling functions or reading from the data model), but `*case` does not persist any new names into the shared scope.\n\nThe scope used inside each branch is effectively:\n\n- A shallow clone of the incoming scope, plus `$switch`.\n\n\n#### Use with *default and *break\n\n`*case` interacts closely with `*default` and `*break` inside the same `*switch` block.\n\n- `*default` / `n-default`:\n\n  - A default branch is picked only if no earlier case branch has matched.\n  - If a default branch is the starting point, it participates in fallthrough exactly like any other branch.\n  - The runtime does not enforce that there is only one default; for clarity, you should keep at most one default per switch.\n\n- `*break` / `n-break` on a branch:\n\n  - In a switch context, `*break` is treated as a flag, not as a conditional:\n    - If a branch carries `*break` or `n-break`, the switch stops after that branch is rendered.\n    - The attribute value is not inspected by the switch engine.\n  - This is separate from how `*break` is used in loops; here it simply means \"stop fallthrough now\".\n\n- `*case.break` / `n-case.break`:\n\n  - Sugar for `*case=\"expr\" *break`.\n  - The expression part participates in case matching exactly like `*case`.\n  - The presence of `.break` also marks the branch as a break point:\n    - If it is selected as the starting branch, rendering stops after this branch.\n    - If rendering has already started earlier and execution reaches a `.break` branch in fallthrough, it will render that branch and then stop.\n\nExample with fallthrough and break:\n\n```html\n<serc-rod id=\"app\" data='{\"level\":2}'>\n  <div *switch=\"level\">\n    <p *case=\"1\">Level 1</p>\n    <p *case=\"2\">Level 2</p>\n    <p *case=\"3\" *break>Level 3 (stop here)</p>\n    <p *default>Level is 4 or more</p>\n  </div>\n</serc-rod>\n```\n\n- When `level` is `2`:\n  - Rendering starts at `*case=\"2\"`.\n  - The branch for `2` is rendered.\n  - The branch `*case=\"3\" *break` is also rendered.\n  - Because of `*break` on the `3` branch, the `*default` branch is not rendered.\n\n\n#### Use with conditionals and loops\n\nBranches can freely combine `*case` with other directives:\n\n- `*if` on the same element:\n\n  - The case still participates in branch selection based on its case expression.\n  - Once selected, the cloned element is rendered by the normal pipeline; any `*if` on that element is evaluated as usual.\n  - This means a selected case can still be hidden by a false `*if`.\n\n- `*for`, `*each`, and other structural directives inside the branch:\n\n  - You can place loops inside the body of a branch:\n\n    ```html\n    <div *switch=\"$data.mode\">\n      <section *case=\"'list'\">\n        <ul *each=\"item of items\">\n          <li *print=\"item.label\"></li>\n        </ul>\n      </section>\n      <section *default>\n        <p>No list available.</p>\n      </section>\n    </div>\n    ```\n\n  - You can also put `*for` or `*each` on the same element as `*case`:\n\n    ```html\n    <div *switch=\"view\">\n      <ul *case=\"'list'\" *each=\"item of items\">\n        <li *print=\"item.label\"></li>\n      </ul>\n      <p *default>Select a view.</p>\n    </div>\n    ```\n\n    In this pattern:\n\n    - The `<ul>` is chosen as the branch when `view` is `\"list\"`.\n    - After selection, `*each` is evaluated on the cloned `<ul>` as usual.\n\n- Nested switches:\n\n  - A case branch can itself contain another `*switch` / `n-switch` block; the inner switch sees its own `$switch` value.\n  - Outer `$switch` remains accessible via the parent scope if you store it in data or pass it through.\n\n\n#### Best practices\n\n- Prefer simple case expressions where possible:\n\n  - Literal values (`'ready'`, `1`, `\"admin\"`) keep the logic easy to read.\n  - Arrays, sets, regular expressions, and predicate functions are powerful but should be used where they clearly add value.\n\n- Use `*case.break` for the common \"match and stop\" pattern:\n\n  - This keeps the markup compact:\n\n    ```html\n    <p *case.break=\"'ready'\">Ready</p>\n    ```\n\n- Use fallthrough intentionally:\n\n  - If you omit breaks, later branches will be rendered as part of the same switch pass.\n  - Use this to group related output or to build a sequence of messages.\n  - Avoid \"accidental fallthrough\" by placing `*break` or using `*case.break` where you expect a strict one-branch behavior.\n\n- Keep branch ordering explicit:\n\n  - Sercrod always processes branches in DOM order.\n  - Place more specific cases before more general ones when using patterns such as regular expressions or predicate functions.\n\n- Keep switch blocks shallow:\n\n  - Since only direct children of the `*switch` host participate as branches, keep the branch markers (`*case`, `*case.break`, `*default`) at the top level under the host.\n  - Nested `*case` under additional wrappers are not seen by the switch engine.\n\n\n#### Additional examples\n\nMultiple values and regular expression:\n\n```html\n<serc-rod id=\"router\" data='{\"path\":\"/admin/users\"}'>\n  <div *switch=\"path\">\n    <p *case=\"['/', '/home']\">Home</p>\n    <p *case=\"/^\\\\/admin\\\\//\">Admin area</p>\n    <p *default>Unknown path</p>\n  </div>\n</serc-rod>\n```\n\nPredicate function:\n\n```html\n<serc-rod id=\"grader\" data='{\"score\":82}'>\n  <div *switch=\"score\">\n    <p *case=\"value => value >= 90\">Grade A</p>\n    <p *case=\"value => value >= 80\">Grade B</p>\n    <p *case=\"value => value >= 70\">Grade C</p>\n    <p *default>Needs improvement</p>\n  </div>\n</serc-rod>\n```\n\nHere the first predicate that returns `true` determines the starting branch, and fallthrough behavior applies as usual.\n\n\n#### Notes\n\n- `*case`, `n-case`, `*case.break`, and `n-case.break` are only meaningful as direct children of a `*switch` / `n-switch` host.\n  - Outside that context, they are treated as normal attributes and have no special effect.\n\n- `$switch` is injected by the switch host and is available:\n  - Inside case expressions.\n  - Inside default branches.\n  - Inside the rendered content of all selected branches.\n\n- The runtime does not enforce a single default branch.\n  - For readability and to avoid surprising fallthrough, it is recommended to keep at most one `*default` / `n-default` per switch.\n\n- In a switch context, `*break` / `n-break` on a branch is treated as a flag; the attribute value is not inspected for match conditions.\n  - Expression-based break behavior belongs to loop contexts and is documented separately.\n\n- `*case.break` / `n-case.break` are pure sugar for \"case plus break\".\n  - They share the same expression semantics as `*case` / `n-case`.\n  - They additionally guarantee that the switch stops after rendering that branch.\n",
  "compose": "### *compose\n\n#### Summary\n\n`*compose` composes the contents of a single element from the result of an expression and writes it into the element as HTML via the `html` filter.\nIt is the structural counterpart of `*innerHTML` and has an alias `n-compose`.\n\nKey ideas:\n\n- The expression is evaluated once per render in the normal Sercrod expression sandbox.\n- The result is passed through `Sercrod._filters.html(raw, ctx)` and then assigned to `innerHTML` of the rendered element.\n- `*compose` does not create new local variables; it only consumes the current scope.\n- At runtime it shares the same pipeline as `*innerHTML`, but projects are expected to give `*compose` a higher-level meaning (for example, composing from templates, partials, or hosts) by customizing the `html` filter.\n\nAlias:\n\n- `*compose` and `n-compose` are aliases; they accept the same syntax and behave identically.\n\n\n#### Basic example\n\nA basic composition from a precomputed HTML string:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"cardHtml\": \"<div class=&quot;card&quot><h2>Title</h2><p>Body</p></div>\"\n}'>\n  <section class=\"wrapper\">\n    <div class=\"slot\" *compose=\"cardHtml\"></div>\n  </section>\n</serc-rod>\n```\n\nBehavior:\n\n- Sercrod evaluates `cardHtml` in the current scope.\n- The result is passed through `Sercrod._filters.html` and becomes the `innerHTML` of the `<div class=\"slot\">`.\n- The `<section>` and `<div>` themselves are created as normal elements; only the `<div>`'s content is supplied by `*compose`.\n\n\n#### Behavior\n\nAt a high level, `*compose`:\n\n- Targets the **children** of the host element, not the element itself.\n- Evaluates its expression once per render of the host.\n- Treats `null` and `false` as \"empty content\".\n- Uses the `html` filter to transform the raw result into the final HTML string.\n\nDetails:\n\n- Host element:\n\n  - The host element is cloned and appended to the parent as usual.\n  - All other bindings on the host (`:class`, `:style`, attribute bindings, `n-class`, `n-style`, etc.) still apply.\n\n- Content:\n\n  - The expression on `*compose` is evaluated in the current scope.\n  - The value is normalized:\n\n    - If `v` is `null` or `false`, it is treated as `\"\"`.\n    - Otherwise the value is passed as-is to the `html` filter.\n\n  - The final `innerHTML` of the rendered element is:\n\n    - `el.innerHTML = Sercrod._filters.html(raw, { el, expr, scope })`.\n\n- Default filter:\n\n  - By default, `Sercrod._filters.html` returns `raw` as-is.\n  - That means, without customization, `*compose` behaves like \"evaluate an expression and insert the result as raw HTML\".\n\n- Interaction with children:\n\n  - `*compose` runs **before** Sercrod recursively renders the template's child nodes.\n  - After `innerHTML` is set, Sercrod still walks `node.childNodes` (the template children) and renders them into the same element.\n  - In common usage, you normally give the host no children when using `*compose`, or treat those children as optional \"extra\" content.\n\n\n#### Evaluation timing\n\nWithin `_renderElement`, `*compose` takes part in the following order:\n\n- The host must first survive structural directives:\n\n  - `*if` / `n-if` on the same element are evaluated earlier; if they fail, the element is not rendered and `*compose` is not reached.\n  - Structural loops like `*for` / `n-for` and `*each` / `n-each` are processed earlier and return after they finish, so `*compose` never runs on elements where those loops took effect.\n\n- Scalar text directives have priority:\n\n  - `*print` / `n-print` and `*textContent` / `n-textContent` are evaluated before `*compose`.\n  - When any of those directives run, they set `textContent`, append the element, and return; `*compose` is skipped entirely.\n\n- Static text with `%...%` expansion also runs before `*compose`:\n\n  - If there is exactly one text child and it contains `%` markers, Sercrod uses `_expand_text`, appends the result, and returns.\n\n- Only when the element has not been handled by the above cases does Sercrod check for `*compose` / `n-compose` or `*innerHTML` / `n-innerHTML`.\n\nAfter `*compose`:\n\n- Sercrod continues with:\n\n  - `*class` / `n-class`, `*style` / `n-style`.\n  - Other attribute bindings.\n  - Recursive rendering of `node.childNodes`.\n  - Any `postApply` work for things like `<select>`.\n\nImplications:\n\n- `*compose` is **non-structural**; it does not short-circuit rendering.\n- It is best thought of as a \"content injection\" step in the later part of the element pipeline.\n\n\n#### Execution model\n\nConceptually, the engine behaves as follows when it hits `*compose`:\n\n1. Decide which attribute to read:\n\n   - If the element has any of `*compose`, `n-compose`, `*innerHTML`, or `n-innerHTML`, one of those names is chosen as `srcAttr`.\n   - Only one attribute is used; others are ignored for this step.\n\n2. Read the expression:\n\n   - `expr = node.getAttribute(srcAttr)`.\n\n3. Evaluate the expression:\n\n   - `v = eval_expr(expr, scope, { el: node, mode: \"compose\" or \"innerHTML\" })`.\n   - Any variables in the expression are resolved from the merged scope and special helpers.\n\n4. Normalize the result:\n\n   - If `v` is `null` or `false`, treat it as `\"\"`.\n   - Otherwise, use it as `raw`.\n\n5. Call the `html` filter:\n\n   - `ctx = { el, expr, scope }`.\n   - `htmlString = Sercrod._filters.html(raw, ctx)`.\n\n6. Write into `innerHTML`:\n\n   - `el.innerHTML = htmlString`.\n\n7. On error:\n\n   - If evaluation or filtering throws, Sercrod logs a `[Sercrod warn] html filter:` message (when `error.warn` is enabled).\n   - `el.innerHTML` is set to an empty string.\n\nAfter that, the standard rendering pipeline continues, including child rendering and post-apply actions.\n\n\n#### Integration with the html filter\n\n`*compose` is tightly coupled with the `html` filter:\n\n- Default behavior:\n\n  - The built-in `html` filter is:\n\n    - `html: (raw, ctx) => raw`\n\n  - So by default, `*compose=\"expr\"` is equivalent to:\n\n    - `el.innerHTML = exprResult`.\n\n- Custom behavior:\n\n  - Before Sercrod starts, you can define `window.__Sercrod_filter.html` to override the `html` filter:\n\n    - `Sercrod._filters` is initialized by merging the built-in filters with `window.__Sercrod_filter` once at startup.\n    - This is the intended hook for project-specific HTML composition.\n\n  - The `ctx` object gives the filter access to:\n\n    - `ctx.el` - the rendered element.\n    - `ctx.expr` - the raw expression string.\n    - `ctx.scope` - the evaluation scope at the time of the call.\n\n  - A project can:\n\n    - Treat specific values as template or partial names.\n    - Resolve host references and build HTML from other Sercrod hosts.\n    - Apply sanitization or escaping before insertion.\n\n\n#### Security considerations\n\n`*compose` ultimately writes to `innerHTML` and does not perform any built-in escaping.\nThis is a standard XSS risk surface: if the value you compose contains active HTML (such as script tags or event handlers) and that content comes from untrusted input, it may execute in the browser.\n\nThis manual's role is to clearly document that behavior, not to claim that a particular pattern is always safe.\nEven when you apply sanitization in the `html` filter, no single step can guarantee that all XSS vectors are eliminated.\nIn practice, XSS protection is a multi-layer task that includes server-side validation, output encoding, and careful template design.\n\nWithin that broader context, Sercrod expects at least the following minimum precautions when using `*compose`:\n\n- Do not pass raw user input directly into `*compose`.\n- Prefer to keep `*compose` values under your control (for example, prebuilt fragments, trusted templates, or server-side generated HTML that has already been validated).\n- If you need to deal with untrusted data, use the `html` filter to sanitize or strip active content, and consider handling such data with text-based directives like `*print` or `*textContent` instead of composing HTML around it.\n- Treat `*compose` as a convenience for trusted or prepared HTML, not as a generic \"render any user string as HTML\" mechanism.\n\nThese notes should be read as a \"speed limit sign\": they describe how the engine behaves and where the risks lie, and they indicate a minimum level of care.\nThey do not replace application-level security measures, especially on the server side.\n\n\n#### Variable creation and scope layering\n\n`*compose` does **not** create any new local variables.\n\nScope behavior:\n\n- The expression is evaluated in the standard Sercrod expression scope:\n\n  - All data from the current `<serc-rod>` host (`this._data`) are visible.\n  - The merged scope includes:\n\n    - The local scope for the current element.\n    - `$data` - current host data (if available).\n    - `$root` - root host data (if available).\n    - `$parent` - nearest ancestor host's data (if available).\n    - Internal methods and any methods registered via `*methods`.\n\n- The directive does not inject extra names or loop variables.\n- Any variable names used in the expression follow the normal shadowing rules of Sercrod expressions.\n\n\n#### Parent access\n\nBecause `*compose` is purely expression-driven and does not alter scope:\n\n- You can refer to outer data with:\n\n  - `someProp`, `state.items`, `config.layout` (whatever your data shape is).\n  - `$data` for the current host's data object.\n  - `$root` to reach the root host data.\n  - `$parent` for the nearest ancestor host's data.\n\n- There is no special \"compose parent\" object; everything uses the standard scope resolution.\n\n\n#### Use with conditionals and loops\n\n`*compose` often appears together with conditionals or in contexts controlled by loops, but it is not itself a loop or condition.\n\n- With host-level conditionals:\n\n  - You can guard the entire composed block with `*if`:\n\n    ```html\n    <section *if=\"showDetails\">\n      <div *compose=\"detailsHtml\"></div>\n    </section>\n    ```\n\n  - `*if` is evaluated on `<section>` before its children are rendered; if it fails, the `<div>` and its `*compose` are never processed.\n\n- Inside loops:\n\n  - Common pattern: use `*for` or `*each` to repeat parent elements, then `*compose` inside the loop body.\n\n    ```html\n    <ul>\n      <li *for=\"item of items\">\n        <div class=\"card\" *compose=\"item.html\"></div>\n      </li>\n    </ul>\n    ```\n\n  - In this case, `*for` is structural (repeats `<li>`), and `*compose` just fills each card with iteration-specific HTML.\n\n- Same-element combinations with loops:\n\n  - If you combine `*compose` (or `n-compose`) with `*for` or `*each` on the **same element**, the structural directive runs first and returns, so `*compose` is effectively ignored.\n  - While the engine does not throw, you should treat such combinations as invalid; always separate structural loops and composition onto different elements.\n\n\n#### Use with *include and *import\n\n`*include` / `*import` and `*compose` all affect the children of a host, but at **different stages**:\n\n- `*include` / `*import`:\n\n  - Resolve a template or fetch HTML.\n  - Assign it to `node.innerHTML` (the template node).\n  - Remove `*include` / `*import` from the rendered element.\n  - Do **not** return; the engine later renders the resulting children normally.\n\n- `*compose`:\n\n  - Runs later, on the rendered element.\n  - Evaluates an expression, passes it through `Sercrod._filters.html`, and sets `el.innerHTML`.\n\nAs a result:\n\n- If you attach both `*include` / `*import` and `*compose` to the same element:\n\n  - `*include` / `*import` change the template's innerHTML.\n  - `*compose` then sets the rendered element's `innerHTML` from the expression result.\n  - After that, Sercrod still walks the (possibly replaced) `node.childNodes` and appends their rendered versions under the same element.\n\n- This effectively means:\n\n  - The HTML created by `*compose` becomes the initial content.\n  - The included/imported children are then rendered additionally, using the modified template.\n\nGuidance:\n\n- The engine allows this combination, but the outcome can be subtle and hard to reason about.\n- Recommended patterns:\n\n  - Decide clearly which directive \"owns\" the content of a given element.\n  - If you want an included template plus extra composition, consider wrapping:\n\n    ```html\n    <div *include=\"'card-template'\">\n      <div *compose=\"extraHtml\"></div>\n    </div>\n    ```\n\n  - Or use `*compose` alone and let your `html` filter handle template resolution internally.\n\n- For most projects, it is simpler to treat `*include` / `*import` and `*compose` as mutually exclusive on a single element, even though the runtime does not enforce this with an error.\n\n\n#### Best practices\n\n- Do not mix multiple \"content\" directives on one element:\n\n  - Avoid putting any combination of `*print`, `*textContent`, `*literal`, `*rem`, `*compose`, and `*innerHTML` on the same element.\n  - In the current implementation, whichever branch matches first wins and the others are ignored, which can be confusing to debug.\n\n- Prefer `*compose` over `*innerHTML` for higher-level composition:\n\n  - Keep `*innerHTML` as a low-level escape hatch for raw HTML strings.\n  - Give `*compose` a meaningful semantic in your project by customizing `Sercrod._filters.html`.\n\n- Keep expressions simple:\n\n  - Use `*compose=\"cardHtml\"` rather than embedding large concatenated strings.\n  - Precompute complex HTML or descriptors in data or methods.\n\n- Be careful with untrusted data:\n\n  - Remember that `*compose` writes directly to `innerHTML` and does not escape by itself.\n  - Untrusted values should either be processed by a defensive `html` filter or handled via text-based directives instead of being composed as HTML.\n  - This is a minimum precaution; it does not replace server-side validation or other security layers.\n\n- Use children intentionally:\n\n  - If you rely purely on the composed HTML, do not define template children on the same element.\n  - If you intentionally want \"compose + extra children\", document that pattern within your team so future maintainers are aware.\n\n\n#### Examples\n\nComposition from a pre-rendered fragment:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"fragments\": {\n    \"hero\": \"<h1>Welcome</h1><p>This is Sercrod.</p>\"\n  }\n}'>\n  <header *compose=\"fragments.hero\"></header>\n</serc-rod>\n```\n\nUsing a method to produce HTML:\n\n```html\n<serc-rod id=\"app\" data='{\"items\":[{\"name\":\"Alpha\"},{\"name\":\"Beta\"}]}'>\n  <script type=\"application/json\" *methods='{\n    \"renderList\": function(items){\n      return \"<ul>\" + items.map(function(it){\n        return \"<li>\" + it.name + \"</li>\";\n      }).join(\"\") + \"</ul>\";\n    }\n  }'></script>\n\n  <section *compose=\"renderList(items)\"></section>\n</serc-rod>\n```\n\nCustom `html` filter for template names (conceptual pattern):\n\n```html\n<script>\n  // Before Sercrod loads\n  window.__Sercrod_filter = {\n    html: function(raw, ctx){\n      // Example: treat values starting with \"tpl:\" as template names\n      if(typeof raw === \"string\" && raw.startsWith(\"tpl:\")){\n        var name = raw.slice(4);\n        // Resolve name to preloaded HTML (implementation-specific)\n        var html = window.TEMPLATES && window.TEMPLATES[name];\n        return html || \"\";\n      }\n      return raw;\n    }\n  };\n</script>\n\n<serc-rod id=\"app\" data='{\"currentTpl\":\"tpl:card\"}'>\n  <div class=\"card-container\" *compose=\"currentTpl\"></div>\n</serc-rod>\n```\n\nIn this pattern:\n\n- The Sercrod core still only knows that `*compose` calls `html(raw, ctx)` and writes to `innerHTML`.\n- All higher-level composition logic lives in the `html` filter.\n\n\n#### Notes\n\n- `*compose` / `n-compose` share their implementation with `*innerHTML` / `n-innerHTML`, differing only in which attribute name is used.\n- Null and `false` results are treated as empty strings; other values are forwarded to the `html` filter.\n- The default `html` filter returns the raw value; projects are expected to override it (via `window.__Sercrod_filter.html`) if they need richer composition.\n- Combining structural directives (`*for`, `*each`) with `*compose` on the same element causes the structural directive to win and `*compose` to be ignored; keep them on separate elements.\n- Combining `*compose` with `*include` / `*import` is technically possible but advanced; prefer to pick a single directive as the content owner for predictable behavior.\n- When in doubt, treat `*compose` as \"evaluate once, pass through `html` filter, assign to `innerHTML`\" and keep the rest of the element's logic straightforward.\n",
  "default": "### *default\n\n#### Summary\n\n`*default` is the fallback branch for `*switch` when no `*case` matches.\nIt participates in a JavaScript-like switch/fall-through model:\n\n- The `*switch` host evaluates an expression once and exposes it as `$switch` to its children.\n- The runtime walks child elements in DOM order.\n- Rendering starts at the first matching `*case` or, if none match, at the first `*default`.\n- From that point it falls through and renders subsequent branches until a break is encountered.\n\n`*default` has an alias `n-default` with the same behavior.\n\n\n#### Basic example\n\nA simple switch with a default branch:\n\n```html\n<serc-rod id=\"app\" data='{\"status\":\"idle\"}'>\n  <div *switch=\"status\">\n    <p *case=\"'ready'\">Ready</p>\n    <p *case=\"'error'\">An error occurred</p>\n    <p *default>Unknown status</p>\n  </div>\n</serc-rod>\n```\n\nBehavior:\n\n- `*switch=\"status\"` evaluates to `\"idle\"`.\n- No `*case` matches `\"idle\"`.\n- Rendering starts from the first `*default` and falls through (in this example there are no later branches).\n- The result is a single `<p>` with \"Unknown status\".\n\n\n#### Behavior\n\n`*default` marks an element as part of the default region of a `*switch` block:\n\n- It has no expression; it does not compare values.\n- If no `*case` has started rendering yet and the runtime encounters a `*default`, it starts rendering from that branch.\n- If rendering has already started from an earlier `*case` or `*default`, later `*default` branches are treated as part of the fall-through region and are rendered normally.\n- `*default` is recognized only inside a `*switch` (or `n-switch`) host and only on its direct child elements.\n\nAlias:\n\n- `*default` and `n-default` are interchangeable; they are treated the same by the switch engine.\n\n\n#### Evaluation timing\n\n`*default` is evaluated as part of the `*switch` pass, not as an independent directive:\n\n- The `*switch` host evaluates its expression once, before examining children.\n- The result of the expression is stored as `$switch` in the child scope.\n- The switch engine then walks the host's child elements (direct children) in DOM order:\n  - If it finds a `*case` or `*case.break` whose condition matches `$switch`, it starts rendering from that element.\n  - If it reaches a `*default` while rendering has not yet started, it starts rendering from that `*default`.\n  - Once rendering has started, subsequent `*case`, `*case.break`, and `*default` elements are part of the fall-through region until a break is seen.\n\nOutside of a `*switch`:\n\n- `*default` has no special scheduling or evaluation.\n- It is parsed as an attribute, but there is no separate handler outside `_renderSwitchBlock`, so it does not act as a condition or fallback on its own.\n- In practice, you should treat `*default` as \"only meaningful directly under a `*switch` or `n-switch` host\".\n\n\n#### Execution model\n\nThe `*switch` execution model (simplified):\n\n1. On the host element that has `*switch` or `n-switch`:\n   - Evaluate the switch expression in the current scope to produce `switchVal`.\n   - Prepare a `childScope` that includes the original scope plus `$switch: switchVal`.\n\n2. Walk each direct child element of the `*switch` host in DOM order:\n   - Compute two flags for each child:\n     - `isDefault` if it has `*default` or `n-default`.\n     - `caseRaw` if it has any of `*case`, `n-case`, `*case.break`, or `n-case.break`.\n   - If neither `isDefault` nor `caseRaw` is present, the child is not a switch branch and is skipped at this stage.\n\n3. Before rendering starts (`falling === false`):\n   - If `isDefault` is true:\n     - Start rendering from this branch: set `falling = true`.\n   - Else if there is a `caseRaw`:\n     - Use the internal `_matchCase` helper to compare the case expression with `switchVal`.\n     - If it matches, start rendering from this branch: set `falling = true`.\n     - If it does not match, skip this child and continue scanning.\n\n4. Once rendering has started (`falling === true`):\n   - Clone the branch element.\n   - Remove control attributes from the clone:\n     - `*case`, `n-case`\n     - `*default`, `n-default`\n     - `*case.break`, `n-case.break`\n     - `*break`, `n-break`\n   - Render the clone normally with `childScope` as the scope and append it to the parent.\n\n5. Break handling:\n   - After rendering a branch, determine if it has any break-related attributes:\n     - `*break`, `n-break`\n     - `*case.break`, `n-case.break`\n   - If any of these are present on the original branch, stop processing further branches in this `*switch` block.\n\nNotes about `*default` in this model:\n\n- There is no expression to match; its role is simply:\n  - \"Start the fall-through region here if nothing has matched before.\"\n- If a `*default` appears after a matching `*case`, it is rendered as part of the fall-through region, similar to how `default:` can appear after other labels in a JavaScript `switch`.\n\n\n#### Variable creation and scope layering\n\n`*default` does not introduce any new variables by itself.\n\nInside a `*default` branch:\n\n- You can access everything that is normally visible in the scope:\n  - Data bound on the Sercrod host (`data` or similar).\n  - Special helpers like `$data`, `$root`, and `$parent`.\n  - Methods injected via `*methods` or other configuration.\n- You can also access `$switch`, which is injected by the surrounding `*switch` host.\n\nTypical usage:\n\n```html\n<div *switch=\"status\">\n  <p *case=\"'ready'\">Ready</p>\n  <p *case=\"'error'\">Error: <span *print=\"$switch\"></span></p>\n  <p *default>Unknown status \"<span *print=\"$switch\"></span>\"</p>\n</div>\n```\n\nHere, the default branch reports the unknown value by reading `$switch` from the child scope.\n\n\n#### Parent access\n\n`*default` does not change the way parent data is accessed:\n\n- `$parent` (if present) still refers to the nearest ancestor Sercrod host's data.\n- `$root` still refers to the outermost Sercrod host's data.\n- Regular data paths (such as `state`, `config`, or `data.something`) behave as usual.\n\nIn other words, `*default` only affects where rendering starts and how branches fall through; it does not introduce a new \"parent\" concept.\n\n\n#### Use with conditionals and loops\n\nInside a `*default` branch you can freely use other directives:\n\n- `*if` and `*elseif`:\n\n  ```html\n  <div *switch=\"status\">\n    <p *case=\"'ready'\">Ready</p>\n    <p *default *if=\"status\">\n      Status \"<span *print=\"status\"></span>\" is not recognized\n    </p>\n  </div>\n  ```\n\n  In this example:\n  - `*default` determines that this branch participates in the switch.\n  - `*if` further refines whether the default branch contents are actually shown.\n\n- `*for`, `*each`:\n\n  ```html\n  <div *switch=\"status\">\n    <section *case=\"'ok'\">All good</section>\n    <section *default>\n      <h2>Problems detected</h2>\n      <ul *each=\"msg of messages\">\n        <li *print=\"msg\"></li>\n      </ul>\n    </section>\n  </div>\n  ```\n\n  Here:\n  - The `*default` section becomes active when no `*case` matches.\n  - Inside that default block, `*each` is used normally to list messages.\n\n- Nested `*switch`:\n\n  - You can nest another `*switch` inside a default branch if you want a second-level decision.\n  - The inner `*switch` has its own `$switch`, independent of the outer one.\n\n\n#### Use with *case, *case.break, and *break\n\n`*default` participates in the same fall-through model as `*case` and `*case.break`:\n\n- Start of rendering:\n\n  - If a `*case` or `*case.break` matches the switch value, it becomes the starting point.\n  - If no case matches and a `*default` is present, the first `*default` becomes the starting point.\n  - If there is neither a matching `*case` nor a `*default`, the `*switch` block renders nothing from its branches.\n\n- Fall-through:\n\n  - After the starting branch, each subsequent sibling that has either `*case`, `*case.break`, or `*default` is rendered in order until a break is found.\n  - This matches the \"fall-through until break\" style from JavaScript.\n\n- Break from default:\n\n  - A `*default` branch can have `*break` or `n-break` to stop fall-through after itself:\n\n    ```html\n    <div *switch=\"status\">\n      <p *case=\"'ok'\">OK</p>\n      <p *default *break>Unknown status</p>\n      <p>Will not be rendered if default was used</p>\n    </div>\n    ```\n\n  - Similarly, `*case.break` or `n-case.break` on any branch stops rendering after that branch, so any later `*default` is ignored in that case.\n\nSercrod-specific constraints and recommendations:\n\n- Do not put `*default` and `*case` / `*case.break` on the same element.\n  - The implementation will effectively treat the element as a default branch and ignore the case condition.\n  - For clarity and maintainability, keep each branch either a case or a default, not both.\n- Use at most one `*default` per `*switch` block.\n  - Multiple defaults are technically treated as part of the fall-through region, but they are confusing to read.\n  - Prefer a single default region that covers all fallback content.\n\n\n#### Best practices\n\n- Place `*default` at the end of the switch block:\n\n  - While the implementation supports a JavaScript-like fall-through model, placing the default at the end makes the flow easier to understand.\n  - This also matches common expectations from other languages.\n\n- Keep default content focused on the \"unknown\" case:\n\n  - Use the default branch to handle \"anything else\" clearly.\n  - Show the unexpected value via `$switch` if it helps debugging or UX.\n\n- Use explicit breaks when you want no fall-through:\n\n  - If you want the default branch to be the only fallback, add `*break` to it so that later branches are skipped.\n  - Similarly, use `*case.break` for cases that must not fall through.\n\n- Treat `*default` as \"inside switch only\":\n\n  - Only use `*default` on direct children of a `*switch` / `n-switch` host.\n  - Avoid using it as a generic conditional or as an alternative to `*if`; it is not designed for that.\n\n- Avoid mixing control concerns on the same element:\n\n  - Do not combine `*default` with `*switch`, `*case`, or `*case.break` on the same element.\n  - Let each branch element have exactly one switch-related role: either `*case`/`*case.break` or `*default`.\n\n\n#### Additional examples\n\nDefault with a detailed fallback view:\n\n```html\n<serc-rod id=\"dashboard\" data='{\n  \"mode\": \"unknown\",\n  \"knownModes\": [\"list\",\"detail\"]\n}'>\n  <section *switch=\"mode\">\n    <div *case=\"'list'\">\n      <h2>List view</h2>\n      <!-- ... -->\n    </div>\n\n    <div *case=\"'detail'\">\n      <h2>Detail view</h2>\n      <!-- ... -->\n    </div>\n\n    <div *default>\n      <h2>Unsupported mode</h2>\n      <p>\n        Mode \"<span *print=\"$switch\"></span>\" is not supported.\n      </p>\n      <p>Supported modes are:</p>\n      <ul *each=\"m of knownModes\">\n        <li *print=\"m\"></li>\n      </ul>\n    </div>\n  </section>\n</serc-rod>\n```\n\nDefault plus break to isolate fallback:\n\n```html\n<div *switch=\"status\">\n  <p *case=\"'ok'\" *break>OK</p>\n\n  <p *default *break>\n    Status \"<span *print=\"$switch\"></span>\" is not OK\n  </p>\n\n  <p>\n    This paragraph is never rendered, because all branches break.\n  </p>\n</div>\n```\n\n\n#### Notes\n\n- `*default` and `n-default` are aliases and behave identically inside a `*switch` block.\n- `*default` has no expression; it only controls where the switch's fall-through rendering can start.\n- `*default` is only meaningful as a direct child of a `*switch` or `n-switch` host.\n- The switch engine uses the same fall-through and break concepts as a JavaScript `switch` statement:\n  - Start at the first matching `*case` or the first `*default` when nothing matches.\n  - Continue rendering subsequent branches until a break is encountered.\n- For clarity and maintainability:\n  - Use one `*default` per `*switch` block.\n  - Place it last.\n  - Avoid mixing `*default` with other switch-role attributes on the same element.\n",
  "download": "### *download / n-download\n\n#### Summary\n\n`*download` turns any element into an accessible download trigger. It evaluates an expression to a download specification, fetches the resource from the given URL, and starts a browser download using a Blob-backed Object URL.:contentReference[oaicite:0]{index=0}:contentReference[oaicite:1]{index=1}\n\n`n-download` is a star-less alias for the same directive. Both forms share one implementation and one manual entry.:contentReference[oaicite:2]{index=2}:contentReference[oaicite:3]{index=3}\n\n\n#### Basic example\n\nTrigger a CSV export from a server endpoint:\n\n  ```html\n  <serc-rod>\n    <button type=\"button\" *download=\"'/api/report.csv'\">\n      Download report\n    </button>\n  </serc-rod>\n  ```\n\n\n#### Description\n\n`*download` (and `n-download`) decorates an element so that activating it (mouse click, keyboard Enter/Space) downloads a file from a server endpoint. The directive:\n\n- evaluates its expression once per render to a configuration object,\n- normalizes that configuration to a standard `{ url, method, headers, credentials, filename, transport }` shape, and\n- binds handlers that perform the network request and trigger a download via an invisible `<a download>` element.:contentReference[oaicite:4]{index=4}:contentReference[oaicite:5]{index=5}:contentReference[oaicite:6]{index=6}\n\nIt is purely side-effecting: it does not write into Sercrod data such as `$download` or `$upload`. Those slots are reserved for directives like `*api`, `*fetch`, and `*post`, and are cleared in `_finalize` independently of `*download`.:contentReference[oaicite:7]{index=7}\n\n\n#### Behavior\n\nAt render time:\n\n1. **Read and evaluate the expression**\n\n   Sercrod reads the attribute value from `*download` or `n-download` on the original template element and evaluates it as a JavaScript expression in the current scope:\n\n   - The evaluation runs via `eval_expr(expr, scope, { el: ctx_el, mode: \"download\" })`.\n   - The `scope` is the effective scope after `*let` and other scope modifiers.\n   - `ctx_el` is the template element that carried the directive.:contentReference[oaicite:8]{index=8}:contentReference[oaicite:9]{index=9}\n\n2. **Normalize options**\n\n   The result of the expression must be either:\n\n   - a string, interpreted as `{ url: \"<string>\" }`, or\n   - an object, which is normalized by `_normalize_download_opts`.:contentReference[oaicite:10]{index=10}\n\n   Normalization guarantees at least:\n\n   - `url` (required): download URL.\n   - `method` (default `\"GET\"`).\n   - `headers` (default `{}`).\n   - `credentials` (default `false`).\n   - `filename` (default `null`).\n   - `transport` (default `\"fetch\"`, or `\"xhr\"` for the XHR fallback).:contentReference[oaicite:11]{index=11}\n\n   If the normalized result has no `url`, `_normalize_download_opts` throws an error, which is caught and reported as a `sercrod-error` event with `stage: \"download-init\"`. The element stays in the DOM but no download handler is bound.:contentReference[oaicite:12]{index=12}:contentReference[oaicite:13]{index=13}\n\n3. **Accessibility and event binding**\n\n   When options are valid, Sercrod:\n\n   - ensures the element is keyboard-accessible by setting `role=\"button\"` and `tabIndex=0` when those attributes are missing, and:contentReference[oaicite:14]{index=14}\n   - attaches a shared `on_click` handler to `click` and `keydown` (Enter/Space, non-repeating) events.:contentReference[oaicite:15]{index=15}\n\n4. **Activation**\n\n   When the element is activated:\n\n   - The handler calls `e.preventDefault()`. On an `<a>` element this stops the normal navigation; the download is always driven by the Blob-based URL, not any static `href`.:contentReference[oaicite:16]{index=16}\n   - Sercrod dispatches a `CustomEvent(\"sercrod-download-start\", { detail:{ host, el, url }, bubbles:true, composed:true })` from the host.:contentReference[oaicite:17]{index=17}:contentReference[oaicite:18]{index=18}\n   - It then performs the network request:\n\n     - If `transport === \"xhr\"`, it calls `_xhr_download(opt)`:\n       - Uses `XMLHttpRequest` with `responseType=\"blob\"`.\n       - Applies `opt.method || \"GET\"`, `opt.url`, `opt.headers`, and `opt.credentials`.:contentReference[oaicite:19]{index=19}\n     - Otherwise, it uses `fetch(opt.url, { method, headers, credentials, cache:\"no-cache\" })`:\n       - `method` defaults to `\"GET\"`.\n       - `headers` defaults to `{}`.\n       - `credentials` is `\"include\"` when `opt.credentials` is truthy, `\"same-origin\"` otherwise.\n       - Non-2xx responses throw an error.:contentReference[oaicite:20]{index=20}:contentReference[oaicite:21]{index=21}\n\n   - After a successful response, Sercrod:\n     - converts it to a `Blob`,\n     - creates an object URL via `URL.createObjectURL(blob)`,\n     - creates a temporary `<a>` element, sets its `href` to the object URL and its `download` attribute to `opt.filename || \"download\"`,:contentReference[oaicite:22]{index=22}\n     - programmatically clicks the `<a>`, then removes it and revokes the object URL.:contentReference[oaicite:23]{index=23}\n\n   - Finally, Sercrod emits `CustomEvent(\"sercrod-downloaded\", { detail:{ host, el, url, filename, status }, bubbles:true, composed:true })` on success, or `CustomEvent(\"sercrod-error\", { detail:{ host, el, stage:\"download\", error }, ... })` on errors during the request.:contentReference[oaicite:24]{index=24}:contentReference[oaicite:25]{index=25}\n\nErrors during option evaluation or normalization are surfaced as `sercrod-error` with `stage: \"download-init\"`, while errors during the actual network request use `stage: \"download\"`.:contentReference[oaicite:26]{index=26}\n\n\n#### Download options\n\nThe directive expression must evaluate to either:\n\n- **string** ? treated as `{ url: \"<string>\" }` with defaults:\n  - `method: \"GET\"`, `headers: {}`, `credentials: false`, `filename: null`, `transport: \"fetch\"`.:contentReference[oaicite:27]{index=27}\n- **object** ? merged into:\n\n  - `url` (required, string)  \n    Absolute or relative URL for the file.\n\n  - `method` (string, default `\"GET\"`)  \n    HTTP method used by `fetch` or XHR.\n\n  - `headers` (object, default `{}`)  \n    Additional request headers for both `fetch` and XHR.:contentReference[oaicite:28]{index=28}\n\n  - `credentials` (boolean, default `false`)  \n    For `fetch`, `true` maps to `credentials: \"include\"`, `false` to `\"same-origin\"`.  \n    For XHR, `true` maps to `xhr.withCredentials = true`.:contentReference[oaicite:29]{index=29}:contentReference[oaicite:30]{index=30}\n\n  - `filename` (string or null, default `null`)  \n    File name used for the temporary `<a download=\"...\">`. If omitted or `null`, `\"download\"` is used. Sercrod does not inspect `Content-Disposition`; the server cannot override this name.:contentReference[oaicite:31]{index=31}\n\n  - `transport` (`\"fetch\"` | `\"xhr\"`, default `\"fetch\"`)  \n    Selects the network implementation. `\"fetch\"` uses the Fetch API; `\"xhr\"` uses `_xhr_download` with `XMLHttpRequest`.:contentReference[oaicite:32]{index=32}:contentReference[oaicite:33]{index=33}\n\nAny extra keys in the object are currently ignored by the `*download` implementation.\n\n\n#### Evaluation timing\n\n- The directive expression is evaluated **once per render** of the element, not on every click.\n- The resulting options are closed over and reused by all activations until the host re-renders (for example, due to data changes).\n- A new render creates new DOM nodes, re-evaluates the expression in the then-current scope, and rebinds `*download`.:contentReference[oaicite:34]{index=34}:contentReference[oaicite:35]{index=35}\n\nIf you need the URL or headers to reflect changing data, ensure that changes cause the host to call `update()` (usually via normal Sercrod data mutations), so the directive is re-evaluated.\n\n\n#### Execution model\n\n- Activating a `*download` element performs an asynchronous network request and a Blob-based download, but **does not** touch Sercrod data (`this._data`) anywhere in its implementation.:contentReference[oaicite:36]{index=36}\n- There is no automatic call to `update()` after a download succeeds or fails; the DOM stays as it was unless your own event handlers modify data.:contentReference[oaicite:37]{index=37}\n- The host’s `_finalize()` step clears `$upload`, `$download`, and any `*into` targets, but `*download` itself never populates those fields.:contentReference[oaicite:38]{index=38}\n\nTo react to downloads (for example, to set a “lastDownloadedAt” field), listen to the host events and update data in your own handlers.\n\n\n#### Variable creation\n\n`*download` does **not** create or modify any Sercrod variables. In particular:\n\n- It does not set `$download` or `$upload`. Those are only written by directives like `*api`, `*fetch`, or `*post`.:contentReference[oaicite:39]{index=39}\n- It does not create any new keys on `this._data`.\n\nAll state and progress information is exposed via DOM events, not via the data model.\n\n\n#### Scope layering\n\nThe directive expression is evaluated with the same scope rules as other expression-based directives:\n\n- Base scope is the host data (or staged data when `*stage` is active).\n- Local variables introduced by `*let` and other scope modifiers around the element are layered on top.\n- `$parent` is injected to refer to the nearest parent Sercrod host’s data.:contentReference[oaicite:40]{index=40}\n- Methods exposed through `*methods` and internal Sercrod helpers are added if not already present.:contentReference[oaicite:41]{index=41}\n\nThis means a `*download` expression can freely use the same variables, helpers, and `$parent` access patterns as any other Sercrod expression.\n\n\n#### Parent access\n\nInside a `*download` expression you can:\n\n- read from `$parent` to reference data on an outer Sercrod host,\n- use any values from enclosing `*let` scopes,\n- call methods injected via `*methods`.\n\nFor example, in a nested host you might compute the URL from a parent configuration:\n\n  ```html\n  <serc-rod id=\"outer\" data=\"{ apiBase: '/api' }\">\n    <serc-rod id=\"inner\" data=\"{ reportId: 42 }\">\n      <button\n        type=\"button\"\n        *download=\"{ url: $parent.apiBase + '/report/' + reportId + '.csv',\n                    filename: 'report-' + reportId + '.csv' }\">\n        Download report\n      </button>\n    </serc-rod>\n  </serc-rod>\n  ```\n\n\n#### Use with conditionals and loops\n\n`*download` belongs to the group of “own-element” directives in `renderNode` that:\n\n- clone the template element,\n- attach specific behavior,\n- render children into the clone,\n- and then `return`, preventing any later branches from running on the same element.:contentReference[oaicite:42]{index=42}\n\nIn particular:\n\n- `*upload`, `*download`, `*websocket`, and `*ws-send` are mutually exclusive on the **same** element; only the first matching branch in the implementation runs (currently `*upload`, then `*download`, then `*websocket`, then `*ws-send`).:contentReference[oaicite:43]{index=43}\n- Do not rely on combining these directives on a single tag; treat such combinations as unsupported. Instead, use nested elements.\n\nExample: combine `*for` and `*download` by putting `*for` on a parent and `*download` on a child:\n\n  ```html\n  <serc-rod data=\"{ files: [\n    { name: 'a.csv', url: '/api/a.csv' },\n    { name: 'b.csv', url: '/api/b.csv' }\n  ] }\">\n    <ul>\n      <li *for=\"file of files\">\n        <span *print=\"file.name\"></span>\n        <button\n          type=\"button\"\n          *download=\"{ url: file.url, filename: file.name }\">\n          Download\n        </button>\n      </li>\n    </ul>\n  </serc-rod>\n  ```\n\nSimilarly, you can gate the presence of a download button with `*if` on an ancestor:\n\n  ```html\n  <div *if=\"user.canDownload\">\n    <button\n      type=\"button\"\n      *download=\"{ url: reportUrl, filename: 'report.csv' }\">\n      Download report\n    </button>\n  </div>\n  ```\n\n\n#### Best practices\n\n- **Keep expressions simple**  \n  Prefer moving complex option building into data or helper methods, and keep the `*download` expression small.\n\n- **Always set a `filename`**  \n  Since Sercrod does not inspect `Content-Disposition`, the browser will use `filename` or `\"download\"`. Setting it explicitly leads to more user-friendly downloads.:contentReference[oaicite:44]{index=44}\n\n- **Use `transport: \"xhr\"` only when necessary**  \n  Default to `fetch` unless you have a specific environment (for example, corporate proxies) where `XMLHttpRequest` is more reliable.:contentReference[oaicite:45]{index=45}\n\n- **React via events, not data**  \n  Listen for `sercrod-download-start`, `sercrod-downloaded`, and `sercrod-error` on the host to drive loading indicators, error banners, or logging.:contentReference[oaicite:46]{index=46}\n\n- **Keyboard accessibility**  \n  Sercrod adds `role=\"button\"` and `tabIndex=0` when missing, but you should still write accessible text labels and, when appropriate, ARIA attributes.\n\n- **Avoid mixing with other own-element directives**  \n  Do not put `*download` on the same element as `*upload`, `*websocket`, or `*ws-send`. Use nested tags instead, so each directive has its own element.\n\n\n#### Examples\n\n##### 1. Simple config in data\n\nMove configuration into data and reference it from the directive:\n\n  ```html\n  <serc-rod data=\"{\n    downloadSpec: {\n      url: '/api/report.csv',\n      filename: 'report.csv'\n    }\n  }\">\n    <button type=\"button\" *download=\"downloadSpec\">\n      Download CSV\n    </button>\n  </serc-rod>\n  ```\n\n##### 2. Secure download with credentials and headers\n\n  ```html\n  <serc-rod data=\"{\n    reportUrl: '/api/secure/report',\n    csrfToken: '...'\n  }\">\n    <button\n      type=\"button\"\n      *download=\"{\n        url: reportUrl,\n        method: 'POST',\n        headers: { 'X-CSRF-Token': csrfToken },\n        credentials: true,\n        filename: 'secure-report.pdf'\n      }\">\n      Download secure PDF\n    </button>\n  </serc-rod>\n  ```\n\n##### 3. XHR transport fallback\n\nUse XHR when `fetch` is problematic in the target environment:\n\n  ```html\n  <serc-rod data=\"{ url: '/api/proxy/report.csv' }\">\n    <button\n      type=\"button\"\n      *download=\"{ url, transport: 'xhr', filename: 'report.csv' }\">\n      Download via XHR\n    </button>\n  </serc-rod>\n  ```\n\n##### 4. Listening to download events\n\nUse DOM events on the host to update your own state:\n\n  ```html\n  <serc-rod id=\"app\" data=\"{ url: '/api/report.csv' }\">\n    <button type=\"button\" *download=\"{ url, filename: 'report.csv' }\">\n      Download report\n    </button>\n  </serc-rod>\n\n  <script>\n    const host = document.getElementById('app');\n\n    host.addEventListener('sercrod-download-start', (e) => {\n      console.log('Download started:', e.detail.url);\n    });\n\n    host.addEventListener('sercrod-downloaded', (e) => {\n      console.log('Download finished:', e.detail.filename, e.detail.status);\n    });\n\n    host.addEventListener('sercrod-error', (e) => {\n      if (e.detail.stage === 'download' || e.detail.stage === 'download-init') {\n        console.error('Download error:', e.detail.error);\n      }\n    });\n  </script>\n  ```\n\n\n#### Notes\n\n- `*download` and `n-download` are fully equivalent; they share the same implementation and manual entry. Use `n-download` when the star character is inconvenient in your environment.:contentReference[oaicite:47]{index=47}:contentReference[oaicite:48]{index=48}\n- The directive is intended for elements **inside** a Sercrod host. There is currently no special handling for `*download` on the host element itself; host-level behavior is reserved for directives like `*fetch` and `*websocket`.:contentReference[oaicite:49]{index=49}\n- When the expression or options are invalid (for example, missing `url`), Sercrod surfaces this via a `sercrod-error` event with `stage: \"download-init\"` and does not bind a click handler.:contentReference[oaicite:50]{index=50}:contentReference[oaicite:51]{index=51}\n- During the download itself, network or HTTP errors are reported via `sercrod-error` with `stage: \"download\"`. In both cases, the element stays in the DOM; nothing is automatically removed.:contentReference[oaicite:52]{index=52}\n- Because downloads are always performed via an in-memory `Blob`, very large files will consume browser memory before the save dialog appears. Consider whether direct links or server-side streaming are more appropriate for very large assets.\n",
  "each": "### *each\n\n#### Summary\n\n`*each` repeats the children of a single host element for each entry in a list, object, or other iterable.\nThe host element itself is rendered exactly once and acts as a container.\nThe directive understands JavaScript-like `in` and `of` loop syntax and has an alias `n-each`.\n\nUse `*each` when you want one structural wrapper (such as `<ul>`, `<tbody>`, or `<div>`) whose contents are repeated.\n\nImportant restriction:\n\n- A single element must not combine `*each` with `*include` or `*import`.\n  These structural directives all want to control the host's children, so they are not allowed on the same element.\n\n\n#### Basic example\n\nA simple list:\n\n```html\n<serc-rod id=\"app\" data='{\"items\":[\"Apple\",\"Banana\",\"Cherry\"]}'>\n  <ul *each=\"item of items\">\n    <li *print=\"item\"></li>\n  </ul>\n</serc-rod>\n```\n\nBehavior:\n\n- `<ul>` is rendered once.\n- `*each=\"item of items\"` iterates the array.\n- For each item, Sercrod renders the original `<li>` subtree with a local variable `item` bound to the current value.\n- The result is a single `<ul>` containing three `<li>` elements.\n\n\n#### Behavior\n\n- `*each` is a structural directive that controls how many times the original child nodes are rendered.\n- The host element is cloned once as a container; its original children are used as a template for each iteration.\n- The expression on `*each` is evaluated once per render of the host.\n- Inside each iteration, Sercrod renders the original children with an iteration-specific scope.\n\nAlias:\n\n- `*each` and `n-each` are aliases. They accept the same syntax and behave identically.\n\n\n#### Expression syntax\n\nThe expression to the right of `*each` uses a restricted, JS-like loop syntax:\n\n- `value of iterable`\n- `(key, value) of iterable`\n- `key in object`\n\n`key` and `value` must be simple identifiers (no destructuring or complex patterns).\n\nSupported and recommended patterns:\n\n- Array values:\n\n  - `item of items`\n    Iterates an array, binding `item` to each element.\n\n  - `(index, item) of items`\n    Iterates an array, binding `index` to the numeric index and `item` to the element.\n\n- Object values:\n\n  - `key in obj`\n    Iterates over the enumerable keys of `obj`, binding `key` to each property name.\n\n  - `(key, value) of obj`\n    Iterates over `Object.entries(obj)`, binding `key` to the property name and `value` to the value.\n\nCollection evaluation:\n\n- The right-hand side is evaluated as a normal Sercrod expression.\n- If the expression returns a falsy value (such as `null`, `undefined`, `false`, `0`, or an empty string), `*each` treats it as an empty collection and renders nothing.\n- Arrays and plain objects are the primary targets; other iterables may work but are not the main focus.\n\n\n#### Value semantics\n\nFor `modeWord = \"of\"`:\n\n- `item of items`:\n\n  - If `items` is an array, each iteration sees `item` as one element of the array.\n  - If `items` is a non-null, non-array object, `*each` iterates `Object.values(items)`, so `item` is each value.\n\n- `(index, item) of items`:\n\n  - If `items` is an array, Sercrod uses `Array.from(items.entries())`.\n    - `index` receives the numeric index.\n    - `item` receives the element.\n  - If `items` is a non-null object, Sercrod uses `Object.entries(items)`.\n    - `index` receives the key.\n    - `item` receives the value.\n\nFor `modeWord = \"in\"`:\n\n- `key in obj`:\n\n  - Sercrod runs a `for...in` style enumeration.\n  - The single variable (`key` in this example) receives each property name.\n  - To access values, you write expressions like `obj[key]` inside the body.\n\n- `(key, value) in obj`:\n\n  - The current implementation of `*each` does not bind `key` and `value` separately.\n  - Only the second name is bound, and it receives the key string.\n  - This form is therefore not usable in practice for `*each`.\n  - If you need both key and value, always use `(key, value) of expr`.\n\nRecommendation:\n\n- Use `of` when you need values, or key and value pairs.\n- Use `in` only when you need keys, and stick to the single-variable form (`key in obj`) with `*each`.\n\n\n#### Evaluation timing\n\n`*each` participates in Sercrod's structural evaluation order:\n\n- Host-level `*if` and `n-if` run before `*each`.\n  - If a host `*if` condition is falsy, the host and its children are dropped and `*each` does not run.\n- `*switch` on the same host (if present) is processed before `*each` and may completely replace the children.\n- After those structural checks, `*each` is evaluated on the host.\n- The expression for `*each` is evaluated once per render of the host, not once per iteration.\n- Child directives (`*if` on child elements, nested `*for`, nested `*each`, `*include`, bindings, event handlers, and so on) are evaluated separately for each iteration when the child nodes are rendered.\n\n\n#### Execution model\n\nConceptually, the runtime behaves like this when it encounters `*each`:\n\n1. Evaluate the expression on `*each` to obtain a collection (`iterable`).\n   - If the result is falsy, treat it as an empty collection and stop.\n2. Create a shallow clone of the host element as a container.\n   - The tag name and all attributes are copied.\n   - The `*each` / `n-each` attribute is removed from the clone.\n3. Take the original child nodes of the host as the template body.\n4. For each entry in the collection:\n   - Prepare a per-iteration scope that merges:\n     - The effective parent scope.\n     - The per-iteration variables (for example `item`, `index`, `key`, `value`).\n   - Render each original child node with this scope into the container.\n5. Append the container to the parent of the original host.\n   - The original host node is not appended.\n\nOn re-renders:\n\n- When the surrounding Sercrod host re-renders, `*each` re-evaluates the expression and rebuilds the loop body from the original template children.\n- There is no diffing or keyed patching; the children are regenerated from the template, which keeps the implementation small and predictable.\n\n\n#### Variable creation and scope layering\n\nInside the body of `*each`:\n\n- The loop variables (`item`, `index`, `key`, `value`, or whatever names you choose) are added to the scope for each iteration.\n- These variables shadow any outer variables with the same names.\n- All existing scope entries remain available:\n  - Data from the host (`data` or whatever you bound on `<serc-rod>`).\n  - Special helpers like `$data`, `$root`, and `$parent` injected by Sercrod.\n  - Methods injected via `*methods` or similar configuration.\n\nGuidelines:\n\n- Choose iteration variable names that do not unintentionally shadow important outer variables.\n- If you need to access the original collection while using a short name for the item, keep a long-form name in the data, such as `state.items`, and refer to `state.items` when needed.\n\n\n#### Parent access\n\n`*each` does not introduce a separate parent object, but parent data remain available through the normal Sercrod scope model:\n\n- You can access outer data through whatever names you used in `data` (for example `items`, `state`, `config`).\n- You can access the root data with `$root`.\n- You can access the nearest ancestor Sercrod host's data with `$parent`.\n\nThe only additional names introduced by `*each` are the loop variables themselves.\n\n\n#### Use with conditionals and loops\n\n`*each` is designed to compose with other directives when they target different layers:\n\n- Host-level condition:\n\n  - `*if` on the same element is evaluated first and acts as a gate for the entire loop.\n\n  ```html\n  <ul *if=\"items && items.length\" *each=\"item of items\">\n    <li *print=\"item.label\"></li>\n  </ul>\n  ```\n\n  - If `items` is falsy or has zero length, the `<ul>` and its body are not rendered at all.\n\n- Child-level conditions:\n\n  - You can freely use `*if` or `*for` inside the body of `*each`.\n\n  ```html\n  <ul *each=\"item of items\">\n    <li *if=\"item.visible\">\n      <span *print=\"item.label\"></span>\n    </li>\n  </ul>\n  ```\n\n- Nested loops:\n\n  - Nested `*for` or nested `*each` inside the body of `*each` are allowed.\n  - Each nested loop sees the outer loop's variables in its scope.\n\n  ```html\n  <table>\n    <tbody *each=\"row of rows\">\n      <tr *each=\"cell of row.cells\">\n        <td *print=\"cell.text\"></td>\n      </tr>\n    </tbody>\n  </table>\n  ```\n\n  - In this example, `<tbody>` is rendered once, and its `<tr>` children are repeated per row, with `<td>` repeated per cell.\n\n\n#### Use with templates, *include and *import\n\n`*each`, `*include`, and `*import` are all structural directives that control the children of a host element, but in different ways:\n\n- `*each`:\n  - Takes the original children of the host as a template.\n  - Repeats those children for each entry in a collection.\n\n- `*include`:\n  - Finds a named `*template`.\n  - Replaces the host's inner content with the inner content of that template.\n\n- `*import`:\n  - Is typically a higher-level helper that internally relies on the template/include mechanism.\n  - Also wants to control the host's children as a single unit.\n\nBecause all of these directives want to own the host's children, putting them on the same element is not supported.\n\nInvalid patterns:\n\n```html\n<ul *each=\"item of items\" *include=\"'user-item'\">\n  <!-- This combination is not supported -->\n</ul>\n\n<ul *each=\"item of items\" *import=\"'user-item'\">\n  <!-- This combination is also not supported -->\n</ul>\n```\n\nReasons:\n\n- `*each` expects to read the host's original children and use them as the loop body.\n- `*include` and `*import` want to overwrite the host's children with template content.\n- The implementation does not merge these behaviors; whichever transformation happens first would effectively erase the assumptions of the others.\n- The result is undefined and will almost certainly break expectations.\n\nSupported patterns:\n\n- Put `*each` on the container and `*include` or `*import` on a child element:\n\n  ```html\n  <ul *each=\"item of items\">\n    <li *include=\"'user-item'\"></li>\n  </ul>\n\n  <ul *each=\"item of items\">\n    <li *import=\"'user-item'\"></li>\n  </ul>\n  ```\n\n  In these cases:\n\n  - `<ul>` is the loop container, rendered once.\n  - `<li>` is the template body for each iteration.\n  - `*include` / `*import` runs independently inside each iteration, and the included template can use `item`.\n\n- Or use `*include` / `*import` to bring in a template that contains its own loop:\n\n  ```html\n  <section *include=\"'user-list-block'\"></section>\n  <section *import=\"'user-list-block'\"></section>\n  ```\n\n  Where `user-list-block` is a `*template` that uses `*each` internally.\n\nIf your project wraps `*include` with other helpers, or defines `*import` as such a helper, the same restriction applies: do not put those helpers on the same element as `*each`.\n\n\n#### Comparison with *for\n\nBoth `*for` and `*each` iterate collections, but they do so at different structural levels.\n\n- `*for`:\n\n  - Repeats the host element itself.\n  - Typical pattern for repeated siblings.\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <span *print=\"item\"></span>\n    </li>\n  </ul>\n  ```\n\n- `*each`:\n\n  - Keeps the host element as a single container and repeats its children.\n\n  ```html\n  <ul *each=\"item of items\">\n    <li *print=\"item\"></li>\n  </ul>\n  ```\n\nRecommendations:\n\n- Use `*for` when the host itself is the repeated unit (list items, cards, rows).\n- Use `*each` when the host is a structural wrapper that must stay unique (a `<ul>`, `<tbody>`, `<g>` in SVG, and similar).\n\n\n#### Best practices\n\n- Prefer `of` for value iteration:\n\n  - Use `item of items` or `(index, item) of items` for arrays.\n  - Use `(key, value) of obj` when you need both key and value.\n\n- Use `in` only for keys:\n\n  - `key in obj` is appropriate when you only care about property names.\n  - Avoid `(key, value) in expr` with `*each`; the current implementation does not bind the pair as you might expect.\n\n- Keep expressions simple:\n\n  - If you need complex filtering or sorting, consider precomputing the collection in data or in a method instead of writing a very long expression in `*each`.\n\n- Avoid mutating the iterated collection in the body:\n\n  - Modifying the array or object you are looping over while rendering makes behavior harder to reason about.\n  - Prefer to build a derived collection and iterate that.\n\n- Use containers that match markup semantics:\n\n  - For table rows, use `*each` on `<tbody>` or `<thead>` and keep `<tr>` as the repeated child.\n  - For lists, use `*each` on `<ul>` or `<ol>` only when you explicitly want a single list node.\n\n- Remember the structural restriction:\n\n  - Do not combine `*each` with `*include` or `*import` on the same element.\n\n\n#### Additional examples\n\nIterating over an object map:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"users\": {\n    \"u1\": { \"name\": \"Alice\" },\n    \"u2\": { \"name\": \"Bob\" }\n  }\n}'>\n  <dl *each=\"(id, user) of users\">\n    <dt *print=\"id\"></dt>\n    <dd *print=\"user.name\"></dd>\n  </dl>\n</serc-rod>\n```\n\nUsing `*each` on `<tbody>`:\n\n```html\n<serc-rod id=\"table\" data='{\n  \"rows\": [\n    { \"id\": 1, \"name\": \"Alpha\" },\n    { \"id\": 2, \"name\": \"Beta\" }\n  ]\n}'>\n  <table>\n    <thead>\n      <tr>\n        <th>ID</th>\n        <th>Name</th>\n      </tr>\n    </thead>\n    <tbody *each=\"row of rows\">\n      <tr>\n        <td *print=\"row.id\"></td>\n        <td *print=\"row.name\"></td>\n      </tr>\n    </tbody>\n  </table>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*each` and `n-each` are aliases; choose one style per project for consistency.\n- The expression on `*each` is evaluated as normal JavaScript inside Sercrod's expression sandbox.\n- In the current implementation:\n  - `*each` expects arrays, plain objects, or other iterable values.\n  - Falsy results behave like an empty collection.\n  - `(key, value) in expr` is parsed but not useful; always prefer `(key, value) of expr` when using `*each`.\n- Structural combinations where multiple directives compete for the same host's children (such as `*each` plus `*include` or `*each` plus `*import` on one element) are not supported and should be avoided.\n",
  "eager": "### *eager\n\n#### Summary\n\n`*eager` is an optional modifier for `*input` / `n-input` that makes Sercrod re-render the host \"eagerly\" on every input-like event for text fields and textareas.\n\n- Without `*eager`, data is still written on every event, but the host only performs a narrower update (typically propagating changes to child Sercrod components).\n- With `*eager`, the host calls `update()` after each input event on the control (subject to `*stage` and host-level `*lazy`).\n- `*eager` has an alias `n-eager`.\n\n`*eager` only has effect on elements that also carry `*input` or `n-input` and are treated as text-like controls by Sercrod.\n\n\n#### Basic example\n\nEnable eager updates on a text input:\n\n```html\n<serc-rod id=\"profile\" data='{\"user\":{\"name\":\"\"}}'>\n  <form>\n    <label>\n      Name:\n      <input type=\"text\" *input=\"user.name\" *eager>\n    </label>\n\n    <p>Preview: <span *print=\"user.name\"></span></p>\n  </form>\n</serc-rod>\n```\n\nBehavior:\n\n- Every keystroke in the input writes to `user.name`.\n- Because of `*eager`, Sercrod calls `update()` for the host after each input event.\n- The `<span *print=\"user.name\">` is kept in sync keystroke-by-keystroke, without waiting for a change or submit event.\n\n\n#### Behavior\n\nScope of `*eager`:\n\n- `*eager` is interpreted only when the element also has `*input` or `n-input`.\n- It is a non-structural modifier: it does not change how templates are expanded, only when and how the host re-renders in response to user input.\n\nTargeted controls:\n\n- When the host binds a form control through `*input` / `n-input`, it distinguishes three main groups:\n\n  - Text-like controls:\n    - `<input>` with a type that is not `checkbox` or `radio`.\n    - `<textarea>`.\n    - These use the `input` event for \"live\" updates.\n\n  - Change-driven controls:\n    - `<input type=\"checkbox\">`.\n    - `<input type=\"radio\">`.\n    - `<select>` (single or multiple).\n    - These use the `change` event.\n\n  - Everything else:\n    - Treated as change-driven; bound on `change`.\n\nEffects of `*eager`:\n\n- For text-like controls (text inputs and textareas):\n\n  - Every input event:\n    1. Writes the new value to the bound data path (via `*input`).\n    2. If the host is not staged (`*stage` is not active):\n\n       - If `*eager` (or `n-eager`) is truthy:\n         - The host calls `update()` immediately.\n       - Otherwise:\n         - The host triggers only child updates through `_updateChildren(...)`.\n\n- For change-driven controls (checkbox, radio, select, other):\n\n  - `*eager` is currently not consulted.\n  - These controls always write on `change`, and the update strategy is governed by `*lazy` (not by `*eager`).\n\nIn other words:\n\n- `*eager` specifically upgrades the update policy for text-like `*input` bindings.\n- It does not override the change-event logic of checkboxes, radios, or selects.\n\n\n#### Activation and value semantics\n\n`*eager` and `n-eager` are treated as boolean or conditionally-boolean attributes with a small amount of convenience logic.\n\nRecognized forms:\n\n- Bare attribute (always enabled):\n\n  ```html\n  <input *input=\"form.name\" *eager>\n  <input *input=\"form.email\" n-eager>\n  ```\n\n  - If `*eager` / `n-eager` is present and its attribute value is empty or `null`, `isEager` becomes `true`.\n\n- Conditional expression:\n\n  ```html\n  <input *input=\"form.name\" *eager=\"settings.livePreview\">\n  <input *input=\"form.email\" n-eager=\"user.prefersEagerInputs\">\n  ```\n\n  - Sercrod evaluates the attribute value as a normal expression in the current scope.\n  - If evaluation succeeds:\n    - `isEager` is `Boolean(result)`.\n  - If evaluation throws (for example, due to a `ReferenceError`):\n    - Sercrod falls back to a string-based rule:\n      - If the raw attribute value is `\"false\"` (any case), `isEager` is `false`.\n      - Any other non-empty literal string makes `isEager` `true`.\n\nPractical interpretation:\n\n- Use bare `*eager` when you always want eager behavior.\n- Use `*eager=\"expr\"` when you want to toggle eager behavior from application data.\n- If you accidentally give it a literal string that is not a valid expression, `\"false\"` disables eagerness, everything else enables it.\n\n\n#### Interaction with *input / n-input\n\n`*eager` has no meaning by itself. It only matters in combination with `*input` or `n-input`:\n\n- Without `*input` / `n-input`:\n\n  - `*eager` is effectively ignored.\n  - Sercrod does not attach eager behavior to arbitrary elements.\n\n- With `*input` / `n-input`:\n\n  - Sercrod reads `*eager` / `n-eager` during binding setup for that control.\n  - `*eager` influences how often the host re-renders in response to that control's user events.\n\nData writes:\n\n- Regardless of `*eager`, when the control's event handler fires:\n\n  - Sercrod always writes the new value to the bound path via `assign_expr`.\n  - `*eager` does not delay or batch the write; it only changes the re-render strategy.\n\nSummary:\n\n- `*input` controls \"what gets written and where\".\n- `*eager` controls \"how aggressively the host re-renders after a text input changes\".\n\n\n#### Interaction with *lazy\n\nThere are two separate uses of `*lazy` in Sercrod:\n\n1. Host-level `*lazy` on `<serc-rod>`:\n   - Affects how often the host re-renders in response to generic updates and events.\n\n2. Input-level `*lazy` on controls that also have `*input` / `n-input`:\n   - Affects how change-driven controls (checkbox, radio, select, other) update the host after writes.\n\n`*eager` interacts only with the first group (text-like `*input` bindings) and does not override the second:\n\n- For text inputs and textareas:\n\n  - `*eager` decides whether to call `update()` on each input event.\n  - Host-level `*lazy` can still influence what `update()` actually does:\n    - If the host is marked `*lazy`, an `update()` triggered by `*eager` may short-circuit to \"child-only updates plus hooks\" instead of a full template rebuild.\n\n- For change-driven controls:\n\n  - `*eager` is ignored.\n  - `*lazy` on the control (or on the host) controls whether `update()` is called on change, or whether only children are updated.\n\nRule of thumb:\n\n- Use `*eager` on text-like `*input` controls when you want immediate visual feedback.\n- Use `*lazy` on hosts or change-driven controls when you want to avoid full host re-renders on every change.\n\n\n#### Interaction with *stage\n\n`*eager` respects `*stage`:\n\n- When the host is in staged mode (`*stage` is active and `_stage` is set):\n\n  - Input handlers still write into the staged data copy.\n  - However, the eager update path is disabled:\n    - The guards around `update()` check `!this._stage` before re-rendering.\n  - The host is not re-rendered on each input, even if `*eager` is true.\n\n- When you later apply the staged changes (for example via `*apply` or the appropriate host-level flow):\n\n  - The host re-renders based on the staged data being committed.\n  - `*eager` does not affect this commit-time re-render; it only affects live updates while the host is not staged.\n\nThis allows you to combine:\n\n- `*stage` for \"edit, then apply\" flows, with\n- `*eager` for immediate feedback in non-staged contexts.\n\n\n#### Evaluation timing\n\nThe value of `*eager` is determined when Sercrod binds the element:\n\n- During `_renderElement`, when a node with `*input` / `n-input` is processed:\n  - Sercrod parses and evaluates the `*eager` / `n-eager` attribute.\n  - The resulting boolean `isEager` is captured in the event handlers.\n\nConsequences:\n\n- Changes to the expression behind `*eager` only take effect after the host re-renders and the binding is re-established.\n- `*eager` is not re-evaluated on every keystroke; it is per-render, not per-event.\n\nIf you need to switch between eager and non-eager modes at runtime:\n\n- Drive the condition in `*eager=\"...\"` from reactive data.\n- Cause a host re-render (for example by updating any data that affects the template).\n- The next binding pass will pick up the new value of `*eager`.\n\n\n#### Execution model\n\nFor a text-like input with `*input` and optional `*eager`, the flow is:\n\n1. Binding setup (render time):\n\n   - Sercrod reads the `*input` / `n-input` expression and figures out a target data object.\n   - It evaluates `*eager` / `n-eager` once to compute `isEager`.\n   - It sets the initial control value based on the current data.\n   - It attaches event listeners (`input` and/or `change`) that capture `isEager`.\n\n2. On each `input` event (text inputs and textareas):\n\n   - Sercrod transforms the raw value using any installed input filters.\n   - It writes the value to the bound data path with `assign_expr`.\n   - If the host is not staged:\n     - If `isEager` is `true`:\n       - It calls `update()` on the host.\n     - Otherwise:\n       - It only propagates updates to child Sercrod instances (`_updateChildren(...)`).\n\n3. On `change` events (checkbox/radio/select/others):\n\n   - Sercrod computes and writes the new value, similar to text inputs.\n   - `isEager` is ignored; the actual update policy is controlled by `*lazy`.\n\nIn all cases, `*eager` does not change what is written, only when and how broadly the host re-renders.\n\n\n#### Best practices\n\n- Use `*eager` for \"live preview\" fields:\n\n  - Examples: search boxes, \"slug\" fields, inline previews, character counters.\n  - These benefit from the host re-rendering on every keystroke.\n\n- Avoid `*eager` on very heavy components:\n\n  - If the host template is large or expensive to render, eager updates on every keystroke can become costly.\n  - Consider:\n    - Leaving the host in the default behavior (child-only updates).\n    - Or using a staged / debounced pattern at the application level.\n\n- Combine with `*stage` for complex forms:\n\n  - Use `*stage` to isolate edits.\n  - Use `*eager` only on selected inputs where live feedback is clearly worth the cost.\n  - Remember that `*eager` is ignored while the host is staged; the main benefit appears once the host returns to non-staged mode.\n\n- Keep `*eager` expressions simple:\n\n  - Prefer boolean or simple property checks, such as `*eager=\"settings.eagerInputs\"`.\n  - If you need more elaborate logic, compute it in data or methods, and reference it from `*eager`.\n\n- Do not rely on `*eager` for change-only controls:\n\n  - For checkboxes, radios, and selects, `*eager` does not alter the re-render policy in the current implementation.\n  - For those controls, use `*lazy` (or host-level `*lazy`) to adjust behavior.\n\n\n#### Examples\n\nLive search box:\n\n```html\n<serc-rod id=\"search\" data='{\"query\":\"\",\"results\":[]}'>\n  <div>\n    <input\n      type=\"search\"\n      placeholder=\"Type to search\"\n      *input=\"query\"\n      *eager>\n  </div>\n\n  <ul *each=\"item of results\">\n    <li *print=\"item.label\"></li>\n  </ul>\n</serc-rod>\n```\n\n- Each keystroke updates `query` and triggers `update()` because of `*eager`.\n- A separate mechanism (for example `*post` or `*api` wired to `query`) can refresh `results`.\n\nConditional eager mode:\n\n```html\n<serc-rod id=\"settings\" data='{\n  \"user\": { \"name\": \"\" },\n  \"ui\":   { \"eagerPreview\": true }\n}'>\n  <label>\n    <input type=\"checkbox\" *input=\"ui.eagerPreview\">\n    Enable eager preview\n  </label>\n\n  <label>\n    Name:\n    <input\n      type=\"text\"\n      *input=\"user.name\"\n      *eager=\"ui.eagerPreview\">\n  </label>\n\n  <p>Preview: <span *print=\"user.name\"></span></p>\n</serc-rod>\n```\n\n- When `ui.eagerPreview` is `true`, name changes re-render the host on each keystroke.\n- When it is `false`, the host falls back to the default, narrower update behavior.\n\n\n#### Notes\n\n- `*eager` and `n-eager` are aliases; choose one naming style and stick to it for consistency.\n- `*eager` is meaningful only in combination with `*input` / `n-input`; on other elements, it is effectively ignored.\n- In the current implementation:\n  - Text-like controls with `*input` always write data on each `input` event; `*eager` controls whether the host re-renders fully on each event.\n  - Change-driven controls (checkbox, radio, select, others) do not consult `*eager` and instead rely on `*lazy`.\n- Host-level `*lazy` and `*stage` are still respected; `*eager` can request eager host updates, but the host's own lazy and staged policies can limit when full re-renders actually occur.\n",
  "else": "### *else / n-else\n\n#### Summary\n\n`*else` marks the fallback branch of an `*if / *elseif / *else` chain.\nWhen all previous `*if` and `*elseif` conditions in the same sibling chain are false, Sercrod renders the `*else` branch instead.\n\n`n-else` is a prefix-agnostic alias with identical behavior.\n\n\n#### Description\n\n`*else` is a structural directive that participates in a contiguous sibling chain together with `*if` and `*elseif`:\n\n- The chain always starts at a sibling whose element has `*if` or `n-if`.\n- Zero or more `*elseif` / `n-elseif` branches may follow.\n- At most one `*else` / `n-else` is expected at the end of that chain.\n\nAt render time, Sercrod:\n\n1. Finds the \"head\" element for the chain by walking left from the current node until it encounters a sibling with `*if` / `n-if`. If no such element is found, the `*elseif` / `*else` is treated as invalid and ignored.\n2. Starting from the head, walks right across siblings as long as each sibling has at least one of `*if`, `*elseif`, or `*else` (or their `n-` equivalents) and they share the same parent. The run of such elements forms a single chain; the next `*if` starts a new chain.\n3. Evaluates branches in order:\n   - `if` branches first, then `elseif` branches.\n   - The first branch whose condition is true becomes the chosen branch.\n   - If none of the conditions are true, the `else` branch is chosen (when present).\n\nThe `*else` attribute itself never has its value evaluated; Sercrod only checks whether it is present. Any string given as its value is ignored and acts purely as documentation for humans.\n\nOnce a branch is chosen, Sercrod clones that element, strips `*if`, `*elseif`, `*else`, and `*let` / `n-let` from the clone, and renders that clone with the computed branch scope into the parent. Exactly one branch per chain is rendered.\n\n\n#### Basic example\n\nA simple access check with `*if` and `*else`:\n\n```html\n<div *if=\"user && user.isAdmin\">\n  <p>Welcome, admin.</p>\n</div>\n<div *else>\n  <p>Access denied.</p>\n</div>\n```\n\n- When `user.isAdmin` is truthy, only the first `<div>` is rendered.\n- When `user` is missing or `user.isAdmin` is falsy, the second `<div>` (with `*else`) is rendered instead.\n- Both elements share the same parent and form a single chain headed by the first `*if`.\n\n\n#### Behavior\n\n- **Presence-based directive**\n  `*else` / `n-else` is treated as a boolean marker. Sercrod never reads its value as an expression; only the presence of the attribute matters.\n\n- **Single-branch selection**\n  For each chain, Sercrod selects at most one branch:\n  - First matching `*if` or `*elseif` branch wins.\n  - If none match, the `*else` / `n-else` branch (if any) is chosen.\n  - If there is no `*else` and no condition matches, nothing is rendered for that chain.\n\n- **Template versus clone**\n  The decision is made using the original template elements, but Sercrod renders a clone:\n  - The clone has `*if`, `*elseif`, `*else`, and `*let` / `n-let` removed.\n  - The clone is then processed as a normal element by `renderNode`.\n\n- **Invalid chains are ignored**\n  - If Sercrod cannot find a head `*if` for a `*else` or `*elseif`, the chain is considered invalid and is ignored; the current node is not rendered as part of any chain.\n\n\n#### Evaluation timing\n\n- **Chain discovery**\n  When `renderNode` encounters an element with `*if`, `*elseif`, or `*else`, it runs the chain logic exactly once, on the head node of that chain (`node === head`). Any later siblings in the same chain do not re-run the logic themselves.\n\n- **Condition evaluation**\n  For `if` and `elseif` branches:\n  - The corresponding attribute (`*if`, `n-if`, `*elseif`, `n-elseif`) is read from the template element.\n  - The expression is evaluated with `_eval_cond` against the branch scope derived from the effective scope (and optional branch `*let`).\n\n- **Else evaluation**\n  For `else` branches:\n  - There is no condition expression.\n  - The branch is selected only if no previous branch has already been chosen.\n  - Once the `else` branch is tentatively chosen, iteration ends.\n\n- **Per-update behavior**\n  On each host `update`, the template is re-rendered, the chain is recomputed, and the appropriate branch is re-selected based on the current data. There is no caching of \"last chosen\" beyond a single render pass.\n\n\n#### Execution model\n\n- `*else` is a **structural directive**:\n  - It controls which sibling element is rendered, but has no effect on DOM mutations beyond the chain.\n  - It does not directly modify data; it only selects which subtree to render.\n\n- The chain traversal and selection happen inside the templating engine before any non-structural directives on the chosen clone are processed, so the contents of the chosen branch are then rendered normally (including `*print`, bindings, event handlers, etc.).\n\n\n#### Variable creation\n\n`*else` / `n-else` does not create any variables on its own.\n\nWithin a chain:\n\n- The effective scope for the head `*if` branch is the current scope (`effScope`).\n- Each branch can optionally have its own `*let` / `n-let` attribute; when present:\n  - Sercrod creates a new branch scope with the current scope as prototype.\n  - `eval_let` is executed for that branch to populate the branch scope.\n  - That branch scope is used when rendering the chosen clone.\n\n`*else` can therefore see variables defined by its own branch `*let`, but it does not introduce any special names like `$switch` or loop indices by itself.\n\n\n#### Scope layering\n\nFor a typical chain:\n\n- **Base scope**: the effective scope at the point where the chain is rendered (`effScope`).\n- **Branch scope**:\n  - If a branch has `*let` / `n-let`, Sercrod builds a new scope object that inherits from `effScope` and applies the `let` bindings into it.\n  - Otherwise, the branch reuses `effScope` directly.\n- **Else branch**:\n  - Uses its own branch scope (with or without `*let`), just like `if` and `elseif`.\n\nNo additional scope layering is introduced by `*else` itself; it simply participates in the same mechanism as the other branches.\n\n\n#### Parent access\n\n`*else` does not introduce any new parent-access semantics. Inside expressions used in the chosen branch (for example in `*print` or `*if` nested within the branch), access to:\n\n- Parent scopes,\n- Root host data,\n- And any Sercrod-specific helper variables\n\nworks exactly as it does in any other element. The only responsibility of `*else` is to decide whether this branch is selected as the fallback.\n\n\n#### Use with conditionals and loops\n\n- **With `*if` / `*elseif`**\n  `*else` must be part of a contiguous run of siblings whose first element is `*if` / `n-if`:\n  - Siblings without any of `*if`, `*elseif`, `*else` break the chain.\n  - Encountering a new `*if` / `n-if` ends the previous chain and starts a new one.\n\n- **Multiple chains in the same parent**\n  A parent element can host multiple independent chains, each starting at its own `*if`. An `*else` always belongs to the most recent chain whose head is the nearest previous sibling with `*if` / `n-if` and no intervening non-conditional element.\n\n- **Inside loops**\n  `*else` works as expected inside repeated structures such as `*each` or `*for`:\n  - Each iteration renders its own copy of the chain.\n  - The chain selection runs independently per iteration, based on the iteration's scope.\n\n- **With `*switch`**\n  `*else` is unrelated to `*switch` / `*case` / `*default`. It is not consulted by the switch machinery; within `*switch`, you should use `*default` for fallback behavior instead of `*else`.\n\n\n#### Best practices\n\n- Always keep `*else` at the end of a chain, after all `*if` and `*elseif` branches, and with the same parent element.\n- Use `*else` for genuinely unconditional fallbacks. If you need a conditional fallback, use an extra `*elseif` instead of an `*else` with a value (since that value is ignored).\n- Keep chains contiguous and focused:\n  - Avoid inserting unrelated elements between the `*if` head and its `*else`.\n  - If you need layout markup between branches, wrap each branch in a container and apply the conditional directive to that container.\n- Prefer a single `*else` per chain. While the runtime ignores extra `*else` siblings when a branch has already been chosen, multiple fallback-like elements can be confusing to readers.\n\n\n#### Examples\n\n##### Multiple chains in the same parent\n\nTwo independent chains controlled by different conditions:\n\n```html\n<div *if=\"user\">\n  <p>Logged in as %user.name%.</p>\n</div>\n<div *else>\n  <p>You are not logged in.</p>\n</div>\n\n<div *if=\"notifications.length > 0\">\n  <p>You have %notifications.length% notifications.</p>\n</div>\n<div *else>\n  <p>No notifications.</p>\n</div>\n```\n\n- The first pair (`user` check) forms one chain.\n- The second pair (`notifications.length > 0`) forms another, independent chain.\n- Each `*else` only belongs to its own chain headed by the preceding `*if`.\n\n##### Using `n-else` with mixed prefixes\n\nYou can mix `*if` with `n-else` in the same chain:\n\n```html\n<section *if=\"status === 'ok'\">\n  <p>All systems go.</p>\n</section>\n<section n-else>\n  <p>Something is wrong.</p>\n</section>\n```\n\n- The `*if` head uses the `*` prefix.\n- The fallback uses the `n-` prefix.\n- The implementation checks both `*else` and `n-else` attributes and treats them equivalently.\n\n\n#### Notes\n\n- `*else` / `n-else` is only meaningful as part of a valid `*if` chain. If Sercrod cannot find a preceding `*if` / `n-if` in the same sibling run, the `*else` is considered part of an invalid chain and is not used in conditional rendering.\n- The attribute value of `*else` / `n-else` is ignored; do not rely on it to carry conditions.\n- Only one branch per chain is rendered. If you see multiple branches appearing, check that you did not accidentally break the chain by inserting non-conditional siblings or by misplacing `*if`.\n- Control attributes `*if`, `*elseif`, `*else`, and `*let` (and their `n-` variants) are removed from the rendered clone, so they never appear in the final DOM.\n",
  "elseif": "### *elseif\n\n#### Summary\n\n`*elseif` defines an else-if branch inside a `*if / *elseif / *else` chain. Only one branch of the chain is rendered. The attribute form `n-elseif` is available and behaves identically.\n\nThe short built-in manual text describes it as:\n\n- \"else-if branch paired with a preceding *if or *elseif.\"\n\n\n#### Description\n\n`*elseif` is a structural directive that participates in a sibling chain together with `*if` and `*else`. The chain is evaluated from left to right:\n\n- The left-most `*if` (or `n-if`) in the group is the head of the chain.\n- Each `*elseif` (or `n-elseif`) provides an additional condition that is only checked if all previous `*if` / `*elseif` conditions were false.\n- An optional `*else` can be used as a final fallback when no condition in the chain matched.\n\nThe engine guarantees that at most one branch of a chain is rendered. All decisions are made at the head `*if` node; each branch is then cloned or skipped as needed.\n\n\n#### Basic example\n\n```html\n<ul>\n  <li *if=\"score >= 90\">Grade A</li>\n  <li *elseif=\"score >= 80\">Grade B</li>\n  <li *elseif=\"score >= 70\">Grade C</li>\n  <li *else>Grade F</li>\n</ul>\n```\n\nIn this example:\n\n- If `score >= 90`, only the first `<li>` is rendered.\n- If not, but `score >= 80`, only the second `<li>` is rendered.\n- If not, but `score >= 70`, only the third `<li>` is rendered.\n- Otherwise, only the `*else` branch is rendered.\n\nSemantically it works like a classic `if / else if / else` chain in JavaScript.\n\n\n#### Behavior\n\n##### Chain formation (how *elseif joins *if)\n\n`*elseif` never works alone. It must be part of a chain headed by a `*if` (or `n-if`) on a preceding sibling:\n\n- For every element, Sercrod checks whether it has any of `*if`, `*elseif`, or `*else` (or their `n-` forms).\n- If the current element has `*if` / `n-if`, it is considered the head of a chain.\n- If the current element has `*elseif` or `*else` (no `*if`), Sercrod searches to the left among previous siblings for the nearest element that has `*if` or `n-if`.\n  - The search stops early if it encounters a sibling that has none of `*if`, `*elseif`, or `*else`. In that case, the `*elseif` cannot join a chain through that sibling.\n  - If a suitable `*if` is found, that `*if` becomes the head of the chain for this `*elseif`.\n\nIf Sercrod cannot find a preceding `*if` for a `*elseif` (or `*else`), that element is treated as an invalid chain and is completely ignored during rendering.\n\n**Important Sercrod-specific constraint**\n\nBecause chain membership is determined only through adjacent conditional siblings:\n\n- All branches belonging to a chain must be consecutive siblings that all carry one of `*if`, `*elseif`, or `*else` (or their `n-` forms).\n- Any non-conditional element between them terminates the chain.\n\nFor example:\n\n```html\n<div *if=\"mode === 'view'\">View</div>\n<p>separator</p>\n<div *elseif=\"mode === 'edit'\">Edit</div>\n```\n\nHere the second `div` is **not** part of the first `*if` chain because the `<p>` in between does not have conditional attributes. The `*elseif` cannot find a valid `*if` head and is therefore ignored.\n\n##### Head-only evaluation\n\nOnce the head `*if` is known, Sercrod enforces the following rule:\n\n- Only the head `*if` node actually evaluates and resolves the whole chain.\n- When `renderNode` is called on a `*elseif` or `*else` node that belongs to a chain, it immediately returns without rendering, because the head has already handled the chain.\n\nThis guarantees that the chain is evaluated exactly once, from its head.\n\n##### Collecting the chain\n\nStarting from the head `*if`, Sercrod walks to the right through sibling elements and collects all consecutive conditional siblings belonging to the same chain:\n\n- Each element that has `*if` / `*elseif` / `*else` (or `n-` forms) is added as a branch:\n  - `type: \"if\"` for `*if` / `n-if`\n  - `type: \"elif\"` for `*elseif` / `n-elseif`\n  - `type: \"else\"` for `*else` / `n-else`\n- The scan stops when:\n  - It encounters an element with none of the conditional attributes, or\n  - It encounters another `*if` / `n-if`, which starts a completely new chain.\n\nIf the collected list is empty (which should not normally happen once a head is found), nothing is rendered.\n\n##### Branch selection and *elseif conditions\n\nOnce Sercrod has the ordered list of branches, it selects exactly one branch:\n\n1. For each branch in order (`if`, then zero or more `elseif`s, then optional `else`):\n   - Sercrod starts from the current effective scope (`effScope`).\n   - If the branch element has `*let` / `n-let`, Sercrod creates a new branch-local scope that inherits from `effScope`, evaluates the `*let` code into this new scope, and uses it as `branchScope` for this branch only.\n2. If the branch type is `\"else\"`:\n   - It is selected only if no previous `if` / `elseif` was selected.\n   - After selecting `else`, Sercrod stops looking at later branches.\n3. If the branch type is `\"if\"` or `\"elif\"`:\n   - Sercrod chooses the correct attribute name:\n     - For `\"if\"`: `*if` or `n-if`.\n     - For `\"elif\"`: `*elseif` or `n-elseif`.\n   - It reads the expression string from that attribute. Empty strings are treated as \"no condition\" and will not match.\n   - If there is a non-empty expression, it evaluates the condition via the internal `_eval_cond` helper on the `branchScope`.\n   - If `_eval_cond` returns true, this branch is selected and no further branches are considered.\n\nIf no branch is selected (all conditions are falsy and there is no `*else`), nothing is rendered for this chain.\n\n##### Rendering the chosen branch\n\nWhen a branch (which may be `*elseif`) has been chosen:\n\n- Sercrod clones the corresponding element node (deep clone, including its children).\n- On the clone, it removes the structural control attributes:\n  - `*if` / `n-if`\n  - `*elseif` / `n-elseif`\n  - `*else` / `n-else`\n  - `*let` / `n-let`\n- It then calls `renderNode` again on this clone with the selected `outScope` (either the branch-local scope or the previous `effScope`).\n- The original template nodes (including the original `*elseif` template) are not rendered directly; they act only as templates.\n\nAs a result, the output DOM contains only the chosen branch, without `*if`, `*elseif`, `*else`, or branch-local `*let` attributes.\n\n##### n-elseif\n\nThe `n-elseif` attribute is a direct alias of `*elseif`:\n\n- Chains treat `*elseif` and `n-elseif` identically when detecting branches and evaluating conditions.\n- Manual entries and user documentation treat `*xxx` and `n-xxx` as the same directive.\n\n\n#### Evaluation timing\n\n`*elseif` conditions are evaluated during the structural rendering phase of `renderNode`, after `*let` and `*global` have updated the effective scope but before other structural directives like `*switch`, `*each`, and `*for` run on the same host node.\n\nCondition evaluation uses `_eval_cond`, which wraps the general `eval_expr` helper:\n\n- First, the expression is evaluated as a Sercrod expression with `eval_expr`, using a scope that includes:\n  - The current branch scope (including local `*let` variables).\n  - `$data` for the host data.\n  - `$root` for root data.\n  - `$parent` injected from the nearest ancestor Sercrod host.\n  - Any methods configured via `*methods` and built-in internal methods.\n- Then `_eval_cond` normalizes the value into a boolean:\n  - `false`, `null`, and `undefined` are false.\n  - Numbers are false only when `0` or `NaN`.\n  - Strings are false when empty or equal (case-insensitive) to `\"false\"`, `\"0\"`, `\"null\"`, or `\"undefined\"`. All other non-empty strings are true.\n  - Any other values are converted with JavaScript `Boolean(...)`.\n- If evaluating the expression throws an error, `_eval_cond` falls back to:\n  - Interpreting literal `\"true\"` and `\"false\"` strings directly.\n  - Otherwise logging a warning (when enabled) and returning false.\n\nThis means `*elseif=\"flag\"` and `*elseif=\"'false'\"` behave differently: `flag` depends on the runtime value of `flag`, but the literal string `\"false\"` is considered falsy by the helper.\n\n\n#### Execution model\n\nSercrod's overall update flow re-renders the host's children from the stored template on each update call. Within that flow, a `*if / *elseif / *else` chain is a structural (\"returning\") directive group:\n\n- The chain decides which branch (if any) to render.\n- Exactly one branch is cloned and rendered; the others are ignored.\n- Because the entire DOM subtree is reconstructed from the template on each update, the active `*elseif` branch can change between updates.\n\nThe clone of the chosen branch then runs all non-structural directives (such as `*print`, `:class`, event bindings, etc.) as part of regular element rendering.\n\n\n#### Variable creation\n\n`*elseif` itself does not create variables, but it interacts closely with `*let` on the same element:\n\n- When a branch (including `*elseif`) has a `*let` or `n-let` attribute:\n  - Sercrod creates a new local scope whose prototype is the current effective scope (`effScope`).\n  - It runs `eval_let` in this new scope, allowing the branch to define local variables.\n  - This branch-local scope is then used to evaluate the `*if` / `*elseif` condition and to render the chosen branch if it is selected.\n- Branch-local variables do **not** leak into the outer host or into other branches.\n\nExample:\n\n```html\n<div *if=\"user\">\n  <p *let=\"full = user.first + ' ' + user.last\">\n    Hello, %full%!\n  </p>\n</div>\n<div *elseif=\"guest\">\n  <p *let=\"label = 'Guest ' + guest.id\">\n    Hello, %label%!\n  </p>\n</div>\n<div *else>\n  <p>Hello, anonymous visitor.</p>\n</div>\n```\n\nIn this pattern, `full` and `label` exist only inside their respective branches.\n\n\n#### Scope layering\n\n`*elseif` uses the same scope layering as `*if`:\n\n- The starting scope for chain evaluation is the effective scope (`effScope`) after top-level `*let` and `*global` on the host element have run.\n- Each branch can further extend this scope with a branch-local `*let`.\n- Expressions inside `*elseif` have access to:\n  - Host data via `$data`.\n  - Root data via `$root`.\n  - Parent host data via `$parent`.\n  - Shared methods from `*methods` and internal utilities.\n\nIf a `*if / *elseif / *else` group is nested inside loops (`*for`, `*each`) or inside other components, the loop/parent scopes are simply part of the normal scope chain and are available to the `*elseif` condition.\n\n\n#### Parent access\n\nBecause `eval_expr` injects `$parent` into the evaluation scope when it is not already defined, `*elseif` conditions can safely refer to properties of the parent Sercrod host's data.\n\nTypical patterns:\n\n```html\n<div *if=\"$parent.mode === 'edit'\">Edit in parent context</div>\n<div *elseif=\"$parent.mode === 'view'\">View in parent context</div>\n<div *else>Other mode</div>\n```\n\nThis works regardless of whether the chain is inside loops, nested components, or other control structures.\n\n\n#### Use with conditionals and loops\n\n`*elseif` is designed to be combined with other control structures by **nesting**, not by mixing multiple structural directives on the same element.\n\nCommon patterns include:\n\n- `*for` with an inner `*if / *elseif / *else` chain:\n\n  ```html\n  <ul>\n    <li *for=\"item in items\">\n      <span *if=\"item.status === 'ok'\">OK: %item.name%</span>\n      <span *elseif=\"item.status === 'pending'\">Pending: %item.name%</span>\n      <span *else>Unknown: %item.name%</span>\n    </li>\n  </ul>\n  ```\n\n- `*switch` outside, `*if / *elseif / *else` inside a case:\n\n  ```html\n  <div *switch=\"view\">\n    <section *case=\"'detail'\">\n      <div *if=\"item\">Showing details</div>\n      <div *elseif=\"fallbackItem\">Showing fallback</div>\n      <div *else>No item selected</div>\n    </section>\n    <section *default>\n      Default view\n    </section>\n  </div>\n  ```\n\nIn these patterns, the outer constructs (`*for`, `*switch`) decide which subtree is rendered, and the inner `*if` chain refines the result inside that subtree. The scopes provided by the outer structures are available to the `*elseif` conditions via normal scope inheritance.\n\n\n#### Best practices\n\n- **Keep branches adjacent.**\n  Never insert non-conditional elements between `*if`, `*elseif`, and `*else` branches. Any such element will terminate the chain and can cause `*elseif` to become an invalid chain that is silently ignored.\n\n- **Always start with *if.**\n  Do not start a chain with `*elseif` or `*else`. They must have a preceding `*if` / `n-if` in the same parent to be valid.\n\n- **Use *else for catch-all cases.**\n  When you need a fallback, use `*else` instead of `*elseif=\"true\"`. The helper `_eval_cond` treats literal `\"true\"` and `\"false\"` specially only in error situations; using `*else` makes the intent clearer and avoids surprises.\n\n- **Keep conditions simple and predictable.**\n  Remember that strings like `\"false\"` and `\"0\"` are treated as false by `_eval_cond`. Prefer real booleans or simple comparisons like `value > 0`, `flag === true`, or `status === 'ok'`.\n\n- **Use branch-local *let for clarity.**\n  When a branch needs derived values, define them with `*let` on the branch itself so the scope is clearly limited to that branch.\n\n- **Prefer nesting over mixing structural directives on one element.**\n  While Sercrod defines an order for processing structural directives, it is clearer and less error-prone to keep one structural directive per element and nest structures in the DOM tree instead of stacking them on the same tag.\n\n\n#### Examples\n\n##### Example 1: Multi-mode display\n\n```html\n<div>\n  <p *if=\"mode === 'view'\">Viewing %item.title%</p>\n  <p *elseif=\"mode === 'edit'\">Editing %item.title%</p>\n  <p *elseif=\"mode === 'create'\">Creating a new item</p>\n  <p *else>Please select a mode.</p>\n</div>\n```\n\nBehavior:\n\n- Exactly one `<p>` is rendered based on `mode`.\n- If `mode` is none of `\"view\"`, `\"edit\"`, or `\"create\"`, the fallback `*else` is used.\n\n##### Example 2: Branch-local *let\n\n```html\n<section>\n  <h2>Order summary</h2>\n\n  <p *if=\"order\">\n    <span *let=\"total = order.price * order.qty\">\n      Total: %total%\n    </span>\n  </p>\n\n  <p *elseif=\"draft\">\n    <span *let=\"estimate = draft.price * draft.qty\">\n      Estimated total: %estimate%\n    </span>\n  </p>\n\n  <p *else>\n    No order or draft found.\n  </p>\n</section>\n```\n\nHere:\n\n- Only one of the paragraphs is rendered.\n- `total` and `estimate` variables are branch-local and cannot be seen outside their own branch.\n\n##### Example 3: Avoiding invalid chains\n\n```html\n<div *if=\"status === 'ok'\">OK</div>\n<div *elseif=\"status === 'warn'\">Warning</div>\n\n<!-- This element breaks the chain -->\n<hr>\n\n<div *elseif=\"status === 'error'\">Error</div>\n<div *else>Unknown</div>\n```\n\nIn this layout:\n\n- The first two `div` elements form a valid `*if / *elseif` chain.\n- The `<hr>` breaks the chain.\n- The later `*elseif` and `*else` do not see the first `*if` as their head, so they form invalid chains and are ignored. Only the first chain participates in rendering.\n\nTo fix this, keep the branches adjacent:\n\n```html\n<div *if=\"status === 'ok'\">OK</div>\n<div *elseif=\"status === 'warn'\">Warning</div>\n<div *elseif=\"status === 'error'\">Error</div>\n<div *else>Unknown</div>\n\n<hr>\n```\n\n\n#### Notes\n\n- `*elseif` always belongs to a chain with `*if`; it is never evaluated on its own. If it cannot find a preceding `*if` or `n-if` in the same parent without crossing non-conditional siblings, the element is silently ignored.\n- `n-elseif` is simply the attribute form of the same directive; all semantics, including chaining and condition evaluation, are identical.\n- Only one branch of a chain is ever rendered. If no condition matches and there is no `*else`, the entire chain produces no output.\n- Conditions are evaluated using the same expression semantics as other directives, with `$data`, `$root`, `$parent`, and configured methods all available in scope.\n",
  "event-blur": "### @blur\n\n#### Summary\n\n`@blur` attaches a handler to the native `blur` event on an element.\nThe expression on `@blur` is evaluated when the element loses focus.\nTypical uses include marking a field as \"touched\", running lightweight validation, or committing staged values when the user leaves a control.\n\n`@blur` is part of the event handler family (such as `@click`, `@input`, `@change`, `@focus`) and follows the same general evaluation rules as other `@` directives.\n\n\n#### Basic example\n\nMarking an input as \"touched\" when it loses focus:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"profile\": { \"name\": \"\" },\n  \"touched\": { \"name\": false }\n}'>\n  <label>\n    Name:\n    <input type=\"text\"\n           :value=\"profile.name\"\n           @blur=\"touched.name = true\">\n  </label>\n\n  <p *if=\"touched.name && !profile.name\">\n    Please enter your name.\n  </p>\n</serc-rod>\n```\n\nBehavior:\n\n- The input is bound to `profile.name` via `:value`.\n- When the user focuses the input and then moves focus elsewhere, the browser fires a `blur` event.\n- Sercrod invokes the `@blur` expression in the current scope, setting `touched.name = true`.\n- The paragraph becomes visible if the field is now empty and has been blurred at least once.\n\n\n#### Behavior\n\nCore rules:\n\n- Target event\n  `@blur` wires the native `blur` event on the element where it is declared.\n  The element must be focusable for the handler to fire (for example, inputs, textareas, buttons, links, or elements made focusable via `tabindex`).\n\n- Expression evaluation\n  Sercrod parses the attribute value as an expression (for example `touched.name = true` or `save(profile)`).\n  When the event fires, Sercrod evaluates this expression using the same scope model as other directives on that element.\n\n- One way effect\n  The result of the expression is ignored by Sercrod.\n  What matters is the side effect of the expression (writing to data, calling functions, or both).\n\n- Multiple handlers\n  You can combine `@blur` with other event directives on the same element (such as `@focus` or `@input`), as long as each one uses a distinct event name.\n  Each handler is evaluated independently when its event fires.\n\nSercrod does not change the native timing or semantics of the `blur` event; it only injects expression evaluation when the event occurs.\n\n\n#### Evaluation timing\n\n`@blur` participates in Sercrod's normal event lifecycle:\n\n- Structural directives first\n  Directives such as `*if`, `*for`, `*each`, `*switch`, and `*include` decide the presence and shape of the element.\n  If an element is removed by a structural directive, no event handler is attached.\n\n- Attribute phase\n  Once an element is kept, Sercrod processes its attributes.\n  Event attributes starting with `@` are recognized in the same pass as colon attributes like `:value` and directive attributes like `*if`.\n\n- Listener attachment\n  During this pass, Sercrod registers a `blur` listener for each element with `@blur`.\n  On re-renders, Sercrod ensures that the handler reflects the current expression and scope.\n\n- Event firing\n  When the browser fires `blur` on that element, Sercrod evaluates the stored expression in the current Sercrod scope associated with the host.\n\nThere is no special debounce or scheduling around `@blur`; it runs synchronously when the event fires, in the same turn as the native `blur` event.\n\n\n#### Execution model\n\nConceptually, the runtime behaves as follows for `@blur`:\n\n1. During rendering, Sercrod finds an element with an attribute named `@blur`.\n2. It reads the attribute value as a source string, for example `touched.name = true` or `validateField('name')`.\n3. Sercrod compiles or stores this expression so it can be evaluated later in the current host's scope.\n4. Sercrod attaches a native event listener for `\"blur\"` on that element.\n5. When the browser fires `blur`:\n   - Sercrod prepares the evaluation context for this host and element.\n   - Sercrod evaluates the stored expression with its expression evaluator.\n   - If evaluation throws, Sercrod catches the error and logs it through the configured logging mechanism; the error does not stop other handlers or other elements from updating.\n6. If `cleanup.handlers` is enabled in the configuration, Sercrod may remove the original `@blur` attribute from the DOM, leaving only the wired listener and any other visible attributes.\n\nThe listener itself is lean: it does not try to change the propagation of the event (such as stopping it) unless your expression does so through normal browser APIs.\n\n\n#### Use with form fields and data bindings\n\n`@blur` is often paired with data bindings to form controls:\n\n- With `:value` or `:checked`:\n\n  ```html\n  <input type=\"text\"\n         :value=\"profile.email\"\n         @blur=\"profile.email = profile.email.trim()\">\n  ```\n\n  - `:value` keeps the DOM value in sync with `profile.email`.\n  - `@blur` ensures that the value is trimmed when the user leaves the field.\n\n- With staging via `*stage`:\n\n  ```html\n  <form *stage=\"'profile'\">\n    <input type=\"text\"\n           :value=\"profile.name\"\n           @blur=\"profile.name = profile.name.trim()\">\n    <button type=\"button\" *apply=\"'profile'\">Save</button>\n    <button type=\"button\" *restore=\"'profile'\">Cancel</button>\n  </form>\n  ```\n\n  Here, `@blur` cleans up the staged copy of `profile.name`.\n  Only when the user presses \"Save\" does `*apply` commit the staged profile back to main data.\n\nBecause `@blur` runs only when focus leaves the element, it is a good fit for:\n\n- Validation that should not interrupt typing.\n- Cleaning up formatting.\n- Toggling \"touched\" flags for UI feedback.\n\n\n#### Use with conditionals and loops\n\n`@blur` can be freely combined with structural directives that control the element itself:\n\n- Conditional display:\n\n  ```html\n  <input type=\"text\"\n         *if=\"mode === 'edit'\"\n         :value=\"item.title\"\n         @blur=\"item.title_touched = true\">\n  ```\n\n  The handler is attached only when the `*if` condition is true.\n\n- Inside loops:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <input type=\"text\"\n             :value=\"item.label\"\n             @blur=\"item.touched = true\">\n    </li>\n  </ul>\n  ```\n\n  Each iteration gets its own `@blur` handler, and the expression is evaluated with that iteration's `item` in scope.\n\nIf a structural directive replaces the element on re-render (for example via key changes or switching a block), Sercrod re-attaches `@blur` on the new element instance as part of its normal rendering process.\n\n\n#### Sercrod-specific restrictions\n\nFor `@blur`, there are no special Sercrod-only restrictions beyond the general event rules:\n\n- You may put `@blur` on any focusable element.\n- You may combine `@blur` with other event directives on the same element, as long as each uses a different event name (for example `@focus`, `@input`, `@change`).\n- You may combine `@blur` with attribute bindings like `:value`, `:class`, and `:style`, and with structural directives such as `*if` or `*for` that target the same element.\n\nUnlike structural directives (`*each`, `*include`, `*import`), event directives do not compete for ownership of the element's children, so there is no \"only one of these\" constraint specific to `@blur`.\n\n\n#### Best practices\n\n- Keep handlers small\n  Use `@blur` for small, local side effects: marking fields as touched, trimming strings, or triggering simple validations.\n  If the logic is large or used in multiple places, move it into a helper function and call that from `@blur`.\n\n- Avoid heavy work\n  Since `blur` is synchronous, avoid expensive operations (such as large network calls or heavy computation) directly inside the handler expression.\n  Instead, delegate to asynchronous functions or queues if needed.\n\n- Combine with \"touched\" flags\n  Use dedicated flags (for example `touched.name`) to decouple user interactions from validation logic.\n  UI components can then react to those flags instead of inferring state from the presence of errors alone.\n\n- Be mindful of non-bubbling behavior\n  The native `blur` event does not bubble in the same way as `click`.\n  Place `@blur` on the actual element that should react to losing focus, not on a distant ancestor.\n\n- Use `@focus` and `@blur` together when needed\n  For complex interactions, combine `@focus` and `@blur` on the same element to track active state or to highlight inputs while they are being edited.\n\n\n#### Additional examples\n\nMark field as dirty only when value actually changed:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"original\": { \"name\": \"Taro\" },\n  \"current\":  { \"name\": \"Taro\" },\n  \"dirty\":    { \"name\": false }\n}'>\n  <input type=\"text\"\n         :value=\"current.name\"\n         @blur=\"dirty.name = (current.name !== original.name)\">\n</serc-rod>\n```\n\nLocal validation on blur:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"email\": \"\",\n  \"errors\": { \"email\": \"\" }\n}'>\n  <input type=\"email\"\n         :value=\"email\"\n         @blur=\"\n           errors.email =\n             (!email || email.indexOf('@') === -1)\n               ? 'Please enter a valid email.'\n               : '';\n         \">\n\n  <p *if=\"errors.email\" *print=\"errors.email\"></p>\n</serc-rod>\n```\n\nToggle helper text visibility:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"show_help\": true\n}'>\n  <input type=\"text\"\n         :value=\"''\"\n         @blur=\"show_help = false\">\n\n  <p *if=\"show_help\">\n    You can leave this field empty.\n  </p>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `@blur` is an event handler directive that wires the element's native `blur` event to a Sercrod expression.\n- The expression runs in the same scope as other directives on that element and is used for side effects, not for returning values.\n- `@blur` composes with other directives, including structural ones, without competing for ownership of the element's children.\n- If `cleanup.handlers` is enabled in the Sercrod configuration, the original `@blur` attribute is removed from the output DOM after the listener has been attached, keeping the rendered markup clean.\n",
  "event-change": "### @change\n\n#### Summary\n\n@change attaches a handler to the native change event on an element.\nThe expression on @change is evaluated when the browser fires a change event, typically after the user has committed a new value for a form control.\nTypical uses include synchronising derived state, running validation at commit time, and triggering side effects when a select, checkbox, or other control is changed.\n\n@change is part of the general event handler family (such as @click, @input, @blur) and follows the same evaluation model as other at sign directives.\n\n\n#### Basic example\n\nA select that updates a selected value when the user chooses an option:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"selected\": \"apple\",\n  \"options\": [\"apple\", \"banana\", \"orange\"]\n}'>\n  <label>\n    Favorite fruit:\n    <select :value=\"selected\"\n            @change=\"selected = $event.target.value\">\n      <option *for=\"opt of options\"\n              :value=\"opt\"\n              *textContent=\"opt\"></option>\n    </select>\n  </label>\n\n  <p>\n    You chose:\n    <span *textContent=\"selected\"></span>\n  </p>\n</serc-rod>\n```\n\nBehaviour:\n\n- When the user selects a different option, the browser fires change on the select element.\n- Sercrod evaluates the @change expression in the current scope, using the native event as $event.\n- The expression writes back into data, updating selected.\n- After the handler runs, Sercrod performs a lightweight child update of the host so that the rendered content stays in sync.\n\n\n#### Behavior\n\nCore rules for @change:\n\n- Target event\n  @change wires the native change event on the element where it appears.\n  On typical form controls, this event fires when the value is committed, not on every keystroke.\n\n- Expression evaluation\n  The attribute value is parsed as an expression, for example selected = $event.target.value or onChange($event, el).\n  When the event fires, Sercrod evaluates this expression against the current host data and local scope.\n\n- Access to the event and element\n  Inside the expression:\n  - $event and $e refer to the native Event object.\n  - el and $el refer to the current DOM element.\n\n- Data updates\n  When the expression assigns to properties that live on the host data object, Sercrod updates both the scoped view and the underlying host data.\n  The result value of the expression is ignored; only its side effects matter.\n\n- Modifiers\n  @change supports the standard event modifiers:\n  - .prevent calls event.preventDefault before evaluating the expression.\n  - .stop calls event.stopPropagation before evaluating the expression.\n  - .once registers a listener that automatically removes itself after the first event.\n  - .capture and .passive are passed through to addEventListener options.\n  These modifiers are parsed from the attribute name, for example @change.prevent.stop or @change.once.\n\n- Cleanup\n  If cleanup.handlers is enabled in Sercrod configuration, the original @change attribute is removed from the output DOM after it has been processed, leaving only the attached listener and normal attributes visible.\n\n\n#### Evaluation timing\n\n@change follows the normal rendering and event wiring phases:\n\n1. Structural directives such as *if, *for, *each, *switch, *include and *import decide whether an element exists and what its children look like.\n2. Once an element is kept, Sercrod walks its attributes.\n   Attributes whose names start with the configured events prefix (by default @) are treated as event handlers.\n3. For an attribute named @change or equivalent with modifiers, Sercrod calls the dedicated event renderer to register a change listener.\n4. On later renders, Sercrod reattaches handlers as needed, replacing previous listeners for the same event name on the same element.\n\nWhen the browser fires change on the element, the expression is executed immediately in the same turn as the native event.\n\n\n#### Execution model\n\nAt a high level, Sercrod processes @change as follows:\n\n1. During rendering, Sercrod finds an attribute whose name starts with the events prefix and whose event name part is change.\n2. It extracts:\n   - The event name change.\n   - The set of modifiers such as prevent, stop, once, capture or passive.\n   - The expression string from the attribute value.\n3. It builds an evaluation scope that:\n   - Exposes host data and local scope variables.\n   - Provides $event and $e for the native event and el and $el for the element.\n   - Propagates writes back into host data when assigning to known keys.\n4. Sercrod defines a handler function that:\n   - Applies .prevent and .stop modifiers to the native event if requested.\n   - Evaluates the expression through Sercrod's event evaluator with the prepared scope and context.\n   - Decides how to update the host after the handler runs.\n5. For each element, Sercrod stores a per event handler map so that re rendering removes the previous handler for that event before adding the new one.\n   For the change event there is at most one active handler per element; the last declaration wins.\n\nError handling:\n\n- If evaluation of the @change expression throws, Sercrod logs a warning through its error.warn channel but keeps the application running.\n- Errors in one handler do not prevent other elements from updating.\n\n\n#### Update behaviour after @change\n\nAfter a @change handler finishes, Sercrod decides how to refresh the view:\n\n- The runtime classifies input, change, beforeinput, keydown, keyup, composition events and clicks on form controls as input like events.\n- For these input like events, Sercrod always performs a lightweight child update on the host:\n  - It calls the internal update children method for that host, which propagates updates into child Sercrod instances without forcing a full re render of the host itself.\n  - This strategy is chosen to keep focus stable while reflecting changes.\n\nFor @change specifically:\n\n- change is treated as an input like event, so it always triggers the child update path.\n- Configuration of events.non_mutating does not prevent this lightweight update for @change, because change is not considered non mutating.\n- There is no automatic full host update triggered solely by @change; the child update is usually sufficient to keep form state and related content in sync.\n\n\n#### Use with form fields and data bindings\n\n@change is especially useful on form controls where the committed value matters more than each keystroke.\n\nWith n input or *input:\n\n- n input and *input handle two way binding of values for form controls by attaching their own internal listeners.\n- @change can be used alongside n input or *input on the same element to perform additional side effects when the bound value is committed.\n\nExample: normalise and mark dirty when a field changes:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"profile\": { \"age\": 30 },\n  \"dirty\":   { \"age\": false }\n}'>\n  <input type=\"number\"\n         n-input=\"profile.age\"\n         @change=\"\n           profile.age = Number($event.target.value || 0);\n           dirty.age = true;\n         \">\n</serc-rod>\n```\n\nWith checkbox and radio:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"newsletter\": false\n}'>\n  <label>\n    <input type=\"checkbox\"\n           :checked=\"newsletter\"\n           @change=\"newsletter = $event.target.checked\">\n    Receive newsletter\n  </label>\n</serc-rod>\n```\n\nWith select multiple:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"selected_tags\": []\n}'>\n  <select multiple\n          @change=\"\n            selected_tags = Array.from($event.target.selectedOptions)\n                                  .map(o => o.value);\n          \">\n    <option value=\"news\">News</option>\n    <option value=\"offers\">Offers</option>\n    <option value=\"tips\">Tips</option>\n  </select>\n</serc-rod>\n```\n\nIn each case, @change runs only when the control's value is committed by the browser, not on every key stroke, making it suitable for validations or side effects that should not run too frequently.\n\n\n#### Use with conditionals and loops\n\nAs with other event directives, @change composes with structural directives that control the element itself.\n\nInside conditionals:\n\n```html\n<input type=\"text\"\n       *if=\"mode === 'edit'\"\n       :value=\"item.title\"\n       @change=\"item.title_saved = false\">\n```\n\nInside loops:\n\n```html\n<ul>\n  <li *for=\"item of items\">\n    <input type=\"text\"\n           :value=\"item.label\"\n           @change=\"item.touched = true\">\n  </li>\n</ul>\n```\n\nEach instance inside the loop receives its own @change handler bound to that iteration's item.\nIf a structural directive removes or replaces the element on update, Sercrod re attaches the @change handler to the new element as part of the normal render process.\n\n\n#### Sercrod specific restrictions\n\n@change follows the general rules for event directives and has no extra, unique prohibitions:\n\n- You may attach @change to any element, but it is only triggered when the browser actually fires a change event for that element.\n  For practical purposes, it is mainly useful on form controls such as input, textarea, select, and content editable elements.\n- You may combine @change with other event directives on the same element as long as each uses a different event name (for example @input, @focus, @blur).\n- You may combine @change with data bindings such as :value, :checked, :class, :style and structural directives such as *if or *for.\n\nNo additional exclusion rules are imposed that are specific to @change.\n\n\n#### Best practices\n\n- Use @change for commit level logic\n  Prefer @change for operations that should run when the user has finished editing a field, such as validation, formatting, or triggering a save.\n  For real time feedback, @input is usually more appropriate.\n\n- Keep handlers focused\n  @change handlers should perform small, focused tasks such as updating a few flags or queuing work.\n  If logic becomes large, factor it into named functions and call those from the handler expression.\n\n- Use $event responsibly\n  Prefer to use the data model (for example values managed by n input) as the primary source of truth.\n  Use $event.target only when necessary, and keep the DOM access in one place if possible.\n\n- Avoid heavy synchronous work\n  Because the handler runs synchronously in the event loop, avoid expensive computation or long running operations inside the @change expression.\n  Delegate heavy work to asynchronous functions if needed.\n\n- Combine with staging when appropriate\n  When using *stage, keep @change handlers operating on the staged copy, and let *apply control when staged values are committed back to the main data.\n\n\n#### Additional examples\n\nTrigger validation logic on change:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"value\": \"\",\n  \"error\": \"\"\n}'>\n  <input type=\"text\"\n         :value=\"value\"\n         @change=\"\n           const v = $event.target.value.trim();\n           value = v;\n           error = v ? '' : 'This field is required.';\n         \">\n\n  <p *if=\"error\" *textContent=\"error\"></p>\n</serc-rod>\n```\n\nIntegrate with a manual save action:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"draft\": \"\",\n  \"saved\": \"\",\n  \"dirty\": false\n}'>\n  <textarea :value=\"draft\"\n            @change=\"dirty = true\"></textarea>\n\n  <button type=\"button\"\n          @click=\"\n            saved = draft;\n            dirty = false;\n          \">\n    Save\n  </button>\n\n  <p *if=\"dirty\">\n    You have unsaved changes.\n  </p>\n</serc-rod>\n```\n\n\n#### Notes\n\n- @change wires a native change event on the element and evaluates a Sercrod expression when that event fires.\n- The handler expression has access to $event and $e for the event object, and el and $el for the element, and can write back into host data.\n- Sercrod treats change as an input like event and always performs a lightweight child update of the host after the handler runs.\n- Cleanup options such as cleanup.handlers can be used to remove the @change attribute from the final DOM while keeping the behaviour intact.\n",
  "event-click": "### @click\n\n#### Summary\n\n`@click` attaches a handler to the native `click` event on an element.\nThe expression on `@click` is evaluated every time the element is clicked.\n\nTypical uses:\n\n- Toggle UI state (open or close menus, expand sections).\n- Increment counters or update data.\n- Invoke application methods, optionally using the native event as `$event`.\n\n`@click` belongs to the general event handler family (such as `@input`, `@change`, `@blur`, `@focus`) and follows the same evaluation rules as other `@event` directives.\n\n\n#### Basic example\n\nA simple counter:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"count\": 0\n}'>\n  <button @click=\"count++\">\n    Clicked {{%count%}} times\n  </button>\n</serc-rod>\n```\n\nBehavior:\n\n- The Sercrod host has initial data with `count: 0`.\n- Each click on the button evaluates `count++` in the current scope.\n- Sercrod then re-renders the host according to the event update rules, so the button label reflects the new `count` value.\n\n\n#### Accessing the event and element\n\nWhen a `click` event fires, Sercrod evaluates the `@click` expression with a special event scope:\n\n- `$event` and `$e`\n  Refer to the native `MouseEvent` (or `PointerEvent`) instance for this click.\n  Use this when you need event details such as `clientX`, `shiftKey`, or the original `target`.\n\n- `$el` and `el`\n  Refer to the DOM element on which `@click` is declared.\n\nExample:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"log\": []\n}'>\n  <button @click=\"log.push({ x: $event.clientX, y: $event.clientY })\">\n    Log click position\n  </button>\n</serc-rod>\n```\n\nAssignments to `$event`, `$e`, `$el`, or `el` are not used by Sercrod internally; treat them as read only in event expressions.\n\n\n#### Behavior\n\nCore rules:\n\n- Target event\n  `@click` wires the native `click` event to the expression.\n  Sercrod does not alter the native `click` semantics; it just adds expression evaluation.\n\n- Expression execution\n  The attribute value is treated as a JavaScript snippet (for example `count++`, `toggleOpen()`, or `doSomething($event, item)`).\n  When the click occurs, Sercrod evaluates the snippet in the current scope.\n\n- Data writes\n  Assignments to properties that exist in the host data are reflected back into the host's data store.\n  This drives subsequent re-renders.\n\n- Result value\n  The return value of the expression is ignored.\n  Side effects (writing to data, calling methods) are what matter.\n\n- Cleanup (optional)\n  If `cleanup.handlers` is enabled in the Sercrod configuration, the original `@click` attribute is removed from the DOM after the listener is attached.\n  The compiled HTML then shows only structural and standard attributes, not the `@click` source code.\n\n\n#### Evaluation timing and re-rendering\n\n`@click` participates in the normal render and update cycle for a Sercrod host:\n\n1. Structural directives such as `*if`, `*for`, `*each`, `*switch`, and `*include` decide whether the element is present.\n   If the element is removed by a structural directive, no handler is attached.\n\n2. During attribute processing, Sercrod recognises attributes starting with the configured event prefix (by default `\"@\"`).\n   Each event attribute (such as `@click`) is converted into a listener with its expression and modifiers.\n\n3. When the native `click` fires:\n   - Sercrod builds the merged event scope (data plus `$event`, `$el`, and global fallback).\n   - Sercrod evaluates the expression.\n   - If evaluation throws, Sercrod logs the error (when `error.warn` is enabled) but does not halt the framework.\n\n4. After the expression runs, Sercrod decides how to re-render:\n\n   - It reads the `events.non_mutating` list from configuration.\n   - It treats `click` as a mutating event by default (so it usually causes an update).\n   - For clicks on form controls (inputs, textareas, selects, or contentEditable elements) Sercrod prefers a lightweight child update to preserve focus.\n   - For other clicks, Sercrod triggers a full host update unless the modifiers override this (see `.update` and `.noupdate`).\n\nYou usually do not need to call any manual update function.\nChanging data in `@click` handlers is sufficient to trigger re-renders according to these rules.\n\n\n#### Use with buttons, links, and other controls\n\n`@click` is most natural on interactive elements:\n\n- Buttons:\n\n  ```html\n  <button type=\"button\" @click=\"open = !open\">\n    Toggle panel\n  </button>\n  ```\n\n- Links:\n\n  ```html\n  <a href=\"/profile\"\n     @click.prevent=\"goToProfile()\">\n    Profile\n  </a>\n  ```\n\n  In this pattern:\n\n  - `@click.prevent` calls `event.preventDefault()` before evaluating `goToProfile()`.\n  - The native navigation is blocked; your handler can perform SPA style navigation or other logic.\n\n- Custom clickable containers:\n\n  ```html\n  <div class=\"card\" @click=\"select(item)\">\n    <!-- card contents -->\n  </div>\n  ```\n\n  Any element can be made clickable by combining `@click` with styling and accessibility attributes (for example `role=\"button\"` and `tabindex=\"0\"`).\n\n\n#### Use with forms and submission\n\n`@click` often appears on form buttons:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"profile\": { \"name\": \"\" },\n  \"saving\": false\n}'>\n  <form method=\"post\" :action=\"saveUrl\">\n    <input type=\"text\" name=\"name\" :value=\"profile.name\">\n\n    <button type=\"submit\"\n            @click.prevent=\"saving = true; submitProfile(profile)\">\n      Save\n    </button>\n  </form>\n</serc-rod>\n```\n\nTypical patterns:\n\n- `@click.prevent` on a submit button to stop the native form submission and handle the post yourself (via `*post`, `fetch`, or any other mechanism).\n- Using `@click` on `type=\"button\"` buttons for actions that should not submit forms at all.\n\nFor clicks on form controls (inputs, textareas, selects) or on buttons that are treated as input controls, Sercrod performs a lightweight child update to keep focus stable while still reflecting data changes.\n\n\n#### Modifiers\n\n`@click` supports a set of event modifiers that are shared by all `@event` directives.\n\nThere are three kinds of modifiers:\n\n- Modifiers that map to DOM listener options (`capture`, `once`, `passive`).\n- Modifiers that call standard DOM methods on the event (`prevent`, `stop`).\n- Sercrod specific modifiers that control how Sercrod re-renders after the event (`update`, `noupdate`).\n\nConcretely:\n\n- `@click.prevent`\n  Sercrod specific.\n  Calls `event.preventDefault()` before evaluating the expression.\n  Use this to block default navigation or form submission.\n\n- `@click.stop`\n  Sercrod specific.\n  Calls `event.stopPropagation()` before evaluating the expression.\n  Use this to prevent the click from bubbling to ancestor elements with their own `@click` handlers.\n\n- `@click.once`\n  Framework syntax that maps directly to the standard DOM listener option `once: true`.\n  Sercrod passes this through to `addEventListener`, so the browser detaches the listener after the first successful call.\n  Subsequent clicks no longer trigger the handler.\n\n- `@click.capture`\n  Maps directly to the standard DOM listener option `capture: true`.\n  Attaches the listener in the capture phase instead of the bubbling phase, following normal DOM capture semantics.\n\n- `@click.passive`\n  Maps directly to the standard DOM listener option `passive: true`.\n  This is mainly useful for scroll or touch events; it is rarely needed for `click`, but Sercrod exposes it consistently for all events.\n\n- `@click.update` and `@click.noupdate`\n  Sercrod specific modifiers that control re-rendering only; they are not standard JS event options and are not passed to `addEventListener`.\n\n  - `@click.update` forces a host update even if the event is listed as non mutating in configuration.\n  - `@click.noupdate` suppresses host updates even though the handler runs.\n\n  These flags affect only Sercrod's internal \"should we re-render?\" decision.\n  They do not change the event's propagation, default behavior, or DOM listener options.\n\nExample:\n\n```html\n<button @click.noupdate=\"log.push('clicked')\">\n  Log without re-render\n</button>\n```\n\n\n#### Use with conditionals and loops\n\n`@click` composes naturally with structural directives:\n\n- Conditionals:\n\n  ```html\n  <button *if=\"canDelete\"\n          @click=\"confirmDelete(item)\">\n    Delete\n  </button>\n  ```\n\n  The handler exists only if `canDelete` is true at render time.\n\n- Loops:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <button @click=\"selected = item\">\n        Select {{%item.label%}}\n      </button>\n    </li>\n  </ul>\n  ```\n\n  Each iteration has its own `@click` that sees the iteration's `item` in scope.\n\nWhen structural directives replace or remove the element, Sercrod tears down and re-attaches `@click` listeners as needed, so you do not need to manage them manually.\n\n\n#### Best practices\n\n- Keep handlers focused\n  Use `@click` handlers for small, clear pieces of logic: toggling booleans, selecting items, or delegating to well named methods.\n\n- Prefer data updates over direct DOM mutations\n  Let Sercrod re-render based on data changes instead of manually manipulating the DOM in handlers.\n\n- Use modifiers explicitly\n  Use `.prevent` and `.stop` to make intent clear, especially on links and nested clickable areas.\n  Use `.once` when you want the browser to call the handler only once.\n  Use `.update` and `.noupdate` sparingly to override the default re-rendering policy when you know it is safe.\n\n- Compose with accessibility\n  For non button clickable elements, add appropriate ARIA attributes and keyboard handlers so that `@click` is part of an accessible interaction pattern.\n\n- Avoid heavy work in handlers\n  If a click triggers a heavy operation (such as a large network request), start it asynchronously and keep the handler itself lean.\n\n\n#### Additional examples\n\nToggle a menu:\n\n```html\n<serc-rod id=\"menu\" data='{\n  \"open\": false\n}'>\n  <button @click=\"open = !open\">\n    {{% open ? 'Close menu' : 'Open menu' %}}\n  </button>\n\n  <nav *if=\"open\">\n    <!-- menu items -->\n  </nav>\n</serc-rod>\n```\n\nCall a shared method with the event:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"log\": []\n}'>\n  <button @click=\"logClick($event, 'primary')\">\n    Primary\n  </button>\n  <button @click=\"logClick($event, 'secondary')\">\n    Secondary\n  </button>\n</serc-rod>\n```\n\nAttach `@click` to a container with nested controls:\n\n```html\n<div class=\"card\" @click=\"select(item)\">\n  <h2 *print=\"item.title\"></h2>\n  <p *print=\"item.summary\"></p>\n  <button type=\"button\" @click.stop=\"openDetails(item)\">\n    Details\n  </button>\n</div>\n```\n\nIn this example:\n\n- The card click selects `item`.\n- The Details button uses `.stop` so that clicking it does not also select the card.\n\n\n#### Notes\n\n- `@click` uses the event prefix defined by `config.events.prefix` (default `\"@\"`), so projects can replace it with another prefix if needed.\n- Event handlers receive `$event` / `$e` and `$el` / `el` in their scope in addition to the host data.\n- Among modifiers, `capture`, `once`, and `passive` map directly to standard DOM listener options.\n  `prevent`, `stop`, `update`, and `noupdate` are Sercrod level behaviours implemented on top of the DOM event.\n- `click` is treated as a mutating event by default and triggers re-rendering after the handler runs, with special handling for form controls to keep focus stable.\n- All `@event` directives, including `@click`, share the same modifier semantics; only `update` and `noupdate` are Sercrod specific controls for re-rendering and do not correspond to native JS event options.\n- If `cleanup.handlers` is enabled, the original `@click` attribute is removed from the rendered DOM, while the event listener remains active.\n",
  "event-focus": "### @focus\n\n#### Summary\n\n@focus attaches a handler to the native focus event on an element.\nThe expression on @focus is evaluated when that element receives focus.\nTypical uses include highlighting a field while it is active, resetting error messages when the user returns to a control, or tracking which part of a form is currently focused.\n\n@focus is part of the general event handler family (for example @click, @input, @blur) and follows the same evaluation rules as other event directives that use the Sercrod event prefix.\n\n\n#### Basic example\n\nTracking whether an input is currently focused:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"profile\": { \"name\": \"\" },\n  \"focus\":   { \"name\": false }\n}'>\n  <label>\n    Name:\n    <input type=\"text\"\n           :value=\"profile.name\"\n           @focus=\"focus.name = true\"\n           @blur=\"focus.name = false\">\n  </label>\n\n  <p *if=\"focus.name\">\n    This field is currently focused.\n  </p>\n</serc-rod>\n```\n\nBehavior:\n\n- The input is bound to profile.name via :value.\n- When the user clicks into or tabs into the input, the browser fires a focus event.\n- Sercrod evaluates the @focus expression in the current scope, setting focus.name to true.\n- When the user leaves the input, @blur sets focus.name back to false.\n- The paragraph is shown only while the field is focused.\n\n\n#### Behavior\n\nCore rules:\n\n- Target event\n  @focus wires the native focus event on the element where it is declared.\n  The element must be able to receive focus (for example input, textarea, select, button, link, or any element with a suitable tabindex).\n\n- Scope and data\n  When focus fires, Sercrod evaluates the @focus expression in the same Sercrod scope used for other directives on that element.\n  Reads and writes in that expression see the current Sercrod data, including local variables from surrounding directives and the host data object.\n\n- Access to event and element\n  Inside @focus expressions, Sercrod injects:\n\n  - $event and $e for the native event object.\n  - el and $el for the element that received focus.\n\n  Data remains the primary source; event and element are additional helpers inside the evaluation context.\n\n- One way effect\n  The result value of the expression is ignored by Sercrod.\n  What matters is the side effect (for example updating flags such as focus.name, calling helper functions, and so on).\n\n- Coexistence with other bindings\n  @focus can be combined with colon bindings (such as :value, :class, :style) and with structural directives targeting the same element (such as *if or *for), as long as the markup remains valid.\n  It can also be combined with other event handlers such as @blur, @input, or @change on the same element, because they use different event names.\n\n\n#### Evaluation timing\n\n@focus participates in Sercrod's normal event lifecycle:\n\n- Structural phase\n  Sercrod first processes structural directives such as *if, *for, *each, *switch, and *include.\n  If those directives remove the element from the output, no @focus handler is attached.\n\n- Attribute and event phase\n  For elements that are kept, Sercrod walks through the attributes:\n\n  - Attributes whose name starts with the event prefix (by default \"@\") are treated as event handlers.\n  - For @focus, Sercrod extracts the event name focus and any modifiers from the attribute name.\n  - The attribute value is stored as the expression string to run later.\n  - Sercrod then registers a native focus listener on the element with options derived from the modifiers.\n\n- Re-renders\n  When the host re-renders, Sercrod ensures that the listener continues to point to the current expression and scope, replacing any old listener as needed so it does not accumulate duplicates.\n\nThe actual focus event still fires according to browser rules; Sercrod only attaches its handler to that event and evaluates your expression when it occurs.\n\n\n#### Execution model\n\nConceptually, the runtime behaves as follows for @focus:\n\n1. During rendering, Sercrod encounters an attribute whose name starts with the configured events.prefix and whose event name portion is focus.\n2. It splits the attribute name into:\n\n   - The event name focus.\n   - A set of modifiers (for example prevent, stop, once, capture, passive, update, noupdate).\n\n3. Sercrod reads the attribute value as an expression string.\n4. Sercrod constructs listener options:\n\n   - capture is true if the name includes the capture modifier.\n   - passive is true if the name includes the passive modifier.\n   - once is true if the name includes the once modifier.\n\n5. Sercrod creates a handler function that:\n\n   - Applies generic modifiers:\n\n     - If the name includes prevent, it calls event.preventDefault() before anything else.\n     - If the name includes stop, it calls event.stopPropagation().\n\n   - Constructs a proxy scope in which:\n\n     - $event and $e refer to the native focus event.\n     - el and $el refer to the focused element.\n     - Other reads first consult the Sercrod scope; if a name is not found there, the proxy falls back to the global window.\n\n   - Evaluates the expression with this proxy scope using the event evaluator.\n   - Catches and logs any errors so that one failing handler does not break other updates.\n\n6. After the expression runs, Sercrod decides how to update the host:\n\n   - It consults the configured set events.non_mutating to see whether the event should cause a re-render by default.\n   - The update modifier forces a re-render even if the event is configured as non mutating.\n   - The noupdate modifier suppresses updates even if the event is normally treated as mutating.\n   - For focus, this means you can opt into or out of re-rendering per handler by using these modifiers.\n\n7. Sercrod either triggers a lightweight children update or a host level update according to its update strategy and then returns control to the browser.\n\nFinally, Sercrod records the handler in an internal map so that subsequent re-renders can remove or replace it cleanly.\n\n\n#### Use with form fields and UI state\n\n@focus is most commonly used with form controls and interactive UI components:\n\n- Highlighting the active field:\n\n  @@html\n  <serc-rod id=\"app\" data='{\n    \"active\": { \"field\": null }\n  }'>\n    <input type=\"text\"\n           name=\"email\"\n           @focus=\"active.field = 'email'\">\n\n    <textarea name=\"message\"\n              @focus=\"active.field = 'message'\"></textarea>\n\n    <p *if=\"active.field === 'email'\">\n      You are editing the email.\n    </p>\n\n    <p *if=\"active.field === 'message'\">\n      You are editing the message.\n    </p>\n  </serc-rod>\n  @@\n\n- Clearing errors when returning to a field:\n\n  @@html\n  <input type=\"text\"\n         :value=\"profile.email\"\n         @focus=\"errors.email = ''\">\n  @@\n\n  When the user focuses the input again, outdated error messages are cleared, which can be more user friendly than showing an old error while they are trying to fix it.\n\n- Pairing with :class for visual focus:\n\n  @@html\n  <input type=\"text\"\n         :class=\"focus.name ? 'is-focused' : ''\"\n         @focus=\"focus.name = true\"\n         @blur=\"focus.name = false\">\n  @@\n\n  The CSS class is-focused is present only while the input has focus.\n\n\n#### Use with conditionals and loops\n\nBecause @focus is an event directive on the element itself, it composes naturally with structural directives:\n\n- Conditional inputs:\n\n  @@html\n  <input type=\"text\"\n         *if=\"mode === 'advanced'\"\n         :value=\"settings.detail\"\n         @focus=\"focus.detail = true\"\n         @blur=\"focus.detail = false\">\n  @@\n\n  The focus handlers exist only in advanced mode, and every time the element is created a fresh listener is attached.\n\n- Inputs inside loops:\n\n  @@html\n  <ul>\n    <li *for=\"item of items\">\n      <input type=\"text\"\n             :value=\"item.label\"\n             @focus=\"item.is_focused = true\"\n             @blur=\"item.is_focused = false\">\n    </li>\n  </ul>\n  @@\n\n  Each iterated input receives its own @focus and @blur handlers.\n  The expression is evaluated in the per item scope so it can safely write to item.is_focused.\n\n\n#### Sercrod-specific notes and restrictions\n\n- No bubbling shortcut\n  The native focus event does not bubble in the same way as click events.\n  Sercrod does not change this.\n  If you attach @focus to a parent container, it will not see focus events from descendants unless the browser fires those events on the container as well.\n  To react when a specific element gains focus, put @focus directly on that element.\n\n- Event prefix\n  The actual attribute name for focus uses whatever prefix is configured in events.prefix.\n  By default this is \"@\", so the attribute is @focus.\n  If you change the prefix to something like \"ne-\", the same handler would be written as ne-focus.\n\n- Cleanup of handler attributes\n  If cleanup.handlers is enabled in Sercrod configuration, Sercrod removes the original @focus attribute from the output DOM after the listener has been attached.\n  The listener remains active; the cleanup only affects the visible markup.\n\n\n#### Best practices\n\n- Track simple flags\n  Use @focus to maintain simple flags, such as focus.name or active.field, rather than recalculating focus state indirectly.\n  This keeps templates easy to read and styles easy to apply.\n\n- Combine with @blur\n  For most flows, treat @focus and @blur as a pair:\n  set a flag true on focus and false on blur.\n  This pattern fits well with conditions like *if=\"focus.name\" and with :class or :style bindings.\n\n- Keep handlers small\n  Focus events can occur often when users tab through a form or when scripts programmatically move focus.\n  Keep @focus expressions short and inexpensive, and delegate complex logic to helper functions when necessary.\n\n- Use update and noupdate modifiers deliberately\n  Whether @focus causes a re-render by default depends on the events.non_mutating configuration.\n  If you need UI to change as soon as focus moves (for example to show helper text or highlight a row), use @focus.update to force an update.\n  If you use @focus only for internal bookkeeping that does not affect the page, you can use @focus.noupdate to keep rendering cost low.\n\n- Consider keyboard users\n  Remember that many users navigate by keyboard.\n  @focus is a natural place to add or remove keyboard friendly affordances such as outline highlights or helper text.\n\n\n#### Additional examples\n\nHighlighting the currently focused row in a table:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"rows\": [\n    { \"id\": 1, \"name\": \"Alpha\" },\n    { \"id\": 2, \"name\": \"Beta\" }\n  ],\n  \"focused_id\": null\n}'>\n  <table>\n    <tbody>\n      <tr *for=\"row of rows\"\n          :class=\"row.id === focused_id ? 'is-focused-row' : ''\">\n        <td>\n          <input type=\"text\"\n                 :value=\"row.name\"\n                 @focus=\"focused_id = row.id\"\n                 @blur=\"focused_id = null\">\n        </td>\n      </tr>\n    </tbody>\n  </table>\n</serc-rod>\n```\n\nShowing contextual helper text only while a field has focus:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"focus\": { \"password\": false }\n}'>\n  <label>\n    Password:\n    <input type=\"password\"\n           @focus=\"focus.password = true\"\n           @blur=\"focus.password = false\">\n  </label>\n\n  <p *if=\"focus.password\">\n    Use at least 12 characters, mixing letters, numbers, and symbols.\n  </p>\n</serc-rod>\n```\n\n\n#### Notes\n\n- @focus is an event directive that wires the native focus event to a Sercrod expression.\n- The expression runs in the usual Sercrod scope, with access to $event, $e, el, and $el in addition to normal data.\n- @focus composes cleanly with colon bindings, structural directives, and other event handlers on the same element.\n- Update behavior is governed by the global events.non_mutating configuration and can be overridden per handler using the update and noupdate modifiers.\n- If cleanup.handlers is enabled, the original @focus attribute is removed from the rendered DOM after the listener is attached, leaving the final HTML clean while keeping the handler active.\n",
  "event-input": "### @input\n\n#### Summary\n\n@input attaches a handler to the native input event on an element.\nThe expression on @input is evaluated every time the browser fires an input event for that control.\nTypical uses include live validation, updating derived state as the user types, or reacting to slider or range changes.\n\n@input is part of the event handler family (such as @click, @change, @blur) and follows the same general evaluation rules as other @ directives, with input-specific update behavior inside the Sercrod runtime.\n\n\n#### Basic example\n\nTracking a live character count while the user types:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"text\": \"\",\n  \"length\": 0\n}'>\n  <textarea :value=\"text\"\n            @input=\"length = ($event.target.value || '').length\">\n  </textarea>\n\n  <p>\n    Length: {{%length%}} characters\n  </p>\n</serc-rod>\n```\n\nBehavior:\n\n- The textarea is initially empty, with `text` and `length` both zero-like.\n- Every time the user edits the textarea, the browser fires an input event.\n- Sercrod evaluates the @input expression with `$event` set to the native event.\n- `length` is updated to match the length of the current textarea value.\n- The paragraph is re-rendered with the latest length.\n\n\n#### Behavior\n\nCore rules:\n\n- Target event\n  @input wires the native input event on the element where it appears.\n  Which user actions cause input to fire is determined by the browser and the element type (for example, typing in text inputs, dragging sliders, or changing some controls).\n\n- Expression evaluation\n  Sercrod parses the attribute value as a JavaScript expression or statement block.\n  When input fires, Sercrod evaluates that expression in the element's current Sercrod scope.\n\n- Access to event and element\n  Inside the expression you can access:\n\n  - `$event` or `$e` for the native event object.\n  - `el` or `$el` for the element that received the event.\n\n- One way effect\n  The return value of the expression is ignored.\n  @input is for side effects such as writing to data, calling helper functions, or both.\n\n- Coexistence with bindings\n  @input does not replace Sercrod's form bindings (for example *input).\n  You can use @input alongside *input, :value, or :checked on the same element. Sercrod will attach both the data-binding listener and the @input listener, and both handlers will run when the browser fires input.\n\nSercrod does not change the native timing or semantics of the input event; it only evaluates the expression when the event occurs and then applies its usual re-render rules for input-like events.\n\n\n#### Evaluation timing\n\nFor @input, evaluation follows the general event pipeline:\n\n- Structural phase\n  Structural directives such as *if, *for, *each, *switch, and *include first decide whether the element exists and what its children are.\n  If an element is removed by a structural directive, no @input handler is attached.\n\n- Attribute phase\n  Once Sercrod has decided to keep the element, it processes its attributes.\n  Event attributes whose name begins with the configured event prefix (by default \"@\") are recognized as event handlers.\n  For @input, this means an input listener is registered in the same pass where colon bindings like :value or :class are processed.\n\n- Re-render phase\n  After the handler expression has run, Sercrod classifies the event as input-like.\n  For input-like events (including input itself), Sercrod performs a lightweight update of the host's children rather than a full host re-render.\n  This is done to keep focus stable while still updating dependent content (for example, live counters or validation messages).\n\nOn subsequent renders (due to data changes or other events), Sercrod ensures that @input still points to the current expression and scope.\n\n\n#### Execution model\n\nConceptually, the runtime behaves as follows for @input:\n\n1. During rendering, Sercrod finds an attribute whose name matches the event prefix plus input (by default @input).\n2. It extracts:\n\n   - The event name `input`.\n   - Any modifiers (for example, `@input.prevent.update`).\n   - The raw expression string from the attribute value.\n\n3. Sercrod registers a native listener on the element using `addEventListener(\"input\", handler, options)` with:\n\n   - `capture` set if `.capture` modifier is present.\n   - `passive` set if `.passive` modifier is present.\n   - `once` set if `.once` modifier is present.\n\n4. When the browser fires input:\n\n   - The handler applies modifiers:\n\n     - `.prevent` calls `event.preventDefault()`.\n     - `.stop` calls `event.stopPropagation()`.\n\n   - Sercrod creates a proxy scope in which:\n\n     - `$event` and `$e` refer to the native event.\n     - `el` and `$el` refer to the target element.\n     - Reads fall back from the local scope to `window` if a name is not found.\n     - Writes update the local scope and, when relevant, the host data.\n\n   - Sercrod calls `eval_event` with the expression and this proxy scope.\n   - If evaluation throws, Sercrod catches the error and routes it through the configured logging mechanisms.\n\n5. After the expression runs, Sercrod decides how to refresh:\n\n   - For @input, the event is classified as \"input-like\", so Sercrod performs a lightweight children-only update of the host.\n   - Non-input events may instead perform a full host update, depending on configuration and modifiers.\n\n6. If `cleanup.handlers` is enabled in the configuration, Sercrod removes the original @input attribute from the DOM after the listener is attached, leaving only the wired listener and any non-directive attributes.\n\nThe @input handler does not attempt to modify the value of the control by itself; it just runs your expression and then triggers the appropriate re-render path.\n\n\n#### Use with form fields and data bindings\n\n@input is often paired with form bindings and colon attributes:\n\n- With `*input` (two-way binding):\n\n  ```html\n  <serc-rod id=\"app\" data='{\n    \"form\": { \"name\": \"\" },\n    \"touched\": { \"name\": false }\n  }'>\n    <input type=\"text\"\n           *input=\"form.name\"\n           @input=\"touched.name = true\">\n\n    <p *if=\"touched.name && !form.name\">\n      Please enter your name.\n    </p>\n  </serc-rod>\n  ```\n\n  - *input keeps `form.name` synchronized with the element's value.\n  - @input marks the field as touched whenever the user edits it, regardless of validation result.\n\n- With `*eager` and `*lazy`:\n\n  ```html\n  <input type=\"text\"\n         *input=\"form.query\"\n         *eager\n         @input=\"results = search(form.query)\">\n  ```\n\n  Here:\n\n  - *input with *eager updates `form.query` on every input event.\n  - @input immediately runs a search using the latest query string.\n  - Because @input is treated as input-like, Sercrod applies a children-only update after the handler, keeping focus stable.\n\n- With simple colon bindings:\n\n  ```html\n  <input type=\"range\" min=\"0\" max=\"100\"\n         :value=\"level\"\n         @input=\"level = Number($event.target.value)\">\n  ```\n\n  In this pattern, @input itself manages the data update, while :value reflects the current value back into the control on re-render.\n\n\n#### Use with conditionals and loops\n\n@input works well inside conditionals and loops:\n\n- Conditional inputs:\n\n  ```html\n  <input type=\"text\"\n         *if=\"mode === 'advanced'\"\n         *input=\"filters.keyword\"\n         @input=\"filters.dirty = true\">\n  ```\n\n  The handler is attached only when the element exists (for example, in advanced mode).\n\n- Inputs in loops:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <input type=\"text\"\n             *input=\"item.label\"\n             @input=\"item.dirty = true\">\n    </li>\n  </ul>\n  ```\n\n  Each iterated input gets its own @input handler, and the expression is evaluated with that iteration's `item` in scope.\n\nIf a structural directive replaces an input element on re-render, Sercrod re-attaches the @input handler on the new node as part of normal rendering.\n\n\n#### Sercrod-specific restrictions\n\nFor @input there are no special \"cannot combine on the same element\" rules beyond the shared event rules:\n\n- You can combine @input with:\n\n  - Other event handlers on the same element, as long as they use different event names (for example `@focus`, `@blur`, `@change`).\n  - Colon bindings like :value, :class, :style.\n  - Structural directives that own the element itself (for example `*if`, `*for`, `*each`) as long as they are compatible with the markup.\n\n- @input does not conflict with *input or n-input.\n  Sercrod attaches both the form binding listeners and the @input listener when they are present together.\n\nInternally, Sercrod treats input as an \"input-like\" event and always performs a lightweight children-only update after your handler runs.\nIn the current implementation, the generic `.update` and `.noupdate` modifiers do not suppress this behavior for @input.\n\n\n#### Best practices\n\n- Keep handlers light\n  @input can fire very frequently (on every keystroke or small change).\n  Keep the expression small and inexpensive: avoid heavy loops, long-running computations, or large network calls directly inside @input.\n\n- Delegate heavy work\n  For expensive operations (such as full-text search or remote validation), delegate to a debounced or throttled helper function rather than doing everything inline in the @input expression.\n\n- Use @input for UI reactions, *input for data sync\n  Prefer *input (and n-input) to keep data synchronized with control values.\n  Use @input for supplementary side effects such as tracking \"dirty\" or \"touched\" flags, updating previews, or showing helper messages.\n\n- Be mindful of IME composition\n  Sercrod's @input handler receives input events as the browser fires them.\n  On some platforms and input methods, this can happen many times while the user is composing text.\n  Design @input logic so it remains robust and fast under frequent updates.\n\n- Combine with @change when appropriate\n  If you only care about the final value (for example, after a select or radio change), consider using @change instead of @input, or combine both for separate behaviors.\n\n\n#### Additional examples\n\nLive preview of formatted text:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"source\": \"\",\n  \"preview\": \"\"\n}'>\n  <textarea *input=\"source\"\n            *eager\n            @input=\"preview = source.toUpperCase()\">\n  </textarea>\n\n  <h3>Preview</h3>\n  <pre *print=\"preview\"></pre>\n</serc-rod>\n```\n\nNumeric input with live clamping:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"volume\": 50\n}'>\n  <input type=\"number\" min=\"0\" max=\"100\"\n         *input=\"volume\"\n         *eager\n         @input=\"\n           if (volume < 0)   volume = 0;\n           if (volume > 100) volume = 100;\n         \">\n\n  <p>Volume: {{%volume%}}</p>\n</serc-rod>\n```\n\nLogging raw input events for debugging:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"log\": []\n}'>\n  <input type=\"text\"\n         @input=\"log.push($event.target.value)\">\n\n  <ul>\n    <li *for=\"v of log\">\n      {{%v%}}\n    </li>\n  </ul>\n</serc-rod>\n```\n\n\n#### Notes\n\n- @input is an event handler directive that wires the native input event to a Sercrod expression.\n- The expression runs in the same scope as other directives on the element, with `$event` / `$e` for the native event and `el` / `$el` for the element.\n- @input composes cleanly with *input, :value, *stage, and other form-related directives.\n- After @input runs, Sercrod treats the event as input-like and performs a lightweight children-only update to keep the UI in sync without breaking focus.\n- If `cleanup.handlers` is enabled, the original @input attribute is removed from the output DOM once the handler has been installed.\n",
  "event-keydown": "### @keydown\n\n#### Summary\n\n`@keydown` attaches a handler to the native `keydown` event on an element.\nThe expression on `@keydown` is evaluated every time a key is pressed while the element has focus (and may fire repeatedly if the key is held, depending on the browser and operating system).\n\nTypical uses:\n\n- Handling keyboard shortcuts.\n- Reacting to Enter, Escape, or arrow keys.\n- Implementing keyboard driven navigation or editing commands.\n\n`@keydown` is part of Sercrod's event handler family (such as `@click`, `@input`, `@change`, `@focus`, `@blur`), but it also supports keyboard-specific key-name modifiers, modifier-key combinations, and optional `.window` observation.\n\n\n#### Basic example\n\nA simple handler that reacts to Enter and Escape:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"log\": [],\n  \"value\": \"\"\n}'>\n  <input type=\"text\"\n         :value=\"value\"\n         @keydown=\"\n           if($event.key === 'Enter'){\n             log.push('submit:' + value);\n           } else if($event.key === 'Escape'){\n             value = '';\n             log.push('clear');\n           }\n         \">\n\n  <ul>\n    <li *for=\"msg of log\" *textContent=\"msg\"></li>\n  </ul>\n</serc-rod>\n```\n\nBehavior:\n\n- While the input has focus, every physical key press triggers a `keydown` event.\n- Sercrod evaluates the `@keydown` expression in the current scope, with `$event` bound to the native `KeyboardEvent`.\n- Pressing Enter logs a submit entry.\n- Pressing Escape clears the value and logs a clear entry.\n\n\n#### Behavior\n\nCore rules:\n\n- Target event\n  `@keydown` listens for the native `\"keydown\"` event on the element where it appears.\n  Without `.window`, the element must be focusable (for example inputs, textareas, buttons, links, or elements with `tabindex`) or must receive a bubbled key event from a focused descendant. With `.window`, the declaration element is not the event target; the key event is observed on `window`.\n\n- Expression evaluation\n  The attribute value (for example `onKey($event)` or the inline logic above) is parsed and stored as a Sercrod expression.\n  When the event fires, the expression is evaluated in the context of the current host and element.\n\n- Event object and element access\n  Inside the expression:\n\n  - `$event` and `$e` refer to the native `KeyboardEvent` instance.\n  - `el` and `$el` refer to the element that owns `@keydown`.\n\n  For example:\n\n  - `$event.key` and `$event.code` give the key that was pressed.\n  - `$event.ctrlKey`, `$event.shiftKey`, `$event.altKey`, `$event.metaKey` expose modifier keys.\n\n- One way effect\n  Sercrod ignores the return value of the expression.\n  Only side effects such as updating data, calling methods, or triggering other APIs matter.\n\n- Repeated firing\n  Because `keydown` may fire repeatedly while a key is held, your handler may run multiple times in quick succession.\n  If you need to ignore repeats, check `$event.repeat` in the expression.\n\n\n#### Event modifiers\n\n`@keydown` supports the same modifier suffixes as other `@event` directives.\nModifiers are appended to the attribute name, separated by dots:\n\n- `prevent`\n  Calls `event.preventDefault()` before evaluating the expression.\n\n- `stop`\n  Calls `event.stopPropagation()` before evaluating the expression.\n\n- `once`\n  Uses the browser's `once` option so the handler runs at most once per element and event type, then removes itself.\n\n- `capture`\n  Registers the listener in the capture phase.\n\n- `passive`\n  Registers the listener as passive, signaling that the handler will not call `preventDefault()`.\n\n- `update`\n  Forces a Sercrod update after the handler runs, even for events that would normally be treated as non mutating.\n\n- `noupdate`\n  Suppresses Sercrod's automatic update after the handler runs.\n\nExamples:\n\n```html\n<input @keydown.prevent=\"handleKey($event)\">\n<input @keydown.stop=\"handleKey($event)\">\n<input @keydown.once=\"registerShortcut($event)\">\n<input @keydown.noupdate=\"handleKeyWithoutRerender($event)\">\n```\n\nRules specific to `@keydown`:\n\n- `keydown` is not in Sercrod's default `non_mutating` event list.\n  By default, Sercrod treats `@keydown` as a mutating event, so it will re render after the handler unless you use `.noupdate` or change configuration.\n- Using `.noupdate` is often appropriate for pure keyboard navigation where you manually apply visual changes or where a full re render would be too heavy.\n\n\n\n#### Keyboard-specific syntax\n\n`@keydown` supports additional keyboard-specific attribute syntax.\n\nExamples:\n\n```html\n@keydown.arrowup=\"y=y-1\"\n@keydown.arrowup+shift=\"y=y-10\"\n@keydown.shift+arrowup=\"y=y-10\"\n@keydown.s+ctrl=\"saved=true\"\n@keydown.window.escape=\"open=false\"\n@keydown.window.arrowup.prevent=\"y=y-1\"\n```\n\nKey-name tokens such as `arrowup`, `enter`, and `escape` are matched against `KeyboardEvent.key` after case-insensitive normalization.\n\nThe recommended documentation style is lowercase, but official browser key names such as `ArrowUp`, `Enter`, `Escape`, and `Shift` are also accepted.\n\nModifier-key combinations using `+` are order-independent.\n\nFor example, these two forms are equivalent:\n\n```html\n@keydown.arrowup+shift=\"y=y-10\"\n@keydown.shift+arrowup=\"y=y-10\"\n```\n\nSupported modifier keys include `shift`, `ctrl`, `alt`, and `meta`.\n\nArbitrary non-modifier combinations such as `@keydown.a+b` are not supported in the initial keyboard specification and should produce `[Sercrod warn]`.\n\nThe reason is that true arbitrary multi-key detection requires pressed-key state management.\n\nModifier-key combinations such as `arrowup+shift`, `s+ctrl`, and `enter+ctrl+shift` are supported.\n\n#### Window key declarations\n\nWhen `.window` is present, `@keydown` observes the event on `window` instead of treating the declaration element as the keyboard target.\n\n```html\n<serc-rod data=\"{ y: 0 }\">\n  <input type=\"hidden\" @keydown.window.arrowup=\"y=y-1\">\n  <p>Y: %y%</p>\n</serc-rod>\n```\n\nIn this example, the hidden input does not receive keyboard focus.\n\nIt is only a declaration location.\n\nThe action runs in the nearest `<serc-rod>` context, and only that host is updated.\n\nThis is also valid:\n\n```html\n<serc-rod data=\"{ x: 0, y: 0 }\">\n  <div\n    @keydown.window.arrowleft=\"x=x+1\"\n    @keydown.window.arrowright=\"x=x-1\"\n    @keydown.window.arrowup=\"y=y-1\"\n    @keydown.window.arrowdown=\"y=y+1\">\n  </div>\n\n  <p>X: %x%</p>\n  <p>Y: %y%</p>\n</serc-rod>\n```\n\nThe grouped form and the split hidden-input form are both valid.\n\nUse whichever is more readable for the template.\n\nWindow-level prevent behavior:\n\nWhen `.window.prevent` is used, the matching key's default browser behavior is prevented at the window level.\n\nThis can also suppress page scrolling or the normal key behavior of focused form controls.\n\nUse it only when the keyboard action is intended to take over that key.\n\nWindow key registrations are tied to the nearest `<serc-rod>` host.\n\nWhen that host is removed from the DOM, Sercrod should clean up its window key registrations through the Custom Element lifecycle.\n\n\n#### Evaluation timing\n\n`@keydown` fits into Sercrod's event and render pipeline as follows:\n\n1. Structural directives (`*if`, `*for`, `*each`, `*switch`, `*include`, etc.) first decide whether the element exists and in what form.\n2. Sercrod processes attributes on the kept element:\n\n   - Colon bindings like `:value`, `:class`, `:style`, etc.\n   - Event bindings such as `@keydown`, `@input`, `@click`.\n\n3. For `@keydown`, Sercrod extracts:\n\n   - The event name `\"keydown\"` from the attribute name (after removing the configured prefix).\n   - Any modifiers from the rest of the attribute name.\n   - The expression string from the attribute value.\n\n4. Sercrod registers a real DOM event listener `keydown` on the element, using `capture`, `passive`, and `once` as requested.\n5. When the browser fires `keydown`:\n\n   - Sercrod builds an evaluation scope that proxies the current data scope and injects `$event` / `$e` and `el` / `$el`.\n   - Sercrod runs the expression through its event evaluator.\n   - Sercrod then decides whether and how to re render based on the event name, modifiers, and configuration.\n\n`@keydown` executes synchronously as part of the native event dispatch, so any side effects (such as updating data) happen immediately before any subsequent re renders.\n\n\n#### Execution model and updates\n\nInternally, after evaluating the handler expression, Sercrod chooses the update strategy:\n\n- It reads the configured set of non mutating event names: `this.constructor._config.events.non_mutating`.\n- It decides whether this event wants an update by default:\n\n  - `wantsUpdate` is `true` if the event name is not in `non_mutating`.\n  - For `keydown`, this is `true` by default, because `keydown` is not in the built in `non_mutating` list.\n\n- It checks modifiers:\n\n  - `.update` forces `wantsUpdate = true`.\n  - `.noupdate` forces `wantsUpdate = false`.\n  - `.once` combined with a non mutating event disables updates for that handler.\n\n- It detects input like interactions:\n\n  - Sercrod distinguishes between input like events (such as `input`, `change`, composition events, and form control clicks) and other events.\n  - For input like events, Sercrod prefers a lightweight child update rather than a full re render to preserve focus.\n\n- For `@keydown`:\n\n  - `keydown` is not treated as a special input like event by default.\n  - If `wantsUpdate` is `true` and no modifiers override it, Sercrod updates the entire host via `update()` after the handler.\n  - If you combine `@keydown` with `.noupdate`, Sercrod will skip that update entirely.\n\nThis means `@keydown` is powerful but potentially expensive if many key events fire in quick succession and each triggers a full update.\nUse `.noupdate` or `events.non_mutating` if you need tight keyboard handling without frequent re renders.\n\n\n#### Use with focusable controls and forms\n\n`@keydown` is often paired with inputs and textareas:\n\n- Updating data on every key press:\n\n  ```html\n  <input type=\"text\"\n         :value=\"draft\"\n         @keydown=\"draft = $event.target.value\">\n  ```\n\n  This pattern is usually better handled by `n-input` or `*input`, but shows how `@keydown` can directly read `event.target.value`.\n\n- Handling shortcuts while editing:\n\n  ```html\n  <textarea\n    :value=\"note\"\n    @keydown=\"\n      if($event.key === 'Tab'){\n        $event.preventDefault();\n        note += '    ';\n      }\n    \">\n  </textarea>\n  ```\n\nWith forms:\n\n- Use `@keydown` to intercept Enter in specific fields:\n\n  ```html\n  <form @submit.prevent=\"submitForm()\">\n    <input type=\"text\"\n           :value=\"query\"\n           @keydown=\"\n             if($event.key === 'Enter'){\n               submitForm();\n             }\n           \">\n  </form>\n  ```\n\nIf you want to globally prevent Enter from submitting a form, consider using `*prevent-default` with the `\"enter\"` mode instead of repeating `@keydown.prevent` handlers on every control.\n\n\n#### Use with conditionals and loops\n\n`@keydown` composes cleanly with structural directives:\n\n- Conditional fields:\n\n  ```html\n  <input type=\"text\"\n         *if=\"mode === 'search'\"\n         :value=\"query\"\n         @keydown=\"\n           if($event.key === 'Enter'){\n             runSearch(query);\n           }\n         \">\n  ```\n\n  The handler exists only when the condition is true.\n\n- Inside loops:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <input type=\"text\"\n             :value=\"item.label\"\n             @keydown=\"\n               if($event.key === 'Enter'){\n                 item.editing = false;\n               }\n             \">\n    </li>\n  </ul>\n  ```\n\nEach iteration gets its own `@keydown` handler, bound to that iteration's `item` in scope.\n\n\n#### Sercrod-specific restrictions\n\nFor local `@keydown` itself, Sercrod does not impose special structural restrictions beyond the general event rules:\n\n- You may put local `@keydown` on any element that can receive focus or receive bubbled key events.\n- You may put `@keydown.window.*` on ordinary elements, including hidden inputs, because the element is only a declaration location.\n- You may combine `@keydown` with other event directives on the same element (such as `@keyup`, `@input`, `@blur`) as long as each uses a different event name.\n- You may combine `@keydown` with colon bindings like `:value`, `:class`, or `:style`, and with structural directives like `*if`, `*for`, and `*each` that target the same element.\n\nThe main Sercrod specific consideration is update behavior:\n\n- `keydown` is treated as a mutating event by default.\n- If you do not want re renders on every key, attach `.noupdate` or add `\"keydown\"` to `config.events.non_mutating` for your application.\n\n\n#### Best practices\n\n- Use `$event.key` and `$event.code`\n  Use `$event.key` for user facing logic (for example `\"Enter\"`, `\"Escape\"`, arrow keys) and `$event.code` if you need physical key positions independent of keyboard layout.\n\n- Avoid heavy work in handlers\n  Because `keydown` can fire many times while keys are held, avoid heavy logic directly in the `@keydown` expression.\n  Delegate to lightweight helpers or throttle logic on the data side if needed.\n\n- Control updates explicitly\n  For navigation or text editing shortcuts where you manage DOM directly (for example scrolling, `focus()` calls), use `.noupdate` so that Sercrod does not re render on every key.\n\n- Use `.once` for setup shortcuts\n  If a certain keyboard shortcut needs to be wired only once per element, and the handler does not mutate data in a way that requires re render, use `.once` and possibly `.noupdate`.\n\n- Prefer `n-input` for value tracking\n  Use `n-input` or `*input` for tracking changes to input values.\n  Use `@keydown` when you need to react to specific keys or combinations, not as a general value sync mechanism.\n\n\n#### Additional examples\n\nArrow key navigation in a list:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"items\": [\"Alpha\", \"Beta\", \"Gamma\"],\n  \"index\": 0\n}'>\n  <ul tabindex=\"0\"\n      @keydown=\"\n        if($event.key === 'ArrowUp'){\n          if(index > 0) index -= 1;\n        } else if($event.key === 'ArrowDown'){\n          if(index < items.length - 1) index += 1;\n        }\n      \">\n    <li *for=\"(i, item) of items\"\n        :class=\"i === index ? 'is-active' : ''\">\n      {{%item%}}\n    </li>\n  </ul>\n</serc-rod>\n```\n\nClosing a dialog with Escape:\n\n```html\n<serc-rod id=\"dialogHost\" data='{\n  \"open\": true\n}'>\n  <div *if=\"open\"\n       class=\"dialog\"\n       tabindex=\"0\"\n       @keydown=\"\n         if($event.key === 'Escape'){\n           open = false;\n         }\n       \">\n    <p>Press Escape to close.</p>\n  </div>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `@keydown` wires the native `keydown` event to a Sercrod expression.\n- `@keydown` is the preferred form for keyboard shortcuts and movement controls.\n- Key-name modifiers such as `.arrowup` and combinations such as `.arrowup+shift` are supported.\n- `.window` observes key events on `window` while executing the action in the nearest `<serc-rod>` context.\n- Inside the handler, `$event` / `$e` and `el` / `$el` are always available.\n- Modifiers such as `.prevent`, `.stop`, `.once`, `.capture`, `.passive`, `.update`, and `.noupdate` are supported and follow the same semantics as for other `@event` directives.\n- By default, `keydown` is not in the built in `non_mutating` list, so Sercrod will re render after the handler unless you suppress updates with `.noupdate` or change `config.events.non_mutating`.\n- If `cleanup.handlers` is enabled in the Sercrod configuration, the original `@keydown` attribute is removed from the output DOM after the handler is wired, keeping the rendered HTML clean.\n",
  "event-keyup": "### @keyup\n\n#### Summary\n\n`@keyup` attaches a handler to the native `keyup` event on an element.\nThe expression on `@keyup` is evaluated when the user releases a key while the element has focus.\nTypical uses include:\n\n- reacting to keyboard shortcuts,\n- running validation as the user types,\n- triggering search or filtering when the user releases Enter,\n- updating live previews based on current input.\n\n`@keyup` is part of the event handler family (such as `@click`, `@input`, `@keydown`) and follows the same generic event rules in Sercrod. It also supports the keyboard-specific key-name modifiers, modifier-key combinations, and optional `.window` observation described in the `keyboard-events` manual entry.\n\n\n#### Basic example\n\nTriggering search when the user presses Enter:\n\n```html\n<serc-rod id=\"search-app\" data='{\n  \"query\": \"\",\n  \"results\": [],\n  \"last_key\": \"\"\n}'>\n  <input type=\"search\"\n         :value=\"query\"\n         @keyup=\"\n           last_key = $event.key;\n           if($event.key === 'Enter'){\n             search(query);\n           }\n         \">\n\n  <p *if=\"last_key\">\n    Last key: {{%last_key%}}\n  </p>\n\n  <ul>\n    <li *for=\"item of results\">{{%item%}}</li>\n  </ul>\n</serc-rod>\n```\n\nBehavior:\n\n- The input is bound to `query`.\n- On every `keyup`, the handler records the last key to `last_key`.\n- When the key is `\"Enter\"`, the expression calls `search(query)` in the current scope.\n- Sercrod runs a light child update so that `last_key` and `results` are reflected in the DOM.\n\n\n#### Behavior\n\nCore rules:\n\n- Target event\n  `@keyup` wires the native `keyup` event on the element where it is declared.\n  Without `.window`, the element must be focusable or must receive a bubbled key event from a focused descendant. With `.window`, the declaration element is not the event target; the key event is observed on `window`.\n\n- Expression evaluation\n  Sercrod parses the attribute value as an expression (for example `search(query)` or `if($event.key === 'Enter') submit()`).\n  When the event fires, Sercrod evaluates this expression using its event evaluator, with access to data and the event object.\n\n- Side effects only\n  The return value of the expression is ignored.\n  What matters is the side effect of the expression (for example writing to data or calling functions).\n\n- Keyboard event access\n  The native `KeyboardEvent` instance is exposed as:\n\n  - `$event` and `$e` (both point to the same `KeyboardEvent`),\n  - The element is exposed as `el` and `$el`.\n\n  You can use `$event.key`, `$event.code`, `$event.ctrlKey`, and similar properties exactly as you would in plain JavaScript.\n\n- Multiple directives on the same element\n  You can combine `@keyup` with other event directives on the same element (for example `@keydown`, `@input`, `@blur`) as long as each uses a different event name.\n  Each handler is evaluated independently when its event fires.\n\n\n\n#### Keyboard-specific syntax\n\n`@keyup` supports the same key-name and modifier syntax as `@keydown`.\n\nExamples:\n\n```html\n@keyup.escape=\"open=false\"\n@keyup.window.arrowup=\"released=true\"\n@keyup.arrowup+shift=\"selected=true\"\n```\n\nHowever, shortcuts and movement controls are usually handled with `keydown`.\n\nUse `keyup` when the action should happen when a key is released.\n\nBe careful with modifier-key conditions on `keyup`.\n\nFor example:\n\n```html\n@keyup.arrowup+shift=\"selected=true\"\n```\n\nThis matches only if `Shift` is still pressed when `ArrowUp` is released.\n\nIf `Shift` is released first, the condition may not match.\n\nKey-name matching, `.window`, `.prevent`, `.preventDefault`, modifier-key combinations, and the `[Sercrod warn]` rule for arbitrary non-modifier combinations are the same as the `@keydown` keyboard specification.\n\nFor the full keyboard syntax, see the `keyboard-events` manual entry.\n\n\n#### Evaluation timing\n\n`@keyup` participates in Sercrod's normal rendering and event lifecycle:\n\n- Structural phase\n  Structural directives such as `*if`, `*for`, `*each`, `*switch`, and `*include` are applied first.\n  If these directives remove or replace the element, `@keyup` is attached only to the final rendered element.\n\n- Attribute phase\n  Once the element is kept, Sercrod processes its attributes.\n  Event attributes whose names start with the configured event prefix (by default `\"@\"`) are detected, and `_renderEvent` is called for each of them, including `@keyup`.\n\n- Listener attachment\n  In this phase, Sercrod creates a handler function and attaches it with `addEventListener(\"keyup\", handler, options)`, where `options` reflects event modifiers such as `.capture`, `.passive`, and `.once`.\n\n- Event firing\n  When the browser fires `keyup` on that element:\n\n  - Sercrod prepares an evaluation scope for this host and element.\n  - It injects the `KeyboardEvent` into `$event` and `$e`, and the element into `el` and `$el`.\n  - It evaluates the `@keyup` expression in that scope.\n  - After evaluation, Sercrod triggers an update pass (described below) so that data changes are reflected in the DOM.\n\nThere is no special debounce or delay attached to `@keyup` itself; it executes in the same turn as the native event.\n\n\n#### Execution model\n\nAt a high level, the runtime behaves as follows for `@keyup`:\n\n1. During rendering, Sercrod finds an attribute whose name starts with the event prefix (by default `\"@\"`) and whose event name portion is `\"keyup\"`.\n2. It extracts:\n\n   - the event name `ev = \"keyup\"`,\n   - the list of modifiers (for example `\"prevent\"`, `\"stop\"`, `\"once\"`, `\"capture\"`, `\"passive\"`),\n   - the expression string from the attribute value, for example `onKey($event)`.\n\n3. It constructs listener options from modifiers:\n\n   - `capture: mods.has(\"capture\")`,\n   - `passive: mods.has(\"passive\")`,\n   - `once: mods.has(\"once\")`.\n\n4. It defines a handler `handler(e)` that:\n\n   - Applies `preventDefault` if `mods` contains `\"prevent\"`.\n   - Applies `stopPropagation` if `mods` contains `\"stop\"`.\n   - Constructs an evaluation scope in which:\n\n     - `$event` and `$e` refer to `e`,\n     - `el` and `$el` refer to the element,\n     - other names are resolved first against Sercrod data, then against the provided scope, and finally against `window`.\n\n   - Calls `this.eval_event(expr, scope, { el, $event: e })` to execute the expression.\n   - Decides the update strategy and runs a suitable update method (see below).\n   - If the `\"once\"` modifier was present, removes the handler from the element after the first call.\n\n5. Before attaching the handler, Sercrod ensures that there is at most one handler per event name on the element:\n\n   - It keeps a private `el._sercrod_handlers[ev]`.\n   - If a previous handler exists for `\"keyup\"`, it is removed before the new handler is attached.\n\n6. It attaches the handler using `el.addEventListener(\"keyup\", handler, options)`.\n\nThis mechanism is shared by all `@event` directives; `@keyup` is one instance of this generic path, with the event name `\"keyup\"`.\n\n\n#### Key data and $event\n\nInside a `@keyup` handler expression:\n\n- `$event` and `$e` are instances of `KeyboardEvent`.\n- Useful properties include:\n\n  - `key` (logical key, for example `\"Enter\"`, `\"a\"`, `\"Escape\"`),\n  - `code` (physical key, for example `\"KeyA\"`, `\"Enter\"`),\n  - modifier states such as `ctrlKey`, `altKey`, `shiftKey`, `metaKey`,\n  - `repeat` (whether the key is being held down and auto repeating).\n\n- `el` and `$el` point to the element where `@keyup` is declared.\n- Reads of other identifiers consult:\n\n  1. Sercrod's data object for the host,\n  2. The provided scope for directives around this element (such as loop variables),\n  3. `window` as a final fallback.\n\n- Writes to properties in the data object propagate back into Sercrod data and mark them as dirty so that the subsequent update pass can reflect changes in the DOM.\n\n\n#### Update behavior for @keyup\n\n`@keyup` is treated as an input-like event in Sercrod's update strategy.\n\nAfter the handler runs, the runtime computes:\n\n- whether the event is considered input-like (`is_inputish`), and\n- whether the event is classified as non mutating according to `config.events.non_mutating`.\n\nFor `ev === \"keyup\"`:\n\n- `keyup` is explicitly listed as input-like.\n- As a result, after running the handler, Sercrod calls a lightweight child update on the host:\n\n  - `this._updateChildren(false, this)`.\n\nThis has two practical consequences:\n\n- Keyup handlers can safely mutate data without causing the whole host to be re rendered.\n- Focus and caret position are preserved more reliably during keyboard input, since only children are refreshed.\n\nThe classification as input-like is built into the runtime and is independent of the default `non_mutating` configuration (which lists hover and pointer events, but not `keyup`).\n\n\n#### Use with loops and conditionals\n\n`@keyup` composes naturally with structural directives controlling the element itself:\n\n- Inside loops:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <input type=\"text\"\n             :value=\"item.label\"\n             @keyup=\"item.label = $event.target.value\">\n    </li>\n  </ul>\n  ```\n\n  Each iteration has its own handler and its own `item` in scope.\n\n- With `*if`:\n\n  ```html\n  <input *if=\"mode === 'search'\"\n         type=\"search\"\n         :value=\"query\"\n         @keyup=\"if($event.key === 'Enter') runSearch(query)\">\n  ```\n\n  The handler is attached only when `mode === 'search'` is true.\n  If the element is removed by `*if`, the listener is removed along with the element during the next render.\n\nStructural directives do not change the semantics of `@keyup`; they only decide whether the element (and its handler) exist in the rendered tree.\n\n\n#### Use with form inputs and staged updates\n\n`@keyup` is especially useful for form inputs and staged editing:\n\n- Live filtering without committing immediately:\n\n  ```html\n  <input type=\"search\"\n         :value=\"filters.query\"\n         @keyup=\"filters.query = $event.target.value\">\n  ```\n\n  The data is kept in sync with the DOM on each key release, and Sercrod performs a child update for efficient re rendering.\n\n- Combining with `*stage` for drafts:\n\n  ```html\n  <form *stage=\"'editor'\">\n    <textarea\n      :value=\"editor.body\"\n      @keyup=\"editor.body = $event.target.value\">\n    </textarea>\n\n    <button type=\"button\" *apply=\"'editor'\">Save</button>\n    <button type=\"button\" *restore=\"'editor'\">Cancel</button>\n  </form>\n  ```\n\n  Here, `@keyup` only touches the staged `editor` snapshot.\n  The user can type freely, and only when they press \"Save\" does `*apply` commit the staged data back into the main data object.\n\n\n#### Sercrod-specific restrictions\n\nFor `@keyup`, the main Sercrod-specific points are:\n\n- Single handler per event name per element\n  Sercrod maintains at most one handler per event name on each element.\n\n  - This is a runtime detail: when the host re renders and encounters `@keyup` again, it removes the previous handler for `\"keyup\"` before attaching a new one.\n  - From a template perspective, you normally declare `@keyup` once per element.\n\n- Event prefix\n  The event prefix (by default `\"@\"`) is taken from `config.events.prefix`.\n  If you change this prefix globally, you must also update your templates accordingly (for example from `@keyup` to `ne-keyup` if the prefix was changed to `\"ne-\"`).\n  The event name `keyup` itself does not change.\n\nThere are no special combination restrictions unique to `@keyup`:\n\n- You can combine `@keyup` with other event directives (`@keydown`, `@input`, `@blur`, and so on) as long as each uses a different event name.\n- You can combine `@keyup` with attribute bindings like `:value`, `:class`, `:style`, or with structural directives like `*if` and `*for` on the same element.\n\n\n#### Best practices\n\n- Use `@keyup` for user facing feedback\n  `@keyup` is well suited for behaviors where you want to respond to user input without blocking typing, such as live search hints or inline validation.\n\n- Use `$event.key` for shortcut logic\n  Prefer `if($event.key === 'Enter')` and similar checks over raw key codes for readability and compatibility.\n\n- Combine with `@keydown` when necessary\n  For some interactions (such as continuous movement or game controls), you may want to use `@keydown` to start an action and `@keyup` to stop it.\n\n- Keep handler expressions focused\n  As with other event directives, keep inline expressions small.\n  Move complex logic to functions defined in your data or helpers, and call those from `@keyup`.\n\n- Avoid heavy work in the handler\n  Do not perform long running computations or blocking operations directly inside the `@keyup` expression.\n  Use asynchronous APIs or queue work into other parts of your application if needed.\n\n\n#### Additional examples\n\nSimple shortcut: submit on Enter, ignore other keys:\n\n```html\n<form *post=\"'/api/search:result'\">\n  <input type=\"search\"\n         :value=\"query\"\n         @keyup=\"\n           if($event.key === 'Enter'){\n             $el.form.requestSubmit();\n           }\n         \">\n\n  <button type=\"submit\">Search</button>\n</form>\n```\n\nToggle a help overlay with a keyboard shortcut:\n\n```html\n<serc-rod id=\"help-app\" data='{\n  \"show_help\": false\n}'>\n  <input type=\"text\"\n         :value=\"input\"\n         @keyup=\"\n           if($event.key === 'F1'){\n             show_help = !show_help;\n           }\n         \">\n\n  <aside *if=\"show_help\">\n    Press F1 again to hide this help.\n  </aside>\n</serc-rod>\n```\n\nImplement a simple key logger for debugging:\n\n```html\n<serc-rod id=\"logger\" data='{\n  \"keys\": []\n}'>\n  <input type=\"text\"\n         @keyup=\"\n           keys.push($event.key);\n           if(keys.length > 20) keys.shift();\n         \">\n\n  <p>Recent keys: {{%keys.join(' ')%}}</p>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `@keyup` is a generic event handler for the native `keyup` event.\n- The event object is available as `$event` and `$e`, while the element is available as `el` and `$el`.\n- After each `@keyup` handler call, Sercrod performs a lightweight child update on the host, treating keyup as an input-like event so that focus and caret remain stable.\n- Event modifiers such as `.prevent`, `.stop`, `.once`, `.capture`, and `.passive` are supported and handled before the expression is evaluated.\n- The event prefix is configurable, but the event name `keyup` is not; when the prefix changes, you must update the markup accordingly.\n",
  "keyboard-events": "### Keyboard events\n\n#### Summary\n\nSercrod supports keyboard actions with `@keydown` and `@keyup`.\n\nUse keyboard actions when a component needs to react to keys such as `ArrowUp`, `ArrowDown`, `Enter`, `Escape`, or modifier combinations such as `Shift + ArrowUp`.\n\nIn most shortcut and movement patterns, use `keydown`.\n\n`keyup` is also supported, but it is usually for release timing, cleanup, or actions that should happen after a key is released.\n\nKey event attributes are still part of the normal `@event` family, but they have keyboard-specific modifier rules:\n\n- Key names such as `arrowup`, `enter`, and `escape` may be written as modifiers.\n- Modifier-key combinations such as `arrowup+shift` and `s+ctrl` are supported.\n- The order of key tokens does not matter.\n- `.window` changes the observation target from the element to `window`.\n- `.prevent` and `.preventDefault` are accepted for `preventDefault()` behavior when the key condition matches.\n\n#### Local key events\n\nA normal `@keydown` action follows the browser's standard keyboard event behavior.\n\nThe element must receive the key event directly, or the event must bubble from a focused descendant.\n\n```html\n<serc-rod data=\"{ y: 0 }\">\n  <div tabindex=\"0\" @keydown.arrowup=\"y=y-1\" @keydown.arrowdown=\"y=y+1\">\n    <p>Focus this area, then press ArrowUp or ArrowDown.</p>\n    <p>Y: %y%</p>\n  </div>\n</serc-rod>\n```\n\nIn this example, the `div` is focusable because it has `tabindex=\"0\"`.\n\nWithout focus, a normal local `keydown` event will not be fired on that element.\n\nThis is ordinary DOM behavior. Sercrod does not make non-focusable elements focusable automatically.\n\n#### Window key events\n\nFor application-level keyboard controls, Sercrod supports the `.window` modifier.\n\n```html\n<serc-rod data=\"{ y: 0 }\">\n  <div @keydown.window.arrowup=\"y=y-1\" @keydown.window.arrowdown=\"y=y+1\">\n    <p>Press ArrowUp or ArrowDown anywhere in the window.</p>\n    <p>Y: %y%</p>\n  </div>\n</serc-rod>\n```\n\nWhen `.window` is used, the element is not the actual keyboard event target.\n\nInstead, the attribute is treated as a declaration.\n\nThe key event is observed on `window`.\n\nThe action is executed in the nearest `<serc-rod>` context.\n\nOnly that `<serc-rod>` host is updated.\n\nThis means that `.window` lets HTML describe a keyboard shortcut without hiding the logic in a separate JavaScript function.\n\nFor example:\n\n```html\n@keydown.window.arrowup=\"y=y-1\"\n```\n\nThis reads as:\n\n- When the window receives `ArrowUp`, decrease `y` in this Sercrod host.\n\n#### Declaring multiple window keys on one element\n\nYou may place several window key declarations on one ordinary element.\n\n```html\n<serc-rod data=\"{ x: 0, y: 0 }\">\n  <div\n    @keydown.window.arrowleft=\"x=x+1\"\n    @keydown.window.arrowright=\"x=x-1\"\n    @keydown.window.arrowup=\"y=y-1\"\n    @keydown.window.arrowdown=\"y=y+1\">\n  </div>\n\n  <p>X: %x%</p>\n  <p>Y: %y%</p>\n</serc-rod>\n```\n\nThis style is useful when you want to keep the keyboard map in one place.\n\nThe `div` does not need to be focusable in this example, because the declarations use `.window`.\n\n#### Declaring window keys with hidden inputs\n\nYou may also split the declarations across multiple ordinary elements.\n\nFor example, `input type=\"hidden\"` can be used as an invisible declaration location.\n\n```html\n<serc-rod data=\"{ x: 0, y: 0 }\">\n  <input type=\"hidden\" @keydown.window.arrowleft=\"x=x+1\">\n  <input type=\"hidden\" @keydown.window.arrowright=\"x=x-1\">\n  <input type=\"hidden\" @keydown.window.arrowup=\"y=y-1\">\n  <input type=\"hidden\" @keydown.window.arrowdown=\"y=y+1\">\n\n  <p>X: %x%</p>\n  <p>Y: %y%</p>\n</serc-rod>\n```\n\nIn this example, the hidden inputs do not receive keyboard focus.\n\nThey are only used as declaration locations.\n\nBecause `.window` is present, keyboard events are observed on `window`.\n\nThe actions still run in the nearest `<serc-rod>` context.\n\nBoth styles are valid.\n\nYou can group several declarations on one element, or split them across multiple hidden inputs or ordinary elements.\n\nThe difference is mainly readability and placement.\n\nIf declarations are placed inside a nested `<serc-rod>`, the nearest `<serc-rod>` changes, and the action belongs to that nested host.\n\n#### Key names\n\nKeyboard key names are matched case-insensitively.\n\nThe recommended Sercrod style is lowercase:\n\n```html\n@keydown.arrowup=\"y=y-1\"\n@keydown.enter=\"submitted=true\"\n@keydown.escape=\"open=false\"\n```\n\nOfficial `KeyboardEvent.key` names are also accepted.\n\nFor example, these refer to the same key:\n\n```text\nArrowUp\narrowup\nARROWUP\narrowUp\n```\n\nThe official browser key name is `ArrowUp`, but Sercrod examples use `arrowup` because lowercase names are easier to read in HTML attributes.\n\n#### Modifier keys\n\nModifier keys can be combined with `+`.\n\nThe order does not matter.\n\n```html\n<serc-rod data=\"{ y: 0 }\">\n  <div\n    @keydown.arrowup+shift=\"y=y-10\"\n    @keydown.shift+arrowdown=\"y=y+10\">\n    <p>Y: %y%</p>\n  </div>\n</serc-rod>\n```\n\nThese two forms are equivalent:\n\n```html\n@keydown.arrowup+shift=\"y=y-10\"\n@keydown.shift+arrowup=\"y=y-10\"\n```\n\nSupported modifier keys include:\n\n```text\nshift\nctrl\nalt\nmeta\n```\n\nExamples:\n\n```html\n@keydown.s+ctrl=\"saved=true\"\n@keydown.enter+ctrl=\"submitted=true\"\n@keydown.enter+ctrl+shift=\"submitted=true\"\n```\n\n#### Multiple non-modifier keys\n\nSercrod does not treat arbitrary non-modifier combinations such as `a+b` as a normal shortcut condition in the initial keyboard specification.\n\n```html\n@keydown.a+b=\"value=value+1\"\n```\n\nThis should produce `[Sercrod warn]`.\n\nThe reason is that true arbitrary multi-key detection requires pressed-key state management.\n\nModifier combinations such as `arrowup+shift`, `s+ctrl`, and `enter+ctrl+shift` are supported.\n\n#### Preventing default browser behavior\n\nUse `.prevent` to call `preventDefault()` when the key condition matches.\n\n```html\n<serc-rod data=\"{ y: 0 }\">\n  <div @keydown.window.arrowup.prevent=\"y=y-1\">\n    <p>Y: %y%</p>\n  </div>\n</serc-rod>\n```\n\n`.preventDefault` is also accepted.\n\n```html\n@keydown.window.arrowup.preventDefault=\"y=y-1\"\n```\n\nThe recommended form is `.prevent`.\n\n`preventDefault()` is applied only when the key condition matches.\n\nFor example, `@keydown.window.arrowup.prevent` prevents the default behavior for `ArrowUp`, not for every key.\n\n#### About keyup\n\n`keyup` uses the same key and modifier syntax.\n\n```html\n@keyup.escape=\"open=false\"\n@keyup.window.arrowup=\"released=true\"\n```\n\nHowever, shortcuts and movement controls are usually handled with `keydown`.\n\nUse `keyup` when the action should happen when a key is released.\n\nBe careful with modifier conditions on `keyup`.\n\nFor example:\n\n```html\n@keyup.arrowup+shift=\"selected=true\"\n```\n\nThis matches only if `Shift` is still pressed when `ArrowUp` is released.\n\nIf `Shift` is released first, the condition may not match.\n\n#### Window cleanup\n\nWindow key declarations are tied to the nearest `<serc-rod>` host.\n\nWhen that host is removed from the DOM, Sercrod cleans up its window key registrations using the Custom Element lifecycle.\n\nThis prevents removed components from continuing to react to window keyboard events.\n\n#### Notes\n\n- `.window` is powerful and should be used intentionally.\n- If multiple `<serc-rod>` hosts declare the same window shortcut, each matching host may react.\n- Sercrod does not automatically decide which host is active.\n- Use local key events when the shortcut should belong to a focused area.\n- Use `.window` when the shortcut should work at the window level.\n- `@keydown` should be the main form used in documentation examples for shortcuts and movement controls.\n- `@keyup` is supported, but should be explained mainly as a release-timing feature.\n",
  "event-submit": "### @submit\n\n#### Summary\n\n@submit attaches a handler to the native submit event.\nThe expression on @submit is evaluated when a form is submitted and the submit event reaches the element that declares @submit.\nTypical uses include validating data, toggling flags, or invoking custom submission logic before or instead of the browser's default form submission.\n\n@submit is part of the event handler family (such as @click, @input, @change, @keydown) and follows the same general evaluation rules as other @ directives.\nIt does not automatically prevent the browser's default submission; use modifiers or *prevent-default when you want to block navigation.\n\n\n#### Basic example\n\nIntercept form submission and mark the form as submitted without leaving the page:\n\n```html\n<serc-rod id=\"contact\" data='{\n  \"form\": {\n    \"name\": \"\",\n    \"message\": \"\"\n  },\n  \"submitted\": false\n}'>\n  <form @submit.prevent=\"submitted = true\">\n    <label>\n      Name:\n      <input type=\"text\" name=\"name\" :value=\"form.name\">\n    </label>\n\n    <label>\n      Message:\n      <textarea name=\"message\" :value=\"form.message\"></textarea>\n    </label>\n\n    <button type=\"submit\">Send</button>\n\n    <p *if=\"submitted\">\n      Thank you, we received your message.\n    </p>\n  </form>\n</serc-rod>\n```\n\nBehavior:\n\n- When the user clicks the button or presses Enter in a field, the browser fires a submit event on the form.\n- Sercrod invokes the expression submitted = true on that host.\n- The modifier .prevent on @submit.prevent calls event.preventDefault, so the browser does not perform a full page navigation.\n- The paragraph shows after the first successful submit event.\n\n\n#### Behavior\n\nCore rules:\n\n- Target event\n  @submit wires the native submit event.\n  The handler is attached to the element that carries @submit.\n  Usually this is a form element, but you can also listen on an ancestor and rely on event bubbling.\n\n- Expression evaluation\n  Sercrod treats the attribute value as a script expression, for example handleSubmit($event) or submitted = true.\n  When the event fires, Sercrod evaluates this expression in the current scope.\n\n- Event context\n  Inside the expression, Sercrod provides:\n\n  - $event and $e: the native Event or SubmitEvent instance.\n  - el and $el: the element that has @submit (typically the form).\n\n  For example:\n\n  - @submit.prevent=\"handleSubmit($event, $el)\"\n\n- Default action\n  By default, @submit does not alter the browser's default submit behavior.\n  The default action runs unless it is prevented by your handler or by *prevent-default.\n  To block navigation, use a modifier such as .prevent or use *prevent-default on the same form.\n\n- Modifiers\n  Modifiers are appended to the event name with dots, for example:\n\n  - @submit.prevent\n  - @submit.prevent.stop\n  - @submit.once\n  - @submit.noupdate\n  - @submit.update\n\n  The generic modifier semantics are:\n\n  - prevent: call event.preventDefault() before evaluating the expression.\n  - stop: call event.stopPropagation() before evaluating the expression.\n  - once: automatically remove the handler after the first successful call.\n  - capture: attach the listener in the capture phase.\n  - passive: attach the listener as passive.\n  - update: force a host update after the handler, even if the event is considered non mutating.\n  - noupdate: skip automatic updates after the handler.\n\n- Cleanup\n  If Sercrod configuration sets cleanup.handlers to a truthy value, the original @submit attribute is removed from the output DOM after the listener has been registered.\n  The listener itself remains attached; only the attribute string disappears from the markup.\n\n\n#### Evaluation timing\n\n@submit participates in the normal Sercrod render and event lifecycle:\n\n- Structural phase\n  Structural directives such as *if, *for, *each, *switch, and *include run first, deciding whether the form is present and what its children look like.\n\n- Attribute and directive phase\n  Once Sercrod decides to keep the element, it processes its attributes.\n  Event attributes whose names start with the configured events.prefix (by default \"@\") are handled in this phase.\n  For @submit, the handler is registered at this time.\n\n- Event firing\n  When the browser fires submit:\n\n  - If @submit is on the form, the handler runs at the form.\n  - If @submit is on an ancestor, the handler runs when the submit event bubbles to that ancestor, unless the event was stopped earlier.\n\n- Re renders\n  On subsequent renders, Sercrod reattaches or updates the handler so it always reflects the latest expression and scope.\n\n\n#### Execution model\n\nConceptually, Sercrod handles @submit as follows:\n\n1. During rendering, Sercrod encounters an element with an attribute whose name begins with the events prefix and whose base name is submit, such as @submit or @submit.prevent.stop.\n2. Sercrod strips the prefix and splits the remaining part on dots:\n\n   - \"submit.prevent.stop\" becomes event name submit and modifiers prevent and stop.\n\n3. Sercrod reads the attribute value as an expression string, for example handleSubmit($event) or submitted = true.\n4. Sercrod builds a handler function handler(e) that:\n\n   - Applies the prevent modifier by calling e.preventDefault() if present.\n   - Applies the stop modifier by calling e.stopPropagation() if present.\n   - Creates a proxy scope that:\n\n     - Exposes $event and $e as the native event.\n     - Exposes el and $el as the element with @submit.\n     - Falls back to data on the host and then to window for global names.\n\n   - Invokes eval_event(expr, mergedScope, { el, $event: e }).\n\n   - Decides how to update the host after the expression, based on the event name and modifiers (see below).\n\n   - Removes itself if the once modifier is set.\n\n5. Sercrod registers this handler with addEventListener(\"submit\", handler, options) on the element, where options reflect capture, passive, and once.\n\n6. When the handler finishes without throwing, Sercrod applies its default update strategy:\n\n   - It treats submit as a mutating event, so by default it triggers a full host update, unless modifiers override this behavior.\n\nThe expression runs in the context of the host; any property writes to data are marked as dirty and become visible in subsequent renders.\n\n\n#### Interaction with form submission\n\nBecause @submit hooks into the native submit event, it integrates with normal form behavior:\n\n- Where to place @submit\n\n  - Placing @submit on the form element is the most direct pattern.\n  - You can also place @submit on a wrapper element if you rely on the submit event bubbling from the form.\n\n- Default navigation\n  Without any prevention:\n\n  - The browser submits the form to its action URL (or to the current URL if action is absent).\n  - Sercrod still runs the @submit expression before or alongside the browser's default action, depending on modifiers and other listeners.\n\n- Preventing navigation\n\n  You have two main options to prevent native navigation:\n\n  - Use a modifier on @submit:\n\n    - @submit.prevent=\"handleSubmit($event)\"\n    - @submit.prevent.stop=\"handleSubmit($event)\"\n\n    The prevent modifier calls event.preventDefault() on the same event that triggers your handler.\n\n  - Or use *prevent-default on the form:\n\n    - <form *prevent-default=\"submit\" @submit=\"handleSubmit($event)\">...</form>\n\n    *prevent-default with mode submit (or all) adds a submit listener on the form that always calls event.preventDefault(), regardless of @submit modifiers.\n\n  When both @submit.prevent and *prevent-default=\"submit\" are present, the event is prevented in both listeners; this is safe but redundant.\n\n- Button clicks and keyboard submit\n\n  @submit fires when the submit event fires, regardless of whether it was initiated by a button click or pressing Enter in a field.\n  The exact triggering rules follow native browser behavior; Sercrod does not change which interactions emit submit.\n\n\n#### Update strategy and performance\n\nThe generic event engine applies an update strategy after each handler:\n\n- Submit is treated as a mutating event\n  By default, submit is not listed among non mutating events in configuration.\n  That means wantsUpdate is true unless you explicitly mark submit as non mutating.\n\n- Default behavior\n  For a submit event, Sercrod performs a full host update after the handler, unless:\n\n  - You explicitly add the noupdate modifier, for example @submit.noupdate=\"handleSubmit()\".\n  - You add submit to the events.non_mutating configuration set and do not force update with a modifier.\n\n- Overriding the default\n\n  - To always update, even for events configured as non mutating, use the update modifier.\n  - To avoid updates when you only use @submit to trigger external side effects (for example, sending data via a third party library), use noupdate.\n\nThis strategy ensures that typical form flows see an updated UI after submit (for example showing confirmation messages or clearing fields), while still allowing you to suppress updates when not needed.\n\n\n#### Use with attribute bindings and staged forms\n\n@submit combines naturally with attribute bindings and staging mechanisms:\n\n- With :action and :method\n\n  ```html\n  <form method=\"post\"\n        :action=\"endpoint\"\n        @submit.prevent=\"handleSubmit($event)\">\n    <!-- fields -->\n  </form>\n  ```\n\n  :action and :method control the visible form attributes; @submit controls what happens when the form is submitted.\n  Sercrod does not automatically couple @submit to :action; they are separate mechanisms that share the same DOM element.\n\n- With staged data via *stage\n\n  ```html\n  <serc-rod id=\"profile\" data='{\n    \"profile\": { \"name\": \"\", \"email\": \"\" },\n    \"saved\": false\n  }'>\n    <form *stage=\"'profile'\"\n          @submit.prevent=\"\n            saved = true;\n            // apply staged profile back to main data\n            *apply is still driven by its own directive\n          \">\n      <input type=\"text\" :value=\"profile.name\">\n      <input type=\"email\" :value=\"profile.email\">\n      <button type=\"submit\">Save</button>\n    </form>\n  </serc-rod>\n  ```\n\n  Here, @submit.prevent is used to mark the form as saved and to coordinate with other directives that commit staged data.\n  The actual commit is still controlled by the staging directives; @submit only runs expressions.\n\n\n#### Use with conditionals and loops\n\nLike other event directives, @submit can be used with structural directives that control the element itself:\n\n- Conditional forms:\n\n  ```html\n  <form *if=\"mode === 'login'\"\n        @submit.prevent=\"onLogin($event)\">\n    <!-- login fields -->\n  </form>\n\n  <form *if=\"mode === 'register'\"\n        @submit.prevent=\"onRegister($event)\">\n    <!-- registration fields -->\n  </form>\n  ```\n\n  Each form gets its own @submit handler, and only the active form's handler is attached.\n\n- Forms inside loops:\n\n  ```html\n  <div *for=\"item of items\">\n    <form @submit.prevent=\"submitItem(item, $event)\">\n      <!-- fields bound to item -->\n      <button type=\"submit\">Save {{%item.name%}}</button>\n    </form>\n  </div>\n  ```\n\n  Each iteration receives a dedicated handler whose expression is evaluated with that iteration's item in scope.\n\n\n#### Sercrod-specific restrictions\n\nFor @submit itself, there are no special Sercrod only restrictions beyond the general event rules:\n\n- You may add @submit to any element; in practice it is most useful on form elements or ancestors that receive the bubbling submit event.\n- You may combine @submit with other event directives on the same element, as long as they use distinct event names (for example @click, @keydown).\n- You may combine @submit with attribute directives such as :action, :method, :class, :style, and with structural directives such as *if and *for targeting the same element.\n\nUnlike structural directives such as *each, *include, or *import, @submit does not participate in element ownership rules for children, so there is no restriction like \"only one per element\" for @submit.\n\n\n#### Best practices\n\n- Prefer form level @submit\n  Attach @submit to the form element rather than to individual buttons when you want to handle the logical submission of the form as a whole.\n\n- Use .prevent when staying on the same page\n  For single page flows where you do not want a full page reload, always use @submit.prevent or *prevent-default=\"submit\".\n  This lets you run custom logic and update the UI without leaving the host page.\n\n- Keep handlers focused\n  Keep the logic in @submit handlers relatively small and delegate heavy work to helper functions or services.\n  This keeps expressions readable and easier to maintain.\n\n- Combine with feedback flags\n  Use flags such as pending, submitted, or error on the host data to drive visual feedback (for example disabling the button or showing status messages) after submit.\n\n- Tune updates when needed\n  If a submit handler only triggers an external side effect and does not need to update the Sercrod host, consider using @submit.prevent.noupdate to avoid unnecessary re renders.\n\n\n#### Additional examples\n\nMinimal custom submit with validation:\n\n```html\n<serc-rod id=\"signup\" data='{\n  \"email\": \"\",\n  \"error\": \"\",\n  \"ok\": false\n}'>\n  <form @submit.prevent=\"\n           error = (!email || email.indexOf('@') === -1)\n             ? 'Please enter a valid email.'\n             : '';\n           ok = !error;\n         \">\n    <input type=\"email\" name=\"email\" :value=\"email\">\n    <button type=\"submit\">Sign up</button>\n\n    <p *if=\"error\" *print=\"error\"></p>\n    <p *if=\"ok\">\n      Check your inbox to confirm your email.\n    </p>\n  </form>\n</serc-rod>\n```\n\nListening on a wrapper element instead of the form:\n\n```html\n<serc-rod id=\"host\" data='{\n  \"count\": 0\n}'>\n  <div @submit.prevent=\"count = count + 1\">\n    <form>\n      <input type=\"text\" name=\"q\">\n      <button type=\"submit\">Search</button>\n    </form>\n  </div>\n\n  <p>Submits in this area: {{%count%}}</p>\n</serc-rod>\n```\n\nIn this pattern, the form still fires submit, and the event bubbles to the wrapper div where @submit is attached.\n\n\n#### Notes\n\n- @submit is an event handler directive for the native submit event.\n- The expression runs with $event and $e set to the native event, and el and $el set to the element that declared @submit.\n- By default, submit is treated as a mutating event, so Sercrod performs a host update after the handler unless you explicitly suppress it with noupdate.\n- Use .prevent on @submit or *prevent-default=\"submit\" on a form when you need to intercept submission without letting the browser navigate away.\n- If cleanup.handlers is enabled, the @submit attribute is removed from the rendered DOM after listener registration; this does not affect behavior, only the visible markup.\n",
  "events": "### Event bindings (fallback for @name)\n\nThis page describes the general behavior of Sercrod's **event bindings** for `@name` when there is **no dedicated manual** for that event.\n\nTypical examples that rely on this fallback:\n\n- `@pointerdown`, `@pointerup`, `@pointermove`\n- `@mousedown`, `@mouseup`, `@mousemove`\n- `@dragstart`, `@dragover`, `@drop`\n- `@wheel`\n- custom events dispatched via `element.dispatchEvent(new CustomEvent(\"...\"))`\n\nEvents such as:\n\n- `@click`\n- `@submit`\n- `@input`\n- `@change`\n- `@keydown`\n- `@keyup`\n- keyboard-specific combinations such as `@keydown.arrowup+shift` and `@keydown.window.escape`\n- `@focus`\n- `@blur`\n\nhave (or may have) their own manuals (`event-click.md`, `event-submit.md`, and so on) and are not fully described here.\n\nThis page explains the **common rules** for all `@name` event bindings that do not have a dedicated entry.\n\n> Note: This document uses `@` as the event prefix.\n> If you configure `config.events.prefix` to something else (for example `ne-`), mentally replace `@click` with the actual prefix you use (`ne-click`, and so on).\n\n\n#### Keyboard event exception\n\n`@keydown` and `@keyup` belong to the event binding family, but they have a dedicated keyboard syntax.\n\nFor keyboard events, dot modifiers may include key names such as `arrowup`, `enter`, and `escape`, and `+` may combine a normal key with modifier keys such as `shift`, `ctrl`, `alt`, or `meta`.\n\nExamples:\n\n```html\n@keydown.arrowup=\"y=y-1\"\n@keydown.arrowup+shift=\"y=y-10\"\n@keydown.window.escape=\"open=false\"\n```\n\nUse the `keyboard-events` manual entry for these cases instead of treating them as ordinary fallback `@name` events.\n\n\n#### Overview\n\n- `@name` attaches a DOM event listener for the event `name` on that element.\n- When the event fires, Sercrod evaluates the attribute value as **JavaScript** in the current Sercrod scope.\n- The scope contains:\n  - the `data` object from the `<serc-rod>` host,\n  - stage data if you use staging (`*stage`),\n  - local variables from `*let`, loops, and other structural directives.\n- The **DOM `Event` object** is available as:\n  - `$event` (primary name),\n  - `$e` (short alias).\n- The **current element** is available as:\n  - `el`,\n  - `$el`.\n- The return value of the code is ignored:\n  - what matters is the side effect (for example updating data, calling functions),\n  - re-rendering after the handler depends on the event type and on modifiers such as `.update` or `.noupdate`.\n\nYou can read `@name` as \"run this code when this DOM event occurs on this element\".\n\n\n#### Syntax\n\nGeneral form:\n\n- Without modifiers:\n\n  - `@eventName=\"code\"`\n\n- With modifiers:\n\n  - `@eventName.mod1.mod2=\"code\"`\n\n##### Recognized modifiers and categories\n\nSercrod understands the following modifier names.\nThey fall into three categories:\n\n1. **DOM listener option modifiers**\n   These map directly to standard `addEventListener` options and are **not** Sercrod specific:\n\n   - `.capture`\n     - Sets the listener option `capture: true`.\n     - The handler runs in the capture phase.\n\n   - `.once`\n     - Sets the listener option `once: true`.\n     - The browser calls the handler at most once and then automatically removes it.\n\n   - `.passive`\n     - Sets the listener option `passive: true`.\n     - Signals that the handler will not call `preventDefault()` in normal browser semantics.\n\n2. **DOM method helpers (Sercrod specific syntax, standard DOM behavior)**\n   These modifiers are **Sercrod syntax** that call standard methods on the event object.\n   They are not native event options and are not passed to `addEventListener`:\n\n   - `.prevent`\n     - Calls `event.preventDefault()` for that event before evaluating the handler code.\n\n   - `.stop`\n     - Calls `event.stopPropagation()` for that event before evaluating the handler code.\n\n3. **Sercrod specific re-rendering modifiers**\n   These modifiers control Sercrod's update logic only.\n   They are **not** standard JS event options and are **not** passed to `addEventListener`:\n\n   - `.update`\n     - Forces a re-render after the handler, even if the event is normally treated as non mutating.\n\n   - `.noupdate`\n     - Suppresses host re-rendering after the handler, even if the event type would normally trigger an update.\n\n   These flags affect only Sercrod's internal \"should we update the host?\" decision.\n   They do not change event propagation, default behavior, or listener options.\n\nExamples:\n\n```html\n<div @pointerdown=\"startDrag($event)\"></div>\n\n<div @dragover=\"handleDragOver($event)\"\n     @drop=\"handleDrop($event)\"></div>\n\n<section @wheel.prevent=\"zoomWithWheel($event)\">\n  ...\n</section>\n\n<button @pointerdown.stop.update=\"handlePress($event)\">\n  Press\n</button>\n```\n\nThe `code` part is arbitrary JavaScript that runs inside a Sercrod evaluation context similar to:\n\n```js\nwith (scope) {\n  // your expression or statements here\n}\n```\n\nYou can write either:\n\n- a simple expression:\n\n  - `@pointerdown=\"drag.active = true\"`\n\n- or multiple statements separated by semicolons:\n\n  - `@pointerdown=\"drag.active = true; drag.startX = $event.clientX\"`\n\n\n#### Event object and element access\n\nFor all event bindings:\n\n- The DOM event object is injected as:\n\n  - `$event` (primary name),\n  - `$e` (short alias).\n\n- The current element is injected as:\n\n  - `el`,\n  - `$el`.\n\nTypical patterns:\n\n```html\n<div\n  @pointerdown=\"\n    drag = {\n      active: true,\n      x: $event.clientX,\n      y: $event.clientY\n    }\n  \"\n  @pointermove=\"drag && drag.active && updateDrag($event)\"\n  @pointerup=\"endDrag($event)\"\n></div>\n```\n\nInside a helper function you can use the full event API:\n\n```js\nfunction updateDrag($event){\n  const t = $event.target;\n  // Use $event.clientX, $event.clientY, t.dataset.id, and so on.\n}\n```\n\nNotes:\n\n- There is no special variable named `event` injected by Sercrod.\n  - Use `$event` or `$e` instead.\n- `el` and `$el` always refer to the element that owns the `@name` attribute.\n- Assigning to `$event`, `$e`, `el`, or `$el` has no effect on Sercrod and should be treated as read only in handlers.\n\n\n#### Data updates and re-render timing\n\nEvent handlers often mutate data. Sercrod's event pipeline is tuned so that:\n\n- High frequency events (such as pointer moves) do **not** force heavy re-renders by default.\n- Input related events perform **lightweight** updates that keep focus stable.\n\nThe basic rules are:\n\n1. **During the handler**:\n\n   - Writes like `count++` or `state.value = ...` go into the current evaluation scope.\n   - If a written key also exists in the host `data` object, Sercrod updates `data[key]` as well and marks it as dirty.\n\n2. **After the handler**, Sercrod decides how to update based on:\n\n   - the event type (`ev`),\n   - the `events.non_mutating` configuration list,\n   - whether the event is treated as input like,\n   - and modifiers `.update`, `.noupdate`, and `.once`.\n\n   Concretely:\n\n   - Sercrod maintains a set `NON_MUTATING` derived from `config.events.non_mutating`.\n     By default it contains events like:\n\n     - `mouseover`, `mouseenter`, `mousemove`, `mouseout`, `mouseleave`, `mousedown`\n     - `pointerover`, `pointerenter`, `pointermove`, `pointerout`, `pointerleave`, `pointerrawupdate`, `pointerdown`\n     - `wheel`, `scroll`, `touchmove`, `touchstart`\n     - `dragstart`, `drag`, `dragenter`, `dragover`, `dragleave`, `dragend`\n     - `resize`, `timeupdate`, `selectionchange`\n\n   - It detects **input like events**:\n\n     - `input`, `change`, `beforeinput`,\n     - `keydown`, `keyup`,\n     - `compositionstart`, `compositionupdate`, `compositionend` (any event name starting with `composition`),\n     - `click` on form controls (inputs, textareas, selects, or contentEditable elements).\n\n   - It then computes `wantsUpdate`:\n\n     - Start with `wantsUpdate = !NON_MUTATING.has(ev)`.\n     - If `.update` is present, set `wantsUpdate = true`.\n     - If `.noupdate` is present, set `wantsUpdate = false`.\n     - If `.once` is present and `ev` is in `NON_MUTATING`, Sercrod forces `wantsUpdate = false` to avoid re-creating a one time listener on a non mutating event.\n\n   - Finally, it applies the update:\n\n     - If the event is input like (as defined above), or a `click` on a form control, Sercrod performs a **lightweight children update**:\n       - it calls an internal `_updateChildren(...)` so only the host's children are reconciled, preserving focus.\n     - Otherwise, if `wantsUpdate` is true, Sercrod triggers a normal host update.\n     - If `wantsUpdate` is false, no re-render is triggered by this handler.\n\n3. **Modifiers override defaults**:\n\n   - `.update` forces an update even for events listed in `events.non_mutating`.\n   - `.noupdate` suppresses updates even for events that would normally re-render.\n\nExamples:\n\n```html\n<!-- No automatic update: wheel is non mutating by default -->\n<div @wheel=\"pan.x += $event.deltaX; pan.y += $event.deltaY\"></div>\n\n<!-- Force update after a move event -->\n<div @pointermove.update=\"cursor = { x: $event.clientX, y: $event.clientY }\"></div>\n\n<!-- Explicitly suppress update (even if you touch data) -->\n<div @mousedown.noupdate=\"debug.lastDown = $event.clientX\"></div>\n```\n\nPractical guidelines:\n\n- For high frequency events (`pointermove`, drag over, scroll, wheel):\n  - Start without `.update`.\n  - Add `.update` only when you really need DOM changes on each event.\n- For most other events:\n  - The default behavior already re-renders when it is meaningful.\n  - Use `.noupdate` only when you are sure the handler does not affect the visible DOM.\n\n\n#### Interaction with *input and other helpers\n\nInput helpers such as `n-input` / `*input`, `*lazy`, and `*eager` control:\n\n- how form control values are synchronized into data,\n- when that synchronization happens (immediately, on blur, and so on).\n\n`@name` is orthogonal to those helpers:\n\n- You can attach `@keydown`, `@input`, or `@change` on the same element that uses `n-input`.\n- Use the event handlers for extra behavior (validation, logging, analytics).\n- Let the input helpers manage the primary data binding.\n\nExample:\n\n```html\n<input\n  n-input=\"form.name\"\n  @keydown=\"lastKey = $event.key\"\n  @change=\"validate(form)\"\n>\n```\n\n\n#### Interaction with *prevent-default and *prevent\n\nSercrod also provides **structural directives** for some common default behaviors:\n\n- `*prevent-default`\n- `*prevent` (alias)\n\nThese directives are evaluated when the element is rendered.\nIn the current implementation:\n\n- They read an optional mode from the attribute value:\n\n  - `\"enter\"` (default) ? affects `keydown` for the Enter key on that element.\n  - `\"submit\"` ? affects `submit` events on `<form>` elements.\n  - `\"all\"` ? applies both rules.\n\n- They then attach helper listeners that call `event.preventDefault()` for:\n\n  - `keydown` Enter on the element itself (for `\"enter\"` or `\"all\"`),\n  - `submit` events on `<form>` elements (for `\"submit\"` or `\"all\"`).\n\nThe directives do **not** stop propagation, and they do not automatically cover all event types.\n\nFor fine grained control on a specific `@name` binding, use **modifiers on the event attribute**:\n\n- `.prevent` on `@name`:\n  - Sercrod specific syntax that calls `event.preventDefault()` for that particular handler.\n- `.stop` on `@name`:\n  - Sercrod specific syntax that calls `event.stopPropagation()` for that particular handler.\n\nTypical patterns:\n\n```html\n<!-- Prevent Enter key from submitting or triggering default behavior -->\n<input *prevent-default=\"enter\"\n       @keydown=\"handleKey($event)\">\n\n<!-- Prevent form submission via browser default, use JavaScript instead -->\n<form *prevent-default=\"submit\"\n      @submit=\"saveForm()\">\n  ...\n</form>\n\n<!-- Per event control with modifiers -->\n<a href=\"/danger\"\n   @click.prevent=\"confirmLeave && !confirmLeave()\">\n  Leave page\n</a>\n\n<button @click.stop=\"handleClick($event)\">\n  Click\n</button>\n```\n\nFor complete details, see the separate manuals for `*prevent-default` and `*prevent`.\n\n\n#### Custom events\n\nYou can also bind handlers to **custom events** dispatched from JavaScript.\n\nIn the template:\n\n```html\n<div @card-activated=\"setActive($event.detail.id)\">\n  ...\n</div>\n```\n\nIn JavaScript:\n\n```js\nelement.dispatchEvent(new CustomEvent(\"card-activated\", {\n  detail: { id: 123 }\n}));\n```\n\nInside the handler code, `$event.detail` contains the payload passed by the dispatcher.\n\n\n#### Error handling and edge cases\n\n- **If handler code throws**:\n\n  - Sercrod catches the error.\n  - If `config.error.warn` (or the equivalent runtime flag) is enabled, Sercrod logs a warning such as:\n    - `[Sercrod warn] @event handler: ...`\n  - The event listener remains attached.\n  - Later events still trigger the handler.\n\n- **Bubbling and nesting**:\n\n  - Standard DOM bubbling rules apply.\n  - If you have nested elements with the same `@event`:\n    - parent handlers see the event only if it is not stopped,\n    - use `.stop` on the inner handler when you want to keep the event local.\n\n- **Manual listeners**:\n\n  - Sercrod's listeners coexist with listeners added via `addEventListener`.\n  - The firing order follows normal browser rules (capture vs bubble, registration order, passive listeners).\n\n- **Multiple re-renders**:\n\n  - Sercrod tracks listeners by event name and element.\n  - On re-render, it removes old listeners for that event on that element and reattaches them.\n  - You should not manually remove Sercrod's event handlers.\n\n\n#### Relation to specific event manuals\n\nSome events are common enough to have their own detailed manuals, for example:\n\n- `event-click.md` ? click interactions, buttons, and modifiers.\n- `event-submit.md` ? form submissions and interaction with `*prevent-default`.\n- `event-input.md` and `event-change.md` ? text fields and update timing.\n- `event-keydown.md` and `event-keyup.md` ? keyboard handling patterns.\n- `event-focus.md` and `event-blur.md` ? focus management and \"touched\" state.\n\nThose pages:\n\n- inherit all the rules described here,\n- add **event specific** patterns, edge cases, and recommendations.\n\nIf you bind an event with `@something` and there is **no** `event-something.md`, the behavior of that binding is determined by the rules on this page.\n",
  "fetch": "### *fetch\n\n#### Summary\n\n`*fetch` loads JSON from a URL and writes it into Sercrod host data.\nThe JSON response can either replace the entire root data object or be merged into a specific property path.\n`*fetch` has an alias `n-fetch`.\n\nOn a Sercrod host element (`<serc-rod>`), `*fetch` is typically used for initial data loading.\nOn normal elements (such as `<button>` or `<div>`), it can be used for explicit reload buttons or one-time auto fetches.\n\nKey points:\n\n- URL spec is a plain string `URL[:prop]` (no expression evaluation).\n- `prop` is optional. If omitted, the entire data root is replaced by the fetched JSON.\n- If `prop` is present, that path inside `data` is updated instead.\n- For non-clickable elements, `*fetch` auto-runs once per URL (with a special rule for `ts=` query parameters).\n- For clickable elements, `*fetch` runs on click only.\n- On the host `<serc-rod>`, `*fetch` runs during `connectedCallback` before the first render.\n\n\n#### Basic example\n\nInitial data load into a host:\n\n```html\n<serc-rod id=\"app\" *fetch=\"/api/items.json:items\">\n  <h1>Items</h1>\n  <ul *each=\"item of items\">\n    <li *print=\"item.name\"></li>\n  </ul>\n</serc-rod>\n```\n\nBehavior:\n\n- When the Sercrod host is connected, it calls `fetch(\"/api/items.json\")`.\n- The JSON response is written into `data.items`.\n- After the fetch completes, Sercrod performs the first render and the list shows the loaded items.\n\n\n#### URL spec format\n\nThe `*fetch` attribute value is interpreted as a simple string in the following format:\n\n- `URL`\n- `URL:prop`\n- `URL:base[key]`\n\nParsing rules:\n\n- The runtime splits the spec with `spec.split(\":\")` and uses:\n\n  - `file`: the first part before the first `:`.\n  - `prop`: the second part after the first `:` (if any).\n\n- `file` is passed directly to `fetch(file)`.\n\n- `prop` controls where the JSON is stored:\n\n  - No `prop`: the entire root `data` is replaced with the JSON.\n  - `prop` without brackets:\n\n    - Example: `\"/api/items.json:items\"`  \n      The JSON response becomes `data.items`.\n\n  - `prop` with brackets:\n\n    - Example: `\"/api/users.json:users[current]\"`  \n      The JSON response becomes `data.users[current]`.\n\n    - Internally, the runtime uses a simple pattern:\n\n      - It matches `(.+?)\\[(.+)\\]` to split `base` and `key`.\n      - It ensures `data[base]` is an object and writes `data[base][key] = json`.\n\nImportant implications:\n\n- Only the first `:` in the spec is treated as the separator between `file` and `prop`.\n- If your URL itself contains `:`, the part before the first `:` is used as `file` and the rest is treated as `prop`.\n  In practice, you should use relative paths or encoded URLs to avoid ambiguous `:` characters.\n\n\n#### Behavior\n\nCore behavior:\n\n- `*fetch` always performs an HTTP GET using the global `fetch()` API:\n\n  - `fetch(file).then(r => r.json())`\n\n- The response is expected to be valid JSON.\n  If `r.json()` fails, the error is routed to an error event (see “Events” below).\n\nData writing rules:\n\n- When `prop` is present:\n\n  - If `prop` matches `base[key]`:\n\n    - Ensure `data[base]` exists and is an object.\n    - Write `data[base][key] = json`.\n\n  - Otherwise:\n\n    - Write `data[prop] = json`.\n\n- When `prop` is absent:\n\n  - The entire root data object is replaced.\n  - The new root is wrapped with Sercrod’s internal proxy wrapper to keep observation consistent.\n\n  Conceptually:\n\n  - `data = json` becomes `data = wrap(json)`.\n\nUpdate:\n\n- After a successful fetch, Sercrod schedules `update()` on the host using `requestAnimationFrame`.\n- The template is re-rendered against the updated data.\n\nErrors:\n\n- On fetch or JSON parse failure, Sercrod does not modify data.\n- Instead, it emits a `sercrod-load-error` event (see “Events”).\n- `update()` is not automatically called by `*fetch` in the error path.\n\n\n#### Host vs normal elements\n\n`*fetch` behaves differently depending on whether it is placed on the Sercrod host or on a normal element.\n\n1. On the Sercrod host (`<serc-rod>`):\n\n   - Handled inside `connectedCallback`.\n   - Sequence:\n\n     - The host parses `data`.\n     - If the parent host is currently loading (for example from its own `*fetch`), the child host waits.\n     - If the host has `*fetch` or `n-fetch`:\n\n       - `this._loading` is set to `true`.\n       - The spec is read from the attribute.\n       - Warnings are temporarily suppressed for the duration of `_do_load`.\n       - `_do_load(spec)` is called.\n\n     - On success:\n\n       - Data is updated according to the rules above.\n       - `this._loading` is set to `false`.\n       - `requestAnimationFrame(() => this.update())` is called.\n\n     - If the host does not have `*fetch`:\n\n       - The host calls `update()` normally.\n\n   Implications:\n\n   - When `*fetch` is on the host, the first Sercrod render happens after the fetch completes.\n   - Child hosts can check `parent._loading` and delay their own initialization until the parent is done.\n\n2. On normal elements (button, link, div, etc.):\n\n   - Handled inside the element rendering pipeline.\n   - Sercrod clones the element, sets up `*fetch` behavior, appends the clone, and renders its children.\n\n   Clickable vs non-clickable:\n\n   - Clickable elements:\n\n     - Recognized as clickable if:\n\n       - `tagName` is `BUTTON`, or\n       - `tagName` is `A` without `download`, or\n       - `tagName` is `INPUT` with `type` in `button`, `submit`, `reset`, `image`.\n\n     - For clickable elements:\n\n       - No automatic fetch is performed.\n       - A click listener is attached, calling `_do_load(spec)` on each click.\n\n   - Non-clickable elements:\n\n     - For non-clickable tags (such as `div`, `section`, `span`, etc.):\n\n       - `*fetch` is auto-triggered once after initial render.\n       - A “once key” is derived from the spec to prevent repeated automatic fetches.\n\n       - If this once key has not been seen before, Sercrod:\n\n         - Records it in an internal set for this host.\n         - Schedules `_do_load(spec)` with `requestAnimationFrame`.\n\n   Children:\n\n   - In both cases, after cloning and setting up `*fetch`, Sercrod renders the element’s children normally.\n   - Child directives (`*print`, `*if`, `*each`, etc.) can immediately read from existing data.\n   - After the fetch completes, the host rerender updates those children with the new data.\n\n\n#### Once-key and the `ts` parameter\n\nFor non-clickable elements, `*fetch` auto-runs “at most once per URL spec” by using an internal once key.\n\nThe once key is derived as follows:\n\n- Sercrod tries to parse the spec as a URL relative to `location.href`.\n- If that succeeds:\n\n  - It removes the `ts` query parameter from the URL.\n  - It uses `pathname` plus the remaining query string to form the once key.\n\n- If parsing fails:\n\n  - It removes any `ts` parameter with a regular expression.\n  - It also strips trailing `?` or `&` characters.\n\nImplications:\n\n- Adding a `ts` query parameter is treated as a cache-busting trick that does not affect the once key.\n- Re-rendering with the same `*fetch` spec will not auto-run the fetch again for non-clickable elements.\n- To re-run fetch automatically, you must either:\n\n  - Change the spec in a way that changes the once key, or\n  - Use a clickable element and let the user trigger fetch explicitly.\n\n\n#### Events\n\n`*fetch` communicates its progress via DOM events on the host:\n\n- Before fetch starts:\n\n  - `sercrod-load-start`\n\n    - `detail`:\n\n      - `stage`: `\"fetch\"`\n      - `host`: the Sercrod host instance\n      - `spec`: the full spec string\n      - `file`: the URL part (`file`)\n      - `prop`: the property spec (if any)\n\n- After a successful fetch, before `update()`:\n\n  - `sercrod-loaded`\n\n    - `detail`:\n\n      - `stage`: `\"fetch\"`\n      - `host`: the Sercrod host instance\n      - `spec`: the full spec string\n      - `file`: the URL part\n      - `prop`: the property spec (if any)\n      - `json`: the parsed JSON value\n      - `paths`: an array of logical data paths that were touched\n\n        - Either `[\"$root\"]` when the root was replaced, or\n        - A list such as `[\"items\"]` or `[\"users[current]\"]` when merging into a property.\n\n- On error:\n\n  - `sercrod-load-error`\n\n    - `detail`:\n\n      - `stage`: `\"fetch\"`\n      - `host`: the Sercrod host instance\n      - `spec`: the full spec string\n      - `file`: the URL part\n      - `prop`: the property spec (if any)\n      - `error`: the error message string\n\nNotes:\n\n- These events bubble and are composed.\n- They allow external code to observe loading, success, and failure, and to implement custom UI such as progress indicators or retry buttons.\n\n`*fetch` does not set or use `$pending`, `$error`, `$download`, or `$upload` in data. Those state helpers are used by `*api` and `*post`, not by `*fetch`.\n\n\n#### Interaction with *api, *post, and *into\n\n`*fetch`, `*post`, and `*api` are all “request” directives that write into host data, but they differ in capabilities:\n\n- `*fetch`:\n\n  - Simple GET-only helper.\n  - Takes a static `URL[:prop]` string.\n  - Writes JSON into data or a property path.\n  - Does not use `*into` or `$pending` / `$error` / `$download` / `$upload`.\n\n- `*post`:\n\n  - Sends JSON built from host data via POST.\n  - Uses a similar `URL[:prop]` spec to map the JSON response back into data.\n  - Integrates with `$pending`, `$error`, `$upload`, `$download`.\n\n- `*api`:\n\n  - General-purpose API helper.\n  - Supports method, headers, file uploads, `*into`, and once rules at the API level.\n\nOrdering and combinations:\n\n- Inside the element rendering pipeline:\n\n  - `*post` is processed before `*fetch`.\n  - `*fetch` is processed before `*api`.\n\n- Each of these directives claims the element and returns early after setting up behavior, so only the first matching directive on a given element executes.\n\nRecommendations:\n\n- Do not combine `*fetch` with `*post` or `*api` on the same element.\n  Only the first one in the internal order will take effect, and the rest will be ignored.\n- Use separate elements if you need multiple request types (for example, a `*fetch` for initial data and a `*post` button for submitting changes).\n\n`*into` is reserved for `*api` and is not used by `*fetch`.\n\n\n#### Use with conditionals and loops\n\n`*fetch` is not a structural directive by itself, so it composes with structural directives as long as they are applied at different levels.\n\nTypical patterns:\n\n- Conditional host fetch:\n\n  - For a host, `*fetch` runs from `connectedCallback`.\n    You normally do not wrap the host itself with `*if`.\n\n- Conditional child fetch:\n\n  ```html\n  <serc-rod id=\"app\" data='{\"ready\": false, \"items\": []}'>\n    <button\n      *if=\"ready\"\n      *fetch=\"/api/items.json:items\"\n    >\n      Load items\n    </button>\n  </serc-rod>\n  ```\n\n  - `*if` is processed before `*fetch`.\n  - If `ready` is false, the button (and its `*fetch`) is not rendered.\n\n- Fetch inside loops:\n\n  - You can place `*fetch` on elements inside `*for` or `*each` bodies, but you should be cautious:\n\n    - Each rendered element with `*fetch` may set up its own auto fetch or click handler.\n    - Non-clickable `*fetch` inside a loop can trigger many automatic requests, once per iteration.\n\n  ```html\n  <ul *each=\"user of users\">\n    <li>\n      <span *print=\"user.name\"></span>\n      <button *fetch=\"'/api/user/' + user.id + '.json:details'\">\n        Load details\n      </button>\n    </li>\n  </ul>\n  ```\n\n  - Note that the spec for `*fetch` is still a literal string in the current implementation; the example above illustrates intent, but the runtime does not evaluate expressions in the spec. For dynamic URLs, prefer `*api` which expands text with `%expr%` placeholders.\n\n\n#### Server-side contract and recommended API shape\n\nBecause `*post`, `*fetch`, and `*api` all treat HTTP communication as “JSON in, JSON out” and share the same state flags, it is natural to standardize server-side handlers around this contract.\n\nRecommended approach on the server:\n\n- Treat Sercrod-driven endpoints as JSON endpoints:\n\n  - Always accept a JSON request body for write operations.\n  - Always return a JSON response for both success and application-level errors.\n  - Use a stable envelope shape so that `URL[:prop]` and `*into` can be wired consistently.\n\n- Reuse the same processing pipeline:\n\n  - Parse JSON.\n  - Run validation, authentication, business logic, and logging in a shared middleware.\n  - Produce a JSON object that Sercrod can store as-is into `data[prop]`, `data[base][key]`, or a target selected by `*into`.\n\nBenefits for server-side code:\n\n- You can implement a “Sercrod API style” once and reuse it across multiple endpoints.\n- Monitoring and logging become easier because every Sercrod request and response has the same structure.\n- Frontend and backend teams can agree on a single JSON contract instead of negotiating many small variations.\n\nPosition in Sercrod’s design:\n\n- Sercrod does not force this server-side style, but the runtime is optimized around it:\n  - `*post` and `*fetch` share the `URL[:prop]` rule and write values back without further transformation.\n  - `*api` writes the raw response into the variable named by `*into`.\n  - All of them update `$pending`, `$error`, `$download`, and `$upload` in a consistent way.\n- For new projects that adopt Sercrod end to end, designing server APIs to follow this unified JSON contract is strongly recommended.\n- For existing APIs, you can:\n  - Use `*api` to integrate with legacy endpoints as they are.\n  - Gradually introduce Sercrod-style JSON endpoints for new features and move existing endpoints toward the same contract when possible.\n\n\n#### Best practices\n\n- Use `*fetch` for simple JSON GETs:\n\n  - When you only need to GET JSON and place it in a single property or replace the root, `*fetch` is the smallest tool.\n\n- Keep the spec simple and relative:\n\n  - Prefer relative URLs such as `\"/api/items.json\"` and use `:prop` for data placement.\n  - Avoid unencoded `:` in the URL part, since `*fetch` uses `spec.split(\":\")`.\n\n- Use props for partial updates:\n\n  - Use `:prop` when you want to update a subset of data without replacing the root.\n  - Use `base[key]` syntaxes such as `:users[current]` when you need keyed slots.\n\n- Reserve `*fetch` on the host for initial data load:\n\n  - Host-level `*fetch` is ideal for “page load → fetch → render once” behavior.\n  - It keeps the initial render in sync with fetched data.\n\n- Use clickable `*fetch` for explicit reloads:\n\n  - Putting `*fetch` on a button or link gives users control over when to refresh data.\n  - This avoids the implicit “auto-run once” behavior of non-clickable `*fetch`.\n\n- Handle errors through events:\n\n  - Attach listeners for `sercrod-load-error` when you need explicit error handling.\n  - For more advanced error-state tracking inside data, consider using `*api` or `*post` instead.\n\n\n#### Additional examples\n\nHost-level full replacement:\n\n```html\n<serc-rod id=\"profile\" *fetch=\"/api/profile.json\">\n  <h1 *print=\"name\"></h1>\n  <p *print=\"email\"></p>\n</serc-rod>\n```\n\n- The entire root data is replaced by the JSON from `/api/profile.json`.\n- The template then uses `name` and `email` from the new root.\n\nHost-level partial update:\n\n```html\n<serc-rod id=\"dashboard\" data='{\"stats\": {}, \"user\": {}}' *fetch=\"/api/stats.json:stats\">\n  <h2>Dashboard</h2>\n  <section>\n    <p>Total users: <span *print=\"stats.totalUsers\"></span></p>\n    <p>Active users: <span *print=\"stats.activeUsers\"></span></p>\n  </section>\n</serc-rod>\n```\n\n- Only `data.stats` is overwritten by the JSON response.\n- Other properties such as `data.user` remain untouched.\n\nNon-clickable auto fetch:\n\n```html\n<serc-rod id=\"news\" data='{\"articles\": []}'>\n  <section *fetch=\"/api/news.json:articles\">\n    <h2>Latest news</h2>\n    <ul *each=\"article of articles\">\n      <li>\n        <strong *print=\"article.title\"></strong>\n        <p *print=\"article.summary\"></p>\n      </li>\n    </ul>\n  </section>\n</serc-rod>\n```\n\n- The `<section>` is not clickable, so `*fetch` auto-runs once per spec.\n- After the fetch, `articles` is updated and the list is rendered.\n\nClickable reload button:\n\n```html\n<serc-rod id=\"log-viewer\" data='{\"log\": []}'>\n  <button *fetch=\"/api/log.json:log\">\n    Reload log\n  </button>\n\n  <ul *each=\"entry of log\">\n    <li *print=\"entry.message\"></li>\n  </ul>\n</serc-rod>\n```\n\n- Each click triggers a fresh GET request for `/api/log.json`.\n- The JSON response replaces `data.log`, and the list is updated.\n\n\n#### Notes\n\n- `*fetch` and `n-fetch` are aliases with identical behavior.\n- The spec is treated as a raw string; `*fetch` does not evaluate expressions or expand `%expr%` placeholders.\n- The directive expects JSON responses. Non-JSON responses cause `r.json()` to reject and result in `sercrod-load-error`.\n- Root replacement via `*fetch` always re-wraps the new root data to maintain Sercrod’s internal observation and proxy invariants.\n- When used on normal elements, `*fetch` sets up behavior on a cloned element and then returns early; no additional directives on the same element are evaluated after `*fetch` in the rendering pipeline.\n- On hosts, only one `*fetch` per host is meaningful; additional fetches should be implemented via nested elements or separate buttons.\n- For complex API use cases (headers, methods, payloads, file uploads, and rich state tracking), consider using `*api` and `*post` instead of `*fetch`.\n",
  "for": "### *for\n\n#### Summary\n\n`*for` repeats the host element for each entry in a list, object, or other iterable.\nThe host element itself is duplicated as many times as needed, and each clone is rendered with its own iteration scope.\nThe directive understands JavaScript-like `in` and `of` loop syntax and has an alias `n-for`.\n\nThere is also a special host-level form when `*for` is placed directly on a Sercrod host (`<serc-rod>`). In that case the host is not repeated; instead, the inner template is rendered multiple times inside the same host with different scopes.\n\nStructural restriction:\n\n- Do not combine `*for` and `*each` on the same element.\n  - In the implementation, `*each` takes precedence and `*for` is ignored.\n  - Use either `*for` or `*each`, not both, and prefer moving one directive to a child or parent element if you need both behaviors.\n\n\n#### Basic example\n\nClassic list items:\n\n```html\n<serc-rod id=\"app\" data='{\"items\":[\"Apple\",\"Banana\",\"Cherry\"]}'>\n  <ul>\n    <li *for=\"item of items\">\n      <span *print=\"item\"></span>\n    </li>\n  </ul>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<ul>` is rendered once.\n- `*for=\"item of items\"` clones the `<li>` for each element in `items`.\n- For each clone, Sercrod renders the `<li>` subtree with a local variable `item` bound to the current value.\n- The result is a `<ul>` containing three `<li>` elements as siblings.\n\n\n#### Behavior\n\nElement-level `*for`:\n\n- `*for` is a structural directive that controls how many times the element itself is rendered.\n- The host element and all of its attributes and children are cloned for each iteration.\n- The original element acts as a template and is not rendered directly.\n- The directive is available as both `*for` and `n-for`; they behave the same.\n\nHost-level `*for` on `<serc-rod>`:\n\n- When you put `*for` on a Sercrod host (`<serc-rod>`), the host is not duplicated.\n- Instead, the host clears its content and repeatedly renders its internal template with different iteration scopes.\n- This is useful when you want a single Sercrod host that manages multiple repeated blocks of content with a shared outer container.\n\n\n#### Expression syntax\n\nThe expression to the right of `*for` uses a restricted, JS-like loop syntax:\n\n- `value of iterable`\n- `(key, value) of iterable`\n- `key in object`\n- `(key, value) in object` (allowed but not recommended; `of` is clearer)\n\n`key` and `value` must be simple identifiers (no destructuring patterns).\n\nSupported patterns for element-level `*for`:\n\n- Arrays:\n\n  - `item of items`\n    Iterates an array, binding `item` to each element.\n\n  - `(index, item) of items`\n    Iterates an array, binding `index` to the numeric index and `item` to the element.\n\n  - `key in items`\n    Iterates an array-like object by key, binding `key` to the property name as a string. For a normal array this means `\"0\"`, `\"1\"`, and so on.\n\n  - `(key, value) in items`\n    Iterates an array-like object by key and value. `key` receives the property name and `value` receives the value at that index.\n\n- Objects:\n\n  - `key in obj`\n    Iterates over enumerable property names of `obj`. `key` receives each property name.\n\n  - `(key, value) in obj`\n    Iterates over enumerable properties of `obj`. `key` receives the property name, `value` receives the value.\n\n  - `value of obj`\n    Iterates over `Object.entries(obj)` and binds `value` to each value. An extra implicit variable `key` is also added when you use the single-variable `value of obj` form.\n\n  - `(key, value) of obj`\n    Iterates over `Object.entries(obj)` and binds both `key` and `value`.\n\nHost-level `*for` on `<serc-rod>` uses the same syntax but normalizes it slightly differently (see the dedicated section below).\n\n\n#### Value semantics (element-level *for)\n\nElement-level `*for` distinguishes `of` and `in` like JavaScript:\n\n- For arrays with `of`:\n\n  - `item of items`:\n\n    - Sercrod loops with `for (const v of items)`.\n    - For each value `v`, it clones the host and binds `item` to `v`.\n\n  - `(index, item) of items`:\n\n    - Sercrod uses `items.entries()`.\n    - `index` receives the numeric index.\n    - `item` receives the value.\n\n- For objects with `of`:\n\n  - `value of obj`:\n\n    - Sercrod uses `Object.entries(obj)`.\n    - For each `[k, v]`, it clones the host and binds:\n      - `key` to the property name (implicit when you do not specify a key variable).\n      - `value` to the value.\n\n  - `(key, value) of obj`:\n\n    - Sercrod uses `Object.entries(obj)`.\n    - `key` receives the property name, `value` receives the value.\n\n- For arrays and objects with `in`:\n\n  - `key in expr`:\n\n    - Sercrod uses `for (const k in expr)`.\n    - The single variable receives the key (property name).\n\n  - `(key, value) in expr`:\n\n    - Sercrod uses `for (const k in expr)`.\n    - `key` receives the key.\n    - `value` receives `expr[k]`.\n\nDeprecation note:\n\n- `(key, value) in expr` is supported for compatibility but is discouraged.\n- Sercrod prints a console warning when you use `in` with two variables.\n- Prefer `(key, value) of expr` for new code when you need both key and value.\n\n\n#### Evaluation timing\n\nElement-level `*for` participates in Sercrod's structural evaluation order inside `renderNode`:\n\n- Text interpolation and static/dynamic handling happen first.\n- `*if` and its chain (`*elseif`, `*else`) are evaluated and may select a specific branch.\n- `*switch` and its branches (`*case`, `*case.break`, `*default`) are processed.\n- `*each` runs before `*for` when both directives are present.\n- `*for` is then evaluated on the element if it is still in play.\n\nImportant consequences:\n\n- If `*if` or `*switch` is on the same element as `*for`, the conditional logic runs first and can select or skip that element. Once an element is selected by `*if` or a `*case`, the cloned branch is fed back into `renderNode`, where `*for` is then applied inside the clone.\n- If both `*each` and `*for` are present on the same element, `*each` runs first and returns early. The `*for` block is never reached in that case. This is why combining `*each` and `*for` on a single element is considered unsupported.\n\nHost-level `*for` is evaluated during the host's update cycle, before its template is rendered. When a host `<serc-rod>` has `*for`, it clears its content, runs the host-level loop, and calls the internal template renderer once per iteration.\n\n\n#### Execution model\n\nElement-level `*for`:\n\n1. Sercrod locates the `*for` (or `n-for`) attribute on the element.\n2. It parses the expression into `keyName`, `valName`, `modeWord` (`\"in\"` or `\"of\"`), and `srcExpr`.\n3. It evaluates `srcExpr` in the current scope with `{ el: work, mode: \"for\" }`.\n4. If the result is falsy, `*for` acts as an empty loop and renders nothing.\n5. For each entry in the collection (interpreted according to `in` or `of`):\n   - Sercrod clones the entire element subtree with `cloneNode(true)`.\n   - It removes `*for` and `n-for` from the clone.\n   - It merges the iteration variables into the scope for that clone.\n   - It calls the internal element renderer on the clone.\n6. The clones are appended to the original parent in order.\n7. The original element is not appended; it serves only as the template.\n\nHost-level `*for` on `<serc-rod>`:\n\n1. During an update, the host decides whether it should re-render.\n2. The host clears its current content (`innerHTML = \"\"`).\n3. It determines the current top-level scope (`_stage` if a staging branch is active, otherwise `_data`).\n4. It reads the host's `*for` or `n-for` expression and parses it with the same `(key, value) in|of expr` pattern.\n5. It evaluates the iterable expression with `{ el: this, mode: \"update\" }`.\n6. It normalizes the result into `[key, value]` pairs, using a helper that:\n   - Treats `x in array` with a single variable similar to `x of array` for backward compatibility (values rather than keys).\n   - For objects, returns `[key, value]` pairs for both `of` and `in`, but emphasizes `of` as the clearer option when you need `(key, value)`.\n7. For each `[k, v]` pair:\n   - If both `keyName` and `valName` are present, the host calls its template renderer with `{ ...scope, [keyName]: k, [valName]: v }`.\n   - If only `valName` is present, it renders with `{ ...scope, [valName]: v }`.\n8. The result is a single `<serc-rod>` instance whose children are repeated blocks rendered from the host's template.\n\n\n#### Variable creation and scope layering\n\nElement-level `*for`:\n\n- Creates loop variables in the per-iteration scope:\n  - `item` in `item of items`.\n  - `index` and `item` in `(index, item) of items`.\n  - `key` in `key in obj`.\n  - `key` and `value` in `(key, value) in obj` or `(key, value) of obj`.\n- These variables shadow any outer variables with the same names for the duration of the iteration.\n- All original scope entries remain available, including:\n  - Host data.\n  - `$data`, `$root`, `$parent`, and any global helpers.\n  - Methods defined through Sercrod configuration.\n\nHost-level `*for`:\n\n- Works with the same pattern of loop variables, but applies them at the host scope level:\n  - Each iteration constructs a new top-level scope for rendering the host's template.\n  - The loop variables are added on top of the base scope (`_stage` or `_data`).\n- This allows each iteration to treat the host's template as a root-level view for a different item.\n\nGuidelines:\n\n- Prefer descriptive names like `user`, `row`, or `entry` rather than very short names when it improves readability.\n- Be careful not to unintentionally shadow important data names used elsewhere in the template.\n\n\n#### Parent access\n\n`*for` does not introduce a dedicated parent reference, but you can still access parent data as usual:\n\n- Through the data tree, for example `state.items`, `config`, or `data.users`.\n- Through `$root`, which points to the root Sercrod data.\n- Through `$parent`, which points to the nearest ancestor Sercrod host's data.\n\nLoop variables exist alongside these references and do not prevent you from reading outer scopes.\n\n\n#### Use with conditionals and loops\n\nYou can safely combine `*for` with other directives when they are placed thoughtfully:\n\n- Host-level `*if` and `*switch`:\n\n  - `*if` and `*switch` can appear on the same element as `*for`.\n  - The conditional logic is resolved first.\n  - Once a branch is selected, that branch is cloned and then inspected again; `*for` on the cloned node runs normally.\n\n  ```html\n  <li *if=\"items && items.length\" *for=\"item of items\">\n    <span *print=\"item\"></span>\n  </li>\n  ```\n\n  - In practice, most code is easier to read if you put `*if` on a parent or child rather than combining them on the same element, but the combination is supported.\n\n- Child-level conditions and nested loops:\n\n  - You can use `*if` or nested `*for` / nested `*each` inside the body of a `*for` loop.\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <span *if=\"item.visible\" *print=\"item.label\"></span>\n      <ul *each=\"tag of item.tags\">\n        <li *print=\"tag\"></li>\n      </ul>\n    </li>\n  </ul>\n  ```\n\n- Interaction with `*each`:\n\n  - Use `*for` when you want to repeat the element itself as a sibling.\n  - Use `*each` when you want to keep a single container and repeat its children.\n  - Do not put `*for` and `*each` on the same element; as noted above, `*each` will take precedence and `*for` is effectively ignored.\n\n\n#### Use with templates and *include\n\n`*for` works well with templates and `*include`:\n\n- Typical pattern:\n\n  ```html\n  <serc-rod id=\"app\" data='{\"items\":[{\"name\":\"Ann\"},{\"name\":\"Bob\"}]}'>\n    <template *template=\"user-item\">\n      <li>\n        <span *print=\"item.name\"></span>\n      </li>\n    </template>\n\n    <ul>\n      <li *for=\"item of items\" *include=\"'user-item'\"></li>\n    </ul>\n  </serc-rod>\n  ```\n\n- In this pattern:\n  - `*for` repeats the `<li>` for each `item`.\n  - `*include` fills each `<li>` body from the named template, and the included template can use `item`.\n- Because `*for` clones the entire element (including structural attributes) before rendering, `*include` runs inside each clone and uses the iteration scope correctly.\n\n\n#### Comparison with *each\n\nBoth `*for` and `*each` iterate collections, but they operate on different structural levels:\n\n- `*for`:\n\n  - Repeats the host element itself.\n  - Typical for repeated siblings, such as list items, cards, and table rows.\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <span *print=\"item\"></span>\n    </li>\n  </ul>\n  ```\n\n- `*each`:\n\n  - Keeps the host element as a single container and repeats its children.\n  - Useful when the container must remain unique (for example `<ul>`, `<tbody>`, or SVG groups).\n\n  ```html\n  <ul *each=\"item of items\">\n    <li *print=\"item\"></li>\n  </ul>\n  ```\n\nGuideline:\n\n- When in doubt, ask whether you want multiple copies of the container element or just one container with repeated children.\n  - Multiple containers: use `*for`.\n  - Single container: use `*each`.\n\n\n#### Host-level *for on <serc-rod> (advanced)\n\nPlacing `*for` directly on a Sercrod host is an advanced but powerful pattern:\n\nBasic example:\n\n```html\n<serc-rod id=\"host\" *for=\"user of users\" data='{\n  \"users\": [\n    { \"name\": \"Alice\", \"age\": 30 },\n    { \"name\": \"Bob\",   \"age\": 25 }\n  ]\n}'>\n  <section class=\"user-card\">\n    <h2 *print=\"user.name\"></h2>\n    <p *print=\"user.age + ' years old'\"></p>\n  </section>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<serc-rod>` element itself appears once in the DOM.\n- Its internal template (the `<section>` block) is rendered once per `user`.\n- Each iteration receives a scope where `user` refers to the current user.\n- This is similar in spirit to `*each` on a top-level container, but it is implemented at the Sercrod host level and uses the host's template renderer.\n\nNotes on semantics:\n\n- Single-variable `x of expr` is the recommended form for values in host-level `*for`.\n- Single-variable `x in expr` is treated as a value loop for arrays for backward compatibility and, with objects, also yields values when you access `x`.\n- When you need both key and value, use `(key, value) of expr`. Using `(key, value) in expr` is supported but emits a console warning; `of` is clearer.\n\n\n#### Best practices\n\n- Prefer `of` for new code:\n\n  - Use `item of items` or `(index, item) of items` for arrays.\n  - Use `(key, value) of obj` for objects when you need both key and value.\n  - Reserve `in` for cases where you explicitly care about keys and understand the differences.\n\n- Keep loop expressions simple:\n\n  - Complex filtering, sorting, or mapping is easier to maintain when done in data or helper methods rather than written inline in the `*for` expression.\n\n- Avoid mutating the iterated collection while rendering:\n\n  - Modifying the array or object you are looping over during rendering can lead to hard-to-follow behavior.\n  - Prefer to compute a derived collection, then iterate over that.\n\n- Choose between `*for` and `*each` explicitly:\n\n  - If your design calls for multiple sibling elements, use `*for`.\n  - If your design calls for a single container with repeated children, use `*each`.\n\n- Do not combine `*for` with `*each` on the same element:\n\n  - In current Sercrod, `*each` wins when both are present, so `*for` never runs.\n  - For clarity, always keep them on separate elements.\n\n\n#### Additional examples\n\nIterating over an object map with both key and value:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"users\": {\n    \"u1\": { \"name\": \"Alice\" },\n    \"u2\": { \"name\": \"Bob\" }\n  }\n}'>\n  <ul>\n    <li *for=\"(id, user) of users\">\n      <strong *print=\"id\"></strong>\n      <span *print=\"user.name\"></span>\n    </li>\n  </ul>\n</serc-rod>\n```\n\nUsing `*for` on table rows:\n\n```html\n<serc-rod id=\"table\" data='{\n  \"rows\": [\n    { \"id\": 1, \"name\": \"Alpha\" },\n    { \"id\": 2, \"name\": \"Beta\" }\n  ]\n}'>\n  <table>\n    <thead>\n      <tr>\n        <th>ID</th>\n        <th>Name</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr *for=\"row of rows\">\n        <td *print=\"row.id\"></td>\n        <td *print=\"row.name\"></td>\n      </tr>\n    </tbody>\n  </table>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*for` and `n-for` are aliases; projects should choose one style and use it consistently.\n- The expression on `*for` is evaluated as normal JavaScript inside Sercrod's expression sandbox.\n- Element-level `*for` repeats the element itself, while host-level `*for` on `<serc-rod>` repeats the inner template.\n- Single-variable `x in array` is treated differently at the host level for backward compatibility; for clarity and predictability, prefer `x of expr` and `(key, value) of expr` in new code.\n- Structural combinations where `*each` and `*for` compete for control of the same host element are not supported. Use one structural directive per element.\n",
  "global": "### *global\n\n#### Summary\n\n`*global` executes one or more JavaScript statements with side effects that write into Sercrod's shared data or the JavaScript global object.\nUnlike `*let`, it does not create a local scope for children; instead, it updates existing data or `globalThis` and then continues rendering.\n\nUse `*global` when you need to:\n\n- Update shared Sercrod data from inside a template, especially from nested components.\n- Bridge Sercrod with traditional global scripts or libraries by writing to `window` / `globalThis`.\n\nImportant points:\n\n- `*global` does not remove its own attribute. Its expression is evaluated on every render of the element.\n- Write targets are chosen dynamically:\n  - If the key already exists in Sercrod data (`this._data`), that data is updated.\n  - Otherwise the key is created on `globalThis` (for example `window` in a browser).\n\n\n#### Basic example\n\nSet an app title in shared data or global scope:\n\n```html\n<serc-rod id=\"app\" data='{\"message\": \"Hello\"}'>\n  <h1 *print=\"appTitle || 'Default title'\"></h1>\n\n  <!-- If appTitle exists in data, update it there; otherwise create window.appTitle -->\n  <div *global=\"appTitle = 'Sercrod Demo'\"></div>\n</serc-rod>\n```\n\nBehavior:\n\n- On the first render, Sercrod evaluates `appTitle = 'Sercrod Demo'` inside a sandbox.\n- If `appTitle` already exists in the Sercrod data for this host, that value is updated.\n- If it does not exist, `globalThis.appTitle` is created.\n- After the update, Sercrod schedules a re-render, so the `<h1>` sees the new value.\n\n\n#### Behavior\n\n- `*global` is a non-structural directive. It does not change how many times the element or its children are rendered.\n- It executes arbitrary JavaScript statements in a special sandbox that decides where writes go:\n  - Reads: current scope first, then `globalThis`, then a placeholder for unknown keys.\n  - Writes: Sercrod data when a matching key already exists, otherwise `globalThis`.\n\nKey differences from `*let`:\n\n- `*let`:\n  - Creates a local scope for the element and its children.\n  - New variables are stored in that local scope and optionally promoted to data if they do not exist there yet.\n  - Does not intentionally write into `globalThis`.\n\n- `*global`:\n  - Reuses the current effective scope for reads.\n  - Writes into data if the key already exists, otherwise into `globalThis`.\n  - Does not create a new child scope; children see the same scope that was used before `*global`.\n\nThe attribute is not removed:\n\n- `*global` remains on the element.\n- Every time the host re-renders the element, `*global` executes again.\n- Expressions should therefore be idempotent or self-guarded to avoid unintended repeated side effects.\n\n\n#### Expression model\n\nThe value of `*global` is treated as one or more JavaScript statements, not as a special Sercrod grammar.\n\n- Sercrod wraps your text as:\n\n  - `with(scope){ <expr> }`\n\n- There is no parsing or rewriting by Sercrod itself.\n- Any valid JavaScript statement is accepted, for example:\n  - Simple assignments: `count = 0`, `appTitle = 'Sercrod'`\n  - Property updates: `settings.theme = 'dark'`\n  - Function calls: `logChange(message)`, `store.dispatch(action)`\n  - Multiple statements separated by semicolons.\n\nExamples:\n\n```html\n<div *global=\"\n  appTitle = 'Sercrod Demo';\n  settings.ready = true;\n\"></div>\n```\n\n```html\n<div *global=\"\n  // Update a known data field\n  profile.name = 'Taro';\n\"></div>\n```\n\nNotes:\n\n- Because the code runs inside `with(scope){ ... }`, free identifiers first resolve against Sercrod's evaluation scope, then fall back to `globalThis`.\n- `*global` is intended for side effects, not for returning values.\n\n\n#### Data and global write targets\n\n`*global` uses a clear policy for writes:\n\n- Check if the key already exists in Sercrod data (`this._data`):\n  - If yes, assign to that data field.\n  - If no, assign to `globalThis`.\n\nIn simplified terms:\n\n- On write:\n\n  - If `_data` has a key `k`, then `this._data[k] = value`.\n  - Otherwise `globalThis[k] = value`.\n\n- On read:\n\n  - Look in the current scope (local variables, methods, `$data`, `$root`, `$parent`, etc).\n  - If not found, look in `globalThis` (Math, custom globals, and so on).\n  - If still not found, create a special placeholder object that tracks nested property access and will later decide where to write when assignment happens.\n\nPractical consequences:\n\n- If you want `*global` to update a Sercrod data field, declare that field in `data` so the key already exists.\n- If you want to intentionally write to a true global variable (for example `window.appVersion`), do not define that key in `data`.\n\n\n#### Scope and special helpers\n\nBefore evaluating the `*global` expression, Sercrod enriches the scope:\n\n- `$parent`:\n  - Injected when not already present.\n  - Points to the data object of the nearest ancestor `serc-rod` element (or equivalent Sercrod host).\n  - Not enumerable, so it does not get accidentally copied into data.\n\n- External methods (from configuration):\n\n  - Any names listed in `this._methods_names` are made visible in the scope.\n  - If `window[name]` is a function, it is exposed as `scope[name]`.\n  - If `window[name]` is an object, its function-valued properties are flattened into the scope.\n\n- Internal Sercrod utilities:\n\n  - All functions in `this.constructor._internal_methods` are added to the scope when not already present.\n\nChildren of the element:\n\n- `*global` does not change the effective scope passed to children.\n- Children see the same `effScope` that was active before `*global` ran, but any changes made to data or globals will influence subsequent evaluations on later renders.\n\n\n#### Evaluation timing\n\n`*global` participates in the non-structural phase of the render pipeline.\n\nFor each element processed by Sercrod:\n\n1. Non-structural directives (no return, can be applied multiple times):\n   - `*let` is evaluated first.\n   - `*global` is evaluated next, using the current effective scope.\n2. Structural directives (returning a specialized render result) follow:\n   - `*if` / `*elseif` / `*else` chain, if present.\n   - `*switch`, if present.\n   - `*each` and `*for`, which control repetition.\n3. Other bindings and directives on children are evaluated during child rendering.\n\nConsequences:\n\n- `*global` runs even if a later `*if` on the same element would decide not to render that element.\n- To guard side effects, include the condition inside the `*global` expression itself:\n\n  ```html\n  <div *if=\"user\" *global=\"if(user) { lastUserId = user.id; }\"></div>\n  ```\n\n- Because `*global` runs before structural directives, changes it makes to data can affect `*if`, `*switch`, `*each`, and `*for` on the same element.\n\n\n#### Execution model\n\nConceptually, when Sercrod encounters `*global` on an element:\n\n1. It builds an evaluation scope (`scope`) that includes:\n   - Current data, including `$data`, `$root`, `$parent`.\n   - Methods from configuration.\n   - Sercrod's internal helper methods.\n\n2. It wraps this scope in a `Proxy` that:\n   - Always reports `true` from `has` so that `with(scope){ ... }` sees every identifier as present.\n   - On `get`:\n     - Returns values from `scope` if available.\n     - Otherwise, returns from `globalThis` if available.\n     - Otherwise, returns a special placeholder made by `_makehole_scoped()` that records the access path.\n   - On `set`:\n     - Converts the property key to a string.\n     - If SERCROD data already has that key, writes into data.\n     - Otherwise writes to `globalThis`.\n\n3. It executes:\n\n   - `Function(\"scope\",\"el\",\"$event\", \"with(scope){ \" + expr + \" }\")(sandbox, el, null);`\n\n4. After execution, it calls `_schedule_update()` if available to ensure any changes to data are picked up by the reactive update loop.\n\nError handling:\n\n- If execution throws an exception, and `this.error?.warn` is truthy, Sercrod logs a warning:\n\n  - It includes the directive name (`*global`), the error message, and the original expression text.\n\n\n#### Use with conditionals, loops, and events\n\nConditionals on the same element:\n\n- Because `*global` runs before `*if`, the side effect happens regardless of whether the element is ultimately rendered.\n\n  ```html\n  <div\n    *if=\"ready\"\n    *global=\"logChange('host visited');\"\n  >\n    Content\n  </div>\n  ```\n\n- Here `logChange` runs whenever Sercrod processes the element, even when `ready` is false and the element content is not displayed.\n\nLoops:\n\n- `*global` can be used inside repeated structures (`*for` or `*each`), but be careful:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\"\n        *global=\"selectedId = item.selected ? item.id : selectedId\">\n      <span *print=\"item.label\"></span>\n    </li>\n  </ul>\n  ```\n\n  - The expression runs once per iteration, on each render of the loop.\n  - Prefer to keep `*global` logic idempotent and quick, especially in large loops.\n\nEvents:\n\n- `*global` itself is not an event directive and does not receive `$event` directly.\n- To combine event handling and global updates, use `@click` (or other event directives) to call a function that performs the global update, or call `*global`-style expressions inside that function.\n\n\n#### Best practices\n\n- Prefer data-first design:\n\n  - Define your shared state under the Sercrod host's `data`.\n  - Use `*global` to update known keys in that data instead of creating new global variables.\n\n- Avoid accidental global pollution:\n\n  - If a key does not exist in `data`, `*global` writes to `globalThis`.\n  - To keep the global namespace clean, predefine important keys in `data` so that updates go there instead.\n\n- Make expressions idempotent where possible:\n\n  - Because `*global` is re-evaluated on each render, design expressions so re-running them is safe.\n  - For example:\n\n    ```html\n    <div *global=\"config.ready = !!config.ready\"></div>\n    ```\n\n- Centralize complex logic:\n\n  - For non-trivial operations, call a named function instead of writing complex inline code:\n\n    ```js\n    function applyServerConfig(config){\n      // Complex logic here\n    }\n    ```\n\n    ```html\n    <div *global=\"applyServerConfig(config)\"></div>\n    ```\n\n- Use `$parent` when writing from nested components:\n\n  - When placed inside a nested Sercrod host, `*global` can still see `$parent`:\n\n    ```html\n    <serc-rod id=\"parent\" data='{\"state\": {\"count\": 0}}'>\n      <serc-rod id=\"child\">\n        <button *global=\"$parent.state.count++\">\n          Increment\n        </button>\n      </serc-rod>\n    </serc-rod>\n    ```\n\n    - Here, `$parent.state.count` in the child updates the parent's data.\n\n\n#### Additional examples\n\nUpdate an existing data key or create a global fallback:\n\n```html\n<serc-rod id=\"app\" data='{\"settings\": { \"theme\": \"light\" }}'>\n  <div *global=\"\n    if(settings.theme === 'light'){\n      settings.theme = 'dark';\n    }\n  \"></div>\n</serc-rod>\n```\n\nBridge with a global analytics object:\n\n```js\n// External script\nwindow.Analytics = {\n  trackPage(name){\n    console.log(\"Tracking page:\", name);\n  }\n};\n```\n\n```html\n<serc-rod id=\"page\" data='{\"title\": \"Home\"}'>\n  <div *global=\"Analytics.trackPage(title)\"></div>\n</serc-rod>\n```\n\nWrite root-level counters:\n\n```html\n<serc-rod id=\"app\" data='{\"counter\": 0}'>\n  <button\n    @click=\"counter++\"\n    *global=\"totalClicks = (totalClicks || 0) + 1\"\n  >\n    Click\n  </button>\n</serc-rod>\n```\n\n- `counter` is stored in Sercrod data.\n- `totalClicks` is created on `globalThis` (for example `window.totalClicks`) unless you declare it in `data` first.\n\n\n#### Notes\n\n- `*global` and `n-global` are aliases; they behave the same and differ only in attribute name.\n- The expression is executed as plain JavaScript inside a `with(scope){ ... }` block.\n- Writes follow this order:\n  - If the key already exists in Sercrod data (`_data`), update data.\n  - Otherwise, write to `globalThis`.\n- `*global` is evaluated before structural directives such as `*if`, `*switch`, `*each`, and `*for` on the same element.\n- The attribute is not removed after evaluation, so side effects happen on every render of that element.\n- There is no prohibition on combining `*global` with other directives on the same element, but you should remember that:\n  - `*global` runs even when later conditionals or structural directives skip rendering.\n  - Repeated structures can cause the expression to run many times.\n- For ordinary data flow within a component, prefer `*let` and direct assignments in bindings; reserve `*global` for shared state and interoperability with non-Sercrod code.\n",
  "if": "### *if\n\n#### Summary\n\n*if conditionally renders an element when the expression is truthy.\n*elseif and *else form a chain with a preceding *if so that exactly one branch is chosen.\nThe chain is defined across sibling elements and is evaluated from left to right.\n\nAliases:\n\n- *if and n-if are aliases.\n- *elseif and n-elseif are aliases.\n- *else and n-else are aliases.\n\nOnly one of the branches in a single chain is rendered; the others are skipped.\n\n\n#### Basic example\n\nA simple visible or hidden panel:\n\n```html\n<serc-rod id=\"app\" data='{\"show\": true}'>\n  <section *if=\"show\">\n    <h2>Panel</h2>\n    <p>This panel is visible when show is truthy.</p>\n  </section>\n</serc-rod>\n```\n\nWhen show is truthy, the section is rendered.\nWhen show becomes falsy, the section is not rendered at all.\n\n\n#### Behavior\n\n*if, *elseif, and *else work together as a chain on sibling elements.\n\nCore rules:\n\n- A chain starts at an element that has *if or n-if.\n- Following siblings that have *elseif, n-elseif, *else, or n-else belong to the same chain, as long as there are no non-conditional elements in between.\n- The chain ends when:\n  - A non-conditional element appears, or\n  - A new *if or n-if appears, which starts a new chain.\n\nFor each chain:\n\n1. Sercrod evaluates the branches from left to right.\n2. The first branch whose condition is truthy is selected.\n3. If no condition is truthy and there is an *else branch, that *else branch is selected.\n4. If no branch is selected (no truthy condition and no *else), nothing is rendered for this chain.\n\nInvalid chains:\n\n- A *elseif or *else that is not connected to a preceding *if in the same sibling group is ignored.\n  It does not render anything on its own.\n\nRendering result:\n\n- Only the chosen branch is cloned and rendered into the output.\n- All branches in the original chain serve as templates and are not directly appended to the DOM.\n\n\n#### Condition evaluation semantics\n\nThe expression on *if or *elseif is evaluated using Sercrod's expression evaluator with special truthiness rules:\n\n- If the evaluated value is exactly false, the condition is false.\n- If it is exactly true, the condition is true.\n- If it is null or undefined, the condition is false.\n- If it is a number:\n  - 0 or NaN are treated as false.\n  - Any other number is treated as true.\n- If it is a string:\n  - The string is trimmed and lowercased.\n  - An empty string, \"false\", \"0\", \"null\", and \"undefined\" are treated as false.\n  - Any other string is treated as true.\n- For other types, JavaScript Boolean semantics are used (Boolean(value)).\n\nIf evaluating the expression throws an error:\n\n- Sercrod falls back to a simple string check:\n  - If the raw expression text is exactly \"true\" (ignoring spaces and case), the condition is treated as true.\n  - If it is exactly \"false\", the condition is treated as false.\n- Otherwise, Sercrod warns (when error logging is enabled) and treats the condition as false.\n\nEmpty expressions:\n\n- If the attribute value is empty (for example *if=\"\" or missing), the condition is treated as false and the branch is never selected.\n\n\n#### Evaluation timing\n\nThe evaluation order around *if is:\n\n1. Host-level *let (non structural) is evaluated before structural directives:\n   - *let or n-let on the same element can prepare or adjust the scope before *if runs.\n2. The *if / *elseif / *else chain is detected and evaluated on sibling elements.\n   - If the current node is not the head of a chain, it delegates to the head.\n3. If no branch is selected, nothing in the chain is rendered and Sercrod returns.\n4. If a branch is selected:\n   - The chosen element is cloned.\n   - Conditional attributes (*if, n-if, *elseif, n-elseif, *else, n-else) and branch-level *let are removed from the clone.\n   - The clone is then rendered as a normal element, so other directives on it (such as *switch, *each, *for, attribute bindings, events, and so on) are applied in the usual order.\n\nEffectively:\n\n- Host-level *let runs before *if.\n- *if chaining decides which branch is used.\n- All remaining directives on the chosen branch are processed afterwards on the clone.\n\n\n#### Execution model\n\nConceptually, Sercrod processes *if chains like this:\n\n1. Starting from a given node, check whether it has any of *if, *elseif, *else, or their n- aliases.\n2. If so, find the head of the chain:\n   - If the current node has *if or n-if, it is the head.\n   - Otherwise, scan previous siblings to find the nearest *if or n-if, stopping when:\n     - A non-conditional element is encountered, or\n     - The beginning of the container is reached.\n3. If no *if is found, the current *elseif or *else is treated as an invalid chain and ignored.\n4. Only when processing the head element:\n   - Collect the chain by walking right through sibling elements that have *if, *elseif, or *else.\n   - Stop when hitting:\n     - A non-conditional element, or\n     - A new *if or n-if (which starts another chain).\n5. For each collected branch:\n   - Start with the effective scope that includes host-level *let.\n   - If the branch element has *let or n-let, create a branch scope that prototypes the parent scope and apply that *let into it.\n   - For an else branch:\n     - If no branch has been chosen yet, mark this branch as a candidate and break evaluation.\n   - For an if or elseif branch:\n     - Evaluate the condition using the branch scope.\n     - If truthy, choose this branch, remember its branch scope, and stop evaluating further branches.\n6. If no branch has been chosen:\n   - The entire chain renders nothing and returns.\n7. If a branch has been chosen:\n   - Clone the chosen element.\n   - Remove all conditional attributes and branch-level *let attributes from the clone.\n   - Render the clone with the chosen branch scope.\n   - The original chain elements remain only as template nodes.\n\nThis model keeps the chain logic compact and ensures exactly one branch (or none) appears in the final DOM.\n\n\n#### Variable creation and scope layering\n\n*if by itself does not create new variables.\nHowever, it interacts with *let in two ways:\n\n- Host-level *let:\n  - *let or n-let on the same element (prior to the chain) is applied once before structural directives.\n  - It can define or update variables in the effective scope for all directives, including *if and *switch.\n\n- Branch-level *let:\n  - Each branch in a chain can have its own *let or n-let.\n  - For each branch, Sercrod creates a branch scope that:\n    - Inherits from the current effective scope.\n    - Is populated with variables and changes from branch-level *let.\n  - Conditions for that branch are evaluated using this branch scope.\n  - If the branch is selected, the branch scope is used for rendering the chosen element.\n\nImportant points:\n\n- Branch *let only affects the branch in which it is declared.\n- Branch-level variables live inside the chosen branch and its descendants, not across the entire chain.\n- Special helpers such as $data, $root, and $parent are still available inside branch scopes.\n\n\n#### Parent access\n\n*if does not introduce any special parent references on its own.\n\nWithin a chosen branch:\n\n- You can access the current Sercrod host's data using the names defined on the host (for example state or items).\n- You can access root-level data through $root.\n- You can access the nearest ancestor Sercrod host's data through $parent.\n- All of these are injected by Sercrod's expression engine, not by *if itself.\n\nBranch-level *let can add or override variables on top of these, but does not remove access to parent data.\n\n\n#### Use with conditionals and loops\n\nCombining *if with loops and other structural directives is common and supported when they are placed thoughtfully.\n\nBasic patterns:\n\n- Gating a loop container:\n\n  ```html\n  <ul *if=\"items && items.length\" *each=\"item of items\">\n    <li>\n      <span *print=\"item.label\"></span>\n    </li>\n  </ul>\n  ```\n\n  - If items is falsy or empty, the whole list is not rendered.\n  - When the condition passes, the body of *each runs as usual on the chosen branch.\n\n- Gating a repeated element:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\" *if=\"item.visible\">\n      <span *print=\"item.label\"></span>\n    </li>\n  </ul>\n  ```\n\n  - The *if is evaluated first on the host element.\n  - When *if passes, the chosen branch is cloned and then *for runs on that clone.\n  - If *if fails, the element does not participate in the loop.\n\n- Using *if inside a loop body:\n\n  ```html\n  <ul *each=\"item of items\">\n    <li>\n      <span *if=\"item.visible\" *print=\"item.label\"></span>\n      <span *else>Hidden</span>\n    </li>\n  </ul>\n  ```\n\n  - In this pattern, *if, *elseif, and *else form small chains inside the loop body.\n  - The chain is evaluated separately for each iteration.\n\n*if and *switch:\n\n- A chosen *if branch may still contain *switch, *each, *for, and other structural directives.\n- The chosen element is rendered again as a normal node, so *switch will run on it after *if has selected the branch.\n\n\n#### Use with templates, include, and import\n\n*if works naturally with *template, *include, and *import.\n\nTypical combinations:\n\n- Conditional include:\n\n  ```html\n  <div *if=\"user\" *include=\"'user-card'\"></div>\n  ```\n\n  - *if chooses whether the div participates.\n  - On the chosen branch, *include replaces the inner content with the template content.\n\n- Conditional import:\n\n  ```html\n  <section *if=\"logged_in\" *import=\"'dashboard-block'\"></section>\n  ```\n\n  - *if controls whether the section is rendered.\n  - *import then fetches and injects HTML into the section.\n  - After *import sets node.innerHTML and removes itself, Sercrod continues rendering so any *if directives inside the imported HTML are processed.\n\n- Conditions inside templates:\n\n  ```html\n  <script type=\"application/sercrod-partial\">\n    partial.data = {\n      title: \"User list\",\n      description: \"A list with conditional badges.\",\n      terminator: null\n    };\n  </script>\n\n  <template *template=\"'user-row'\">\n    <tr>\n      <td>{{ name }}</td>\n      <td *if=\"is_admin\">Admin</td>\n      <td *else>Regular</td>\n    </tr>\n  </template>\n  ```\n\n  - The *if/*else chain lives inside the template and is evaluated each time the template is rendered.\n\nRestrictions:\n\n- *if does not have any special prohibition with *include or *import on the same element.\n- The main structural restriction for *if is within its own chain:\n  - *elseif and *else must have a valid preceding *if in the same sibling group.\n  - Otherwise they are ignored.\n\n\n#### Best practices\n\n- Keep conditions simple:\n\n  - Prefer short, readable expressions such as mode === \"edit\".\n  - Move complex logic into data or helper functions that return simple booleans.\n\n- Use chains for clear branching:\n\n  - Use *if, *elseif, *else chains for mutually exclusive branches instead of stacking multiple separate *if elements.\n\n  ```html\n  <p *if=\"mode === 'view'\">Viewing</p>\n  <p *elseif=\"mode === 'edit'\">Editing</p>\n  <p *else>Unknown mode</p>\n  ```\n\n- Use branch-level *let for local derivations:\n\n  ```html\n  <div *if=\"user\" *let=\"full = user.first_name + ' ' + user.last_name\">\n    <p>Hello, {{ full }}</p>\n  </div>\n  ```\n\n  - Branch *let is a good place to compute display-specific values without polluting global data.\n\n- Avoid relying on subtle truthiness of strings:\n\n  - Remember that empty string and several special literals (\"false\", \"0\", \"null\", \"undefined\") are treated as false.\n  - If you want to be explicit, use clear conditions such as status === \"ok\".\n\n- Keep chains contiguous:\n\n  - Place *if, *elseif, and *else on consecutive sibling elements without unrelated elements in between.\n  - Inserting a non-conditional element breaks the chain and can make trailing *elseif or *else invalid.\n\n- Do not expect *else to render by itself:\n\n  - *else only makes sense as part of a chain.\n  - Without a preceding *if, it is ignored.\n\n\n#### Additional examples\n\nToggling content based on numeric thresholds:\n\n```html\n<serc-rod id=\"score-app\" data='{\"score\": 72}'>\n  <p *if=\"score >= 80\">Great job!</p>\n  <p *elseif=\"score >= 50\">Good effort.</p>\n  <p *else>Keep trying.</p>\n</serc-rod>\n```\n\nChecking multiple flags:\n\n```html\n<serc-rod id=\"flags\" data='{\"is_guest\": false, \"is_admin\": true}'>\n  <p *if=\"is_admin\">Administrator view</p>\n  <p *elseif=\"is_guest\">Guest view</p>\n  <p *else>Standard user view</p>\n</serc-rod>\n```\n\nUsing *if to guard a costly block:\n\n```html\n<serc-rod id=\"lazy\" data='{\"show_details\": false}'>\n  <button @click=\"show_details = !show_details\">\n    Toggle details\n  </button>\n\n  <section *if=\"show_details\">\n    <h2>Details</h2>\n    <p>Only rendered when needed.</p>\n  </section>\n</serc-rod>\n```\n\n\n#### Notes\n\n- *if, *elseif, and *else form a sibling-based chain; only one branch is rendered.\n- The n- prefixed forms (n-if, n-elseif, n-else) are aliases intended to help with HTML validators that dislike asterisk-prefixed attributes.\n- Conditions are evaluated with normalized truthiness:\n  - False, null, undefined, 0, NaN, empty string, \"false\", \"0\", \"null\", and \"undefined\" are treated as false.\n  - True and other values follow JavaScript Boolean semantics.\n- Branch-level *let is evaluated per branch and only affects that branch's scope.\n- *elseif and *else without a valid preceding *if in the same sibling group are ignored.\n- *if can be combined with other structural directives such as *for, *each, *switch, *include, and *import, as long as you remember that:\n  - Host-level *let and *if run before other structural directives on the chosen branch.\n  - The chosen branch is rendered as a normal element afterwards, so its remaining directives are processed in the usual order.\n",
  "import": "### *import\n\n#### Summary\n\n`*import` loads HTML from an external URL and injects it into the current element’s `innerHTML`.\nThe imported HTML is then processed by Sercrod in the same render pass, so any directives inside the imported content (`*if`, `*for`, `*each`, `*include`, `*template`, and so on) are evaluated as usual.\n\nKey points:\n\n- `*import` and `n-import` are aliases.\n- The directive resolves a URL string, fetches HTML with a synchronous XMLHttpRequest, caches the response per URL, and replaces the host’s inner content.\n- Infinite or deeply nested `*include` / `*import` chains are guarded by a depth limit.\n- A single element must not combine `*import` with `*each` or `*include`. Putting multiple structural directives on the same host is unsupported and leads to undefined behavior.\n\n\n#### Basic example\n\nLoad a simple partial file:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\"}}'>\n  <section *import=\"'/partials/user-card.html'\"></section>\n</serc-rod>\n```\n\nBehavior:\n\n- Sercrod evaluates the expression `'/partials/user-card.html'` and resolves it to a URL string.\n- It performs a synchronous HTTP request to that URL (with optional configuration).\n- If the request succeeds and returns non-empty HTML, Sercrod assigns that HTML to `section.innerHTML`.\n- Sercrod then continues rendering the `<section>` subtree, so any directives inside `user-card.html` are processed in the same pass.\n\n\n#### Behavior\n\nAt a high level, `*import` does the following when encountered on an element:\n\n1. It resolves a URL from the attribute value, using expression evaluation and simple heuristics.\n2. It checks the current include/import nesting depth and refuses to exceed a configured `max_depth`.\n3. It performs a synchronous HTTP request to download HTML from the resolved URL.\n4. If HTML is successfully obtained, it replaces the element’s `innerHTML` with the fetched HTML.\n5. It removes `*import` / `n-import` from the element.\n6. It does not return early after filling `innerHTML` so that normal child rendering (including directives inside the imported HTML) can run immediately.\n\nAlias:\n\n- `*import` and `n-import` behave identically.\n- Use one style consistently across your project.\n\n\n#### URL resolution\n\nThe value of `*import` is interpreted as a URL in two steps:\n\n1. **Expression evaluation**\n\n   Sercrod first tries to evaluate the raw attribute value as an expression:\n\n   - It calls `eval_expr(raw_text, scope, { el: node, mode: \"import\", quiet: true })`.\n   - If the evaluation result is not `null` or `undefined`, Sercrod converts it to a string and trims it.\n   - If the trimmed string is non-empty, that string becomes the URL.\n\n   This allows patterns such as:\n\n   ```html\n   <serc-rod id=\"app\" data='{\"base\":\"/partials\",\"name\":\"user-card.html\"}'>\n     <section *import=\"base + '/' + name\"></section>\n   </serc-rod>\n   ```\n\n2. **Fallback to raw text**\n\n   If expression evaluation does not yield a usable string, Sercrod falls back to the raw attribute text and checks whether it “looks like a URL”:\n\n   - The raw text must not contain whitespace.\n   - If it starts with `http://` or `https://`, or\n   - If it starts with `./`, `../`, or `/`, or\n   - If it contains a dot `.` or a slash `/`,\n\n   then the raw text is treated as the URL.\n\nIf neither step produces a non-empty URL string, or the URL becomes the literal `\"false\"`, the import is treated as invalid:\n\n- The element’s `*import` / `n-import` attributes are removed.\n- Depending on configuration, Sercrod may mark the element with `sercrod-import-invalid`.\n- The element may or may not be kept in the DOM, depending on `remove_element_if_empty` (see configuration below).\n\n\n#### Network Loading\n\nOnce a URL is resolved, `*import` uses a synchronous XMLHttpRequest to fetch HTML:\n\n- It reads `this.constructor._config.import` (if present) to configure the request:\n\n  - `method`: HTTP method, default is `\"GET\"`.\n  - `credentials`: boolean, default is `false`. If `true`, `xhr.withCredentials` is set.\n  - `headers`: plain object with additional HTTP headers.\n\n- Responses are cached per URL in a class-level map:\n\n  - If a cached entry exists and is non-empty, Sercrod skips the network request and uses the cached HTML.\n  - If the cache is empty for that URL, Sercrod performs a network request and, on success, stores the response text.\n\nError handling:\n\n- If the HTTP status code is not in the `2xx` range, Sercrod does not cache the response and may mark the element with `sercrod-import-error=\"<status>\"`, depending on `warn_on_element`.\n- If an exception occurs during the request, Sercrod may mark the element with `sercrod-import-error=\"exception\"`, again depending on configuration.\n- If no HTML is obtained (empty string), the import is considered failed:\n  - `*import` / `n-import` are removed.\n  - The element may be kept or dropped based on `remove_element_if_empty` (see below).\n\nNote:\n\n- Because `*import` uses a synchronous request, it can block the main thread during network I/O.\n- For large or remote resources, consider server-side rendering, static generation, or pre-fetching strategies instead of heavy runtime imports.\n\n\n#### Depth management and recursion guard\n\n`*import` shares its depth tracking with `*include`:\n\n- Sercrod maintains a numeric “include/import depth” in an internal WeakMap.\n- Each `*include` or `*import` increments the depth relative to the nearest ancestor `*include`/`*import`.\n\nConfiguration:\n\n- `this.constructor._config.include.max_depth` (or an internal `_include_max_depth`) limits the allowed depth.\n- If a `*import` would exceed this `max_depth`:\n\n  - When `warn_on_element` is true, the element is marked with `sercrod-import-depth-overflow=\"<max_depth>\"`.\n  - The `*import` / `n-import` attributes are removed.\n  - Depending on configuration, the element may be appended in its current (unfilled) state, or import is simply skipped.\n\nPractical meaning:\n\n- You can safely use nested includes and imports, but extremely deep or cyclic chains will be stopped.\n- This prevents infinite recursion between templates that import or include each other.\n\n\n#### Evaluation timing\n\nWithin the host rendering pipeline, `*import` runs after template registration and `*include`, but before literal-only blocks:\n\n- `*template` on the same element is processed first.\n  - If an element has `*template`, it is treated as a template declaration and is not rendered; `*import` on the same element will effectively never run.\n- `*include` is handled before `*import`.\n- `*import` then resolves and injects HTML into the element’s `innerHTML`.\n- After that, Sercrod continues normal child rendering, which means:\n  - Directives inside the imported HTML are evaluated in the same pass.\n  - Binding directives, event handlers, and nested loops inside the imported content work as expected.\n\nThe `*import` directive itself is one-time per render pass:\n\n- Once processed, `*import` / `n-import` are removed, so the same element will not re-import content during the same render cycle.\n- Re-imports only occur if the surrounding Sercrod host re-renders from scratch or if the element is re-created.\n\n\n#### Execution model\n\nThe internal steps for `*import` are roughly:\n\n1. **Depth and configuration**\n\n   - Read include-related config (`max_depth`, `warn_on_element`, `remove_element_if_empty`).\n   - Compute this element’s depth based on the nearest ancestor `*include` / `*import`.\n\n2. **Depth check**\n\n   - If depth exceeds `max_depth`, handle overflow (mark the element if configured, remove directive, optionally keep the element) and stop.\n\n3. **Resolve raw text**\n\n   - Read `*import` / `n-import` as `raw_text`.\n   - If `raw_text` is empty, remove the attribute and stop.\n\n4. **Resolve URL**\n\n   - Attempt expression evaluation for `raw_text`.\n   - If that yields no usable string, treat `raw_text` as a potential URL and apply simple heuristics.\n   - If no URL can be chosen or the result is `\"false\"`, treat import as invalid, remove the directive, and stop (optionally marking the element).\n\n5. **Fetch HTML**\n\n   - Use class-level cache for the URL.\n   - If not cached yet, perform a synchronous HTTP request with configured method, headers, and credentials.\n   - If response status is `2xx`, treat `responseText` as HTML and cache it.\n   - On error or exception, optionally mark the element, then stop if there is no HTML.\n\n6. **Inject HTML and clean up**\n\n   - Assign `node.innerHTML = html`.\n   - Remove `*import` / `n-import` attributes from the element.\n   - Do not return here; instead, let the renderer continue to process the new children so that directives inside the imported HTML are executed.\n\n\n#### Scope and interaction with imported HTML\n\n`*import` does not create new variables or new scope layers by itself:\n\n- The imported HTML is treated as if it had been written inside the element from the start.\n- Directives inside the imported content see the same scope as any other child of the host element:\n\n  - They can access host data (for example `user`, `items`, `state`).\n  - They can access `$data`, `$root`, `$parent`, and any injected methods.\n\nIf you want imported content to have a specific scope:\n\n- Place `*import` on an element that already has the right data and context.\n- Or combine import with `*let` or `*stage` on the same Sercrod host (but not on the same element if the directives would conflict structurally).\n\n`*import` itself does not inject any new names into the scope.\n\n\n#### Use with *template, *include, and *each\n\n`*import` is one of several structural directives that take control of a host element’s children:\n\n- `*template` registers a template and skips rendering the original element.\n- `*include` resolves a template name and replaces the host’s `innerHTML` with the template’s body.\n- `*import` fetches HTML from a URL and replaces the host’s `innerHTML` with the fetched content.\n- `*each` transforms the host’s children into a loop body.\n\nBecause they all try to own the same inner content, there are important restrictions:\n\n1. **`*import` with `*template` on the same element**\n\n   - In the current implementation, `*template` is processed first and returns early.\n   - That means `*import` on the same element never runs.\n   - Treat this combination as unsupported; pick one or separate the responsibilities into different elements.\n\n2. **`*import` with `*include` on the same element**\n\n   - `*include` and `*import` both want to replace the host’s `innerHTML`.\n   - The runtime does not merge their behaviors.\n   - Putting both on the same element is unsupported and leads to undefined behavior.\n   - Use either `*include` (for local templates) or `*import` (for external HTML), but not both on one tag.\n\n3. **`*import` with `*each` on the same element**\n\n   - `*each` expects to use the host’s original children as a loop body.\n   - `*import` wants to overwrite those children with imported HTML.\n   - The implementation does not coordinate these operations.\n   - Therefore, a single element must not combine `*each` and `*import`. This combination is explicitly unsupported and should be avoided.\n\nSupported pattern examples:\n\n- `*import` on a container element, then use loops inside the imported HTML:\n\n  ```html\n  <section *import=\"'/partials/user-list.html'\"></section>\n  ```\n\n- `*each` on the container and `*import` on a child:\n\n  ```html\n  <ul *each=\"user of users\">\n    <li *import=\"'/partials/user-row.html'\"></li>\n  </ul>\n  ```\n\n  Here:\n\n  - `<ul>` is the loop container.\n  - `<li>` is the template body per iteration.\n  - The imported snippet can freely use `user` from the loop scope.\n\n- `*import` to bring in a block that defines or uses `*template` inside:\n\n  ```html\n  <section *import=\"'/partials/templates.html'\"></section>\n  ```\n\n  The imported HTML may register templates with `*template`, which can be used later by `*include` elsewhere.\n\n\n#### Configuration\n\n`*import` reads settings from the Sercrod constructor’s config object:\n\n- `this.constructor._config.include`:\n\n  - `max_depth`: maximum allowed depth for nested `*include` / `*import` (default is 16 if not overridden).\n  - `warn_on_element`: if true, errors related to include/import depth or URL resolution are recorded as attributes on the element (for example `sercrod-import-depth-overflow`, `sercrod-import-invalid`, `sercrod-import-error`).\n  - `remove_element_if_empty`: if true, elements whose include/import fails may be omitted entirely instead of being kept as empty placeholders.\n\n- `this.constructor._config.import`:\n\n  - `method`: HTTP method used for import requests (default `\"GET\"`).\n  - `credentials`: boolean, mapped to `xhr.withCredentials`.\n  - `headers`: object with additional headers, applied via `xhr.setRequestHeader`.\n\nThese configuration values are optional; if you do not set them, Sercrod uses sensible defaults.\n\n\n#### Best practices\n\n- Prefer `*include` for in-document templates:\n\n  - When your content already lives in the same document, `*template` plus `*include` is usually simpler and faster than `*import`.\n\n- Use `*import` for coarse-grained external HTML:\n\n  - Import whole blocks or sections (cards, lists, layout pieces) rather than tiny fragments.\n  - Each `*import` implies a synchronous request when not cached.\n\n- Keep URLs simple and explicit:\n\n  - Use quoted string literals or short expressions.\n  - If you need to encode multiple pieces of information (for example file and section name), do it in the URL and let the server interpret it; Sercrod does not parse `\":name\"` postfixes in any special way.\n\n- Avoid heavy or large imports during interaction:\n\n  - Because `*import` is synchronous, it can introduce noticeable pauses for remote or slow resources.\n  - For interactivity, prefer data-driven updates, JSON APIs, or pre-rendered HTML instead of repeated imports.\n\n- Respect structural restrictions:\n\n  - Do not put `*import` on the same element as `*each` or `*include`.\n  - Avoid combining `*import` and `*template` on the same element; only `*template` will take effect.\n\n\n#### Additional examples\n\nDynamic URL based on data:\n\n```html\n<serc-rod id=\"app\" data='{\"lang\":\"en\",\"page\":\"about\"}'>\n  <main *import=\"`/pages/${lang}/${page}.html`\"></main>\n</serc-rod>\n```\n\nUsing headers and credentials via config (conceptual sketch):\n\n```js\nSercrod._config = Sercrod._config || {};\nSercrod._config.import = {\n  method: \"GET\",\n  credentials: true,\n  headers: {\n    \"X-Requested-With\": \"XMLHttpRequest\"\n  }\n};\n```\n\nThen:\n\n```html\n<section *import=\"'/secure/partial.html'\"></section>\n```\n\n\n#### Notes\n\n- `*import` and `n-import` are aliases.\n- The directive uses Sercrod’s expression sandbox for URL resolution but ultimately treats the resolved value as a plain string.\n- Sercrod does not implement any special parsing for suffixes like `:card` in URLs. Such patterns are treated as part of the URL string and must be interpreted by your server if you use them.\n- Infinite include/import recursion is prevented by a configurable depth limit that applies to both `*include` and `*import`.\n- Structural combinations where multiple directives try to control the same host’s children (such as `*import` plus `*each`, or `*import` plus `*include`) are not supported and should be considered invalid usage.\n",
  "include": "### *include\n\n#### Summary\n\n`*include` injects the inner content of a named template into the current element.\nThe host element stays in the DOM as a single wrapper, and only its `innerHTML` is replaced.\nIt has an alias `n-include`.\n\n`*include` works with templates declared by `*template` (or `n-template`) in the current Sercrod world or ancestor worlds.\nAfter injecting the template content, Sercrod continues to process that content as normal children (conditions, loops, bindings, events, and so on).\n\nImportant structural restriction:\n\n- A single element must not combine `*include` with `*each` or `*import`.\n  These directives all want to control the host’s children, so they are not allowed on the same element.\n\n\n#### Basic example\n\nA local template and an include:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\",\"role\":\"admin\"}}'>\n\n  <!-- Template declaration -->\n  <template *template=\"user-card\">\n    <article class=\"user-card\">\n      <h2 *print=\"user.name\"></h2>\n      <p *print=\"user.role\"></p>\n    </article>\n  </template>\n\n  <!-- Include: insert the template here -->\n  <section>\n    <div *include=\"user-card\"></div>\n  </section>\n\n</serc-rod>\n```\n\nBehavior:\n\n- The `<template *template=\"user-card\">` defines a reusable template named `user-card`.\n- The `<div *include=\"user-card\">` replaces its `innerHTML` with the inner HTML of the template.\n- The resulting DOM under `<div>` is equivalent to:\n\n  ```html\n  <div>\n    <article class=\"user-card\">\n      <h2>Alice</h2>\n      <p>admin</p>\n    </article>\n  </div>\n  ```\n\n- The included content is then rendered with the same scope as the `*include` host, so `user.name` and `user.role` are available inside the template body.\n\n\n#### Behavior\n\nConceptually, `*template` and `*include` have these roles:\n\n- `*template`:\n  - Registers a reusable template under a string name.\n  - Stores a deep clone of the original element as the template prototype.\n  - The template prototype is not rendered by itself.\n\n- `*include`:\n  - Finds a registered template by name.\n  - Copies the template prototype’s `innerHTML` into the host element.\n  - Leaves the host element itself in place.\n  - Removes `*include` / `n-include` from the rendered element so children can be processed normally.\n\nKey points:\n\n- `*include` copies only the template’s `innerHTML`, not the template element itself.\n  - If the template is `<template *template=\"card\"> ... </template>`, the content between the tags is inserted.\n  - If the template is `<div *template=\"card\"> ... </div>`, only the children of that `<div>` are inserted.\n- Included content is rendered as if it had been written directly inside the `*include` host.\n\n\n#### Template name and expression syntax\n\nThe value of `*include` is a Sercrod expression that must resolve to a template name string.\n\nTypical patterns:\n\n- Direct string literal:\n\n  ```html\n  <div *include=\"'user-card'\"></div>\n  ```\n\n- Bare identifier resolved from data:\n\n  ```html\n  <serc-rod data='{\"currentTemplate\":\"user-card\"}'>\n    <div *include=\"currentTemplate\"></div>\n  </serc-rod>\n  ```\n\n- Expression that computes a name:\n\n  ```html\n  <div *include=\"isAdmin ? 'admin-card' : 'user-card'\"></div>\n  ```\n\nResolution rules (as implemented by `_resolve_template_name`):\n\n1. Sercrod first tries to evaluate the raw text as an expression in the current scope, in `include` mode.\n   - If evaluation succeeds and yields a non-empty string, that string is used as the template name.\n   - Errors such as `ReferenceError` are suppressed (quiet mode), so they do not print console warnings.\n\n2. If expression evaluation does not yield a usable name:\n   - If the raw text looks like a simple identifier (letters, digits, underscore, hyphen), Sercrod uses it directly as the name.\n   - Otherwise, the name resolution fails.\n\n3. If the final name is empty or no template with that name exists in any accessible world, `*include` fails and behaves as described in the error handling section below.\n\nPractical guidance:\n\n- Prefer explicit string literals (`'card'`) or data-bound strings (`currentTemplate`) for clarity.\n- Avoid relying on implicit identifier fallbacks unless you really want to treat the raw attribute text as the template name.\n\n\n#### Template lookup and worlds\n\nTemplates are stored per Sercrod world:\n\n- When you declare `*template=\"name\"` inside a `<serc-rod>` host, Sercrod saves a deep clone of that element as a template prototype in that host’s world.\n- `*include` searches for the template name in two stages:\n  1. The current world’s registry (`this._template_registry`) if it exists.\n  2. Parent worlds via `_lookupTemplateNear(name)`:\n     - Sercrod climbs the chain of Sercrod instances (worlds) upward.\n     - The first world that has a matching template returns its prototype.\n\nEffect:\n\n- Templates are local to a world, but nested worlds can reuse templates from ancestors.\n- You can define shared templates in an outer layout and include them from inner components.\n\n\n#### Evaluation timing\n\nWithin the rendering pipeline, `*include` is evaluated after `*template` registration and before normal child rendering of the host:\n\n- First, `*template` or `n-template` on elements is processed and registered.\n- Then, for each node with `*include` or `n-include`:\n  1. Sercrod resolves the template name.\n  2. If successful, it sets `node.innerHTML` to the template prototype’s `innerHTML`.\n  3. It removes `*include` / `n-include` from the rendered element.\n- After that, Sercrod proceeds to render the children, which now consist of the included content.\n\nImportant:\n\n- The included content is processed in the same evaluation cycle as the host.\n- Any directives inside the template body (`*if`, `*for`, `*each`, bindings, event handlers) are evaluated after the include has taken effect.\n\n\n#### Execution model\n\nAt a high level, the implementation behaves as follows for a node `node` with `*include` or `n-include`:\n\n1. Determine include depth:\n   - Sercrod uses an internal map `_include_depth_map` plus `_get_nearest_include_depth(node)` to compute the nesting depth of this `*include` relative to surrounding `*include` and `*import` directives.\n   - If the depth exceeds the configured maximum (`config.include.max_depth`, default 16):\n     - If `config.include.warn_on_element` is `true`, Sercrod marks the rendered element with `sercrod-include-depth-overflow=\"<max_depth>\"`.\n     - It removes `*include` / `n-include` from the rendered element.\n     - The include is skipped to prevent infinite recursion.\n\n2. If the `*include` value is empty:\n   - Sercrod removes `*include` / `n-include` from the source node.\n   - No template is inserted.\n\n3. Resolve the template name:\n   - Sercrod calls `_resolve_template_name(raw_text, scope, {el: node, mode: \"include\"})`.\n   - If name resolution fails (empty string):\n     - If `this.error?.warn` is enabled:\n       - Optionally marks the source node with `sercrod-template-not-found=\"<raw_text>\"` when `config.include.warn_on_element` is `true`.\n       - Removes `*include` / `n-include` from the source node.\n     - The include is skipped.\n\n4. Find the template:\n   - If the current world has a template registry and it contains `name`, that template is used.\n   - Otherwise, `_lookupTemplateNear(name)` searches parent worlds.\n   - If no template is found anywhere:\n     - If `this.error?.warn` is enabled:\n       - Optionally marks the rendered element with `sercrod-template-not-found=\"<name or raw_text>\"` when `config.include.warn_on_element` is `true`.\n       - Removes `*include` / `n-include` from the rendered element.\n       - If `config.include.remove_element_if_empty` is false, the host element is still appended, potentially empty.\n     - The include is skipped.\n\n5. Inject the template content:\n   - If a template is found, Sercrod obtains its prototype `proto`.\n   - It sets `node.innerHTML = proto.innerHTML || \"\"`.\n   - It removes `*include` / `n-include` from the rendered element.\n   - It does not return early at this point: normal child rendering continues, now processing the included children.\n\nThis model keeps `*include` simple and predictable: name resolution, template lookup, then direct HTML injection followed by a standard child-rendering pass.\n\n\n#### Variable creation and scope layering\n\n`*include` does not create new variables or a new data layer.\n\n- The included template body is rendered in the same scope as the `*include` host.\n- All variables and helpers that are visible at the host are also visible in the included content:\n  - Data bound to `<serc-rod>` (for example `user`, `items`, `state`).\n  - Special helpers such as `$data`, `$root`, `$parent`.\n  - Methods defined by `*methods` or similar configuration.\n\nIf the template itself defines additional directives (for example `*let`):\n\n- Those directives behave exactly as if they had been written directly inside the host.\n- Any variables created by `*let` inside the template body are scoped to the template body and shadow outer variables with the same name.\n\nGuidelines:\n\n- Think of `*include` as a textual template expansion that runs in the current scope.\n- Do not expect an isolated component-like scope; for that you can nest a new `<serc-rod>` block inside the template.\n\n\n#### Parent access\n\nBecause `*include` does not introduce a new world or host, parent data access works in the usual way:\n\n- `$root` refers to the root data of the nearest Sercrod host.\n- `$parent` refers to the data of the parent Sercrod world if you are inside a nested Sercrod.\n- Any plain data objects (for example `state`, `config`) are referenced exactly as in normal templates.\n\nThe only change is that the HTML you wrote under `*template` is now located under the `*include` host at render time.\n\n\n#### Use with conditionals and loops\n\n`*include` is designed to cooperate with `*if`, `*for`, and `*each`, as long as they are on different structural layers.\n\nHost-level condition:\n\n- You can guard an include with `*if` on the same element:\n\n  ```html\n  <div *if=\"user\" *include=\"'user-card'\"></div>\n  ```\n\n- Evaluation order:\n  - Host-level `*if` is processed before `*include`.\n  - If the condition is falsy, the element is not rendered at all, and `*include` is never evaluated.\n\nHost-level loops:\n\n- `*for` on the same element as `*include` is allowed and repeats the host:\n\n  ```html\n  <ul>\n    <li *for=\"user of users\" *include=\"'user-item'\"></li>\n  </ul>\n  ```\n\n- For each iteration of `*for`:\n  - Sercrod evaluates `*include`, injects the `user-item` template into the `<li>`, and then renders its children.\n  - The template body can use the loop variable `user`.\n\nChild-level condition and loops:\n\n- Inside included content, you can freely use `*if`, `*for`, and `*each`:\n\n  ```html\n  <template *template=\"user-item\">\n    <article>\n      <h2 *print=\"user.name\"></h2>\n      <ul *each=\"tag of user.tags\">\n        <li *print=\"tag\"></li>\n      </ul>\n    </article>\n  </template>\n  ```\n\n- From the point of view of these directives, there is no difference between inline markup and markup that arrived via `*include`.\n\n\n#### Relationship with *import\n\n`*import` is the I/O counterpart of `*include`:\n\n- `*include`:\n  - Uses templates that are already registered in the current page via `*template`.\n  - Works only with in-memory templates.\n\n- `*import`:\n  - Resolves a URL from its expression (or from the literal attribute text if it looks like a URL).\n  - Fetches HTML using a synchronous `XMLHttpRequest`, with optional configuration from `config.import`:\n    - `method` (default `\"GET\"`).\n    - `credentials` (boolean, for `withCredentials`).\n    - `headers` (object of request headers).\n  - Caches HTML per URL on the class in `_import_cache`.\n  - On success, sets `node.innerHTML` to the fetched HTML, then removes `*import` / `n-import`.\n  - On failure, can mark the rendered element with `sercrod-import-error` and either keep or drop the host based on `config.include.remove_element_if_empty`.\n\nFor the injected HTML, the behavior is intentionally parallel:\n\n- Both `*include` and `*import` end up setting `node.innerHTML` and then letting the standard child rendering logic process the result.\n\n\n#### Structural restrictions\n\nBecause `*include`, `*each`, and `*import` all want to control the host’s children, there are intentional restrictions:\n\n- `*include` and `*each` must not appear on the same element:\n\n  - `*each` treats the host’s original children as the loop body and repeats them.\n  - `*include` overwrites the host’s `innerHTML` with template content.\n  - Combining them on one element produces undefined behavior and is not supported.\n  - Officially, Sercrod does not allow a single element to have both `*each` and `*include`.\n\n- `*include` and `*import` must not appear on the same element:\n\n  - Both want to provide the entire `innerHTML` for the host.\n  - The runtime does not merge these operations.\n  - A single element must not declare both directives.\n\n- `*include` and `*template` on the same element:\n\n  - If both are written, the `*template` logic runs first, registers the template, and returns without rendering the host.\n  - The `*include` on that element is effectively ignored.\n  - In practice, this combination is useless and should be avoided.\n\nRecommended patterns:\n\n- If you want to repeat a template, put `*each` on a container and `*include` (or `*import`) on a child element:\n\n  ```html\n  <ul *each=\"user of users\">\n    <li *include=\"'user-item'\"></li>\n  </ul>\n  ```\n\n- Or put `*include` / `*import` on a container, and let the imported template contain its own loops.\n\n\n#### Comparison: *include vs *import\n\nFrom the template system’s point of view:\n\n- `*include`:\n\n  - Source: a named template in the registry (`*template`).\n  - Input: a template name (expression or identifier).\n  - Transport: in-memory, no network.\n  - Effect: copy `proto.innerHTML` into `node.innerHTML`, then render children.\n  - Safety: depth is limited by `include.max_depth` to avoid infinite include loops.\n\n- `*import`:\n\n  - Source: external HTML content (file, endpoint, or module).\n  - Input: an URL (expression or URL-like string).\n  - Transport: synchronous HTTP (XMLHttpRequest) with a per-class cache.\n  - Effect: set `node.innerHTML` to the received HTML string, then render children.\n  - Safety: shares the same depth tracking (`_include_depth_map` and `include.max_depth`) to avoid recursive import chains.\n\nTypical usage patterns:\n\n- Use `*include` when:\n  - The template is defined in the same document or in a Sercrod world that is already in memory.\n  - You want predictable, purely in-memory reuse.\n\n- Use `*import` when:\n  - You want to load HTML from another file or service.\n  - That HTML may contain `*template` declarations, reusable snippets, or complete fragments.\n  - After the import, you can use `*include` to consume any templates defined in the imported content.\n\nThe string passed to `*import` is treated purely as an URL from Sercrod’s perspective.\nIf you use patterns like `\"/partials/card.html:card\"`, the `:card` part is just part of the URL and is not parsed specially by Sercrod itself.\nAny such semantics (for example, serving only one named fragment from a combined file) must be implemented on the server side.\n\n\n#### Best practices\n\n- Prefer explicit names:\n  - Use quoted literals (`'user-card'`) or string-valued data properties for template names.\n  - This makes it clear which template is expected and avoids surprises when refactoring.\n\n- Keep templates small and focused:\n  - Use `*template` for logical blocks (cards, list items, table rows), not for entire pages.\n  - Compose pages by including a few well-defined templates.\n\n- Avoid deep include chains:\n  - Although Sercrod has a depth guard (`config.include.max_depth`, default 16), very deep nesting is harder to reason about.\n  - If you see `sercrod-include-depth-overflow`, review your include structure.\n\n- Use worlds to structure templates:\n  - Define shared templates in outer layouts and local templates closer to the components that use them.\n  - Let world-based lookup resolve the nearest appropriate template.\n\n- Do not mix conflicting structural directives:\n  - Do not place `*include`, `*each`, and `*import` on the same element.\n  - Keep the responsibility of each element clear.\n\n\n#### Additional examples\n\nDynamic template selection:\n\n```html\n<serc-rod data='{\n  \"mode\": \"compact\",\n  \"cards\": [1,2,3]\n}'>\n  <template *template=\"card-compact\">\n    <div class=\"card compact\">\n      <p>Compact card</p>\n    </div>\n  </template>\n\n  <template *template=\"card-full\">\n    <div class=\"card full\">\n      <p>Full card with details</p>\n    </div>\n  </template>\n\n  <section *for=\"id of cards\">\n    <div *include=\"mode === 'compact' ? 'card-compact' : 'card-full'\"></div>\n  </section>\n</serc-rod>\n```\n\nUsing a template as a partial inside a layout:\n\n```html\n<serc-rod data='{\"user\":{\"name\":\"Alice\"}}'>\n\n  <template *template=\"layout\">\n    <header>\n      <h1>Sercrod demo</h1>\n    </header>\n    <main>\n      <!-- Slot for content -->\n      <div class=\"content\">\n        <!-- This area will be further filled by other includes or inline markup -->\n      </div>\n    </main>\n    <footer>\n      <small>Footer</small>\n    </footer>\n  </template>\n\n  <div class=\"page\" *include=\"'layout'\">\n    <!-- After include, the header/main/footer structure appears here -->\n  </div>\n\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*include` and `n-include` are aliases; choose one style per project for consistency.\n- `*include` uses Sercrod’s expression evaluator in `include` mode and suppresses JavaScript-level reference errors.\n- Template lookup is world-aware:\n  - The current Sercrod world is searched first.\n  - Parent worlds are searched next via `_lookupTemplateNear`.\n- Depth and error diagnostics:\n  - Excessive nesting of `*include` and `*import` can be flagged with `sercrod-include-depth-overflow` or `sercrod-import-depth-overflow` on the rendered element.\n  - Missing templates can be flagged with `sercrod-template-not-found`.\n  - Failed imports can be flagged with `sercrod-import-error`.\n- Structural restrictions are part of the official Sercrod behavior:\n  - A single element must not combine `*include` with `*each` or `*import`.\n  - `*include` on an element that also has `*template` is effectively ignored for rendering and should be treated as invalid usage.\n",
  "innerHTML": "### *innerHTML\n\n#### Summary\n\n`*innerHTML` sets the DOM `innerHTML` property of an element from a Sercrod expression.\nIt is the low-level directive for inserting HTML markup as a string.\nThe alias `n-innerHTML` behaves identically.\n\nUse `*innerHTML` when you already have an HTML string and you want to insert it as markup, not as plain text.\nUnlike `*print` or `*textContent`, `*innerHTML` does not escape the value; it passes it through the global `html` filter and then assigns it directly to `el.innerHTML`.\n\n\n#### Basic example\n\nA simple example that renders an HTML fragment stored in data:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"contentHtml\": \"<strong>Hello</strong> <em>world</em>!\"\n}'>\n  <p *innerHTML=\"contentHtml\"></p>\n</serc-rod>\n```\n\nBehavior:\n\n- The `contentHtml` string is evaluated from the current scope.\n- Sercrod calls the global `html` filter with that value.\n- The result is assigned to the `<p>` element's `innerHTML`.\n- The `<p>` ends up containing `<strong>Hello</strong> <em>world</em>!` as real HTML, not as text.\n\n\n#### Behavior\n\nCore behavior:\n\n- `*innerHTML` is a structural output directive that controls the inner markup of the host element.\n- For each render of the host:\n  - Sercrod evaluates the expression on `*innerHTML` in the current scope.\n  - If the result is `null` or `false`, it is treated as an empty string.\n  - Otherwise the raw value is passed to the global `html` filter:\n    - `html(raw, { el, expr, scope })`.\n  - The filter result is assigned to `el.innerHTML`.\n\nAliases:\n\n- `*innerHTML` and `n-innerHTML` are aliases.\n- They share the same evaluation pipeline; the only difference is the attribute name.\n\nRelationship to `*print` and `*textContent`:\n\n- `*print` and `*textContent` set `textContent` through the `text` filter.\n- `*innerHTML` sets `innerHTML` through the `html` filter.\n- `*print` and `*textContent` escape or normalize text; `*innerHTML` does not.\n- If an element has both `*print`/`*textContent` and `*innerHTML`, the `*print`/`*textContent` branch runs first and returns, so `*innerHTML` on that element is effectively ignored.\n  - In practice you should not combine them on the same element.\n\n\n#### Evaluation timing\n\nWithin the rendering pipeline of a single element:\n\n- Host-level `*if` and conditional chains (`*if`, `*elseif`, `*else`) are resolved first.\n  - If the host `*if` condition evaluates to false, the element (and its directives) are skipped entirely; `*innerHTML` is not evaluated.\n- `*include` and `*import` (if present on the same element) run earlier than `*innerHTML`.\n  - They may rewrite `node.innerHTML` of the template but do not prevent `*innerHTML` from running.\n- `*print` and `*textContent` run before `*innerHTML`.\n  - If they match, they set `textContent`, append the element to the parent, and return; no later output directive runs.\n- If none of the earlier output directives return, `*innerHTML` is evaluated.\n- After `*innerHTML`, Sercrod continues with attribute bindings, style bindings, and finally recursive child rendering from the original template node.\n\nImportant consequence:\n\n- The HTML string inserted by `*innerHTML` is not traversed by Sercrod's renderer.\n  - Sercrod still walks `node.childNodes` from the template, not the newly inserted HTML string.\n  - Any Sercrod directives that appear inside the inserted HTML string are not compiled or bound by Sercrod.\n\n\n#### Execution model\n\nConceptually, for an element `<div *innerHTML=\"expr\">` the runtime behaves like this:\n\n1. Clone the template node (without children) into `el`.\n2. Resolve any earlier host-level directives (for example `*if`, `*include`, `*import`, `*literal`, `*rem`, `*print`, `*textContent`).\n3. When the `*innerHTML` branch is reached:\n   - Read the attribute value `expr` from the host.\n   - Evaluate it with `eval_expr(expr, scope, { el: node, mode: \"innerHTML\" })`.\n   - Map `null` or `false` to an empty string, otherwise use the returned value as `raw`.\n   - Build a context object `{ el, expr, scope }`.\n   - Call `Sercrod._filters.html(raw, ctx)` and assign the result to `el.innerHTML`.\n4. Recursively render the original template children:\n   - For each `child` in `node.childNodes`, call `renderNode(child, scope, el)`.\n   - This may append Sercrod-generated child content after the HTML inserted by `*innerHTML`.\n5. At the end of `_renderElement`, if `cleanup.directives` is enabled in the global config:\n   - All attributes that are known Sercrod directives (including `*innerHTML` and `n-innerHTML`) are removed from `el`.\n6. Append `el` to the parent and run any log hooks.\n\nThe inserted HTML string is therefore a one-shot, low-level injection step; Sercrod does not automatically apply its own directives inside that string.\n\n\n#### Variable creation and scope layering\n\n`*innerHTML` does not create new variables.\n\nIts expression is evaluated in the normal Sercrod scope:\n\n- The current data bound to the host (for example from `data` on `<serc-rod>`).\n- Special injected variables:\n  - `$data` for the current host's data object.\n  - `$root` for the root Sercrod host data.\n  - `$parent` for the nearest ancestor Sercrod host data.\n- Methods exposed via `*methods` or the global method injection mechanism.\n- Internal helper methods registered in `Sercrod._internal_methods`.\n\nThere is no per-directive scope layering; `*innerHTML` simply looks at whatever is currently visible in the merged scope.\n\n\n#### Parent access\n\n`*innerHTML` does not alter parent relationships:\n\n- You can read values from the root or parent using `$root` and `$parent` in the expression.\n- The inserted HTML string does not introduce a new Sercrod scope; it is just markup.\n\nExample:\n\n```html\n<serc-rod id=\"app\" data='{\n  \"post\": { \"id\": 1, \"summaryHtml\": \"<p>Summary</p>\" }\n}'>\n  <article>\n    <header>\n      <h1 *print=\"$parent.title\"></h1>\n    </header>\n    <section *innerHTML=\"post.summaryHtml\"></section>\n  </article>\n</serc-rod>\n```\n\nHere, `*innerHTML` sees `post.summaryHtml` from the current scope.\nThe `<h1>` can still use `$parent` or `$root` as usual.\n\n\n#### Use with conditionals and loops\n\n`*innerHTML` composes well with structural directives when they target different elements:\n\n- Host-level condition:\n\n  - If the same element has `*if` and `*innerHTML`, `*if` acts as a gate.\n\n  ```html\n  <section *if=\"post && post.summaryHtml\" *innerHTML=\"post.summaryHtml\"></section>\n  ```\n\n  - If the condition is falsy, the section is not rendered and `*innerHTML` is not evaluated.\n\n- Child-level condition:\n\n  - You can keep `*innerHTML` on a parent and put `*if` on children defined in the template.\n\n  ```html\n  <div *innerHTML=\"introHtml\">\n    <p *if=\"showNote\" class=\"note\">This note is rendered by Sercrod.</p>\n  </div>\n  ```\n\n  - The `introHtml` string is injected as markup.\n  - The `<p>` is still rendered or skipped by Sercrod based on `showNote`.\n\n- Loops:\n\n  - You can use `*innerHTML` inside a `*for` or `*each` loop body:\n\n  ```html\n  <ul *each=\"item of items\">\n    <li>\n      <h3 *print=\"item.title\"></h3>\n      <div *innerHTML=\"item.summaryHtml\"></div>\n    </li>\n  </ul>\n  ```\n\n  - Each iteration gets its own `<div>` whose HTML is taken from `item.summaryHtml`.\n\n  - You can also put `*innerHTML` on the loop container if you want a static wrapper per loop, but usually it is clearer to keep HTML injection on child elements.\n\n\n#### Use with templates, *include, and *import\n\n`*innerHTML` shares the same low-level insertion mechanism (`innerHTML`) as `*include` and `*import`, but with different responsibilities:\n\n- `*include`:\n  - Resolves a named `*template`.\n  - Copies the template's `innerHTML` into `node.innerHTML` (the template node), not directly into `el`.\n  - Leaves element tags and attributes in place.\n  - Does not return; it allows Sercrod to walk the inserted template children and process their directives.\n\n- `*import`:\n  - Fetches HTML from a URL (using a synchronous XHR, with optional caching).\n  - Writes the fetched HTML into `node.innerHTML`.\n  - Removes `*import` / `n-import` from `el`.\n  - Also relies on later child rendering to process any directives inside the imported HTML.\n\n- `*innerHTML`:\n  - Evaluates an expression to get an HTML string.\n  - Passes it through the `html` filter.\n  - Writes the result directly into `el.innerHTML`.\n  - Does not touch `node.innerHTML`; Sercrod still walks the original template children.\n\nCombined effect when used together:\n\n- If you put `*include` or `*import` and `*innerHTML` on the same element:\n\n  - `*include` / `*import` runs first and rewrites `node.innerHTML` for the template.\n  - Later, `*innerHTML` sets `el.innerHTML` from the expression value.\n  - Finally, Sercrod recursively renders `node.childNodes` (now coming from the include/import) into `el`.\n\n  This means the final element can contain:\n\n  - The markup produced by `*innerHTML` (through the `html` filter).\n  - Plus additional children generated from the included/imported template.\n\n- Although this is well-defined in the current implementation, it produces two separate sources of children on the same element and can be difficult to reason about.\n  - In practice it is clearer to separate responsibilities:\n    - Use `*include` / `*import` on one element.\n    - Use `*innerHTML` on a nested element inside the included or imported content, or on a sibling container.\n\nRecommendation:\n\n- Treat `*innerHTML` as a low-level primitive for HTML injection.\n- Use `*include` and `*import` for Sercrod-managed templates and remote HTML.\n- Avoid designing APIs that rely on mixing them on the same element unless you have a very specific reason and fully understand the combined behavior.\n\n\n#### Security and the html filter\n\nThe `html` filter is the main hook for controlling how HTML strings are inserted:\n\n- Default definition in the runtime:\n\n  - `html(raw, ctx) => raw`.\n\n- Responsibilities:\n\n  - This filter is the extension point for:\n    - Sanitizing HTML to prevent XSS when values come from untrusted input.\n    - Post-processing HTML strings before insertion (for example, rewriting links, adding attributes, or delegating to another HTML engine).\n\n- When `html` throws:\n\n  - If `error.warn` is enabled on the Sercrod instance, the runtime logs a warning:\n\n    - It includes the message, the expression, the scope, and the element.\n\n  - The element's `innerHTML` is set to an empty string.\n\nGuidelines:\n\n- Never pass untrusted user input directly to `*innerHTML` without a proper sanitizer in the `html` filter.\n- For trusted static or server-generated HTML, `*innerHTML` can be used as-is.\n- For Markdown or other formats:\n  - Convert them to HTML in normal JavaScript (for example, `renderMarkdownToHtml(text)`).\n  - Apply sanitization and then return the safe string.\n  - Sercrod simply calls your function through the expression.\n\nExample with an external sanitizer:\n\n```html\n<script>\n  function safeHtmlFromMarkdown(md){\n    const html = renderMarkdownToHtml(md);     // your own converter\n    return sanitizeHtml(html);                // your own sanitizer\n  }\n</script>\n\n<serc-rod id=\"doc\" data='{\"bodyMd\": \"# Title\"}'>\n  <article *innerHTML=\"safeHtmlFromMarkdown(bodyMd)\"></article>\n</serc-rod>\n```\n\n\n#### Best practices\n\n- Prefer `*print` or `*textContent` for plain text.\n  - Use `*innerHTML` only when you intentionally need markup injection.\n\n- Use the `html` filter for security:\n  - Override `Sercrod._filters.html` to integrate a sanitizer when working with any untrusted input.\n\n- Keep responsibilities separate:\n  - Avoid combining `*innerHTML` with `*print` or `*textContent` on the same element; only one of them will take effect.\n  - Avoid designing components that rely on mixing `*innerHTML` with `*include` or `*import` on the same element; prefer nesting instead.\n\n- Keep expressions simple:\n  - If HTML strings need complex assembly, build them in normal JavaScript functions and call those functions from `*innerHTML`.\n  - This keeps templates readable and logic testable.\n\n- Be aware of re-renders:\n  - On each re-render of the host, `*innerHTML` rebuilds the inner markup from scratch.\n  - Any state stored only inside the injected HTML (for example, manual event listeners or form values) may be lost unless you manage it separately.\n\n- Do not expect Sercrod to process directives inside injected HTML:\n  - The inserted string is not parsed by Sercrod.\n  - If you need Sercrod directives inside dynamic content, use `*include` or `*import` so that `node.innerHTML` is updated before child rendering.\n\n\n#### Additional examples\n\nFallback summary:\n\n```html\n<serc-rod id=\"post\" data='{\n  \"post\": {\n    \"title\": \"Post title\",\n    \"summaryHtml\": null\n  }\n}'>\n  <h2 *print=\"post.title\"></h2>\n  <div *innerHTML=\"post.summaryHtml || '<p>No summary available.</p>'\"></div>\n</serc-rod>\n```\n\n- If `summaryHtml` is `null` or `false`, the expression falls back to the HTML snippet.\n- `0` or an empty string are not treated as `null`/`false` by the directive and will still be inserted.\n\nCombining `*include` with `*innerHTML` on nested elements:\n\n```html\n<template *template=\"post-card\">\n  <article class=\"post-card\">\n    <h2 *print=\"post.title\"></h2>\n    <div class=\"summary\" *innerHTML=\"post.summaryHtml\"></div>\n  </article>\n</template>\n\n<serc-rod id=\"list\" data='{\"posts\":[\n  { \"title\": \"First\",  \"summaryHtml\": \"<p>First summary</p>\" },\n  { \"title\": \"Second\", \"summaryHtml\": \"<p>Second summary</p>\" }\n]}'>\n  <section *each=\"post of posts\">\n    <div *include=\"'post-card'\"></div>\n  </section>\n</serc-rod>\n```\n\n- `*include` copies the `post-card` template into the innerHTML of the `<div>`.\n- Inside that template, `*innerHTML` injects `post.summaryHtml` as markup.\n- This pattern keeps Sercrod templates in control while using `*innerHTML` only where HTML strings already exist.\n\n\n#### Notes\n\n- `*innerHTML` and `n-innerHTML` are functionally identical; choose one naming style per project.\n- The directive maps `null` and `false` to an empty string; other values (including `0` and empty strings) are passed to the `html` filter as-is.\n- Errors in the `html` filter produce an optional console warning and clear the element's innerHTML.\n- Inserted HTML strings are not scanned for Sercrod directives; they are treated as plain DOM content.\n- When `cleanup.directives` is enabled, `*innerHTML` and `n-innerHTML` attributes are removed from the output DOM like other Sercrod directives.\n",
  "input": "### *input\n\n#### Summary\n\n`*input` binds a form control's value to a data path.\nIt keeps a DOM control (such as `<input>`, `<textarea>`, or `<select>`) in sync with a field in Sercrod's data or staged data.\nThe alias `n-input` behaves identically.\n\n`*input` is focused on value-level binding and works together with `*lazy` and `*eager` to control how often the host re-renders after a change.\n\n\n#### Basic example\n\nA simple text field bound to `form.name`:\n\n```html\n<serc-rod id=\"app\" data='{\"form\":{\"name\":\"Alice\"}}'>\n  <label>\n    Name:\n    <input type=\"text\" *input=\"form.name\">\n  </label>\n\n  <p>Hello, <span *print=\"form.name\"></span>!</p>\n</serc-rod>\n```\n\nBehavior:\n\n- On initial render, the `<input>` shows `\"Alice\"`.\n- When the user edits the field, `form.name` is updated.\n- The `<span *print=\"form.name\">` reflects the new value after Sercrod's update cycle.\n\n\n#### Behavior\n\n`*input` provides a two-way binding between a control and a data expression:\n\n- Data to UI:\n  - On render, Sercrod evaluates the expression from `*input` and sets the control's current value, checked state, or selection.\n- UI to data:\n  - When the user interacts with the control, Sercrod writes the new value back to the expression using `assign_expr`.\n  - Optional filters (`model_out` and `input_in`) can transform data when rendering or capturing input.\n- Staged vs live data:\n  - If the host has staged data (`this._stage`), `*input` writes into the staging area.\n  - If not, `*input` writes directly into the main data.\n\nSupported tags:\n\n- `INPUT`\n- `TEXTAREA`\n- `SELECT`\n\nOther elements with a `.value` property may technically work, but `*input` is designed primarily for standard form controls.\n\n\n#### Expression syntax\n\nThe `*input` expression is evaluated as a normal Sercrod expression and used as an assignment target:\n\n- Typical pattern:\n\n  - `*input=\"form.name\"`\n  - `*input=\"user.profile.email\"`\n  - `*input=\"settings.volume\"`\n\n- The left-hand side can be:\n  - A simple identifier (`username`).\n  - A dotted path (`form.name`, `user.settings.theme`).\n  - Any expression that is valid on the left of an assignment inside `with(scope){ ... }`.\n\nAssignment semantics:\n\n- Sercrod's `assign_expr(lhs, value, scope)`:\n  - Uses a sandboxed `with(scope){ lhs = __val }`.\n  - For unknown top-level identifiers, it allocates nested objects on demand.\n    - For example, `*input=\"profile.name\"` on an initially empty data object will create `data.profile` if needed.\n  - If the assignment fails, Sercrod logs a warning (when warn logging is enabled) and keeps the UI functional.\n\nBest practice:\n\n- Use simple property paths for `*input` (for example `form.name` or `user.email`).\n- Avoid complex arbitrary expressions on the left-hand side, as they may be hard to reason about in assignment.\n\n\n#### Evaluation timing\n\n`*input` participates in two phases:\n\n1. Initial reflection (data to UI):\n\n   - During render, after Sercrod has prepared the scope, it:\n     - Evaluates the `*input` expression on either staged or main data.\n     - Applies the value to the control:\n       - For `input[type=\"checkbox\"]`: sets `checked` from a boolean or array value.\n       - For `input[type=\"radio\"]`: sets `checked` based on equality against the bound value.\n       - For other inputs and textarea: sets `.value`.\n       - For select: sets the selected option(s).\n\n2. Event-driven updates (UI to data):\n\n   - Sercrod attaches event listeners:\n     - `input` for text-like controls.\n     - `change` for checkboxes, radios, selects, and others.\n   - On those events, it:\n     - Normalizes the new value (including optional numeric conversion).\n     - Passes the value through `input_in` filter.\n     - Writes the result back via `assign_expr`.\n     - Triggers either a full update or a more focused child update depending on `*lazy` and `*eager`, and whether staging is active.\n\nIf the host has staged data, updates do not trigger a live host re-render until the staged changes are applied.\n\n\n#### Execution model\n\nA simplified execution model for `*input`:\n\n1. Resolve binding:\n\n   - Sercrod obtains the expression string from `n-input` or `*input`.\n   - It chooses a target object:\n     - Prefer staged data (`this._stage`) when present.\n     - Otherwise use main data (`this._data`).\n   - It tests evaluation:\n     - If evaluating the expression on staged or main data throws a `ReferenceError`, Sercrod falls back to the current scope object.\n     - This allows bindings to work when the path only exists in the scope, not yet in the main data.\n\n2. Initial data to UI:\n\n   - Evaluate the expression to get `curVal`.\n   - For different controls:\n     - `INPUT`:\n       - `type=\"checkbox\"`:\n         - If `curVal` is an array, the checkbox is checked when its `value` is included in that array (string comparison).\n         - Otherwise, the checkbox is checked when `curVal` is truthy.\n       - `type=\"radio\"`:\n         - After rendering, a `postApply` hook re-evaluates the bound expression and sets `checked` when the bound value equals the control's `value`.\n       - Other types:\n         - `curVal` is passed through `model_out` (if defined), then normalized:\n           - `null`, `undefined`, and `false` become an empty string.\n         - The resulting string is assigned to `el.value`.\n     - `TEXTAREA`:\n       - Similar to text inputs:\n         - `null` and `false` normalize to an empty string.\n         - `model_out` (if defined) can transform the value before it is written.\n     - `SELECT`:\n       - For `multiple`:\n         - Expects `curVal` as an array of values (strings after normalization).\n         - A `postApply` hook selects all options whose value is contained in that array.\n       - For single select:\n         - Writes a string value, with `null` mapped to an empty string.\n\n3. IME composition handling:\n\n   - For text-like controls (`input` except checkbox/radio, and `textarea`), Sercrod tracks IME composition:\n     - While composing (between `compositionstart` and `compositionend`), `input` events are ignored.\n     - After composition ends, normal `input` handling resumes.\n\n4. UI to data (input events):\n\n   - For `input` (text-like) and `textarea`:\n     - On each `input` event, if not composing:\n       - Read `el.value` as `nextVal`.\n       - Align numeric types:\n         - If `type=\"number\"`, convert to a number when possible, keeping empty string as empty.\n         - Otherwise, if the current bound value is a number and the new value is not empty, try to parse a number.\n       - Pass `nextVal` through `input_in` filter.\n       - Assign the result back to the expression via `assign_expr`.\n       - If there is no staged data:\n         - If `*eager` is active, call `update()` on the host.\n         - Otherwise, call `_updateChildren(...)` to update only the host's descendants.\n\n5. UI to data (change events):\n\n   - For `change` on all relevant controls:\n     - Compute `nextVal` depending on control type:\n       - Checkbox:\n         - If the bound value is an array, toggle membership of `el.value` in that array.\n         - Otherwise, use `el.checked` as a boolean.\n       - Radio:\n         - If `el.checked`, use `el.value`.\n       - Select:\n         - For `multiple`, use an array of selected `option.value`.\n         - Otherwise, use `el.value`.\n       - Others:\n         - Use `el.value`.\n     - If the original bound value is a number (and `nextVal` is not empty and not an array), try to convert to number.\n     - Pass `nextVal` through `input_in` and assign to the binding expression.\n     - If there is no staged data:\n       - If `*lazy` is active, update only the host's children.\n       - Otherwise, perform a full `update()` of the host.\n\n6. Staged data:\n\n   - When the host uses `*stage`, `*input` writes into `this._stage` instead of `this._data`.\n   - Because the update logic checks `if (!this._stage)`, staged inputs do not trigger automatic re-rendering of the live view.\n   - Applying or restoring the stage (for example via `*apply` / `*restore`) will drive the next visible update.\n\n\n#### Variable creation and scope layering\n\n`*input` does not create new variables in the template scope.\n\nScope behavior:\n\n- The binding expression is evaluated against:\n  - Staged data (`this._stage`) or main data (`this._data`) as primary scopes.\n  - The current scope as fallback on certain reference errors.\n- When writing, `assign_expr` operates on the chosen scope and can create missing path segments.\n- All existing scope entries (such as `$data`, `$root`, `$parent`, and variables from `*let`) remain available but are not modified unless your `*input` expression explicitly targets them.\n\nGuidelines:\n\n- Prefer to bind to stable data paths derived from `data` on `<serc-rod>`.\n- Avoid binding directly to transient local variables created by `*let` unless you understand the implications.\n\n\n#### Parent access\n\n`*input` does not provide its own parent helper, but you can target parent data explicitly:\n\n- Use normal paths to reach parent data structures (`parentForm.name`, `wizard.steps[current].value`, and similar).\n- Use `$root` and `$parent` inside expressions if you need to bind to root-level or parent-host data.\n\nThe binding still obeys the same assignment semantics: the expression is the left-hand side of an assignment inside the current scope.\n\n\n#### Use with conditionals and loops\n\n`*input` can be freely combined with conditional rendering and loops, as long as the rendered control remains a valid form element:\n\n- With `*if`:\n\n  ```html\n  <label *if=\"form.enableName\">\n    Name:\n    <input type=\"text\" *input=\"form.name\">\n  </label>\n  ```\n\n  - When the condition becomes falsy, the input is removed from the DOM; when it becomes truthy again, the control is re-created and bound using the current data.\n\n- With `*for`:\n\n  ```html\n  <serc-rod id=\"list\" data='{\n    \"tags\":[\"HTML\",\"CSS\",\"JavaScript\"]\n  }'>\n    <ul>\n      <li *for=\"tag of tags\">\n        <label>\n          <input type=\"checkbox\" *input=\"selectedTags\" :value=\"tag\">\n          <span *print=\"tag\"></span>\n        </label>\n      </li>\n    </ul>\n\n    <p>Selected: <span *print=\"selectedTags.join(', ')\"></span></p>\n  </serc-rod>\n  ```\n\n  - Here, `selectedTags` is expected to be an array.\n  - Each checkbox toggles its tag inside that array.\n\n- With `*each`:\n\n  - `*input` works as expected inside bodies of `*each`, just like with `*for`.\n  - The loop variables are visible to binding expressions.\n\n\n#### Use with *stage, *lazy and *eager\n\n`*input` is designed to integrate with staged editing and timing control directives.\n\n- With `*stage`:\n\n  ```html\n  <serc-rod id=\"profile\" data='{\"profile\":{\"name\":\"Taro\",\"email\":\"taro@example.com\"}}'>\n    <form *stage=\"'profile'\">\n      <label>\n        Name:\n        <input type=\"text\" *input=\"profile.name\">\n      </label>\n\n      <label>\n        Email:\n        <input type=\"email\" *input=\"profile.email\">\n      </label>\n\n      <button type=\"button\" *apply=\"'profile'\">Save</button>\n      <button type=\"button\" *restore=\"'profile'\">Reset</button>\n    </form>\n  </serc-rod>\n  ```\n\n  - Inside the staged area, `profile` refers to staged data under `stage.profile`.\n  - Inputs modify only the staged copy, not live data.\n  - Applying or restoring the stage controls when the live data changes.\n\n- With `*lazy`:\n\n  - `*lazy` and `n-lazy` adjust how change-driven updates behave:\n    - When active, change events (for example from checkbox, radio, select) update the data but trigger a lighter child-only update instead of a full host re-render.\n    - The attribute can be used with or without a value:\n      - `*lazy` or `*lazy=\"\"` means \"lazy is enabled\".\n      - `*lazy=\"expr\"` is evaluated; if the expression is truthy, lazy behavior is enabled.\n\n- With `*eager`:\n\n  - `*eager` and `n-eager` affect text-style inputs and textarea on `input` events:\n    - When `*eager` is enabled, text changes cause a full `update()` of the host.\n    - Without `*eager`, Sercrod only updates the host's children, which can be cheaper.\n  - Similar to `*lazy`, it can be:\n    - Present as a bare attribute (`*eager`).\n    - Or have an expression value (`*eager=\"expr\"`) that decides whether eager behavior is active.\n\nProject-level recommendation:\n\n- Use `*eager` sparingly, only when the host needs to react immediately to every keystroke.\n- Use `*lazy` when you want heavy recomputation to happen on change rather than on every adjustment, especially for checkboxes, radios, and selects.\n\n\n#### Best practices\n\n- Use simple, stable paths:\n\n  - Favor expressions like `form.name` or `user.email`.\n  - Avoid writing to very complex expressions; keep assignments intuitive.\n\n- Initialize data types:\n\n  - When you expect a number, initialize the bound value as a number or use `type=\"number\"`.\n  - When you expect a checkbox group, initialize the bound value as an array.\n  - For multi-selects, also use arrays.\n\n- Use filters for formatting and parsing:\n\n  - Implement `Sercrod._filters.model_out` to format bound values before they appear in controls.\n  - Implement `Sercrod._filters.input_in` to parse or validate input before storing it in data (for example trimming whitespace or coercing to domain-specific types).\n\n- Combine with `*stage` for editing flows:\n\n  - Wrap groups of inputs in staged sections when you need explicit \"Save\" / \"Cancel\" flows.\n  - Let `*apply` and `*restore` control when staged values become live.\n\n- Be mindful of update costs:\n\n  - Use `*eager` only when necessary; eager updates can be expensive on large templates.\n  - Use `*lazy` to limit full rerenders from frequently changing inputs.\n\n- Treat `name` attributes as optional:\n\n  - `*input` binds via its expression, not via `name`.\n  - You can still use `name` for browser-level features (such as form submission), but the binding does not rely on it.\n\n\n#### Additional examples\n\nCheckbox bound to a boolean:\n\n```html\n<serc-rod id=\"flag\" data='{\"settings\":{\"enabled\":true}}'>\n  <label>\n    <input type=\"checkbox\" *input=\"settings.enabled\">\n    Enabled\n  </label>\n\n  <p>Status: <span *print=\"settings.enabled ? 'ON' : 'OFF'\"></span></p>\n</serc-rod>\n```\n\nCheckbox group bound to an array:\n\n```html\n<serc-rod id=\"colors\" data='{\"colors\":[\"red\",\"green\",\"blue\"],\"selected\":[\"red\"]}'>\n  <div *for=\"color of colors\">\n    <label>\n      <input type=\"checkbox\" *input=\"selected\" :value=\"color\">\n      <span *print=\"color\"></span>\n    </label>\n  </div>\n\n  <p>Selected: <span *print=\"selected.join(', ')\"></span></p>\n</serc-rod>\n```\n\nRadio group bound to a single value:\n\n```html\n<serc-rod id=\"plan\" data='{\"plan\":\"basic\"}'>\n  <label>\n    <input type=\"radio\" name=\"plan\" *input=\"plan\" value=\"basic\">\n    Basic\n  </label>\n  <label>\n    <input type=\"radio\" name=\"plan\" *input=\"plan\" value=\"pro\">\n    Pro\n  </label>\n\n  <p>Current plan: <span *print=\"plan\"></span></p>\n</serc-rod>\n```\n\nSelect with multiple:\n\n```html\n<serc-rod id=\"multi\" data='{\n  \"allOptions\":[\"A\",\"B\",\"C\"],\n  \"chosen\":[\"A\",\"C\"]\n}'>\n  <label>\n    Choose:\n    <select multiple *input=\"chosen\">\n      <option *for=\"opt of allOptions\" :value=\"opt\" *print=\"opt\"></option>\n    </select>\n  </label>\n\n  <p>Chosen: <span *print=\"chosen.join(', ')\"></span></p>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*input` and `n-input` are aliases; choose one style and use it consistently.\n- `*input` is designed for `<input>`, `<textarea>`, and `<select>`.\n- The bound expression is treated as an assignment target; Sercrod will attempt to create missing intermediate objects when necessary.\n- `*lazy` and `*eager` are optional helpers for tuning update timing; they do not change the core binding semantics.\n- IME composition is respected for text-like fields, so partial compositions do not repeatedly overwrite data.\n- As with other directives, `*input` follows Sercrod's general rules for expressions, filters, and scope resolution; there are no special restrictions on combining it with other directives on the same element, as long as the result is still a well-formed form control.\n",
  "into": "### *into\n\n#### Summary\n\n`*into` selects a data slot that receives the result of certain Sercrod directives that talk to the outside world.\nIt is most often used together with `*api`, `*upload`, or `*websocket` to capture responses in a named field on the root data object.\n\n`*into` has an alias `n-into`. Both forms behave the same.\n\nKey points:\n\n- `*into` does not do anything by itself.\n- It is only consulted by directives that explicitly read it (for example `*api`, `*upload`, `*websocket`).\n- Responses are also mirrored into built-in fields such as `$download`, `$upload`, or `$ws_last`, so `*into` is an opt-in extra sink, not the only place where data goes.\n\n\n#### Basic example\n\nCapture an API response into `user`:\n\n```html\n<serc-rod id=\"app\" data='{\"user\": null}'>\n  <button\n    *api=\"'/api/user.json'\"\n    method=\"GET\"\n    *into=\"user\"\n  >\n    Load user\n  </button>\n\n  <pre *if=\"user\" *print=\"JSON.stringify(user, null, 2)\"></pre>\n</serc-rod>\n```\n\nBehavior:\n\n- Clicking the button performs an HTTP GET request to `/api/user.json`.\n- When the request succeeds, the parsed response body is stored in:\n  - `$download` (because it is a GET request)\n  - `user` (because of `*into=\"user\"`)\n- On the next finalize step, `$download` and `user` are reset to `null`. If you want to keep the data, copy it to a long-lived field in your data.\n\n\n#### Behavior\n\n`*into` is a routing hint for Sercrod's I/O helpers. It tells those helpers which top-level key on the root data object should receive their latest result.\n\nImplemented behavior in this version:\n\n- `*api` / `n-api`:\n\n  - Use `*into` on the same element to select a data field where the parsed response is stored.\n  - Also maintain `$download` and `$upload` metrics.\n\n- `*upload`:\n\n  - Uses `*into` on the element that defines the upload behavior (see below) to decide where to place the server response.\n\n- `*websocket` / `n-websocket`:\n\n  - Treat `*into` as the destination for the last received message.\n\nNo other directives read `*into` in the current implementation.\nPutting `*into` on an element that does not participate in these features has no effect.\n\n\n#### Evaluation timing\n\n`*into` is always read on the element that owns the directive which uses it.\n\n- For `*api`:\n\n  - During rendering, `*api` reads `*into` or `n-into` from the same element.\n  - The network request is run when Sercrod decides to activate that `*api` (for example on click or on initial render, depending on how `*api` is used).\n  - When the request resolves, the response body is passed to a small helper `place(value)` which:\n    - Updates `$download` or `$upload`.\n    - Writes to the `*into` target if one was provided.\n\n- For `*upload`:\n\n  - When an upload button is bound, Sercrod reads `*into` from the original element that defines the upload behavior.\n  - The value is stored on an internal helper and reused when the upload completes.\n  - The upload completion handler then writes the response body to the chosen key.\n\n- For `*websocket`:\n\n  - When a WebSocket connection is initialized, Sercrod reads `*into` from the element that has `*websocket` / `n-websocket`.\n  - The destination is stored together with the WebSocket instance and reused whenever messages arrive.\n  - Each incoming message is run through a lightweight parser and then written to the `*into` target, if present.\n\n\n#### Execution model\n\nAt a high level, the execution model for `*into` looks like this for each supported directive.\n\n- With `*api`:\n\n  1. Sercrod finds an element with `*api` or `n-api`.\n  2. It clones the element as a real DOM node and reads:\n     - `*api` / `n-api` for the URL expression.\n     - `method` for the HTTP method (default `\"GET\"`).\n     - `*into` / `n-into` for the destination key.\n  3. It ensures `$download`, `$upload`, and the `*into` target (if any) exist on the root data as keys with `null` values.\n  4. It runs the HTTP request, either as JSON style or other depending on the options.\n  5. On success:\n     - For GET requests, it stores the body into `$download`.\n     - For non-GET requests or file uploads, it stores the body into `$upload`.\n     - If `*into` is present, it stores the same value into `data[into]` and remembers that key for later clearing.\n  6. It dispatches custom events to signal start, success, or error.\n  7. Sercrod continues its normal update cycle. At the finalize step, `$download`, `$upload`, and any remembered `*into` keys are reset to `null`.\n\n- With `*upload`:\n\n  1. Sercrod normalizes upload options from an expression and element attributes.\n  2. It creates or reuses a hidden `<input type=\"file\">` for the clickable element.\n  3. It reads `*into` / `n-into` from the upload context element and stores this value on the hidden input.\n  4. When the user selects files and the upload completes, the server response body is stored into:\n     - A key derived from `*into` if it exists.\n     - `$upload` as a generic metric.\n  5. That key is also added to the list of `*into` targets that will be cleared at finalize.\n\n- With `*websocket`:\n\n  1. Sercrod resolves the WebSocket specification from:\n     - The `*websocket` / `n-websocket` attribute.\n     - The current data scope.\n  2. It reads `*into` / `n-into` and constructs a holder `{ ws, into, el }`.\n  3. When messages arrive:\n     - The last payload is stored into `$ws_last`.\n     - Sercrod tries to push it into `$ws_messages` if that array exists.\n     - If `into` is non-empty, the value is also written to `data[into]` and the key is added to the list for later clearing.\n\n\n#### Variable creation and scope layering\n\n`*into` does not create new variables for expressions.\nInstead, it targets the root data object:\n\n- The value of `*into` is taken as a key on the root data object (`this._data`).\n- Sercrod writes directly to `this._data[into]`.\n- Nested path semantics (for example `\"user.profile\"`) are not interpreted.\n  Such a string is treated literally as a property name.\n- The stored values can later be read by any directive that uses the normal scope, for example:\n\n  - `*print=\"uploadResult\"`\n  - `*if=\"user && user.name\"`\n  - `*let=\"latest = wsMessage\"`\n\nThere is no special `$into` variable or local binding created by `*into` itself.\n\n\n#### Scope layering and parent access\n\nBecause `*into` writes to the root data object, scope behaves as follows:\n\n- The new key is visible as a normal data field.\n- Inside the same `serc-rod` host, it is available through:\n  - The implicit root scope.\n  - `$data`, if you refer to the unwrapped data.\n  - `$root`, if you are inside nested hosts.\n- If a child host defines its own `data`, it sees the parent field through `$parent` or higher-level references, depending on how you structure your templates.\n\n`*into` does not introduce any extra scope layers by itself.\n\n\n#### Use with *api\n\n`*into` is most directly tied to the `*api` / `n-api` logic.\n\nKey behaviors:\n\n- For every `*api` request:\n\n  - `$download` is always updated for GET style requests.\n  - `$upload` is always updated for non-GET style requests and file uploads.\n  - If `*into` is given a non-empty string, that key receives the same value.\n\n- The destination key is initialized to `null` if it did not exist yet.\n- At the finalize step, that key is set back to `null` so that stale responses do not remain in data forever.\n\nTypical patterns:\n\n```html\n<!-- GET: store response into \"user\" and also into $download -->\n<button *api=\"'/api/user.json'\" method=\"GET\" *into=\"user\">\n  Load user\n</button>\n\n<!-- POST: store result into \"result\" and also into $upload -->\n<button\n  *api=\"'/api/save'\"\n  method=\"POST\"\n  body=\"{ name: form.name }\"\n  *into=\"result\"\n>\n  Save\n</button>\n```\n\nIf you need to keep the data beyond one update cycle, copy it to a stable field in an event handler or effect, instead of relying on the short-lived `*into` slot.\n\n\n#### Use with *upload\n\nFor uploads, `*into` is read from the element that defines the upload behavior and is then used on the hidden file input.\n\nBehavior:\n\n- If `*into` is present:\n\n  - The response body is written to `data[into]`.\n  - The same value is mirrored into `$upload` if `$upload` was previously empty.\n  - The `into` key is added to the list of keys to be cleared at finalize.\n\n- If `*into` is not present:\n\n  - The response body is written to `$upload`.\n  - `$upload` is also added to the list of keys to be cleared at finalize.\n\nExample pattern:\n\n```html\n<serc-rod\n  data='{\n    \"uploadResult\": null\n  }'\n>\n  <button\n    *upload=\"{ url: '/api/upload', accept: 'image/*' }\"\n    *into=\"uploadResult\"\n  >\n    Upload image\n  </button>\n\n  <p *if=\"uploadResult\">\n    Uploaded: <span *print=\"uploadResult.fileName\"></span>\n  </p>\n</serc-rod>\n```\n\nIn this pattern:\n\n- Clicking the button opens a file picker.\n- The upload happens via `fetch` or `XMLHttpRequest`, depending on the upload options.\n- The server's JSON response is stored in `uploadResult` and `$upload`.\n- After finalize, `uploadResult` and `$upload` are reset to `null`.\n\n\n#### Use with *websocket\n\n`*into` is also supported on WebSocket hosts.\n\nBehavior:\n\n- On an element with `*websocket` or `n-websocket`, Sercrod:\n\n  - Resolves a WebSocket specification that includes a URL and optional extra options.\n  - Reads `*into` / `n-into` from the same element.\n  - Connects to the WebSocket and stores the connection in an internal map together with the `into` key.\n\n- On incoming messages:\n\n  - `$ws_last` receives the last payload.\n  - `$ws_messages` (if present and array-like) collects all payloads.\n  - If an `into` key is present, `data[into]` is updated with the latest payload and added to the list of keys cleared at finalize.\n\nIf the `*websocket` expression resolves to an object with an `into` property, Sercrod also respects that, but a markup `*into` on the same element takes priority.\n\nExample:\n\n```html\n<serc-rod id=\"ws-app\" data='{\"lastMessage\": null}'>\n  <section\n    *websocket=\"'wss://example.com/live'\"\n    *into=\"lastMessage\"\n  >\n    <p>Connection status: <span *print=\"$ws_ready ? 'ready' : 'connecting'\"></span></p>\n    <p *if=\"lastMessage\">Last message: <span *print=\"JSON.stringify(lastMessage)\"></span></p>\n  </section>\n</serc-rod>\n```\n\n\n#### Best practices\n\n- Choose stable, descriptive keys:\n\n  - Use names like `user`, `profile`, `uploadResult`, or `wsPayload`.\n  - Avoid reusing the same key for completely unrelated APIs in the same host, unless you deliberately want to share a single slot.\n\n- Treat `*into` slots as transient:\n\n  - Values in `$download`, `$upload`, and `*into` destinations are cleared at the finalize step.\n  - Copy values you want to keep into long-lived fields in your data model.\n\n- Keep the value simple:\n\n  - The full response body is stored as is.\n  - If you only need part of it, consider extracting that part into another field inside your own code and leaving the `*into` slot for debugging or inspection.\n\n- Avoid overloading `*into` on the same element:\n\n  - Do not expect `*into` to work with directives that do not reference it.\n  - Keep one main I/O directive per element that uses `*into` so it is obvious which operation writes to that field.\n\n\n#### Examples\n\nCapture download status separately for GET and POST:\n\n```html\n<serc-rod\n  data='{\n    \"user\": null,\n    \"saveResult\": null\n  }'\n>\n  <button *api=\"'/api/user'\" method=\"GET\" *into=\"user\">\n    Load user\n  </button>\n\n  <button\n    *api=\"'/api/user'\"\n    method=\"POST\"\n    body=\"{ user }\"\n    *into=\"saveResult\"\n  >\n    Save user\n  </button>\n\n  <section *if=\"user\">\n    <h2>User</h2>\n    <pre *print=\"JSON.stringify(user, null, 2)\"></pre>\n  </section>\n\n  <section *if=\"saveResult\">\n    <h2>Last save result</h2>\n    <pre *print=\"JSON.stringify(saveResult, null, 2)\"></pre>\n  </section>\n</serc-rod>\n```\n\nUse `*into` with WebSocket messages:\n\n```html\n<serc-rod data='{\"ticker\": null}'>\n  <div\n    *websocket=\"{ url: 'wss://example.com/ticker', into: 'ticker' }\"\n  >\n    <p *if=\"ticker\">\n      Price: <span *print=\"ticker.price\"></span>\n    </p>\n  </div>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*into` and `n-into` are aliases. Use one style consistently in your project.\n- In this version, `*into` is only read by:\n  - `*api` / `n-api`\n  - `*upload` bindings\n  - `*websocket` / `n-websocket`\n- `*into` targets operate on the root data object. The value is written as `data[into]` without interpreting dots or bracket notation.\n- The same response is also placed into `$download`, `$upload`, or `$ws_last` depending on the directive. You can always inspect those fields even when you do not use `*into`.\n- After the finalize step:\n  - `$download` and `$upload` are reset to `null`.\n  - All keys that were used as `*into` destinations are reset to `null`.\n- If you need persistent storage of responses, copy them out of the short-lived `*into` slots into your own fields before they are cleared.\n",
  "lazy": "### *lazy\n\n#### Summary\n\n`*lazy` is a small flag directive that makes Sercrod less aggressive about full re-renders.\n\nIt has two main roles:\n\n- On a Sercrod host (`<serc-rod>`):\n  - When `*lazy` is active, normal internal updates do not rebuild the host template.\n  - The host only propagates changes to child Sercrod instances and runs `*updated` hooks.\n  - A full rebuild still happens on forced updates.\n\n- On a form control with `*input` / `n-input`:\n  - `*lazy` controls whether a `change` event triggers a full host re-render or only a child update.\n  - The attribute may be a simple boolean flag or an expression that decides laziness at runtime.\n\nThe alias `n-lazy` behaves the same as `*lazy`.\n\n\n#### Basic example\n\nDelayed host re-render for a form input:\n\n```html\n<serc-rod id=\"app\" *lazy data='{\"form\":{\"name\":\"\"}}'>\n  <h2>Profile</h2>\n\n  <label>\n    Name:\n    <input type=\"text\" *input=\"form.name\">\n  </label>\n\n  <p>Preview: <strong *print=\"form.name\"></strong></p>\n</serc-rod>\n```\n\nIn this example:\n\n- Typing in the input updates `form.name`.\n- Because the host `<serc-rod>` has `*lazy`, internal updates do not rebuild the host template.\n- Instead, child bindings (such as `*print`) are updated and `*updated` hooks run, but the outer DOM structure stays stable.\n\n\n#### Behavior\n\n`*lazy` does not create variables; it only changes how and when Sercrod decides to re-render.\n\nThere are two independent behaviors:\n\n1. Host-level `*lazy` (on `<serc-rod>` or another Sercrod-based host)\n\n- Checked inside `update(force, caller, evt, isInit)`.\n- Implementation:\n\n  - Sercrod reads\n\n    - `lazyAttr = this.getAttribute(\"*lazy\") || this.getAttribute(\"n-lazy\")`\n\n    and then\n\n    - `isLazy = (this.hasAttribute(\"*lazy\") || this.hasAttribute(\"n-lazy\")) && String(lazyAttr ?? \"\").toLowerCase() !== \"false\"`\n\n  - There is no expression evaluation at the host level.\n  - Presence of the attribute with no value, or with any value except `\"false\"`, enables laziness.\n  - If `isLazy` is true and `force` is false:\n\n    - The host skips its own template rebuild.\n    - It calls `_updateChildren(false, this)` so that child Sercrod hosts see the new data.\n    - It calls `*updated` hooks.\n    - It finalizes and returns from `update` early.\n\n  - If `force` is true or `isLazy` is false:\n\n    - The host clears its content and fully rebuilds from the stored template.\n\nEffectively, host-level `*lazy` says:\n\n- \"On normal reactivity-driven updates, do not tear down and rebuild this host; only propagate to children and call hooks.\"\n- \"On explicit forced updates (for example `update(true, ...)`, initial render, or certain targeted operations), rebuild as usual.\"\n\n2. Input-level `*lazy` (on elements with `*input` / `n-input`)\n\n- Checked in the `*input` binding logic for each bound node.\n- Implementation:\n\n  - Sercrod reads\n\n    - `LazyAttr = node.getAttribute(\"*lazy\") ?? node.getAttribute(\"n-lazy\")`\n\n    then:\n\n    - If the element has `*lazy` / `n-lazy` and the attribute value is empty or `null`, `isLazy = true`.\n    - Otherwise it tries `this.eval_expr(LazyAttr, scope, { el: node, mode: \"Lazy\" })`:\n      - If evaluation succeeds, `isLazy = Boolean(result)`.\n      - If evaluation throws, it falls back to string semantics:\n        - `isLazy = String(LazyAttr).toLowerCase() !== \"false\"`.\n\n- For `input` events on text-like controls:\n\n  - When `tag === \"INPUT\"` and `type` is neither `\"checkbox\"` nor `\"radio\"`, Sercrod:\n\n    - Updates the bound data (`assign_expr`).\n    - If the host is not staging (`!this._stage`):\n\n      - If `*eager` / `n-eager` is truthy (`isEager`), it calls `this.update()` ? full host re-render.\n      - Otherwise it calls `_updateChildren(true, this)` ? child-only update (the comment describes this as the default \"lazy\" behavior).\n\n  - In this path, `isLazy` is not consulted; laziness is controlled by the absence of `*eager`.\n\n- For `change` events (checkbox / radio / select / other):\n\n  - After mapping the new UI value back into the model and calling `assign_expr`, Sercrod checks `isLazy` for that control.\n  - If the host is not staging (`!this._stage`):\n\n    - If `isLazy` is false (no `*lazy` / `n-lazy`, or expression evaluates to false):\n\n      - Sercrod calls `this.update()` ? full host re-render.\n\n    - If `isLazy` is true:\n\n      - Sercrod calls `_updateChildren(false, this)` ? propagating to child Sercrod hosts without rebuilding the host.\n\nSo on a control that uses `*input`:\n\n- `*lazy` / `n-lazy` is only consulted for the `change` event path.\n- `*eager` / `n-eager` is only consulted for the `input` event path.\n- Both are ignored when the host is currently using a stage buffer (`*stage`), because the event handlers guard on `if(!this._stage)` before scheduling any update.\n\n\n#### Evaluation timing\n\n- Host-level `*lazy`:\n\n  - Evaluated on every call to `update(...)` on the host.\n  - Only the literal string value matters; there is no expression evaluation.\n  - A host whose `*lazy` attribute changes between updates will reflect that change the next time `update` runs.\n\n- Input-level `*lazy`:\n\n  - Evaluated when the `*input` binding logic runs for that node.\n  - Sercrod computes `isLazy` using the current `scope`:\n\n    - If the expression evaluates successfully, its boolean value is cached for that run.\n    - If evaluation fails, the fallback string logic is used.\n\n  - On each re-render where the input node is recreated, the `*lazy` expression is re-evaluated.\n\n\n#### Execution model\n\n1. Host-level flow (simplified):\n\n- Some internal change occurs (for example, data mutation through the reactive proxy, or an event handler that calls into data).\n- Sercrod schedules or calls `update(force=false, caller, evt, isInit=false)` on the host.\n- Inside `update`:\n\n  - It computes `isLazy` from the host attributes.\n  - If `force` is false and `isLazy` is true:\n\n    - It skips the host's template rebuild.\n    - It calls `_updateChildren(false, this)` so that child Sercrod hosts refresh.\n    - It calls `_call_updated_hooks(evt, isInit)` so `*updated` hooks still run.\n    - It calls `_finalize()` and returns.\n\n  - Otherwise:\n\n    - It clears `innerHTML`.\n    - It rebuilds the host content from the stored template using the current scope.\n    - It then updates children and runs `*updated` as part of the normal flow.\n\n2. Input-level flow for text-like `<input>`:\n\n- User types into the input; the `input` event fires.\n- Sercrod:\n\n  - Computes `nextVal`, doing some type normalization (for example numbers).\n  - Applies `input_in` filters.\n  - Assigns the value into the model (`assign_expr` on `inputExpr`).\n  - If the host is not staging (`!this._stage`):\n\n    - If `isEager` is true (`*eager` or `n-eager`):\n\n      - Calls `this.update()` ? full host re-render.\n\n    - Otherwise:\n\n      - Calls `_updateChildren(true, this)` ? child-only update.\n\n- Later, when the input loses focus, a `change` event may fire and follow the generic `change` path described below.\n\n3. Input-level flow for `change` (checkbox / radio / select / others):\n\n- User changes the control and the `change` event fires.\n- Sercrod:\n\n  - Derives `nextVal` from the control (for example mapping checkbox arrays, radio selection, or select values).\n  - Applies `input_in` filters and writes into the model.\n  - If the host is not staging (`!this._stage`):\n\n    - If `isLazy` is false:\n\n      - Calls `this.update()` ? full host re-render from template.\n\n    - If `isLazy` is true:\n\n      - Calls `_updateChildren(false, this)` ? child-only propagation.\n\nAt all times:\n\n- If the host uses a stage buffer (`*stage` / `n-stage`), `!this._stage` is false and neither branch schedules automatic updates. In staged forms, commit timing is controlled by `*stage` and `*apply`, not by `*lazy` or `*eager`.\n\n\n#### Variable creation\n\n`*lazy` does not create or modify any variables:\n\n- It does not introduce new names into the expression scope.\n- It does not change the behavior of `*let`, `*for`, `*each`, or other data directives.\n- It only affects how frequently Sercrod calls `update` and what kind of updates (host vs. children) are performed.\n\n\n#### Scope layering\n\n`*lazy` does not change scope layering:\n\n- The same scope rules apply as without `*lazy`:\n\n  - `$root` and `$parent` still point to the same objects.\n  - Data objects bound via `data=\"...\"` or `*let` remain unchanged.\n  - Methods registered via `*methods` are unaffected.\n\nThe only interaction with scope is when `*lazy` on an input uses an expression, for example:\n\n```html\n<input *input=\"form.name\" *lazy=\"form.mode === 'slow'\">\n```\n\nIn this case:\n\n- `form.mode === 'slow'` is evaluated in the same scope in which `*input` is evaluated.\n- The expression does not create new variables; it simply decides whether the input should behave lazily for `change` events on that render.\n\n\n#### Parent access\n\n`*lazy` does not change how parent data are accessed:\n\n- Parent and root data are still accessible with `$parent` and `$root`.\n- The presence or absence of `*lazy` does not affect which data object is used as the evaluation root for other directives.\n\nYou can freely use `$parent` or `$root` inside expressions for `*lazy` when used on inputs, but they are not required.\n\n\n#### Use with conditionals and loops\n\n`*lazy` is not a structural directive, so it does not compete with `*if`, `*for`, or `*each` for control of the DOM.\n\n- It can appear on the same element as:\n\n  - `*if`, `*elseif`, `*else`\n  - `*for`, `*each`\n  - `*include`, `*import`, `*template`\n  - Event directives like `@click`\n\n- On a host `<serc-rod>`:\n\n  - `*lazy` is read by the host's `update` implementation.\n  - Structural directives on child elements continue to work as usual.\n  - There is no special ordering rule; `*lazy` only affects whether the host rebuilds its template on a given update.\n\n- On an input:\n\n  - `*lazy` only has an effect if the same element also has `*input` / `n-input`.\n  - There is no special structural interaction with `*if` or loops; `*lazy` simply changes how strongly that control drives updates.\n\n\n#### Best practices\n\n- Use host-level `*lazy` for heavy containers:\n\n  - When a Sercrod host is expensive to rebuild but its child Sercrod hosts can cheaply update themselves, `*lazy` reduces unnecessary work.\n  - Typical examples include dashboards or pages that host multiple independent Sercrod widgets.\n\n- Use input-level `*lazy` for commit-style controls:\n\n  - On `checkbox`, `radio`, or `select` elements, `*lazy` can prevent every change from triggering a full host re-render.\n  - This is useful when a single `change` would otherwise cause a large subtree to rebuild.\n\n- Prefer `*eager` when you want immediate feedback:\n\n  - For text inputs where you want the entire host to react on every keystroke, use `*eager` instead of relying on the default lazy behavior.\n\n- Avoid placing `*lazy` where it has no effect:\n\n  - On elements that are not Sercrod hosts and do not use `*input` / `n-input`, `*lazy` is ignored.\n  - Keeping `*lazy` only on hosts and input controls makes intent clearer.\n\n- Keep expressions simple:\n\n  - When using `*lazy=\"expr\"` on inputs, keep the expression short and readable.\n  - For complex conditions, prefer to compute a boolean field in your data (for example `form.isSlow`) and reference that.\n\n\n#### Additional examples\n\nHost-level `*lazy` on a widget:\n\n```html\n<serc-rod id=\"counter\" *lazy data='{\"count\":0}'>\n  <button @click=\"count++\">Increment</button>\n  <p>Count is <span *print=\"count\"></span></p>\n</serc-rod>\n```\n\nBehavior:\n\n- Clicking the button updates `count`.\n- The host is marked `*lazy`, so internal updates do not rebuild the host template.\n- Child bindings re-evaluate and only the printed value changes.\n\nConditional lazy input:\n\n```html\n<serc-rod id=\"form-app\" data='{\n  \"form\": { \"name\": \"\", \"mode\": \"slow\" }\n}'>\n  <label>\n    Name:\n    <input type=\"text\"\n           *input=\"form.name\"\n           *lazy=\"form.mode === 'slow'\">\n  </label>\n\n  <p>Mode: <span *print=\"form.mode\"></span></p>\n</serc-rod>\n```\n\nBehavior:\n\n- When `form.mode` is `\"slow\"`, `*lazy` on the input is truthy and `change` events only trigger child updates.\n- If `form.mode` becomes `\"fast\"` (and the expression evaluates to false), `change` events trigger full host updates again.\n\nLazy checkbox:\n\n```html\n<serc-rod id=\"flags\" data='{\"flags\":{\"debug\":false}}'>\n  <label>\n    <input type=\"checkbox\"\n           *input=\"flags.debug\"\n           *lazy>\n    Debug mode\n  </label>\n\n  <section *if=\"flags.debug\">\n    <h2>Debug panel</h2>\n    <p>Extra diagnostics are now visible.</p>\n  </section>\n</serc-rod>\n```\n\nBehavior:\n\n- Toggling the checkbox updates `flags.debug`.\n- Because the checkbox has `*lazy`, Sercrod uses the child-update path for the `change` event instead of rebuilding the host.\n- The `*if` condition is re-evaluated and the debug section appears or disappears accordingly.\n\n\n#### Notes\n\n- `*lazy` and `n-lazy` are aliases.\n- On hosts, `*lazy` is a simple string-based flag; there is no expression evaluation.\n- On inputs with `*input` / `n-input`, `*lazy` may be:\n\n  - A bare attribute (enabled), or\n  - An expression that decides laziness, with a fallback to string-based `\"false\"` semantics.\n\n- `*lazy` does not create variables or change scope; it only influences how and when Sercrod schedules host and child updates.\n- There are no forbidden directive combinations specific to `*lazy`. The only limitation is that it is only meaningful:\n\n  - On Sercrod hosts (where it affects `update`), and\n  - On controls that use `*input` / `n-input` (where it affects `change` handling).\n",
  "let": "### *let\n\n#### Summary\n\n`*let` runs a small piece of JavaScript in Sercrod's sandbox to define local helper variables.\nThese variables are available to expressions on the same element and all of its descendants.\nNewly created variable names are also promoted into the host data so that later elements inside the same `<serc-rod>` can read them.\n\nAlias:\n\n- `*let` and `n-let` are aliases and behave the same.\n\n\n#### Basic example\n\nCompute a derived value once and reuse it in the element's subtree:\n\n```html\n<serc-rod id=\"invoice\" data='{\"price\": 1200, \"qty\": 3}'>\n  <p *let=\"total = price * qty\">\n    Subtotal: <span *print=\"total\"></span> JPY\n  </p>\n</serc-rod>\n```\n\nIn this example:\n\n- `*let` runs before any other directive on the `<p>`.\n- The code `total = price * qty` creates a new variable `total` in the local scope.\n- The child `<span *print=\"total\">` can read `total` directly.\n- Because `total` did not exist in the host data before, it is also promoted into the Sercrod data scope for this `<serc-rod>`.\n\n\n#### Behavior\n\nAt a high level, `*let` behaves like \"execute this code in the current data scope and keep any new variables\":\n\n- It reads the current data for the host `<serc-rod>` and any in-scope iteration variables.\n- It executes the `*let` string as JavaScript (expressions or simple statements) in a sandboxed scope.\n- It updates the effective scope for the current element and its descendants.\n- It promotes newly created variable names into the host data, but does not overwrite existing ones.\n\nKey points:\n\n- `*let` is a non-structural directive: it does not clone or repeat elements; it just prepares values for other directives.\n- The attribute remains on the element; internally Sercrod re-evaluates it on each re-render.\n- `*let` runs before `*if`, `*switch`, `*each`, and `*for` on the same element, so later directives can rely on variables created by `*let`.\n\n\n#### Expression model\n\nThe value of `*let` is treated as JavaScript code:\n\n- The code runs inside Sercrod's expression sandbox using a dedicated scope object.\n- You can write one or more simple statements.\n\nTypical patterns:\n\n- Single assignment:\n\n  ```html\n  <div *let=\"fullName = user.first + ' ' + user.last\">\n    <p *print=\"fullName\"></p>\n  </div>\n  ```\n\n- Multiple assignments or function calls:\n\n  ```html\n  <div *let=\"\n    subtotal = price * qty;\n    tax = subtotal * taxRate;\n    total = subtotal + tax;\n  \">\n    <p *print=\"total\"></p>\n  </div>\n  ```\n\n- Calling helper functions:\n\n  ```html\n  <div *let=\"displayName = formatUserName(user)\">\n    <span *print=\"displayName\"></span>\n  </div>\n  ```\n\n  Here `formatUserName` must be available in the scope (for example, exported via `*methods` on the host).\n\nWhat the sandbox does:\n\n- Reads variables from the current per-element scope (data fields, loop variables, earlier `*let` values).\n- Reads built-in globals (such as `Math`, `Date`) from the real global environment.\n- Writes always go into the local scope used for `*let`, never directly into `globalThis`.\n\n\n#### Evaluation timing\n\n`*let` is evaluated early in the per-element pipeline:\n\n- It is processed before structural directives on the same element:\n\n  - Before `*if` / `n-if`.\n  - Before `*switch` / `n-switch`.\n  - Before `*each` / `n-each`.\n  - Before `*for` / `n-for`.\n\n- It is also evaluated before `*global` on the same element.\n\nAs a result:\n\n- You can compute helper values in `*let` and use them immediately in:\n\n  - `*if` conditions on the same element.\n  - `*switch` expressions.\n  - `*each` and `*for` expressions.\n  - Any `*print`, bindings, or event handlers on this element and its children.\n\nExample:\n\n```html\n<li *let=\"is_expensive = price > 1000\"\n    *if=\"is_expensive\">\n  <span *print=\"name\"></span>\n  <span>(premium)</span>\n</li>\n```\n\nHere `*if` can safely use `is_expensive` because `*let` runs first.\n\n\n#### Execution model\n\nConceptually, Sercrod handles `*let` on an element like this:\n\n1. Compute the current effective scope `effScope` for this element:\n   - Based on the host data for the `<serc-rod>`.\n   - Including any variables from surrounding loops or parent `*let` directives.\n\n2. If the element has `*let` or `n-let`:\n\n   - Create a new scope object whose prototype points to `effScope`.\n   - Copy the current values from `effScope` into this new scope.\n   - Inject `$parent` so that branch-local code has access to the nearest ancestor Sercrod's data.\n   - Inject any methods that were registered via `*methods` and Sercrod's internal helper methods.\n\n3. Run the `*let` code inside Sercrod's sandbox:\n\n   - Reads go through the scope or, as a fallback, the real global environment for standard objects (such as `Math`).\n   - Writes update only the local scope object created for `*let`.\n   - Unknown identifiers become new variables on this local scope when you assign to them.\n\n4. After the code runs:\n\n   - Sercrod copies any variables that did not exist in the host data into the host data object.\n   - Existing host data keys are not overwritten by `*let`.\n   - The new scope becomes the effective scope for this element and its descendants.\n\n5. Sercrod schedules a re-render if necessary so that bindings see the updated values.\n\nThis model keeps `*let` local by default, but still lets you share newly defined helper variables with other elements in the same `<serc-rod>`.\n\n\n#### Variable creation and promotion\n\n`*let` distinguishes between:\n\n- New variable names created by `*let`.\n- Existing data properties already present in the host's data.\n\nRules:\n\n- When `*let` creates a new name (for example `total`):\n\n  - The new name lives in the local `*let` scope and is visible to:\n\n    - The current element and its descendants.\n    - Later elements in the same `<serc-rod>`, because Sercrod promotes this name into the host data.\n\n- When `*let` assigns to a name that already exists in the host data (for example `price`):\n\n  - The host data's property is not overwritten by `*let`.\n  - Only the local `*let` scope sees the updated value.\n  - Expressions in the current element and its descendants see the updated value (because they use the `*let` scope), but siblings outside this subtree still see the original host data.\n\nIn practice:\n\n- Use `*let` to create new, derived variables (such as `total`, `label`, `filteredItems`).\n- Do not rely on `*let` to permanently modify existing host data properties. If you need that behavior, use `*global` instead.\n\n\n#### Scope layering and parent access\n\nInside `*let`:\n\n- You can access the same variables that ordinary expressions can see:\n\n  - Fields from the host data (for example `user`, `items`, `config`).\n  - Loop variables like `item`, `index`, `row`, `cell` when used inside `*each` or `*for` bodies.\n  - Methods referenced via `*methods` for the host.\n\n- Additionally, Sercrod injects `$parent` into the scope:\n\n  - `$parent` refers to the data of the nearest ancestor `<serc-rod>` component (if any).\n  - This makes it possible to compute values based on both local data and parent data.\n\nThe local `*let` scope then becomes the base scope for:\n\n- All expressions on the same element (such as `*if`, `*print`, `:class`, `@click`).\n- All expressions on child elements, including nested loops and conditionals.\n\n\n#### Use with conditionals and loops\n\n`*let` works closely with conditionals and loops.\n\nOn the same element as `*if` / `*elseif` / `*else`:\n\n- `*let` is evaluated before the condition for that branch.\n- Each branch can have its own `*let`, and Sercrod uses a branch-specific scope when checking its condition.\n\nExample:\n\n```html\n<li *if=\"kind === 'user'\" *let=\"label = user.name\">\n  <span *print=\"label\"></span>\n</li>\n<li *elseif=\"kind === 'guest'\" *let=\"label = guest.nickname\">\n  <span *print=\"label\"></span>\n</li>\n<li *else *let=\"label = 'Unknown'\">\n  <span *print=\"label\"></span>\n</li>\n```\n\nHere:\n\n- Each branch computes its own `label` before the branch condition is evaluated.\n- Only the chosen branch is rendered, along with its computed label.\n\nInside loops:\n\n- When used inside `*each` or `*for` bodies, `*let` runs once per iteration.\n\nExample:\n\n```html\n<ul *each=\"item of items\">\n  <li *let=\"label = item.name + ' (#' + item.id + ')'\">\n    <span *print=\"label\"></span>\n  </li>\n</ul>\n```\n\nEach `<li>` computes its own `label` using the `item` from that iteration.\n\nYou can also use `*let` on the loop container:\n\n```html\n<ul *each=\"item of items\"\n    *let=\"hasItems = !!items && items.length > 0\">\n  <li *if=\"hasItems\" *print=\"item.name\"></li>\n</ul>\n```\n\n`hasItems` is computed before the `*each` and is available to each child `*if` and `*print`.\n\n\n#### Best practices\n\n- Prefer new helper variables:\n\n  - Use `*let` to create new derived names (`total`, `label`, `normalizedUsers`), not to overwrite existing ones.\n  - For permanent data mutations, use `*global` or update your data outside of templates.\n\n- Keep `*let` code simple:\n\n  - Compute values, do light branching, and call small helper functions.\n  - Avoid long, complex procedures inside templates; move heavy logic into reusable JavaScript functions and call them from `*let`.\n\n- Use `*let` to prepare values for multiple bindings:\n\n  - If the same expression appears multiple times in one element or subtree, compute it once in `*let` and reuse the variable.\n\n- Avoid unnecessary side effects:\n\n  - `*let` re-runs on re-render, so side-effectful operations (such as network calls) should not be placed directly inside `*let`.\n  - Instead, call idempotent helpers or use other mechanisms designed for side effects.\n\n\n#### Additional examples\n\nSharing a derived variable with siblings:\n\n```html\n<serc-rod id=\"totals\" data='{\"items\":[{\"name\":\"A\",\"price\":100},{\"name\":\"B\",\"price\":200}]}'>\n  <section *let=\"\n    subtotal = 0;\n    for(const item of items){\n      subtotal = subtotal + item.price;\n    }\n  \">\n    <p>Subtotal: <span *print=\"subtotal\"></span> JPY</p>\n  </section>\n\n  <!-- Later element can also see subtotal, because it was a new name -->\n  <p>Summary: total amount is <span *print=\"subtotal\"></span> JPY</p>\n</serc-rod>\n```\n\nUsing `$parent` data in a nested component:\n\n```html\n<serc-rod id=\"root\" data='{\"currency\":\"JPY\"}'>\n  <serc-rod id=\"child\" data='{\"price\": 500}'>\n    <p *let=\"text = price + ' ' + $parent.currency\">\n      <span *print=\"text\"></span>\n    </p>\n  </serc-rod>\n</serc-rod>\n```\n\nHere:\n\n- The inner Sercrod host defines `price` in its own data.\n- `*let` reads `$parent.currency` from the outer host and combines it with `price`.\n\n\n#### Notes\n\n- `*let` and `n-let` are aliases.\n- The code runs inside Sercrod's sandbox and uses a special scope; it is not the same as writing code directly into global script tags.\n- `*let` does not overwrite existing host data properties; it only promotes newly created names into the host data.\n- Reads can see global built-in objects like `Math`, but writes go into the local scope instead of the real global environment.\n- `*let` is evaluated on each render of the element. Ensure that the code is safe to run multiple times.\n- There are no special combination restrictions for `*let` beyond its evaluation order:\n  - It may appear together with `*if`, `*switch`, `*each`, `*for`, and other directives on the same element.\n  - It simply runs first and prepares values for the rest of the directives on that element.\n",
  "literal": "### *literal\n\n#### Summary\n\n`*literal` is used when you want to keep Sercrod-style markup (or any template-like text) exactly as written, without Sercrod expanding or interpreting it.\nTypical use cases include:\n\n- Showing Sercrod examples in documentation.\n- Emitting `%placeholders%` or `*directives` as plain text for another system to process later.\n\nInside a `*literal` block:\n\n- Sercrod does not evaluate `%...%` interpolation.\n- Sercrod does not treat `*if`, `*for`, `@click`, or any other directives as behavior.\n- Only the `*literal` / `n-literal` attribute itself is removed in the final output; the content is kept as plain text.\n\n`*literal` has an alias `n-literal`.\n\n\n#### Basic example\n\nDisplay Sercrod markup as-is, so that it is shown as code instead of being executed:\n\n```html\n<serc-rod id=\"docs\">\n  <h2>Counter example</h2>\n  <pre *literal>\n<serc-rod data='{\"count\":0}'>\n  <button @click=\"count++\">+</button>\n  <span *print=\"count\"></span>\n</serc-rod>\n  </pre>\n</serc-rod>\n```\n\nBehavior:\n\n- Sercrod does not interpret the inner `<serc-rod>`, `@click`, or `*print` in the `<pre>` block.\n- The final DOM contains a `<pre>` whose text content is the Sercrod snippet, exactly as written.\n- The `*literal` attribute itself is removed by the cleanup phase, so end users will not see it.\n\n\n#### Behavior\n\nCore rules:\n\n- If an element has `*literal` or `n-literal`, Sercrod treats the element's content as plain text.\n- Directives and interpolation markers inside that element are not executed; they are preserved as characters.\n- The host element itself is kept (for example `<pre>`, `<p>`, `<div>`), but `*literal` / `n-literal` is removed from the output DOM when directive cleanup is enabled.\n\nSource of the text:\n\n- Sercrod first looks at the attribute value:\n\n  - If `*literal` / `n-literal` has a non-empty value, that string is used as the text.\n  - Example: `<div *literal=\"*if=&quot;cond&quot;\">...</div>` outputs `*if=\"cond\"` as text.\n\n- If the attribute value is empty or not specified:\n\n  - Sercrod uses the original `innerHTML` as the text.\n  - Example: `<pre *literal>...inner markup...</pre>` outputs the inner markup exactly as characters.\n\nIn both modes:\n\n- The chosen text is emitted without Sercrod expression evaluation.\n- Characters like `<`, `>`, `%`, `\"`, and `*` are not treated specially by Sercrod.\n\n\n#### \"Keep Sercrod as text\" use case\n\nThe primary design goal of `*literal` is to display Sercrod markup itself:\n\n- You can write Sercrod templates inside `*literal` blocks to show them as examples.\n- You can keep `%user.name%` or `%item.price%` as placeholders, ready for a different rendering engine.\n\nExample:\n\n```html\n<serc-rod id=\"docs\">\n  <p>Description</p>\n  <pre *literal>\n%user.name% ordered %item.name% at %item.price%.\n  </pre>\n</serc-rod>\n```\n\nResult:\n\n- Sercrod does not try to evaluate `%user.name%` or `%item.price%`.\n- The placeholders appear exactly as written in the rendered page.\n- Only `*literal` is removed from the markup; the rest is preserved as plain text inside `<pre>`.\n\n\n#### Attribute vs innerHTML source\n\nTwo common patterns:\n\n1. Boolean-style `*literal` (innerHTML source):\n\n   - No value, or an empty value:\n\n     ```html\n     <pre *literal>\n<serc-rod data='{\"count\":0}'>\n  <button @click=\"count++\">+</button>\n  <span *print=\"count\"></span>\n</serc-rod>\n     </pre>\n     ```\n\n   - Sercrod uses the inner HTML as the text source.\n   - The element becomes a `<pre>` with that snippet as its text content.\n   - The `*literal` attribute is removed.\n\n2. Value-style `*literal` (attribute source):\n\n   - Value is treated as a plain string, not as an expression:\n\n     ```html\n     <p *literal=\"*if=&quot;isVisible&quot;\">Will show *if=\"isVisible\"</p>\n     ```\n\n   - The visible text is `*if=\"isVisible\"`.\n   - The inner content (`Will show ...`) is ignored in this mode.\n   - Sercrod does not try to evaluate `isVisible`; it just prints the attribute value as text.\n\nImportant:\n\n- The `*literal` value is never interpreted as a Sercrod expression.\n- If you need data-driven text based on scope variables, use `*print` or bindings instead.\n\n\n#### Evaluation timing\n\n`*literal` is handled early in the rendering pipeline:\n\n- In the main render flow, Sercrod:\n\n  - Handles static/dynamic flags.\n  - Then checks for `*literal` / `n-literal`.\n\n- If `*literal` or `n-literal` is present:\n\n  - Sercrod decides the text to output (from the attribute or innerHTML).\n  - Sets the element's text content or appends a text node (depending on the internal path).\n  - Skips further processing for that node.\n\nBecause of this:\n\n- Any other directive on the same element as `*literal` is effectively ignored by the runtime.\n- Children of that element are not visited by Sercrod's directive engine.\n- The element becomes a \"literal island\" with no Sercrod behavior inside it.\n\n\n#### Execution model\n\nConceptually, for an element with `*literal`:\n\n1. Sercrod detects `*literal` / `n-literal` on the element.\n2. It reads:\n\n   - `attr = element.getAttribute(\"*literal\") || element.getAttribute(\"n-literal\")`.\n\n3. It determines the text:\n\n   - If `attr` is a non-empty string, `text = String(attr)`.\n   - Otherwise, `text = element.innerHTML` (as originally written in the template).\n\n4. It clears or replaces the element's children with a single text content node based on `text`.\n5. In the cleanup phase:\n\n   - `*literal` and `n-literal` attributes are removed from the output DOM.\n   - The element itself remains in the DOM (for example `<pre>`, `<p>`).\n\nAt no point does Sercrod interpret or expand directives, events, or `%...%` placeholders within that element.\n\n\n#### Variable creation and scope layering\n\n`*literal` does not create or modify any variables:\n\n- No new local variables are introduced.\n- `$data`, `$root`, `$parent`, and other scope entries are unaffected.\n- There is no per-child scope inside a `*literal` block, because Sercrod does not descend into the element to render children.\n\nYou can still use scope and data around the literal element:\n\n- Ancestors and siblings behave as usual.\n- Use outer `*if`, `*for`, or bindings on surrounding elements to control when and where the literal block appears.\n\n\n#### Parent access\n\nBecause Sercrod does not descend into `*literal` blocks:\n\n- There is no Nested Sercrod scope inside that element.\n- Inner markup (even if it looks like Sercrod) is just text; it cannot access `$parent` or other scope variables.\n- Parent scopes are only relevant for deciding whether the element itself is rendered at all (through directives on parent elements).\n\n\n#### Use with conditionals and loops\n\nThe rule becomes very straightforward once you think of `*literal` as \"keep everything inside as plain text\":\n\n- `*literal` and other directives on the same element conflict conceptually:\n\n  - `*if` or `*for` on the same element would suggest \"execute logic\".\n  - `*literal` says \"do not execute anything inside; keep it as text\".\n\n- Implementation-wise, `*literal` wins:\n\n  - Because `*literal` is handled early, Sercrod does not get to the other directives on that element.\n  - So `*if`, `*for`, `@click`, or any other directives on the same element effectively do nothing.\n\nFor this reason:\n\n- Do not combine `*literal` with other `*` / `n-` / `@` directives on the same element.\n\nInstead, use an outer element for logic:\n\n- Conditional display of a literal block:\n\n  ```html\n  <section *if=\"showExample\">\n    <pre *literal>\n<serc-rod data='{\"count\":0}'>\n  <button @click=\"count++\">+</button>\n  <span *print=\"count\"></span>\n</serc-rod>\n    </pre>\n  </section>\n  ```\n\n  - `*if` controls whether the whole example is shown.\n  - `*literal` ensures the inner Sercrod snippet is preserved as text.\n\n- Looping over literal snippets:\n\n  ```html\n  <ul *for=\"snippet of snippets\">\n    <li>\n      <pre *literal>\n%snippet.body%\n      </pre>\n    </li>\n  </ul>\n  ```\n\n  - `*for` repeats `<li>` for each `snippet`.\n  - Each `<pre *literal>` contains a literal template for that snippet.\n\n\n#### Best practices\n\n- Use `*literal` whenever you want Sercrod markup or `%placeholders%` to appear as text:\n\n  - Documentation for Sercrod itself.\n  - Email or template previews that use `%%`-style placeholders.\n  - Raw Markdown or other template languages that should not be touched by Sercrod.\n\n- Keep `*literal` alone on its element:\n\n  - Do not add `*if`, `*for`, `*each`, `@event`, or other directives to the same element.\n  - Place conditionals and loops on a parent element, and use `*literal` purely to protect the inner content.\n\n- Prefer innerHTML for larger literal blocks:\n\n  - For large code samples or long Markdown sections, use the boolean-style `*literal` and write the content in the body.\n  - Use attribute-style `*literal=\"...text...\"` for short strings that must remain literal.\n\n- Combine with `*rem` when you want no output:\n\n  - `*literal` is for \"keep literal content as text\".\n  - `*rem` is for \"keep in the template only; remove from rendered output\".\n\n\n#### Additional examples\n\nLiteral Sercrod block with outer logic:\n\n```html\n<serc-rod id=\"examples\" data='{\"show\":\"counter\"}'>\n  <section *if=\"show === 'counter'\">\n    <h3>Counter example</h3>\n    <pre *literal>\n<serc-rod data='{\"count\":0}'>\n  <button @click=\"count++\">+</button>\n  <span *print=\"count\"></span>\n</serc-rod>\n    </pre>\n  </section>\n</serc-rod>\n```\n\nLiteral placeholders for another system:\n\n```html\n<serc-rod id=\"mailer\">\n  <p *literal>\n%USER_NAME%, thank you for signing up.\nYour order number is %ORDER_ID%.\n  </p>\n</serc-rod>\n```\n\n- Sercrod does not expand `%USER_NAME%` or `%ORDER_ID%`.\n- The string is emitted exactly as written, ready for another mail-merge system.\n\n\n#### Notes\n\n- `*literal` and `n-literal` are aliases; choose one style per project for consistency.\n- `*literal` is evaluated early and prevents Sercrod from interpreting anything inside that element.\n- The main purpose is to keep Sercrod-style markup (or other templates) as text, while removing only the `*literal` directive itself from the final HTML.\n- Combining `*literal` with other directives on the same element is not supported in practice, because `*literal` short-circuits those directives; use parent elements for conditionals and loops instead.\n",
  "load": "### *load / *load.file / *load.session / *load.store\n\n#### Summary\n\n`*load.file` loads JSON data from a user-selected file and merges it into the Sercrod host’s data.\n`*load.session` loads JSON from browser `sessionStorage` and applies the same merge rules.\n`*load.store` loads JSON from persistent browser storage backed by IndexedDB and applies the same merge rules.\nIf a staged view is active (via `*stage`), the JSON is merged into the stage; otherwise it is merged into the live data.\n`*load` remains as the legacy-compatible file form, equivalent to `*load.file` when no storage suffix is used.\n\nTypical use:\n\n- Put `*load.file` on a button or other clickable element.\n- Use `*load.session=\"'key'\"` when the source is a `sessionStorage` key.\n- Use `*load.store=\"'key'\"` when the source is persistent browser storage.\n- Optionally use `*keys` to choose top-level properties from the JSON.\n- Optionally use `*into` to place the loaded value under one host data key.\n- Sercrod reads JSON from the selected source, parses it, merges it into data or stage, and triggers a re-render.\n\n\n#### Basic example\n\nA simple load button that merges the entire JSON into host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"user\":{\"name\":\"\",\"email\":\"\"}}'>\n  <p>Name: <span *print=\"user.name\"></span></p>\n  <p>Email: <span *print=\"user.email\"></span></p>\n\n  <button type=\"button\" *load.file>Load profile…</button>\n</serc-rod>\n```\n\nIf the user selects a JSON file like:\n\n```json\n{\n  \"user\": {\n    \"name\": \"Alice\",\n    \"email\": \"alice@example.com\"\n  }\n}\n```\n\nthen after loading:\n\n- `user.name` becomes `\"Alice\"`.\n- `user.email` becomes `\"alice@example.com\"`.\n- The view is refreshed automatically.\n\nLoad selected keys into one destination:\n\n```html\n<button type=\"button\" *load.file *keys=\"profile settings\" *into=\"draft\">\n  Load draft\n</button>\n```\n\nIn this form:\n\n- `*load.file` describes the browser file source.\n- `*keys` selects keys from the loaded JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from the current browser session:\n\n```html\n<button type=\"button\" *load.session=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load session draft\n</button>\n```\n\nIn this form:\n\n- `*load.session` describes the `sessionStorage` key.\n- `*keys` selects keys from the stored JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from persistent browser storage:\n\n```html\n<button type=\"button\" *load.store=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load persistent draft\n</button>\n```\n\nIn this form:\n\n- `*load.store` describes the persistent browser storage key.\n- Sercrod reads JSON from IndexedDB.\n- `*keys` and `*into` use the same merge rules as file and session loads.\n\n\n#### Behavior\n\n- `*load.file` is an action directive that attaches file-loading behavior to an element.\n- `*load.session` is an action directive that reads JSON from `window.sessionStorage` on click.\n- `*load.store` is an action directive that reads JSON from IndexedDB on click.\n- The directive works with the browser’s file picker and uses `FileReader` to read the chosen file.\n- Only JSON is expected; other file types are not supported by the current implementation.\n- The directive merges the parsed JSON into:\n\n  - `_stage`, if the host has a staged view (for example due to `*stage`).\n  - `_data`, otherwise.\n\n- The merge strategy depends on `*keys` and `*into`:\n\n  - No `*keys`, no `*into`: merge the entire JSON object into the target object with `Object.assign`.\n  - `*keys`, no `*into`: copy only those top-level properties into the target object.\n  - `*into`: place the loaded value under that one target key.\n\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event and calls `update()` on the host to re-render the view.\n\nAliases and compatibility:\n\n- `*load.file` and `n-load.file` are aliases.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain supported as the old file-load spelling.\n- In new examples, prefer explicit action forms such as `*load.file`, `*load.session`, or `*load.store` plus `*keys` and `*into` where needed.\n\n\n#### Storage and merge semantics\n\nKey and destination values:\n\n- If `*keys` and `*into` are omitted:\n\n  - The parsed JSON must be an object.\n  - Sercrod merges all enumerable properties into `_stage` or `_data`:\n\n    - With stage: `Object.assign(this._stage, json)`\n    - Without stage: `Object.assign(this._data, json)`\n\n- If `*keys` has a value:\n\n  - The value is split by whitespace into a list of property names:\n\n    - `*keys=\"user settings\"`\n\n      becomes `[\"user\",\"settings\"]`.\n\n  - For each property name `p`:\n\n    - With stage: `this._stage[p] = json[p]`\n    - Without stage: `this._data[p] = json[p]`\n\n  - Only direct top-level keys are supported; dotted paths or nested selectors are not interpreted.\n\n- If `*into` has a value:\n\n  - Without `*keys`, the entire loaded JSON is assigned to `data[into]`.\n  - With one key, `json[key]` is assigned to `data[into]`.\n  - With multiple keys, an object containing those keys is assigned to `data[into]`.\n\nOld spelling:\n\n```html\n<button *load=\"user settings\">Load user+settings</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax.\n\nError handling:\n\n- The chosen file is read as text and parsed with `JSON.parse`.\n- `*load.session` warns and makes no data change when the storage key is empty, inaccessible, missing, or contains invalid JSON.\n- `*load.store` warns and makes no data change when IndexedDB is unavailable, the key is missing, or the stored JSON cannot be parsed.\n- If parsing fails and Sercrod is configured to warn, the runtime logs:\n\n  - `[Sercrod warn] *load JSON parse: ...`\n\n- On parse error, no merge is performed and the view is not updated.\n\n\n#### File input integration\n\n`*load.file` and legacy `*load` work both with native file inputs and with regular clickable elements.\n\n- If the element is an `<input type=\"file\">`:\n\n  - Sercrod reuses the native file input.\n  - If the input has no `accept` attribute, Sercrod sets it to `\"application/json\"` by default.\n  - On `change`, the first selected file is read and processed.\n\n- If the element is not an `<input type=\"file\">`:\n\n  - Sercrod attaches a `click` handler to the element.\n  - When clicked, Sercrod creates a hidden `<input type=\"file\">`, sets its `accept` attribute, and forwards the selection to `*load`.\n  - The temporary file input is not meant to be visible or controlled directly.\n\nAccept attribute:\n\n- If the element has an `accept` attribute, Sercrod respects it.\n- If not, Sercrod uses `\"application/json\"` as the default.\n- This influences what the browser shows in the file picker but does not perform additional runtime validation beyond JSON parsing.\n\n\n#### Stage interaction\n\n`*load` is designed to cooperate with staged editing:\n\n- If the host has an active stage (for example due to `*stage`), `*load` merges into `_stage` instead of `_data`.\n- This lets you preview or edit the loaded data in a staged view and then decide when to apply it.\n\nTypical pattern:\n\n```html\n<serc-rod id=\"editor\" data='{\"doc\":{\"title\":\"\",\"body\":\"\"}}'>\n  <section *stage>\n    <label>\n      Title:\n      <input *input=\"doc.title\">\n    </label>\n\n    <label>\n      Body:\n      <textarea *input=\"doc.body\"></textarea>\n    </label>\n\n    <button type=\"button\" *load.file *keys=\"doc\">Load draft…</button>\n    <button type=\"button\" *apply>Apply</button>\n    <button type=\"button\" *restore>Restore</button>\n  </section>\n</serc-rod>\n```\n\nIn this pattern:\n\n- `*load.file *keys=\"doc\"` replaces the staged `doc` object with `json.doc` from the file.\n- `*apply` copies staged changes back into the live data.\n- `*restore` discards staged changes and returns to the last stable state.\n\n\n#### Evaluation timing\n\n- `*load` is evaluated when Sercrod renders the element that carries it.\n- During rendering:\n\n  - Sercrod clones the original element.\n  - It attaches the necessary event listeners for the selected load source.\n  - It appends the cloned element to the DOM and returns from the internal render function.\n\n- `*load` does not perform any data changes during rendering itself.\n  - Data changes happen later, in response to user interaction.\n  - File loads react to file selection.\n  - Session and store loads react to click and then read from browser storage.\n- When JSON is successfully loaded and merged, Sercrod explicitly calls `update()` on the host to re-run the render pipeline and update the view.\n\n\n#### Execution model\n\nConceptually, the runtime behaves like this for `*load`:\n\n1. Sercrod detects a load directive on an element.\n2. It clones the element.\n\n   - All attributes and children are copied as-is.\n   - The `*load` / `n-load` attribute is preserved on the clone for visibility, but Sercrod does not re-interpret it later.\n\n3. It resolves the action attribute:\n\n   - `*load.file` means browser file input.\n   - `*load.session` gives a `sessionStorage` key.\n   - `*load.store` gives a persistent browser storage key.\n   - legacy `*load` values are treated as old `*keys` syntax.\n\n4. It parses `*keys` and `*into`.\n\n5. For file loads, it determines the desired `accept` type:\n\n   - Uses the element’s own `accept` attribute if present.\n   - Otherwise, defaults to `\"application/json\"`.\n\n6. It wires source handling:\n\n   - For `*load.store`, it attaches a click listener that reads the JSON string from IndexedDB.\n   - For `*load.session`, it attaches a click listener that reads the JSON string from `sessionStorage`.\n   - For file loads, if the cloned element is an `<input type=\"file\">`:\n\n     - Ensures `accept` is set.\n     - Adds a `change` listener that calls `handleFile(file)` for the selected file.\n\n   - For file loads on any other element (button, link, etc.):\n\n     - Adds a `click` listener.\n     - That listener creates a temporary `<input type=\"file\">`, sets `accept`, and listens for `change`.\n     - When a file is chosen, it calls `handleFile(file)`.\n\n7. The common JSON path:\n\n   - Reads JSON text from the selected source.\n   - Parses the JSON.\n   - Merges it into `_stage` or `_data` according to `*keys` and `*into`.\n   - Dispatches `sercrod-loaded` with source details.\n   - Calls `update()`.\n\n8. The cloned element is appended to the parent in the rendered DOM; the original template node is not appended.\n\n\n#### Variable creation\n\n`*load` does not create new template variables:\n\n- It does not add loop variables, local aliases, or special names to the scope.\n- All changes happen directly in the host’s `_data` (or `_stage`) object.\n- Templates and expressions continue to use the regular data paths (`user`, `settings`, etc.) after the data is updated.\n\n\n#### Scope layering\n\n`*load` respects the existing scope model:\n\n- It operates on the Sercrod host’s data or stage, not on local loop scopes.\n- It does not change how `$data`, `$root`, or `$parent` are injected.\n- After a successful load, any expressions that read from the updated data see the new values at the next render.\n\nBecause `*load` is an action on the host data, it does not affect how inner scopes are layered; it only changes the values they eventually read.\n\n\n#### Parent access\n\n`*load` does not introduce a new parent object:\n\n- Parent access via `$parent` and `$root` remains unchanged.\n- Any templates that use `$parent` or `$root` simply see updated data after the load and re-render, as long as they reference the affected fields.\n\n\n#### Use with conditionals and loops\n\nYou can place `*load` inside conditional blocks or loops just like any other action element:\n\n- Inside `*if`:\n\n  - The element exists and is interactive only when the `*if` condition is truthy.\n\n  ```html\n  <div *if=\"canLoad\">\n    <button type=\"button\" *load.file>Load config…</button>\n  </div>\n  ```\n\n- Inside loops:\n\n  - Each iteration can have its own `*load` element, although typically you want just one loader per host.\n\n  ```html\n  <serc-rod data='{\"sections\":[{\"id\":1},{\"id\":2}]}'>\n    <section *each=\"section of sections\">\n      <h2 *print=\"section.id\"></h2>\n      <button type=\"button\" *load.file *keys=\"section\">Load section…</button>\n    </section>\n  </serc-rod>\n  ```\n\nRestrictions:\n\n- `*load` is not a structural directive and does not control how many times an element is rendered.\n- It is best used for standalone controls (buttons, links, inputs) rather than for elements that also carry structural directives like `*for` or `*each`.\n- Combining `*load` with other action directives that also replace the element (such as `*save`, `*post`, or `*fetch`) on the same element is not recommended:\n\n  - Only one branch in the internal evaluation order will run.\n  - Other directives on the same element will effectively be ignored.\n  - Use separate elements if you need multiple actions.\n\n\n#### Best practices\n\n- Use dedicated controls:\n\n  - Attach `*load` to buttons or file inputs specifically intended for loading data.\n  - Avoid mixing `*load` with other unrelated behaviors on the same element.\n\n- Keep the JSON shape predictable:\n\n  - Decide on a stable JSON schema for exports and imports (for example, via `*save`).\n  - Document which top-level properties exist (`user`, `settings`, etc.).\n\n- Use `*keys` for partial updates:\n\n  - When you want to protect unrelated data from being overwritten, specify only the properties you want to import:\n\n    - `*load.file *keys=\"user settings\"`\n\n- Combine with staged editing:\n\n  - Pair `*load` with `*stage`, `*apply`, and `*restore` to allow safe previewing of loaded data before committing.\n\n- Keep `*load` elements structurally simple:\n\n  - The element with `*load` is cloned and used as-is.\n  - Avoid relying on nested Sercrod directives inside the `*load` element itself; keep its content mostly static (plain text or icons).\n\n- Validate externally if needed:\n\n  - `*load` does basic JSON parsing only.\n  - If you require more validation (schema checks, versioning), perform it in code that reacts to `sercrod-loaded`.\n\n\n#### Storage backends and adapters\n\nThe default `*load.file` path is intentionally simple: open a file picker, read JSON text, parse it, merge it into `_stage` or `_data`, dispatch `sercrod-loaded`, and update the host. `*load.session` and `*load.store` use the same merge step after reading JSON text from browser storage.\n\nThat does not mean `*load` is only a file-picker feature. The important boundary is the merge step. Built-in session/store forms or a file adapter can read JSON from another local source, then pass the parsed value into the same load path.\n\nUseful browser storage patterns:\n\n- IndexedDB:\n\n  - Good for JSON snapshots, metadata, key-value records, and indexes.\n  - Good for remembering which saved item should be loaded later.\n  - Used by the built-in `*save.store` / `*load.store` JSON store.\n  - Can also store Blob values, but host data should normally keep only a key and metadata.\n  - Use it as the default when the payload type does not clearly require a file-like store.\n\n- OPFS:\n\n  - Good for larger app-local payloads and file-like working directories.\n  - Good when the application needs an internal workspace rather than a user-visible downloaded file.\n  - Usually pairs well with IndexedDB metadata or indexes.\n  - Prefer it for obvious image payloads and suspiciously large Blob/File values, with IndexedDB metadata as the lookup record.\n\nRecommended data shape:\n\n- Keep host data JSON-like and render-friendly.\n- Store large payloads outside host data.\n- Keep keys, names, MIME types, sizes, timestamps, and status fields in host data.\n- Let `*load` restore the JSON state or source record that the template actually reads.\n\nDo not create more backend-named directives just to name a storage backend. Prefer the action family (`*save.file`, `*save.session`, `*save.store`, and matching load forms), and put backend-specific behavior behind adapters or explicit helpers.\n\n\n#### Examples\n\nFull data import:\n\n```html\n<serc-rod id=\"app\" data='{\"config\":{\"theme\":\"light\",\"lang\":\"en\"}}'>\n  <pre *literal=\"JSON.stringify(config, null, 2)\"></pre>\n  <button type=\"button\" *load.file>Load config…</button>\n</serc-rod>\n```\n\nPartial import:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{},\"settings\":{}}'>\n  <button type=\"button\" *load.file *keys=\"user settings\">\n    Load user and settings\n  </button>\n</serc-rod>\n```\n\nCustom accept type on a native file input:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{}}'>\n  <input type=\"file\" accept=\"application/json,.json\" *load.file *keys=\"user\">\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*load.file` and `n-load.file` are aliases; choose one style for consistency.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain old compatible file-load spellings.\n- `*load.file` is designed for browser environments where `FileReader` and file dialogs are available.\n- `*load.store` requires IndexedDB.\n- The directive expects JSON text; other content types will fail JSON parsing.\n- When JSON parsing fails and warnings are enabled, Sercrod logs a warning and does not modify data.\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event:\n\n  - `detail.stage`: `\"load\"` for file/legacy loads, `\"load.session\"` for session loads, or `\"load.store\"` for store loads.\n  - `detail.host`: the Sercrod host element\n  - `detail.fileName`: the selected file name (or `null`)\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file loads.\n  - `detail.storageKey`: the storage key for `*load.session` or `*load.store`.\n  - `detail.into`: the `*into` destination key, or `null`.\n  - `detail.props`: the property list used for partial merge (or `null`)\n  - `detail.keys`: the same property list, provided for the unified action syntax.\n  - `detail.json`: the parsed JSON object\n\n  You can listen to this event on the host to perform additional validation or side effects.\n\n- For clarity and maintainability, avoid combining `*load` with other I/O directives (`*save`, `*post`, `*fetch`) on the same element; use separate elements for each distinct action.\n",
  "log": "### *log\n\n#### Summary\n\n`*log` evaluates an expression and logs its result together with the expression and a short host snippet.\nIt is meant as a lightweight debugging helper for Sercrod templates.\nThe alias `n-log` behaves the same.\n\n- On normal elements, `*log` prints to the browser console.\n- On `<pre *log>`, `*log` writes a formatted debug block into the `<pre>` element itself.\n\nLogging is side-effect free with respect to Sercrod data: `*log` does not modify the host's data.\n\n\n#### Basic example\n\nConsole logging a value:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\",\"age\":30}}'>\n  <div *log=\"user\"></div>\n\n  <p>\n    Hello,\n    <span *print=\"user.name\"></span>\n  </p>\n</serc-rod>\n```\n\nBehavior:\n\n- On first render, Sercrod evaluates `user` in the host scope.\n- In the browser console, it prints the value of `user` (an object), the expression string `\"user\"`, and a compact snippet of the `<div *log>` element.\n- The `<div>` itself stays empty in the DOM.\n\n\nUsing `<pre *log>` to display logs on the page:\n\n```html\n<serc-rod id=\"debug\" data='{\"items\":[1,2,3]}'>\n  <pre *log=\"items\"></pre>\n</serc-rod>\n```\n\nBehavior:\n\n- Sercrod evaluates `items`.\n- Instead of logging to the console, it writes a formatted block into the `<pre>` element as plain text.\n- The output includes the value of `items`, the expression, and a snippet of the `<pre *log>` itself.\n\n\n#### Behavior\n\nCore behavior:\n\n- `*log` evaluates an expression in the current Sercrod scope.\n- It formats three pieces of information:\n  - The value of the expression (or the scope when no expression is given).\n  - The expression string itself (or the literal `(scope)` when omitted).\n  - A compact snippet of the element that carries `*log`.\n\nWhere the log is sent:\n\n- On `<pre *log=\"...\">` or `<pre n-log=\"...\">`:\n  - Sercrod writes a multi-line debug message into `pre.textContent`.\n  - The content is plain text, not HTML, so markup is not interpreted.\n- On any other element:\n  - Sercrod prints to the browser console using `console.log`.\n  - The debug entry starts with `[Sercrod log]` and then prints the value, expression, snippet, and any error.\n\nSingle-shot semantics:\n\n- Each `*log` element logs exactly once per Sercrod host instance.\n- On subsequent updates of the same host, the same `*log` element is skipped by an internal `+logged` flag.\n- Newly created elements with `*log` (for example from loops or conditionals) will log once when they first appear.\n\n\n#### Expression rules\n\nThe attribute value of `*log` is a standard Sercrod expression:\n\n- Typical usage:\n\n  - `*log=\"user\"`\n  - `*log=\"user.name\"`\n  - `*log=\"items.length\"`\n  - `*log=\"state\"`\n\n- The alias `n-log` is identical in behavior:\n\n  - `n-log=\"user\"` can be used instead of `*log=\"user\"`.\n\nExpression is optional:\n\n- If an expression is provided:\n\n  - Sercrod calls `eval_expr(expr, scope, { el, mode: \"log\" })`.\n  - The result is used as the value to log.\n\n- If the attribute value is empty or missing:\n\n  - Sercrod uses the whole current scope as the value to log.\n  - The expression label in the output becomes `(scope)`.\n\nValue formatting:\n\n- For `null` or `undefined`:\n\n  - The internal value and string are treated as empty.\n  - The log entry still includes the expression and snippet.\n\n- For objects:\n\n  - The console receives the live object value when not using `<pre>`.\n  - For `<pre *log>`, Sercrod also tries to build a pretty JSON string using `JSON.stringify(value, null, 2)`.\n  - If `JSON.stringify` fails (for example because of circular references), the string becomes `[Sercrod warn] JSON stringify failed`.\n\n- For primitive values (string, number, boolean):\n\n  - The value is converted to a string and used for both console and `<pre>` output.\n\nError handling:\n\n- If evaluation throws an error:\n\n  - The value becomes a message like `[Sercrod warn] *log eval error: ...`.\n  - The expression and snippet are still shown.\n  - The error message is included in the console entry or `<pre>` text.\n\n\n#### Evaluation timing\n\n`*log` is tied to the lifecycle of the Sercrod host:\n\n- After the host completes its normal render (`_renderTemplate`) and the internal flag index is rebuilt, Sercrod schedules log evaluation.\n- The actual logging is triggered in a `requestAnimationFrame` callback:\n\n  - This means the DOM tree is fully updated and painted by the time `*log` runs.\n  - The snippet for the element (a compact `outerHTML` summary) reflects the final rendered markup.\n\n- `*log` runs after:\n\n  - Structural directives (such as `*if`, `*for`, `*each`, `*switch`).\n  - Data bindings and attribute bindings.\n  - The `*man` hooks.\n\n- Because logging is single-shot per element, a later data change does not cause the same `*log` element to log again.\n  - If you need to observe later states, you can:\n    - Place `*log` on elements created conditionally or through loops so that a new element appears when state changes.\n    - Or use your own event handlers and `console.log` in JavaScript.\n\n\n#### Execution model\n\nInternally, Sercrod executes `*log` roughly as follows:\n\n1. During render, Sercrod builds an index of elements that carry `*log` or `n-log`.\n2. After the host render and index rebuild, Sercrod schedules `_call_log_hooks(scope)` inside `requestAnimationFrame`.\n3. When `_call_log_hooks` runs:\n   - If `this.log` is `false`, it aborts immediately (global log-off for this host).\n   - It takes all elements indexed under the `*log` flag.\n4. For each element `el`:\n   - If `el` already has a `+logged` flag, it is skipped.\n   - Otherwise, Sercrod sets the `+logged` flag on `el`.\n   - Sercrod reads `expr` from `*log` or `n-log`.\n   - It evaluates `expr` (if provided) in the current scope, or uses `scope` directly when there is no expression.\n   - It computes:\n     - `val`: the value used for console output.\n     - `str`: the text representation (pretty JSON for objects when possible).\n     - `html`: a compact summary of `el.outerHTML`, collapsed whitespace and limited to 256 characters.\n5. Depending on the element type:\n   - If `el.tagName === \"PRE\"`:\n     - Sercrod builds a multi-line debug string with:\n       - A prefix line such as `[Sercrod pr]`.\n       - The stringified value.\n       - A line indicating the expression or `(scope)`.\n       - A line containing the snippet.\n       - An optional line for the error message when evaluation failed.\n     - Sercrod assigns this string to `el.textContent`.\n   - Otherwise:\n     - Sercrod calls `console.log` with a formatted block including:\n       - A label `[Sercrod log]`.\n       - The value, expression (or `(scope)`), snippet, and optional error message.\n\nThis model guarantees that each `*log` node contributes at most one entry and that the entry reflects the post-render state of the DOM.\n\n\n#### Scope and variables\n\n`*log` does not introduce new variables or change existing ones.\n\n- It reads the current scope as-is and evaluates the expression in that scope.\n- All usual scope features are available:\n  - Data defined on the host (`data` attribute).\n  - `$data`, `$root`, `$parent`, and other special injections.\n  - Methods defined through `*methods` or global helpers.\n\nBecause `*log` is read-only with respect to data:\n\n- It is safe to attach `*log` to elements that also use data bindings, conditions, or loops.\n- It will not interfere with expression results for other directives.\n\n\n#### Use with conditionals and loops\n\n`*log` composes naturally with other directives:\n\n- With `*if`:\n\n  - If `*if` on the same element or an ancestor removes the element from the DOM, there is nothing to log.\n  - If the element is rendered, `*log` will run once for that element.\n\n  ```html\n  <div *if=\"debugMode\" *log=\"state\">\n    <!-- This block is logged only when debugMode is truthy -->\n  </div>\n  ```\n\n- Inside loops:\n\n  - You can place `*log` on elements in `*for` or `*each` bodies to log each iteration once.\n\n  ```html\n  <ul *each=\"item of items\">\n    <li *log=\"item\" *print=\"item.label\"></li>\n  </ul>\n  ```\n\n  - Each `<li>` logs once when it first appears, using the `item` from that iteration.\n\n- With event handlers:\n\n  - `*log` does not react to events; it is evaluated only after render.\n  - For event-driven logging, use `console.log` or `_log` from your own handlers instead.\n\n\n#### Best practices\n\n- Use `<pre *log>` when you want visible debug output on the page:\n\n  - Helpful in environments where the browser console is not easily accessible.\n  - Keeps the content as plain text, so you can safely inspect JSON or other structured data.\n\n- Use `*log` sparingly in production:\n\n  - Logging is controlled by the `log` flag on the Sercrod host instance.\n  - You can disable all `*log` output for a host by setting `host.log = false` in JavaScript.\n\n- Prefer simple expressions:\n\n  - `*log` is most useful for inspecting the shape of objects and the values of key fields.\n  - Avoid very complex expressions; instead, log the data from which they are derived.\n\n- Combine with scopes:\n\n  - Use `*log=\"$data\"` to inspect the entire host data.\n  - Use `*log=\"$root\"` when you want to see the root Sercrod data tree.\n  - Use `*log=\"$parent\"` inside nested hosts to understand parent-child relationships.\n\n\n#### Examples\n\nInspect the entire host data:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\"},\"debug\":true}'>\n  <pre *log=\"$data\"></pre>\n</serc-rod>\n```\n\nInspect a subset of state:\n\n```html\n<serc-rod id=\"app\" data='{\"state\":{\"step\":1,\"status\":\"idle\"}}'>\n  <div *log=\"state.status\"></div>\n</serc-rod>\n```\n\nLog per item in a list (console only):\n\n```html\n<serc-rod id=\"list\" data='{\"items\":[{\"id\":1},{\"id\":2},{\"id\":3}]}'>\n  <ul>\n    <li *for=\"item of items\" *log=\"item.id\">\n      Item <span *print=\"item.id\"></span>\n    </li>\n  </ul>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*log` and `n-log` are aliases; choose a consistent style for your project.\n- `*log` is a diagnostic directive:\n  - It does not update data.\n  - It does not participate in structural decisions (unlike `*if`, `*for`, or `*each`).\n- Logging is controlled per Sercrod host via the `log` property:\n  - `this.log` defaults to `true` in the host constructor.\n  - Setting `host.log = false` (where `host` is a Sercrod element) disables `*log` output for that host.\n- Each element with `*log` logs only once per host instance due to the internal `+logged` flag.\n- There are no special combination restrictions for `*log`:\n  - It can share the same element with other directives such as `*if`, `*for`, `*each`, `*print`, `@events`, and so on.\n  - It does not compete for ownership of the host's children in the way structural directives like `*include` or `*import` do.\n",
  "man": "### *man / n-man\n\n#### Summary\n\n`*man` is the built-in manual and inspection directive for Sercrod.\n\nIt can show short built-in help in the console and, on `<pre>` elements, it can display longer manual text loaded from `man.json`.\n\n`*man` is intended for documentation, inspection, and debugging. It does not change application data and should not be treated as application logic.\n\n#### Output behavior\n\nOn a `<pre>` element:\n\n- `*man` without a value shows the top-level manual index.\n- `*man=\"directives\"` shows the directive list.\n- `*man=\"debug\"` shows the runtime debugging guide.\n- `*man=\"*post\"`, `*man=\"@click\"`, or `*man=\":style\"` shows the matching manual entry.\n- The text is written to `textContent`, so angle brackets in examples are displayed as text.\n\nOn other elements:\n\n- `*man` does not modify the DOM.\n- It writes a short summary and, when available, an example to the console.\n\n#### Key rules\n\nConcrete feature keys should include a prefix:\n\n- `*` for Sercrod directives, such as `*post`, `*fetch`, or `*input`.\n- `@` for event bindings, such as `@click`, `@submit`, or `@input`.\n- `:` for attribute bindings, such as `:text`, `:class`, or `:href`.\n\nThe same entry is used for `*xxx` and `n-xxx` forms.\n\nSpecial built-in aliases include:\n\n- `directives` - maps to the internal directive list.\n- `debug` - maps to the internal `__debug` entry.\n\n#### External man.json\n\nSercrod can load longer manual text from `man.json`.\n\nWhen the external file is available, `<pre *man=\"...\">` prefers the external full text for the requested key. Built-in short help remains available as a fallback and for console output.\n\nThis design keeps the runtime small while allowing projects to provide a detailed manual for humans, AI assistants, documentation tools, and runtime inspection tools.\n\n#### Examples\n\n```html\n<pre *man></pre>\n<pre *man=\"directives\"></pre>\n<pre *man=\"debug\"></pre>\n<pre *man=\"*post\"></pre>\n<p *man=\"@click\"></p>\n<span *man=\":style\"></span>\n```\n\nAttach `*man` to the same element you are using:\n\n```html\n<button *post=\"/api/contact.php:result\" *man=\"*post\">\n\tSend\n</button>\n```\n\nIn this pattern, `*man` documents the directive used on the element.\n\n#### Notes for AI assistants\n\n- Prefer the detailed external manual entry when it exists.\n- Use built-in short help only as a fallback or quick inspection aid.\n- Do not infer unsupported behavior from the existence of `*man`.\n- `*man` explains and exposes documentation. It does not perform the documented directive's action.\n",
  "methods": "### *methods\n\n#### Summary\n\n`*methods` and `n-methods` import global functions into a Sercrod host so that they can be called from expressions inside that host.\n\n- The attribute value is a space-separated list of names.\n- Each name refers to either:\n  - a function on `window`, or\n  - an object on `window` whose function properties should be imported.\n- Imported functions are injected into the expression scope for this host.\n- `*methods` is not a structural directive. It does not change the DOM. It only changes which functions are visible to expressions.\n\n`*methods` and `n-methods` are aliases. They behave identically.\n\n\n#### Basic example\n\nSingle global function:\n\n```html\n<script>\n  // Define a global helper\n  function toLabel(value){\n    return `Value: ${value}`;\n  }\n</script>\n\n<serc-rod id=\"app\" data='{\"value\": 1}' *methods=\"toLabel\">\n  <p *print=\"toLabel(value)\"></p>\n</serc-rod>\n```\n\nObject of functions:\n\n```html\n<script>\n  // Group helpers under a single global object\n  window.calc = {\n    inc(x){ return x + 1; },\n    double(x){ return x * 2; }\n  };\n</script>\n\n<serc-rod id=\"app\" data='{\"value\": 2}' *methods=\"calc\">\n  <p *print=\"inc(value)\"></p>\n  <p *print=\"double(value)\"></p>\n</serc-rod>\n```\n\nIn this example:\n\n- `*methods=\"calc\"` looks up `window.calc`.\n- Because it is an object, Sercrod imports each function property as a top-level name in the expression scope:\n  - `inc` and `double` become available directly.\n- There is no implicit `calc` variable in expressions. You call `inc(value)`, not `calc.inc(value)`.\n\n\n#### Behavior\n\n`*methods` is an attribute on the Sercrod host element:\n\n- It is observed by the custom element class via `observedAttributes`.\n- When the attribute changes, the value is split into tokens and stored as `_methods_names`.\n- When Sercrod evaluates expressions or `*let` blocks inside this host, it uses `_methods_names` to inject functions into the evaluation scope.\n\nKey properties:\n\n- The attribute is read-only from the template's point of view. It does not create or modify DOM nodes.\n- Changing `*methods` does not trigger a re-render by itself.\n  - It only affects which functions are visible to future expression evaluations in this host.\n- Import is non-destructive:\n  - If a name already exists in the evaluation scope, `*methods` does not overwrite it.\n\n\n#### Configuration syntax\n\n`*methods` and `n-methods` accept a space-separated list of identifiers:\n\n- On the Sercrod host:\n\n  ```html\n  <serc-rod\n    id=\"app\"\n    data='{\"value\": 10}'\n    *methods=\"toLabel calc\"\n  >\n    ...\n  </serc-rod>\n  ```\n\n- Equivalent alias:\n\n  ```html\n  <serc-rod\n    id=\"app\"\n    data='{\"value\": 10}'\n    n-methods=\"toLabel calc\"\n  >\n    ...\n  </serc-rod>\n  ```\n\nResolution for each token `name`:\n\n- If `window[name]` is a function:\n  - The function is injected as `name` into the expression scope.\n- Else if `window[name]` is an object (non-null):\n  - For each property `k` in `window[name]`:\n    - If `window[name][k]` is a function and that `k` is not already defined in the scope, it is injected as `k`.\n- Other cases (non-function primitives, null, undefined) are ignored.\n\nThe import model is intentionally simple:\n\n- You can import a single function, or\n- You can import many functions at once via a global object, with their keys as function names in the scope.\n\n\n#### Evaluation timing\n\n`*methods` influences expression evaluation at the point where Sercrod builds the sandbox for each expression.\n\n- For general expressions (such as `*if`, `*for`, `*each`, `*input`, interpolations, and most bindings), Sercrod calls `eval_expr(expr, scope, opt)`:\n  - It creates a `merged` scope object from the current `scope`.\n  - It injects special values such as `$data` and `$root`.\n  - It injects `$parent` if needed.\n  - It then injects functions from `*methods` via `_methods_names`.\n  - Finally, it injects Sercrod's internal methods from `_internal_methods`.\n\n- For `*let`, Sercrod calls `eval_let(expr, scope, opt)`:\n  - It starts from the current `scope`.\n  - It injects `$parent` if needed.\n  - It injects functions from `*methods`.\n  - It injects internal methods.\n  - It then evaluates the `*let` expression and writes back into the appropriate target.\n\nImportant points:\n\n- The `*methods` attribute is not re-evaluated on every expression; rather, its tokenized list `_methods_names` is reused.\n- Actual function lookup (`window[name]`) happens at evaluation time, based on the current global state.\n- If `*methods` changes at runtime:\n  - `_methods_names` is updated when the attribute changes.\n  - Future calls to `eval_expr` and `eval_let` reflect the new method list.\n  - No automatic re-render is triggered just by changing `*methods`.\n\n\n#### Execution model\n\nIn pseudocode, evaluation with `*methods` looks like this:\n\n- For `eval_expr` (used by directives such as `*if`, `*for`, `*each`, bindings):\n\n  1. Start with `merged = { ...scope }`.\n  2. Inject reserved helpers:\n     - `$data` from this host's `data`.\n     - `$root` from the root host's `data`, if any.\n     - `$parent` from the nearest ancestor Sercrod host's `data`, if not already set.\n  3. For each `name` in `_methods_names`:\n     - If `window[name]` is a function and `merged[name]` is undefined:\n       - Inject `merged[name] = window[name]`.\n     - Else if `window[name]` is a non-null object:\n       - For each `k` in `window[name]`:\n         - If `window[name][k]` is a function and `merged[k]` is undefined:\n           - Inject `merged[k] = window[name][k]`.\n  4. For each `k` in `Sercrod._internal_methods`:\n     - If `merged[k]` is undefined:\n       - Inject the built-in helper as `merged[k]`.\n  5. Evaluate the expression in `with(merged){ return (expr) }`.\n\n- For `eval_let`:\n\n  1. Start from `scope` (the target scope for `*let`).\n  2. If `$parent` is not yet set, inject it.\n  3. Inject methods from `_methods_names` using the same logic as in `eval_expr`.\n  4. Inject internal methods from `_internal_methods` where names are still free.\n  5. Evaluate the `*let` expression in a dedicated sandbox and commit assignments according to its mode.\n\nThis model guarantees:\n\n- Expressions cannot accidentally see arbitrary global variables unless you explicitly import them via `*methods` or put them into `data`.\n- Internal helpers are always available unless you intentionally override them.\n- The evaluation environment is stable and predictable.\n\n\n#### Scope and resolution order\n\nWhen you call `foo()` inside an expression on a host with `*methods`, Sercrod resolves `foo` in the following effective order:\n\n1. Local scope (loop variables, `*let` bindings, and similar).\n2. Host `data` and any stage buffer associated with it.\n3. `$parent` and `$root` references (if you explicitly use those names).\n4. Functions imported via `*methods` and `n-methods`:\n   - First, functions from global functions listed directly in `_methods_names`.\n   - Then, functions from global objects listed in `_methods_names`.\n5. Internal helpers from `_internal_methods` (only if the name is still free).\n\nCollisions:\n\n- If `data` defines `double`, and `*methods` also exposes a function named `double`, the `data` entry wins.\n- If two method containers both define `format`, only the first one listed in `*methods` is used for `format`.\n  - Later containers cannot overwrite existing names; imports only fill gaps.\n\nThis allows you to:\n\n- Keep built-in helpers available by default.\n- Override them explicitly in your own data or method containers when you need a different implementation.\n\n\n#### Use with conditionals, loops, and bindings\n\n`*methods` affects any directive that relies on `eval_expr` or `eval_let` inside this host. That includes:\n\n- Conditional directives:\n  - `*if`, `*elseif`, `*else` conditions can call imported methods.\n\n- Loop directives:\n  - `*for` and `*each` loop conditions and item expressions can call imported methods.\n\n- Data directives:\n  - `*let` expressions can call imported methods when computing derived values.\n  - `*input`, `*value`, and similar binding directives can call imported methods for formatting or parsing.\n\nExample: formatting in conditionals and loops\n\n```html\n<script>\n  window.userHelpers = {\n    isAdult(user){ return user.age >= 18; },\n    displayName(user){ return `${user.last}, ${user.first}`; }\n  };\n</script>\n\n<serc-rod\n  id=\"users\"\n  data='{\"users\":[{\"first\":\"Ann\",\"last\":\"Lee\",\"age\":22},{\"first\":\"Bob\",\"last\":\"Smith\",\"age\":15}]}'\n  *methods=\"userHelpers\"\n>\n  <ul *each=\"user of users\">\n    <li *if=\"isAdult(user)\">\n      <span *print=\"displayName(user)\"></span>\n    </li>\n  </ul>\n</serc-rod>\n```\n\nExample: derived values via `*let`\n\n```html\n<script>\n  function priceWithTax(price, rate){\n    return Math.round(price * (1 + rate));\n  }\n</script>\n\n<serc-rod\n  id=\"cart\"\n  data='{\"price\": 1000, \"taxRate\": 0.1}'\n  *methods=\"priceWithTax\"\n>\n  <p *let=\"total = priceWithTax(price, taxRate)\">\n    Subtotal: <span *print=\"price\"></span><br>\n    Total: <span *print=\"total\"></span>\n  </p>\n</serc-rod>\n```\n\n\n#### Use with events\n\nEvent handlers (`@click`, `@input`, and similar) are evaluated via a different helper (`eval_event`), which has a built-in window fallback.\n\n- Event expressions already see `window` by default.\n- This means you can call global functions directly from event handlers even without `*methods`.\n\nExample:\n\n```html\n<script>\n  function logClick(message){\n    console.log(\"clicked:\", message);\n  }\n</script>\n\n<serc-rod id=\"app\" data='{\"label\":\"Hello\"}'>\n  <button @click=\"logClick(label)\">\n    Click\n  </button>\n</serc-rod>\n```\n\nThis works even without `*methods`, because:\n\n- `eval_event` checks `window` if a name is not in the local base scope or parent data.\n\nWhere `*methods` still helps:\n\n- For consistency between events and non-event expressions.\n  - With `*methods`, you can call the same helpers in `*if`, `*let`, loops, and events.\n- For central control:\n  - Using `*methods` makes it explicit which global helpers are considered part of a host's public API.\n\nIn short:\n\n- Events can access `window` directly.\n- Non-event expressions require `*methods` (or explicit data) if you want to use global helpers.\n- Using `*methods` across the board keeps your templates more predictable and self-documenting.\n\n\n#### Best practices\n\n- Prefer method containers for related helpers:\n\n  - Group related functions into global objects and import them as a unit.\n\n  ```html\n  <script>\n    window.str = {\n      upper(s){ return String(s).toUpperCase(); },\n      lower(s){ return String(s).toLowerCase(); }\n    };\n  </script>\n\n  <serc-rod data='{\"name\":\"Ann\"}' *methods=\"str\">\n    <p *print=\"upper(name)\"></p>\n  </serc-rod>\n  ```\n\n- Keep the list small and explicit:\n\n  - Avoid importing large, unfiltered libraries directly into the expression scope.\n  - Instead, expose a curated wrapper object with only the helpers you actually use.\n\n- Avoid name collisions:\n\n  - Choose method names that are unlikely to collide with data keys or other helpers.\n  - When using multiple containers in `*methods`, order them so that the most important ones appear first.\n\n- Keep methods side-effect aware:\n\n  - Expressions are often re-evaluated during updates.\n  - Prefer methods that are pure or idempotent for use in `*if`, `*for`, `*each`, and formatting.\n  - Reserve side-effect-heavy operations for event handlers or dedicated APIs.\n\n- Do not rely on `*methods` outside Sercrod hosts:\n\n  - The attribute is only observed on the Sercrod custom element.\n  - Using `*methods` on a normal HTML element has no effect.\n\n\n#### Examples\n\nMultiple containers:\n\n```html\n<script>\n  window.math = {\n    add(a, b){ return a + b; },\n    mul(a, b){ return a * b; }\n  };\n\n  window.format = {\n    asCurrency(yen){\n      return `${yen} JPY`;\n    }\n  };\n</script>\n\n<serc-rod\n  id=\"checkout\"\n  data='{\"unitPrice\": 1200, \"quantity\": 3}'\n  *methods=\"math format\"\n>\n  <p *let=\"total = mul(unitPrice, quantity)\">\n    Total: <span *print=\"asCurrency(total)\"></span>\n  </p>\n</serc-rod>\n```\n\nOverriding internal helpers:\n\n- Sercrod injects internal helper methods after `*methods`.\n- If you want to override a built-in helper, you can define a function with the same name in data or import it via `*methods` before it would be filled from `_internal_methods`.\n\nExample (conceptual):\n\n```html\n<script>\n  window.helpers = {\n    htmlEscape(s){\n      // Your own implementation\n      return String(s).replace(/[&<>\"]/g, \"_\");\n    }\n  };\n</script>\n\n<serc-rod\n  id=\"app\"\n  data='{\"text\": \"<b>unsafe</b>\"}'\n  *methods=\"helpers\"\n>\n  <p *print=\"htmlEscape(text)\"></p>\n</serc-rod>\n```\n\nHere, if Sercrod has a built-in `htmlEscape`, your version imported via `*methods` will be used instead, because expression scopes are filled from data and imported methods before `_internal_methods`.\n\n\n#### Notes\n\n- `*methods` and `n-methods` are attributes on the Sercrod host. They are not general-purpose directives for arbitrary elements.\n- The attribute value is parsed as a space-separated list each time it changes.\n- For each name, Sercrod looks at `window[name]` at evaluation time:\n  - Functions are imported as-is under their own name.\n  - Objects contribute their function properties as top-level names.\n- Imported methods affect:\n  - `eval_expr` (conditions, loops, bindings).\n  - `eval_let` (local variable definitions).\n  - They do not change how `eval_event` falls back to `window`, although using `*methods` keeps usage consistent.\n- Imports only fill missing names. They never overwrite existing entries in the scope, data, or previously imported methods.\n- Internal helpers from `_internal_methods` are always available as a last resort, unless you override them via data or `*methods`.\n- There is no special runtime handling for `type=\"application/sercrod-methods\"` by itself. How you define and attach functions to `window` is up to your application; `*methods` simply imports those global functions into Sercrod's expression scope.\n",
  "post": "### *post\n\n#### Summary\n\n`*post` sends the current Sercrod host data as JSON via HTTP POST to a given URL and writes the JSON response back into host data.\nIt is typically attached to a button or similar control inside a Sercrod host.\n`*post` and `n-post` are aliases.\n\nKey points:\n\n- Serializes either the staged data or the live host data into JSON.\n- Sends that JSON as the request body for a POST request.\n- Parses the response, preferring JSON when the server advertises it.\n- Writes the parsed value back into host data using a simple `URL[:prop]` convention.\n- Updates common state flags such as `$pending`, `$error`, `$upload`, and `$download`.\n- Emits lifecycle events you can listen to from the outside.\n\n\n#### Basic example\n\nA minimal contact form that posts all data and stores the response in `result`:\n\n```html\n<serc-rod id=\"contact\" data='{\n  \"form\": { \"name\": \"\", \"message\": \"\" },\n  \"result\": null\n}'>\n  <form>\n    <label>\n      Name:\n      <input type=\"text\" *input=\"form.name\">\n    </label>\n\n    <label>\n      Message:\n      <textarea *input=\"form.message\"></textarea>\n    </label>\n\n    <button type=\"button\" *post=\"/api/contact.php:result\">\n      Send\n    </button>\n  </form>\n\n  <p *if=\"result\">\n    <strong>Server response:</strong>\n    <span *print=\"result.status\"></span>\n  </p>\n</serc-rod>\n```\n\nBehavior in this example:\n\n- When the button is clicked, `*post` takes the host data as JSON and sends it to `/api/contact.php`.\n- When the response returns and can be parsed, the value is written to `result`.\n- While the request is in flight, `$pending` is `true`.\n- If an error occurs at the transport or JSON level, `$error` is set and an error event is emitted.\n\n\n#### Behavior\n\nAttachment and rendering:\n\n- `*post` is evaluated when Sercrod renders a normal element inside a Sercrod host.\n- The runtime clones the element deep, including all attributes and children:\n\n  - Internally this is equivalent to `work.cloneNode(true)`.\n\n- A click listener is attached to the cloned element.\n- The cloned element is appended to the parent, and the original template node is not rendered.\n\nImportant consequence:\n\n- Sercrod does not recursively process directives on the children of the `*post` element itself.\n- The children of a `*post` button or link are treated as static markup.\n  - Do not put `*print`, `*if`, or other Sercrod directives on the same element or its direct children if you expect them to run.\n  - Place dynamic content in sibling elements or in the surrounding layout instead.\n\nTrigger timing:\n\n- `*post` always fires on a click on the rendered element, regardless of tag name.\n- There is no automatic initial POST and no special detection of clickable tag types.\n\n\n#### Request specification\n\nThe attribute value for `*post` uses a compact `URL[:prop]` format:\n\n- `URL` is the request target for the HTTP POST.\n- `prop` is an optional data path that controls where the response is written.\n\nFormat:\n\n- `*post=\"URL\"`\n  Send data to `URL` and replace the entire host data with the response.\n\n- `*post=\"URL:prop\"`\n  Send data to `URL` and write the response into `data[prop]`.\n\nProcessing rules:\n\n- The raw attribute is read from `*post` or `n-post` and trimmed.\n- If the result is empty, the runtime logs a warning (when warnings are enabled) and does nothing.\n- The value is split at the first `:`:\n  - The part before `:` is the URL.\n  - The part after `:` is the optional `prop` string, trimmed again.\n\nRestrictions and details:\n\n- URL is used as-is:\n  - There is no expression evaluation or placeholder expansion in the `*post` attribute.\n  - The string is not combined with any base URL from `*api`.\n- `prop` is a simple textual key, with one special case for bracket syntax described below.\n- If URL is missing after trimming, the runtime logs the same warning and aborts the operation.\n- When the attribute is malformed, no request is issued and no state flags are changed.\n\n\n#### Data source and JSON encoding\n\nSource of data:\n\n- `*post` uses the following source when building the request body:\n\n  - If the host has a staged buffer, `src = this._stage`.\n  - Otherwise, `src = this._data`.\n\n- In other words, when `*stage` is active for the host, `*post` sends the staged snapshot; otherwise it sends the live data.\n\nEncoding:\n\n- The source object is serialized using `JSON.stringify(src, null, 2)`.\n  - This yields a human readable JSON representation but the indentation has no functional effect.\n- If `JSON.stringify` throws (for example due to circular references or non-serializable values):\n  - The runtime logs a warning when warnings are enabled.\n  - No request is issued.\n  - No events are fired.\n  - `$pending` and `$error` are not changed.\n\nThe exact JSON string that will be sent is also exposed to the lifecycle events for debugging and logging.\n\n\n#### Response handling and writeback\n\nOnce the POST request completes, the response is handled in two stages:\n\n1. Derive a `value` and `text` from the HTTP response.\n2. Write `value` back into host data according to `prop`.\n\nContent negotiation:\n\n- The response `Content-Type` header is inspected:\n\n  - If it includes `application/json` (case insensitive):\n\n    - The runtime calls `await res.json()` to obtain `value`.\n    - It then computes `text` for event payloads:\n      - If `value` is already a string, use it as `text`.\n      - Otherwise, try to `JSON.stringify(value)`, falling back to `String(value)` if needed.\n\n  - Otherwise:\n\n    - The runtime reads `text = await res.text()`.\n    - It then tries `value = JSON.parse(text)`:\n      - If parsing succeeds, `value` is the resulting object or array.\n      - If parsing fails, `value` is the original text.\n\n- If `res.json()` throws when the content type promises JSON, the error path is taken (see below).\n\nData writeback:\n\n- Before writing, the runtime always stores the last write result into `$upload`:\n\n  - `this._data.$upload = value`.\n\n- Then the response is applied to host data using `prop`:\n\n  - When `prop` is non-empty:\n\n    - If `prop` matches the pattern `base[key]`:\n\n      - `base` and `key` are extracted textually.\n      - If `data[base]` is missing or not an object, it is initialized to `{}`.\n      - The runtime assigns:\n\n        - `data[base][key] = value`.\n\n    - Otherwise:\n\n      - The runtime assigns:\n\n        - `data[prop] = value`.\n\n  - When `prop` is empty or omitted:\n\n    - The entire host data is replaced by the response:\n\n      - `this._data = this._wrap_data(value)`.\n\n    - This re-wraps the root in Sercrod's proxy layer similarly to other data entry points.\n\nTracking:\n\n- Internally the runtime keeps a list of updated paths (such as `result` or `items[id]`) for event payloads.\n- These paths reflect the textual interpretation of `prop` and do not perform nested path parsing beyond the single `base[key]` pattern.\n\n\n#### State flags and error reporting\n\n`*post` shares the same state slots as `*api` and `*fetch`:\n\n- On first use, it ensures that the following properties exist on host data:\n\n  - `$pending`  - whether an HTTP operation is currently in flight.\n  - `$error`    - last error object or `null`.\n  - `$download` - reserved for fetch results and set to `null` here.\n  - `$upload`   - last write result, initially `null`.\n\nRequest lifecycle:\n\n- Before the request is issued:\n\n  - `$pending` is set to `true`.\n  - `$error` is reset to `null`.\n  - The host re-renders.\n\n- On success (including non 2xx status codes as long as the response can be read):\n\n  - `$pending` is set back to `false`.\n  - `$upload` holds the parsed result until it is later cleared by the host's finalize phase.\n  - `$error` remains `null`.\n\n- On error:\n\n  - `$pending` is set back to `false`.\n  - `$error` is set to an object of the form:\n\n    - `{ code: \"POST\", message: String(err) }`.\n\n  - The error path covers:\n    - Network failures or rejected fetch promises.\n    - Exceptions thrown by `res.json()` when the response claims to be JSON.\n\nLog output:\n\n- When `this.error.warn` is `true`, `*post` writes warnings and errors to the console:\n  - Missing or empty URL specification.\n  - JSON encode failures on the request side.\n  - POST failures on the response side.\n\nEphemeral nature of `$upload` and `$download`:\n\n- After each update cycle, Sercrod's finalize phase resets `$upload` and `$download` back to `null`.\n- Treat these properties as one-shot state indicators rather than long term storage.\n\n\n#### Events\n\n`*post` emits three dedicated events during its lifecycle:\n\n- `sercrod-post-start`\n\n  - Fired just before the request is issued.\n  - Only fired when the URL and spec are valid and JSON encoding succeeded.\n  - Event detail:\n\n    - `stage` - `\"post\"`.\n    - `host` - the Sercrod host instance.\n    - `url` - the request URL.\n    - `spec` - the original attribute string.\n    - `prop` - the parsed property key, possibly an empty string.\n    - `json` - the JSON string that will be sent as the request body.\n\n- `sercrod-post-done`\n\n  - Fired after a successful fetch and writeback.\n  - This includes non 2xx HTTP statuses as long as the response could be read and interpreted.\n  - Event detail:\n\n    - `stage`    - `\"post\"`.\n    - `host`     - the Sercrod host instance.\n    - `url`      - the request URL.\n    - `spec`     - the original attribute string.\n    - `prop`     - the parsed property key.\n    - `status`   - numeric HTTP status code.\n    - `response` - raw response text used for logging.\n    - `value`    - parsed value as written into data.\n    - `json`     - the JSON request body that was sent.\n    - `paths`    - array of touched paths such as `[\"result\"]` or `[\"items[id]\"]`.\n\n- `sercrod-post-error`\n\n  - Fired when the POST operation fails at the transport or JSON decoding level.\n  - Event detail:\n\n    - `stage` - `\"post\"`.\n    - `host`  - the Sercrod host instance.\n    - `url`   - the request URL.\n    - `spec`  - the original attribute string.\n    - `prop`  - the parsed property key.\n    - `error` - stringified error message.\n\nAll three events bubble and are composed, so you can listen for them at the host level or higher in the DOM tree.\n\n\n#### Integration with staged data (*stage, *apply, *restore)\n\n`*post` is designed to cooperate with Sercrod's staging directives:\n\n- When the host uses `*stage` or `n-stage`, the host maintains a staging buffer `_stage` that holds an editable snapshot of data.\n- `*post` looks at this buffer first:\n\n  - If `_stage` is not `null`, it is used as the POST body.\n  - If `_stage` is `null` or missing, `*post` falls back to `_data`.\n\nImplications:\n\n- Together with `*stage`, `*apply`, and `*restore` you can:\n\n  - Let users edit a staged copy of data.\n  - Submit that staged copy with `*post` without immediately changing `_data`.\n  - Decide later when to apply staged values into the live state.\n\n- This means `*post` can be used both for optimistic flows (post and then apply) and for flows where a server response is needed before committing.\n\n\n#### Relation to *fetch and *api\n\n`*post` shares several concepts with `*fetch` and `*api`, but with distinct responsibilities:\n\n- `*post`:\n\n  - Always uses HTTP POST.\n  - Always sends JSON built from the whole staged or live data object.\n  - Uses `URL[:prop]` and the same writeback rules as `*fetch`.\n  - Maintains state flags (`$pending`, `$error`, `$download`, `$upload`).\n  - Emits dedicated `sercrod-post-*` events.\n\n- `*fetch`:\n\n  - Typically uses HTTP GET to load data into the host.\n  - Uses `path/to.json[:prop]` and a similar writeback mechanism.\n  - Can be attached to hosts or normal elements.\n  - Provides automatic initial fetch for certain elements.\n\n- `*api`:\n\n  - General purpose HTTP client directive.\n  - Supports configurable methods, bodies, and file uploads.\n  - Uses `*into` or `n-into` to select a data target instead of `URL[:prop]`.\n  - Reuses `$pending`, `$error`, `$download`, and `$upload`.\n\nWhen to use which:\n\n- Use `*post` when:\n\n  - You want a simple \"send current state as JSON\" button.\n  - You do not need per-request body expressions or dynamic URLs.\n  - You want a symmetric counterpart to `*fetch` with similar writeback rules.\n\n- Use `*api` when:\n\n  - You need more control over HTTP method, body, URL composition, or file uploads.\n  - You want to send only a subset of data or a custom payload.\n\n\n#### Server-side contract and recommended API shape\n\nBecause `*post`, `*fetch`, and `*api` all treat HTTP communication as \"JSON in, JSON out\" and share the same state flags, it is natural to standardize server-side handlers around this contract.\n\nRecommended approach on the server:\n\n- Treat Sercrod-driven endpoints as JSON endpoints:\n\n  - Always accept a JSON request body for write operations.\n  - Always return a JSON response for both success and application-level errors.\n  - Use a stable envelope shape so that `URL[:prop]` and `*into` can be wired consistently.\n\n- Reuse the same processing pipeline:\n\n  - Parse JSON.\n  - Run validation, authentication, business logic, and logging in a shared middleware.\n  - Produce a JSON object that Sercrod can store as-is into `data[prop]`, `data[base][key]`, or a target selected by `*into`.\n\nBenefits for server-side code:\n\n- You can implement a \"Sercrod API style\" once and reuse it across multiple endpoints.\n- Monitoring and logging become easier because every Sercrod request and response has the same structure.\n- Frontend and backend teams can agree on a single JSON contract instead of negotiating many small variations.\n\nPosition in Sercrod's design:\n\n- Sercrod does not force this server-side style, but the runtime is optimized around it:\n  - `*post` and `*fetch` share the `URL[:prop]` rule and write values back without further transformation.\n  - `*api` writes the raw response into the variable named by `*into`.\n  - All of them update `$pending`, `$error`, `$download`, and `$upload` in a consistent way.\n- For new projects that adopt Sercrod end to end, designing server APIs to follow this unified JSON contract is strongly recommended.\n- For existing APIs, you can:\n  - Use `*api` to integrate with legacy endpoints as they are.\n  - Gradually introduce Sercrod-style JSON endpoints for new features and move existing endpoints toward the same contract when possible.\n\n\n#### Best practices\n\n- Keep the `*post` element simple:\n\n  - Treat it as a button or link with static content.\n  - Avoid placing other Sercrod directives on the same element or inside it.\n\n- Choose `prop` carefully:\n\n  - Use `:result` or similar when you want to preserve the rest of the host data.\n  - Omit `:prop` only when you intentionally want to replace the entire root data with the response.\n\n- Use the bracket notation for keyed maps:\n\n  - `*post=\"/api/save.php:items[id]\"` writes the response into `data.items[id]`.\n  - Sercrod initializes `data[base]` as an object if needed.\n\n- Watch `$pending` and `$error`:\n\n  - Bind elements to `$pending` to disable buttons or show spinners while the request is running.\n  - Bind to `$error` to show a simple error message when something fails at the transport or JSON level.\n\n- Use events for advanced handling:\n\n  - Listen for `sercrod-post-done` when you need status code based handling or logging.\n  - Listen for `sercrod-post-error` when you need centralized error reporting.\n\n- Avoid non-serializable values:\n\n  - Ensure that the staged or live data does not contain functions, DOM nodes, or cyclic references that cannot be JSON encoded.\n  - If JSON encoding fails, no request is sent and only a warning is produced when warnings are enabled.\n\n- Align server APIs with Sercrod's JSON contract:\n\n  - Prefer designing endpoints that accept JSON bodies and return JSON values that can be stored directly in Sercrod data.\n  - Use a consistent envelope or field naming scheme so that `URL[:prop]` and `*into` can be mapped mechanically.\n  - This reduces glue code on both sides and makes error handling and logging easier.\n\n\n#### Notes\n\n- `*post` and `n-post` are exact aliases. Only one should be used per element.\n- `*post` is only handled on regular elements inside a Sercrod host. It does not have special host level behavior.\n- The `*post` attribute is treated as a literal string. There is no expression expansion or base URL resolution.\n- The response is always treated as successful from a data perspective as long as it can be read:\n  - HTTP status codes other than 2xx still result in a `sercrod-post-done` event.\n  - If you need application level error handling, use the status code in that event.\n- The writeback rule follows the same design as `*fetch`:\n  - `:prop` writes into `data[prop]` or `data[base][key]` for bracket syntax.\n  - No `:prop` replaces the entire data root.\n- There are no additional structural restrictions specific to `*post`, but you should keep the clickable element free of other directives to avoid unexpected behavior due to the way it is cloned and inserted.\n",
  "prevent-default": "### *prevent-default\n\n#### Summary\n\n`*prevent-default` attaches passive helpers that call `event.preventDefault()` for common UI events on the host element:\n\n- Prevent the default action for the Enter key on that element.\n- Optionally prevent form submission for `submit` events.\n\nIt has a short alias `*prevent`, and both directives share the same behavior and options.\n`n-prevent-default` and `n-prevent` are attribute aliases for the same feature.\n\nUse `*prevent-default` when you always want to suppress the browser's default behavior for Enter and/or form submission on a specific element, regardless of what your own event handlers do.\n\n\n#### Basic example\n\nPrevent Enter from submitting a search form, but still handle the submit event in Sercrod:\n\n```html\n<serc-rod id=\"search\" data='{}'>\n  <form *prevent-default=\"'submit'\" @submit=\"runSearch()\">\n    <input type=\"search\" name=\"q\" placeholder=\"Search...\" />\n    <button type=\"submit\">Search</button>\n  </form>\n</serc-rod>\n```\n\nBehavior:\n\n- Clicking the submit button or pressing Enter inside the form fires your `@submit` handler `runSearch()`.\n- After your handler runs, `*prevent-default` calls `event.preventDefault()` on the `submit` event, so the browser does not reload or navigate.\n\n\n#### Behavior\n\n`*prevent-default` is not a structural directive; it does not change how the DOM is rendered.\nInstead, it wires extra event listeners on the rendered element:\n\n- It reads an optional mode from its attribute value.\n- Based on the mode, it attaches one or both of:\n\n  - A `keydown` listener for Enter on that element.\n  - A `submit` listener on the element when it is a `FORM`.\n\n- These listeners always call `event.preventDefault()` when they fire.\n- Your own `@event` handlers still run as normal; `*prevent-default` runs in addition to them.\n\nAliases:\n\n- `*prevent-default` and `*prevent` behave identically.\n- `n-prevent-default` and `n-prevent` are attribute-name aliases for the same directive.\n\n\n#### Modes\n\nThe directive supports a small set of string modes.\nThe attribute value is read and normalized as:\n\n- If the value is empty or missing: `\"enter\"`.\n- Otherwise: lowercase of the value (for example `\"ENTER\"` -> `\"enter\"`).\n\nRecognized modes:\n\n- `\"enter\"` (default):\n\n  - Attaches a `keydown` listener on the element.\n  - When the user presses Enter (`key === \"Enter\"`), `event.preventDefault()` is called.\n\n- `\"submit\"`:\n\n  - If the element is a `FORM`, attaches a `submit` listener.\n  - When the form is submitted, `event.preventDefault()` is called.\n\n- `\"all\"`:\n\n  - Combines both behaviors:\n    - Prevent default on Enter keydown.\n    - Prevent default on form submission (if the element is a `FORM`).\n\nAny other string:\n\n- If the normalized mode is not `\"enter\"`, `\"submit\"`, or `\"all\"`, no listeners are attached and the directive has no effect.\n\n\n#### Evaluation timing\n\n`*prevent-default` is evaluated when Sercrod renders the host element and sets up its event handlers:\n\n- The attribute value is read once during render.\n- The mode is determined at that time.\n- The corresponding listeners are attached to the real DOM element.\n\nOn re-render:\n\n- When the element is re-rendered, Sercrod creates a fresh DOM element and re-applies `*prevent-default` to that instance.\n- The directive does not dynamically reconfigure listeners based on later data changes; changes to the mode expression take effect on the next render of that element.\n\n\n#### Execution model\n\nConceptually, when Sercrod processes an element that has `*prevent-default` or `*prevent`:\n\n1. Determine the mode:\n\n   - Read `*prevent-default` or `*prevent` from the template node.\n   - If it is empty or missing, use `\"enter\"`; otherwise, use the lowercase string value.\n\n2. Attach listeners on the rendered element:\n\n   - For `\"enter\"` and `\"all\"`:\n     - Add `keydown` listener.\n     - If `e.key === \"Enter\"`, call `e.preventDefault()`.\n\n   - For `\"submit\"` and `\"all\"`:\n     - If the element is a `FORM`, add `submit` listener.\n     - In that listener, always call `e.preventDefault()`.\n\n3. The directive does not alter the element's scope or its child rendering.\n   It only adds these helper listeners.\n\nInteraction with `@event` handlers:\n\n- Sercrod also registers handlers for attributes like `@click`, `@submit`, `@keydown`, etc.\n- Those handlers are attached separately from `*prevent-default`.\n- On an actual event:\n\n  - Your handler from `@event` runs.\n  - The helper from `*prevent-default` runs and calls `preventDefault()` when its conditions match.\n\nThe exact order of handler execution is an implementation detail, but you can rely on the fact that `preventDefault()` will be called for the configured modes, regardless of whether your handler calls it.\n\n\n#### Scope and variables\n\n`*prevent-default`:\n\n- Does not create new variables.\n- Does not modify the evaluation scope.\n- Does not introduce `$event` or `$e` by itself.\n\nUse inside event handlers:\n\n- When you define `@event` handlers, Sercrod evaluates expressions in the normal template scope.\n- The directive's only responsibility is to add the preventive listeners; it does not change how event handler expressions are evaluated.\n\n\n#### Use with event handlers and modifiers\n\n`*prevent-default` is designed to complement Sercrod's event attributes:\n\n- With `@submit`:\n\n  ```html\n  <form *prevent-default=\"'submit'\" @submit=\"save()\">\n    <!-- fields -->\n  </form>\n  ```\n\n  - Your `save()` handler runs.\n  - The directive ensures the form does not perform the default browser submit.\n\n- With key handling:\n\n  ```html\n  <input\n    type=\"text\"\n    *prevent-default=\"'enter'\"\n    @keydown=\"onKey()\"\n  />\n  ```\n\n  - Pressing Enter triggers `@keydown=\"onKey()\"`.\n  - The helper calls `preventDefault()` on the Enter `keydown` event.\n\nRelationship to event modifiers:\n\n- Sercrod's event system supports modifiers like `@click.prevent` or `@submit.stop`.\n- Those modifiers apply per handler and control behavior only when that particular handler runs.\n- `*prevent-default` is a separate, directive-level helper that:\n\n  - Does not depend on any particular `@event`.\n  - Works even if you omit `.prevent` on your handlers.\n  - Can be used together with event modifiers if you want both patterns.\n\n\n#### Best practices\n\n- Choose the simplest suitable mode:\n\n  - Use `\"enter\"` when you only want to block Enter key behavior on an element (for example to prevent implicit submits in forms with custom behavior).\n  - Use `\"submit\"` when you want to stop default form submission but still handle `@submit` yourself.\n  - Use `\"all\"` on forms when you want both behaviors at once (block Enter and block submit).\n\n- Make the mode explicit:\n\n  - Even though `\"enter\"` is the default, it is clearer to write the mode explicitly in most cases:\n\n    ```html\n    <form *prevent-default=\"'submit'\" @submit=\"save()\">\n      ...\n    </form>\n    ```\n\n  - Explicit modes help avoid confusion for future readers of the template.\n\n- Combine with `@submit` instead of removing it:\n\n  - `*prevent-default` is not a replacement for an actual `@submit` handler.\n  - Prefer to keep `@submit` for your logic and use the directive only to suppress the browser's navigation or reload.\n\n- Avoid unsupported modes:\n\n  - Do not rely on arbitrary strings like `\"click\"` or `\"change\"` as modes; they are currently ignored.\n  - For per-event control outside the built-in modes, use event modifiers such as `@click.prevent`.\n\n\n#### Additional examples\n\nPrevent both Enter and submit on a login form:\n\n```html\n<serc-rod id=\"login\" data='{}'>\n  <form *prevent-default=\"'all'\" @submit=\"doLogin()\">\n    <label>\n      Email\n      <input type=\"email\" name=\"email\" />\n    </label>\n    <label>\n      Password\n      <input type=\"password\" name=\"password\" />\n    </label>\n    <button type=\"submit\">Sign in</button>\n  </form>\n</serc-rod>\n```\n\nUse the short alias `*prevent`:\n\n```html\n<serc-rod id=\"contact\" data='{}'>\n  <form *prevent=\"'submit'\" @submit=\"sendMessage()\">\n    <textarea name=\"body\"></textarea>\n    <button type=\"submit\">Send</button>\n  </form>\n</serc-rod>\n```\n\nIn both examples:\n\n- The form's default submission is suppressed by the directive.\n- Your handler is responsible for sending data (for example via `fetch`, `*post`, or another mechanism).\n\n\n#### Notes\n\n- `*prevent-default` and `*prevent` are non-structural event helpers; they never change the rendered DOM structure.\n- Their effect is limited to adding listeners for the Enter `keydown` event and the `submit` event (for form elements).\n- Modes other than `\"enter\"`, `\"submit\"`, and `\"all\"` are ignored by the current implementation.\n- The directive is independent of event modifiers like `.prevent` or `.stop` and can be composed with them when needed.\n",
  "prevent": "### *prevent\n\n#### Summary\n\n`*prevent` is a shorthand for `*prevent-default`.\nIt attaches low-level listeners that call `event.preventDefault()` for specific events on the host element:\n\n- Enter key presses (`keydown`).\n- Form `submit` events (when the host is a `<form>`).\n- Or both, depending on the mode.\n\nUnlike many other directives, the attribute value is not a Sercrod expression.\nIt is a simple mode string such as `enter`, `submit`, or `all`.\n\n\n#### Basic example\n\nPrevent the browser's default form submission, but still run your handler:\n\n```html\n<serc-rod id=\"form-app\" data='{\"status\": null}'>\n  <form *prevent=\"submit\" @submit=\"status = 'saved'\">\n    <input type=\"text\" name=\"title\">\n    <button type=\"submit\">Save</button>\n    <p *if=\"status\" *print=\"status\"></p>\n  </form>\n</serc-rod>\n```\n\nIn this example:\n\n- The browser's built-in form submission is blocked.\n- The `@submit=\"status = 'saved'\"` handler still runs.\n- The page does not navigate away; you stay in the current Sercrod host.\n\n\n#### Behavior\n\n`*prevent` and `*prevent-default` are handled by the same runtime branch:\n\n- If the element has either `*prevent-default` or `*prevent`, Sercrod:\n\n  - Reads the raw attribute value.\n  - Interprets it as a mode string.\n  - Installs one or more DOM event listeners on the host element.\n\n- Supported modes (case-insensitive):\n\n  - `enter` (default)\n  - `submit`\n  - `all`\n\n- Defaulting:\n\n  - If the attribute value is empty or omitted, the mode is treated as `enter`.\n\nBehavior by mode:\n\n- `enter`:\n\n  - Attaches a `keydown` listener to the host.\n  - When `e.key === \"Enter\"`, it calls `e.preventDefault()`.\n  - No `submit` listener is installed.\n\n- `submit`:\n\n  - If the host element is a `<form>`, attaches a `submit` listener.\n  - That listener always calls `e.preventDefault()` on submit.\n  - No `keydown` listener is installed for Enter.\n\n- `all`:\n\n  - Installs both:\n\n    - `keydown` listener that blocks the Enter key.\n    - `submit` listener on `<form>` that blocks form submission.\n\nAny other mode string:\n\n- If the mode is not `enter`, `submit`, or `all`, no listeners are attached.\n- The directive effectively does nothing in that case.\n\n\n#### Relationship to *prevent-default\n\n`*prevent` is purely syntactic sugar:\n\n- The runtime branch is shared:\n\n  - It checks for `*prevent-default` or `*prevent`.\n  - It uses whichever attribute is present to read the mode string.\n\n- This means:\n\n  - `*prevent-default` and `*prevent` accept the same set of modes.\n  - They produce the same effect when given the same mode.\n\nTypical usage patterns:\n\n- Verbose form:\n\n  ```html\n  <form *prevent-default=\"all\" @submit=\"save()\">\n    ...\n  </form>\n  ```\n\n- Shorthand:\n\n  ```html\n  <form *prevent=\"all\" @submit=\"save()\">\n    ...\n  </form>\n  ```\n\nIn both cases:\n\n- The browser's default form submission is blocked.\n- Sercrod event handlers attached via `@submit` still run.\n\n\n#### Modes in detail\n\nMode string handling:\n\n- The raw attribute value is not evaluated as an expression.\n- It is treated as plain text and converted to lower case internally.\n- Whitespace is not trimmed inside; you should write simple tokens such as `enter` or `submit`.\n\nSupported modes:\n\n- `enter` (default):\n\n  - Installs `keydown` listener:\n\n    - On any element, when Enter is pressed (`e.key === \"Enter\"`), `e.preventDefault()` is called.\n\n  - Recommended when:\n\n    - You want to prevent Enter from triggering built-in behaviors, for example:\n\n      - Implicit form submit.\n      - Activating a focused control in a way you do not want.\n\n- `submit`:\n\n  - Effective only when the host is a `<form>`.\n  - Installs a `submit` listener that always calls `e.preventDefault()`.\n\n  - Recommended when:\n\n    - You want full manual control over form submission via `@submit` or other handlers.\n    - You intend to submit via `fetch`, `XMLHttpRequest`, or some other custom logic.\n\n- `all`:\n\n  - Combines the two:\n\n    - Blocks Enter key default on the host.\n    - Blocks native form submit if the host is a `<form>`.\n\n  - Recommended when:\n\n    - You want to make sure that neither Enter nor submit cause any built-in navigation or reload.\n\n\n#### Evaluation timing\n\n`*prevent` and `*prevent-default` are processed when Sercrod renders each element:\n\n- After attribute-based handlers (`@click`, `@submit`, and so on) are attached.\n- Before Sercrod moves on to other non-structural directives on the same element.\n\nImportant points:\n\n- The mode is read once when the element is processed.\n- The mode string is not re-evaluated on subsequent updates.\n  - Changing the attribute value after initial render does not reconfigure the listeners.\n- The listeners remain attached for the lifetime of the host element.\n\n\n#### Execution model\n\nConceptually, the runtime flow for `*prevent` looks like:\n\n1. Detect:\n\n   - Check whether the node has `*prevent-default` or `*prevent`.\n\n2. Read mode:\n\n   - `raw = value of \"*prevent-default\" or \"*prevent\" (whichever exists)`.\n   - `mode = (raw || \"enter\").toLowerCase()`.\n\n3. Attach listeners:\n\n   - If `mode` is `enter` or `all`:\n\n     - Add a `keydown` listener like:\n\n       - On `keydown`, if `e.key === \"Enter\"`, call `e.preventDefault()`.\n\n   - If `mode` is `submit` or `all` and the host is a `<form>`:\n\n     - Add a `submit` listener that always calls `e.preventDefault()`.\n\n4. Continue:\n\n   - Sercrod continues processing other directives and attributes as usual.\n   - Event handlers registered via `@...` attributes are not replaced; they remain intact.\n\n\n#### Interaction with Sercrod event handlers\n\n`*prevent` is complementary to the `@` event system:\n\n- `*prevent` controls whether the browser's default action runs.\n- `@event=\"handler(...)\"` controls which Sercrod expression is executed.\n\nExample: manual form handling\n\n```html\n<serc-rod id=\"login-app\" data='{\"error\": null}'>\n  <form *prevent=\"submit\" @submit=\"login()\">\n    <input type=\"text\" name=\"user\">\n    <input type=\"password\" name=\"pass\">\n    <button type=\"submit\">Log in</button>\n    <p *if=\"error\" *print=\"error\"></p>\n  </form>\n</serc-rod>\n```\n\nIn this setup:\n\n- The `submit` event is delivered to the Sercrod handler `login()`.\n- The browser does not perform a real HTTP form submission.\n- You are free to implement `login()` with `fetch`, show errors in the page, etc.\n\nKey points:\n\n- `*prevent` does not automatically invoke your handlers.\n- It only changes whether the browser's built-in behavior (navigation, form submit) happens.\n- You should still define `@submit`, `@keydown`, or other `@event` handlers as needed.\n\n\n#### Use cases\n\nTypical scenarios where `*prevent` is useful:\n\n- Prevent accidental form submissions:\n\n  - When you want to treat the Enter key as \"do nothing\" or \"move focus\" instead of \"submit now\".\n\n- Single-page flows:\n\n  - When forms are handled entirely in JavaScript, and navigating away would destroy state.\n\n- Controlled forms:\n\n  - When you have a Sercrod-based validation or saving flow and do not want an actual page reload.\n\n\n#### Best practices\n\n- Always specify a mode for clarity:\n\n  - `*prevent=\"submit\"` for forms.\n  - `*prevent=\"enter\"` for non-form controls where Enter should be ignored.\n  - `*prevent=\"all\"` when you want both behaviors.\n\n- Prefer `*prevent` for readability:\n\n  - Use `*prevent=\"submit\"` instead of the longer `*prevent-default=\"submit\"` wherever you can.\n  - Both are supported, but `*prevent` is shorter and easier to scan.\n\n- Keep in mind it is not an expression:\n\n  - Do not write `*prevent=\"enablePrevent ? 'submit' : null\"`.\n  - The directive will treat that entire string literally and not evaluate it.\n\n- Combine with `@` handlers:\n\n  - `*prevent` is most useful when combined with explicit handlers that implement your custom logic.\n  - For example, `*prevent=\"submit\" @submit=\"saveDraft()\"` gives you full control without navigation.\n\n\n#### Additional examples\n\nBlock Enter on a text input:\n\n```html\n<input type=\"text\"\n       *prevent=\"enter\"\n       @keydown=\"onKeydown($event)\">\n```\n\n- Enter key default is blocked.\n- Your `onKeydown` handler still receives the event via `@keydown`.\n\nBlock both Enter and submit on a form:\n\n```html\n<form *prevent=\"all\" @submit=\"save()\">\n  <input type=\"text\" name=\"title\">\n  <button type=\"submit\">Save</button>\n</form>\n```\n\n- Enter in the form does not cause a browser-level submit.\n- Clicking the submit button does not perform a native submit.\n- Your `save()` handler still runs.\n\n\n#### Notes\n\n- `*prevent` and `*prevent-default` share a single implementation branch and support the same modes.\n- The attribute value is a plain mode string, not a Sercrod expression; unsupported mode strings result in no special behavior.\n- `*prevent` only covers Enter keydown and form submit:\n\n  - It does not automatically block other keys or mouse events.\n  - For more complex behaviors, combine `*prevent` with explicit `@event` handlers and custom logic.\n\n- There are no Sercrod-level restrictions on combining `*prevent` with other directives on the same element, but:\n\n  - It only affects Enter and `submit`.\n  - It does not alter the semantics of other structural or data-binding directives.\n",
  "print": "### *print\n\n#### Summary\n\n`*print` evaluates an expression and writes the result into the element as plain text by setting `textContent`.\nThe value is passed through Sercrod's `text` filter and then rendered as a string, with `null`, `undefined`, and `false` treated as empty.\nThe alias `n-print` behaves the same and shares the same implementation.\n\nRelated low level variants:\n\n- `*textContent` and `n-textContent` use the same code path as `*print` and `n-print`.\n- All four directives end up assigning to `el.textContent` after applying the `text` filter.\n\n\n#### Basic example\n\nThe simplest case, printing a single property from host data:\n\n```html\n<serc-rod id=\"app\" data='{\"name\":\"Alice\"}'>\n  <p *print=\"name\"></p>\n</serc-rod>\n```\n\nA simple greeting based on host data:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\"}}'>\n  <p *print=\"`Hello, ${user.name}!`\"></p>\n</serc-rod>\n```\n\nAt runtime:\n\n- Sercrod evaluates `` `Hello, ${user.name}!` `` in the current scope.\n- The result \"Hello, Alice!\" is given to the `text` filter.\n- The `<p>` element's `textContent` becomes `Hello, Alice!`.\n- Any child nodes inside the original `<p>` template are ignored when `*print` is present.\n\n\n#### Behavior\n\nCore behavior of `*print` and `n-print`:\n\n- Sercrod reads the expression from the attribute (`*print` or `n-print`).\n- The expression string is optionally normalized by `normalizeTpl` if it is defined on the Sercrod class (otherwise it is used as is).\n- The expression is evaluated with `eval_expr(expr, scope, { el: node, mode: \"print\" })` or `mode: \"n-print\"`.\n- The result value `v` is normalized:\n\n  - If `v` is `null`, `undefined`, or `false`, Sercrod treats it as an empty value.\n  - For any other value, Sercrod uses it as is.\n\n- The value is passed to `Sercrod._filters.text`:\n\n  - Default `text` filter simply returns `String(raw ?? \"\")`.\n\n- The final string is assigned to `el.textContent`.\n\nImportant details:\n\n- Because `textContent` is used, any `<` or `>` characters in the value are treated as literal text by the browser. They do not become HTML markup.\n- When `cleanup.directives` is enabled in the global config, Sercrod removes `*print`, `n-print`, `*textContent`, and `n-textContent` from the output DOM after rendering.\n- On unexpected internal errors in this pass, the element's `textContent` falls back to an empty string.\n\n\n#### Value normalization and filters\n\nThe implementation normalizes values in two steps:\n\n1. Expression result:\n\n   - `v = eval_expr(...)`\n   - If `v` is `null`, `undefined`, or `false`, it is treated as empty and replaced with \"\".\n   - All other values (including `0`, `true`, and objects) are kept as `raw`.\n\n2. Text filter:\n\n   - The normalized `raw` value is passed to `this.constructor._filters.text(raw, { el, expr, scope })`.\n   - Default `text` filter is:\n\n     - `text: (raw, ctx) => String(raw ?? \"\")`\n\n   - This means:\n     - `null`, `undefined`, or `false` become \"\".\n     - `0` becomes \"0\".\n     - `true` becomes \"true\".\n     - Objects become \"[object Object]\" unless you override the `text` filter.\n\nYou can override `Sercrod._filters.text` (or provide `window.__Sercrod_filter.text` before Sercrod is loaded) to change how `*print` and `*textContent` values are normalized.\nThe same `text` filter is also used by the fallback path that renders `%expr%` text expansions into plain text.\n\n\n#### Evaluation timing\n\n`*print` is a non structural directive.\nIt is handled in the per element rendering stage after structural directives and before child nodes are rendered.\n\nMore precisely:\n\n- Structural directives such as `*if`, `*elseif`, `*else`, `*for`, `*each`, `*switch`, and `*let` are processed in `renderNode`.\n  - They may clone or skip the element before `_renderElement` is called.\n  - If a structural directive decides that the element should be skipped, `_renderElement` is never reached and `*print` never runs.\n- Only when none of the structural branches take over, Sercrod calls `_renderElement(node, scope, parent)`.\n\nInside `_renderElement`:\n\n- The `*print` / `n-print` / `*textContent` / `n-textContent` block runs before:\n  - `%expr%` text expansions on the element as a whole.\n  - Recursing into child nodes.\n\nAs a result:\n\n- When `*print` or `n-print` is present, the element is rendered as a plain text node and its children are not processed.\n- Other text modes on that element, such as `%expr%` expansion on the same element, are skipped because `*print` short circuits the rendering of children.\n\n\n#### Execution model\n\nConceptually, the runtime behaves as follows for `*print` and `n-print`:\n\n1. Detect directive:\n\n   - If the element has `*print` or `n-print` or `*textContent` or `n-textContent`, Sercrod enters the text assignment path.\n\n2. Choose the active attribute:\n\n   - Priority is:\n\n     - `*print`\n     - `n-print`\n     - `*textContent`\n     - `n-textContent`\n\n   - The first one that exists on the element provides the expression string.\n\n3. Prepare the expression:\n\n   - Read the raw attribute value.\n   - If `normalizeTpl` exists on the Sercrod class, pass the expression string through it.\n   - Otherwise use the original string.\n\n4. Evaluate:\n\n   - Run `eval_expr` with the current effective scope and the element as context.\n   - If `eval_expr` fails internally, it logs a warning with `mode:\"print\"` or \"textContent\" and returns `false`.\n\n5. Normalize and filter:\n\n   - Map `null`, `undefined`, and `false` to \"\".\n   - Pass the result to the `text` filter with `{ el, expr, scope }`.\n\n6. Assign:\n\n   - Set `el.textContent` to the filtered string.\n\n7. Cleanup and append:\n\n   - Optionally remove `*print`, `n-print`, `*textContent`, and `n-textContent` from the element if `cleanup.directives` is enabled.\n   - Append the element to its parent.\n   - Return from `_renderElement` without rendering child nodes.\n\nFor `*textContent` and `n-textContent`, the same steps are taken, except that `mode` is \"textContent\" or \"n-textContent\" for logging purposes.\nFunctionally they are equivalent to `*print` and `n-print` in the current implementation.\n\n\n#### Variable creation\n\n`*print` does not create any new variables.\n\n- It only reads from the current effective scope.\n- Any variables available in expressions come from:\n\n  - Host data (`data=\"...\"` or `data={...}` on `<serc-rod>`).\n  - Variables introduced by `*let` or `n-let` on this element or ancestors.\n  - Variables introduced by loops, such as `item` in `*for=\"item of items\"` or `*each=\"item of items\"`.\n  - Special helper variables injected by `eval_expr`:\n    - `$data` for the host data object.\n    - `$root` for the root Sercrod host's data.\n    - `$parent` for the nearest ancestor Sercrod host's data.\n  - Functions and methods injected via `*methods` and internal helper methods.\n\nThe expression on `*print` is evaluated in exactly the same environment as other expression based directives.\n\n\n#### Scope layering\n\n`*print` uses the same scope that is effective at the point where the element is rendered.\n\n- Structural directives such as `*let` and `*for` may replace or extend the scope before `_renderElement` is called.\n- For a typical pattern:\n\n  ```html\n  <ul *for=\"item of items\">\n    <li *print=\"item.label\"></li>\n  </ul>\n  ```\n\n  - The `*for` directive creates a per iteration scope with `item` bound.\n  - `_renderElement` is called on each `<li>` with that scope.\n  - `*print=\"item.label\"` reads `item` from the per iteration scope.\n\n`*print` itself does not change or wrap the scope; it only reads from it.\n\n\n#### Parent access\n\nBecause `*print` uses `eval_expr`, it supports the usual Sercrod special variables for accessing parent data:\n\n- `$data` refers to the data of the current Sercrod host.\n- `$root` refers to the data of the root Sercrod host.\n- `$parent` refers to the data of the nearest ancestor Sercrod host.\n\nExamples:\n\n```html\n<serc-rod id=\"app\" data='{\"title\":\"Dashboard\",\"user\":{\"name\":\"Alice\"}}'>\n  <header>\n    <h1 *print=\"$data.title\"></h1>\n    <p *print=\"`Signed in as ${$data.user.name}`\"></p>\n  </header>\n</serc-rod>\n```\n\nIn nested hosts, you can use `$parent` or `$root` when the inner host wants to display some outer header information inside a `*print` expression.\n\n\n#### Use with conditionals and loops\n\n`*print` is often used inside elements that are controlled by `*if`, `*for`, or `*each`.\n\n- With `*if` on the same element:\n\n  ```html\n  <p *if=\"user\" *print=\"user.name\"></p>\n  ```\n\n  - The `*if` chain is processed in `renderNode`.\n  - If the condition is falsy, the element is not rendered and `*print` never runs.\n  - If the condition is truthy, `_renderElement` is called on the element, and `*print` renders the name.\n\n- Inside `*for`:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <span *print=\"item.label\"></span>\n    </li>\n  </ul>\n  ```\n\n  - `*for` clones `<li>` for each `item`.\n  - `*print` renders the label within each clone.\n\n- Inside `*each`:\n\n  ```html\n  <table>\n    <tbody *each=\"row of rows\">\n      <tr>\n        <td *print=\"row.id\"></td>\n        <td *print=\"row.name\"></td>\n      </tr>\n    </tbody>\n  </table>\n  ```\n\n  - `*each` repeats the `<tbody>` children for each `row`.\n  - `*print` displays fields from `row` in each cell.\n\nThere is no special interaction with loops beyond using the scope that loops provide.\n\n\n#### Comparison with text interpolation (%expr%)\n\nSercrod supports `%expr%` style text interpolation inside plain text nodes using the configured delimiters.\nBy default the delimiters are `%` and `%`, and interpolation is implemented by `_expand_text`.\n\nKey differences between `*print` and `%expr%`:\n\n- Where the expression lives:\n\n  - `*print` uses an attribute expression.\n  - `%expr%` syntax uses inline expressions inside text content.\n\n- Filter used:\n\n  - `*print` and `*textContent` use the `text` filter.\n  - `%expr%` expansions are combined with the `placeholder` filter.\n\n- How the element is rendered:\n\n  - `*print` replaces the entire content of the element and prevents child rendering.\n  - `%expr%` runs only when:\n    - There is no `*print`, `n-print`, `*textContent`, or `n-textContent` on the element.\n    - The text node (or single text child) contains the delimiters.\n\nExample with `%expr%` only:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\"}}'>\n  <p>Hello, %user.name%!</p>\n</serc-rod>\n```\n\nExample with `*print` taking precedence:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\"}}'>\n  <p *print=\"`Hello, ${user.name}!`\">\n    This inner text, including %user.name%, is ignored.\n  </p>\n</serc-rod>\n```\n\nIn this second case:\n\n- `*print` runs, sets `textContent` from the expression, and skips child rendering.\n- The inline `%user.name%` inside the children is never evaluated.\n\n\n#### Related directives: *textContent and *innerHTML\n\n- `*textContent` and `n-textContent`:\n\n  - Use the same pipeline as `*print` and `n-print`.\n  - They are logically equivalent in the current implementation, differing mainly in naming and the `mode` label passed to `eval_expr`.\n  - All four are handled by the same code branch that assigns to `el.textContent`.\n\n- `*innerHTML` and `n-innerHTML`:\n\n  - Are separate directives that assign to `el.innerHTML`.\n  - They pass the raw value through the `html` filter instead of `text`.\n  - They do not use the `text` filter and are intended for inserting HTML strings.\n  - When you want to insert HTML markup rather than text, use `*innerHTML` and provide a suitable `html` filter.\n\nCombination rules and caveats:\n\n- On a single element, you should not rely on combining `*print` or `*textContent` with `*innerHTML` or `n-innerHTML`.\n\n  - If both `*print` and `*textContent` are present, only the first one in the internal priority order is used.\n  - If `*print` or `*textContent` is present, the `*innerHTML` block on the same element is skipped, because `*print` short circuits child rendering.\n  - Although the attributes can be written in HTML, only one text or HTML mode will effectively control the element.\n\nTo keep behavior predictable, treat `*print`, `*textContent`, `*innerHTML`, and `n-innerHTML` as mutually exclusive on a single element and choose one per element.\n\n\n#### Best practices\n\n- Use `*print` for text only content:\n\n  - When an element is meant to show only an expression result, prefer `*print` instead of mixing static text and `%expr%`.\n  - Example: badges, counters, titles that are entirely dynamic.\n\n- Use `%expr%` for small inline substitutions:\n\n  - When the text is mostly static and you want a few interpolated pieces, prefer `%expr%` inside a text node.\n  - Example: Hello, %user.name% (id: %user.id%).\n\n- Do not rely on children with `*print`:\n\n  - When `*print` or `n-print` is present, children are not rendered.\n  - Avoid putting important markup or directives inside the element body if you also use `*print`.\n\n- Do not mix `*print` and `*innerHTML` on the same element:\n\n  - Only one set of semantics will effectively apply.\n  - If you need both plain text and HTML portions, split them into separate child elements.\n\n- Remember the `text` filter:\n\n  - If you need custom normalization (for example trimming whitespace or mapping specific values), override `Sercrod._filters.text`.\n  - Keep in mind that this affects all uses of `*print` and `*textContent` as well as the fallback `%expr%` text expansion path that uses the same basic stringification rules.\n\n- Prefer simple expressions:\n\n  - For maintainability, keep `*print` expressions short.\n  - For complex formatting, move the logic into a helper function and call that function from `*print`.\n\n\n#### Additional examples\n\nDisplay a number with a suffix:\n\n```html\n<serc-rod id=\"counter\" data='{\"count\": 42}'>\n  <p *print=\"`${count} items`\"></p>\n</serc-rod>\n```\n\nFallback for missing values:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{}}'>\n  <p *print=\"user.name || 'Anonymous'\"></p>\n</serc-rod>\n```\n\nUsing methods:\n\nAssume `window.formatUser` is a function that returns a display name.\n\n```js\nfunction formatUser(user) {\n  return user && user.name ? `User: ${user.name}` : \"Guest\";\n}\n```\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{\"name\":\"Alice\"}}' *methods=\"formatUser\">\n  <p *print=\"formatUser(user)\"></p>\n</serc-rod>\n```\n\nThe `*methods=\"formatUser\"` directive makes `formatUser` available in the expression scope for `*print` and other directives.\n\n\n#### Notes\n\n- `*print` and `n-print` share the same manual entry in `*man`.\n- `*print` is implemented as a non structural directive inside `_renderElement` and does not affect layout or siblings beyond replacing the element's text content.\n- `*textContent` and `n-textContent` are currently implemented with the same behavior as `*print` and `n-print`, using the same value normalization and `text` filter.\n- Behavior described here is based on the current `sercrod.js` implementation and may evolve if the text or HTML filters are customized through the official extension points.\n",
  "rem": "### *rem\n\n#### Summary\n\n`*rem` removes an element from the rendered output and treats it as a Sercrod-only comment.\nThe directive has an alias `n-rem`.\n\nUse `*rem` when you want to keep blocks of markup, notes, or alternate versions in your templates for Sercrod, while ensuring that nothing from those blocks appears in the final HTML that the browser or user sees.\n\n\n#### Basic example\n\nA debug-only block that you want to keep in the template, but never ship:\n\n```html\n<serc-rod id=\"app\" data='{\"debug\": true}'>\n  <div *rem>\n    This block is visible only to Sercrod and your editor,\n    not to end users.\n  </div>\n\n  <p>Real content goes here.</p>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<div *rem>` element and its content are not rendered to the output DOM.\n- The `<p>` remains and is rendered normally.\n- Directives inside the `*rem` block are not evaluated.\n\n\n#### Behavior\n\n- `*rem` and `n-rem` mark an element and its entire subtree as a Sercrod-only comment.\n- When Sercrod encounters an element with `*rem` or `n-rem`:\n  - It does not evaluate child nodes or directives inside that element.\n  - It does not keep the element in the final rendered tree.\n- The contents are effectively erased from the runtime DOM, while remaining in the source template for development and maintenance.\n\nAliases:\n\n- `*rem` and `n-rem` are aliases and behave identically.\n- Choose one style per project for consistency (attribute form `*rem` is usually preferred in documentation).\n\n\n#### Typical use cases\n\nCommon situations where `*rem` is useful:\n\n- Temporary debug markup:\n\n  ```html\n  <div *rem>\n    <p>Debug details: %JSON.stringify($data)%</p>\n  </div>\n  ```\n\n- Keeping alternative layouts or prototypes in the template:\n\n  ```html\n  <section>\n    <header>Production header</header>\n\n    <div *rem>\n      <header>Experimental header layout for later</header>\n    </div>\n  </section>\n  ```\n\n- In-place documentation and TODO notes for template authors:\n\n  ```html\n  <main>\n    <div *rem>\n      TODO:\n      - Replace this panel with a new component\n      - Verify a11y for keyboard navigation\n    </div>\n\n    <section>\n      <!-- Real content -->\n    </section>\n  </main>\n  ```\n\n\n#### Evaluation timing\n\n- `*rem` is checked when Sercrod visits a node during rendering.\n- As soon as Sercrod sees `*rem` or `n-rem` on a node:\n  - It stops further processing for that node.\n  - It does not traverse or render the children.\n  - Any other directives on the same node are effectively ignored for rendering.\n\nPractical effect:\n\n- `*rem` acts as a short-circuit for rendering that subtree.\n- You can safely leave incomplete markup or directives inside a `*rem` block without triggering errors or side effects at runtime, as long as `*rem` remains on the element.\n\n\n#### Execution model\n\nConceptually, the renderer behaves like this:\n\n1. Sercrod visits a node in the template.\n2. It checks whether the node has `*rem` or `n-rem`.\n3. If neither attribute is present:\n   - Rendering proceeds normally, and other directives are considered.\n4. If `*rem` or `n-rem` is present:\n   - Sercrod does not evaluate any directives on that node.\n   - Sercrod does not descend into children.\n   - The element is omitted from the rendered output.\n\nThe important points:\n\n- The subtree is not expanded:\n  - No data interpolation.\n  - No conditional evaluation.\n  - No loops.\n  - No event bindings.\n- The block is treated as a Sercrod-only comment that is simply not emitted to the final DOM.\n\n\n#### Variable creation and scope layering\n\n`*rem` does not introduce new variables or scopes.\n\n- There is no additional per-node scope or local variable created by `*rem`.\n- The directive only affects whether the element (and its children) is rendered.\n- Any variables, methods, or helpers that would normally be visible inside the block are irrelevant, because the block is not evaluated.\n\nYou can think of `*rem` as a rendering guard that operates before any scope-sensitive logic.\n\n\n#### Parent access\n\nBecause `*rem` prevents the entire subtree from being rendered:\n\n- Access to `$root`, `$parent`, or any data inside the block is never evaluated at runtime.\n- Any expression inside a `*rem` block is effectively inert.\n\nThis is useful for:\n\n- Writing notes that refer to data or expressions without risking runtime errors.\n- Copying and pasting example code snippets inside a template for later use without executing them accidentally.\n\n\n#### Use with conditionals and loops\n\n`*rem` is stronger than conditionals and loops on the same element:\n\n- If `*rem` or `n-rem` is present on an element:\n\n  - Host-level directives such as `*if`, `*for`, `*each`, `*switch`, `*include`, or `*import` on the same element are effectively ignored in the rendered output.\n  - The element is treated as a comment, regardless of those directives.\n\nExamples of patterns that do not make sense and should be avoided:\n\n```html\n<div *if=\"debug\" *rem>\n  <!-- The *if is meaningless here; *rem wins. -->\n  <p>Debug panel</p>\n</div>\n\n<li *for=\"item of items\" *rem>\n  <!-- The *for is also meaningless; the item is never rendered. -->\n  <span *print=\"item.name\"></span>\n</li>\n```\n\nRecommended usage:\n\n- Place `*rem` on its own element whose only purpose is to be a comment.\n- Do not attach `*rem` to elements that you intend to render under some conditions.\n- If you want a block that sometimes renders and sometimes hides, use `*if` instead.\n\n\n#### Best practices\n\n- Treat `*rem` as a template-level comment mechanism:\n\n  - Use it to keep extra information for developers.\n  - Keep blocks small and focused so they remain easy to understand.\n\n- Do not store critical behavior inside `*rem` blocks:\n\n  - Anything inside may be removed or ignored at any time.\n  - Production behavior should not depend on code that is currently under `*rem`.\n\n- Avoid combining `*rem` with other host-level directives:\n\n  - While Sercrod ignores them under `*rem`, mixing them makes templates harder to reason about.\n  - Prefer a clean split: either a block is a comment (`*rem`) or it participates in rendering (no `*rem`).\n\n- Use `*literal` for literal output instead:\n\n  - If you want to show raw text or markup without Sercrod expansion, use `*literal`.\n  - If you want to hide a block entirely, use `*rem`.\n\n- Keep the intent obvious:\n\n  - Add a short comment inside the `*rem` block explaining why it exists, such as `TODO`, `DEBUG`, or `EXPERIMENT`.\n\n\n#### Additional examples\n\nPrototype section kept in the template:\n\n```html\n<main>\n  <section>\n    <h1>Current layout</h1>\n    <p>This is the layout users see.</p>\n  </section>\n\n  <section *rem>\n    <h1>Future layout experiment</h1>\n    <p>\n      This section is only for designers and developers.\n      It will not appear in the production DOM.\n    </p>\n  </section>\n</main>\n```\n\nInline TODO reminder:\n\n```html\n<footer>\n  <div *rem>\n    TODO:\n    - Add social media links\n    - Confirm final copyright text\n  </div>\n\n  <small>&copy; Example Inc.</small>\n</footer>\n```\n\n\n#### Notes\n\n- `*rem` and `n-rem` are aliases; they are treated the same by the renderer.\n- The subtree under `*rem` is not evaluated; it is safe to leave incomplete directives or expressions inside.\n- `*rem` is specifically intended as a Sercrod-only comment mechanism, not as a runtime toggle.\n- For literal output without Sercrod expansion, use `*literal` instead.\n- For runtime conditions, loops, or structural changes, use `*if`, `*for`, `*each`, and related directives on elements that do not carry `*rem`.\n",
  "restore": "### *restore\n\n#### Summary\n\n`*restore` discards staged edits and restores the staged view back to the last stable state of the host data.\nIt is part of the staged editing flow together with `*stage` and `*apply`, and it only makes sense when the host is configured with `*stage` or `n-stage`.\n\nAlias:\n\n- `*restore`\n- `n-restore`\n\n\n#### Basic example\n\nA typical staged form with apply and restore buttons:\n\n```html\n<serc-rod id=\"profile\" data='{\n  \"user\": { \"name\": \"Alice\", \"email\": \"alice@example.com\" }\n}'>\n  <form *stage=\"draft\">\n    <label>\n      Name:\n      <input *input=\"draft.user.name\">\n    </label>\n    <label>\n      Email:\n      <input *input=\"draft.user.email\">\n    </label>\n\n    <button type=\"button\" *apply>Save</button>\n    <button type=\"button\" *restore>Reset</button>\n  </form>\n</serc-rod>\n```\n\nBehavior:\n\n- While editing, the view uses a staged copy (`_stage`) derived from the host data.\n- `*apply` copies staged changes into the real data (`_data`) and remembers that snapshot as the new stable state.\n- `*restore` discards the current staged edits and restores the staged view back to the last stable state:\n  - If there has been at least one successful `*apply`, it rolls back to that applied snapshot.\n  - Otherwise it rolls back to the original host data.\n\n\n#### Behavior\n\nKey points:\n\n- `*restore` is a button-like directive:\n  - It attaches a click handler that manipulates the host's internal staged buffer.\n  - It does not create or expose new variables in the template scope.\n  - The attribute value (if any) is ignored; `*restore` is used as a flag only.\n\n- `*restore` only has an effect when the host Sercrod element has `*stage` or `n-stage`:\n  - Without `*stage`, the click handler checks the host and does nothing.\n\n- Interaction with the staged model:\n  - The host maintains:\n    - `_data`: the canonical data.\n    - `_stage`: a staged copy used for editing and rendering while staging is active.\n    - `_applied`: the last snapshot of `_data` taken immediately after `*apply`.\n  - `*restore` rewrites `_stage` using:\n    - `_applied` if it exists (last applied snapshot), otherwise\n    - `_data` (the current canonical data).\n\n- Rendering impact:\n  - Since the host's visible scope is `this._stage ?? this._data`, updating `_stage` followed by `update()` immediately reverts the UI to the restored state.\n\n\n#### Relationship to *stage and *apply\n\n`*restore` is one of three core directives that define the staged editing lifecycle:\n\n- `*stage` on the host:\n  - Enables the staged editing model.\n  - The host constructs `_stage` as a clone of the initial scope (usually `_data` or a parent-provided scope).\n  - The visible scope becomes `_stage` whenever it is present, falling back to `_data` otherwise.\n\n- `*apply` on a child element:\n  - Copies the staged content from `_stage` into `_data` when clicked.\n  - After a successful apply:\n    - `Object.assign(this._data, this._stage)` is performed.\n    - The host re-renders.\n    - `_applied` is updated to a clone of the new `_data` for use by `*restore`.\n\n- `*restore` on a child element:\n  - Uses `_applied` if present, otherwise `_data`, as the base.\n  - Clones that base into `_stage` again.\n  - Triggers a re-render so the UI reflects the restored state.\n\nIn other words:\n\n- `*stage`: prepare a safe workspace.\n- `*apply`: commit workspace changes to canonical data and remember that commit.\n- `*restore`: discard workspace changes and go back to the last commit (or the original data).\n\n\n#### Evaluation timing\n\n`*restore` is processed during element rendering after host-level `*if` and other structural checks:\n\n- If the element with `*restore` also has `*if` or `n-if`, the conditional is evaluated first.\n  - If the condition is falsy, the element is not rendered and no restore handler is attached.\n  - If the condition is truthy, Sercrod proceeds and the `*restore` logic runs.\n\n- When the renderer encounters `*restore`:\n  - It clones the current working element (`work`) into a real DOM element (`el`).\n  - It attaches a single `click` listener to `el`.\n  - It appends `el` to the parent and stops further processing of that element for the current render.\n\n- The `click` handler runs at user interaction time, not at render time.\n\n\n#### Execution model\n\nConceptually, for each element with `*restore` or `n-restore`:\n\n1. During render:\n\n   - Sercrod detects that `work` has `*restore` or `n-restore`.\n   - It creates `el = work.cloneNode(true)`.\n   - It registers:\n\n     - `el.addEventListener(\"click\", handler)`,\n\n     where `handler` is closed over the host instance.\n\n   - It appends `el` to the parent.\n   - It returns from the directive branch for this element.\n\n2. When the user clicks the element:\n\n   - The handler checks if the host has `*stage` or `n-stage`.\n     - If not, it returns without changes.\n   - It calculates the base:\n\n     - `base = this._applied ?? this._data`.\n\n   - It clones `base` into `_stage`:\n     - Preferentially via `structuredClone`.\n     - Falls back to `JSON.parse(JSON.stringify(base))` if needed.\n\n   - It calls `this.update()` to re-render the host.\n\n3. On re-render:\n\n   - Because `_stage` is now a fresh clone of the base state, the entire staged view is reset to that state.\n\n\n#### Variable creation and scope layering\n\n`*restore` does not introduce or modify template-level variables:\n\n- No new local variables are created for each element.\n- It does not change the scopes used by expressions inside the template directly.\n- Instead, it modifies the internal data buffers (`_stage` and indirectly the visible scope) of the host component.\n\nFrom the perspective of expressions:\n\n- After a successful restore, any expression that reads from the visible scope (for example `user.name`) will see values from the restored staged buffer.\n- Special helpers like `$data`, `$root`, and `$parent` are still provided in the usual way by Sercrod's expression engine.\n\n\n#### Parent access\n\n`*restore` does not provide a dedicated handle to parent scopes on its own:\n\n- It acts on the enclosing Sercrod host where `*stage` is configured.\n- The handler uses internal properties (`this._stage`, `this._data`, `this._applied`) of that host.\n- Templates can still rely on `$root` and `$parent` as usual when rendering, but those bindings are independent of `*restore` itself.\n\nThe main effect of `*restore` is to redefine the contents of `_stage`, which then becomes the source for subsequent renders.\n\n\n#### Use with conditionals and loops\n\nYou can combine `*restore` with conditionals and loops on the same or surrounding elements, as long as they do not conflict structurally:\n\n- On the same element:\n\n  - `*if` and `*restore` on a button:\n\n    ```html\n    <button type=\"button\"\n            *if=\"isDirty\"\n            *restore>\n      Reset changes\n    </button>\n    ```\n\n    - The button only appears when `isDirty` is truthy.\n    - When visible and clicked, it restores the staged data.\n\n- Inside loops:\n\n  - You can put `*restore` in a repeated area if it logically refers to the same staged host:\n\n    ```html\n    <serc-rod data='{\"rows\":[{\"id\":1},{\"id\":2}]}' *stage=\"draft\">\n      <table>\n        <tbody *each=\"row of draft.rows\">\n          <tr>\n            <td *print=\"row.id\"></td>\n            <td>\n              <button type=\"button\" *restore>Reset table edits</button>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </serc-rod>\n    ```\n\n    - All restore buttons operate on the same staged host.\n    - Clicking any of them resets the entire staged view, not just a single row.\n\n\n#### Best practices\n\n- Use `*restore` only when `*stage` is enabled:\n\n  - Without `*stage` or `n-stage` on the host, `*restore` does not perform any meaningful work.\n  - Treat `*restore` as part of a staged editing pattern, not as a global reset.\n\n- Pair `*restore` with `*apply`:\n\n  - `*restore` relies on `_applied` for \"last stable state\".\n  - Without `*apply`, it falls back to `_data`, which can still be useful as an initial reset.\n  - In a typical workflow, you will have:\n\n    - One or more staged inputs bound into the staged scope.\n    - A `Save` button with `*apply`.\n    - A `Reset` button with `*restore`.\n\n- Avoid mixing unrelated behaviors on the same button:\n\n  - It is technically possible to add other attributes such as `@click` alongside `*restore`, but that will combine event handlers.\n  - For clarity and maintainability, keep `*restore` buttons focused on restore behavior and use separate buttons for additional actions.\n\n- Think in terms of \"last committed change\":\n\n  - `*restore` does not reconstruct an arbitrary history; it only knows the last successfully applied snapshot.\n  - If you need multi-step undo or history, you should build that at the data layer and expose it to Sercrod as part of `_data` or `_stage`.\n\n\n#### Additional examples\n\nSimple \"revert to original data\" without any prior apply:\n\n```html\n<serc-rod id=\"simple\" data='{\"counter\": 0}' *stage=\"draft\">\n  <p>Value: <span *print=\"draft.counter\"></span></p>\n\n  <button type=\"button\" *restore>Reset</button>\n  <button type=\"button\" @click=\"draft.counter++\">Increment</button>\n</serc-rod>\n```\n\n- On first render:\n  - `_stage` is cloned from `_data` (`{ counter: 0 }`).\n  - The view shows `0`.\n- Clicking `Increment` changes `draft.counter` inside `_stage`.\n- Clicking `Reset` clones `_data` into `_stage` again (since `_applied` is unset), bringing the view back to `0`.\n\n\nStaged form with multiple commits:\n\n```html\n<serc-rod id=\"multi\" data='{\"value\": \"initial\"}' *stage=\"draft\">\n  <input *input=\"draft.value\">\n\n  <button type=\"button\" *apply>Apply</button>\n  <button type=\"button\" *restore>Restore</button>\n</serc-rod>\n```\n\n- After editing the input and clicking `Apply`:\n  - `_data.value` becomes the edited value.\n  - `_applied` is updated to the new `_data`.\n- Further edits only affect `_stage`.\n- Clicking `Restore` discards those new edits and clones `_applied` into `_stage`, returning the UI to the last applied value.\n\n\n#### Notes\n\n- `*restore` and `n-restore` are aliases; choose one style per project and stick to it for consistency.\n- The directive does not use its attribute value; any text written in `*restore=\"...\"` is ignored.\n- `*restore` is a staged-data helper only:\n  - It assumes the host is in staged mode via `*stage` or `n-stage`.\n  - It rebuilds the staged buffer from `_applied` or `_data` and re-renders.\n- Because it operates on `_stage`, `*restore` affects the visible state immediately, but it does not directly overwrite `_data`.\n  - `_data` changes only when `*apply` or other data-mutating mechanisms are invoked.\n- There are no additional hard restrictions specific to `*restore` beyond its dependency on `*stage`:\n  - It does not conflict with `*include`, `*import`, or other structural directives the way `*each` does, because it does not reshape the host's children.\n",
  "save": "### *save / *save.file / *save.session / *save.store\n\n#### Summary\n\n`*save.file` exports the host data (or its staged view) as a JSON file in the browser.\n`*save.session` stores the same JSON payload in browser `sessionStorage`.\n`*save.store` stores the same JSON payload in persistent browser storage backed by IndexedDB.\nIt is typically used on a button inside a `<serc-rod>` host.\nWhen clicked, Sercrod collects the host’s current data and builds a JSON string. File saves start a download; session saves write that string under the storage key given by `*save.session`; store saves write it under the key given by `*save.store`.\n\nBy default, `*save` exports the entire host data.\nUse `*keys` to export only selected top-level properties.\n`*save` remains as the legacy-compatible file form, equivalent to `*save.file` when no storage suffix is used.\n\n\n#### Basic example\n\nSave the entire host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"name\":\"Alice\",\"age\":30}'>\n  <button *save.file>Download profile JSON</button>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<button>` is cloned and given a click handler by Sercrod.\n- When the button is clicked, Sercrod takes the host’s current data and serializes it to JSON.\n- A file named like `Sercrod-YYYYMMDD-HHMMSS.json` is generated and downloaded by the browser.\n\nSave selected keys to an explicit filename:\n\n```html\n<button type=\"button\" *save.file=\"'profile-backup.json'\" *keys=\"profile settings\">\n  Save profile\n</button>\n```\n\nIn this form:\n\n- `*save.file` describes the file destination and optional filename.\n- `*keys` describes which host data keys are saved.\n\nSave selected keys into the current browser session:\n\n```html\n<button type=\"button\" *save.session=\"'profile-draft'\" *keys=\"profile settings\">\n  Save session draft\n</button>\n```\n\nIn this form:\n\n- `*save.session` describes the `sessionStorage` key.\n- `*keys` selects which host data keys are serialized.\n- Omitting `*keys` saves the whole host data or stage.\n\nSave selected keys into persistent browser storage:\n\n```html\n<button type=\"button\" *save.store=\"'profile-draft'\" *keys=\"profile settings\">\n  Save persistent draft\n</button>\n```\n\nIn this form:\n\n- `*save.store` describes a persistent browser storage key.\n- Sercrod stores a JSON string in IndexedDB.\n- The value remains available after page reloads and browser restarts, subject to normal browser storage policy.\n\n\n#### Behavior\n\n- `*save.file` attaches a click handler to the element it is placed on.\n- `*save.session` attaches the same kind of click handler, but writes JSON to `window.sessionStorage`.\n- `*save.store` attaches the same kind of click handler, but writes JSON to IndexedDB.\n- The handler runs in the context of the surrounding Sercrod host and serializes:\n\n  - `this._stage` if it exists, otherwise\n  - `this._data`.\n\n- No network request is sent by `*save` itself.\n- The resulting JSON is written into a Blob, and a temporary `<a download>` element is used to trigger the browser’s download dialog.\n- For `*save.session`, the resulting JSON is stored with `sessionStorage.setItem(storageKey, json)`.\n- For `*save.store`, the resulting JSON is stored in the `sercrod-store` IndexedDB database by default.\n- After a successful save action, a `CustomEvent(\"sercrod-saved\")` is dispatched from the host for application-specific hooks.\n\nAliases and compatibility:\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain supported as the old file-save spelling.\n- In new examples, prefer explicit action forms such as `*save.file`, `*save.session`, or `*save.store` plus `*keys`.\n\n\n#### Data source and property selection\n\nData source:\n\n- `*save` always uses host-level data, not per-element scope variables.\n- On click:\n\n  - If the host has a staged buffer (`_stage`), `*save` reads from `_stage`.\n  - Otherwise, it reads from the committed data object `_data`.\n\nKey selection:\n\n- Without an attribute value:\n\n  - `*save.file` exports the entire data object (`_stage` or `_data`).\n  - `*save.session` and `*save.store` require a storage key value, but still save the entire data object when `*keys` is omitted.\n\n- With `*keys`:\n\n  - The `*keys` value is treated as a whitespace-separated list of top-level property names.\n  - These names are not expressions; they are taken as-is and are not evaluated.\n  - Only properties that exist on the data object are copied into a new object.\n\nExample (selective save):\n\n```html\n<serc-rod id=\"settings\" data='{\n  \"user\": { \"name\": \"Alice\", \"age\": 30 },\n  \"theme\": { \"mode\": \"dark\" },\n  \"debug\": true\n}'>\n  <!-- Only save \"user\" and \"theme\" from the host data -->\n  <button *save.file=\"'user-theme.json'\" *keys=\"user theme\">Download user+theme</button>\n</serc-rod>\n```\n\nIn this example:\n\n- `src` is `host._stage ?? host._data`.\n- If `*keys` is `\"user theme\"`, Sercrod builds:\n\n  - `data = { user: src.user, theme: src.theme }` (if those properties exist).\n\n- The JSON file contains only `user` and `theme` at the top level.\n- Nested paths (such as `user.name`) are not supported by `*keys` directly.\n\nOld spelling:\n\n```html\n<button *save=\"user theme\">Download user+theme</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax, not as a filename.\n\n\n#### Evaluation timing\n\nRender-time:\n\n- When Sercrod renders the host, it looks for elements with `*save`, `*save.file`, `*save.session`, `*save.store`, or their `n-` aliases.\n- For each such element:\n\n  - Sercrod clones the element.\n  - Attaches a click handler on the clone.\n  - Appends the clone to the parent.\n  - Returns from the element renderer without recursing into the children of that clone.\n\nClick-time:\n\n- When the user clicks the save button:\n\n  1. Sercrod resolves the action attribute.\n     - `*save.file` gives an optional filename.\n     - `*save.session` gives a `sessionStorage` key.\n     - `*save.store` gives a persistent browser storage key.\n     - legacy `*save` values are treated as old key-selection syntax.\n  2. Sercrod selects `src = this._stage ?? this._data` from the host.\n  3. It builds a plain object:\n\n     - Entire `src` if no `*keys` list was provided.\n     - A subset object if `*keys` was provided.\n\n  4. It serializes that object with `JSON.stringify(data, null, 2)`.\n  5. It writes the JSON to the selected destination: file download, `sessionStorage`, or IndexedDB-backed persistent browser storage.\n  6. It dispatches the `sercrod-saved` event from the host.\n\nBecause the JSON is built at click time, `*save` always reflects the current state of `_stage` or `_data` at the moment of the click.\n\n\n#### Execution model\n\nInternally, `*save` behaves as follows (conceptually):\n\n1. During render, Sercrod finds an element `work` with a save directive.\n2. Sercrod clones `work` into `el`.\n3. Sercrod attaches:\n\n   - `el.addEventListener(\"click\", () => { /* build JSON and save to the selected destination */ })`.\n\n4. Sercrod appends `el` to the parent node and returns, without processing `el`’s children for further Sercrod directives.\n\nOn click, the handler:\n\n1. Resolves the action attribute and `*keys`.\n2. Selects `src`:\n\n   - `src = host._stage ?? host._data`.\n\n3. Builds `data`:\n\n   - If there is a `*keys` list, `data` is a new object populated only with properties present in `src`.\n   - Otherwise, `data` is `src` itself.\n\n4. Serializes `data` with a pretty-printed `JSON.stringify(data, null, 2)`.\n5. If the action is `*save.store`, writes `{ key, json, updatedAt }` into the configured IndexedDB object store and returns.\n6. If the action is `*save.session`, writes the JSON string with `sessionStorage.setItem(storageKey, json)` and returns.\n7. Otherwise, creates a Blob of type `application/json`.\n8. Creates an `ObjectURL` and a temporary `<a>` element with:\n\n   - `href = url`.\n   - `download = \"Sercrod-YYYYMMDD-HHMMSS.json\"` (in the local time of the browser).\n\n9. Programmatically clicks the anchor to prompt download.\n10. Cleans up (removes the anchor from the DOM and revokes the `ObjectURL`).\n11. Dispatches `CustomEvent(\"sercrod-saved\", { detail: { ... } })` from the host.\n\n\n#### Use on nested elements and scope\n\n- `*save` must live inside a Sercrod host to be meaningful, since it reads from the host’s `_stage` or `_data`.\n- `*save` does not use per-element scope; it only uses the host’s data object.\n- Placing `*save` on a deeply nested element is allowed, but it still always saves the surrounding host’s data, not a subset scoped by `*for` or `*each`.\n\nIn other words:\n\n- The location of the `*save` button in the DOM tree does not change the data source.\n- It only changes where the button appears in the layout.\n\n\n#### Events\n\nAfter a successful save, Sercrod dispatches a bubbling, composed `CustomEvent` from the host:\n\n- Event type:\n\n  - `\"sercrod-saved\"`\n\n- Event detail structure:\n\n  - `detail.stage`: `\"save\"` for file/legacy saves, `\"save.session\"` for session saves, or `\"save.store\"` for store saves.\n  - `detail.host`: the Sercrod host element (`<serc-rod>` instance).\n  - `detail.fileName`: the file name used for file downloads (for example `\"Sercrod-20251205-093000.json\"`).\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file saves.\n  - `detail.storageKey`: the storage key for `*save.session` or `*save.store`.\n  - `detail.props`: the property list array if provided; `null` if no list was specified.\n  - `detail.keys`: the same property list array, provided for the unified action syntax.\n  - `detail.json`: the JSON string that was generated.\n\nExample hook:\n\n```js\ndocument.addEventListener(\"sercrod-saved\", (evt) => {\n  const { host, fileName, storage, storageKey, props, json } = evt.detail;\n  console.log(\"Saved from host:\", host.id);\n  console.log(\"File name:\", fileName);\n  console.log(\"Storage:\", storage, storageKey);\n  console.log(\"Props:\", props);\n  console.log(\"JSON preview:\", json.slice(0, 200));\n});\n```\n\nYou can use this event to:\n\n- Mirror the saved JSON to another storage or API when the built-in file/session/store destinations are not enough.\n- Show a toast notification after the download is triggered.\n- Log or audit save operations.\n\n\n#### Best practices\n\n- Treat `*save.file` elements as simple buttons:\n\n  - Because the renderer does not recursively process children of `*save` hosts after cloning, avoid placing other Sercrod directives inside the same element.\n  - Use plain text or static markup inside the button where possible.\n\n- Use `*keys` for focused exports:\n\n  - If your host data is large, consider exposing smaller subsets via:\n\n    - `*save.file=\"'profile.json'\" *keys=\"profile settings\"`\n    - `*save.file=\"'chart.json'\" *keys=\"chart filters\"`\n\n- Keep the root data export-friendly:\n\n  - Plan your top-level keys (`user`, `settings`, `rows`, `config`, and so on) so that it is easy to export meaningful subsets by name.\n\n- Combine with matching `*load` forms for round trips:\n\n  - Use `*save.file` and `*load.file` for user-visible JSON files.\n  - Use `*save.session` and `*load.session` for session-scoped browser storage.\n  - Use `*save.store` and `*load.store` for persistent browser storage.\n\n- Use `sercrod-saved` for integration:\n\n  - Attach listeners to `\"sercrod-saved\"` if you want to route the JSON elsewhere instead of or in addition to the download.\n\n\n#### Advanced - Using save forms with *stage, *apply, *restore, load forms, and *post\n\n`*save` is part of a broader data management workflow:\n\n- `*stage`:\n\n  - Enables a staged buffer `_stage` for the host (a working copy of the data).\n  - When `_stage` exists, `*save` prefers `_stage` over `_data`.\n  - This lets you export the staged view without committing it.\n\n- `*apply`:\n\n  - Copies `_stage` into `_data` and updates the host.\n  - Subsequent `*save` clicks, after `*apply`, will see the committed state in `_data`.\n\n- `*restore`:\n\n  - Rolls back `_stage` to the last snapshot, or to `_data` if no snapshot is available.\n  - After a restore, `*save` again sees whatever `_stage` currently holds.\n\n- `*load`:\n\n  - Reads JSON from a file, session storage, or persistent browser storage and merges it into `_stage` or `_data`.\n  - You can use matching `*load` forms to import JSON previously written by `*save` forms.\n\n- `*post`:\n\n  - Sends host data to a server as JSON over HTTP.\n  - `*save` is complementary to `*post`: one saves locally as a file, the other sends over the network.\n\nThe core rule is:\n\n- `*save` always targets “the current data view” of the host, prioritizing `_stage` when present.\n- This makes it safe to stage edits with `*stage`, try them out, export via `*save`, and later apply or restore as needed.\n\n\n#### Notes\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain old compatible file-save spellings.\n- In old syntax, the value of `*save` is parsed as plain text and split by whitespace as a key list.\n- When no property list is provided, the entire `_stage ?? _data` object is serialized.\n- When a property list is provided, only the listed top-level properties are included if they exist.\n- The file name is generated as `\"Sercrod-YYYYMMDD-HHMMSS.json\"` using the browser’s local time.\n- `*save` itself does not change `_stage` or `_data`; it is a read-only export operation.\n- There are no special structural restrictions specific to `*save` beyond the general behavior described above; it can be combined with directives such as `*if` on the same element, as long as you keep in mind that `*save` turns that element into a “save button” whose children are not further processed by Sercrod.\n",
  "stage": "### *stage\n\nAliases: `*stage`, `n-stage`\nCategory: host-level data control\n\n#### Summary\n\n`*stage` enables a two-phase editing model on a Sercrod host.\nInstead of mutating the host data directly, the runtime creates a deep copy called the staged buffer.\nAll bindings inside the host work against this staged buffer, and you can later commit or discard those changes using other directives such as `*apply` and `*restore`.\n\n- Without `*stage`: bindings read and write the host data (`_data`) directly.\n- With `*stage`: bindings read and write a staged copy (`_stage`), and the host data is only updated when you explicitly apply changes.\n\nThe attribute value of `*stage` or `n-stage` is currently not evaluated. Presence alone enables staging.\n\n#### Basic example\n\n@@@html\n<serc-rod data={ user: { name: \"Alice\", email: \"alice@example.com\" } } *stage>\n  <form>\n    <label>\n      Name\n      <input *input=\"user.name\">\n    </label>\n\n    <label>\n      Email\n      <input *input=\"user.email\">\n    </label>\n\n    <p>Staged preview: <span *print=\"user.name\"></span></p>\n\n    <button type=\"button\" *apply>Apply</button>\n    <button type=\"button\" *restore>Reset</button>\n  </form>\n</serc-rod>\n@@@\n\nIn this configuration:\n\n- Typing into the inputs updates the staged `user` object.\n- The host template is evaluated against the staged buffer.\n- Clicking `*apply` copies the staged values back to the host data.\n- Clicking `*restore` discards staged edits and resets the staged buffer from the last applied state.\n\n#### Behavior\n\nAt runtime, Sercrod maintains two internal objects per host:\n\n- `_data`: the stable host data that represents the committed state.\n- `_stage`: an optional staged copy used while `*stage` is active.\n\nWhen a Sercrod host is created:\n\n- `_data` is initialised from the `data` attribute or inherited scope as usual.\n- If the host has `*stage` or `n-stage`, and `_stage` is still `null`, the runtime creates a deep copy of `_data` and stores it in `_stage`.\n\nWhen the host renders:\n\n- The effective scope for expressions is `this._stage ?? this._data`.\n- If `_stage` exists, all ordinary bindings and directives inside the host see only the staged copy.\n- If `_stage` is `null`, everything behaves like a normal, non staged host.\n\nWhen inputs bound with `*input` or `n-input` fire:\n\n- The runtime chooses `target = this._stage ?? this._data`.\n- The binding expression is evaluated against `target` and then assigned back into `target`.\n- If `_stage` exists, input bindings update the staged buffer, not the committed data.\n\n#### Evaluation timing\n\n`*stage` affects when and where data is cloned, but it does not introduce its own expression language or custom timing. The key timings are:\n\n- During initialisation (`connectedCallback`):\n  - After `_data` is prepared, if the host has `*stage` or `n-stage` and `_stage` is still `null`, Sercrod deep copies `_data` into `_stage`.\n- During each `update()`:\n  - For nested hosts that receive a parent scope via `__sercrod_scope`, if the host has `*stage` or `n-stage` and `__sercrod_scope` is set, `_stage` is refreshed from the inherited scope.\n- During input events:\n  - `*input` and `n-input` always write to `this._stage ?? this._data`.\n  - When `_stage` is present, the input handlers skip automatic full host re render, because the form control already reflects the current user input.\n\nNo additional scheduling hooks are introduced by `*stage` itself. It changes which object is edited and rendered, not when expressions are evaluated.\n\n#### Execution model\n\nConceptually, `*stage` splits the host data into:\n\n- Stable state: `_data` (committed, long lived).\n- Working state: `_stage` (temporary, editable).\n\nThe execution model is:\n\n1. Host initialises `_data`.\n2. If `*stage` is present:\n   - `_stage` is created as a deep copy of `_data`.\n   - All template expressions inside the host see `_stage`.\n3. While the user edits:\n   - `*input` and `n-input` writes go into `_stage`.\n   - Other directives that mutate data (such as `*load`) also prefer `_stage` over `_data`.\n4. When the user commits:\n   - `*apply` copies `_stage` back into `_data` and refreshes `_stage` from the new committed state.\n5. When the user discards:\n   - `*restore` throws away the current `_stage` contents and recreates `_stage` from the last committed snapshot.\n\nInternally, Sercrod first tries `structuredClone` to create `_stage`. If that fails, it falls back to `JSON.parse(JSON.stringify(...))`. This means `*stage` is primarily intended for JSON like data (plain objects, arrays, numbers, strings, booleans, and null).\n\n#### Variable creation\n\n`*stage` does not create new variables in the template scope. It only changes which data object is used as the root when resolving existing expressions.\n\n- Identifiers like `user`, `form`, and so on are still defined where they normally are.\n- `$root`, `$parent`, and other special values behave as usual.\n- There is no additional binding such as `$stage` or similar introduced by this directive in the current runtime.\n\nIn other words, the same expressions you would write without `*stage` continue to work. They now read and write from the staged buffer instead of the committed data when staging is active.\n\n#### Scope layering\n\nThe behavior of `*stage` depends on whether the Sercrod host is a root host or a nested host.\n\n- Root host:\n  - `_data` comes from the host `data` attribute.\n  - `_stage` is cloned from `_data` once during initialisation.\n  - Subsequent `update()` calls do not automatically overwrite `_stage` from `_data`. The staged buffer is kept until you explicitly overwrite or reset it via `*apply`, `*restore`, or other operations that rebuild `_stage`.\n\n- Nested host (child Sercrod with an inherited scope `__sercrod_scope`):\n  - `_data` is a proxied view of the inherited scope.\n  - On `update()`, if `*stage` is present and `__sercrod_scope` is available, `_stage` is refreshed from `__sercrod_scope`.\n  - When you click `*apply`, changes in `_stage` are written back into `_data` (and therefore into the parent scope), then `_stage` is refreshed from the new committed state.\n\nIn both cases, expressions inside the host are evaluated against `this._stage ?? this._data`. Parent scopes above the host are not changed by `*stage` unless you explicitly propagate changes using `*apply` on a nested host or other application specific code.\n\n#### Interaction with bindings and data directives\n\n`*stage` changes the behavior of several other directives that work on host data:\n\n- `*input` / `n-input`:\n  - By default, bindings write to `this._stage ?? this._data`.\n  - With `*stage` active, all input writes go into `_stage`.\n  - The input handler does not automatically trigger a full host re render when `_stage` is present, so derived views are refreshed when you explicitly re render (for example via `*apply`, `*restore`, or `*load`).\n\n- `*load` / `n-load`:\n  - When loading JSON, the runtime updates `_stage` if it exists, otherwise `_data`.\n  - With `*stage`, this lets you load draft data without touching the committed state until you explicitly apply it.\n\n- `*post` / `n-post`:\n  - When serialising data for sending, the runtime uses `this._stage ?? this._data` as the source.\n  - With `*stage`, the posted payload is the staged buffer by default.\n\n- `*save` / `n-save`:\n  - When generating a downloadable JSON snapshot, the runtime again uses `this._stage ?? this._data`.\n  - With `*stage`, the exported file reflects the current staged state.\n\nOther directives that only read data (such as `*print`, `*textContent`, or conditions and loops) automatically see the staged values when `*stage` is present, because they evaluate against the same effective scope.\n\n#### Interaction with *apply and *restore\n\n`*stage` is designed to be used together with `*apply` and `*restore`.\n\n- `*apply`:\n  - Available on any descendant element inside a staged host.\n  - On click, if `_stage` exists:\n    - Copies the staged values into `_data` using `Object.assign`.\n    - Calls `update()` on the host.\n    - Stores a snapshot of the committed `_data` into an internal `_applied` buffer used by `*restore`.\n  - Without `_stage` (no `*stage` on the host), `*apply` does nothing.\n\n- `*restore`:\n  - Also available on any descendant element inside a staged host.\n  - On click:\n    - Only executes if the host has `*stage` or `n-stage`.\n    - Recreates `_stage` from `_applied` if available, otherwise from `_data`.\n    - Calls `update()` so that the view reflects the restored state.\n  - Without `*stage`, `*restore` has no effect.\n\nThis trio (`*stage` plus `*apply` plus `*restore`) is the recommended pattern for implementing editable but confirmable forms.\n\n#### Use with conditionals and loops\n\n`*stage` does not introduce any new restrictions on conditionals or loops. Instead, it transparently changes the data that those directives see:\n\n- `*if`, `*for`, and `*each` evaluate their expressions against the effective scope `this._stage ?? this._data`.\n- With staging enabled, these directives respond to staged values instead of committed values.\n\nExamples:\n\n- Show a banner when there are unsaved staged changes (assuming your data includes such a flag).\n\n@@@html\n<serc-rod data={ form: { dirty: false } } *stage>\n  <section *if=\"form.dirty\">\n    <p>You have unsaved changes.</p>\n  </section>\n\n  <!-- bindings that can toggle form.dirty in the staged buffer -->\n</serc-rod>\n@@@\n\n- Render a preview list based on staged filters and staged items.\n\n@@@html\n<serc-rod data={ filter: \"\", items: [] } *stage>\n  <input *input=\"filter\">\n\n  <ul>\n    <li *each=\"item of items\" *if=\"!filter || item.includes(filter)\">\n      <span *print=\"item\"></span>\n    </li>\n  </ul>\n\n  <button type=\"button\" *apply>Apply filter and items</button>\n</serc-rod>\n@@@\n\nIn both cases, the loop and conditional behavior is identical to non staged code, but they operate on staged values.\n\n#### Best practices\n\n- Use `*stage` at the host level for two phase edits:\n  - Form drafts that should not be committed until an explicit click.\n  - Bulk edits that should be applied or discarded in one step.\n  - Import or load flows where the user reviews the loaded data before it becomes effective.\n\n- Keep staged data JSON like:\n  - `*stage` uses a deep clone based on `structuredClone` with a JSON fallback.\n  - Avoid storing functions, DOM nodes, or complex non serialisable objects in the part of the data you expect to stage.\n\n- Combine with `*apply` and `*restore`:\n  - Always provide a clear commit button (`*apply`) and, ideally, a reset button (`*restore`) when using staging.\n  - This makes the two phase model obvious in the UI.\n\n- Be explicit about when the view refreshes:\n  - With `*stage`, input handlers do not trigger a full host re render on each keystroke.\n  - If you need derived non input elements to update live based on staged changes, call `update()` from your own methods or trigger operations (`*load`, `*apply`, `*restore`) that call `update()` internally.\n\n- Restrict `*stage` to Sercrod hosts:\n  - In the current runtime, only Sercrod host elements (such as `<serc-rod>` or other Sercrod based custom elements) actually interpret `*stage` or `n-stage`.\n  - Adding `*stage` to ordinary elements has no effect from Sercrod's perspective.\n\n#### Additional examples\n\nSimple staged form with save and reset:\n\n@@@html\n<serc-rod data={ profile: { name: \"\", bio: \"\" } } *stage>\n  <h2>Edit profile (staged)</h2>\n\n  <label>\n    Name\n    <input *input=\"profile.name\">\n  </label>\n\n  <label>\n    Bio\n    <textarea n-input=\"profile.bio\"></textarea>\n  </label>\n\n  <p>Committed name: <span *print=\"$root.profile.name\"></span></p>\n  <p>Staged name: <span *print=\"profile.name\"></span></p>\n\n  <button type=\"button\" *apply>Apply to committed profile</button>\n  <button type=\"button\" *restore>Discard staged changes</button>\n</serc-rod>\n@@@\n\nStaged load and post flow:\n\n@@@html\n<serc-rod data={ form: {} } *stage>\n  <input type=\"file\" *load>\n  <button type=\"button\" *post=\"/api/preview\">Send staged form to preview API</button>\n\n  <pre *print=\"form | json\"></pre>\n\n  <button type=\"button\" *apply>Apply staged form to committed data</button>\n</serc-rod>\n@@@\n\nHere:\n\n- `*load` merges the loaded JSON into `_stage` rather than `_data`.\n- `*post` sends the staged data to the server.\n- Only when `*apply` is clicked does the committed data change.\n\n#### Notes\n\n- `*stage` is a host level flag. It does not have per field granularity in the current implementation. Once activated, all host level data that bindings touch are read and written via the staged buffer.\n- The attribute value of `*stage` or `n-stage` is currently ignored. Use it only as a presence flag. Existing examples that show `*stage=\"draft\"` are effectively equivalent to `*stage`.\n- Without `*stage`, directives such as `*apply` and `*restore` safely do nothing, so you can keep button markup in place and enable staging later by adding the host flag.\n- Because the staged buffer is a deep copy, large data structures may have a cost when staging is first enabled or when a nested staged host resynchronises from its parent scope. Design your data shape accordingly.\n",
  "switch": "### *switch\n\n#### Summary\n\n`*switch` selects one branch from its direct children based on an expression, then renders that branch and optionally falls through to later branches until a break is reached.\nIt behaves like a JavaScript `switch` statement with fallthrough, but is expressed as HTML attributes on a single container element.\n\nKey points:\n\n- The element with `*switch` is not rendered itself; only its matching `*case` / `*default` children are rendered.\n- Only direct child elements that declare `*case`, `*case.break`, or `*default` (or their `n-` aliases) participate in the switch.\n- Fallthrough is explicit and controlled with `*break` or `*case.break` on the same child element.\n- The evaluated switch value is exposed to children as `$switch`.\n\nAliases:\n\n- `*switch` and `n-switch` are aliases.\n- Child branch attributes also have `n-` aliases: `*case`, `n-case`, `*case.break`, `n-case.break`, `*default`, `n-default`, `*break`, `n-break`.\n\n\n#### Basic example\n\nA simple status switch:\n\n```html\n<serc-rod id=\"app\" data='{\"status\":\"ready\"}'>\n  <div *switch=\"status\">\n    <p *case=\"'idle'\">Waiting...</p>\n    <p *case=\"'ready'\">Ready</p>\n    <p *case=\"'running'\">Running</p>\n    <p *default>Unknown status</p>\n  </div>\n</serc-rod>\n```\n\nBehavior:\n\n- Sercrod evaluates `status` on the `*switch` element.\n- It scans the direct child elements of the `div` in DOM order.\n- The first `*case` whose expression matches `status` becomes the entry point.\n- That `p` and all subsequent `*case` / `*default` elements are rendered until a break is encountered.\n- If no `*case` matches, the first `*default` is used instead.\n- The `<div *switch=\"...\">` itself is not rendered; only the chosen branch elements appear in the final DOM.\n\n\n#### Behavior\n\n`*switch` is a structural directive applied to a single container element.\n\nOn each render:\n\n- Sercrod evaluates the `*switch` (or `n-switch`) expression on the host.\n- The evaluated value is stored in a local variable `$switch` and passed to all branch expressions and branch bodies.\n- Sercrod iterates over the host's **direct child elements** in DOM order.\n- It treats only children that declare:\n\n  - `*case`, `n-case`\n  - `*case.break`, `n-case.break`\n  - `*default`, `n-default`\n\n  as branches. All other children are ignored for this switch.\n\nBranch selection and fallthrough:\n\n- Before any branch has been selected, Sercrod looks for:\n\n  - The first `*case` / `n-case` / `*case.break` / `n-case.break` whose expression matches the switch value.\n  - Or, if no case has matched yet, the first `*default` / `n-default`.\n\n- Once a branch is selected, Sercrod:\n\n  - Clones that child node.\n  - Strips the control attributes (`*case`, `*case.break`, `*default`, `*break` and their `n-` aliases) from the clone.\n  - Renders the clone as a normal element with `_renderElement`, using a scope that includes `$switch`.\n\n- Sercrod then continues to the **next** eligible branch child (fallthrough) and repeats the process until a break is encountered or there are no more branch children.\n\nImportant structural details:\n\n- Only direct child elements participate.\n- All descendants inside a branch are rendered normally; they can use any other directives (`*if`, `*for`, `*each`, `*include`, `*import`, bindings, events, etc.).\n- Siblings of the `*switch` host element are unaffected.\n\n\n#### Case expressions\n\nEach `*case` / `n-case` / `*case.break` / `n-case.break` attribute holds an expression used to test against the evaluated switch value.\n\nExpression evaluation:\n\n- Sercrod evaluates the case expression with an extended scope that includes `$switch`:\n\n  - You can reference `$switch` directly inside the case expression if you need it.\n  - All other data and helpers available on the switch host are also accessible.\n\n- If the expression evaluates successfully, matching behavior depends on the resulting type:\n\n  - **Function:** Treated as a custom predicate.\n\n    - The result is `true` if `fn($switch, scope)` returns a truthy value.\n\n  - **RegExp:** Treated as a pattern.\n\n    - The result is `true` if `regexp.test(String($switch))` returns `true`.\n\n  - **Array:** Treated as a list of allowed values.\n\n    - The result is `true` if any element of the array matches the switch value.\n\n  - **Set-like object:** Any object with a `.has()` method.\n\n    - The result is `true` if `set.has($switch)` returns `true`.\n\n  - **Boolean:** Used directly.\n\n    - `true` means the case matches, `false` means it does not.\n\n  - **Other values (primitives, objects without `.has`, etc.):**\n\n    - The value is compared to the switch value using Sercrod's internal equality check.\n\n- If evaluation throws, or none of the above patterns succeed, Sercrod falls back to a simple string-list matcher:\n\n  - The raw attribute text is split by `,` and `|` into tokens.\n  - Each token is trimmed and evaluated as an expression when possible.\n  - If evaluating a token fails, it is used as a literal.\n  - If any token's result equals the switch value, the case matches.\n\nCommon patterns:\n\n- Simple value:\n\n  ```html\n  <p *case=\"'ready'\">Ready</p>\n  ```\n\n- Multiple string values in one case:\n\n  ```html\n  <p *case=\"'idle' | 'waiting'\">Waiting...</p>\n  ```\n\n- Predicate function:\n\n  ```html\n  <p *case=\"(val) => val > 10\">Large value</p>\n  ```\n\n- Regular expression:\n\n  ```html\n  <p *case=\"/^error:/\">Error</p>\n  ```\n\n- Array / Set of allowed values:\n\n  ```html\n  <p *case=\"['draft','pending']\">Not published</p>\n  ```\n\n\n#### Evaluation timing\n\n`*switch` participates in Sercrod's structural evaluation in this order:\n\n- Host-level `*let` / `n-let` are evaluated first, so they can influence the switch expression and case expressions.\n\n- Host-level `*if` / `*elseif` / `*else` chains are resolved **before** `*switch`:\n\n  - If a `*switch` element is part of a conditional chain, Sercrod first selects the active branch of that chain.\n  - It then renders only that chosen branch node, and `*switch` is applied to that branch node as usual.\n\n- After the `*if` chain, Sercrod looks for `*switch` / `n-switch` on the (possibly cloned) working node.\n\n- If `*switch` is present:\n\n  - Sercrod runs `_renderSwitchBlock`, which renders only the selected cases/defaults.\n  - The host element itself is not rendered.\n  - Sercrod does not proceed to other structural directives on that host (`*each`, `*for`, `*template`, `*include`, `*import`).\n\n- Child directives inside each case/default are evaluated during `_renderElement` for the case clone:\n\n  - `*if`, `*for`, `*each`, `*include`, `*import`, bindings, events, and so on behave as usual inside each branch.\n  - Each branch is rendered with its own scope that includes `$switch`.\n\n\n#### Execution model\n\nConceptually, the runtime performs these steps for `*switch`:\n\n1. **Evaluate switch expression**\n\n   - Read `*switch` or `n-switch` from the host element.\n   - Evaluate the expression with the current effective scope to obtain `switchVal`.\n\n2. **Prepare child scope**\n\n   - Construct `childScope` as a shallow copy of the host scope extended with `$switch: switchVal`.\n   - This scope is used both for case-expression evaluation and for rendering branch bodies.\n\n3. **Scan direct children**\n\n   - Collect direct child nodes of the host into an array.\n   - Iterate over them in DOM order.\n   - Ignore any node that is not an element.\n   - For each element, determine whether it is:\n     - A default branch (`*default` / `n-default`).\n     - A case branch (`*case`, `n-case`, `*case.break`, `n-case.break`).\n     - Or not part of the switch at all (no case/default attributes).\n\n4. **Select entry branch**\n\n   - While no branch has been selected (`falling === false`):\n\n     - If the child is a default branch and no earlier case has matched, start fallthrough from this default.\n     - Otherwise, if it has a case expression:\n       - Evaluate the case expression using `_matchCase` with `$switch`.\n       - If it matches, set `falling = true`.\n       - If not, skip this child and continue scanning.\n\n5. **Render fallthrough**\n\n   - Once `falling` becomes `true`:\n\n     - Clone the case/default child (`cloneNode(true)`).\n     - Strip the control attributes from the clone:\n       - `*case`, `n-case`, `*default`, `n-default`, `*case.break`, `n-case.break`, `*break`, `n-break`.\n     - Call `_renderElement` on the clone with `childScope` and the parent of the original switch host.\n       - The clone's descendants are rendered normally.\n\n6. **Break handling**\n\n   - After rendering each branch child, Sercrod checks the original child element for break markers:\n\n     - If it has any of `*break`, `n-break`, `*case.break`, `n-case.break`, Sercrod stops the scan and does not consider any later branches.\n     - The value of the `*break` attribute is not evaluated by the `*switch` implementation; it is treated as a simple presence marker in this context.\n\n7. **Host element**\n\n   - The original `*switch` node itself is not appended to the output DOM.\n   - Only the rendered clones of case/default children appear in the final tree.\n\n\n#### Variable creation and scope layering\n\n`*switch` does not introduce new data variables by itself, but it adds a special helper:\n\n- `$switch` is injected into the scope used for case expressions and case bodies:\n\n  - Inside `*case` / `*default` branches you can read `$switch` directly.\n  - `childScope` also inherits all existing data:\n\n    - Root data bound on `<serc-rod>` (for example `data='{\"status\":\"ready\"}'`).\n    - Any variables created by `*let` on the switch host or ancestors.\n    - Methods and globals configured via Sercrod's configuration.\n\nScope behavior:\n\n- When you use `*let` on the `*switch` host, those variables are available to both the switch expression and case expressions, as well as to branch bodies.\n- Branch-specific `*let` directives on case/default nodes work as usual and can introduce additional local variables.\n\n\n#### Parent access\n\n`*switch` runs within the normal Sercrod scope chain:\n\n- `$root` continues to refer to the root data of the Sercrod world.\n- `$parent` continues to refer to the nearest ancestor Sercrod host's data.\n- `$switch` is just an additional helper, not a replacement for existing parent or root access.\n\nInside case/default branches:\n\n- All bindings and expressions see the extended `childScope`, which includes:\n\n  - Normal data access (for example `status`, `user`, `config`).\n  - `$root`, `$parent`, other Sercrod helpers.\n  - `$switch` with the current switch value.\n\n\n#### Use with conditionals and loops\n\n`*switch` composes with other control-flow directives, but you need to be aware of where each directive is placed.\n\nHost-level conditionals:\n\n- When `*switch` appears on a node that is also part of a `*if` / `*elseif` / `*else` chain:\n\n  - Sercrod first resolves the conditional chain and selects exactly one branch element.\n  - That branch is cloned, stripped of its conditional attributes, and passed to `renderNode`.\n  - `*switch` on that chosen branch then executes normally.\n\nExample:\n\n```html\n<div *if=\"mode === 'simple'\" *switch=\"status\">\n  <p *case=\"'ready'\">Simple / Ready</p>\n  <p *case=\"'running'\">Simple / Running</p>\n  <p *default>Simple / Unknown</p>\n</div>\n<div *elseif=\"mode === 'advanced'\" *switch=\"status\">\n  <p *case=\"'ready'\">Advanced / Ready</p>\n  <p *default>Advanced / Other</p>\n</div>\n<div *else>\n  <p>No switch here</p>\n</div>\n```\n\n- Only one of the three outer `<div>` elements is chosen by the conditional chain.\n- If the chosen branch has `*switch`, it runs on that branch node as usual.\n\nBranches containing loops:\n\n- You can safely use `*for` / `*each` inside case/default branches:\n\n  ```html\n  <serc-rod id=\"app\" data='{\n    \"status\":\"list\",\n    \"items\":[\"A\",\"B\",\"C\"]\n  }'>\n    <section *switch=\"status\">\n      <p *case=\"'empty'\">No items.</p>\n      <ul *case=\"'list'\">\n        <li *for=\"item of items\" *print=\"item\"></li>\n      </ul>\n      <p *default>Unknown mode.</p>\n    </section>\n  </serc-rod>\n  ```\n\n- In this example, the `*switch` selects the `<ul>` branch for `\"list\"`, and then `*for` runs inside that branch to render `<li>` elements.\n\nLoops containing switches:\n\n- You can also put `*switch` inside a loop body:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\">\n      <span *print=\"item.name\"></span>\n      <span *switch=\"item.status\">\n        <span *case=\"'active'\">Active</span>\n        <span *case=\"'inactive'\">Inactive</span>\n        <span *default>Unknown</span>\n      </span>\n    </li>\n  </ul>\n  ```\n\n- Here, each iteration of the `*for` loop receives its own `*switch` evaluation for `item.status`.\n\n\n#### Use with templates, *include and *import\n\n`*switch` is itself a structural directive that controls which children are rendered and how they are cloned.\nOn the **same host element**, Sercrod processes `*switch` **before** other structural directives like `*each`, `*for`, `*template`, `*include`, and any helpers built on top of them (such as `*import`).\n\nStructural restriction on the host element:\n\n- When a node has `*switch` or `n-switch`, Sercrod:\n\n  - Executes the switch block for that node.\n  - Does not render the host element itself.\n  - Does not run other structural host-level logic such as:\n\n    - `*each` / `n-each`\n    - `*for` / `n-for`\n    - `*template` / `n-template`\n    - `*include` / `n-include`\n    - Any higher-level helpers that rely on these (for example, an `*import` helper built on `*include` / `*template`).\n\n- Practically, this means:\n\n  - If you put `*switch` together with `*each`, `*for`, `*include`, `*import`, or `*template` on the same element, `*switch` takes over and the other directive will not behave as you might expect.\n  - Sercrod treats the `*switch` host as a pure container for branch children.\n\nRecommended pattern:\n\n- Use `*switch` on a dedicated container element:\n\n  ```html\n  <section *switch=\"view\">\n    <div *case=\"'list'\">\n      <ul *include=\"'item-list-template'\"></ul>\n    </div>\n    <div *case=\"'detail'\">\n      <article *import=\"'item-detail-template'\"></article>\n    </div>\n    <p *default>Select a view.</p>\n  </section>\n  ```\n\n- Move loops (`*for` / `*each`) and template includes (`*include`, `*import`) into the individual branch elements, not onto the same element that owns `*switch`.\n\nWhat is allowed:\n\n- Inside a case/default branch (on the child element), you are free to combine:\n\n  - `*case` / `*default` with `*for`, `*each`, `*include`, `*import`, and other directives.\n  - The branch node is cloned and then passed to the normal element renderer, so structural directives on the branch node itself work as usual.\n\n\n#### Best practices\n\n- Keep the switch host simple:\n\n  - Use `*switch` on a neutral container (such as `<div>`, `<section>`, `<span>`) whose only job is to hold branch elements.\n  - Avoid attaching other structural directives to the same host.\n\n- One branch per element:\n\n  - Treat each `*case` / `*default` element as the root of one branch.\n  - If you need multiple siblings in a branch, wrap them in a container that carries `*case` or `*default`.\n\n- Prefer simple case expressions:\n\n  - Start with basic patterns such as string literals (`'ready'`), value lists (`'a' | 'b'`), or small predicate functions.\n  - Use regular expressions, arrays, or Sets when they clearly simplify your intent.\n\n- Use `$switch` for clarity:\n\n  - When a case expression does more than simple equality, make the dependency on the switch value explicit:\n\n    ```html\n    <p *case=\"(v) => v && v.startsWith('error:')\">Error</p>\n    ```\n\n- Combine with `*if` only where it simplifies the layout:\n\n  - Prefer a single `*switch` over a long sequence of `*if` / `*elseif` when you are matching on a single value.\n  - Use `*if` around `*switch` only when there is a genuine structural difference at a higher level.\n\n- Break explicitly:\n\n  - Use `*case.break` or add `*break` / `n-break` to the last branch you want to render.\n  - This makes fallthrough behavior easier to understand.\n\n\n#### Additional examples\n\nMultiple fallthrough branches:\n\n```html\n<serc-rod id=\"app\" data='{\"level\":\"warning\"}'>\n  <div *switch=\"level\">\n    <p *case=\"'info'\">Info: low priority.</p>\n    <p *case=\"'warning'\">Warning: check this.</p>\n    <p *case.break=\"'error'\">Error: action required.</p>\n    <p *default>Fallback message.</p>\n  </div>\n</serc-rod>\n```\n\n- If `level` is `'warning'`, the second `<p>` matches and fallthrough continues, rendering:\n\n  - `Warning: check this.`\n  - `Error: action required.`\n\n- Because the third `<p>` uses `*case.break`, rendering stops there and the `*default` branch is not rendered.\n\nSimple default-only switch:\n\n```html\n<div *switch=\"status\">\n  <p *case=\"'ready'\">Ready</p>\n  <p *default>Not ready yet</p>\n</div>\n```\n\nUsing `$switch` inside the branch body:\n\n```html\n<div *switch=\"status\">\n  <p *case=\"'error'\">\n    Status is <strong *print=\"$switch\"></strong>.\n  </p>\n  <p *default>Everything looks fine.</p>\n</div>\n```\n\n\n#### Notes\n\n- `*switch` / `n-switch` are structural and control only which direct children are rendered; the host element itself is not output.\n- Only elements marked with `*case` / `n-case` / `*case.break` / `n-case.break` / `*default` / `n-default` participate in the switch; other children of the switch host are ignored.\n- `$switch` is available both in case expressions and inside branch bodies.\n- `*case.break` and `n-case.break` behave like `*case` / `n-case` with an implicit break: once such a branch is rendered, later branches are not considered for this switch.\n- When a branch element has `*break` or `n-break`, it also stops further fallthrough for this switch.\n- Combining `*switch` with other host-level structural directives like `*for`, `*each`, `*template`, `*include`, or `*import` on the **same element** is not supported in practice; only `*switch` is applied. Move loops and includes into individual case/default branches or to surrounding elements.\n",
  "template": "### *template\n\n#### Summary\n\n`*template` marks a subtree as a reusable template.\nThe subtree is registered under a name in the current Sercrod world and is not rendered where it is declared.\nLater, `*include` can refer to that name and copy the template’s inner content into a real element.\nIf you want to share templates across files, you usually combine `*template` with `*import`: `*import` loads HTML from another file, and any `*template` declarations inside that HTML are then available to `*include`.\n\nKey points:\n\n- `*template` / `n-template` define templates.\n- The definition is non-rendering: the host element is used only as a prototype.\n- The template name is resolved through a shared helper that is also used by `*include`.\n- Each Sercrod world keeps its own template registry.\n- A single element should not combine `*template` with other structural directives such as `*include`, `*import`, `*for`, or `*each`: `*template` always runs first, registers the template, and stops rendering that node, so the other directives on that same element never take effect.\n\n\n#### Basic example\n\nA simple reusable card template and a loop that includes it:\n\n```html\n<serc-rod\n  id=\"app\"\n  data='{\n    \"users\": [\n      { \"name\": \"Alice\", \"bio\": \"Loves minimal HTML.\" },\n      { \"name\": \"Bob\",   \"bio\": \"Enjoys fast renderers.\" }\n    ]\n  }'\n>\n  <!-- Declare a reusable template named \"userCard\" -->\n  <template *template=\"'userCard'\">\n    <article class=\"user-card\">\n      <h2 *print=\"user.name\"></h2>\n      <p *print=\"user.bio\"></p>\n    </article>\n  </template>\n\n  <!-- Use the template inside a loop -->\n  <section *each=\"user of users\">\n    <div *include=\"'userCard'\"></div>\n  </section>\n</serc-rod>\n```\n\nBehavior:\n\n- `<template *template=\"'userCard'\">` is registered once as `userCard` in the current Sercrod world.\n- The `<template>` node itself is not rendered.\n- For each `user` in `users`, `*include=\"'userCard'\"` copies the inner content of the template (`<article class=\"user-card\">…`) and renders it in the loop’s scope.\n\n\n#### Behavior\n\n- `*template` is a structural, non-rendering directive.\n- When Sercrod encounters an element with `*template` or `n-template`:\n  - It resolves the template name via `_resolve_template_name`.\n  - If the name is valid and not already registered in that world, it deep-clones the element and stores the clone in the world-local template registry.\n  - The original element is not appended to the rendered output.\n- Templates by themselves never produce visible output. They only become visible when something else (usually `*include`) uses them by name.\n\nAlias:\n\n- `*template` and `n-template` are aliases and behave identically.\n\n\n#### Name resolution\n\n`*template` uses the shared helper `_resolve_template_name(raw_text, scope, { el, mode: \"template\" })`.\n\nFor an attribute like:\n\n```html\n<template *template=\"expr\">...</template>\n```\n\nSercrod resolves the name as follows:\n\n1. Convert the attribute to a string and trim it.\n2. Try to evaluate it as an expression:\n\n   - `this.eval_expr(src, scope, { el, mode: \"template\", quiet: true })` is called.\n   - If the evaluation yields a non-null, non-undefined value, Sercrod turns it into a string, trims it, and if the result is non-empty, that becomes the name.\n\n3. If evaluation does not produce a usable name, fall back to identifier rules:\n\n   - If the original trimmed text matches `/^[A-Za-z_][A-Za-z0-9_-]*$/`, it is treated as a name directly.\n   - Otherwise, name resolution fails.\n\nIf name resolution fails:\n\n- The template is not registered.\n- When warnings are enabled, Sercrod logs a message like `*template: empty or invalid name: ...`.\n\nExamples:\n\n- Literal string:\n\n  ```html\n  <template *template=\"'card'\">...</template>\n  ```\n\n  The name is `card`.\n\n- Bare identifier:\n\n  ```html\n  <template *template=\"userCard\">...</template>\n  ```\n\n  If `userCard` evaluates to a non-empty string, that string is used.\n  If not, but `userCard` looks like an identifier, `userCard` itself is used.\n\n\n#### World-local registration and duplicates\n\nTemplates are registered per Sercrod world:\n\n- Each `<serc-rod>` instance has its own `_template_registry`.\n- When `*template` registers a name, it stores a deep clone of the host element as the prototype for that template in that world.\n- When `*include` looks up a template by name, it uses `_lookupTemplateNear(name)`:\n  - It checks the current world first.\n  - If not found, it walks up through parent Sercrod worlds until it finds the first matching template.\n\nDuplicate names:\n\n- If the current world’s registry already has a template with the same name:\n  - `*template` does nothing for the new declaration.\n  - When warnings are enabled, Sercrod logs `*template duplicated: name`.\n- The first registration wins; later ones are ignored.\n\n\n#### Evaluation timing\n\n`*template` runs early in the element pipeline:\n\n- When Sercrod renders an element:\n  - It first checks for `*template` / `n-template`.\n  - If present, it resolves the name, tries to register the template, and then returns immediately for that node.\n- The children of the `*template` element are not rendered at the declaration site.\n- Any structural or binding directives on that same element are effectively ignored, because the renderer does not continue past the `*template` registration step for that node.\n\nFrom the rendered document’s point of view:\n\n- Template declarations are invisible markers. Their only effect is to populate the template registry for later use.\n\n\n#### Execution model\n\nConceptually, Sercrod does the following for `*template`:\n\n1. Detect declaration:\n\n   - If the current node has `*template` or `n-template`, treat it as a template definition.\n\n2. Resolve the name:\n\n   - Use `_resolve_template_name` with `mode: \"template\"` to get a non-empty string.\n\n3. Handle invalid names:\n\n   - If the name is empty or invalid, optionally warn and return without rendering this node.\n\n4. Handle duplicates:\n\n   - If the current world’s `_template_registry` already has that name, optionally warn and return without rendering this node.\n\n5. Register a prototype:\n\n   - Deep-clone the node (including its attributes and children).\n   - Store the clone as the prototype for that template name in `_template_registry`.\n   - Store basic visibility attributes (`inert`, `hidden`, `aria-hidden`) in `_template_attr_snapshot` for possible future use.\n\n6. Skip output:\n\n   - Do not append the original node to the DOM of the rendered result.\n   - Do not process its children at the declaration location.\n\nLater, when `*include` uses this name, Sercrod:\n\n- Looks up the prototype via `_lookupTemplateNear`.\n- Copies `proto.innerHTML` into the caller element’s `innerHTML`.\n- Processes the inserted children using the caller’s scope.\n\n\n#### Variable creation\n\n`*template` does not create any new variables by itself.\n\n- It does not define loop variables or aliases.\n- It does not introduce special `$`-helpers.\n- Only the name of the template is extracted; nothing in the template body is evaluated at declaration time.\n\nVariables such as `user`, `item`, or `config` that appear inside the template body must be supplied by the scope at the call site (where `*include` is used).\n\n\n#### Scope layering\n\nDeclaration-time scope and usage-time scope are distinct:\n\n- At declaration time:\n\n  - Sercrod uses the scope only to resolve the template name (if the name is an expression).\n  - The template body is not evaluated.\n  - No local scope is created for the body.\n\n- At usage time (via `*include`):\n\n  - The template’s inner content is treated as if it had been written inline at the include site.\n  - All directives inside the template body are evaluated in the caller’s scope.\n\nImplications:\n\n- The template body sees:\n\n  - Variables from the caller such as `user`, `row`, `item`.\n  - Host data from `<serc-rod data=\"...\">`.\n  - Special helpers like `$data`, `$root`, `$parent`.\n  - Methods and filters configured on that world.\n\n- The scope of the declaration site does not leak into the template body.\n\n\n#### Parent access\n\nWhen the template content is rendered (via `*include`):\n\n- `$root` refers to the root data of the Sercrod world in which the include is happening.\n- `$parent` refers to the nearest ancestor Sercrod host of the include site.\n- The template body behaves like any other inline markup with respect to `$root` and `$parent`.\n\n`*template` does not add any extra parent layer over this. It only defines where the content comes from.\n\n\n#### Use with conditionals and loops\n\nThere are two places where conditionals and loops can appear:\n\n- On the `*template` declaration element itself.\n- Inside the template body.\n\nDeclaration element:\n\n- If you attach structural directives such as `*if`, `*for`, `*each`, or `*switch` to the same element as `*template`, `*template` wins:\n\n  - The renderer sees `*template`.\n  - It registers the template and returns immediately for that node.\n  - The other structural directives on that same node never run.\n\n- For this reason, do not combine `*template` with other structural directives on a single element. If you need conditions or loops, put them inside the template body.\n\nTemplate body:\n\n- Inside the body of the template, you can use any structural directives normally:\n\n  ```html\n  <template *template=\"'userRow'\">\n    <tr *if=\"user.active\">\n      <td *print=\"user.id\"></td>\n      <td *print=\"user.name\"></td>\n    </tr>\n  </template>\n\n  <tbody *each=\"user of users\">\n    <tr *include=\"'userRow'\"></tr>\n  </tbody>\n  ```\n\n- `*if` and other directives in the body are evaluated when the template is used via `*include`, not when the template is declared.\n\n\n#### Use with *include and *import\n\n`*template` is the definition side of the template system.\n`*include` and `*import` are consumers, but in different ways.\n\n- `*include`:\n\n  - Performs a name-based lookup against the template registry.\n  - Uses `_resolve_template_name` with `mode: \"include\"` and then `_lookupTemplateNear(name)` to find a prototype.\n  - Copies `proto.innerHTML` into the caller element’s `innerHTML`.\n  - Leaves the caller’s tag name and attributes unchanged.\n  - Does not involve any network access.\n\n- `*import`:\n\n  - Does not look up template names.\n  - Resolves its attribute as an URL (expression first, then a simple URL-like string).\n  - Performs a synchronous HTTP request (XMLHttpRequest), with caching per URL.\n  - On success, takes the received HTML string and assigns it directly to `node.innerHTML`.\n  - After that, the normal child rendering pass processes the imported HTML:\n    - Any `*template` declarations inside the imported HTML are registered in that world at this time.\n    - Any `*include`, bindings, and other directives inside the imported HTML run as usual.\n\nIn other words:\n\n- `*template` + `*include` is the local template mechanism (name-based, no network).\n- `*template` + `*import` is the cross-file mechanism:\n  - You can keep templates in an external HTML file.\n  - `*import` pulls that file’s HTML into the current world.\n  - Then `*include` uses those templates by name.\n\nUnsupported combinations on one element:\n\n- Putting `*template` and `*include` on the same element:\n\n  ```html\n  <!-- Not supported: *template wins, *include never runs -->\n  <div *template=\"'card'\" *include=\"'card'\"></div>\n  ```\n\n- Putting `*template` and `*import` on the same element:\n\n  ```html\n  <!-- Not supported: *template wins, *import never runs -->\n  <div *template=\"'card'\" *import=\"'/partials/card.html'\"></div>\n  ```\n\nFor these patterns, `*template` always runs first and suppresses the other directive for that node. Always separate declaration and usage onto different elements.\n\n\n#### Comparison: *include vs *import\n\nFrom the template system’s point of view:\n\n- `*include`:\n\n  - Source: a named template in the registry (`*template`).\n  - Input: a template name (expression or identifier).\n  - Transport: in-memory, no network.\n  - Effect: copy `proto.innerHTML` into `node.innerHTML`, then render children.\n  - Safety: depth is limited by `include.max_depth` to avoid infinite include loops.\n\n- `*import`:\n\n  - Source: external HTML content (file, endpoint, or module).\n  - Input: an URL (expression or URL-like string).\n  - Transport: synchronous HTTP (XMLHttpRequest) with a per-class cache.\n  - Effect: set `node.innerHTML` to the received HTML string, then render children.\n  - Safety: shares the same depth tracking (`_include_depth_map` and `include.max_depth`) to avoid recursive import chains.\n\nTypical usage patterns:\n\n- Use `*include` when:\n  - The template is defined in the same document or in a Sercrod world that is already in memory.\n  - You want predictable, purely in-memory reuse.\n\n- Use `*import` when:\n  - You want to load HTML from another file or service.\n  - That HTML may contain `*template` declarations, reusable snippets, or complete fragments.\n  - After the import, you can use `*include` to consume any templates defined in the imported content.\n\nThe string passed to `*import` is treated purely as an URL from Sercrod’s perspective.\nIf you use patterns like `\"/partials/card.html:card\"`, the `:card` part is just part of the URL and is not parsed specially by Sercrod itself.\nAny such semantics (for example, serving only one named fragment from a combined file) must be implemented on the server side.\n\n\n#### Best practices\n\n- Use `<template>` as the `*template` host:\n\n  - It clearly communicates that the element is a declaration, not a rendered node.\n  - Only its inner content is typically used via `*include`.\n\n- Keep declarations and usage separate:\n\n  - Treat the `*template` block as a catalog of reusable pieces.\n  - Use `*include` (and optionally `*import`) where you want them to appear.\n\n- Name templates consistently:\n\n  - Choose a clear naming scheme, such as `userCard`, `pageShell`, `tableRow`.\n  - Avoid name reuse in the same world to prevent duplicate warnings.\n\n- Design templates for the caller’s scope:\n\n  - Assume that variables such as `user`, `row`, or `item` come from the place where `*include` is used.\n  - Avoid relying on declaration-site local variables.\n\n- Do not combine `*template` with other structural directives on the same element:\n\n  - If you need conditionals or loops, put them inside the template body or in the caller code.\n  - If you need `*import`, use it on a different element that simply loads HTML for later use.\n\n\n#### Additional examples\n\nTemplates for a page shell:\n\n```html\n<serc-rod\n  id=\"page\"\n  data='{\"title\":\"Sercrod Docs\",\"subtitle\":\"Attribute-first templates\"}'\n>\n  <template *template=\"'pageShell'\">\n    <header>\n      <h1 *print=\"title\"></h1>\n      <p *print=\"subtitle\"></p>\n    </header>\n    <main>\n      <slot></slot>\n    </main>\n    <footer>\n      <small>Sercrod example</small>\n    </footer>\n  </template>\n\n  <section *include=\"'pageShell'\">\n    <p>This paragraph is rendered inside the <main> of the pageShell template.</p>\n  </section>\n</serc-rod>\n```\n\nTemplates loaded from another file via *import:\n\n```html\n<serc-rod id=\"root\">\n  <!-- Load external HTML that may define templates like \"card\" or \"layoutHeader\" -->\n  <div *import=\"'/partials/common-templates.html'\"></div>\n\n  <!-- After the import, those templates are available in this world -->\n  <section *include=\"'layoutHeader'\"></section>\n  <div *include=\"'card'\"></div>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*template` / `n-template` define reusable templates and register them per Sercrod world.\n- Template declarations themselves do not create visible output; they only populate the template registry.\n- `*include` uses the same name resolution helper as `*template` and copies `innerHTML` from the prototype into the caller.\n- `*import` does not use template names; it only loads HTML into `innerHTML`. Any templates in that HTML are registered later when the imported children are rendered.\n- Duplicate template names in the same world are ignored after the first registration, with optional warnings.\n- For clarity and predictable behavior, avoid combining `*template` with other structural directives on a single element.\n",
  "shadow": "### *shadow\n\n#### Summary\n\n`*shadow` defines a visible Shadow DOM template that can be connected to a host element with `*host`.\n\nThe directive is written on a `<template>` element. It defines the Shadow DOM side of the structure. A host element can then refer to that template by name.\n\nSercrod does not reimplement slot behavior. Standard `<slot>` and `slot=\"...\"` assignment is handled by the browser.\n\n`*shadow` is part of the Shadow DOM bridge. Sercrod's role is to connect a visible shadow template to a host element, not to move Light DOM children or build a custom component renderer.\n\n#### Basic example\n\nA named shadow template and a host element that uses it:\n\n```html\n<template *shadow=\"messagebox\">\n  <section class=\"message-box\">\n    <div class=\"message-box-title\">\n      <slot name=\"title\"></slot>\n    </div>\n\n    <div class=\"message-box-message\">\n      <slot name=\"message\"></slot>\n    </div>\n  </section>\n</template>\n\n<message-box *host=\"messagebox\">\n  <p slot=\"message\">Maintenance will be performed on May 20.</p>\n  <h2 slot=\"title\">Notice</h2>\n</message-box>\n```\n\nBehavior:\n\n- `<template *shadow=\"messagebox\">` defines a shadow template named `messagebox`.\n- `<message-box *host=\"messagebox\">` connects the host element to that template.\n- `slot=\"title\"` is displayed at `<slot name=\"title\">`.\n- `slot=\"message\"` is displayed at `<slot name=\"message\">`.\n- The host writes the message first and the title second, but the shadow template controls the display positions.\n- The slot assignment is standard browser behavior.\n\n#### Recommended syntax\n\nUse an explicit connection name:\n\n```html\n<template *shadow=\"messagebox\">\n  ...\n</template>\n\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nThe explicit form is recommended because it makes the relationship between the template and the host easy to read for humans, documentation tools, and AI assistants.\n\n#### Where to write *shadow\n\nWrite `*shadow` on a `<template>` element.\n\nRecommended:\n\n```html\n<template *shadow=\"messagebox\">...</template>\n<message-box *host=\"messagebox\">...</message-box>\n```\n\nAvoid:\n\n```html\n<message-box *shadow>...</message-box>\n```\n\n`*shadow` is not a host-side directive. Writing it on the host element can create the wrong impression that the host's children are moved into Shadow DOM, or that the host element itself becomes the shadow-side structure.\n\nThe shadow structure belongs in `<template *shadow=\"...\">`. The host connection belongs on `*host`.\n\n#### Connection name\n\nThe value of `*shadow` is the connection name used by `*host`.\n\n```html\n<template *shadow=\"profilecard\">\n  ...\n</template>\n\n<profile-card *host=\"profilecard\">\n  ...\n</profile-card>\n```\n\nIn this example, `profilecard` is the connection name.\n\nThe recommended style is to write the same explicit name on both sides:\n\n- `*shadow=\"profilecard\"` on the template.\n- `*host=\"profilecard\"` on the host.\n\n#### Use with value-less *host\n\nA value-less `*host` is allowed as a shorthand, but it is not recommended.\n\n```html\n<template *shadow=\"MessageBox\">\n  ...\n</template>\n\n<message-box *host>\n  ...\n</message-box>\n```\n\nWhen `*host` has no value, Sercrod derives a constructor-style connection name from the host element name:\n\n```text\nmessage-box\n  ->\nMessageBox\n```\n\nThen Sercrod looks for `<template *shadow=\"MessageBox\">`.\n\nThis shorthand is allowed, but explicit names are preferred because they are easier to read and harder to misunderstand.\n\n#### Shadow template content\n\nThe shadow template should normally contain:\n\n- Shadow-side structure.\n- Shadow-side `<style>`.\n- Standard `<slot>` elements.\n\n```html\n<template *shadow=\"messagebox\">\n  <style>\n    :host {\n      display: block;\n    }\n\n    .message-box {\n      border: 1px solid #ccc;\n      padding: 1rem;\n    }\n  </style>\n\n  <section class=\"message-box\">\n    <div class=\"message-box-title\">\n      <slot name=\"title\"></slot>\n    </div>\n\n    <div class=\"message-box-message\">\n      <slot name=\"message\"></slot>\n    </div>\n  </section>\n</template>\n```\n\nThe shadow template content is rendered through the normal Sercrod render pipeline before it is mounted into the shadow root.\n\nThat means ordinary Sercrod directives can be used inside `<template *shadow=\"...\">` to generate real Shadow DOM content:\n\n```html\n<template *shadow=\"previewbox\">\n  <style>\n    .preview-title { color: red; }\n  </style>\n\n  <article class=\"preview-card\">\n    <h2 class=\"preview-title\" *print=\"title\"></h2>\n    <p *print=\"body\"></p>\n  </article>\n</template>\n\n<preview-box *host=\"previewbox\" data=\"{ title: `Notice`, body: `Shadow-rendered text.` }\"></preview-box>\n```\n\nIn this pattern, the `<h2>` and `<p>` are generated inside the shadow root, not passed through a slot. Outer page CSS does not select them with ordinary selectors.\n\nUse this when the content itself should be separated from outer page CSS. Use slots when you intentionally want Light DOM children to remain page-owned content.\n\n`*shadow` remains a template declaration and `*host` remains the host-side connection. Do not use those two directives as ordinary nested behavior inside the shadow template.\n\nMisplaced bridge directives are ignored inside shadow templates. If `*shadow`, `*host`, `*shadow-host`, `n-host`, or `n-shadow-host` appears inside `<template *shadow>`, Sercrod warns when warnings are enabled and does not run that bridge behavior there. Put `*shadow` on the outer declaration template, and put `*host` or its aliases on the outer host element.\n\n#### Directive coverage inside shadow templates\n\nShadow template content uses the same directive pipeline as ordinary Light DOM content. This includes render, control-flow, form, event, action, and template directives such as `*print`, `*if`, `*switch`, `*for`, `*each`, `*input`, `@event`, `:attribute`, `*template`, `*include`, synchronous `*import`, `*fetch`, `*api`, `*post`, `*upload`, `*download`, `*websocket`, `*websocket.send`, `*stage`, `*apply`, `*restore`, `*save.file`, `*save.session`, `*save.store`, `*load.file`, `*load.session`, and `*load.store`.\n\nThe owning `<serc-rod>` still owns the data, stage, network state, WebSocket connections, save/load events, and update lifecycle. A directive inside a shadow template runs as part of that owning Sercrod host unless it is inside a nested `<serc-rod>`.\n\nShadow-root traversal is composed-aware for Sercrod internals that need it. Child `<serc-rod>` hosts, `*updated`, `*updated-propagate`, `*log`, `*man`, include depth tracking, and parent template lookup can see the rendered shadow-root content that belongs to the same Sercrod host.\n\n#### Use with Light DOM directives\n\nThe host side can be used as a normal Sercrod scope.\n\n```html\n<template *shadow=\"profilecard\">\n  <section class=\"profile-card\">\n    <header>\n      <slot name=\"name\"></slot>\n    </header>\n\n    <main>\n      <slot name=\"body\"></slot>\n    </main>\n  </section>\n</template>\n\n<profile-card *host=\"profilecard\" data=\"{ first_name: `Taro`, last_name: `Yamada`, message: `Hello.` }\">\n  <h2 slot=\"name\" *let=\"full_name = `${last_name} ${first_name}`\" *print=\"full_name\"></h2>\n  <p slot=\"body\" *print=\"message\"></p>\n</profile-card>\n```\n\nSercrod processes the Light DOM directives as usual. The processed Light DOM content is then displayed at the slot positions by the browser's standard slot behavior.\n\n#### CSS scope\n\nBecause `*shadow` uses the browser's standard Shadow DOM, CSS written inside the shadow template is scoped by standard Shadow DOM behavior.\n\nOuter page CSS does not select elements inside the shadow root with ordinary selectors. CSS written inside the shadow template does not leak out to the outer page. Slotted Light DOM children are different: they remain Light DOM nodes, so outer page CSS still selects them normally.\n\nThis is useful for preview components, editor panels, and admin screens where the outer UI CSS and the preview layout CSS should be easier to keep apart.\n\n```html\n<preview-box *host=\"previewbox\">\n  <div class=\"same-box\" slot=\"content\">\n    <h2 class=\"same-title\">Light DOM article title</h2>\n    <p class=\"same-body\">Light DOM article body.</p>\n  </div>\n</preview-box>\n\n<template *shadow=\"previewbox\">\n  <style>\n    .same-box {\n      border: 1px solid #ccc;\n      padding: 1rem;\n    }\n\n    .same-title {\n      font-size: 1.25rem;\n    }\n\n    .same-body {\n      line-height: 1.7;\n    }\n  </style>\n\n  <article class=\"same-box\">\n    <header class=\"same-title\">Shadow-side title frame</header>\n\n    <main class=\"same-body\">\n      <slot name=\"content\"></slot>\n    </main>\n  </article>\n</template>\n```\n\nIn this example, the same class names are used on both sides, but they are not the same styling surface:\n\n- `.same-box`, `.same-title`, and `.same-body` rules inside the shadow template style the elements cloned into the shadow root.\n- Outer page `.same-box`, `.same-title`, and `.same-body` rules style the Light DOM nodes, including the nodes assigned to `<slot>`.\n- Ordinary selectors do not cross from the page into the shadow root, and ordinary selectors inside the shadow root do not reach into assigned Light DOM children.\n\nSercrod does not implement CSS isolation by itself. Sercrod connects the shadow template and the host element. CSS scoping is handled by the browser as part of standard Shadow DOM behavior.\n\n#### Important note about slotted content\n\nSlotted elements are still Light DOM nodes.\n\nEven when they are displayed at a slot position inside the shadow layout, their actual nodes remain on the Light DOM side. Because of that, outer page CSS applies to slotted elements normally.\n\nCSS inside the shadow root does not style assigned Light DOM nodes through ordinary selectors. Use standard Shadow DOM mechanisms such as `::slotted(...)` for limited slot styling, or style the slotted children from the Light DOM side.\n\nIf the preview content itself must be separated from the outer page CSS, render that content inside the Shadow DOM instead of passing it as slotted Light DOM content.\n\n#### Behavior\n\nConceptually, Sercrod does the following for `*shadow`:\n\n1. Collect `<template *shadow=\"name\">` elements.\n2. Register each shadow template by name.\n3. Let `*host`, `*shadow-host`, `n-host`, or `n-shadow-host` refer to those names.\n4. When a matching host is found, attach an open Shadow DOM to the host when possible.\n5. Render the shadow template content into the host's shadow root.\n6. Leave Light DOM children where they are.\n7. Let the browser handle standard slot assignment.\n8. Process ordinary Sercrod directives in the shadow template content, and process Light DOM Sercrod directives as usual.\n\n#### Duplicate names\n\nDo not define the same `*shadow` name more than once in the same effective registry.\n\n```html\n<template *shadow=\"messagebox\">\n  A\n</template>\n\n<template *shadow=\"messagebox\">\n  B\n</template>\n```\n\nThis is not recommended.\n\nThe intended policy is:\n\n- The first definition wins.\n- Later duplicate definitions are ignored.\n- Sercrod should emit a warning when warnings are enabled.\n\n```text\n[Sercrod warn] duplicate *shadow template: messagebox\n```\n\n`*shadow` is a structure definition, not a CSS-like override rule. Later silent replacement would be harder for both humans and AI tools to follow.\n\n#### Missing host\n\nA `*shadow` template can exist before its host, after its host, or without an immediate host. The template itself only defines a reusable shadow structure.\n\nIf no host refers to the template, it simply produces no visible host output by itself.\n\n#### What *shadow does not do\n\n`*shadow` does not:\n\n- Connect itself to a host without `*host`.\n- Move Light DOM children into Shadow DOM.\n- Reimplement slot assignment.\n- Automatically add `<slot>` elements.\n- Turn Sercrod into a component renderer.\n- Implement CSS isolation by itself.\n\n#### Best practices\n\n- Use `<template>` as the `*shadow` host.\n- Use explicit connection names whenever possible.\n- Keep shadow template content focused on structure, style, and slots.\n- Put Sercrod data and binding directives on the Light DOM side.\n- Use `*host` on the host element to connect to the template.\n- Avoid duplicate `*shadow` names.\n- Remember that slotted content remains Light DOM content.\n\n#### Notes\n\n- `*shadow` defines a visible shadow template.\n- `*host` connects a host element to a named shadow template.\n- `*shadow-host`, `n-host`, and `n-shadow-host` are host-side aliases documented under `*host`.\n- Standard `<slot>` and `slot=\"...\"` behavior is handled by the browser.\n- CSS scoping is also standard Shadow DOM behavior.\n- Sercrod's role is to connect the shadow template and the host element.\n",
  "host": "### *host / *shadow-host / n-host / n-shadow-host\n\n#### Summary\n\n`*host` connects an element to a named `*shadow` template.\n\nThe host element keeps its Light DOM children. Sercrod attaches a Shadow DOM to the host when possible, renders the matching shadow template into it, and lets the browser handle standard `<slot>` assignment.\n\nSercrod does not reimplement slot behavior. It also does not move Light DOM children into the Shadow DOM.\n\n`*shadow-host`, `n-host`, and `n-shadow-host` are aliases for the same host-side connection behavior. The recommended form is `*host`.\n\n#### Basic example\n\nA host element connected to a shadow template:\n\n```html\n<template *shadow=\"messagebox\">\n  <section class=\"message-box\">\n    <div class=\"message-box-title\">\n      <slot name=\"title\"></slot>\n    </div>\n\n    <div class=\"message-box-message\">\n      <slot name=\"message\"></slot>\n    </div>\n  </section>\n</template>\n\n<message-box *host=\"messagebox\">\n  <p slot=\"message\">Maintenance will be performed on May 20.</p>\n  <h2 slot=\"title\">Notice</h2>\n</message-box>\n```\n\nBehavior:\n\n- `<template *shadow=\"messagebox\">` defines a shadow template named `messagebox`.\n- `<message-box *host=\"messagebox\">` connects the host element to that template.\n- `slot=\"title\"` is displayed at `<slot name=\"title\">`.\n- `slot=\"message\"` is displayed at `<slot name=\"message\">`.\n- The slot assignment is standard browser behavior.\n\n#### Recommended syntax\n\nUse an explicit connection name:\n\n```html\n<template *shadow=\"messagebox\">\n  ...\n</template>\n\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\n`*shadow=\"messagebox\"` defines the named shadow template.\n\n`*host=\"messagebox\"` connects the host element to that named template.\n\nExplicit names make the relationship between the template and the host easier to read, easier to document, and easier for AI tools to understand correctly.\n\n#### Aliases\n\nThe following host-side directives are equivalent:\n\n```text\n*host\n*shadow-host\nn-host\nn-shadow-host\n```\n\nRecommended form:\n\n```html\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nAlias examples:\n\n```html\n<message-box *shadow-host=\"messagebox\">\n  ...\n</message-box>\n\n<message-box n-host=\"messagebox\">\n  ...\n</message-box>\n\n<message-box n-shadow-host=\"messagebox\">\n  ...\n</message-box>\n```\n\nUse `*host` in most documentation and examples. Use `*shadow-host` only when a more descriptive form is useful. Treat `n-host` and `n-shadow-host` as namespace-style aliases.\n\n#### Where to write *host\n\nWrite `*host` on the host element, not on the template.\n\nRecommended:\n\n```html\n<template *shadow=\"messagebox\">\n  ...\n</template>\n\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nDo not use `*host` on the shadow template itself:\n\n```html\n<template *host=\"messagebox\">\n  ...\n</template>\n```\n\nThe template side defines the shadow structure with `*shadow`. The host side connects to that structure with `*host`.\n\n#### Connection name\n\nThe value of `*host` is the name of the shadow template to connect.\n\n```html\n<template *shadow=\"profilecard\">\n  ...\n</template>\n\n<profile-card *host=\"profilecard\">\n  ...\n</profile-card>\n```\n\nIn this example, `profilecard` is the connection name.\n\nSercrod looks for a matching `<template *shadow=\"profilecard\">`.\n\n#### Value-less *host shorthand\n\nA value-less `*host` is allowed as a shorthand, but it is not recommended.\n\n```html\n<template *shadow=\"MessageBox\">\n  ...\n</template>\n\n<message-box *host>\n  ...\n</message-box>\n```\n\nWhen `*host` has no value, Sercrod derives a constructor-style connection name from the host element name:\n\n```text\nmessage-box\n  ->\nMessageBox\n```\n\nThen Sercrod looks for `<template *shadow=\"MessageBox\">`.\n\nThis shorthand is allowed for convenience, but explicit names are preferred.\n\nRecommended:\n\n```html\n<template *shadow=\"messagebox\">\n  ...\n</template>\n\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nThe explicit form avoids hidden name conversion and makes the connection easier to follow.\n\n#### Use with standard slots\n\n`*host` works with the browser's standard slot behavior.\n\n```html\n<template *shadow=\"fieldbox\">\n  <div class=\"field-box\">\n    <label class=\"field-box-label\">\n      <slot name=\"label\"></slot>\n    </label>\n\n    <div class=\"field-box-control\">\n      <slot name=\"control\"></slot>\n    </div>\n\n    <div class=\"field-box-help\">\n      <slot name=\"help\"></slot>\n    </div>\n  </div>\n</template>\n\n<field-box *host=\"fieldbox\">\n  <span slot=\"label\">Email</span>\n  <input slot=\"control\" type=\"email\" name=\"email\">\n  <small slot=\"help\">Enter a valid email address.</small>\n</field-box>\n```\n\n`slot=\"label\"`, `slot=\"control\"`, and `slot=\"help\"` are assigned to the matching named slots by the browser.\n\nSercrod only connects the host element to the shadow template.\n\n#### Default slot\n\nAn element without a `slot` attribute is displayed in the default `<slot></slot>`.\n\n```html\n<template *shadow=\"messagebox\">\n  <section class=\"message-box\">\n    <header>\n      <slot name=\"title\"></slot>\n    </header>\n\n    <main>\n      <slot></slot>\n    </main>\n  </section>\n</template>\n\n<message-box *host=\"messagebox\">\n  <h2 slot=\"title\">Notice</h2>\n  <p>Maintenance will be performed on May 20.</p>\n</message-box>\n```\n\nThe `<h2>` is displayed in the named slot. The paragraph has no `slot` attribute, so it is displayed in the default slot.\n\n#### Light DOM directives\n\nThe host side can still be used as a normal Sercrod scope.\n\nYou can write `data`, `*let`, `*print`, `*input`, and other Light DOM directives on the host side.\n\n```html\n<template *shadow=\"profilecard\">\n  <section class=\"profile-card\">\n    <header>\n      <slot name=\"name\"></slot>\n    </header>\n\n    <main>\n      <slot name=\"body\"></slot>\n    </main>\n  </section>\n</template>\n\n<profile-card *host=\"profilecard\" data=\"{ first_name: `Taro`, last_name: `Yamada`, message: `Hello.` }\">\n  <h2 slot=\"name\" *let=\"full_name = `${last_name} ${first_name}`\" *print=\"full_name\"></h2>\n  <p slot=\"body\" *print=\"message\"></p>\n</profile-card>\n```\n\nSercrod processes the Light DOM directives as usual. The processed Light DOM content is then displayed at the slot positions by the browser's standard slot behavior.\n\n#### Shadow template directive coverage\n\nThe matching shadow template is also rendered through the normal directive pipeline. Render, control-flow, form, event, action, network, save/load, template, include, and nested `<serc-rod>` directives can run inside the shadow root. The owning `<serc-rod>` remains the data and lifecycle owner unless the directive is inside a nested `<serc-rod>`.\n\nInside a shadow template, `*host`, `*shadow-host`, `n-host`, and `n-shadow-host` are misplaced bridge directives. Sercrod warns when warnings are enabled and ignores the bridge connection there. Use host directives only on the outer host element that should receive the ShadowRoot.\n\n#### CSS scope\n\nWhen a host is connected to a shadow template, CSS written inside the shadow template is scoped by the browser's standard Shadow DOM behavior.\n\nOuter page CSS does not select elements inside the shadow root with ordinary selectors. CSS written inside the shadow template does not leak out to the outer page. Slotted Light DOM children are different: they remain Light DOM nodes, so outer page CSS still selects them normally.\n\n```html\n<preview-box *host=\"previewbox\">\n  <div class=\"same-box\" slot=\"content\">\n    <h2 class=\"same-title\">Light DOM article title</h2>\n    <p class=\"same-body\">Light DOM article body.</p>\n  </div>\n</preview-box>\n\n<template *shadow=\"previewbox\">\n  <style>\n    .same-box {\n      border: 1px solid #ccc;\n      padding: 1rem;\n    }\n\n    .same-title {\n      font-size: 1.25rem;\n    }\n\n    .same-body {\n      line-height: 1.7;\n    }\n  </style>\n\n  <article class=\"same-box\">\n    <header class=\"same-title\">Shadow-side title frame</header>\n\n    <main class=\"same-body\">\n      <slot name=\"content\"></slot>\n    </main>\n  </article>\n</template>\n```\n\nIn this example, the same class names are used on both sides, but they are not the same styling surface:\n\n- `.same-box`, `.same-title`, and `.same-body` rules inside the shadow template style the elements cloned into the shadow root.\n- Outer page `.same-box`, `.same-title`, and `.same-body` rules style the Light DOM nodes, including the nodes assigned to `<slot>`.\n- Ordinary selectors do not cross from the page into the shadow root, and ordinary selectors inside the shadow root do not reach into assigned Light DOM children.\n\nSercrod does not implement CSS isolation by itself. Sercrod connects the host element to the shadow template. CSS scoping is handled by the browser as part of standard Shadow DOM behavior.\n\n#### Important note about slotted content\n\nSlotted elements are still Light DOM nodes.\n\nEven when they are displayed at a slot position inside the shadow layout, their actual nodes remain on the Light DOM side. Because of that, outer page CSS applies to slotted elements normally.\n\nCSS inside the shadow root does not style assigned Light DOM nodes through ordinary selectors. Use standard Shadow DOM mechanisms such as `::slotted(...)` for limited slot styling, or style the slotted children from the Light DOM side.\n\nIf the preview content itself must be separated from the outer page CSS, render that content inside the Shadow DOM instead of passing it as slotted Light DOM content.\n\n#### Behavior\n\nConceptually, Sercrod does the following for `*host`:\n\n1. Find an element with `*host`, `*shadow-host`, `n-host`, or `n-shadow-host`.\n2. Resolve the shadow template name from the directive value.\n3. If the directive has no value, derive a constructor-style name from the host element name.\n4. Look for a matching `<template *shadow=\"name\">`.\n5. If a matching template exists and the host has no existing `shadowRoot`, attach an open Shadow DOM.\n6. Render the template content into the host's shadow root.\n7. Let the browser handle standard slot assignment.\n8. Process ordinary Sercrod directives in the shadow template content, and process Light DOM Sercrod directives as usual.\n\n#### Missing template\n\nIf the host refers to a shadow template that does not exist, Sercrod should warn when warnings are enabled.\n\n```html\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nIf there is no matching `<template *shadow=\"messagebox\">`, the intended warning is:\n\n```text\n[Sercrod warn] *host requires a matching *shadow template: messagebox\n```\n\nFor a value-less `*host`:\n\n```html\n<message-box *host>\n  ...\n</message-box>\n```\n\nSercrod derives `MessageBox`. If there is no matching `<template *shadow=\"MessageBox\">`, the intended warning is:\n\n```text\n[Sercrod warn] *host requires a matching *shadow template: MessageBox\n```\n\n#### Existing shadowRoot\n\nIf the host already has a `shadowRoot`, Sercrod should not overwrite it.\n\nThe intended warning is:\n\n```text\n[Sercrod warn] shadowRoot already exists: message-box\n```\n\nThis avoids breaking external Web Components or user scripts that already created Shadow DOM on the same element.\n\n#### Shadow mode\n\nThe initial specification supports open Shadow DOM only.\n\n```js\nattachShadow({ mode: \"open\" })\n```\n\nClosed Shadow DOM is not part of the initial `*host` behavior.\n\nThe reason is that closed Shadow DOM is harder to inspect, harder to debug, and harder for Sercrod or AI tools to verify. The initial design keeps the bridge visible and inspectable.\n\n#### HTML order\n\nThe host element may appear before or after the matching shadow template in the HTML.\n\n```html\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n\n<template *shadow=\"messagebox\">\n  ...\n</template>\n```\n\nThe intended implementation should not depend on declaration order.\n\nA safe implementation can first collect `<template *shadow=\"...\">` declarations, and then connect `*host` elements in a second pass.\n\n#### Relation to customElements.define()\n\n`*host` is not a replacement for `customElements.define()`.\n\nThe `*shadow` / `*host` bridge connects a visible shadow template to a host element. It does not need to turn Sercrod into a custom element framework.\n\nSercrod should be careful not to redefine custom elements that user scripts or external libraries may define. If a custom element name is already registered, Sercrod should not redefine it.\n\nThe minimum bridge behavior is to connect the host to the shadow template by using `attachShadow({ mode: \"open\" })` and rendering the template content into the shadow root.\n\n#### What *host does not do\n\n`*host` does not:\n\n- Define the shadow template by itself.\n- Reimplement slot assignment.\n- Move Light DOM children into Shadow DOM.\n- Automatically create missing `<slot>` elements.\n- Overwrite an existing `shadowRoot`.\n- Implement CSS isolation by itself.\n- Replace `customElements.define()`.\n\n#### Best practices\n\n- Use explicit connection names whenever possible.\n- Write `*shadow` on a `<template>` element.\n- Write `*host` on the host element.\n- Use `*host` as the recommended form, and treat `*shadow-host`, `n-host`, and `n-shadow-host` as aliases.\n- Use standard `<slot>` and `slot=\"...\"` for content placement.\n- Use ordinary Sercrod directives inside the shadow template when you want the generated content to live inside the shadow root.\n- Remember that slotted content remains Light DOM content.\n\n#### Notes\n\n- `*host` connects an element to a named `*shadow` template.\n- `*shadow-host`, `n-host`, and `n-shadow-host` are aliases for the host-side connection.\n- A value-less `*host` is allowed, but explicit names are recommended.\n- Standard slot assignment is handled by the browser.\n- CSS scoping is standard Shadow DOM behavior.\n- Sercrod's role is to connect the host element and the shadow template.\n",
  "shadow-host": "### *shadow-host\n\n#### Summary\n\n`*shadow-host` is a descriptive alias for `*host`.\n\nIt connects a host element to a named `*shadow` template. The behavior is the same as `*host`.\n\nRecommended form:\n\n```html\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nAlias form:\n\n```html\n<message-box *shadow-host=\"messagebox\">\n  ...\n</message-box>\n```\n\nUse `*host` in most documentation and examples. Use `*shadow-host` only when the more descriptive name helps make the Shadow DOM connection clearer.\n\nSee the `host` manual entry for the full behavior.\n",
  "n-host": "### n-host\n\n#### Summary\n\n`n-host` is a namespace-style alias for `*host`.\n\nIt connects a host element to a named `*shadow` template. The behavior is the same as `*host`.\n\nRecommended form:\n\n```html\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nAlias form:\n\n```html\n<message-box n-host=\"messagebox\">\n  ...\n</message-box>\n```\n\nUse `*host` in most documentation and examples. Use `n-host` only when the namespace-style form is preferred in a project.\n\nSee the `host` manual entry for the full behavior.\n",
  "n-shadow-host": "### n-shadow-host\n\n#### Summary\n\n`n-shadow-host` is a namespace-style alias for `*host` and `*shadow-host`.\n\nIt connects a host element to a named `*shadow` template. The behavior is the same as `*host`.\n\nRecommended form:\n\n```html\n<message-box *host=\"messagebox\">\n  ...\n</message-box>\n```\n\nAlias form:\n\n```html\n<message-box n-shadow-host=\"messagebox\">\n  ...\n</message-box>\n```\n\nUse `*host` in most documentation and examples. Use `n-shadow-host` only when the namespace-style descriptive form is preferred in a project.\n\nSee the `host` manual entry for the full behavior.\n",
  "textContent": "### *textContent\n\n#### Summary\n\n`*textContent` sets the DOM `textContent` property of an element from an expression.\nIt is a text-oriented directive, similar in spirit to `*print`, and is paired with the alias `n-textContent`.\n\nUse `*textContent` when you want to explicitly bind an expression to an element's `textContent`, ignoring any static child markup.\n\n\n#### Basic example\n\nSimple binding to a message:\n\n```html\n<serc-rod id=\"app\" data='{\"message\":\"Hello Sercrod\"}'>\n  <p *textContent=\"message\"></p>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<p>` element is rendered.\n- Sercrod evaluates `message` in the current scope.\n- The result is converted to text via the `text` filter and assigned to `p.textContent`.\n- Any static children of `<p>` in the template are not rendered; `textContent` completely replaces them.\n\n\n#### Behavior\n\n- `*textContent` is a one-way text binding from data to the DOM.\n- It assigns the stringified result of an expression to the element's `textContent` property.\n- The directive is evaluated once per render of the element (and again on re-renders triggered by data updates).\n- The alias `n-textContent` behaves the same as `*textContent`; only the attribute name differs.\n\nShort manual entry (built into Sercrod):\n\n- `textContent`: set the DOM `textContent` property from an expression.\n- Example: `<div *textContent=\"message\"></div>`\n\n\n#### Expression evaluation\n\nWhen Sercrod encounters an element with `*textContent` or `n-textContent`:\n\n- It chooses a \"source attribute\" in this priority:\n\n  1. `*print`\n  2. `n-print`\n  3. `*textContent`\n  4. `n-textContent`\n\n- Only the first attribute in this list is actually used.\n  - If `*print` or `n-print` is present, the `*textContent` / `n-textContent` expression is ignored.\n  - This makes mixing these attributes on the same element pointless; you should choose one.\n\n- Once the source attribute is chosen:\n\n  - Sercrod reads its value as an expression string.\n  - If a bundler-specific `normalizeTpl` hook is available, it normalizes the expression string.\n  - It evaluates the expression with `eval_expr(expr, scope, { el: node, mode: \"textContent\" })` for `*textContent`, or with a corresponding mode for `n-textContent`.\n  - The raw evaluation result `v` is mapped to a \"raw text\" value:\n\n    - If `v` is `null` or `false`, the raw text becomes the empty string `\"\"`.\n    - For any other value, the raw text is `v` as-is and will be converted to a string later.\n\n  - The raw text is passed through the `text` filter:\n\n    - `Sercrod._filters.text(raw, { el, expr, scope })`\n    - By default, the filter is defined as `String(raw ?? \"\")`.\n    - Projects can override this filter to change how textual values are produced (for example, to clamp length or apply additional formatting).\n\n  - The final string is assigned to `el.textContent`.\n\nError handling:\n\n- If expression evaluation or filtering throws, Sercrod falls back to:\n\n  - `el.textContent = \"\"`.\n\nCleanup:\n\n- If `this.constructor._config.cleanup.directives` is enabled, Sercrod removes the directive attributes from the rendered element:\n\n  - `*print`\n  - `n-print`\n  - `*textContent`\n  - `n-textContent`\n\n- This keeps the output DOM clean, leaving only the resulting text content.\n\n\n#### Evaluation timing\n\n`*textContent` is not a structural directive; it runs after the structural layer for an element has succeeded.\n\nRough evaluation order for a given element:\n\n1. Structural checks on the template node (host):\n\n   - `*if` / `n-if`, `*elseif` / `*else`, `*switch` / `n-switch`, `*each` / `n-each`, `*for` / `n-for`, and similar control directives run at higher stages.\n   - If a structural directive decides to drop or replace the element, `*textContent` does not run.\n\n2. Sercrod host checks and other element-level decisions.\n\n3. Rendering of a concrete DOM element (`el`) for this template node.\n\n4. Text directives:\n\n   - If the element has `*print`, `n-print`, `*textContent`, or `n-textContent`, the combined branch for \"print/textContent\" is executed.\n   - That branch sets `el.textContent` and appends `el` to the parent.\n   - After this branch returns, Sercrod does not recurse into children and does not process other content directives for this element.\n\n5. Fallback text handling:\n\n   - If there is no text directive, but the element has exactly one static text child, Sercrod may:\n     - Copy the text verbatim, or\n     - Expand `%expr%` placeholders via `_expand_text`, then assign the result to `textContent`.\n\n6. Other content directives:\n\n   - Only when no text directive and no simple static-text optimization applies, Sercrod proceeds to check `*compose` / `n-compose`, `*innerHTML` / `n-innerHTML`, and so on.\n\nImportant consequence:\n\n- As soon as `*textContent` (or `*print`) is present, that directive \"wins\" the content for the element:\n  - Child nodes are not rendered.\n  - `%expr%` inline expansions in static text are not used.\n  - `*compose` / `*innerHTML` are never reached for that element.\n\n\n#### Execution model\n\nThe execution model for `*textContent` on one element can be summarized as:\n\n1. Sercrod creates a new DOM element `el` corresponding to the template node.\n\n2. It detects whether the element has any text directive:\n\n   - `*print`, `n-print`, `*textContent`, `n-textContent`.\n\n3. If so:\n\n   - It chooses the source attribute (with `*print` > `n-print` > `*textContent` > `n-textContent` priority).\n   - It evaluates the expression and passes the result through the `text` filter.\n   - It sets `el.textContent` to the filtered string.\n   - It optionally removes the directive attributes from `el` depending on the cleanup configuration.\n   - It appends `el` to the parent.\n   - It returns early; no children are rendered and no other content directives are considered.\n\n4. If not:\n\n   - The renderer falls back to static text or child-node rendering paths.\n\nCombined with reactivity:\n\n- When data changes and triggers an update, the containing Sercrod host re-renders, repeating the same process.\n- `*textContent` expressions are re-evaluated in the new scope, and the new `textContent` is applied.\n\n\n#### Variable creation and scope layering\n\n`*textContent` does not create any new variables in the scope.\n\nInside the expression:\n\n- You can use all normal scope variables:\n\n  - Fields from the host data.\n  - Loop variables from surrounding `*for` / `*each`.\n  - Temporary variables from `*let`.\n  - Special helpers injected by Sercrod such as `$data`, `$root`, and `$parent`.\n\nScope behavior:\n\n- The expression for `*textContent` is evaluated in the \"effective scope\" for this element.\n- The directive itself does not alter dynamic scope; it only reads from it.\n- Assigning to variables inside the expression (for example, `x = x + 1`) is not the purpose of `*textContent` and should be avoided; use `*let`, `*global`, or explicit methods instead when you need side effects.\n\n\n#### Parent access\n\n`*textContent` has no special concept of \"parent data\" beyond what Sercrod already supplies:\n\n- `$parent` gives the nearest ancestor Sercrod host's data.\n- `$root` gives the outermost Sercrod host's data.\n- Normal lexical names refer to the current element's scope, including any loop or `*let` variables.\n\nTypical usage:\n\n```html\n<serc-rod id=\"todo\" data='{\"items\":[{\"title\":\"Buy milk\"}]}'>\n  <ul *each=\"item of items\">\n    <li *textContent=\"item.title\"></li>\n  </ul>\n</serc-rod>\n```\n\nHere, `item` is introduced by `*each`, and `*textContent` simply reads it.\n\n\n#### Use with conditionals and loops\n\n`*textContent` works well inside structural directives:\n\n- With `*if`:\n\n  ```html\n  <p *if=\"user\" *textContent=\"user.name\"></p>\n  ```\n\n  - `*if` runs first; if `user` is falsy, the `<p>` is not rendered and `*textContent` never runs.\n  - If `user` is truthy, the `<p>` is created and `*textContent` sets `textContent`.\n\n- With `*each`:\n\n  ```html\n  <ul *each=\"item of items\">\n    <li *textContent=\"item.label\"></li>\n  </ul>\n  ```\n\n  - `*each` decides the number of iterations.\n  - For each iteration, `*textContent` runs on the `<li>` clone in that iteration's scope.\n\n- With `*for`:\n\n  ```html\n  <ul>\n    <li *for=\"item of items\" *textContent=\"item.label\"></li>\n  </ul>\n  ```\n\n  - `*for` repeats the `<li>` element itself.\n  - `*textContent` sets the `textContent` for each repeated `<li>`.\n\nIn all cases:\n\n- Structural directives determine whether and how many elements exist.\n- `*textContent` controls what text each existing element displays.\n\n\n#### Interaction with other content directives\n\n`*textContent` participates in the same \"content choice\" layer as `*print`, and it precedes other content directives:\n\n- `*print` vs `*textContent`:\n\n  - Both share the same implementation branch.\n  - If both are present on the same element, `*print` (or `n-print`) is used and `*textContent` (or `n-textContent`) is ignored.\n  - This is a defined implementation detail but not a useful pattern; in practice, you should choose one directive.\n  - If you want to emphasize DOM property semantics, use `*textContent`; if you prefer \"printing\" semantics, use `*print`.\n\n- `*textContent` vs `*innerHTML` / `*compose`:\n\n  - The `*print` / `*textContent` branch runs before the `*compose` / `*innerHTML` branch.\n  - If an element has both `*textContent` and `*innerHTML` (or `*compose`), `*textContent` wins:\n    - The element's `textContent` is set from the expression.\n    - The branch for `*innerHTML` / `*compose` is never reached.\n  - Combining these directives on the same element therefore has no useful effect; you should choose one.\n\n- Static `%expr%` expansion:\n\n  - When no text directive is present, Sercrod can expand inline `%expr%` placeholders in a single text node and assign the result to `textContent`.\n  - As soon as `*textContent` is present, this static expansion is skipped, because the directive takes full control of `textContent`.\n\nRecommendation:\n\n- Treat `*textContent` as the unique controller of `textContent` for that element.\n- Avoid putting multiple content-directing attributes (`*print`, `*textContent`, `*innerHTML`, `*compose`) on the same element, because only one branch will be effective.\n\n\n#### Best practices\n\n- Keep expressions side-effect free:\n\n  - `*textContent` is meant for pure formatting; side effects (mutating data) inside the expression make templates harder to reason about.\n\n- Pre-format complex text outside the template:\n\n  - If you need heavy formatting (e.g. date/time, unit conversion, localization), consider computing those values in data or via helper methods instead of embedding long expressions.\n\n- Use filters for cross-cutting concerns:\n\n  - If you need to sanitize or normalize text globally, override `Sercrod._filters.text` in your application.\n  - This way, all `*textContent` and `*print` bindings automatically receive the same treatment.\n\n- Do not rely on attribute priority as a \"feature\":\n\n  - While the runtime clearly prioritizes `*print` over `*textContent`, this is an implementation detail mostly meant to keep behavior predictable when templates accidentally mix them.\n  - In real templates, choose exactly one directive per element to express intent.\n\n- Use `*textContent` when you want a clear \"property binding\" feel:\n\n  - For readers familiar with the DOM, `*textContent` makes it explicit that the element's `textContent` property is controlled by the expression.\n\n\n#### Additional examples\n\nUsing `*textContent` with derived values:\n\n```html\n<serc-rod id=\"price\" data='{\"price\": 1200, \"currency\":\"JPY\"}'>\n  <span *textContent=\"price + ' ' + currency\"></span>\n</serc-rod>\n```\n\nInside a list with conditional prefix:\n\n```html\n<serc-rod id=\"messages\" data='{\n  \"messages\": [\n    { \"important\": true,  \"text\": \"System update required\" },\n    { \"important\": false, \"text\": \"Daily backup completed\" }\n  ]\n}'>\n  <ul *each=\"msg of messages\">\n    <li *textContent=\"(msg.important ? '[!] ' : '') + msg.text\"></li>\n  </ul>\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*textContent` and `n-textContent` are aliases; they share the same implementation and differ only in attribute name.\n- The directive is implemented strictly in terms of `textContent`:\n  - No HTML parsing is performed.\n  - All text is treated as plain text and will be escaped by the browser when rendered.\n- Both `*textContent` and `*print` use the global `text` filter for final string conversion.\n- When multiple content directives are present on a single element, the renderer chooses one branch (with `*print` > `n-print` > `*textContent` > `n-textContent` priority) and ignores the rest; this is defined behavior but discouraged in template design.\n- `*textContent` does not interact with network features (`*post`, `*fetch`, `*websocket`, and others); it is purely about local text rendering for a single element.\n",
  "unwrap": "### *unwrap\n\n#### Summary\n\n`*unwrap` removes the Sercrod host element itself from the DOM after rendering and leaves only its children.\nIt is a host-level directive: it is evaluated on `<serc-rod>` instances, not on arbitrary elements.\nThe attribute is treated as boolean; its value is ignored, and only its presence matters.\n\nThis directive is useful when you want to use Sercrod as a one-shot renderer or build tool but do not want the `<serc-rod>` tag to remain in the final HTML.\n\n\n#### Basic example\n\nSimple one-shot render of a card:\n\n```html\n<serc-rod id=\"card\" *unwrap data='{\"title\":\"Hello\",\"body\":\"World\"}'>\n  <article class=\"card\">\n    <h1 *print=\"title\"></h1>\n    <p *print=\"body\"></p>\n  </article>\n</serc-rod>\n```\n\nAfter the initial render and finalization, the DOM becomes effectively:\n\n```html\n<article class=\"card\">\n  <h1>Hello</h1>\n  <p>World</p>\n</article>\n```\n\n- The `<serc-rod>` wrapper is removed.\n- The `<article>` subtree is moved up one level and kept as plain static HTML.\n\n\n#### Behavior\n\n- `*unwrap` is only checked on Sercrod host elements (the custom element class).\n- If a host has the `*unwrap` attribute, Sercrod replaces that host with its children using a `DocumentFragment`.\n- All children and their descendants, including event listeners and bindings that were already attached, are preserved as they are moved.\n- The Sercrod host instance is removed from the DOM tree; no new updates are applied to that host afterward.\n- The attribute is treated as a simple presence flag:\n  - `*unwrap`, `*unwrap=\"\"`, and `*unwrap=\"true\"` behave the same.\n  - There is no expression evaluation or conditional logic on `*unwrap` itself.\n\n\n#### Evaluation timing\n\n`*unwrap` is evaluated in the host's `_finalize()` phase:\n\n- `_finalize()` is invoked at the end of the update cycle for a Sercrod host.\n- This happens:\n  - After the main template has been rendered.\n  - After child updates and `*updated` hooks have been processed.\n  - Even in the `*lazy` case where only children are updated, `_finalize()` still runs.\n\nConsequences:\n\n- As soon as the first update cycle that sees `*unwrap` completes, the host element unwraps itself and disappears from the DOM.\n- Because the host is removed, subsequent reactive updates via that host are not applied; the content that remains is regular static DOM.\n\n\n#### Execution model\n\nThe internal execution model for `*unwrap` on a `<serc-rod>` host is:\n\n1. At the end of an update, the host calls `_finalize()`.\n2. `_finalize()` calls `_unwrap()` on the host.\n3. `_unwrap()` performs:\n\n   - Check: if the host does not have the `*unwrap` attribute, stop and do nothing.\n   - Find the parent node of the host; if there is no parent, stop.\n   - Create an empty `DocumentFragment`.\n   - Move all existing child nodes from the host into the fragment, in order.\n   - Replace the host element in its parent with that fragment.\n\n4. After this replacement:\n   - The former children now live directly under the parent.\n   - The `<serc-rod>` host element is no longer in the DOM.\n\nNo additional scopes or variables are created by `*unwrap` during this process; it only affects DOM structure after rendering.\n\n\n#### Variable creation and scope layering\n\n- `*unwrap` does not create any new variables.\n- It does not modify the data scope (`this._data`, `$data`, `$root`, `$parent`) for expressions.\n- All data evaluation and binding happen before `_finalize()` runs.\n- Once unwrapped, the DOM subtree no longer has a Sercrod host associated with it, so further scope-based updates from that host are not applied.\n\nIn other words, `*unwrap` is purely structural and post-render; it does not participate in expression evaluation.\n\n\n#### Parent access\n\n- `*unwrap` does not change how `$root` or `$parent` are resolved during rendering.\n- While the host exists, expressions can still use `$root` and `$parent` as usual.\n- After unwrapping, the host is removed, so future expression evaluation via that host does not occur.\n\n\n#### Use with conditionals and loops\n\n`*unwrap` does not itself provide conditional behavior or looping; it runs unconditionally whenever the attribute is present and the host completes an update.\n\nTypical combinations:\n\n- Conditional rendering outside the host:\n\n  - If you want to conditionally include an unwrapped block, use conditionals around the `<serc-rod>` element instead of trying to make `*unwrap` conditional.\n\n  ```html\n  <div *if=\"showCard\">\n    <serc-rod *unwrap data=\"cardData\">\n      <article class=\"card\">\n        <h1 *print=\"title\"></h1>\n      </article>\n    </serc-rod>\n  </div>\n  ```\n\n- Loops outside the host:\n\n  - If you need multiple unwrapped blocks, loop outside Sercrod or generate multiple `<serc-rod *unwrap>` hosts in your surrounding template or build pipeline.\n  - `*unwrap` itself does not loop or replicate content; it only removes one host wrapper.\n\n\n#### Best practices\n\n- Use `*unwrap` for one-shot or build-time rendering:\n\n  - It is best suited for scenarios where Sercrod is used as a preprocessor:\n    - SSG or SSR pipelines where `<serc-rod>` is used only during generation.\n    - Inline rendering in a build step (for example, via Playwright) to produce final HTML.\n\n- Do not expect live updates after unwrapping:\n\n  - Once unwrapped, the host is removed from the DOM and does not drive further reactive changes.\n  - If you need live updates from Sercrod on the client side, do not use `*unwrap` on that host.\n\n- Keep `*unwrap` on the host element:\n\n  - `*unwrap` is implemented as a method of the Sercrod class and is only checked on Sercrod hosts.\n  - Placing `*unwrap` on non-Sercrod elements has no effect in the current implementation.\n\n- Treat `*unwrap` as a structural option:\n\n  - It does not change how directives like `*if`, `*for`, `*each`, `*include`, or `*import` behave inside the host.\n  - It only changes whether the `<serc-rod>` wrapper itself survives after the update cycle.\n\n\n#### Additional examples\n\nUnwrapping a layout section:\n\n```html\n<serc-rod id=\"hero\" *unwrap data='{\n  \"title\": \"Sercrod\",\n  \"tagline\": \"HTML-first data binding\"\n}'>\n  <section class=\"hero\">\n    <h1 *print=\"title\"></h1>\n    <p *print=\"tagline\"></p>\n  </section>\n</serc-rod>\n```\n\nThe final HTML keeps only the `<section>` and its contents, without `<serc-rod>`.\n\nCombining with partials inside:\n\n```html\n<serc-rod id=\"page\" *unwrap data='{\"user\":{\"name\":\"Alice\"}}'>\n  <main>\n    <header *include=\"'site-header'\"></header>\n    <section *include=\"'user-profile'\"></section>\n  </main>\n</serc-rod>\n```\n\n- Sercrod still processes templates and includes inside the host.\n- After rendering, only `<main>` (with all its resolved content) remains; the `<serc-rod>` wrapper is removed.\n\n\n#### Notes\n\n- `*unwrap` is a host-level directive; there is no `n-unwrap` alias in the current implementation.\n- The directive is checked by testing for the presence of the `*unwrap` attribute; the attribute value is not interpreted as an expression.\n- Unwrapping is performed via `DocumentFragment` replacement, so the relative order of siblings around the host is preserved.\n- `*unwrap` does not interact with or override any per-element structural directives on child nodes; it only removes the outer Sercrod host after all child processing is done.\n- Because it removes the host element entirely, `*unwrap` should be used only when you intentionally do not need that host to remain active in the DOM after the first render.\n",
  "updated-propagate": "### *updated-propagate\n\n#### Summary\n\n`*updated-propagate` is the old compatible spelling of `*update`.\n\nPrefer [`*update`](update.md) in new templates.\n\n`*updated-propagate` requests a forced update on a target Sercrod host after the current host or element has been updated.\nIt does not evaluate a JavaScript expression.\nInstead, it reads a small string syntax and calls `update(true, caller)` on a single target Sercrod host.\n\nAliases:\n\n- `*updated-propagate`\n- `n-updated-propagate`\n\nEquivalent preferred aliases:\n\n- `*update`\n- `n-update`\n\nKey points:\n\n- Can be placed on a `<serc-rod>` host or on ordinary elements inside a Sercrod host.\n- Interprets its value as a literal routing spec (selector, `root`, or numeric depth).\n- Does not create variables or change the data scope.\n- Runs after `*updated` on the same element, if `*updated` is present.\n\n\n#### Basic example\n\nPropagate from a nested Sercrod to its outer root:\n\n```html\n<serc-rod id=\"root\" data='{\"message\":\"Hello\"}'>\n  <h1 *print=\"message\"></h1>\n\n  <serc-rod id=\"child\"\n           data='{\"message\":\"Child\"}'\n           *updated=\"onChildUpdated\"\n           *update=\"root\">\n    <p *print=\"message\"></p>\n  </serc-rod>\n</serc-rod>\n```\n\nBehavior:\n\n- When `child` finishes its internal update, `*updated=\"onChildUpdated\"` is called on the child.\n- Then `*update=\"root\"` forces a full update on the top-level `root` Sercrod host.\n- The `root` host runs its own `*updated` hooks (if any) as part of that update.\n\n\n#### Behavior\n\nHigh level behavior:\n\n- On a Sercrod host (`<serc-rod>`):\n\n  - After the host has finished rendering and its own `*updated` hooks have been processed, Sercrod checks `*update` / `n-update` / `*updated-propagate` / `n-updated-propagate` on that host.\n  - If present, it interprets the attribute value as a routing spec and finds exactly one target Sercrod host (if possible).\n  - It then calls `target.update(true, callerHost)`.\n\n- On ordinary elements inside a Sercrod host:\n\n  - After the host’s update completes, the host walks its subtree and looks for `*update` / `n-update` / `*updated-propagate` / `n-updated-propagate` on normal elements (not on child `<serc-rod>`).\n  - For each such element, Sercrod interprets the value as a routing spec and finds a target Sercrod host (if any).\n  - It then calls `target.update(true, host)`.\n\nImportant:\n\n- The attribute’s value is always treated as a literal string.\n- There is no expression evaluation, no variable interpolation, and no access to the data scope from this directive itself.\n- Only a single target host is ever updated per attribute evaluation.\n\n\n#### Target specification syntax\n\nThe value of `*update`, `n-update`, `*updated-propagate`, or `n-updated-propagate` is called the target spec.\nThe runtime interprets it in the following order:\n\n1. Empty or omitted\n\n   - If the attribute is present but has no value (for example `<div *update></div>`) or an empty string, Sercrod treats it as `\"1\"`.\n\n   - `\"1\"` means “the first Sercrod host visible from the attribute location”.\n   - If the attribute is on a Sercrod host, that host itself is `\"1\"`.\n   - If the attribute is on an ordinary element, the nearest containing Sercrod host is `\"1\"`.\n\n2. Parenthesized selector: `\"(selector)\"`\n\n   - If the value matches `\"(...)\":`\n\n     - Sercrod strips the outer parentheses and treats the inside as a CSS selector.\n     - It then calls `closest(selector)` from the element where the directive lives.\n     - If the result is a Sercrod host, that host receives a forced update.\n\n   Examples:\n\n   ```html\n   <!-- On a Sercrod host: propagate to the nearest ancestor matching .layout-root -->\n   <serc-rod *update=\"(.layout-root)\">\n     ...\n   </serc-rod>\n\n   <!-- On a child element: propagate to the closest .card host -->\n   <div class=\"card-body\" *update=\"(.card)\">\n     ...\n   </div>\n   ```\n\n3. Keyword `\"root\"`\n\n   - Special keyword that refers to the top-level Sercrod host around the current element.\n\n   - Sercrod walks upward from the attribute location and chooses the outermost Sercrod host.\n   - That top-level host receives `update(true, caller)`.\n\n   This is the recommended form when you want to ensure that “the top Sercrod container for this UI” refreshes, regardless of nesting depth.\n\n4. Numeric depth: `\"N\"`\n\n   - If the value consists only of digits, Sercrod parses it as an integer depth.\n\n   - On a Sercrod host:\n\n     - The number counts Sercrod hosts visible from the attribute location.\n     - Sercrod starts from the attribute element itself and climbs upward.\n     - Each time it encounters a Sercrod host, it decrements the counter.\n     - When the counter reaches zero on a Sercrod host, that host receives `update(true, callerHost)`.\n\n     Examples:\n\n     - On a Sercrod host, `\"1\"` targets that host itself.\n     - On a normal element, `\"1\"` targets the nearest containing Sercrod host.\n     - `\"2\"` targets the next outer Sercrod host, and so on.\n\n   Normal elements such as `div`, `p`, `button`, and `span` are not counted.\n\n   Example:\n\n   ```html\n   <serc-rod id=\"outer\">\n     <div>\n       <serc-rod id=\"inner\">\n         <p>\n           <button *update=\"1\">Update inner</button>\n           <button *update=\"2\">Update outer</button>\n         </p>\n       </serc-rod>\n     </div>\n   </serc-rod>\n   ```\n\n   In this shape:\n\n   - `*update=\"1\"` on the button updates `#inner`.\n   - `*update=\"2\"` on the button updates `#outer`.\n\n   On a Sercrod host itself:\n\n   ```html\n   <serc-rod id=\"inner\" *update=\"1\">\n   ```\n\n   - `*update=\"1\"` targets `#inner` itself and is usually unnecessary.\n   - `*update=\"2\"` targets the parent Sercrod host.\n\n5. Fallback: bare selector string\n\n   - If the spec does not match any of the above patterns, Sercrod treats it as a CSS selector and calls `closest(spec)` from the element.\n   - If the closest match is a Sercrod host, that host receives `update(true, caller)`.\n\n   Example:\n\n   ```html\n   <!-- Propagate to the nearest Sercrod matching .panel-root -->\n   <div *update=\".panel-root\"></div>\n   ```\n\n\n#### Evaluation timing\n\n`*update` is evaluated after the main update work of the host:\n\n- For a Sercrod host:\n\n  1. The host runs its internal update pipeline (bindings, directives, DOM changes).\n  2. The host executes its own `*updated` / `n-updated` handler(s), if present.\n  3. The host interprets `*update` / `n-update` on itself and performs any propagation.\n  4. The host then scans its normal child elements for `*updated` and `*update` and executes those callbacks.\n\n- For ordinary elements:\n\n  - After step 1?3 above, the host calls `_absorb_child_updated`, which walks the DOM under the host, skipping child `<serc-rod>` instances.\n  - For each normal element:\n    - If `*updated` / `n-updated` is present, the host executes that handler first.\n    - Regardless of `*updated`, if `*update` / `n-update` is present, it is then interpreted and the appropriate target host is updated.\n\nEffects on the target host:\n\n- The target host receives a forced update via `update(true, caller)`.\n- From the target’s perspective, this looks like a normal explicit update:\n  - It runs its own update pipeline.\n  - It then runs its own `*updated` / `n-updated` hooks.\n  - Its own `*update` may in turn propagate further.\n\nLoop prevention:\n\n- Sercrod’s `update` method ignores calls if the host is already updating.\n- This prevents infinite loops where two hosts trigger each other’s propagation back and forth within the same call stack.\n\n\n#### Execution model\n\nConceptually, Sercrod performs the following steps whenever it evaluates `*update`:\n\n1. Read the raw attribute:\n\n   - On a host: read `*update`, `n-update`, or the compatibility aliases.\n   - On a normal element: read `*update`, `n-update`, or the compatibility aliases.\n\n2. Normalize the spec:\n\n   - If the value is null, there is no propagation.\n   - If the value is the empty string, it is treated as `\"1\"`.\n   - The string is trimmed of surrounding whitespace.\n\n3. Interpret the spec:\n\n   - If it starts and ends with parentheses, treat it as a selector inside `\"(...)\"`.\n   - Else if it is exactly `\"root\"`, resolve the appropriate root Sercrod host.\n   - Else if it is all digits, treat it as a numeric depth (with the extra adjustment for normal elements).\n   - Else treat it as a bare CSS selector.\n\n4. Resolve the target host:\n\n   - Use `closest(selector)` on the appropriate starting element.\n   - Or walk upward through parents, counting Sercrod hosts.\n   - Or use the topmost Sercrod root for `\"root\"`.\n\n5. Call update:\n\n   - If a Sercrod host is found, call `target.update(true, caller)` where:\n     - `caller` is the current host when called from a host-level `*update`.\n     - `caller` is the scanning host when called from a normal child element.\n\nNo errors are thrown to user code:\n\n- If resolution fails (no matching Sercrod host, invalid selector, or runtime errors), Sercrod logs a warning (if warn-level logging is enabled) and continues.\n\n\n#### Variable creation and scope layering\n\n`*update` does not introduce any new variables:\n\n- It does not alter data objects.\n- It does not create special variables like `$event` or `$host` in the scope of the element where it appears.\n- It does not affect `data`, `$root`, or `$parent`.\n\nAny data or event context used by the target host’s `*updated` handlers is created by that host itself, according to the rules of `*updated`, not by `*update`.\n\n\n#### Parent access\n\n`*update` is a routing directive, not a data access directive:\n\n- It does not provide direct access to parent data.\n- It only determines which Sercrod host should be updated after the current host or element finishes its update.\n- If you need parent data, use the usual scope rules (`$parent`, `$root`, or explicit data structures) in the `*updated` handler or in regular expressions.\n\nThe main “parent” concept here is the structural parent host in the DOM tree, as used by numeric depth and the `root` keyword.\n\n\n#### Use with *updated and events\n\n`*update` is designed to complement `*updated`:\n\n- On a Sercrod host:\n\n  - Typical pattern:\n\n    ```html\n    <serc-rod\n      data='{\"count\":0}'\n      *updated=\"onChildUpdated\"\n      *update=\"root\">\n      ...\n    </serc-rod>\n    ```\n\n  - `*updated` lets the host react locally to its own update (for example, scanning markers inside itself).\n  - `*update` then escalates the update to a parent or root host.\n\n- On normal elements inside a host:\n\n  - Typical pattern:\n\n    ```html\n    <button\n      *updated=\"onButtonUpdated\"\n      *update=\"root\">\n      Save\n    </button>\n    ```\n\n  - The host first runs `onButtonUpdated` in the host’s data scope.\n  - Then `*update=\"root\"` forces the root host to update.\n\nEvent objects:\n\n- For `*updated` on Sercrod hosts, Sercrod synthesizes an event-like object and injects it into the evaluation as `$event`.\n- `*update` does not forward that event object to other hosts.\n- When a target host is updated via `update(true, caller)`, it receives its own fresh `$event` according to the `*updated` rules, not the original one.\n\n\n#### Use with conditionals and loops\n\n`*update` is independent of structural directives such as `*if`, `*for`, and `*each`:\n\n- On a host:\n\n  - Whether or not the host participated in conditions or loops elsewhere, `*update` runs after the host’s update finishes.\n\n- On normal elements inside loops:\n\n  - If elements with `*update` are created by `*for` or `*each`, they behave like any other elements.\n  - After each host update, Sercrod walks the actual DOM tree and evaluates `*update` on whichever instances currently exist.\n  - This means you can safely attach `*update` to repeated rows or items.\n\nThere are no special rules tying `*update` to conditionals or loops beyond the normal update timing.\n\n\n#### Best practices\n\n- Prefer explicit specs:\n\n  - Use `\"root\"` when you want to refresh the top-level Sercrod container.\n  - Use `\"(selector)\"` when you want to target a particular Sercrod host by CSS.\n\n- Numeric depths:\n\n  - Count only Sercrod hosts.\n  - Normal elements are skipped.\n  - `\"1\"` is the first Sercrod host visible from the attribute location.\n  - If the attribute is on a Sercrod host, `\"1\"` targets that host itself.\n  - If the attribute is on a normal element, `\"1\"` targets the nearest containing Sercrod host.\n  - Use `\"2\"` from a normal element inside a child Sercrod when you want the parent Sercrod.\n\n- Keep specs static:\n\n  - Specs are not expressions; they are always taken literally.\n  - Avoid writing values that depend on runtime data, such as `*update=\"state.target\"`, because they will be treated as CSS selectors and likely fail.\n\n- Avoid unnecessary propagation:\n\n  - Overusing `*update` can cause a lot of forced updates.\n  - Prefer local `*updated` handlers where possible and propagate only when the parent or root really needs to recompute its view.\n\n- Use with `*updated`, not instead of it:\n\n  - Use `*updated` for work that belongs to the current host or element.\n  - Use `*update` as a routing layer to tell other hosts that they should refresh.\n\n\n#### Examples\n\nPropagate to the nearest parent Sercrod host:\n\n```html\n<serc-rod id=\"parent\" data='{\"value\": 0}'>\n  <serc-rod id=\"child\" *update=\"2\">\n    <p>Child content</p>\n  </serc-rod>\n</serc-rod>\n```\n\n- After `child` updates, `\"2\"` points to the nearest Sercrod ancestor (here, `parent`), which receives `update(true, child)`.\n\nPropagate from an inner element to the root:\n\n```html\n<serc-rod id=\"root\" data='{\"saved\": false}'>\n  <form>\n    <button type=\"submit\"\n            *updated=\"onButtonUpdated\"\n            *update=\"root\">\n      Save\n    </button>\n  </form>\n</serc-rod>\n```\n\n- The root host runs `onButtonUpdated` when the button’s update is absorbed.\n- Then `*update=\"root\"` forces the root host to update again, which can re-render based on new state.\n\nNumeric behavior from a normal element:\n\n```html\n<serc-rod id=\"parent\" data='{\"message\":\"parent_message\"}'>\n  <serc-rod id=\"child\" data='{\"child_message\":\"hello\"}'>\n    <button\n      @click=\"$parent.message = child_message\"\n      *update=\"1\">\n      Refresh child host\n    </button>\n\n    <button\n      @click=\"$parent.message = child_message\"\n      *update=\"2\">\n      Refresh parent host\n    </button>\n\n    <button\n      @click=\"$parent.message = child_message\"\n      *update=\"root\">\n      Refresh root host\n    </button>\n  </serc-rod>\n\n  <p *print=\"message\"></p>\n</serc-rod>\n```\n\nFor this shape:\n\n- `*update=\"1\"` updates the containing child Sercrod host.\n- `*update=\"2\"` updates the parent Sercrod host, so the `<p *print=\"message\">` display can refresh.\n- `*update=\"root\"` also updates the top Sercrod host around the button.\n\nUsing `root` or an explicit selector is also valid and often clearer:\n\n```html\n<button\n  @click=\"$parent.message = child_message\"\n  *update=\"root\">\n  Send to parent\n</button>\n\n<button\n  @click=\"$parent.message = child_message\"\n  *update=\"(#parent)\">\n  Send to parent\n</button>\n```\n\nUsing a CSS selector in parentheses:\n\n```html\n<serc-rod class=\"panel\" data='{\"message\": \"\"}'>\n  <div class=\"panel-body\">\n    <input type=\"text\"\n           *input=\"message = $event.target.value\"\n           *update=\"(.panel)\">\n  </div>\n</serc-rod>\n```\n\n- After the input finishes updating, Sercrod finds the closest `.panel` element that is a Sercrod host and forces it to update.\n\n\n#### Notes\n\n- `*update` and its aliases are simple routing directives:\n  - They never evaluate JavaScript.\n  - They do not touch `data`, `stage`, or any scope objects.\n  - They only call `update(true, caller)` on a chosen Sercrod host.\n\n- Specs are interpreted in a strict order:\n  - Parenthesized selector, then `\"root\"`, then numeric depth, then bare selector.\n  - Only the first matching interpretation is used.\n\n- Only one target host is ever updated per directive evaluation.\n  - There is no support for multiple specs separated by spaces or commas.\n  - If the value contains spaces, it is treated as part of a single spec string.\n\n- If resolution fails or a selector is invalid, Sercrod logs a warning (when warnings are enabled) and continues without throwing.\n\n- There are currently no structural incompatibilities specific to `*update`:\n  - It can be combined with `*updated`, event handlers, and other attributes on the same element.\n  - The main thing to watch for is update cascades; rely on Sercrod’s internal `_updating` guard to prevent infinite loops, but try to design propagation paths that are simple and predictable.\n",
  "updated": "### *updated\n\n#### Summary\n\n`*updated` registers a post-update hook.\n\nIt runs JavaScript-style handler code after Sercrod finishes an update cycle. Use it for local post-render work such as DOM integration, measurements, logging, or calling a method after the current host or element has been rendered.\n\nUse [`*update`](update.md) when you want to select another Sercrod host and force that host to update. `*updated-propagate` / `n-updated-propagate` remain compatibility aliases for `*update`, not aliases for `*updated`.\n\n#### Basic Example\n\n```html\n<script>\n  function onHostUpdated(event) {\n    console.log(\"Host updated:\", event.type, event.target.id);\n  }\n</script>\n\n<serc-rod\n  id=\"app\"\n  data='{\"count\": 0}'\n  *updated=\"onHostUpdated($event)\"\n>\n  <button type=\"button\" @click=\"count += 1\">Increment</button>\n  <span *print=\"count\"></span>\n</serc-rod>\n```\n\nWhenever `app` renders, Sercrod evaluates `onHostUpdated($event)` after the render has completed.\n\n#### Behavior\n\n`*updated` can be placed in two places:\n\n- On a Sercrod host, such as `<serc-rod *updated=\"afterRender($event)\">`.\n- On an ordinary descendant element, such as `<div *updated=\"initWidget(el)\">`.\n\nOn a Sercrod host:\n\n1. The host completes its update cycle.\n2. Sercrod reads `*updated` or `n-updated`.\n3. If the value names a global object, Sercrod calls each enumerable function on that object with the host as the argument.\n4. Otherwise, Sercrod evaluates the whole value once as handler code in the host data scope.\n5. After that, any `*update` / `n-update` / `*updated-propagate` / `n-updated-propagate` on the same host is processed.\n\nOn an ordinary element:\n\n1. The containing host completes a real render pass.\n2. The host walks ordinary descendant elements, skipping nested Sercrod hosts.\n3. For each element with `*updated` or `n-updated`, Sercrod evaluates the whole value once as handler code in the nearest host data scope.\n4. After that, any `*update` / `n-update` / compatibility routing alias on the same element is processed.\n\n#### Expression Semantics\n\n`*updated` values are evaluated as handler code, not as target names.\n\n```html\n<serc-rod *updated=\"afterRender($event)\"></serc-rod>\n<div *updated=\"initWidget(el)\"></div>\n```\n\nImportant:\n\n- `*updated=\"(selector)\"` is no longer a routing form.\n- If you need to update another host, write `*update=\"root\"`, `*update=\"2\"`, or `*update=\"(#target)\"`.\n- The `*updated` value may still contain normal JavaScript grouping parentheses as part of an expression.\n\n#### Scope\n\nOn a Sercrod host, handler code runs in the host data scope.\n\nAvailable values include:\n\n- Host data fields as normal identifiers.\n- Methods registered through `*methods` / `n-methods`.\n- `el` / `$el`, pointing to the host element.\n- `$event` / `$e`, with `type`, `target`, and `currentTarget`.\n\nThe synthetic `$event.type` is:\n\n- `\"sercrod:init\"` on the initial render.\n- `\"sercrod:update\"` on later updates.\n\nOn ordinary elements, handler code runs in the nearest host data scope and gets:\n\n- `el` / `$el`, pointing to the element that owns `*updated`.\n- `$event` / `$e`, with `type: \"updated\"`, `target: el`, and `host`.\n\n#### Object-Bulk Host Hooks\n\nFor host-level `*updated`, a bare global object name is a convenience form.\n\n```html\n<script>\n  const Hooks = {\n    log(host) {\n      console.log(\"updated\", host.id);\n    },\n    measure(host) {\n      console.log(host.childElementCount);\n    }\n  };\n</script>\n\n<serc-rod id=\"app\" *updated=\"Hooks\"></serc-rod>\n```\n\nIf `window.Hooks` is an object, Sercrod calls each enumerable function on it with the host as the only argument. This object-bulk form is only for Sercrod hosts; ordinary elements always evaluate the attribute value as handler code.\n\n#### Timing\n\nFor a normal host update, the simplified order is:\n\n1. Render the host template, unless the host skips its own render because of `*lazy`.\n2. Update child Sercrod hosts.\n3. Run host-level `*updated`.\n4. Absorb and run `*updated` on ordinary descendant elements after a real render pass.\n5. Run `*update` routing on the relevant host or element.\n6. Finalize transient state.\n\nHost-level `*updated` runs for each host update cycle, including lazy cycles where the host skips a full redraw. Ordinary element `*updated` runs only after a real render pass creates or updates those elements.\n\n#### Examples\n\nHost-level lifecycle hook:\n\n```html\n<script>\n  function focusFirstInput(event) {\n    const input = event.target.querySelector(\"input, textarea, select\");\n    if (input) input.focus();\n  }\n</script>\n\n<serc-rod\n  data='{\"showForm\": true}'\n  *updated=\"focusFirstInput($event)\"\n>\n  <form *if=\"showForm\">\n    <input type=\"text\" name=\"name\">\n  </form>\n</serc-rod>\n```\n\nElement-level hook:\n\n```html\n<script>\n  function initWidget(el) {\n    if (el.dataset.ready) return;\n    el.dataset.ready = \"1\";\n  }\n</script>\n\n<serc-rod>\n  <div class=\"widget\" *updated=\"initWidget(el)\"></div>\n</serc-rod>\n```\n\nUpdating another host:\n\n```html\n<serc-rod id=\"parent\" data='{\"message\": \"old\"}'>\n  <serc-rod data='{\"draft\": \"new\"}'>\n    <button\n      type=\"button\"\n      @click=\"$parent.message = draft\"\n      *update=\"2\">\n      Apply\n    </button>\n  </serc-rod>\n\n  <p *print=\"message\"></p>\n</serc-rod>\n```\n\nHere `*update=\"2\"` performs target routing. `*updated` is not involved.\n\n#### Notes\n\n- `*updated` and `n-updated` are aliases.\n- `*updated` is for handler execution after an update.\n- `*update` and `n-update` are for target selection and forced host updates.\n- `*updated-propagate` and `n-updated-propagate` remain compatibility aliases for `*update`.\n- Avoid repeated data mutation inside `*updated` unless the handler has a stable stopping condition; otherwise it can create update loops.\n",
  "upload": "### *upload\n\n#### Summary\n\n`*upload` turns any element into an accessible file-upload trigger.\nIt creates a hidden `<input type=\"file\">` next to the element, lets the user pick one or more files, and sends them to a server endpoint using `XMLHttpRequest` plus `FormData`.\nOn success, the response is stored into `$upload` and optionally into a named variable via `*into`, then a re-render is triggered.\n\n#### Basic example\n\nUpload a single file to `/api/upload` and capture the JSON response into `result`:\n\n```html\n<serc-rod data='{\"result\": null}'>\n  <button *upload=\"'/api/upload'\" *into=\"result\">\n    Upload file...\n  </button>\n\n  <p *if=\"result\">\n    <span *print=\"result.message\"></span>\n  </p>\n</serc-rod>\n```\n\nKey points:\n\n- The `*upload` value is a Sercrod expression. In this basic form it evaluates to a string URL.\n- When the upload finishes successfully, the response body is assigned to `result` because of `*into=\"result\"`.\n- The same response is also exposed through `$upload` as a global one-shot value.\n\n#### Behavior\n\n- When Sercrod sees `*upload` (or its alias `n-upload`) on an element, it:\n  - Clones the element (without children).\n  - Evaluates the `*upload` expression in the current effective scope.\n  - Normalizes the result into an option object `{ url, method, field, with, headers, credentials }`.\n  - Ensures the clone is keyboard-clickable (through role and `tabindex`) if it was not already.\n  - Creates (or reuses) a hidden `<input type=\"file\" data-sercrod-generated=\"1\">` as a child of the element.\n  - Mirrors the element attributes `accept`, `multiple`, and `capture` onto the hidden input.\n  - Registers `click` and `keydown` handlers that open the file picker.\n  - Registers a `change` handler on the hidden input that starts the upload when files are selected.\n- When the user selects files and confirms:\n  - Sercrod dispatches a `sercrod-upload-start` event on the host `<serc-rod>` with `detail:{host, el, files, url, with}`.\n  - Sercrod sends the files to the configured `url` using `XMLHttpRequest` and `FormData`.\n  - As the upload progresses, `sercrod-upload-progress` events are dispatched with `detail:{host, el, loaded, total, percent}` whenever `lengthComputable` is true.\n  - When the upload finishes, Sercrod:\n    - Parses the text response as JSON if possible; otherwise keeps it as a string.\n    - Dispatches `sercrod-uploaded` with `detail:{host, el, response, status}`.\n    - Stores the response into `$upload` and/or the `*into` target (described below).\n- If anything goes wrong:\n  - During initial option evaluation or setup, Sercrod dispatches `sercrod-error` with `detail:{host, el, stage:\"upload-init\", error}`.\n  - During the network request, Sercrod dispatches `sercrod-error` with `detail:{host, el, stage:\"upload\", error}`.\n\nThe `*upload` directive itself does not submit any surrounding `<form>` element and does not use `fetch`. It always uses `XMLHttpRequest` so that upload progress events are available.\n\n#### Option object\n\nThe `*upload` value expression must evaluate to either:\n\n- A string\n  - Interpreted as `{ url: \"<that string>\" }`\n- An object\n  - Normalized to:\n\n    - `url` (required) - Target URL for the upload.\n    - `method` (optional) - HTTP method, defaults to `\"POST\"`.\n    - `field` (optional) - Form field name for files, defaults to `\"file\"`.\n    - `with` (optional) - Plain object of extra fields to append to the `FormData`.\n    - `headers` (optional) - Extra request headers (for example CSRF tokens).\n    - `credentials` (optional) - When truthy, enables `xhr.withCredentials`.\n\nIf the resolved value is not a string or an object with `url`, Sercrod throws inside `_normalize_upload_opts` and emits a `sercrod-error` with `stage:\"upload-init\"`.\n\nFiles are added to `FormData` as follows:\n\n- For a single file: `fd.append(field, file)`.\n- For multiple files: `fd.append(field + \"[0]\", file0)`, `fd.append(field + \"[1]\", file1)`, and so on.\n\nExtra keys from `with` are appended directly to the same `FormData` instance.\n\nYou normally do not need to set a `Content-Type` header: `XMLHttpRequest` automatically sets the appropriate multipart boundary when sending `FormData`. If you do specify `Content-Type` yourself in `headers`, it will override this default, so use that only if you know exactly what you are doing.\n\n#### Hidden input and host attributes\n\n`*upload` always works via a hidden file input placed as a child of the element that carries `*upload`:\n\n- The hidden input is created lazily once and reused on subsequent re-renders.\n- It is positioned far off-screen using fixed positioning so it does not affect layout.\n- The element attributes are mirrored:\n\n  - `accept` on the element becomes `accept` on the hidden input.\n  - `multiple` on the element becomes `multiple` on the hidden input.\n  - `capture` on the element becomes `capture` on the hidden input.\n\nWhen Sercrod re-binds the same DOM element (for example after an update where the node is reused), the options and the mirrored attributes on the hidden input are updated, but existing event listeners are reused.\n\n#### Evaluation timing\n\n- The `*upload` expression is evaluated during binding:\n\n  - On the first render of the element with `*upload`.\n  - On subsequent updates when the same DOM element is re-bound (for example when its `*upload` value or other data dependencies change but the element itself is reused).\n\n- The expression is not re-evaluated on every click.\n  To change the upload target or options dynamically, update your data and let Sercrod re-render so that `_bind_upload` runs again and refreshes the options.\n\nAny error during expression evaluation is reported through `sercrod-error` (`stage:\"upload-init\"`) and prevents the upload handler from being configured or refreshed.\n\n#### Execution model\n\n1. Sercrod renders the element and binds `*upload`.\n2. The user activates the element by click or keyboard.\n   The hidden file input is programmatically clicked and the file picker dialog appears.\n3. On file selection, the `change` handler fires:\n   - If there are no files (user cancels), nothing happens.\n   - Otherwise Sercrod emits `sercrod-upload-start`, builds a `FormData`, and calls `_xhr_upload`.\n4. `_xhr_upload` wires up:\n   - Progress events (`XMLHttpRequest.upload.onprogress`) to emit `sercrod-upload-progress`.\n   - Completion to either resolve with `{status, body}` or reject with an error.\n5. On success, Sercrod:\n   - Emits `sercrod-uploaded`.\n   - Writes the response body into data (see \"Variable creation and *into\").\n   - Schedules a re-render via `update(true)`.\n6. After the render cycle finishes, Sercrod internal `_finalize` runs:\n   - It resets `$upload` and `$download` to `null`.\n   - It clears any variables that were registered via `*into` for this cycle by setting them to `null`.\n   - It leaves the rest of the data object untouched.\n\nThe upload is purely client-side. Sercrod does not retry failed uploads and does not perform automatic backoff. Such policies should be implemented on top using the exposed events.\n\n#### Variable creation and *into\n\n`*upload` does not create any loop variables or local aliases.\nInstead, it writes to the data object when an upload completes.\n\n- Default behavior (no `*into`):\n\n  - The response body is stored in `$upload`.\n  - `$upload` is then reset to `null` by `_finalize` after the render cycle completes.\n  - This makes `$upload` a convenient \"last upload result\" scratch space.\n\n- With `*into` or `n-into` on the same element:\n\n  - Sercrod reads the attribute value (for example `*into=\"result\"`).\n  - On success, the response is assigned to `this._data[result]`.\n  - The key is recorded internally so that `_finalize` can later clear it by writing `null`.\n  - `$upload` is also populated on the first truthy response, if it was not already set.\n\nIn other words, `*into` provides a one-shot local variable for the response, while `$upload` is a shared, short-lived global.\n\nExample:\n\n```html\n<serc-rod data='{\"profile\": null}'>\n  <button *upload=\"'/api/profile/upload-avatar'\" *into=\"profile\">\n    Upload avatar\n  </button>\n\n  <div *if=\"profile\">\n    <p>Avatar updated.</p>\n    <p *print=\"profile.url\"></p>\n  </div>\n</serc-rod>\n```\n\nIf you need to persist the response beyond a single render cycle, copy it from `$upload` or the `*into` target into a more permanent field (for example `state.last_upload`) inside an event handler or a computed expression.\n\n#### Scope layering and parent access\n\nThe `*upload` expression is evaluated in the same effective scope as other data directives:\n\n- It sees the current host data.\n- It sees variables introduced by surrounding `*let`.\n- Inside loops (`*for`, `*each`), it sees the loop variables for the current iteration.\n- It can access outer data through normal property access (for example `parent.user.id` if you exposed `parent` yourself).\n\n`*upload` does not change the scope for its children: the element content is rendered with the same scope that was used to evaluate the `*upload` expression.\n\n#### Use with conditionals and loops\n\n`*upload` can be combined with conditional rendering and loops, with a few points to keep in mind.\n\n- Wrapping in `*if`:\n\n  - It is often useful to show or hide the upload button based on state:\n\n    ```html\n    <button *if=\"can_upload\" *upload=\"'/api/upload'\">\n      Upload file\n    </button>\n    ```\n\n  - When the `*if` condition switches from false to true, the element is re-created and `*upload` is bound again, re-evaluating its expression.\n\n- Inside `*for` or `*each`:\n\n  - You can generate multiple upload buttons from a list:\n\n    ```html\n    <button\n      *each=\"folder in folders\"\n      *upload=\"{ url: '/api/upload', with: { folder_id: folder.id } }\"\n    >\n      Upload to <span *print=\"folder.name\"></span>\n    </button>\n    ```\n\n  - Each instance gets its own hidden input and its own options.\n    Uploaded files for each button update data independently (often via different `*into` targets).\n\n- Combining with other control directives on the same element:\n\n  - As with other Sercrod directives, only one structural control branch is applied per element during rendering.\n  - In practice, you should avoid mixing `*upload` on the same element with other directives that also want to own rendering (for example `*template`, `*include`, `*import`).\n  - Use wrapping elements instead:\n\n    ```html\n    <div *if=\"ready\">\n      <button *upload=\"'/api/upload'\">Upload</button>\n    </div>\n    ```\n\nThis pattern keeps the responsibility of each directive clear and predictable.\n\n#### Events and UI integration\n\n`*upload` is designed to be driven from events:\n\n- `sercrod-upload-start` - Fired on the host `<serc-rod>` when an upload begins.\n\n  - `detail.host` - The host element instance.\n  - `detail.el` - The element that has `*upload`.\n  - `detail.files` - The selected files.\n  - `detail.url` and `detail.with` - The resolved URL and extra payload.\n\n- `sercrod-upload-progress` - Fired as the upload proceeds (when the browser can compute total size).\n\n  - `detail.loaded` / `detail.total` - Bytes sent vs total.\n  - `detail.percent` - Rounded percentage from 0 to 100.\n\n- `sercrod-uploaded` - Fired when the upload completes successfully.\n\n  - `detail.response` - Parsed JSON or plain string body.\n  - `detail.status` - HTTP status code.\n\n- `sercrod-error` - Fired on errors.\n\n  - `detail.stage` is `\"upload-init\"` if the options could not be evaluated or normalized.\n  - `detail.stage` is `\"upload\"` for network or HTTP-level errors.\n\nYou can handle these events using Sercrod event attributes on the element with `*upload`. For example:\n\n```html\n<button\n  *upload=\"'/api/upload'\"\n  @sercrod-upload-start=\"log('upload started', $event.detail)\"\n  @sercrod-upload-progress=\"progress = $event.detail.percent\"\n  @sercrod-uploaded=\"last_result = $event.detail.response\"\n>\n  Upload file\n</button>\n```\n\nThis lets you drive progress bars, disable other controls while an upload is active, or copy the response into long-lived state.\n\n\n#### Server-side contract for *upload\n\n`*upload` is slightly different from `*post`, `*fetch`, and `*api` on the request side, but it benefits from the same \"Sercrod API style\" on the response side.\n\nServer-side expectations:\n\n- Request shape:\n\n  - Sercrod always sends files using `multipart/form-data` via `XMLHttpRequest` and `FormData`.\n  - Files are placed under a configurable field name:\n    - Default: `\"file\"`.\n    - Custom: the `field` property of the `*upload` option (for example `field: \"avatar\"`).\n  - When multiple files are allowed, Sercrod appends them as `field[0]`, `field[1]`, and so on.\n  - Any extra data passed through the `with` option is appended to the same `FormData` as simple text fields.\n\n- Response shape:\n\n  - The HTTP response body is read as text and then:\n    - Parsed as JSON when possible.\n    - Left as a plain string when JSON parsing fails.\n  - The resulting value is stored directly into:\n    - `$upload` (global, short-lived).\n    - And, if `*into=\"name\"` is present, `data[name]` for the current host.\n  - There is no `URL[:prop]` shorthand for `*upload`. If you want to expose only a particular property, design your JSON envelope accordingly or copy the property into another field after the upload.\n\nRecommended approach on the server:\n\n- Treat `*upload` endpoints as file-plus-metadata variants of the same Sercrod API style:\n\n  - Accept `multipart/form-data` with:\n    - One or more file fields under a known field name.\n    - Optional additional fields corresponding to the `with` payload.\n  - Always return a JSON response for both success and application-level errors.\n  - Reuse the same JSON envelope that you use for `*post` / `*fetch` / `*api`, so that `*into` and `$upload` can be wired consistently.\n\nBenefits:\n\n- You can use a single \"Sercrod API style\" on the backend:\n\n  - File uploads (`*upload`) and pure JSON calls (`*post`, `*fetch`, `*api`) share the same response contract.\n  - Monitoring and logging can treat all Sercrod endpoints uniformly.\n  - Frontend code can handle upload results in the same way it handles other API responses.\n\n- For existing upload endpoints:\n\n  - You can typically integrate by:\n    - Matching the expected field name using the `field` option.\n    - Adding any legacy flags or identifiers through the `with` option.\n    - Adjusting the handler to always return a JSON envelope.\n  - This lets you gradually align older upload handlers with the Sercrod API style without breaking existing behavior.\n\n\n#### Best practices\n\n- Prefer server endpoints that accept `multipart/form-data` and do not require you to manually craft `Content-Type` headers.\n- Use the element `accept` attribute to restrict selectable file types (for example `accept=\"image/*\"`).\n- Add `multiple` when you want to allow multiple files in a single upload.\n- Add `capture` for camera or microphone capture on supporting mobile browsers.\n- Keep the upload element simple and clearly labeled so that users discover it easily.\n- Treat `*into` and `$upload` as short-lived slots: copy anything you need to preserve into stable data fields.\n- Handle errors via `@sercrod-error` on the host or by listening for `sercrod-error` and showing user-friendly messages.\n\n\n#### Notes\n\n- Alias attribute `n-upload` behaves identically to `*upload` and exists for environments where `*` is inconvenient in attribute names.\n- `*upload` uses `XMLHttpRequest` instead of `fetch` so that upload progress is observable. You should not mix this with separate manual `fetch` logic for the same file input; keep the flow inside Sercrod.\n- `*upload` does not submit a surrounding `<form>`. If you need to send other form fields along with the files, pass them through the `with` option or design a dedicated endpoint that accepts both.\n- The hidden file input is internal to Sercrod. Do not try to style or access it directly; always wire your UI and logic to the element that carries `*upload`.\n",
  "websocket": "### *websocket\n\n#### Summary\n\n`*websocket` opens and manages WebSocket connections for a Sercrod host and its descendants.\n\n- It works in two forms:\n  - As a host attribute on `<serc-rod>` to automatically connect once per URL.\n  - As an element directive on clickable or non-clickable elements to connect from within the template.\n- WebSocket messages update dedicated `$ws_*` state fields and, optionally, a property chosen via `*into`.\n- Outgoing messages are sent with `*websocket.send` and `*keys`.\n- `*ws-send` and `*ws-to` remain available as old compatible spelling.\n\n\n#### Basic examples\n\nHost-level connection with state flags:\n\n```html\n<serc-rod id=\"chat\"\n         data='{\"wsUrl\": \"wss://example.com/chat\"}'\n         *websocket=\"wsUrl\"\n         *into=\"wsData\">\n  <section>\n    <p>\n      Status:\n      <strong *print=\"$ws_ready ? 'connected' : 'disconnected'\"></strong>\n    </p>\n\n    <p *if=\"$ws_error\">\n      Last error: <span *print=\"$ws_error\"></span>\n    </p>\n\n    <pre *if=\"wsData\"\n         *textContent=\"JSON.stringify(wsData, null, 2)\"></pre>\n  </section>\n</serc-rod>\n```\n\nElement-level connection and sending:\n\n```html\n<serc-rod id=\"notify\"\n         data='{\n           \"notifyUrl\": \"wss://example.com/notify\",\n           \"payload\": { \"type\": \"ping\" }\n         }'>\n\n  <!-- Connect on button click -->\n  <button *websocket=\"notifyUrl\"\n          *into=\"lastNotify\">\n    Connect notification channel\n  </button>\n\n  <!-- Send selected host data through that URL -->\n  <button *websocket.send=\"notifyUrl\"\n          *keys=\"payload\">\n    Send ping\n  </button>\n\n  <pre *if=\"lastNotify\"\n       *textContent=\"JSON.stringify(lastNotify, null, 2)\"></pre>\n</serc-rod>\n```\n\n\n#### Host vs element usage\n\n`*websocket` can be attached to:\n\n1. The Sercrod host (`<serc-rod>`) as a host attribute:\n\n   - `*websocket=\"spec\"`\n   - Optional: `*into=\"propName\"`\n\n   Example:\n\n   - `<serc-rod *websocket=\"wsUrl\" *into=\"wsData\">…</serc-rod>`\n\n   Behavior:\n\n   - The host resolves `spec` into a URL (and optional `into`) from the host scope.\n   - It attempts a connection once, asynchronously, after the initial render.\n   - The connection is tracked per URL and controlled by the host’s `websocket` controller.\n\n2. Any element inside a Sercrod host:\n\n   - `*websocket=\"spec\"` on a child element (for example `button`, `a`, `div`) creates or reuses a connection for that URL.\n\n   Behavior:\n\n   - The directive is rendered as a “special element”:\n     - Sercrod clones the element (without children), wires the WebSocket logic, and appends the clone.\n     - Children of the original element are rendered normally into the clone.\n   - The connection is still owned by the host (the WebSocket state lives on the host’s data object), but events are dispatched from the element.\n\nClickable vs non-clickable elements:\n\n- If the element is “clickable” (`<button>`, `<a>` without `download`, or `<input type=\"button|submit|reset\">`):\n  - The WebSocket connection is created when the user clicks the element.\n- Otherwise:\n  - Sercrod automatically attempts the connection once on the next animation frame.\n\n\n#### Spec expression and URL resolution\n\nThe `spec` expression on `*websocket` can be:\n\n- A simple expression that evaluates to a URL string.\n- A template-like string with placeholders.\n- An object expression that describes additional options.\n\nResolution steps (for both host and element):\n\n1. Evaluate the `spec` as a Sercrod expression in `mode: \"attr\"`.\n\n   - Example: `*websocket=\"wsUrl\"` or `*websocket=\"config.ws\"`.\n\n2. If the expression result is `null` or is exactly the same as the raw string, treat it as a template string and run Sercrod’s text expansion:\n\n   - `${expr}` is evaluated like other Sercrod template expansions.\n   - `%name%` placeholders can also be used and are expanded from the current scope.\n\n3. Interpret the final value:\n\n   - If it is an object, the runtime accepts:\n     - `url`: the WebSocket URL (string, required).\n     - `protocols`: reserved for future use (currently ignored).\n     - `into`: optional property name that indicates where to store incoming messages.\n   - Otherwise, the value is treated as the URL string.\n\nExamples:\n\n```html\n<!-- Simple string or data binding -->\n<serc-rod *websocket=\"'wss://example.com/ws'\"></serc-rod>\n<serc-rod *websocket=\"wsUrl\"></serc-rod>\n\n<!-- Template-style expansion -->\n<serc-rod *websocket=\"'wss://example.com/ws/%roomId%'\"></serc-rod>\n\n<!-- Object-style spec -->\n<serc-rod *websocket=\"{ url: wsUrl, into: 'wsData' }\"></serc-rod>\n```\n\nPlaceholder guard:\n\n- Before connecting, Sercrod checks whether the final URL still contains placeholder patterns such as:\n  - `${...}`\n  - `%name%`\n- If placeholders remain, Sercrod does not connect and may emit a warning:\n\n  - Host-level: `\"[Sercrod warn] *websocket(host): URL not expanded\"`.\n  - Element-level: `\"[Sercrod warn] *websocket(el): URL not expanded\"`.\n\n- This usually means your data is not ready yet. You can retry later (for example via a forced update).\n\n\n#### Data integration and state fields\n\n`*websocket` ensures that the host’s data object has the following fields:\n\n- `$ws_ready`: `true` when at least one connection is open, `false` otherwise.\n- `$ws_error`: last error message string, or `null`.\n- `$ws_last`: last received message payload (after JSON decoding if applicable).\n- `$ws_messages`: array of all message payloads received during the lifetime of the host.\n\nConnection metadata:\n\n- `$ws_closed_at`: timestamp (`Date.now()`) of the most recent close event, or `null`.\n- `$ws_close_code`: close code from the WebSocket `close` event, or `null`.\n- `$ws_close_reason`: close reason string, or `null`.\n\nMessage storage:\n\n- Internally, each connection is stored in a map keyed by URL:\n  - `url` → `{ ws, into, el }`\n- On each message:\n\n  - The payload is decoded:\n\n    - If `ev.data` is a string that looks like JSON (starts with `{` / `[` and ends with `}` / `]`), Sercrod attempts `JSON.parse`.\n    - Otherwise the raw string or binary value is used as-is.\n\n  - Then the runtime:\n\n    - Sets `$ws_last` to that payload.\n    - Pushes the payload into `$ws_messages`.\n    - If an `into` key is configured for that connection:\n      - Writes the payload into `data[into]`.\n      - Records the property name for potential later cleanup.\n\nNotes:\n\n- The `into` path is a simple property name on the host’s data object.\n  - If you need nested structures, store payloads into objects and manage nested fields yourself.\n- All these fields live on the host’s data object and are visible to all directives under that host.\n\n\n#### Using *into with *websocket\n\nYou can choose where incoming messages are stored with `*into` or by specifying `into` in the spec object:\n\n- Resolution order:\n\n  1. Host- or element-level `*into` / `n-into` attribute wins if present.\n  2. Otherwise, `spec.into` (if provided) is used.\n  3. If neither is present, only `$ws_last` and `$ws_messages` are updated.\n\nExamples:\n\n```html\n<!-- Host-level with *into -->\n<serc-rod *websocket=\"wsUrl\" *into=\"wsData\">\n  <pre *textContent=\"JSON.stringify(wsData, null, 2)\"></pre>\n</serc-rod>\n\n<!-- Element-level with *into -->\n<button *websocket=\"wsUrl\" *into=\"wsMessage\">\n  Connect and keep last message in wsMessage\n</button>\n\n<!-- Object spec provides into -->\n<serc-rod *websocket=\"{ url: wsUrl, into: 'wsData' }\"></serc-rod>\n```\n\n\n#### Events and lifecycle hooks\n\n`*websocket` dispatches custom DOM events that you can listen to on the host or on the element that owns the directive.\n\nLifecycle events:\n\n- `sercrod-ws-before-connect` (cancelable):\n\n  - Fired just before a connection attempt.\n  - `detail` includes:\n    - `url`: initially resolved URL string.\n    - `into`: currently chosen into property name.\n    - `controller`: the host-level `websocket` controller object.\n  - You can:\n    - Modify `detail.url` and `detail.into` to override connection parameters.\n    - Call `event.preventDefault()` to cancel the connection.\n\n- `sercrod-ws-open`:\n\n  - Fired when the connection is successfully opened.\n  - `detail` includes `{ url }`.\n\n- `sercrod-ws-message`:\n\n  - Fired on each incoming message.\n  - `detail` includes:\n    - `url`: URL of the connection.\n    - `payload`: already-decoded payload (possibly parsed from JSON).\n\n- `sercrod-ws-error`:\n\n  - Fired on errors from the WebSocket.\n  - `detail` includes:\n    - `url`\n    - `error`: message string.\n\n- `sercrod-ws-close`:\n\n  - Fired when the connection closes.\n  - `detail` typically includes:\n    - `url`\n    - `code`, `reason`\n    - `closedAt` (timestamp)\n\nDispatch target:\n\n- For host-level `*websocket`:\n  - Events are dispatched from the `<serc-rod>` host.\n- For element-level `*websocket`:\n  - Events are dispatched from the element that carries the directive.\n  - Events bubble and are composed, so you can listen at the host or further up if needed.\n\n\n#### WebSocket controller (`el.websocket`)\n\nEach Sercrod host exposes a lightweight controller object on the host element:\n\n- Accessible as `host.websocket` in JavaScript.\n- Non-enumerable and safe to overwrite on reinitialization.\n\nProperties and methods (simplified view):\n\n- `last_url`: last URL used to open a connection, or empty string.\n- `last_into`: last `into` value used for a connection, or empty string.\n- `urls()`: array of URLs currently known in the connection map.\n- `status(url?)`:\n\n  - Returns an object like:\n    - `ready`: boolean (whether the chosen connection is open).\n    - `state`: the WebSocket `readyState` value, or `-1` if no connection.\n    - `error`: `$ws_error` or `null`.\n    - `count`: `$ws_messages.length` or `0`.\n\n- `connect(url?, into?)`:\n\n  - Uses the given `url` and optional `into`, or falls back to the last arguments.\n  - Returns `true` if a connection was created or reused, `false` otherwise.\n\n- `reconnect()`:\n\n  - Attempts to reconnect using the last known `url` and `into`.\n  - Returns `true` on success, `false` otherwise.\n\n- `close(url?)`:\n\n  - If `url` is given, closes the connection for that URL.\n  - If omitted, closes all known connections.\n  - Returns `true` if at least one connection was closed, `false` otherwise.\n\n- `send(payload, toUrl?)`:\n\n  - Uses the same low-level send logic as `*websocket.send`:\n    - If `toUrl` is provided, sends to that specific URL (if open).\n    - Otherwise, sends to the first open connection.\n  - Returns `true` if the message was sent, `false` otherwise.\n\nThis controller is useful for advanced orchestration and custom reconnection strategies, but most templates can rely on `*websocket` plus `*websocket.send` and `*keys` without calling it directly.\n\n\n#### Interaction with *websocket.send and *keys\n\n`*websocket` establishes and tracks connections; `*websocket.send` and `*keys` send selected data over them.\n\nConnection map:\n\n- Every `*websocket` (host or element) uses the host’s internal map:\n\n  - Key: URL string.\n  - Value: `{ ws, into, el }`.\n\nSending:\n\n- `*websocket.send` resolves its value to a WebSocket URL.\n- `*keys` selects the payload from the host data:\n  - One key sends that key's value.\n  - Multiple keys send an object containing those keys.\n  - No `*keys` sends the whole host data or staged data.\n- The runtime calls `_ws_send` with:\n\n  - The URL chosen via `*websocket.send`.\n  - The payload object or value.\n\n- `_ws_send` behaves as follows:\n\n  - If `url` is specified, it looks up that URL in the connection map and sends to that connection if it is open.\n  - Otherwise, it sends to the first open connection found in the map.\n  - If no suitable open connection exists, it returns `false` and does nothing.\n\nOld spelling:\n\n- `*ws-send` / `n-ws-send` and `*ws-to` / `n-ws-to` remain supported for compatibility.\n- In that old form, `*ws-send` is the payload expression and `*ws-to` is the target URL template.\n- Do not use the old form in new manual examples unless demonstrating compatibility.\n\nRestrictions:\n\n- `*websocket.send` does not create a connection. A matching `*websocket` connection must already exist or be opened separately.\n- `*into` remains the receive-side destination for incoming messages; it is not used as the send target.\n\n\n#### Evaluation timing and reconnection\n\nHost-level auto connect:\n\n- On first connection:\n\n  - After the host is initialized, Sercrod schedules a `*websocket` host connect on the next animation frame.\n  - It resolves the spec and attempts the connection once per URL.\n  - A one-shot guard ensures the same host and URL are not auto-connected repeatedly.\n\n- On failure or close:\n\n  - If the connection fails (for example, invalid URL) or errors/ closes:\n    - Sercrod clears the one-shot guard for that URL.\n    - It also clears internal retry flags so that the connection can be retried later.\n    - It invalidates the AST cache related to the WebSocket spec so that the expression will be re-evaluated on the next forced update.\n\n- Retrying:\n\n  - Sercrod does not automatically loop or schedule reconnect attempts.\n  - To try again with the same host-level `*websocket`:\n    - You can call `host.update(true)` (or equivalent) to trigger a forced update.\n    - Or invoke `host.websocket.reconnect()` from custom code.\n  - Both rely on the internal flags having been cleared by a prior error or close.\n\nElement-level connect:\n\n- For clickable elements:\n\n  - Each click runs a fresh `connect` attempt:\n    - If the URL is new, a new connection is created.\n    - If a connection for the URL already exists and is open or connecting, it is reused.\n\n- For non-clickable elements:\n\n  - Sercrod attempts one connection on the next animation frame.\n  - After an error or close, you can:\n    - Re-render the element.\n    - Or use the host’s `websocket` controller to reconnect.\n\nPlaceholder behavior:\n\n- If the URL still contains placeholders when evaluated:\n  - Sercrod aborts the connection attempt and logs a warning.\n  - Once the data is ready and the URL expands cleanly, a new connection attempt can be triggered via forced update or user action.\n\n\n#### Best practices\n\n- Prefer JSON payloads:\n\n  - Sending objects from `*websocket.send` is natural because Sercrod automatically `JSON.stringify` values that are plain objects.\n  - Incoming messages that look like JSON are automatically parsed.\n\n- Use `*into` for the main “current message”, `$ws_messages` for history:\n\n  - Use a dedicated property via `*into` or `spec.into` to hold the latest message relevant for the UI.\n  - Use `$ws_messages` when you need a full message log.\n\n- Be explicit about URLs when you have multiple connections:\n\n  - Use `*websocket.send=\"notifyUrl\"` or another explicit URL expression when more than one `*websocket` is active.\n  - Keep connection URLs in your data model (for example `notifyUrl`, `chatUrl`) so that you can reference them consistently.\n\n- Handle errors and closure:\n\n  - Watch `$ws_error` and `$ws_ready` to show user-friendly status.\n  - Listen to `sercrod-ws-error` and `sercrod-ws-close` if you need more precise handling or logging.\n\n- Design your own reconnection strategy:\n\n  - Sercrod intentionally avoids automatic reconnect loops.\n  - If you need reconnects, implement them explicitly using:\n    - `host.websocket.reconnect()`.\n    - Or by calling `host.websocket.connect(url, into)` from your own timers or event handlers.\n\n- Keep specs simple and stable:\n\n  - Avoid writing very complex expressions inside `*websocket`.\n  - Prefer to compute configuration in data and refer to it by a simple expression like `wsConfig.chat` or `wsUrl`.\n\n\n#### Notes\n\n- `*websocket` has an alias `n-websocket`. They are interchangeable; pick one style and use it consistently.\n- `*websocket` can be used on the host and on child elements; all connections end up in the same host-level map and share the same `$ws_*` state.\n- Messages that are not valid JSON strings remain as raw values (strings or binary).\n- `*websocket` does not conflict with most other directives, but it does share `*into` semantics with HTTP directives. Only one `*websocket` / `n-websocket` should be attached to a given element.\n- Use `*websocket.send` and `*keys` for sending messages; do not attempt to send directly from `*websocket`. The directive’s responsibility is connection management and state integration.\n- `*ws-send` and `*ws-to` remain old compatible spellings.",
  "ws-send": "### *ws-send\n\n#### Summary\n\n`*websocket.send` sends selected host data through a WebSocket connection managed by the same Sercrod host.\nIt is an action directive for regular elements (for example `<button>`, `<a>`, or `<input>`).\n\nFor new code, prefer:\n\n```html\n<button *websocket.send=\"wsUrl\" *keys=\"message\">Send</button>\n```\n\nIn this form:\n\n- `*websocket.send` selects the WebSocket connection URL.\n- `*keys` selects the host data to send.\n\n`*ws-send` and `*ws-to` remain supported as the old compatible spelling, but they are no longer the preferred form in new examples.\n\nUse `*ws-send` only when documenting or maintaining old code that already uses it.\n\n\n#### Basic example\n\nA basic setup with a host-level WebSocket and a button that sends one host data key:\n\n```html\n<serc-rod id=\"app\"\n         data='{\"wsUrl\":\"wss://example.com/ws\",\"message\":\"hello\"}'\n         *websocket=\"wsUrl\">\n  <button *websocket.send=\"wsUrl\" *keys=\"message\">\n    Send\n  </button>\n</serc-rod>\n```\n\nBehavior:\n\n- `<serc-rod>` connects to `wss://example.com/ws` because of `*websocket=\"wsUrl\"`.\n- When the button is clicked, Sercrod resolves `wsUrl` as the send target.\n- `*keys=\"message\"` selects `data.message`.\n- The resulting value `\"hello\"` is sent through that WebSocket connection.\n- The button’s content (\"Send\") is rendered as usual; `*websocket.send` only adds behavior.\n\n\n#### Behavior\n\n- `*websocket.send` is an action directive that:\n\n  - Clones the element (without its children) and appends the clone to the DOM.\n  - Attaches a click handler when the element is considered clickable (button-like).\n  - On click, resolves the WebSocket URL from the directive value.\n  - Builds the payload from `*keys`.\n  - Sends the evaluated value as a WebSocket message.\n\n- The directive does not create or manage WebSocket connections by itself.\n  It reuses connections opened by:\n\n  - The host-level `*websocket` on `<serc-rod>` or another Sercrod host.\n  - The element-level `*websocket` on specific elements.\n  - The imperative `this.websocket.connect(...)` helper from scripts.\n\n- Alias:\n\n  - `*websocket.send` and `n-websocket.send` are aliases and behave identically.\n\n- Old spelling:\n\n  - `*ws-send` and `n-ws-send` remain supported for compatibility.\n  - `*ws-to` / `n-ws-to` remain supported only for the old spelling.\n  - In old spelling, `*ws-send` means payload and `*ws-to` means target URL.\n\n\n#### Clickability and event wiring\n\nThe directive decides whether to attach its own click handler based on the cloned element’s tag and type:\n\n- `BUTTON` always counts as clickable.\n- `A` counts as clickable when it does not have a `download` attribute.\n- `INPUT` is clickable when `type` is one of `\"button\"`, `\"submit\"`, or `\"reset\"`.\n\nFor those clickable elements:\n\n- `*ws-send` adds its own click handler that sends the message.\n- Event attributes using the configured prefix (by default `@`) such as `@click`, `@mousedown`, and so on are still respected.\n  Sercrod scans all attributes, and for each one whose name starts with the event prefix, it calls the internal event binder.\n\nThis means:\n\n- You can combine `*ws-send` with `@click` on the same element.\n- Both handlers run: `*ws-send` performs its send, and your `@click` expression is evaluated as usual.\n\n\n#### Expression and payload evaluation\n\nThe attribute value of `*ws-send` is an expression that is evaluated at click time, not at render time:\n\n- On each click, Sercrod calls:\n\n  - `this.eval_expr(expr, scope, { el: work, mode: \"ws-send\", $event: e })`\n\n- Inside the expression you can access:\n\n  - All data in the current scope (for example properties from `data`, `stage`, or `*methods`).\n  - `$data` (this host’s data object).\n  - `$root` (root host’s data, if any).\n  - `$parent` (nearest ancestor Sercrod host’s data).\n  - `el` (the original template element that declared `*ws-send`).\n  - `$event` (the click event object).\n\nPayload conversion:\n\n- After evaluating the expression, `_ws_send` converts the value before sending:\n\n  - If the value is an object:\n\n    - It attempts `JSON.stringify(payload)`.\n    - If that fails, it falls back to `String(payload)`.\n\n  - For non-object values:\n\n    - It uses `String(payload)`.\n\n- The final message sent over the WebSocket is always a string.\n- On the receiving side, `*websocket` will attempt to parse incoming string messages as JSON when they look like arrays or objects (`\"{}\"` or `\"[]\"` style). This makes sending objects via `*ws-send` a natural choice.\n\n\n#### Target selection with the `*ws-to` attribute\n\n`*ws-send` can send messages either to a specific WebSocket URL or to the first open connection it finds.\n\n- Optional `*ws-to` attribute:\n\n  - If present, its value is treated as a text template and resolved via Sercrod’s text expansion logic.\n  - Internally, the directive uses:\n\n    - `this._expand_text(toRaw, scope, work)`\n\n  - This supports the standard placeholder mechanisms (such as `%path.to.url%` or other configured `%...%` forms), as defined by Sercrod’s `_expand_text`.\n\n- Target resolution logic:\n\n  - If `*ws-to` is present and its expanded value is a non-empty string:\n\n    - `_ws_send` looks up a WebSocket in the internal map by that URL.\n    - If there is an open connection for that URL (`readyState === WebSocket.OPEN`), the message is sent to that connection.\n    - Otherwise, the send is skipped.\n\n  - If `*ws-to` is absent or its expanded value is empty:\n\n    - `_ws_send` iterates over the internal map of WebSocket holders.\n    - It picks the first connection whose `readyState` is `WebSocket.OPEN`.\n    - If no open connection is found, the send is skipped.\n\nImportant notes:\n\n- If no connection is open, `*ws-send` does nothing; there is no built-in error dialog.\n- If multiple connections are open and `*ws-to` is not provided, the actual target is whichever open connection is first in the internal map.\n  This order is not a stable contract and should not be relied on.\n  For multiple connections, always use `*ws-to` to select a URL explicitly.\n\n\n#### Evaluation timing\n\n`*ws-send` participates in the rendering pipeline as follows:\n\n- During rendering:\n\n  - Sercrod sees the `*ws-send` or `n-ws-send` attribute.\n  - It clones the element, appends the clone to the parent, and attaches the click handler if the element is clickable.\n  - It also processes event attributes (such as `@click`) on the original element and binds them to the clone.\n\n- During user interaction:\n\n  - The expression in `*ws-send` is evaluated only when the element is clicked.\n  - The `*ws-to` attribute (if present) is also evaluated through text expansion at click time, so it can reflect the current state via `%...%` placeholders.\n\n- Updates:\n\n  - Re-renders of the Sercrod host recreate the element and its event handlers.\n  - The send behavior is always driven by clicks, not by data changes alone.\n\n\n#### Execution model\n\nConceptually, the directive behaves like this:\n\n1. Rendering:\n\n   - Detect `*ws-send` or `n-ws-send` on a node `work`.\n   - Clone the element without children: `el = work.cloneNode(false)`.\n   - Append the clone to the parent.\n   - Read the raw `*ws-to` value once:\n\n     - `const toRaw = work.getAttribute(\"*ws-to\") || \"\";`\n\n   - Compute clickability based on `el`’s tag and type.\n   - If clickable, attach a click listener:\n\n     - On click:\n\n       - Evaluate the expression in the attribute to obtain `payload`.\n       - Resolve the `*ws-to` attribute by calling `_expand_text(toRaw, scope, work)` to obtain `targetUrl` (which may be empty).\n       - Call `_ws_send(targetUrl, payload)`.\n\n   - For each attribute whose name starts with the event prefix (for example `@`), bind the corresponding event handler to `el`.\n   - Render all original child nodes into `el` using the usual rendering rules.\n\n2. Sending:\n\n   - `_ws_send(urlOrEmpty, payload)` selects a target WebSocket:\n\n     - If `urlOrEmpty` is non-empty, look up that URL in the internal map of connections.\n     - Otherwise, scan for the first open WebSocket.\n\n   - If no open target is found, return `false` and do not send anything.\n   - Convert `payload` to a string as described above.\n   - Call `ws.send(out)` on the selected WebSocket.\n   - Return `true` if the send operation succeeds, `false` if it throws.\n\nThe return value from `_ws_send` is not used by the directive itself but is exposed through the `this.websocket.send(...)` helper for more advanced, imperative control from scripts.\n\n\n#### Scope layering and variables\n\n`*ws-send` does not create new scope variables of its own, but its expression runs inside the normal Sercrod scope:\n\n- Available in the expression:\n\n  - All properties from the current evaluation scope (data, stage, and so on).\n  - `$data`, `$root`, `$parent`.\n  - Methods provided via `*methods` or injected internal helpers.\n  - `el` (the original template element).\n  - `$event` (the click `MouseEvent` or `PointerEvent` or similar).\n\n- The directive does not add new special variables other than passing `el` and `$event` to `eval_expr`.\n- Because expressions execute in the standard sandbox, any restrictions that apply to other directives also apply to `*ws-send`.\n\n\n#### Use with *websocket and WebSocket metrics\n\n`*ws-send` is designed to be used together with `*websocket` and the WebSocket status fields maintained in the data object:\n\n- WebSocket connections:\n\n  - Host-level `*websocket` on `<serc-rod>` can automatically connect once at initialization or when you call `update(true)`.\n  - Element-level `*websocket` on, for example, a button, can connect when that element is clicked or immediately if it is not clickable.\n  - Both forms ultimately use `_ws_connect` and share a common map of live connections on the host.\n\n- Status fields:\n\n  - When you use `*websocket`, Sercrod keeps several fields on the data object:\n\n    - `$ws_ready`: `true` when at least the last connection is open and ready, `false` otherwise.\n    - `$ws_error`: last error message, or `null`.\n    - `$ws_last`: last received message (after optional JSON parsing).\n    - `$ws_messages`: array of all received messages for this host.\n    - `$ws_closed_at`: timestamp of the last close event.\n    - `$ws_close_code`: last close code, or `null`.\n    - `$ws_close_reason`: last close reason, or `null`.\n\n  - `*ws-send` itself does not change these fields directly.\n    They change as a result of connection open, message, error, and close events handled by `*websocket`.\n\nPractical pattern:\n\n- Disable send buttons when no connection is ready:\n\n  ```html\n  <serc-rod id=\"app\"\n           data='{\"wsUrl\":\"wss://example.com/ws\",\"message\":\"ping\"}'\n           *websocket=\"wsUrl\">\n    <button *ws-send=\"message\"\n            :disabled=\"!$ws_ready\">\n      Send ping\n    </button>\n  </serc-rod>\n  ```\n\n\n#### Best practices\n\n- Prefer object payloads:\n\n  - Sending objects like `{ type: \"ping\", data: { id: 1 } }` works well because:\n\n    - `*ws-send` serializes objects with `JSON.stringify`.\n    - `*websocket` attempts to parse JSON-looking text on receive.\n\n  - This gives a symmetric request/response shape.\n\n- Use `*ws-to` when multiple connections are in play:\n\n  - If your host manages multiple WebSocket URLs, always set `*ws-to` to avoid ambiguity:\n\n    - For example `*ws-to=\"%primaryUrl%\"` or `*ws-to=\"%notificationsUrl%\"`.\n\n  - Do not rely on the internal ordering of the connection map.\n\n- Keep expressions simple:\n\n  - Derive complex payloads via helper functions or precomputed data.\n\n    - For example: `*ws-send=\"buildPayload($event)\"` with a `buildPayload` function defined via `*methods`.\n\n- Handle offline or closed states in the UI:\n\n  - Use `$ws_ready` and `$ws_error` to enable or disable buttons and show status messages instead of relying on the send result.\n\n- Combine with `@click` when you need local side effects:\n\n  - You can use `@click` to update local state and still let `*ws-send` handle the actual send.\n\n\n#### Additional examples\n\nSending a structured JSON payload:\n\n```html\n<serc-rod id=\"chat\"\n         data='{\n           \"wsUrl\": \"wss://example.com/chat\",\n           \"draft\": \"\"\n         }'\n         *websocket=\"wsUrl\">\n  <input type=\"text\"\n         :value=\"draft\"\n         @input=\"draft = $event.target.value\">\n  <button *ws-send=\"{ type: 'chat', text: draft }\"\n          :disabled=\"!$ws_ready || !draft\">\n    Send message\n  </button>\n</serc-rod>\n```\n\nSelecting a specific connection with `*ws-to`:\n\n```html\n<serc-rod id=\"multi\"\n         data='{\n           \"primaryUrl\": \"wss://example.com/primary\",\n           \"secondaryUrl\": \"wss://example.com/secondary\"\n         }'\n         *websocket=\"primaryUrl\">\n  <button *ws-send=\"{ type: 'ping' }\">\n    Ping (default connection)\n  </button>\n\n  <button *ws-send=\"{ type: 'ping-secondary' }\"\n          *ws-to=\"%secondaryUrl%\">\n    Ping secondary\n  </button>\n</serc-rod>\n```\n\nIn this pattern, you would typically have an element-level `*websocket` somewhere that connects to `secondaryUrl`.\n`*ws-send` simply routes messages to the appropriate connection based on `*ws-to`.\n\n\n#### Notes\n\n- `*ws-send` and `n-ws-send` are aliases; choose one style for consistency.\n- The directive sends only when at least one WebSocket is open and ready; otherwise, it silently does nothing (aside from returning `false` internally).\n- The message is always sent as a string; objects are serialized with `JSON.stringify`.\n- `*ws-send` does not itself attempt reconnection or error handling.\n  Connection lifecycle is handled by `*websocket` and internal helpers like `_ws_connect` and `_ws_clear_retry_flags`.\n- When combining `*ws-send` with other directives on the same element:\n\n  - It is safe to combine it with attribute bindings (such as `:class`, `:disabled`) and event bindings (such as `@click`).\n  - There are no special structural conflicts like those between `*each` and `*include` or `*import`, because `*ws-send` does not take ownership of child structure.\n- The `*ws-to` attribute is purely a targeting helper for `*ws-send`; it does not affect where received messages are stored.\n  Received messages are still controlled by `*websocket` and its `*into` configuration.",
  "ws-to": "### *ws-to\n\n#### Summary\n\n`*ws-to` is the old targeting helper used together with the old `*ws-send` form.\nIt selects which WebSocket connection a `*ws-send` action will use by specifying a WebSocket URL.\nThe directive has an alias `n-ws-to`; in this document, “*ws-to” refers to both `*ws-to` and `n-ws-to`.\n\nFor new code, prefer the unified form:\n\n```html\n<button *websocket.send=\"wsUrl\" *keys=\"message\">Send</button>\n```\n\nIn that form, `*websocket.send` selects the target connection and `*keys` selects the payload.\n\nIf a Sercrod host only ever opens one WebSocket connection, `*ws-to` is not required and can be omitted.\nWhen a host has multiple WebSocket connections open (for example primary, notifications, or other channels), `*ws-to` lets you explicitly choose which URL to send to from markup.\n\n\n#### Relationship to *ws-send and *websocket\n\n`*ws-to` does not open WebSocket connections and does not decide where received messages are stored.\nInstead:\n\n- `*websocket` is responsible for:\n  - Opening connections for specific URLs.\n  - Maintaining the internal map of active connections per host.\n  - Updating data fields such as `$ws_ready`, `$ws_last`, `$ws_messages`, and any property configured via `*into`.\n\n- `*ws-send` is responsible for:\n  - Evaluating a payload expression when the user clicks a button or similar element.\n  - Sending that payload through one of the existing WebSocket connections.\n\n- `*ws-to` is responsible for:\n  - Deciding which connection `*ws-send` will use, by providing a WebSocket URL string that matches one of the connections opened by `*websocket`.\n\n\n#### Basic example: single host with two WebSockets\n\nThe following example shows a host that connects to two different WebSocket URLs and uses `*ws-to` to direct messages to each one.\n\n```html\n<serc-rod id=\"multi\"\n         data='{\n           \"apiUrl\":   \"wss://example.com/api\",\n           \"notifyUrl\":\"wss://example.com/notify\"\n         }'\n         *websocket=\"apiUrl\">\n\n  <!-- Connect a second WebSocket for notifications -->\n  <span *websocket=\"notifyUrl\"></span>\n\n  <!-- Sends to the default connection (apiUrl) -->\n  <button *ws-send=\"{ type: 'ping-api' }\">\n    Ping API\n  </button>\n\n  <!-- Sends specifically to notifyUrl -->\n  <button *ws-send=\"{ type: 'ping-notify' }\"\n          *ws-to=\"%notifyUrl%\">\n    Ping notify\n  </button>\n</serc-rod>\n```\n\nBehavior:\n\n- The host-level `*websocket` opens a connection to `apiUrl`.\n- The `<span>` with `*websocket=\"notifyUrl\"` opens a second connection to `notifyUrl`.\n- The first button has no `*ws-to`, so `*ws-send` sends through the host’s default open connection (here, the first open connection, which is `apiUrl`).\n- The second button has `*ws-to=\"%notifyUrl%\"`, so `*ws-send` sends through the connection associated with `notifyUrl`.\n\n\n#### Behavior\n\nAt render time, the `*ws-send` directive reads the raw `*ws-to` or `n-ws-to` attribute value:\n\n- It looks for a `*ws-to` attribute on the original template element.\n- If not found, it then looks for an `n-ws-to` attribute.\n- If either is present, the raw text is stored as `toRaw`.\n- If neither is present, `toRaw` is an empty string.\n\nOn each click, when the handler runs, `*ws-send` resolves the target URL like this:\n\n- It calls an internal resolver:\n\n  - `resolveTo()` returns:\n    - An empty string if `toRaw` is empty.\n    - Otherwise, the text produced by passing `toRaw` through Sercrod’s text expansion helper.\n\n- Sercrod uses its central text expansion function (the same one used for other attribute expansions) to interpret constructs such as `%notifyUrl%` according to the current scope.\n\nOnce the target URL string has been calculated, the runtime calls the internal send helper:\n\n- `_ws_send(urlOrEmpty, payload)` is invoked with:\n  - `urlOrEmpty` set to the resolved `*ws-to` value, which may be an empty string.\n  - `payload` set to the evaluated result of the `*ws-send` expression.\n\nThe runtime then selects a WebSocket connection based on `urlOrEmpty`:\n\n- If `urlOrEmpty` is a non-empty string:\n  - It looks up that URL in the host’s internal WebSocket map.\n  - If a connection for that URL exists and is open, it becomes the target.\n  - If no open connection for that URL is found, nothing is sent.\n\n- If `urlOrEmpty` is an empty string:\n  - It scans the map of connections for the first instance whose `readyState` is `OPEN`.\n  - If such a connection is found, it becomes the target.\n  - If no open connection exists, nothing is sent.\n\nIn all cases:\n\n- If a target WebSocket is found, the payload is serialized and sent.\n- If no target is found, the send operation is skipped and the internal helper returns `false`.\n\n\n#### Evaluation timing\n\n`*ws-to` participates in the evaluation lifecycle of `*ws-send` in a controlled way:\n\n- At render time:\n  - The raw attribute string from `*ws-to` or `n-ws-to` is captured once.\n  - No WebSocket lookup or send occurs during rendering.\n\n- At click time:\n  - The payload expression of `*ws-send` is evaluated with the current scope and event.\n  - The target URL is resolved by expanding the raw `*ws-to` template with the current scope.\n  - A connection is selected and the message is sent, or skipped if no suitable connection is open.\n\nThis design lets `*ws-to` depend on dynamic state (such as a URL stored in data) without forcing re-renders every time the target changes.\n\n\n#### Interaction with text expansion\n\nThe value of `*ws-to` or `n-ws-to` is processed with Sercrod’s generic text expansion helper, so it follows the same rules as other text templates in the system.\n\nTypical patterns:\n\n- Using a property from data:\n\n  - `*ws-to=\"%notifyUrl%\"`\n\n- Using nested data:\n\n  - `*ws-to=\"%config.websocket.notifyUrl%\"`\n\nThe exact placeholder syntax and expansion rules are defined by Sercrod’s global configuration and text expansion logic.\nThe important point is that `*ws-to` is not a full expression binding; it is a text template that is expanded into a plain string URL for `_ws_send`.\n\n\n#### Multiple connections per host\n\nA Sercrod host can maintain multiple WebSocket connections, one per URL, as long as the browser and server allow it.\n\nInternally, each host keeps a map from URL to a holder object that contains:\n\n- `ws`: the actual WebSocket instance.\n- `into`: optional data path used by `*websocket` for storing received messages.\n- `el`: the element that initiated the connection.\n\nWhen `*websocket` is used with different URLs on the same host, this map grows to include each distinct URL.\nIf `*websocket` is used multiple times with the same URL:\n\n- The existing open connection for that URL is reused.\n- Additional calls do not create duplicate WebSocket instances; they reuse the same one.\n\nIn this environment:\n\n- `*ws-to` (or `n-ws-to`) lets you choose a specific URL entry from this map when sending.\n- It does not change how the map is populated; that is controlled by `*websocket` and the helper API.\n\n\n#### Relationship to *into and received messages\n\n`*ws-to` only affects where outgoing messages are sent.\nIt does not influence how incoming messages are stored.\n\nFor incoming data:\n\n- `*websocket` controls:\n  - Whether received messages are parsed as JSON.\n  - Which data fields receive messages.\n  - Whether messages are pushed into arrays such as `$ws_messages`, or stored in `$ws_last`, or written into a custom property via `*into`.\n\n- `*into` is used by `*websocket` as an optional override to say:\n  - “Store data for this connection under `data[intoKey]`.”\n\n`*ws-to` does not touch `*into` or any of those storage rules:\n\n- Changing `*ws-to` or `n-ws-to` on a button will not change where messages are written when responses arrive.\n- Received messages for each connection still follow the configuration of the corresponding `*websocket`.\n\nIn other words:\n\n- `*into` describes where information comes in.\n- `*ws-to` describes where outbound messages go.\n\n\n#### Use with the websocket helper API\n\nSercrod’s host exposes a `websocket` helper object that includes a `send` method.\nInternally:\n\n- Both the `websocket.send(payload, toUrl)` helper and `*ws-send` with `*ws-to` call the same low-level function for selecting connections and sending payloads.\n\nFrom a conceptual point of view:\n\n- The helper function:\n\n  - `websocket.send(payload, toUrl)`\n\n  lets scripts choose a target by URL.\n\n- The `*ws-to` attribute:\n\n  - `*ws-to=\"someUrlTemplate\"` or `n-ws-to=\"someUrlTemplate\"`\n\n  lets templates choose a target by URL.\n\nThis symmetry ensures that template-driven and script-driven code share the same rules for selecting WebSocket connections.\n\n\n#### Best practices\n\n- Single-connection hosts:\n\n  - When a host only has one WebSocket connection, omit `*ws-to` and `n-ws-to` on all `*ws-send` elements.\n  - In that case, `*ws-send` automatically sends to the single open connection, and no additional configuration is needed.\n\n- Multiple-connection hosts:\n\n  - When a host opens more than one WebSocket URL, prefer to give each send action an explicit `*ws-to` (or `n-ws-to`).\n  - Keep all URLs in the data object (for example `apiUrl`, `notifyUrl`, `metricsUrl`) instead of hardcoding them in attributes.\n  - Use `*ws-to` only to select among those known URLs via simple templates such as `%notifyUrl%`.\n\n- Keep `*ws-to` templates simple:\n\n  - Use `*ws-to` mainly to choose among already computed URLs.\n  - Avoid embedding complex logic or large configuration strings in `*ws-to` itself.\n  - If you need more complex routing, prefer to compute the final URL in data or methods and reference it from `*ws-to`.\n\n- Avoid depending on map ordering:\n\n  - Without `*ws-to`, `*ws-send` will pick the first open connection it finds.\n  - When multiple connections are open and the target matters, always use `*ws-to` to avoid relying on internal map iteration order.\n\n- Configuration and documentation:\n\n  - Treat `*ws-to` as an advanced feature.\n  - In introductory examples for `*ws-send` and `*websocket`, focus on the single-connection pattern and introduce `*ws-to` only when you show multiple connections.\n\n\n#### Additional examples\n\nDynamic target based on environment:\n\n```html\n<serc-rod id=\"env-ws\"\n         data='{\n           \"env\": \"prod\",\n           \"wsUrls\": {\n             \"dev\":  \"wss://dev.example.com/ws\",\n             \"prod\": \"wss://api.example.com/ws\"\n           }\n         }'\n         *websocket=\"wsUrls[env]\">\n\n  <button *ws-send=\"{ type: 'ping', env }\">\n    Ping current env\n  </button>\n</serc-rod>\n```\n\nIn this scenario:\n\n- `*websocket=\"wsUrls[env]\"` decides which URL to connect to based on `env`.\n- There is still only one WebSocket connection, so `*ws-to` is not needed.\n- If you later add a second connection (for notifications), you can add a new `*websocket` and a `*ws-to` on the notification buttons to target it explicitly.\n\nExplicit URL selection for three channels:\n\n```html\n<serc-rod id=\"multi3\"\n         data='{\n           \"chatUrl\":   \"wss://example.com/chat\",\n           \"notifyUrl\": \"wss://example.com/notify\",\n           \"metricsUrl\":\"wss://example.com/metrics\"\n         }'>\n  <span *websocket=\"chatUrl\"></span>\n  <span *websocket=\"notifyUrl\"></span>\n  <span *websocket=\"metricsUrl\"></span>\n\n  <button *ws-send=\"{ type: 'chat-ping' }\"\n          *ws-to=\"%chatUrl%\">\n    Ping chat\n  </button>\n\n  <button *ws-send=\"{ type: 'notify-ping' }\"\n          n-ws-to=\"%notifyUrl%\">\n    Ping notify\n  </button>\n\n  <button *ws-send=\"{ type: 'metrics-ping' }\"\n          *ws-to=\"%metricsUrl%\">\n    Ping metrics\n  </button>\n</serc-rod>\n```\n\nHere, `*ws-to` and `n-ws-to` both associate buttons with specific connections, and the behavior is independent of the internal ordering of WebSocket connections on the host.\n\n\n#### Notes\n\n- `*ws-to` and `n-ws-to` are aliases; choose one style per project for consistency.\n- `*ws-to` only has effect on `*ws-send`. Other directives ignore it.\n- Omitting `*ws-to` or `n-ws-to` is safe and recommended for hosts with a single WebSocket connection.\n- Adding `*ws-to` introduces an explicit dependency on URL strings; keeping those URLs in data rather than hardcoding them in attributes improves maintainability.\n- `*ws-to` does not affect reconnection policies or error handling; those are defined by `*websocket` and the surrounding application logic.",
  "adapters": "### Adapters\n\n#### Summary\n\nAdapters are bridges between Sercrod runtime behavior and an external environment. The core runtime owns host data, directive detection, expression evaluation, and DOM rendering. Platform-specific APIs, browser storage backends, native wrappers, build tools, and test runners belong behind adapters.\n\n#### Registry\n\nAdapters are registered under `window.__Sercrod.adapters`. Role selection lives in `window.__Sercrod.adapter_map`.\n\n```js\nSercrod.set_adapter(\"capacitor.filesystem\", adapter);\nSercrod.use_adapter(\"file\", \"capacitor.filesystem\");\nSercrod._get_adapter(\"file\");\n```\n\nThe file role is used by `*save` and `*load` before the browser fallback runs. If an adapter cannot handle a request, it should return `false` so Sercrod can continue with the default behavior. If the adapter handled the request, it should return any other value.\n\n#### Storage boundary\n\nDo not add new directive families only to name a storage backend. Use explicit action forms such as `*save.file`, `*load.file`, `*save.session`, `*load.session`, `*save.store`, and `*load.store`; put custom IndexedDB, OPFS, Capacitor Filesystem, or other backend behavior behind an adapter or explicit helper when the built-in forms are not enough.\n\nFor `load(context)`, the important bridge is still the normal Sercrod merge path. The file adapter reads from its backend, then passes JSON into that path. Built-in session and store forms use the same merge semantics after reading from browser storage.\n\n#### Related\n\n- `save`\n- `load`\n- `storage-backends`\n- `capacitor-filesystem-adapter`\n",
  "storage-backends": "### Storage Backends\n\n#### Summary\n\nSercrod storage backends describe where local data lives before `*save` exports it or `*load` restores it. The directive surface should stay small: use explicit action forms such as `*save.file`, `*load.file`, `*save.session`, `*load.session`, `*save.store`, and `*load.store` for user-facing actions, and use adapters or helpers for environment-specific work.\n\n#### IndexedDB\n\nIndexedDB is a good fit for JSON snapshots, metadata records, key-value records, and indexes. It can also store Blob values, but large payloads should normally be represented in host data only by a key and metadata. Use IndexedDB as the default when the payload type does not clearly require a file-like store.\n\n#### OPFS\n\nOPFS is useful for obvious image payloads, suspiciously large Blob/File values, larger app-local payloads, and file-like working directories. It is appropriate when the app needs a private workspace rather than a user-visible downloaded file. OPFS should normally pair with IndexedDB metadata or indexes: IndexedDB stores the key, backend name, path, MIME type, size, timestamps, and status; OPFS stores the file body.\n\n#### Relationship to *load\n\nThe value of `*load` is not that it knows every storage API. Its value is that all load sources can converge on the same merge behavior: read JSON or source text, pass it to the load apply path, merge into `_stage` or `_data`, dispatch `sercrod-loaded`, and update the host.\n\n#### Design Rule\n\nKeep host data JSON-like and render-friendly. Store large payloads outside host data. Keep keys, backend names, paths, names, MIME types, sizes, timestamps, and status fields in host data.\n\n#### Related\n\n- `load`\n- `save`\n- `adapters`\n",
  "save.file": "### *save / *save.file / *save.session / *save.store\n\n#### Summary\n\n`*save.file` exports the host data (or its staged view) as a JSON file in the browser.\n`*save.session` stores the same JSON payload in browser `sessionStorage`.\n`*save.store` stores the same JSON payload in persistent browser storage backed by IndexedDB.\nIt is typically used on a button inside a `<serc-rod>` host.\nWhen clicked, Sercrod collects the host’s current data and builds a JSON string. File saves start a download; session saves write that string under the storage key given by `*save.session`; store saves write it under the key given by `*save.store`.\n\nBy default, `*save` exports the entire host data.\nUse `*keys` to export only selected top-level properties.\n`*save` remains as the legacy-compatible file form, equivalent to `*save.file` when no storage suffix is used.\n\n\n#### Basic example\n\nSave the entire host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"name\":\"Alice\",\"age\":30}'>\n  <button *save.file>Download profile JSON</button>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<button>` is cloned and given a click handler by Sercrod.\n- When the button is clicked, Sercrod takes the host’s current data and serializes it to JSON.\n- A file named like `Sercrod-YYYYMMDD-HHMMSS.json` is generated and downloaded by the browser.\n\nSave selected keys to an explicit filename:\n\n```html\n<button type=\"button\" *save.file=\"'profile-backup.json'\" *keys=\"profile settings\">\n  Save profile\n</button>\n```\n\nIn this form:\n\n- `*save.file` describes the file destination and optional filename.\n- `*keys` describes which host data keys are saved.\n\nSave selected keys into the current browser session:\n\n```html\n<button type=\"button\" *save.session=\"'profile-draft'\" *keys=\"profile settings\">\n  Save session draft\n</button>\n```\n\nIn this form:\n\n- `*save.session` describes the `sessionStorage` key.\n- `*keys` selects which host data keys are serialized.\n- Omitting `*keys` saves the whole host data or stage.\n\nSave selected keys into persistent browser storage:\n\n```html\n<button type=\"button\" *save.store=\"'profile-draft'\" *keys=\"profile settings\">\n  Save persistent draft\n</button>\n```\n\nIn this form:\n\n- `*save.store` describes a persistent browser storage key.\n- Sercrod stores a JSON string in IndexedDB.\n- The value remains available after page reloads and browser restarts, subject to normal browser storage policy.\n\n\n#### Behavior\n\n- `*save.file` attaches a click handler to the element it is placed on.\n- `*save.session` attaches the same kind of click handler, but writes JSON to `window.sessionStorage`.\n- `*save.store` attaches the same kind of click handler, but writes JSON to IndexedDB.\n- The handler runs in the context of the surrounding Sercrod host and serializes:\n\n  - `this._stage` if it exists, otherwise\n  - `this._data`.\n\n- No network request is sent by `*save` itself.\n- The resulting JSON is written into a Blob, and a temporary `<a download>` element is used to trigger the browser’s download dialog.\n- For `*save.session`, the resulting JSON is stored with `sessionStorage.setItem(storageKey, json)`.\n- For `*save.store`, the resulting JSON is stored in the `sercrod-store` IndexedDB database by default.\n- After a successful save action, a `CustomEvent(\"sercrod-saved\")` is dispatched from the host for application-specific hooks.\n\nAliases and compatibility:\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain supported as the old file-save spelling.\n- In new examples, prefer explicit action forms such as `*save.file`, `*save.session`, or `*save.store` plus `*keys`.\n\n\n#### Data source and property selection\n\nData source:\n\n- `*save` always uses host-level data, not per-element scope variables.\n- On click:\n\n  - If the host has a staged buffer (`_stage`), `*save` reads from `_stage`.\n  - Otherwise, it reads from the committed data object `_data`.\n\nKey selection:\n\n- Without an attribute value:\n\n  - `*save.file` exports the entire data object (`_stage` or `_data`).\n  - `*save.session` and `*save.store` require a storage key value, but still save the entire data object when `*keys` is omitted.\n\n- With `*keys`:\n\n  - The `*keys` value is treated as a whitespace-separated list of top-level property names.\n  - These names are not expressions; they are taken as-is and are not evaluated.\n  - Only properties that exist on the data object are copied into a new object.\n\nExample (selective save):\n\n```html\n<serc-rod id=\"settings\" data='{\n  \"user\": { \"name\": \"Alice\", \"age\": 30 },\n  \"theme\": { \"mode\": \"dark\" },\n  \"debug\": true\n}'>\n  <!-- Only save \"user\" and \"theme\" from the host data -->\n  <button *save.file=\"'user-theme.json'\" *keys=\"user theme\">Download user+theme</button>\n</serc-rod>\n```\n\nIn this example:\n\n- `src` is `host._stage ?? host._data`.\n- If `*keys` is `\"user theme\"`, Sercrod builds:\n\n  - `data = { user: src.user, theme: src.theme }` (if those properties exist).\n\n- The JSON file contains only `user` and `theme` at the top level.\n- Nested paths (such as `user.name`) are not supported by `*keys` directly.\n\nOld spelling:\n\n```html\n<button *save=\"user theme\">Download user+theme</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax, not as a filename.\n\n\n#### Evaluation timing\n\nRender-time:\n\n- When Sercrod renders the host, it looks for elements with `*save`, `*save.file`, `*save.session`, `*save.store`, or their `n-` aliases.\n- For each such element:\n\n  - Sercrod clones the element.\n  - Attaches a click handler on the clone.\n  - Appends the clone to the parent.\n  - Returns from the element renderer without recursing into the children of that clone.\n\nClick-time:\n\n- When the user clicks the save button:\n\n  1. Sercrod resolves the action attribute.\n     - `*save.file` gives an optional filename.\n     - `*save.session` gives a `sessionStorage` key.\n     - `*save.store` gives a persistent browser storage key.\n     - legacy `*save` values are treated as old key-selection syntax.\n  2. Sercrod selects `src = this._stage ?? this._data` from the host.\n  3. It builds a plain object:\n\n     - Entire `src` if no `*keys` list was provided.\n     - A subset object if `*keys` was provided.\n\n  4. It serializes that object with `JSON.stringify(data, null, 2)`.\n  5. It writes the JSON to the selected destination: file download, `sessionStorage`, or IndexedDB-backed persistent browser storage.\n  6. It dispatches the `sercrod-saved` event from the host.\n\nBecause the JSON is built at click time, `*save` always reflects the current state of `_stage` or `_data` at the moment of the click.\n\n\n#### Execution model\n\nInternally, `*save` behaves as follows (conceptually):\n\n1. During render, Sercrod finds an element `work` with a save directive.\n2. Sercrod clones `work` into `el`.\n3. Sercrod attaches:\n\n   - `el.addEventListener(\"click\", () => { /* build JSON and save to the selected destination */ })`.\n\n4. Sercrod appends `el` to the parent node and returns, without processing `el`’s children for further Sercrod directives.\n\nOn click, the handler:\n\n1. Resolves the action attribute and `*keys`.\n2. Selects `src`:\n\n   - `src = host._stage ?? host._data`.\n\n3. Builds `data`:\n\n   - If there is a `*keys` list, `data` is a new object populated only with properties present in `src`.\n   - Otherwise, `data` is `src` itself.\n\n4. Serializes `data` with a pretty-printed `JSON.stringify(data, null, 2)`.\n5. If the action is `*save.store`, writes `{ key, json, updatedAt }` into the configured IndexedDB object store and returns.\n6. If the action is `*save.session`, writes the JSON string with `sessionStorage.setItem(storageKey, json)` and returns.\n7. Otherwise, creates a Blob of type `application/json`.\n8. Creates an `ObjectURL` and a temporary `<a>` element with:\n\n   - `href = url`.\n   - `download = \"Sercrod-YYYYMMDD-HHMMSS.json\"` (in the local time of the browser).\n\n9. Programmatically clicks the anchor to prompt download.\n10. Cleans up (removes the anchor from the DOM and revokes the `ObjectURL`).\n11. Dispatches `CustomEvent(\"sercrod-saved\", { detail: { ... } })` from the host.\n\n\n#### Use on nested elements and scope\n\n- `*save` must live inside a Sercrod host to be meaningful, since it reads from the host’s `_stage` or `_data`.\n- `*save` does not use per-element scope; it only uses the host’s data object.\n- Placing `*save` on a deeply nested element is allowed, but it still always saves the surrounding host’s data, not a subset scoped by `*for` or `*each`.\n\nIn other words:\n\n- The location of the `*save` button in the DOM tree does not change the data source.\n- It only changes where the button appears in the layout.\n\n\n#### Events\n\nAfter a successful save, Sercrod dispatches a bubbling, composed `CustomEvent` from the host:\n\n- Event type:\n\n  - `\"sercrod-saved\"`\n\n- Event detail structure:\n\n  - `detail.stage`: `\"save\"` for file/legacy saves, `\"save.session\"` for session saves, or `\"save.store\"` for store saves.\n  - `detail.host`: the Sercrod host element (`<serc-rod>` instance).\n  - `detail.fileName`: the file name used for file downloads (for example `\"Sercrod-20251205-093000.json\"`).\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file saves.\n  - `detail.storageKey`: the storage key for `*save.session` or `*save.store`.\n  - `detail.props`: the property list array if provided; `null` if no list was specified.\n  - `detail.keys`: the same property list array, provided for the unified action syntax.\n  - `detail.json`: the JSON string that was generated.\n\nExample hook:\n\n```js\ndocument.addEventListener(\"sercrod-saved\", (evt) => {\n  const { host, fileName, storage, storageKey, props, json } = evt.detail;\n  console.log(\"Saved from host:\", host.id);\n  console.log(\"File name:\", fileName);\n  console.log(\"Storage:\", storage, storageKey);\n  console.log(\"Props:\", props);\n  console.log(\"JSON preview:\", json.slice(0, 200));\n});\n```\n\nYou can use this event to:\n\n- Mirror the saved JSON to another storage or API when the built-in file/session/store destinations are not enough.\n- Show a toast notification after the download is triggered.\n- Log or audit save operations.\n\n\n#### Best practices\n\n- Treat `*save.file` elements as simple buttons:\n\n  - Because the renderer does not recursively process children of `*save` hosts after cloning, avoid placing other Sercrod directives inside the same element.\n  - Use plain text or static markup inside the button where possible.\n\n- Use `*keys` for focused exports:\n\n  - If your host data is large, consider exposing smaller subsets via:\n\n    - `*save.file=\"'profile.json'\" *keys=\"profile settings\"`\n    - `*save.file=\"'chart.json'\" *keys=\"chart filters\"`\n\n- Keep the root data export-friendly:\n\n  - Plan your top-level keys (`user`, `settings`, `rows`, `config`, and so on) so that it is easy to export meaningful subsets by name.\n\n- Combine with matching `*load` forms for round trips:\n\n  - Use `*save.file` and `*load.file` for user-visible JSON files.\n  - Use `*save.session` and `*load.session` for session-scoped browser storage.\n  - Use `*save.store` and `*load.store` for persistent browser storage.\n\n- Use `sercrod-saved` for integration:\n\n  - Attach listeners to `\"sercrod-saved\"` if you want to route the JSON elsewhere instead of or in addition to the download.\n\n\n#### Advanced - Using save forms with *stage, *apply, *restore, load forms, and *post\n\n`*save` is part of a broader data management workflow:\n\n- `*stage`:\n\n  - Enables a staged buffer `_stage` for the host (a working copy of the data).\n  - When `_stage` exists, `*save` prefers `_stage` over `_data`.\n  - This lets you export the staged view without committing it.\n\n- `*apply`:\n\n  - Copies `_stage` into `_data` and updates the host.\n  - Subsequent `*save` clicks, after `*apply`, will see the committed state in `_data`.\n\n- `*restore`:\n\n  - Rolls back `_stage` to the last snapshot, or to `_data` if no snapshot is available.\n  - After a restore, `*save` again sees whatever `_stage` currently holds.\n\n- `*load`:\n\n  - Reads JSON from a file, session storage, or persistent browser storage and merges it into `_stage` or `_data`.\n  - You can use matching `*load` forms to import JSON previously written by `*save` forms.\n\n- `*post`:\n\n  - Sends host data to a server as JSON over HTTP.\n  - `*save` is complementary to `*post`: one saves locally as a file, the other sends over the network.\n\nThe core rule is:\n\n- `*save` always targets “the current data view” of the host, prioritizing `_stage` when present.\n- This makes it safe to stage edits with `*stage`, try them out, export via `*save`, and later apply or restore as needed.\n\n\n#### Notes\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain old compatible file-save spellings.\n- In old syntax, the value of `*save` is parsed as plain text and split by whitespace as a key list.\n- When no property list is provided, the entire `_stage ?? _data` object is serialized.\n- When a property list is provided, only the listed top-level properties are included if they exist.\n- The file name is generated as `\"Sercrod-YYYYMMDD-HHMMSS.json\"` using the browser’s local time.\n- `*save` itself does not change `_stage` or `_data`; it is a read-only export operation.\n- There are no special structural restrictions specific to `*save` beyond the general behavior described above; it can be combined with directives such as `*if` on the same element, as long as you keep in mind that `*save` turns that element into a “save button” whose children are not further processed by Sercrod.\n",
  "load.file": "### *load / *load.file / *load.session / *load.store\n\n#### Summary\n\n`*load.file` loads JSON data from a user-selected file and merges it into the Sercrod host’s data.\n`*load.session` loads JSON from browser `sessionStorage` and applies the same merge rules.\n`*load.store` loads JSON from persistent browser storage backed by IndexedDB and applies the same merge rules.\nIf a staged view is active (via `*stage`), the JSON is merged into the stage; otherwise it is merged into the live data.\n`*load` remains as the legacy-compatible file form, equivalent to `*load.file` when no storage suffix is used.\n\nTypical use:\n\n- Put `*load.file` on a button or other clickable element.\n- Use `*load.session=\"'key'\"` when the source is a `sessionStorage` key.\n- Use `*load.store=\"'key'\"` when the source is persistent browser storage.\n- Optionally use `*keys` to choose top-level properties from the JSON.\n- Optionally use `*into` to place the loaded value under one host data key.\n- Sercrod reads JSON from the selected source, parses it, merges it into data or stage, and triggers a re-render.\n\n\n#### Basic example\n\nA simple load button that merges the entire JSON into host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"user\":{\"name\":\"\",\"email\":\"\"}}'>\n  <p>Name: <span *print=\"user.name\"></span></p>\n  <p>Email: <span *print=\"user.email\"></span></p>\n\n  <button type=\"button\" *load.file>Load profile…</button>\n</serc-rod>\n```\n\nIf the user selects a JSON file like:\n\n```json\n{\n  \"user\": {\n    \"name\": \"Alice\",\n    \"email\": \"alice@example.com\"\n  }\n}\n```\n\nthen after loading:\n\n- `user.name` becomes `\"Alice\"`.\n- `user.email` becomes `\"alice@example.com\"`.\n- The view is refreshed automatically.\n\nLoad selected keys into one destination:\n\n```html\n<button type=\"button\" *load.file *keys=\"profile settings\" *into=\"draft\">\n  Load draft\n</button>\n```\n\nIn this form:\n\n- `*load.file` describes the browser file source.\n- `*keys` selects keys from the loaded JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from the current browser session:\n\n```html\n<button type=\"button\" *load.session=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load session draft\n</button>\n```\n\nIn this form:\n\n- `*load.session` describes the `sessionStorage` key.\n- `*keys` selects keys from the stored JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from persistent browser storage:\n\n```html\n<button type=\"button\" *load.store=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load persistent draft\n</button>\n```\n\nIn this form:\n\n- `*load.store` describes the persistent browser storage key.\n- Sercrod reads JSON from IndexedDB.\n- `*keys` and `*into` use the same merge rules as file and session loads.\n\n\n#### Behavior\n\n- `*load.file` is an action directive that attaches file-loading behavior to an element.\n- `*load.session` is an action directive that reads JSON from `window.sessionStorage` on click.\n- `*load.store` is an action directive that reads JSON from IndexedDB on click.\n- The directive works with the browser’s file picker and uses `FileReader` to read the chosen file.\n- Only JSON is expected; other file types are not supported by the current implementation.\n- The directive merges the parsed JSON into:\n\n  - `_stage`, if the host has a staged view (for example due to `*stage`).\n  - `_data`, otherwise.\n\n- The merge strategy depends on `*keys` and `*into`:\n\n  - No `*keys`, no `*into`: merge the entire JSON object into the target object with `Object.assign`.\n  - `*keys`, no `*into`: copy only those top-level properties into the target object.\n  - `*into`: place the loaded value under that one target key.\n\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event and calls `update()` on the host to re-render the view.\n\nAliases and compatibility:\n\n- `*load.file` and `n-load.file` are aliases.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain supported as the old file-load spelling.\n- In new examples, prefer explicit action forms such as `*load.file`, `*load.session`, or `*load.store` plus `*keys` and `*into` where needed.\n\n\n#### Storage and merge semantics\n\nKey and destination values:\n\n- If `*keys` and `*into` are omitted:\n\n  - The parsed JSON must be an object.\n  - Sercrod merges all enumerable properties into `_stage` or `_data`:\n\n    - With stage: `Object.assign(this._stage, json)`\n    - Without stage: `Object.assign(this._data, json)`\n\n- If `*keys` has a value:\n\n  - The value is split by whitespace into a list of property names:\n\n    - `*keys=\"user settings\"`\n\n      becomes `[\"user\",\"settings\"]`.\n\n  - For each property name `p`:\n\n    - With stage: `this._stage[p] = json[p]`\n    - Without stage: `this._data[p] = json[p]`\n\n  - Only direct top-level keys are supported; dotted paths or nested selectors are not interpreted.\n\n- If `*into` has a value:\n\n  - Without `*keys`, the entire loaded JSON is assigned to `data[into]`.\n  - With one key, `json[key]` is assigned to `data[into]`.\n  - With multiple keys, an object containing those keys is assigned to `data[into]`.\n\nOld spelling:\n\n```html\n<button *load=\"user settings\">Load user+settings</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax.\n\nError handling:\n\n- The chosen file is read as text and parsed with `JSON.parse`.\n- `*load.session` warns and makes no data change when the storage key is empty, inaccessible, missing, or contains invalid JSON.\n- `*load.store` warns and makes no data change when IndexedDB is unavailable, the key is missing, or the stored JSON cannot be parsed.\n- If parsing fails and Sercrod is configured to warn, the runtime logs:\n\n  - `[Sercrod warn] *load JSON parse: ...`\n\n- On parse error, no merge is performed and the view is not updated.\n\n\n#### File input integration\n\n`*load.file` and legacy `*load` work both with native file inputs and with regular clickable elements.\n\n- If the element is an `<input type=\"file\">`:\n\n  - Sercrod reuses the native file input.\n  - If the input has no `accept` attribute, Sercrod sets it to `\"application/json\"` by default.\n  - On `change`, the first selected file is read and processed.\n\n- If the element is not an `<input type=\"file\">`:\n\n  - Sercrod attaches a `click` handler to the element.\n  - When clicked, Sercrod creates a hidden `<input type=\"file\">`, sets its `accept` attribute, and forwards the selection to `*load`.\n  - The temporary file input is not meant to be visible or controlled directly.\n\nAccept attribute:\n\n- If the element has an `accept` attribute, Sercrod respects it.\n- If not, Sercrod uses `\"application/json\"` as the default.\n- This influences what the browser shows in the file picker but does not perform additional runtime validation beyond JSON parsing.\n\n\n#### Stage interaction\n\n`*load` is designed to cooperate with staged editing:\n\n- If the host has an active stage (for example due to `*stage`), `*load` merges into `_stage` instead of `_data`.\n- This lets you preview or edit the loaded data in a staged view and then decide when to apply it.\n\nTypical pattern:\n\n```html\n<serc-rod id=\"editor\" data='{\"doc\":{\"title\":\"\",\"body\":\"\"}}'>\n  <section *stage>\n    <label>\n      Title:\n      <input *input=\"doc.title\">\n    </label>\n\n    <label>\n      Body:\n      <textarea *input=\"doc.body\"></textarea>\n    </label>\n\n    <button type=\"button\" *load.file *keys=\"doc\">Load draft…</button>\n    <button type=\"button\" *apply>Apply</button>\n    <button type=\"button\" *restore>Restore</button>\n  </section>\n</serc-rod>\n```\n\nIn this pattern:\n\n- `*load.file *keys=\"doc\"` replaces the staged `doc` object with `json.doc` from the file.\n- `*apply` copies staged changes back into the live data.\n- `*restore` discards staged changes and returns to the last stable state.\n\n\n#### Evaluation timing\n\n- `*load` is evaluated when Sercrod renders the element that carries it.\n- During rendering:\n\n  - Sercrod clones the original element.\n  - It attaches the necessary event listeners for the selected load source.\n  - It appends the cloned element to the DOM and returns from the internal render function.\n\n- `*load` does not perform any data changes during rendering itself.\n  - Data changes happen later, in response to user interaction.\n  - File loads react to file selection.\n  - Session and store loads react to click and then read from browser storage.\n- When JSON is successfully loaded and merged, Sercrod explicitly calls `update()` on the host to re-run the render pipeline and update the view.\n\n\n#### Execution model\n\nConceptually, the runtime behaves like this for `*load`:\n\n1. Sercrod detects a load directive on an element.\n2. It clones the element.\n\n   - All attributes and children are copied as-is.\n   - The `*load` / `n-load` attribute is preserved on the clone for visibility, but Sercrod does not re-interpret it later.\n\n3. It resolves the action attribute:\n\n   - `*load.file` means browser file input.\n   - `*load.session` gives a `sessionStorage` key.\n   - `*load.store` gives a persistent browser storage key.\n   - legacy `*load` values are treated as old `*keys` syntax.\n\n4. It parses `*keys` and `*into`.\n\n5. For file loads, it determines the desired `accept` type:\n\n   - Uses the element’s own `accept` attribute if present.\n   - Otherwise, defaults to `\"application/json\"`.\n\n6. It wires source handling:\n\n   - For `*load.store`, it attaches a click listener that reads the JSON string from IndexedDB.\n   - For `*load.session`, it attaches a click listener that reads the JSON string from `sessionStorage`.\n   - For file loads, if the cloned element is an `<input type=\"file\">`:\n\n     - Ensures `accept` is set.\n     - Adds a `change` listener that calls `handleFile(file)` for the selected file.\n\n   - For file loads on any other element (button, link, etc.):\n\n     - Adds a `click` listener.\n     - That listener creates a temporary `<input type=\"file\">`, sets `accept`, and listens for `change`.\n     - When a file is chosen, it calls `handleFile(file)`.\n\n7. The common JSON path:\n\n   - Reads JSON text from the selected source.\n   - Parses the JSON.\n   - Merges it into `_stage` or `_data` according to `*keys` and `*into`.\n   - Dispatches `sercrod-loaded` with source details.\n   - Calls `update()`.\n\n8. The cloned element is appended to the parent in the rendered DOM; the original template node is not appended.\n\n\n#### Variable creation\n\n`*load` does not create new template variables:\n\n- It does not add loop variables, local aliases, or special names to the scope.\n- All changes happen directly in the host’s `_data` (or `_stage`) object.\n- Templates and expressions continue to use the regular data paths (`user`, `settings`, etc.) after the data is updated.\n\n\n#### Scope layering\n\n`*load` respects the existing scope model:\n\n- It operates on the Sercrod host’s data or stage, not on local loop scopes.\n- It does not change how `$data`, `$root`, or `$parent` are injected.\n- After a successful load, any expressions that read from the updated data see the new values at the next render.\n\nBecause `*load` is an action on the host data, it does not affect how inner scopes are layered; it only changes the values they eventually read.\n\n\n#### Parent access\n\n`*load` does not introduce a new parent object:\n\n- Parent access via `$parent` and `$root` remains unchanged.\n- Any templates that use `$parent` or `$root` simply see updated data after the load and re-render, as long as they reference the affected fields.\n\n\n#### Use with conditionals and loops\n\nYou can place `*load` inside conditional blocks or loops just like any other action element:\n\n- Inside `*if`:\n\n  - The element exists and is interactive only when the `*if` condition is truthy.\n\n  ```html\n  <div *if=\"canLoad\">\n    <button type=\"button\" *load.file>Load config…</button>\n  </div>\n  ```\n\n- Inside loops:\n\n  - Each iteration can have its own `*load` element, although typically you want just one loader per host.\n\n  ```html\n  <serc-rod data='{\"sections\":[{\"id\":1},{\"id\":2}]}'>\n    <section *each=\"section of sections\">\n      <h2 *print=\"section.id\"></h2>\n      <button type=\"button\" *load.file *keys=\"section\">Load section…</button>\n    </section>\n  </serc-rod>\n  ```\n\nRestrictions:\n\n- `*load` is not a structural directive and does not control how many times an element is rendered.\n- It is best used for standalone controls (buttons, links, inputs) rather than for elements that also carry structural directives like `*for` or `*each`.\n- Combining `*load` with other action directives that also replace the element (such as `*save`, `*post`, or `*fetch`) on the same element is not recommended:\n\n  - Only one branch in the internal evaluation order will run.\n  - Other directives on the same element will effectively be ignored.\n  - Use separate elements if you need multiple actions.\n\n\n#### Best practices\n\n- Use dedicated controls:\n\n  - Attach `*load` to buttons or file inputs specifically intended for loading data.\n  - Avoid mixing `*load` with other unrelated behaviors on the same element.\n\n- Keep the JSON shape predictable:\n\n  - Decide on a stable JSON schema for exports and imports (for example, via `*save`).\n  - Document which top-level properties exist (`user`, `settings`, etc.).\n\n- Use `*keys` for partial updates:\n\n  - When you want to protect unrelated data from being overwritten, specify only the properties you want to import:\n\n    - `*load.file *keys=\"user settings\"`\n\n- Combine with staged editing:\n\n  - Pair `*load` with `*stage`, `*apply`, and `*restore` to allow safe previewing of loaded data before committing.\n\n- Keep `*load` elements structurally simple:\n\n  - The element with `*load` is cloned and used as-is.\n  - Avoid relying on nested Sercrod directives inside the `*load` element itself; keep its content mostly static (plain text or icons).\n\n- Validate externally if needed:\n\n  - `*load` does basic JSON parsing only.\n  - If you require more validation (schema checks, versioning), perform it in code that reacts to `sercrod-loaded`.\n\n\n#### Storage backends and adapters\n\nThe default `*load.file` path is intentionally simple: open a file picker, read JSON text, parse it, merge it into `_stage` or `_data`, dispatch `sercrod-loaded`, and update the host. `*load.session` and `*load.store` use the same merge step after reading JSON text from browser storage.\n\nThat does not mean `*load` is only a file-picker feature. The important boundary is the merge step. Built-in session/store forms or a file adapter can read JSON from another local source, then pass the parsed value into the same load path.\n\nUseful browser storage patterns:\n\n- IndexedDB:\n\n  - Good for JSON snapshots, metadata, key-value records, and indexes.\n  - Good for remembering which saved item should be loaded later.\n  - Used by the built-in `*save.store` / `*load.store` JSON store.\n  - Can also store Blob values, but host data should normally keep only a key and metadata.\n  - Use it as the default when the payload type does not clearly require a file-like store.\n\n- OPFS:\n\n  - Good for larger app-local payloads and file-like working directories.\n  - Good when the application needs an internal workspace rather than a user-visible downloaded file.\n  - Usually pairs well with IndexedDB metadata or indexes.\n  - Prefer it for obvious image payloads and suspiciously large Blob/File values, with IndexedDB metadata as the lookup record.\n\nRecommended data shape:\n\n- Keep host data JSON-like and render-friendly.\n- Store large payloads outside host data.\n- Keep keys, names, MIME types, sizes, timestamps, and status fields in host data.\n- Let `*load` restore the JSON state or source record that the template actually reads.\n\nDo not create more backend-named directives just to name a storage backend. Prefer the action family (`*save.file`, `*save.session`, `*save.store`, and matching load forms), and put backend-specific behavior behind adapters or explicit helpers.\n\n\n#### Examples\n\nFull data import:\n\n```html\n<serc-rod id=\"app\" data='{\"config\":{\"theme\":\"light\",\"lang\":\"en\"}}'>\n  <pre *literal=\"JSON.stringify(config, null, 2)\"></pre>\n  <button type=\"button\" *load.file>Load config…</button>\n</serc-rod>\n```\n\nPartial import:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{},\"settings\":{}}'>\n  <button type=\"button\" *load.file *keys=\"user settings\">\n    Load user and settings\n  </button>\n</serc-rod>\n```\n\nCustom accept type on a native file input:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{}}'>\n  <input type=\"file\" accept=\"application/json,.json\" *load.file *keys=\"user\">\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*load.file` and `n-load.file` are aliases; choose one style for consistency.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain old compatible file-load spellings.\n- `*load.file` is designed for browser environments where `FileReader` and file dialogs are available.\n- `*load.store` requires IndexedDB.\n- The directive expects JSON text; other content types will fail JSON parsing.\n- When JSON parsing fails and warnings are enabled, Sercrod logs a warning and does not modify data.\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event:\n\n  - `detail.stage`: `\"load\"` for file/legacy loads, `\"load.session\"` for session loads, or `\"load.store\"` for store loads.\n  - `detail.host`: the Sercrod host element\n  - `detail.fileName`: the selected file name (or `null`)\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file loads.\n  - `detail.storageKey`: the storage key for `*load.session` or `*load.store`.\n  - `detail.into`: the `*into` destination key, or `null`.\n  - `detail.props`: the property list used for partial merge (or `null`)\n  - `detail.keys`: the same property list, provided for the unified action syntax.\n  - `detail.json`: the parsed JSON object\n\n  You can listen to this event on the host to perform additional validation or side effects.\n\n- For clarity and maintainability, avoid combining `*load` with other I/O directives (`*save`, `*post`, `*fetch`) on the same element; use separate elements for each distinct action.\n",
  "keys": "### *keys\n\n#### Summary\n\n`*keys` selects top-level host data keys for action directives.\n\nIt is used when the action target is already described by another directive:\n\n```html\n<button *save.file=\"'backup.json'\" *keys=\"profile settings\">Save</button>\n<button *load.file *keys=\"profile\" *into=\"draft\">Load</button>\n<button *save.session=\"'draft'\" *keys=\"profile settings\">Save session</button>\n<button *load.session=\"'draft'\" *keys=\"profile\" *into=\"draft\">Load session</button>\n<button *save.store=\"'draft'\" *keys=\"profile settings\">Save store</button>\n<button *load.store=\"'draft'\" *keys=\"profile\" *into=\"draft\">Load store</button>\n<button *websocket.send=\"wsUrl\" *keys=\"message\">Send</button>\n```\n\nIn these examples:\n\n- `*save.file`, `*load.file`, `*save.session`, `*load.session`, `*save.store`, `*load.store`, and `*websocket.send` describe where the action goes.\n- `*keys` describes what data is used.\n- Omitting `*keys` means the action uses the whole host data or staged data where that is appropriate.\n\n\n#### Rules\n\n- Keys are whitespace-separated top-level names.\n- Nested paths are not interpreted by `*keys`.\n- `n-keys` is the alias for `*keys`.\n- Old `*save=\"profile settings\"` and `*load=\"profile settings\"` still work, but new examples should use `*keys`.\n\n\n#### Related\n\n- `*save.file`\n- `*load.file`\n- `*save.session`\n- `*load.session`\n- `*save.store`\n- `*load.store`\n- `*websocket.send`\n- `*into`\n",
  "websocket.send": "### *ws-send\n\n#### Summary\n\n`*websocket.send` sends selected host data through a WebSocket connection managed by the same Sercrod host.\nIt is an action directive for regular elements (for example `<button>`, `<a>`, or `<input>`).\n\nFor new code, prefer:\n\n```html\n<button *websocket.send=\"wsUrl\" *keys=\"message\">Send</button>\n```\n\nIn this form:\n\n- `*websocket.send` selects the WebSocket connection URL.\n- `*keys` selects the host data to send.\n\n`*ws-send` and `*ws-to` remain supported as the old compatible spelling, but they are no longer the preferred form in new examples.\n\nUse `*ws-send` only when documenting or maintaining old code that already uses it.\n\n\n#### Basic example\n\nA basic setup with a host-level WebSocket and a button that sends one host data key:\n\n```html\n<serc-rod id=\"app\"\n         data='{\"wsUrl\":\"wss://example.com/ws\",\"message\":\"hello\"}'\n         *websocket=\"wsUrl\">\n  <button *websocket.send=\"wsUrl\" *keys=\"message\">\n    Send\n  </button>\n</serc-rod>\n```\n\nBehavior:\n\n- `<serc-rod>` connects to `wss://example.com/ws` because of `*websocket=\"wsUrl\"`.\n- When the button is clicked, Sercrod resolves `wsUrl` as the send target.\n- `*keys=\"message\"` selects `data.message`.\n- The resulting value `\"hello\"` is sent through that WebSocket connection.\n- The button’s content (\"Send\") is rendered as usual; `*websocket.send` only adds behavior.\n\n\n#### Behavior\n\n- `*websocket.send` is an action directive that:\n\n  - Clones the element (without its children) and appends the clone to the DOM.\n  - Attaches a click handler when the element is considered clickable (button-like).\n  - On click, resolves the WebSocket URL from the directive value.\n  - Builds the payload from `*keys`.\n  - Sends the evaluated value as a WebSocket message.\n\n- The directive does not create or manage WebSocket connections by itself.\n  It reuses connections opened by:\n\n  - The host-level `*websocket` on `<serc-rod>` or another Sercrod host.\n  - The element-level `*websocket` on specific elements.\n  - The imperative `this.websocket.connect(...)` helper from scripts.\n\n- Alias:\n\n  - `*websocket.send` and `n-websocket.send` are aliases and behave identically.\n\n- Old spelling:\n\n  - `*ws-send` and `n-ws-send` remain supported for compatibility.\n  - `*ws-to` / `n-ws-to` remain supported only for the old spelling.\n  - In old spelling, `*ws-send` means payload and `*ws-to` means target URL.\n\n\n#### Clickability and event wiring\n\nThe directive decides whether to attach its own click handler based on the cloned element’s tag and type:\n\n- `BUTTON` always counts as clickable.\n- `A` counts as clickable when it does not have a `download` attribute.\n- `INPUT` is clickable when `type` is one of `\"button\"`, `\"submit\"`, or `\"reset\"`.\n\nFor those clickable elements:\n\n- `*ws-send` adds its own click handler that sends the message.\n- Event attributes using the configured prefix (by default `@`) such as `@click`, `@mousedown`, and so on are still respected.\n  Sercrod scans all attributes, and for each one whose name starts with the event prefix, it calls the internal event binder.\n\nThis means:\n\n- You can combine `*ws-send` with `@click` on the same element.\n- Both handlers run: `*ws-send` performs its send, and your `@click` expression is evaluated as usual.\n\n\n#### Expression and payload evaluation\n\nThe attribute value of `*ws-send` is an expression that is evaluated at click time, not at render time:\n\n- On each click, Sercrod calls:\n\n  - `this.eval_expr(expr, scope, { el: work, mode: \"ws-send\", $event: e })`\n\n- Inside the expression you can access:\n\n  - All data in the current scope (for example properties from `data`, `stage`, or `*methods`).\n  - `$data` (this host’s data object).\n  - `$root` (root host’s data, if any).\n  - `$parent` (nearest ancestor Sercrod host’s data).\n  - `el` (the original template element that declared `*ws-send`).\n  - `$event` (the click event object).\n\nPayload conversion:\n\n- After evaluating the expression, `_ws_send` converts the value before sending:\n\n  - If the value is an object:\n\n    - It attempts `JSON.stringify(payload)`.\n    - If that fails, it falls back to `String(payload)`.\n\n  - For non-object values:\n\n    - It uses `String(payload)`.\n\n- The final message sent over the WebSocket is always a string.\n- On the receiving side, `*websocket` will attempt to parse incoming string messages as JSON when they look like arrays or objects (`\"{}\"` or `\"[]\"` style). This makes sending objects via `*ws-send` a natural choice.\n\n\n#### Target selection with the `*ws-to` attribute\n\n`*ws-send` can send messages either to a specific WebSocket URL or to the first open connection it finds.\n\n- Optional `*ws-to` attribute:\n\n  - If present, its value is treated as a text template and resolved via Sercrod’s text expansion logic.\n  - Internally, the directive uses:\n\n    - `this._expand_text(toRaw, scope, work)`\n\n  - This supports the standard placeholder mechanisms (such as `%path.to.url%` or other configured `%...%` forms), as defined by Sercrod’s `_expand_text`.\n\n- Target resolution logic:\n\n  - If `*ws-to` is present and its expanded value is a non-empty string:\n\n    - `_ws_send` looks up a WebSocket in the internal map by that URL.\n    - If there is an open connection for that URL (`readyState === WebSocket.OPEN`), the message is sent to that connection.\n    - Otherwise, the send is skipped.\n\n  - If `*ws-to` is absent or its expanded value is empty:\n\n    - `_ws_send` iterates over the internal map of WebSocket holders.\n    - It picks the first connection whose `readyState` is `WebSocket.OPEN`.\n    - If no open connection is found, the send is skipped.\n\nImportant notes:\n\n- If no connection is open, `*ws-send` does nothing; there is no built-in error dialog.\n- If multiple connections are open and `*ws-to` is not provided, the actual target is whichever open connection is first in the internal map.\n  This order is not a stable contract and should not be relied on.\n  For multiple connections, always use `*ws-to` to select a URL explicitly.\n\n\n#### Evaluation timing\n\n`*ws-send` participates in the rendering pipeline as follows:\n\n- During rendering:\n\n  - Sercrod sees the `*ws-send` or `n-ws-send` attribute.\n  - It clones the element, appends the clone to the parent, and attaches the click handler if the element is clickable.\n  - It also processes event attributes (such as `@click`) on the original element and binds them to the clone.\n\n- During user interaction:\n\n  - The expression in `*ws-send` is evaluated only when the element is clicked.\n  - The `*ws-to` attribute (if present) is also evaluated through text expansion at click time, so it can reflect the current state via `%...%` placeholders.\n\n- Updates:\n\n  - Re-renders of the Sercrod host recreate the element and its event handlers.\n  - The send behavior is always driven by clicks, not by data changes alone.\n\n\n#### Execution model\n\nConceptually, the directive behaves like this:\n\n1. Rendering:\n\n   - Detect `*ws-send` or `n-ws-send` on a node `work`.\n   - Clone the element without children: `el = work.cloneNode(false)`.\n   - Append the clone to the parent.\n   - Read the raw `*ws-to` value once:\n\n     - `const toRaw = work.getAttribute(\"*ws-to\") || \"\";`\n\n   - Compute clickability based on `el`’s tag and type.\n   - If clickable, attach a click listener:\n\n     - On click:\n\n       - Evaluate the expression in the attribute to obtain `payload`.\n       - Resolve the `*ws-to` attribute by calling `_expand_text(toRaw, scope, work)` to obtain `targetUrl` (which may be empty).\n       - Call `_ws_send(targetUrl, payload)`.\n\n   - For each attribute whose name starts with the event prefix (for example `@`), bind the corresponding event handler to `el`.\n   - Render all original child nodes into `el` using the usual rendering rules.\n\n2. Sending:\n\n   - `_ws_send(urlOrEmpty, payload)` selects a target WebSocket:\n\n     - If `urlOrEmpty` is non-empty, look up that URL in the internal map of connections.\n     - Otherwise, scan for the first open WebSocket.\n\n   - If no open target is found, return `false` and do not send anything.\n   - Convert `payload` to a string as described above.\n   - Call `ws.send(out)` on the selected WebSocket.\n   - Return `true` if the send operation succeeds, `false` if it throws.\n\nThe return value from `_ws_send` is not used by the directive itself but is exposed through the `this.websocket.send(...)` helper for more advanced, imperative control from scripts.\n\n\n#### Scope layering and variables\n\n`*ws-send` does not create new scope variables of its own, but its expression runs inside the normal Sercrod scope:\n\n- Available in the expression:\n\n  - All properties from the current evaluation scope (data, stage, and so on).\n  - `$data`, `$root`, `$parent`.\n  - Methods provided via `*methods` or injected internal helpers.\n  - `el` (the original template element).\n  - `$event` (the click `MouseEvent` or `PointerEvent` or similar).\n\n- The directive does not add new special variables other than passing `el` and `$event` to `eval_expr`.\n- Because expressions execute in the standard sandbox, any restrictions that apply to other directives also apply to `*ws-send`.\n\n\n#### Use with *websocket and WebSocket metrics\n\n`*ws-send` is designed to be used together with `*websocket` and the WebSocket status fields maintained in the data object:\n\n- WebSocket connections:\n\n  - Host-level `*websocket` on `<serc-rod>` can automatically connect once at initialization or when you call `update(true)`.\n  - Element-level `*websocket` on, for example, a button, can connect when that element is clicked or immediately if it is not clickable.\n  - Both forms ultimately use `_ws_connect` and share a common map of live connections on the host.\n\n- Status fields:\n\n  - When you use `*websocket`, Sercrod keeps several fields on the data object:\n\n    - `$ws_ready`: `true` when at least the last connection is open and ready, `false` otherwise.\n    - `$ws_error`: last error message, or `null`.\n    - `$ws_last`: last received message (after optional JSON parsing).\n    - `$ws_messages`: array of all received messages for this host.\n    - `$ws_closed_at`: timestamp of the last close event.\n    - `$ws_close_code`: last close code, or `null`.\n    - `$ws_close_reason`: last close reason, or `null`.\n\n  - `*ws-send` itself does not change these fields directly.\n    They change as a result of connection open, message, error, and close events handled by `*websocket`.\n\nPractical pattern:\n\n- Disable send buttons when no connection is ready:\n\n  ```html\n  <serc-rod id=\"app\"\n           data='{\"wsUrl\":\"wss://example.com/ws\",\"message\":\"ping\"}'\n           *websocket=\"wsUrl\">\n    <button *ws-send=\"message\"\n            :disabled=\"!$ws_ready\">\n      Send ping\n    </button>\n  </serc-rod>\n  ```\n\n\n#### Best practices\n\n- Prefer object payloads:\n\n  - Sending objects like `{ type: \"ping\", data: { id: 1 } }` works well because:\n\n    - `*ws-send` serializes objects with `JSON.stringify`.\n    - `*websocket` attempts to parse JSON-looking text on receive.\n\n  - This gives a symmetric request/response shape.\n\n- Use `*ws-to` when multiple connections are in play:\n\n  - If your host manages multiple WebSocket URLs, always set `*ws-to` to avoid ambiguity:\n\n    - For example `*ws-to=\"%primaryUrl%\"` or `*ws-to=\"%notificationsUrl%\"`.\n\n  - Do not rely on the internal ordering of the connection map.\n\n- Keep expressions simple:\n\n  - Derive complex payloads via helper functions or precomputed data.\n\n    - For example: `*ws-send=\"buildPayload($event)\"` with a `buildPayload` function defined via `*methods`.\n\n- Handle offline or closed states in the UI:\n\n  - Use `$ws_ready` and `$ws_error` to enable or disable buttons and show status messages instead of relying on the send result.\n\n- Combine with `@click` when you need local side effects:\n\n  - You can use `@click` to update local state and still let `*ws-send` handle the actual send.\n\n\n#### Additional examples\n\nSending a structured JSON payload:\n\n```html\n<serc-rod id=\"chat\"\n         data='{\n           \"wsUrl\": \"wss://example.com/chat\",\n           \"draft\": \"\"\n         }'\n         *websocket=\"wsUrl\">\n  <input type=\"text\"\n         :value=\"draft\"\n         @input=\"draft = $event.target.value\">\n  <button *ws-send=\"{ type: 'chat', text: draft }\"\n          :disabled=\"!$ws_ready || !draft\">\n    Send message\n  </button>\n</serc-rod>\n```\n\nSelecting a specific connection with `*ws-to`:\n\n```html\n<serc-rod id=\"multi\"\n         data='{\n           \"primaryUrl\": \"wss://example.com/primary\",\n           \"secondaryUrl\": \"wss://example.com/secondary\"\n         }'\n         *websocket=\"primaryUrl\">\n  <button *ws-send=\"{ type: 'ping' }\">\n    Ping (default connection)\n  </button>\n\n  <button *ws-send=\"{ type: 'ping-secondary' }\"\n          *ws-to=\"%secondaryUrl%\">\n    Ping secondary\n  </button>\n</serc-rod>\n```\n\nIn this pattern, you would typically have an element-level `*websocket` somewhere that connects to `secondaryUrl`.\n`*ws-send` simply routes messages to the appropriate connection based on `*ws-to`.\n\n\n#### Notes\n\n- `*ws-send` and `n-ws-send` are aliases; choose one style for consistency.\n- The directive sends only when at least one WebSocket is open and ready; otherwise, it silently does nothing (aside from returning `false` internally).\n- The message is always sent as a string; objects are serialized with `JSON.stringify`.\n- `*ws-send` does not itself attempt reconnection or error handling.\n  Connection lifecycle is handled by `*websocket` and internal helpers like `_ws_connect` and `_ws_clear_retry_flags`.\n- When combining `*ws-send` with other directives on the same element:\n\n  - It is safe to combine it with attribute bindings (such as `:class`, `:disabled`) and event bindings (such as `@click`).\n  - There are no special structural conflicts like those between `*each` and `*include` or `*import`, because `*ws-send` does not take ownership of child structure.\n- The `*ws-to` attribute is purely a targeting helper for `*ws-send`; it does not affect where received messages are stored.\n  Received messages are still controlled by `*websocket` and its `*into` configuration.",
  "grammar": "# Grammar\n\n_What you will find:_ a formal description of directive value shapes and handler syntax as implemented in Sercrod v1.06. This page is normative, concise, and tutorial-free.\n\n## 0. Legend (tokens and notation)\n\n- `Expr` - a JavaScript expression evaluated by the runtime (statements are out of scope).\n- `Ident` - a JavaScript identifier (ASCII suggestion; Unicode identifiers follow JS rules).\n- `String` - an attribute string literal shown as `\"...\"` here (actual HTML quoting rules apply).\n- `LValue` - a writable property reference inside host data:  \n  `LValue ::= Ident { ('.' Ident) | ('[' Expr ']') }`\n- `Mods` - event modifier list: zero or more `.`-prefixed modifier names.\n- `Element(attr)` - an element node that declares the attribute described by `attr`.\n- `{ ... }` means repeat zero or more times. `[ ... ]` means optional. Literal commas are required where shown.\n- All directive names are lowercase and case-sensitive.\n- Every `*name` directive has an equivalent `n-name` form (not repeated below).\n- Notation: this document mixes EBNF-style `{}` and `[]` with the BNF-style definition symbol `::=`.\n\n## 1. Common rules\n\n- Attribute values are parsed as strings and then interpreted per rule below.\n- Whitespace around separators is permitted unless a rule says otherwise.\n- Truthiness and comparison use JavaScript semantics unless noted (for example, `*switch` cases use `===`).\n- Value-less directives are written without `=\"...\"` and are equivalent to the presence of the flag.\n\n## 2. Control flow\n\n```\nIfChain           ::= IfHead { ElseIf } [ Else ]\nIfHead            ::= Element('*if=\"' Expr '\"')\nElseIf            ::= Element('*elseif=\"' Expr '\"')\nElse              ::= Element('*else')\n\nSwitchGroup       ::= SwitchHead { CaseClause }\nSwitchHead        ::= Element('*switch=\"' Expr '\"')\nCaseClause        ::= Element('*case=\"' Expr '\"')\n                    | Element('*case.break=\"' Expr '\"')\n                    | Element('*default')\n```\n\nConstraints:\n- `IfChain` is formed by contiguous sibling elements under the same parent: one `IfHead`, followed by zero or more `ElseIf`, and at most one `Else`. The chain ends at the first sibling that is not `ElseIf` or `Else`.\n- `SwitchGroup` is formed by one `SwitchHead`, followed by contiguous sibling elements that are any of `CaseClause`. The group ends at the first sibling that is not a `CaseClause`.\n- Matching in `SwitchGroup` uses strict equality (`===`).\n\n## 3. Iteration\n\n```\nEach              ::= '*each=\"' Expr ' as ' Ident [ ',' Ident ] '\"'\nFor               ::= '*for=\"' Expr '\"'\n```\n\nNotes:\n- In `Each`, the first `Ident` binds the item. The optional second `Ident` binds the index or key.\n\n## 4. Scope and variables\n\n```\nLet               ::= '*let=\"' AssignList '\"'\nGlobal            ::= '*global=\"' AssignList '\"'\nLiteral           ::= '*literal=\"' Expr '\"'\nRem               ::= '*rem=\"' String '\"'\n\nAssignList        ::= Assign { ',' Assign }\nAssign            ::= LHS '=' Expr\nLHS               ::= LValue\n```\n\nNotes:\n- During `*let` evaluation, `$parent` is injected (see expressions reference).\n\n## 5. Input and staging\n\n```\nInput             ::= '*input=\"' LValue '\"'\nLazy              ::= '*lazy'\nEager             ::= '*eager'\nStage             ::= '*stage'\nApply             ::= '*apply'\nRestore           ::= '*restore'\nSaveFile          ::= '*save.file' [ '=\"' Expr '\"' ]\nSaveSession       ::= '*save.session=\"' Expr '\"'\nSaveStore         ::= '*save.store=\"' Expr '\"'\nLoadFile          ::= '*load.file'\nLoadSession       ::= '*load.session=\"' Expr '\"'\nLoadStore         ::= '*load.store=\"' Expr '\"'\nKeys              ::= '*keys=\"' IdentList '\"'\nSaveLegacy        ::= '*save'\nLoadLegacy        ::= '*load'\n```\n\nNotes:\n- `*input` requires an `LValue` (pure expressions are not valid here).\n\n## 6. Output\n\n```\nPrint             ::= '*print=\"' Expr '\"'\nCompose           ::= '*compose=\"' Expr '\"'\nTextCont          ::= '*textContent=\"' Expr '\"'\nInnerHTML         ::= '*innerHTML=\"' Expr '\"'\n```\n\n## 7. Attributes and shorthands\n\n```\nClassBind         ::= ':class=\"' Expr '\"'   | 'n-class=\"' Expr '\"'\nStyleBind         ::= ':style=\"' Expr '\"'   | 'n-style=\"' Expr '\"'\n```\n\n## 8. Communication (HTTP/API)\n\n```\nFetchEmpty        ::= '*fetch'\nFetchExpr         ::= '*fetch=\"' Expr '\"'\nPost              ::= '*post=\"' Expr '\"'\nApi               ::= '*api=\"' Expr '\"'\nInto              ::= '*into=\"' Ident '\"'\n```\n\nNotes:\n- `*into` accepts identifier only. Dynamic names via expressions are not supported.\n\n## 9. Files\n\n```\nUpload            ::= '*upload=\"' Expr '\"'\nDownload          ::= '*download=\"' Expr '\"'\n```\n\n## 10. WebSocket\n\n```\nWebSocket         ::= '*websocket=\"' Expr '\"'\nWebSocketSend     ::= '*websocket.send=\"' Expr '\"'\nWsSendLegacy      ::= '*ws-send=\"' Expr '\"'\n```\n\nNotes:\n- `Expr` resolves to a URL string or an implementation-defined config object.\n- `*websocket.send` resolves to a target URL; `*keys` selects the payload.\n- `*ws-send` is old compatible spelling.\n\n## 11. Events and modifiers\n\n```\nHandler           ::= '@' EvName Mods? '=\"' Expr '\"'\nEvName            ::= Name\nName              ::= ASCII letter followed by { ASCII letter | digit | '-' | ':' }\nMods              ::= { '.' Mod }\nMod               ::= 'prevent' | 'stop' | 'once' | 'capture' | 'passive' | 'update' | 'noupdate'\n```\n\nNotes:\n- `EvName` must not contain `.` (dot) - dots are reserved for modifiers.\n- Modifiers are order-independent. Duplicates are ignored.\n- `update` and `noupdate` are mutually exclusive.\n\n## 12. Strict mode\n\n`*strict` is a compact and rigid call syntax. The runtime performs a single-pass tokenizer with tight validation. The grammar and constraints below reflect the implementation.\n\n### 12.1 Grammar\n\n```\nStrict            ::= '*strict=\"' StrictCall '\"'\n\nStrictCall        ::= FnName '(' [ ArgList ] [ Callback ] ')'\n\nFnName            ::= Letter { Letter | Digit | '_' }\nArgList           ::= Arg { ',' Arg }\nArg               ::= IdentLike\nIdentLike         ::= [A-Za-z0-9_]+    // must not start with '_'\n\nCallback          ::= ',' '()=>{' CbBody '}' \n                    | ',' '(' [WS] Param [WS] ')' [WS] '=>' [WS] '{' CbBody '}'\n\nParam             ::= IdentLike        // must not start with '_'\nCbBody            ::= any chars with balanced braces, length-limited\nWS                ::= space or tab characters only\n\n// Whitespace rules - see 12.2\n```\n\n### 12.2 Whitespace and ordering rules\n\n- No leading whitespace at the beginning of the `*strict` value.\n- No whitespace between `FnName` and `(`. Example: `fn(` is valid, `fn (` is invalid.\n- No whitespace inside `ArgList`. The following are valid: `fn(a,b,c)` and `fn()`. The following are invalid: `fn(a, b)`, `fn( a,b )`.\n- The optional callback must be introduced with a comma immediately after the last argument. Only two forms are accepted:\n  - `,()=>{ ... }` - no whitespace between comma and `()=>`.\n  - `,(x)=>{ ... }` - no whitespace between comma and `(`. Inside this callback header, limited whitespace is allowed at the marked `WS` positions: around the parameter and around `=>`, and before `{`.\n- After the callback block, optional whitespace is allowed before the final `)`.\n- After the final `)`, no trailing characters are allowed.\n\n### 12.3 Identifier and argument rules\n\n- `FnName` must start with a letter. It can contain letters, digits, and underscore. It must not start with `_`.\n- Each `Arg` token consists of one or more characters in `[A-Za-z0-9_]`. It must not start with `_`.\n- Property access and indexing are not allowed in arguments. Dots and brackets are rejected. For example: `user` is allowed, `user.name` and `user['name']` are invalid.\n- Each argument name is resolved from the current effective scope. If the resolved value is a function, the call is rejected for that argument.\n\n### 12.4 Limits and errors\n\n- Maximum total expression length: 256 characters. Exceeding this fails fast.\n- Maximum number of arguments: 16.\n- Maximum callback body length: 2048 characters.\n- An empty argument between commas is an error.\n- Unbalanced or malformed callback braces are errors.\n- Unrecognized characters in `FnName` or arguments are errors.\n\n### 12.5 Resolution order\n\nThe function is resolved in this order:\n1. `this.fnRegistry[FnName]`\n2. `this.constructor.strictRegistry[FnName]`\n3. `Sercrod.strictRegistry[FnName]` (shared reference to `window.__Sercrod_strict`)\n4. Compatibility fallback: `this.constructor.fnRegistry[FnName]`\n5. Final fallback: `globalThis[FnName]`\n\n### 12.6 Examples\n\nValid:\n- `*strict=\"doThing(a,b)\"`  \n- `*strict=\"doThing()\"`  \n- `*strict=\"doThing(a,()=>{ x = a })\"`  \n- `*strict=\"doThing(a,(x)=>{ x += 1 })\"`\n\nInvalid:\n- `*strict=\" doThing(a)\"` - leading space\n- `*strict=\"doThing (a)\"` - space before `(`\n- `*strict=\"doThing(a, b)\"` - space inside argument list\n- `*strict=\"doThing(a, () => { })\"` - spaces after comma and around `=>`\n- `*strict=\"doThing(user.name)\"` - property access not allowed\n- `*strict=\"doThing(_a)\"` - identifier starts with underscore\n- `*strict=\"\"` - empty value\n\n## 13. Shorthand equivalence\n\n- Every directive appears in two equivalent spellings: `*name` and `n-name`.  \n  Example: `*if=\"Expr\"` ⇔ `n-if=\"Expr\"`.\n\n## 14. Non-goals of this grammar\n\n- It does not describe expression semantics, filters, scheduler behavior, or event payloads.  \n  See: [Expressions](./expressions.md), [Filters](./filters.md), [Lifecycle](./lifecycle.md), and [Events](./events.md).\n\n---\nBack to index: [`README.md`](./README.md)\n",
  "lifecycle": "# Lifecycle Directives\n\nLifecycle directives control how data flows over time in a Sercrod host: staged edits, saving and loading, HTTP requests, file transfer, and real-time connections.\n\nThis page groups lifecycle-related directives by purpose. For the host update lifecycle and render order, see [Runtime Behavior](runtime.md).\n\n## Staging Edits\n\nStaging directives let a user edit a temporary branch and then explicitly commit or discard those changes.\n\n- `*stage` marks a subtree as working on staged data instead of the main data.\n- `*apply` commits staged values back to the main data.\n- `*restore` discards staged changes and restores from the previous main data.\n\n```html\n<serc-rod\n\tid=\"profile-app\"\n\tdata='{\"profile\":{\"name\":\"Taro\",\"email\":\"taro@example.com\"}}'\n>\n\t<form *stage>\n\t\t<label>\n\t\t\tName:\n\t\t\t<input type=\"text\" :value=\"profile.name\">\n\t\t</label>\n\n\t\t<label>\n\t\t\tEmail:\n\t\t\t<input type=\"email\" :value=\"profile.email\">\n\t\t</label>\n\n\t\t<button type=\"button\" *apply>Save</button>\n\t\t<button type=\"button\" *restore>Reset</button>\n\t</form>\n\n\t<p>Current name: %profile.name%</p>\n\t<p>Current email: %profile.email%</p>\n</serc-rod>\n```\n\nUse staging when a user should be able to edit a complex form and then explicitly commit or discard changes.\n\n## Saving And Loading\n\n`*save.file`, `*load.file`, `*save.session`, `*load.session`, `*save.store`, and `*load.store` coordinate host data with local persistence.\n\n- `*save.file` serializes host data and triggers a download.\n- `*load.file` loads JSON from a user-selected file into host data.\n- `*save.session` and `*load.session` use browser `sessionStorage`.\n- `*save.store` and `*load.store` use IndexedDB-backed persistent browser storage.\n- `*keys` selects which top-level keys are included.\n\nThe runtime dispatches `sercrod-loaded` and `sercrod-saved` hooks for successful operations.\n\n```html\n<serc-rod data='{\"todos\":[]}'>\n\t<ul>\n\t\t<li *for=\"item of todos\">%item.label%</li>\n\t</ul>\n\n\t<button type=\"button\" *load.file>Load</button>\n\t<button type=\"button\" *save.file>Save</button>\n\t<button type=\"button\" *save.store=\"'todos'\" *keys=\"todos\">Save store</button>\n\t<button type=\"button\" *load.store=\"'todos'\" *keys=\"todos\">Load store</button>\n</serc-rod>\n```\n\nUse save/load when state should be exported, restored, or kept locally in the browser by explicit user action.\n\n## HTTP Lifecycle\n\nHTTP directives coordinate requests and how responses enter host data.\n\n- `*fetch` loads JSON-like data from a URL.\n- `*post` sends host data or staged data as JSON.\n- `*api` is the more general HTTP primitive for method, body, upload, status, and `*into`.\n- `*into` selects the response destination key.\n\nFor request timing and response shape contracts, see [Network Contracts](network-contracts.md).\n\n## File Transfer\n\n`*upload` and `*download` provide file transfer behavior.\n\n- `*upload` binds an element as an upload trigger and sends selected files.\n- `*download` downloads a resource or generated response.\n\nThe runtime exposes progress and completion through custom events:\n\n- `sercrod-upload-start`\n- `sercrod-upload-progress`\n- `sercrod-uploaded`\n- `sercrod-download-start`\n- `sercrod-downloaded`\n- `sercrod-error`\n\nUpload results may be stored in `$upload` and an explicit `*into` key. `*download` does not write result data into the host; it emits download events.\n\n## Real-Time Lifecycle\n\nWebSocket directives connect a host to a WebSocket server.\n\n- `*websocket` opens and manages the connection.\n- `*ws-send` sends a payload through the active connection.\n- `*ws-to` selects a target WebSocket URL when multiple connections are present.\n\nWebSocket state is reflected through data fields such as `$ws_ready`, `$ws_last`, `$ws_messages`, `$ws_error`, and close information.\n\nSee [WebSocket](websocket.md).\n\n## Choosing Lifecycle Directives\n\n- Use staging directives for edit-then-commit forms.\n- Use save/load for user-visible export and restore flows.\n- Use HTTP directives for JSON endpoints and API operations.\n- Use file directives for upload and download operations.\n- Use WebSocket directives for live updates and bidirectional communication.\n",
  "websocket-reference": "# WebSocket\n\n_What you will find:_ normative behavior for `*websocket` and `*websocket.send`, including connection lifecycle, exposed state, events, and host helper APIs. No tutorials are included.\n\n## Scope\n\nThis page defines:\n- When a connection is created and closed.\n- What data fields are updated on the host.\n- Which events are emitted and with what timing.\n- How payloads are parsed and sent.\n\n---\n\n## `*websocket=\"urlOrConfigExpr\"`\n\n- **When**\n  - Evaluated on render.\n  - Re-evaluated when its parameters change or the host is force-updated.\n- **Action**\n  - Opens a WebSocket connection.\n  - If a connection is already open with different parameters, the existing one is closed and a new one is opened.\n- **Value**\n  - `urlOrConfigExpr` can be a string URL or a configuration object understood by the implementation. At minimum, a URL string is supported.\n\n### Host data fields\n\nWhile active, the host maintains the following reactive fields:\n\n- `$ws_ready` - boolean, `true` when the socket is open.\n- `$ws_error` - last error object or descriptive value, `null` if none.\n- `$ws_last` - last received message payload. If JSON is valid, it is the parsed object, otherwise a string or binary representation as supported.\n- `$ws_messages` - array of received payloads in chronological order. Implementation may cap its length.\n- `$ws_closed_at` - timestamp of last close event, or `null`.\n- `$ws_close_code` - numeric close code from the last close event, or `null`.\n- `$ws_close_reason` - string reason from the last close event, or `\"\"`.\n\nThese fields are updated by the directive in response to socket events and are part of the normal update cycle.\n\n### Events\n\nThe host dispatches the following CustomEvents:\n\n- `sercrod-ws-before-connect` - right before attempting to connect. `detail` includes `{ host, url }`.\n- `sercrod-ws-open` - after the socket reaches OPEN. `detail` includes `{ host, url }`.\n- `sercrod-ws-message` - on each incoming message. `detail` includes `{ host, url, raw, body }` where `body` is JSON-parsed if possible.\n- `sercrod-ws-error` - on socket error. `detail` includes `{ host, url, error }`.\n- `sercrod-ws-close` - on close. `detail` includes `{ host, url, code, reason, wasClean }`.\n\nEvent names and timings are stable. Payload shapes may include additional implementation-defined fields.\n\n### Parsing of incoming messages\n\n- If the message text parses as JSON, the directive sets `$ws_last` to the parsed object and pushes it into `$ws_messages`.\n- Otherwise it uses the raw text string. Binary frames are handled according to the implementation - at minimum they are exposed as raw data objects or ArrayBuffers.\n\n### Re-connection policy\n\n- The directive does not imply automatic retries unless the implementation specifies otherwise.\n- Programmatic reconnection can be performed through the host helper API described below.\n\n---\n\n## `*websocket.send=\"urlExpr\"` With `*keys`\n\n- **When**\n  - Evaluated on click for button-like elements.\n- **Action**\n  - Resolves `urlExpr` to a WebSocket URL and sends selected host data through that connection.\n- **Payload rules**\n  - `*keys=\"message\"` sends `data.message`.\n  - `*keys=\"a b\"` sends `{ a: data.a, b: data.b }`.\n  - Omitting `*keys` sends the whole host data or staged data.\n  - Objects and arrays are serialized to JSON.\n  - If the value is a string, it is sent as-is.\n  - Binary payloads are supported if the value is an ArrayBuffer or a view type the implementation accepts.\n\nIf no socket is currently open, the send is a no-op or results in a warning depending on configuration.\n\nOld spelling:\n\n- `*ws-send=\"payloadExpr\"` remains supported.\n- `*ws-to=\"urlTemplate\"` remains supported as the old target selector for `*ws-send`.\n- New examples should prefer `*websocket.send` plus `*keys`.\n\n---\n\n## Host helper API\n\nWhen `*websocket` is present, the host exposes a helper under the element for direct control:\n\n- `el.websocket.connect(urlOrConfig?)` - opens a connection, optionally overriding parameters.\n- `el.websocket.reconnect()` - closes and reopens using the last known parameters.\n- `el.websocket.close(code?, reason?)` - closes the current connection.\n- `el.websocket.send(value)` - sends a payload, applying the same serialization rules as `*websocket.send`.\n- `el.websocket.status()` - returns an object describing the current state `{ ready, url, last_url, last_error, last_close }`.\n- `el.websocket.urls` - an object or record of last resolved URL values as maintained by the implementation.\n\nHelper presence and exact shapes are stable at the names above. Additional helper fields may exist.\n\n---\n\n## Parameter changes and multiple hosts\n\n- Re-evaluating `*websocket` with a new URL or configuration closes the previous connection and opens a new one, updating state fields accordingly.\n- Multiple Sercrod hosts can maintain independent sockets. State is not shared across hosts.\n\n---\n\n## Error handling\n\n- Errors set `$ws_error` and dispatch `sercrod-ws-error`.\n- After an error or close, state fields reflect the last known values. `$ws_ready` becomes `false`.\n- Sending while not ready has no effect or logs a warning.\n\n---\n\n## Cleanup and lifecycle\n\n- On host disconnection, the implementation may close the socket or leave it to the browser. If closed, the standard close event is dispatched and state fields update.\n- No transient keys related to WebSocket are auto-cleared in finalization, except those that are explicitly documented as transient elsewhere. WebSocket state fields persist until the next change.\n\n---\nNext page: [`world.md`](./world.md)",
  "save.session": "### *save / *save.file / *save.session / *save.store\n\n#### Summary\n\n`*save.file` exports the host data (or its staged view) as a JSON file in the browser.\n`*save.session` stores the same JSON payload in browser `sessionStorage`.\n`*save.store` stores the same JSON payload in persistent browser storage backed by IndexedDB.\nIt is typically used on a button inside a `<serc-rod>` host.\nWhen clicked, Sercrod collects the host’s current data and builds a JSON string. File saves start a download; session saves write that string under the storage key given by `*save.session`; store saves write it under the key given by `*save.store`.\n\nBy default, `*save` exports the entire host data.\nUse `*keys` to export only selected top-level properties.\n`*save` remains as the legacy-compatible file form, equivalent to `*save.file` when no storage suffix is used.\n\n\n#### Basic example\n\nSave the entire host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"name\":\"Alice\",\"age\":30}'>\n  <button *save.file>Download profile JSON</button>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<button>` is cloned and given a click handler by Sercrod.\n- When the button is clicked, Sercrod takes the host’s current data and serializes it to JSON.\n- A file named like `Sercrod-YYYYMMDD-HHMMSS.json` is generated and downloaded by the browser.\n\nSave selected keys to an explicit filename:\n\n```html\n<button type=\"button\" *save.file=\"'profile-backup.json'\" *keys=\"profile settings\">\n  Save profile\n</button>\n```\n\nIn this form:\n\n- `*save.file` describes the file destination and optional filename.\n- `*keys` describes which host data keys are saved.\n\nSave selected keys into the current browser session:\n\n```html\n<button type=\"button\" *save.session=\"'profile-draft'\" *keys=\"profile settings\">\n  Save session draft\n</button>\n```\n\nIn this form:\n\n- `*save.session` describes the `sessionStorage` key.\n- `*keys` selects which host data keys are serialized.\n- Omitting `*keys` saves the whole host data or stage.\n\nSave selected keys into persistent browser storage:\n\n```html\n<button type=\"button\" *save.store=\"'profile-draft'\" *keys=\"profile settings\">\n  Save persistent draft\n</button>\n```\n\nIn this form:\n\n- `*save.store` describes a persistent browser storage key.\n- Sercrod stores a JSON string in IndexedDB.\n- The value remains available after page reloads and browser restarts, subject to normal browser storage policy.\n\n\n#### Behavior\n\n- `*save.file` attaches a click handler to the element it is placed on.\n- `*save.session` attaches the same kind of click handler, but writes JSON to `window.sessionStorage`.\n- `*save.store` attaches the same kind of click handler, but writes JSON to IndexedDB.\n- The handler runs in the context of the surrounding Sercrod host and serializes:\n\n  - `this._stage` if it exists, otherwise\n  - `this._data`.\n\n- No network request is sent by `*save` itself.\n- The resulting JSON is written into a Blob, and a temporary `<a download>` element is used to trigger the browser’s download dialog.\n- For `*save.session`, the resulting JSON is stored with `sessionStorage.setItem(storageKey, json)`.\n- For `*save.store`, the resulting JSON is stored in the `sercrod-store` IndexedDB database by default.\n- After a successful save action, a `CustomEvent(\"sercrod-saved\")` is dispatched from the host for application-specific hooks.\n\nAliases and compatibility:\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain supported as the old file-save spelling.\n- In new examples, prefer explicit action forms such as `*save.file`, `*save.session`, or `*save.store` plus `*keys`.\n\n\n#### Data source and property selection\n\nData source:\n\n- `*save` always uses host-level data, not per-element scope variables.\n- On click:\n\n  - If the host has a staged buffer (`_stage`), `*save` reads from `_stage`.\n  - Otherwise, it reads from the committed data object `_data`.\n\nKey selection:\n\n- Without an attribute value:\n\n  - `*save.file` exports the entire data object (`_stage` or `_data`).\n  - `*save.session` and `*save.store` require a storage key value, but still save the entire data object when `*keys` is omitted.\n\n- With `*keys`:\n\n  - The `*keys` value is treated as a whitespace-separated list of top-level property names.\n  - These names are not expressions; they are taken as-is and are not evaluated.\n  - Only properties that exist on the data object are copied into a new object.\n\nExample (selective save):\n\n```html\n<serc-rod id=\"settings\" data='{\n  \"user\": { \"name\": \"Alice\", \"age\": 30 },\n  \"theme\": { \"mode\": \"dark\" },\n  \"debug\": true\n}'>\n  <!-- Only save \"user\" and \"theme\" from the host data -->\n  <button *save.file=\"'user-theme.json'\" *keys=\"user theme\">Download user+theme</button>\n</serc-rod>\n```\n\nIn this example:\n\n- `src` is `host._stage ?? host._data`.\n- If `*keys` is `\"user theme\"`, Sercrod builds:\n\n  - `data = { user: src.user, theme: src.theme }` (if those properties exist).\n\n- The JSON file contains only `user` and `theme` at the top level.\n- Nested paths (such as `user.name`) are not supported by `*keys` directly.\n\nOld spelling:\n\n```html\n<button *save=\"user theme\">Download user+theme</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax, not as a filename.\n\n\n#### Evaluation timing\n\nRender-time:\n\n- When Sercrod renders the host, it looks for elements with `*save`, `*save.file`, `*save.session`, `*save.store`, or their `n-` aliases.\n- For each such element:\n\n  - Sercrod clones the element.\n  - Attaches a click handler on the clone.\n  - Appends the clone to the parent.\n  - Returns from the element renderer without recursing into the children of that clone.\n\nClick-time:\n\n- When the user clicks the save button:\n\n  1. Sercrod resolves the action attribute.\n     - `*save.file` gives an optional filename.\n     - `*save.session` gives a `sessionStorage` key.\n     - `*save.store` gives a persistent browser storage key.\n     - legacy `*save` values are treated as old key-selection syntax.\n  2. Sercrod selects `src = this._stage ?? this._data` from the host.\n  3. It builds a plain object:\n\n     - Entire `src` if no `*keys` list was provided.\n     - A subset object if `*keys` was provided.\n\n  4. It serializes that object with `JSON.stringify(data, null, 2)`.\n  5. It writes the JSON to the selected destination: file download, `sessionStorage`, or IndexedDB-backed persistent browser storage.\n  6. It dispatches the `sercrod-saved` event from the host.\n\nBecause the JSON is built at click time, `*save` always reflects the current state of `_stage` or `_data` at the moment of the click.\n\n\n#### Execution model\n\nInternally, `*save` behaves as follows (conceptually):\n\n1. During render, Sercrod finds an element `work` with a save directive.\n2. Sercrod clones `work` into `el`.\n3. Sercrod attaches:\n\n   - `el.addEventListener(\"click\", () => { /* build JSON and save to the selected destination */ })`.\n\n4. Sercrod appends `el` to the parent node and returns, without processing `el`’s children for further Sercrod directives.\n\nOn click, the handler:\n\n1. Resolves the action attribute and `*keys`.\n2. Selects `src`:\n\n   - `src = host._stage ?? host._data`.\n\n3. Builds `data`:\n\n   - If there is a `*keys` list, `data` is a new object populated only with properties present in `src`.\n   - Otherwise, `data` is `src` itself.\n\n4. Serializes `data` with a pretty-printed `JSON.stringify(data, null, 2)`.\n5. If the action is `*save.store`, writes `{ key, json, updatedAt }` into the configured IndexedDB object store and returns.\n6. If the action is `*save.session`, writes the JSON string with `sessionStorage.setItem(storageKey, json)` and returns.\n7. Otherwise, creates a Blob of type `application/json`.\n8. Creates an `ObjectURL` and a temporary `<a>` element with:\n\n   - `href = url`.\n   - `download = \"Sercrod-YYYYMMDD-HHMMSS.json\"` (in the local time of the browser).\n\n9. Programmatically clicks the anchor to prompt download.\n10. Cleans up (removes the anchor from the DOM and revokes the `ObjectURL`).\n11. Dispatches `CustomEvent(\"sercrod-saved\", { detail: { ... } })` from the host.\n\n\n#### Use on nested elements and scope\n\n- `*save` must live inside a Sercrod host to be meaningful, since it reads from the host’s `_stage` or `_data`.\n- `*save` does not use per-element scope; it only uses the host’s data object.\n- Placing `*save` on a deeply nested element is allowed, but it still always saves the surrounding host’s data, not a subset scoped by `*for` or `*each`.\n\nIn other words:\n\n- The location of the `*save` button in the DOM tree does not change the data source.\n- It only changes where the button appears in the layout.\n\n\n#### Events\n\nAfter a successful save, Sercrod dispatches a bubbling, composed `CustomEvent` from the host:\n\n- Event type:\n\n  - `\"sercrod-saved\"`\n\n- Event detail structure:\n\n  - `detail.stage`: `\"save\"` for file/legacy saves, `\"save.session\"` for session saves, or `\"save.store\"` for store saves.\n  - `detail.host`: the Sercrod host element (`<serc-rod>` instance).\n  - `detail.fileName`: the file name used for file downloads (for example `\"Sercrod-20251205-093000.json\"`).\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file saves.\n  - `detail.storageKey`: the storage key for `*save.session` or `*save.store`.\n  - `detail.props`: the property list array if provided; `null` if no list was specified.\n  - `detail.keys`: the same property list array, provided for the unified action syntax.\n  - `detail.json`: the JSON string that was generated.\n\nExample hook:\n\n```js\ndocument.addEventListener(\"sercrod-saved\", (evt) => {\n  const { host, fileName, storage, storageKey, props, json } = evt.detail;\n  console.log(\"Saved from host:\", host.id);\n  console.log(\"File name:\", fileName);\n  console.log(\"Storage:\", storage, storageKey);\n  console.log(\"Props:\", props);\n  console.log(\"JSON preview:\", json.slice(0, 200));\n});\n```\n\nYou can use this event to:\n\n- Mirror the saved JSON to another storage or API when the built-in file/session/store destinations are not enough.\n- Show a toast notification after the download is triggered.\n- Log or audit save operations.\n\n\n#### Best practices\n\n- Treat `*save.file` elements as simple buttons:\n\n  - Because the renderer does not recursively process children of `*save` hosts after cloning, avoid placing other Sercrod directives inside the same element.\n  - Use plain text or static markup inside the button where possible.\n\n- Use `*keys` for focused exports:\n\n  - If your host data is large, consider exposing smaller subsets via:\n\n    - `*save.file=\"'profile.json'\" *keys=\"profile settings\"`\n    - `*save.file=\"'chart.json'\" *keys=\"chart filters\"`\n\n- Keep the root data export-friendly:\n\n  - Plan your top-level keys (`user`, `settings`, `rows`, `config`, and so on) so that it is easy to export meaningful subsets by name.\n\n- Combine with matching `*load` forms for round trips:\n\n  - Use `*save.file` and `*load.file` for user-visible JSON files.\n  - Use `*save.session` and `*load.session` for session-scoped browser storage.\n  - Use `*save.store` and `*load.store` for persistent browser storage.\n\n- Use `sercrod-saved` for integration:\n\n  - Attach listeners to `\"sercrod-saved\"` if you want to route the JSON elsewhere instead of or in addition to the download.\n\n\n#### Advanced - Using save forms with *stage, *apply, *restore, load forms, and *post\n\n`*save` is part of a broader data management workflow:\n\n- `*stage`:\n\n  - Enables a staged buffer `_stage` for the host (a working copy of the data).\n  - When `_stage` exists, `*save` prefers `_stage` over `_data`.\n  - This lets you export the staged view without committing it.\n\n- `*apply`:\n\n  - Copies `_stage` into `_data` and updates the host.\n  - Subsequent `*save` clicks, after `*apply`, will see the committed state in `_data`.\n\n- `*restore`:\n\n  - Rolls back `_stage` to the last snapshot, or to `_data` if no snapshot is available.\n  - After a restore, `*save` again sees whatever `_stage` currently holds.\n\n- `*load`:\n\n  - Reads JSON from a file, session storage, or persistent browser storage and merges it into `_stage` or `_data`.\n  - You can use matching `*load` forms to import JSON previously written by `*save` forms.\n\n- `*post`:\n\n  - Sends host data to a server as JSON over HTTP.\n  - `*save` is complementary to `*post`: one saves locally as a file, the other sends over the network.\n\nThe core rule is:\n\n- `*save` always targets “the current data view” of the host, prioritizing `_stage` when present.\n- This makes it safe to stage edits with `*stage`, try them out, export via `*save`, and later apply or restore as needed.\n\n\n#### Notes\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain old compatible file-save spellings.\n- In old syntax, the value of `*save` is parsed as plain text and split by whitespace as a key list.\n- When no property list is provided, the entire `_stage ?? _data` object is serialized.\n- When a property list is provided, only the listed top-level properties are included if they exist.\n- The file name is generated as `\"Sercrod-YYYYMMDD-HHMMSS.json\"` using the browser’s local time.\n- `*save` itself does not change `_stage` or `_data`; it is a read-only export operation.\n- There are no special structural restrictions specific to `*save` beyond the general behavior described above; it can be combined with directives such as `*if` on the same element, as long as you keep in mind that `*save` turns that element into a “save button” whose children are not further processed by Sercrod.\n",
  "load.session": "### *load / *load.file / *load.session / *load.store\n\n#### Summary\n\n`*load.file` loads JSON data from a user-selected file and merges it into the Sercrod host’s data.\n`*load.session` loads JSON from browser `sessionStorage` and applies the same merge rules.\n`*load.store` loads JSON from persistent browser storage backed by IndexedDB and applies the same merge rules.\nIf a staged view is active (via `*stage`), the JSON is merged into the stage; otherwise it is merged into the live data.\n`*load` remains as the legacy-compatible file form, equivalent to `*load.file` when no storage suffix is used.\n\nTypical use:\n\n- Put `*load.file` on a button or other clickable element.\n- Use `*load.session=\"'key'\"` when the source is a `sessionStorage` key.\n- Use `*load.store=\"'key'\"` when the source is persistent browser storage.\n- Optionally use `*keys` to choose top-level properties from the JSON.\n- Optionally use `*into` to place the loaded value under one host data key.\n- Sercrod reads JSON from the selected source, parses it, merges it into data or stage, and triggers a re-render.\n\n\n#### Basic example\n\nA simple load button that merges the entire JSON into host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"user\":{\"name\":\"\",\"email\":\"\"}}'>\n  <p>Name: <span *print=\"user.name\"></span></p>\n  <p>Email: <span *print=\"user.email\"></span></p>\n\n  <button type=\"button\" *load.file>Load profile…</button>\n</serc-rod>\n```\n\nIf the user selects a JSON file like:\n\n```json\n{\n  \"user\": {\n    \"name\": \"Alice\",\n    \"email\": \"alice@example.com\"\n  }\n}\n```\n\nthen after loading:\n\n- `user.name` becomes `\"Alice\"`.\n- `user.email` becomes `\"alice@example.com\"`.\n- The view is refreshed automatically.\n\nLoad selected keys into one destination:\n\n```html\n<button type=\"button\" *load.file *keys=\"profile settings\" *into=\"draft\">\n  Load draft\n</button>\n```\n\nIn this form:\n\n- `*load.file` describes the browser file source.\n- `*keys` selects keys from the loaded JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from the current browser session:\n\n```html\n<button type=\"button\" *load.session=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load session draft\n</button>\n```\n\nIn this form:\n\n- `*load.session` describes the `sessionStorage` key.\n- `*keys` selects keys from the stored JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from persistent browser storage:\n\n```html\n<button type=\"button\" *load.store=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load persistent draft\n</button>\n```\n\nIn this form:\n\n- `*load.store` describes the persistent browser storage key.\n- Sercrod reads JSON from IndexedDB.\n- `*keys` and `*into` use the same merge rules as file and session loads.\n\n\n#### Behavior\n\n- `*load.file` is an action directive that attaches file-loading behavior to an element.\n- `*load.session` is an action directive that reads JSON from `window.sessionStorage` on click.\n- `*load.store` is an action directive that reads JSON from IndexedDB on click.\n- The directive works with the browser’s file picker and uses `FileReader` to read the chosen file.\n- Only JSON is expected; other file types are not supported by the current implementation.\n- The directive merges the parsed JSON into:\n\n  - `_stage`, if the host has a staged view (for example due to `*stage`).\n  - `_data`, otherwise.\n\n- The merge strategy depends on `*keys` and `*into`:\n\n  - No `*keys`, no `*into`: merge the entire JSON object into the target object with `Object.assign`.\n  - `*keys`, no `*into`: copy only those top-level properties into the target object.\n  - `*into`: place the loaded value under that one target key.\n\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event and calls `update()` on the host to re-render the view.\n\nAliases and compatibility:\n\n- `*load.file` and `n-load.file` are aliases.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain supported as the old file-load spelling.\n- In new examples, prefer explicit action forms such as `*load.file`, `*load.session`, or `*load.store` plus `*keys` and `*into` where needed.\n\n\n#### Storage and merge semantics\n\nKey and destination values:\n\n- If `*keys` and `*into` are omitted:\n\n  - The parsed JSON must be an object.\n  - Sercrod merges all enumerable properties into `_stage` or `_data`:\n\n    - With stage: `Object.assign(this._stage, json)`\n    - Without stage: `Object.assign(this._data, json)`\n\n- If `*keys` has a value:\n\n  - The value is split by whitespace into a list of property names:\n\n    - `*keys=\"user settings\"`\n\n      becomes `[\"user\",\"settings\"]`.\n\n  - For each property name `p`:\n\n    - With stage: `this._stage[p] = json[p]`\n    - Without stage: `this._data[p] = json[p]`\n\n  - Only direct top-level keys are supported; dotted paths or nested selectors are not interpreted.\n\n- If `*into` has a value:\n\n  - Without `*keys`, the entire loaded JSON is assigned to `data[into]`.\n  - With one key, `json[key]` is assigned to `data[into]`.\n  - With multiple keys, an object containing those keys is assigned to `data[into]`.\n\nOld spelling:\n\n```html\n<button *load=\"user settings\">Load user+settings</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax.\n\nError handling:\n\n- The chosen file is read as text and parsed with `JSON.parse`.\n- `*load.session` warns and makes no data change when the storage key is empty, inaccessible, missing, or contains invalid JSON.\n- `*load.store` warns and makes no data change when IndexedDB is unavailable, the key is missing, or the stored JSON cannot be parsed.\n- If parsing fails and Sercrod is configured to warn, the runtime logs:\n\n  - `[Sercrod warn] *load JSON parse: ...`\n\n- On parse error, no merge is performed and the view is not updated.\n\n\n#### File input integration\n\n`*load.file` and legacy `*load` work both with native file inputs and with regular clickable elements.\n\n- If the element is an `<input type=\"file\">`:\n\n  - Sercrod reuses the native file input.\n  - If the input has no `accept` attribute, Sercrod sets it to `\"application/json\"` by default.\n  - On `change`, the first selected file is read and processed.\n\n- If the element is not an `<input type=\"file\">`:\n\n  - Sercrod attaches a `click` handler to the element.\n  - When clicked, Sercrod creates a hidden `<input type=\"file\">`, sets its `accept` attribute, and forwards the selection to `*load`.\n  - The temporary file input is not meant to be visible or controlled directly.\n\nAccept attribute:\n\n- If the element has an `accept` attribute, Sercrod respects it.\n- If not, Sercrod uses `\"application/json\"` as the default.\n- This influences what the browser shows in the file picker but does not perform additional runtime validation beyond JSON parsing.\n\n\n#### Stage interaction\n\n`*load` is designed to cooperate with staged editing:\n\n- If the host has an active stage (for example due to `*stage`), `*load` merges into `_stage` instead of `_data`.\n- This lets you preview or edit the loaded data in a staged view and then decide when to apply it.\n\nTypical pattern:\n\n```html\n<serc-rod id=\"editor\" data='{\"doc\":{\"title\":\"\",\"body\":\"\"}}'>\n  <section *stage>\n    <label>\n      Title:\n      <input *input=\"doc.title\">\n    </label>\n\n    <label>\n      Body:\n      <textarea *input=\"doc.body\"></textarea>\n    </label>\n\n    <button type=\"button\" *load.file *keys=\"doc\">Load draft…</button>\n    <button type=\"button\" *apply>Apply</button>\n    <button type=\"button\" *restore>Restore</button>\n  </section>\n</serc-rod>\n```\n\nIn this pattern:\n\n- `*load.file *keys=\"doc\"` replaces the staged `doc` object with `json.doc` from the file.\n- `*apply` copies staged changes back into the live data.\n- `*restore` discards staged changes and returns to the last stable state.\n\n\n#### Evaluation timing\n\n- `*load` is evaluated when Sercrod renders the element that carries it.\n- During rendering:\n\n  - Sercrod clones the original element.\n  - It attaches the necessary event listeners for the selected load source.\n  - It appends the cloned element to the DOM and returns from the internal render function.\n\n- `*load` does not perform any data changes during rendering itself.\n  - Data changes happen later, in response to user interaction.\n  - File loads react to file selection.\n  - Session and store loads react to click and then read from browser storage.\n- When JSON is successfully loaded and merged, Sercrod explicitly calls `update()` on the host to re-run the render pipeline and update the view.\n\n\n#### Execution model\n\nConceptually, the runtime behaves like this for `*load`:\n\n1. Sercrod detects a load directive on an element.\n2. It clones the element.\n\n   - All attributes and children are copied as-is.\n   - The `*load` / `n-load` attribute is preserved on the clone for visibility, but Sercrod does not re-interpret it later.\n\n3. It resolves the action attribute:\n\n   - `*load.file` means browser file input.\n   - `*load.session` gives a `sessionStorage` key.\n   - `*load.store` gives a persistent browser storage key.\n   - legacy `*load` values are treated as old `*keys` syntax.\n\n4. It parses `*keys` and `*into`.\n\n5. For file loads, it determines the desired `accept` type:\n\n   - Uses the element’s own `accept` attribute if present.\n   - Otherwise, defaults to `\"application/json\"`.\n\n6. It wires source handling:\n\n   - For `*load.store`, it attaches a click listener that reads the JSON string from IndexedDB.\n   - For `*load.session`, it attaches a click listener that reads the JSON string from `sessionStorage`.\n   - For file loads, if the cloned element is an `<input type=\"file\">`:\n\n     - Ensures `accept` is set.\n     - Adds a `change` listener that calls `handleFile(file)` for the selected file.\n\n   - For file loads on any other element (button, link, etc.):\n\n     - Adds a `click` listener.\n     - That listener creates a temporary `<input type=\"file\">`, sets `accept`, and listens for `change`.\n     - When a file is chosen, it calls `handleFile(file)`.\n\n7. The common JSON path:\n\n   - Reads JSON text from the selected source.\n   - Parses the JSON.\n   - Merges it into `_stage` or `_data` according to `*keys` and `*into`.\n   - Dispatches `sercrod-loaded` with source details.\n   - Calls `update()`.\n\n8. The cloned element is appended to the parent in the rendered DOM; the original template node is not appended.\n\n\n#### Variable creation\n\n`*load` does not create new template variables:\n\n- It does not add loop variables, local aliases, or special names to the scope.\n- All changes happen directly in the host’s `_data` (or `_stage`) object.\n- Templates and expressions continue to use the regular data paths (`user`, `settings`, etc.) after the data is updated.\n\n\n#### Scope layering\n\n`*load` respects the existing scope model:\n\n- It operates on the Sercrod host’s data or stage, not on local loop scopes.\n- It does not change how `$data`, `$root`, or `$parent` are injected.\n- After a successful load, any expressions that read from the updated data see the new values at the next render.\n\nBecause `*load` is an action on the host data, it does not affect how inner scopes are layered; it only changes the values they eventually read.\n\n\n#### Parent access\n\n`*load` does not introduce a new parent object:\n\n- Parent access via `$parent` and `$root` remains unchanged.\n- Any templates that use `$parent` or `$root` simply see updated data after the load and re-render, as long as they reference the affected fields.\n\n\n#### Use with conditionals and loops\n\nYou can place `*load` inside conditional blocks or loops just like any other action element:\n\n- Inside `*if`:\n\n  - The element exists and is interactive only when the `*if` condition is truthy.\n\n  ```html\n  <div *if=\"canLoad\">\n    <button type=\"button\" *load.file>Load config…</button>\n  </div>\n  ```\n\n- Inside loops:\n\n  - Each iteration can have its own `*load` element, although typically you want just one loader per host.\n\n  ```html\n  <serc-rod data='{\"sections\":[{\"id\":1},{\"id\":2}]}'>\n    <section *each=\"section of sections\">\n      <h2 *print=\"section.id\"></h2>\n      <button type=\"button\" *load.file *keys=\"section\">Load section…</button>\n    </section>\n  </serc-rod>\n  ```\n\nRestrictions:\n\n- `*load` is not a structural directive and does not control how many times an element is rendered.\n- It is best used for standalone controls (buttons, links, inputs) rather than for elements that also carry structural directives like `*for` or `*each`.\n- Combining `*load` with other action directives that also replace the element (such as `*save`, `*post`, or `*fetch`) on the same element is not recommended:\n\n  - Only one branch in the internal evaluation order will run.\n  - Other directives on the same element will effectively be ignored.\n  - Use separate elements if you need multiple actions.\n\n\n#### Best practices\n\n- Use dedicated controls:\n\n  - Attach `*load` to buttons or file inputs specifically intended for loading data.\n  - Avoid mixing `*load` with other unrelated behaviors on the same element.\n\n- Keep the JSON shape predictable:\n\n  - Decide on a stable JSON schema for exports and imports (for example, via `*save`).\n  - Document which top-level properties exist (`user`, `settings`, etc.).\n\n- Use `*keys` for partial updates:\n\n  - When you want to protect unrelated data from being overwritten, specify only the properties you want to import:\n\n    - `*load.file *keys=\"user settings\"`\n\n- Combine with staged editing:\n\n  - Pair `*load` with `*stage`, `*apply`, and `*restore` to allow safe previewing of loaded data before committing.\n\n- Keep `*load` elements structurally simple:\n\n  - The element with `*load` is cloned and used as-is.\n  - Avoid relying on nested Sercrod directives inside the `*load` element itself; keep its content mostly static (plain text or icons).\n\n- Validate externally if needed:\n\n  - `*load` does basic JSON parsing only.\n  - If you require more validation (schema checks, versioning), perform it in code that reacts to `sercrod-loaded`.\n\n\n#### Storage backends and adapters\n\nThe default `*load.file` path is intentionally simple: open a file picker, read JSON text, parse it, merge it into `_stage` or `_data`, dispatch `sercrod-loaded`, and update the host. `*load.session` and `*load.store` use the same merge step after reading JSON text from browser storage.\n\nThat does not mean `*load` is only a file-picker feature. The important boundary is the merge step. Built-in session/store forms or a file adapter can read JSON from another local source, then pass the parsed value into the same load path.\n\nUseful browser storage patterns:\n\n- IndexedDB:\n\n  - Good for JSON snapshots, metadata, key-value records, and indexes.\n  - Good for remembering which saved item should be loaded later.\n  - Used by the built-in `*save.store` / `*load.store` JSON store.\n  - Can also store Blob values, but host data should normally keep only a key and metadata.\n  - Use it as the default when the payload type does not clearly require a file-like store.\n\n- OPFS:\n\n  - Good for larger app-local payloads and file-like working directories.\n  - Good when the application needs an internal workspace rather than a user-visible downloaded file.\n  - Usually pairs well with IndexedDB metadata or indexes.\n  - Prefer it for obvious image payloads and suspiciously large Blob/File values, with IndexedDB metadata as the lookup record.\n\nRecommended data shape:\n\n- Keep host data JSON-like and render-friendly.\n- Store large payloads outside host data.\n- Keep keys, names, MIME types, sizes, timestamps, and status fields in host data.\n- Let `*load` restore the JSON state or source record that the template actually reads.\n\nDo not create more backend-named directives just to name a storage backend. Prefer the action family (`*save.file`, `*save.session`, `*save.store`, and matching load forms), and put backend-specific behavior behind adapters or explicit helpers.\n\n\n#### Examples\n\nFull data import:\n\n```html\n<serc-rod id=\"app\" data='{\"config\":{\"theme\":\"light\",\"lang\":\"en\"}}'>\n  <pre *literal=\"JSON.stringify(config, null, 2)\"></pre>\n  <button type=\"button\" *load.file>Load config…</button>\n</serc-rod>\n```\n\nPartial import:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{},\"settings\":{}}'>\n  <button type=\"button\" *load.file *keys=\"user settings\">\n    Load user and settings\n  </button>\n</serc-rod>\n```\n\nCustom accept type on a native file input:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{}}'>\n  <input type=\"file\" accept=\"application/json,.json\" *load.file *keys=\"user\">\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*load.file` and `n-load.file` are aliases; choose one style for consistency.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain old compatible file-load spellings.\n- `*load.file` is designed for browser environments where `FileReader` and file dialogs are available.\n- `*load.store` requires IndexedDB.\n- The directive expects JSON text; other content types will fail JSON parsing.\n- When JSON parsing fails and warnings are enabled, Sercrod logs a warning and does not modify data.\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event:\n\n  - `detail.stage`: `\"load\"` for file/legacy loads, `\"load.session\"` for session loads, or `\"load.store\"` for store loads.\n  - `detail.host`: the Sercrod host element\n  - `detail.fileName`: the selected file name (or `null`)\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file loads.\n  - `detail.storageKey`: the storage key for `*load.session` or `*load.store`.\n  - `detail.into`: the `*into` destination key, or `null`.\n  - `detail.props`: the property list used for partial merge (or `null`)\n  - `detail.keys`: the same property list, provided for the unified action syntax.\n  - `detail.json`: the parsed JSON object\n\n  You can listen to this event on the host to perform additional validation or side effects.\n\n- For clarity and maintainability, avoid combining `*load` with other I/O directives (`*save`, `*post`, `*fetch`) on the same element; use separate elements for each distinct action.\n",
  "save.store": "### *save / *save.file / *save.session / *save.store\n\n#### Summary\n\n`*save.file` exports the host data (or its staged view) as a JSON file in the browser.\n`*save.session` stores the same JSON payload in browser `sessionStorage`.\n`*save.store` stores the same JSON payload in persistent browser storage backed by IndexedDB.\nIt is typically used on a button inside a `<serc-rod>` host.\nWhen clicked, Sercrod collects the host’s current data and builds a JSON string. File saves start a download; session saves write that string under the storage key given by `*save.session`; store saves write it under the key given by `*save.store`.\n\nBy default, `*save` exports the entire host data.\nUse `*keys` to export only selected top-level properties.\n`*save` remains as the legacy-compatible file form, equivalent to `*save.file` when no storage suffix is used.\n\n\n#### Basic example\n\nSave the entire host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"name\":\"Alice\",\"age\":30}'>\n  <button *save.file>Download profile JSON</button>\n</serc-rod>\n```\n\nBehavior:\n\n- The `<button>` is cloned and given a click handler by Sercrod.\n- When the button is clicked, Sercrod takes the host’s current data and serializes it to JSON.\n- A file named like `Sercrod-YYYYMMDD-HHMMSS.json` is generated and downloaded by the browser.\n\nSave selected keys to an explicit filename:\n\n```html\n<button type=\"button\" *save.file=\"'profile-backup.json'\" *keys=\"profile settings\">\n  Save profile\n</button>\n```\n\nIn this form:\n\n- `*save.file` describes the file destination and optional filename.\n- `*keys` describes which host data keys are saved.\n\nSave selected keys into the current browser session:\n\n```html\n<button type=\"button\" *save.session=\"'profile-draft'\" *keys=\"profile settings\">\n  Save session draft\n</button>\n```\n\nIn this form:\n\n- `*save.session` describes the `sessionStorage` key.\n- `*keys` selects which host data keys are serialized.\n- Omitting `*keys` saves the whole host data or stage.\n\nSave selected keys into persistent browser storage:\n\n```html\n<button type=\"button\" *save.store=\"'profile-draft'\" *keys=\"profile settings\">\n  Save persistent draft\n</button>\n```\n\nIn this form:\n\n- `*save.store` describes a persistent browser storage key.\n- Sercrod stores a JSON string in IndexedDB.\n- The value remains available after page reloads and browser restarts, subject to normal browser storage policy.\n\n\n#### Behavior\n\n- `*save.file` attaches a click handler to the element it is placed on.\n- `*save.session` attaches the same kind of click handler, but writes JSON to `window.sessionStorage`.\n- `*save.store` attaches the same kind of click handler, but writes JSON to IndexedDB.\n- The handler runs in the context of the surrounding Sercrod host and serializes:\n\n  - `this._stage` if it exists, otherwise\n  - `this._data`.\n\n- No network request is sent by `*save` itself.\n- The resulting JSON is written into a Blob, and a temporary `<a download>` element is used to trigger the browser’s download dialog.\n- For `*save.session`, the resulting JSON is stored with `sessionStorage.setItem(storageKey, json)`.\n- For `*save.store`, the resulting JSON is stored in the `sercrod-store` IndexedDB database by default.\n- After a successful save action, a `CustomEvent(\"sercrod-saved\")` is dispatched from the host for application-specific hooks.\n\nAliases and compatibility:\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain supported as the old file-save spelling.\n- In new examples, prefer explicit action forms such as `*save.file`, `*save.session`, or `*save.store` plus `*keys`.\n\n\n#### Data source and property selection\n\nData source:\n\n- `*save` always uses host-level data, not per-element scope variables.\n- On click:\n\n  - If the host has a staged buffer (`_stage`), `*save` reads from `_stage`.\n  - Otherwise, it reads from the committed data object `_data`.\n\nKey selection:\n\n- Without an attribute value:\n\n  - `*save.file` exports the entire data object (`_stage` or `_data`).\n  - `*save.session` and `*save.store` require a storage key value, but still save the entire data object when `*keys` is omitted.\n\n- With `*keys`:\n\n  - The `*keys` value is treated as a whitespace-separated list of top-level property names.\n  - These names are not expressions; they are taken as-is and are not evaluated.\n  - Only properties that exist on the data object are copied into a new object.\n\nExample (selective save):\n\n```html\n<serc-rod id=\"settings\" data='{\n  \"user\": { \"name\": \"Alice\", \"age\": 30 },\n  \"theme\": { \"mode\": \"dark\" },\n  \"debug\": true\n}'>\n  <!-- Only save \"user\" and \"theme\" from the host data -->\n  <button *save.file=\"'user-theme.json'\" *keys=\"user theme\">Download user+theme</button>\n</serc-rod>\n```\n\nIn this example:\n\n- `src` is `host._stage ?? host._data`.\n- If `*keys` is `\"user theme\"`, Sercrod builds:\n\n  - `data = { user: src.user, theme: src.theme }` (if those properties exist).\n\n- The JSON file contains only `user` and `theme` at the top level.\n- Nested paths (such as `user.name`) are not supported by `*keys` directly.\n\nOld spelling:\n\n```html\n<button *save=\"user theme\">Download user+theme</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax, not as a filename.\n\n\n#### Evaluation timing\n\nRender-time:\n\n- When Sercrod renders the host, it looks for elements with `*save`, `*save.file`, `*save.session`, `*save.store`, or their `n-` aliases.\n- For each such element:\n\n  - Sercrod clones the element.\n  - Attaches a click handler on the clone.\n  - Appends the clone to the parent.\n  - Returns from the element renderer without recursing into the children of that clone.\n\nClick-time:\n\n- When the user clicks the save button:\n\n  1. Sercrod resolves the action attribute.\n     - `*save.file` gives an optional filename.\n     - `*save.session` gives a `sessionStorage` key.\n     - `*save.store` gives a persistent browser storage key.\n     - legacy `*save` values are treated as old key-selection syntax.\n  2. Sercrod selects `src = this._stage ?? this._data` from the host.\n  3. It builds a plain object:\n\n     - Entire `src` if no `*keys` list was provided.\n     - A subset object if `*keys` was provided.\n\n  4. It serializes that object with `JSON.stringify(data, null, 2)`.\n  5. It writes the JSON to the selected destination: file download, `sessionStorage`, or IndexedDB-backed persistent browser storage.\n  6. It dispatches the `sercrod-saved` event from the host.\n\nBecause the JSON is built at click time, `*save` always reflects the current state of `_stage` or `_data` at the moment of the click.\n\n\n#### Execution model\n\nInternally, `*save` behaves as follows (conceptually):\n\n1. During render, Sercrod finds an element `work` with a save directive.\n2. Sercrod clones `work` into `el`.\n3. Sercrod attaches:\n\n   - `el.addEventListener(\"click\", () => { /* build JSON and save to the selected destination */ })`.\n\n4. Sercrod appends `el` to the parent node and returns, without processing `el`’s children for further Sercrod directives.\n\nOn click, the handler:\n\n1. Resolves the action attribute and `*keys`.\n2. Selects `src`:\n\n   - `src = host._stage ?? host._data`.\n\n3. Builds `data`:\n\n   - If there is a `*keys` list, `data` is a new object populated only with properties present in `src`.\n   - Otherwise, `data` is `src` itself.\n\n4. Serializes `data` with a pretty-printed `JSON.stringify(data, null, 2)`.\n5. If the action is `*save.store`, writes `{ key, json, updatedAt }` into the configured IndexedDB object store and returns.\n6. If the action is `*save.session`, writes the JSON string with `sessionStorage.setItem(storageKey, json)` and returns.\n7. Otherwise, creates a Blob of type `application/json`.\n8. Creates an `ObjectURL` and a temporary `<a>` element with:\n\n   - `href = url`.\n   - `download = \"Sercrod-YYYYMMDD-HHMMSS.json\"` (in the local time of the browser).\n\n9. Programmatically clicks the anchor to prompt download.\n10. Cleans up (removes the anchor from the DOM and revokes the `ObjectURL`).\n11. Dispatches `CustomEvent(\"sercrod-saved\", { detail: { ... } })` from the host.\n\n\n#### Use on nested elements and scope\n\n- `*save` must live inside a Sercrod host to be meaningful, since it reads from the host’s `_stage` or `_data`.\n- `*save` does not use per-element scope; it only uses the host’s data object.\n- Placing `*save` on a deeply nested element is allowed, but it still always saves the surrounding host’s data, not a subset scoped by `*for` or `*each`.\n\nIn other words:\n\n- The location of the `*save` button in the DOM tree does not change the data source.\n- It only changes where the button appears in the layout.\n\n\n#### Events\n\nAfter a successful save, Sercrod dispatches a bubbling, composed `CustomEvent` from the host:\n\n- Event type:\n\n  - `\"sercrod-saved\"`\n\n- Event detail structure:\n\n  - `detail.stage`: `\"save\"` for file/legacy saves, `\"save.session\"` for session saves, or `\"save.store\"` for store saves.\n  - `detail.host`: the Sercrod host element (`<serc-rod>` instance).\n  - `detail.fileName`: the file name used for file downloads (for example `\"Sercrod-20251205-093000.json\"`).\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file saves.\n  - `detail.storageKey`: the storage key for `*save.session` or `*save.store`.\n  - `detail.props`: the property list array if provided; `null` if no list was specified.\n  - `detail.keys`: the same property list array, provided for the unified action syntax.\n  - `detail.json`: the JSON string that was generated.\n\nExample hook:\n\n```js\ndocument.addEventListener(\"sercrod-saved\", (evt) => {\n  const { host, fileName, storage, storageKey, props, json } = evt.detail;\n  console.log(\"Saved from host:\", host.id);\n  console.log(\"File name:\", fileName);\n  console.log(\"Storage:\", storage, storageKey);\n  console.log(\"Props:\", props);\n  console.log(\"JSON preview:\", json.slice(0, 200));\n});\n```\n\nYou can use this event to:\n\n- Mirror the saved JSON to another storage or API when the built-in file/session/store destinations are not enough.\n- Show a toast notification after the download is triggered.\n- Log or audit save operations.\n\n\n#### Best practices\n\n- Treat `*save.file` elements as simple buttons:\n\n  - Because the renderer does not recursively process children of `*save` hosts after cloning, avoid placing other Sercrod directives inside the same element.\n  - Use plain text or static markup inside the button where possible.\n\n- Use `*keys` for focused exports:\n\n  - If your host data is large, consider exposing smaller subsets via:\n\n    - `*save.file=\"'profile.json'\" *keys=\"profile settings\"`\n    - `*save.file=\"'chart.json'\" *keys=\"chart filters\"`\n\n- Keep the root data export-friendly:\n\n  - Plan your top-level keys (`user`, `settings`, `rows`, `config`, and so on) so that it is easy to export meaningful subsets by name.\n\n- Combine with matching `*load` forms for round trips:\n\n  - Use `*save.file` and `*load.file` for user-visible JSON files.\n  - Use `*save.session` and `*load.session` for session-scoped browser storage.\n  - Use `*save.store` and `*load.store` for persistent browser storage.\n\n- Use `sercrod-saved` for integration:\n\n  - Attach listeners to `\"sercrod-saved\"` if you want to route the JSON elsewhere instead of or in addition to the download.\n\n\n#### Advanced - Using save forms with *stage, *apply, *restore, load forms, and *post\n\n`*save` is part of a broader data management workflow:\n\n- `*stage`:\n\n  - Enables a staged buffer `_stage` for the host (a working copy of the data).\n  - When `_stage` exists, `*save` prefers `_stage` over `_data`.\n  - This lets you export the staged view without committing it.\n\n- `*apply`:\n\n  - Copies `_stage` into `_data` and updates the host.\n  - Subsequent `*save` clicks, after `*apply`, will see the committed state in `_data`.\n\n- `*restore`:\n\n  - Rolls back `_stage` to the last snapshot, or to `_data` if no snapshot is available.\n  - After a restore, `*save` again sees whatever `_stage` currently holds.\n\n- `*load`:\n\n  - Reads JSON from a file, session storage, or persistent browser storage and merges it into `_stage` or `_data`.\n  - You can use matching `*load` forms to import JSON previously written by `*save` forms.\n\n- `*post`:\n\n  - Sends host data to a server as JSON over HTTP.\n  - `*save` is complementary to `*post`: one saves locally as a file, the other sends over the network.\n\nThe core rule is:\n\n- `*save` always targets “the current data view” of the host, prioritizing `_stage` when present.\n- This makes it safe to stage edits with `*stage`, try them out, export via `*save`, and later apply or restore as needed.\n\n\n#### Notes\n\n- `*save.file` and `n-save.file` are aliases.\n- `*save.session` and `n-save.session` are aliases.\n- `*save.store` and `n-save.store` are aliases.\n- `*save` and `n-save` remain old compatible file-save spellings.\n- In old syntax, the value of `*save` is parsed as plain text and split by whitespace as a key list.\n- When no property list is provided, the entire `_stage ?? _data` object is serialized.\n- When a property list is provided, only the listed top-level properties are included if they exist.\n- The file name is generated as `\"Sercrod-YYYYMMDD-HHMMSS.json\"` using the browser’s local time.\n- `*save` itself does not change `_stage` or `_data`; it is a read-only export operation.\n- There are no special structural restrictions specific to `*save` beyond the general behavior described above; it can be combined with directives such as `*if` on the same element, as long as you keep in mind that `*save` turns that element into a “save button” whose children are not further processed by Sercrod.\n",
  "load.store": "### *load / *load.file / *load.session / *load.store\n\n#### Summary\n\n`*load.file` loads JSON data from a user-selected file and merges it into the Sercrod host’s data.\n`*load.session` loads JSON from browser `sessionStorage` and applies the same merge rules.\n`*load.store` loads JSON from persistent browser storage backed by IndexedDB and applies the same merge rules.\nIf a staged view is active (via `*stage`), the JSON is merged into the stage; otherwise it is merged into the live data.\n`*load` remains as the legacy-compatible file form, equivalent to `*load.file` when no storage suffix is used.\n\nTypical use:\n\n- Put `*load.file` on a button or other clickable element.\n- Use `*load.session=\"'key'\"` when the source is a `sessionStorage` key.\n- Use `*load.store=\"'key'\"` when the source is persistent browser storage.\n- Optionally use `*keys` to choose top-level properties from the JSON.\n- Optionally use `*into` to place the loaded value under one host data key.\n- Sercrod reads JSON from the selected source, parses it, merges it into data or stage, and triggers a re-render.\n\n\n#### Basic example\n\nA simple load button that merges the entire JSON into host data:\n\n```html\n<serc-rod id=\"profile\" data='{\"user\":{\"name\":\"\",\"email\":\"\"}}'>\n  <p>Name: <span *print=\"user.name\"></span></p>\n  <p>Email: <span *print=\"user.email\"></span></p>\n\n  <button type=\"button\" *load.file>Load profile…</button>\n</serc-rod>\n```\n\nIf the user selects a JSON file like:\n\n```json\n{\n  \"user\": {\n    \"name\": \"Alice\",\n    \"email\": \"alice@example.com\"\n  }\n}\n```\n\nthen after loading:\n\n- `user.name` becomes `\"Alice\"`.\n- `user.email` becomes `\"alice@example.com\"`.\n- The view is refreshed automatically.\n\nLoad selected keys into one destination:\n\n```html\n<button type=\"button\" *load.file *keys=\"profile settings\" *into=\"draft\">\n  Load draft\n</button>\n```\n\nIn this form:\n\n- `*load.file` describes the browser file source.\n- `*keys` selects keys from the loaded JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from the current browser session:\n\n```html\n<button type=\"button\" *load.session=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load session draft\n</button>\n```\n\nIn this form:\n\n- `*load.session` describes the `sessionStorage` key.\n- `*keys` selects keys from the stored JSON.\n- `*into` stores the selected value under `draft`.\n\nLoad selected keys from persistent browser storage:\n\n```html\n<button type=\"button\" *load.store=\"'profile-draft'\" *keys=\"profile\" *into=\"draft\">\n  Load persistent draft\n</button>\n```\n\nIn this form:\n\n- `*load.store` describes the persistent browser storage key.\n- Sercrod reads JSON from IndexedDB.\n- `*keys` and `*into` use the same merge rules as file and session loads.\n\n\n#### Behavior\n\n- `*load.file` is an action directive that attaches file-loading behavior to an element.\n- `*load.session` is an action directive that reads JSON from `window.sessionStorage` on click.\n- `*load.store` is an action directive that reads JSON from IndexedDB on click.\n- The directive works with the browser’s file picker and uses `FileReader` to read the chosen file.\n- Only JSON is expected; other file types are not supported by the current implementation.\n- The directive merges the parsed JSON into:\n\n  - `_stage`, if the host has a staged view (for example due to `*stage`).\n  - `_data`, otherwise.\n\n- The merge strategy depends on `*keys` and `*into`:\n\n  - No `*keys`, no `*into`: merge the entire JSON object into the target object with `Object.assign`.\n  - `*keys`, no `*into`: copy only those top-level properties into the target object.\n  - `*into`: place the loaded value under that one target key.\n\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event and calls `update()` on the host to re-render the view.\n\nAliases and compatibility:\n\n- `*load.file` and `n-load.file` are aliases.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain supported as the old file-load spelling.\n- In new examples, prefer explicit action forms such as `*load.file`, `*load.session`, or `*load.store` plus `*keys` and `*into` where needed.\n\n\n#### Storage and merge semantics\n\nKey and destination values:\n\n- If `*keys` and `*into` are omitted:\n\n  - The parsed JSON must be an object.\n  - Sercrod merges all enumerable properties into `_stage` or `_data`:\n\n    - With stage: `Object.assign(this._stage, json)`\n    - Without stage: `Object.assign(this._data, json)`\n\n- If `*keys` has a value:\n\n  - The value is split by whitespace into a list of property names:\n\n    - `*keys=\"user settings\"`\n\n      becomes `[\"user\",\"settings\"]`.\n\n  - For each property name `p`:\n\n    - With stage: `this._stage[p] = json[p]`\n    - Without stage: `this._data[p] = json[p]`\n\n  - Only direct top-level keys are supported; dotted paths or nested selectors are not interpreted.\n\n- If `*into` has a value:\n\n  - Without `*keys`, the entire loaded JSON is assigned to `data[into]`.\n  - With one key, `json[key]` is assigned to `data[into]`.\n  - With multiple keys, an object containing those keys is assigned to `data[into]`.\n\nOld spelling:\n\n```html\n<button *load=\"user settings\">Load user+settings</button>\n```\n\nThis remains supported for compatibility. Treat the value as old `*keys` syntax.\n\nError handling:\n\n- The chosen file is read as text and parsed with `JSON.parse`.\n- `*load.session` warns and makes no data change when the storage key is empty, inaccessible, missing, or contains invalid JSON.\n- `*load.store` warns and makes no data change when IndexedDB is unavailable, the key is missing, or the stored JSON cannot be parsed.\n- If parsing fails and Sercrod is configured to warn, the runtime logs:\n\n  - `[Sercrod warn] *load JSON parse: ...`\n\n- On parse error, no merge is performed and the view is not updated.\n\n\n#### File input integration\n\n`*load.file` and legacy `*load` work both with native file inputs and with regular clickable elements.\n\n- If the element is an `<input type=\"file\">`:\n\n  - Sercrod reuses the native file input.\n  - If the input has no `accept` attribute, Sercrod sets it to `\"application/json\"` by default.\n  - On `change`, the first selected file is read and processed.\n\n- If the element is not an `<input type=\"file\">`:\n\n  - Sercrod attaches a `click` handler to the element.\n  - When clicked, Sercrod creates a hidden `<input type=\"file\">`, sets its `accept` attribute, and forwards the selection to `*load`.\n  - The temporary file input is not meant to be visible or controlled directly.\n\nAccept attribute:\n\n- If the element has an `accept` attribute, Sercrod respects it.\n- If not, Sercrod uses `\"application/json\"` as the default.\n- This influences what the browser shows in the file picker but does not perform additional runtime validation beyond JSON parsing.\n\n\n#### Stage interaction\n\n`*load` is designed to cooperate with staged editing:\n\n- If the host has an active stage (for example due to `*stage`), `*load` merges into `_stage` instead of `_data`.\n- This lets you preview or edit the loaded data in a staged view and then decide when to apply it.\n\nTypical pattern:\n\n```html\n<serc-rod id=\"editor\" data='{\"doc\":{\"title\":\"\",\"body\":\"\"}}'>\n  <section *stage>\n    <label>\n      Title:\n      <input *input=\"doc.title\">\n    </label>\n\n    <label>\n      Body:\n      <textarea *input=\"doc.body\"></textarea>\n    </label>\n\n    <button type=\"button\" *load.file *keys=\"doc\">Load draft…</button>\n    <button type=\"button\" *apply>Apply</button>\n    <button type=\"button\" *restore>Restore</button>\n  </section>\n</serc-rod>\n```\n\nIn this pattern:\n\n- `*load.file *keys=\"doc\"` replaces the staged `doc` object with `json.doc` from the file.\n- `*apply` copies staged changes back into the live data.\n- `*restore` discards staged changes and returns to the last stable state.\n\n\n#### Evaluation timing\n\n- `*load` is evaluated when Sercrod renders the element that carries it.\n- During rendering:\n\n  - Sercrod clones the original element.\n  - It attaches the necessary event listeners for the selected load source.\n  - It appends the cloned element to the DOM and returns from the internal render function.\n\n- `*load` does not perform any data changes during rendering itself.\n  - Data changes happen later, in response to user interaction.\n  - File loads react to file selection.\n  - Session and store loads react to click and then read from browser storage.\n- When JSON is successfully loaded and merged, Sercrod explicitly calls `update()` on the host to re-run the render pipeline and update the view.\n\n\n#### Execution model\n\nConceptually, the runtime behaves like this for `*load`:\n\n1. Sercrod detects a load directive on an element.\n2. It clones the element.\n\n   - All attributes and children are copied as-is.\n   - The `*load` / `n-load` attribute is preserved on the clone for visibility, but Sercrod does not re-interpret it later.\n\n3. It resolves the action attribute:\n\n   - `*load.file` means browser file input.\n   - `*load.session` gives a `sessionStorage` key.\n   - `*load.store` gives a persistent browser storage key.\n   - legacy `*load` values are treated as old `*keys` syntax.\n\n4. It parses `*keys` and `*into`.\n\n5. For file loads, it determines the desired `accept` type:\n\n   - Uses the element’s own `accept` attribute if present.\n   - Otherwise, defaults to `\"application/json\"`.\n\n6. It wires source handling:\n\n   - For `*load.store`, it attaches a click listener that reads the JSON string from IndexedDB.\n   - For `*load.session`, it attaches a click listener that reads the JSON string from `sessionStorage`.\n   - For file loads, if the cloned element is an `<input type=\"file\">`:\n\n     - Ensures `accept` is set.\n     - Adds a `change` listener that calls `handleFile(file)` for the selected file.\n\n   - For file loads on any other element (button, link, etc.):\n\n     - Adds a `click` listener.\n     - That listener creates a temporary `<input type=\"file\">`, sets `accept`, and listens for `change`.\n     - When a file is chosen, it calls `handleFile(file)`.\n\n7. The common JSON path:\n\n   - Reads JSON text from the selected source.\n   - Parses the JSON.\n   - Merges it into `_stage` or `_data` according to `*keys` and `*into`.\n   - Dispatches `sercrod-loaded` with source details.\n   - Calls `update()`.\n\n8. The cloned element is appended to the parent in the rendered DOM; the original template node is not appended.\n\n\n#### Variable creation\n\n`*load` does not create new template variables:\n\n- It does not add loop variables, local aliases, or special names to the scope.\n- All changes happen directly in the host’s `_data` (or `_stage`) object.\n- Templates and expressions continue to use the regular data paths (`user`, `settings`, etc.) after the data is updated.\n\n\n#### Scope layering\n\n`*load` respects the existing scope model:\n\n- It operates on the Sercrod host’s data or stage, not on local loop scopes.\n- It does not change how `$data`, `$root`, or `$parent` are injected.\n- After a successful load, any expressions that read from the updated data see the new values at the next render.\n\nBecause `*load` is an action on the host data, it does not affect how inner scopes are layered; it only changes the values they eventually read.\n\n\n#### Parent access\n\n`*load` does not introduce a new parent object:\n\n- Parent access via `$parent` and `$root` remains unchanged.\n- Any templates that use `$parent` or `$root` simply see updated data after the load and re-render, as long as they reference the affected fields.\n\n\n#### Use with conditionals and loops\n\nYou can place `*load` inside conditional blocks or loops just like any other action element:\n\n- Inside `*if`:\n\n  - The element exists and is interactive only when the `*if` condition is truthy.\n\n  ```html\n  <div *if=\"canLoad\">\n    <button type=\"button\" *load.file>Load config…</button>\n  </div>\n  ```\n\n- Inside loops:\n\n  - Each iteration can have its own `*load` element, although typically you want just one loader per host.\n\n  ```html\n  <serc-rod data='{\"sections\":[{\"id\":1},{\"id\":2}]}'>\n    <section *each=\"section of sections\">\n      <h2 *print=\"section.id\"></h2>\n      <button type=\"button\" *load.file *keys=\"section\">Load section…</button>\n    </section>\n  </serc-rod>\n  ```\n\nRestrictions:\n\n- `*load` is not a structural directive and does not control how many times an element is rendered.\n- It is best used for standalone controls (buttons, links, inputs) rather than for elements that also carry structural directives like `*for` or `*each`.\n- Combining `*load` with other action directives that also replace the element (such as `*save`, `*post`, or `*fetch`) on the same element is not recommended:\n\n  - Only one branch in the internal evaluation order will run.\n  - Other directives on the same element will effectively be ignored.\n  - Use separate elements if you need multiple actions.\n\n\n#### Best practices\n\n- Use dedicated controls:\n\n  - Attach `*load` to buttons or file inputs specifically intended for loading data.\n  - Avoid mixing `*load` with other unrelated behaviors on the same element.\n\n- Keep the JSON shape predictable:\n\n  - Decide on a stable JSON schema for exports and imports (for example, via `*save`).\n  - Document which top-level properties exist (`user`, `settings`, etc.).\n\n- Use `*keys` for partial updates:\n\n  - When you want to protect unrelated data from being overwritten, specify only the properties you want to import:\n\n    - `*load.file *keys=\"user settings\"`\n\n- Combine with staged editing:\n\n  - Pair `*load` with `*stage`, `*apply`, and `*restore` to allow safe previewing of loaded data before committing.\n\n- Keep `*load` elements structurally simple:\n\n  - The element with `*load` is cloned and used as-is.\n  - Avoid relying on nested Sercrod directives inside the `*load` element itself; keep its content mostly static (plain text or icons).\n\n- Validate externally if needed:\n\n  - `*load` does basic JSON parsing only.\n  - If you require more validation (schema checks, versioning), perform it in code that reacts to `sercrod-loaded`.\n\n\n#### Storage backends and adapters\n\nThe default `*load.file` path is intentionally simple: open a file picker, read JSON text, parse it, merge it into `_stage` or `_data`, dispatch `sercrod-loaded`, and update the host. `*load.session` and `*load.store` use the same merge step after reading JSON text from browser storage.\n\nThat does not mean `*load` is only a file-picker feature. The important boundary is the merge step. Built-in session/store forms or a file adapter can read JSON from another local source, then pass the parsed value into the same load path.\n\nUseful browser storage patterns:\n\n- IndexedDB:\n\n  - Good for JSON snapshots, metadata, key-value records, and indexes.\n  - Good for remembering which saved item should be loaded later.\n  - Used by the built-in `*save.store` / `*load.store` JSON store.\n  - Can also store Blob values, but host data should normally keep only a key and metadata.\n  - Use it as the default when the payload type does not clearly require a file-like store.\n\n- OPFS:\n\n  - Good for larger app-local payloads and file-like working directories.\n  - Good when the application needs an internal workspace rather than a user-visible downloaded file.\n  - Usually pairs well with IndexedDB metadata or indexes.\n  - Prefer it for obvious image payloads and suspiciously large Blob/File values, with IndexedDB metadata as the lookup record.\n\nRecommended data shape:\n\n- Keep host data JSON-like and render-friendly.\n- Store large payloads outside host data.\n- Keep keys, names, MIME types, sizes, timestamps, and status fields in host data.\n- Let `*load` restore the JSON state or source record that the template actually reads.\n\nDo not create more backend-named directives just to name a storage backend. Prefer the action family (`*save.file`, `*save.session`, `*save.store`, and matching load forms), and put backend-specific behavior behind adapters or explicit helpers.\n\n\n#### Examples\n\nFull data import:\n\n```html\n<serc-rod id=\"app\" data='{\"config\":{\"theme\":\"light\",\"lang\":\"en\"}}'>\n  <pre *literal=\"JSON.stringify(config, null, 2)\"></pre>\n  <button type=\"button\" *load.file>Load config…</button>\n</serc-rod>\n```\n\nPartial import:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{},\"settings\":{}}'>\n  <button type=\"button\" *load.file *keys=\"user settings\">\n    Load user and settings\n  </button>\n</serc-rod>\n```\n\nCustom accept type on a native file input:\n\n```html\n<serc-rod id=\"app\" data='{\"user\":{}}'>\n  <input type=\"file\" accept=\"application/json,.json\" *load.file *keys=\"user\">\n</serc-rod>\n```\n\n\n#### Notes\n\n- `*load.file` and `n-load.file` are aliases; choose one style for consistency.\n- `*load.session` and `n-load.session` are aliases.\n- `*load.store` and `n-load.store` are aliases.\n- `*load` and `n-load` remain old compatible file-load spellings.\n- `*load.file` is designed for browser environments where `FileReader` and file dialogs are available.\n- `*load.store` requires IndexedDB.\n- The directive expects JSON text; other content types will fail JSON parsing.\n- When JSON parsing fails and warnings are enabled, Sercrod logs a warning and does not modify data.\n- After a successful load, Sercrod dispatches a `sercrod-loaded` event:\n\n  - `detail.stage`: `\"load\"` for file/legacy loads, `\"load.session\"` for session loads, or `\"load.store\"` for store loads.\n  - `detail.host`: the Sercrod host element\n  - `detail.fileName`: the selected file name (or `null`)\n  - `detail.storage`: `\"session\"` or `\"store\"` for non-file loads.\n  - `detail.storageKey`: the storage key for `*load.session` or `*load.store`.\n  - `detail.into`: the `*into` destination key, or `null`.\n  - `detail.props`: the property list used for partial merge (or `null`)\n  - `detail.keys`: the same property list, provided for the unified action syntax.\n  - `detail.json`: the parsed JSON object\n\n  You can listen to this event on the host to perform additional validation or side effects.\n\n- For clarity and maintainability, avoid combining `*load` with other I/O directives (`*save`, `*post`, `*fetch`) on the same element; use separate elements for each distinct action.\n",
  "update": "### *update\n\n#### Summary\n\n`*update` routes a forced update to one target Sercrod host after the current host or element has finished updating.\n\nIt does not evaluate JavaScript and it does not change data. The attribute value is a literal routing spec that resolves to a Sercrod host, then Sercrod calls:\n\n```js\ntarget.update(true, caller)\n```\n\nAliases:\n\n- `*update`\n- `n-update`\n- `*updated-propagate`\n- `n-updated-propagate`\n\n`*updated-propagate` is the old compatible spelling. Prefer `*update` in new templates.\n\n#### Basic Example\n\nRefresh a parent host after a child changes parent data:\n\n```html\n<serc-rod id=\"parent\" data='{\"message\":\"parent_message\"}'>\n  <serc-rod data='{\"child_message\":\"hello\"}'>\n    <button\n      type=\"button\"\n      @click=\"$parent.message = child_message\"\n      *update=\"2\">\n      Send to parent\n    </button>\n  </serc-rod>\n\n  <p *print=\"message\"></p>\n</serc-rod>\n```\n\nHere the button is inside the child Sercrod host.\n\n- `*update=\"1\"` would update the nearest host, the child.\n- `*update=\"2\"` updates the next outer host, the parent.\n- The parent re-renders, so `<p *print=\"message\">` can reflect the changed parent data.\n\n#### Behavior\n\n`*update` can be placed on either:\n\n- A Sercrod host, such as `<serc-rod *update=\"root\">`.\n- An ordinary element inside a Sercrod host, such as `<button *update=\"2\">`.\n\nOn a Sercrod host:\n\n1. The host completes its normal update cycle.\n2. The host runs its own `*updated` / `n-updated` hooks, if any.\n3. Sercrod reads `*update`, `n-update`, `*updated-propagate`, or `n-updated-propagate` on that host.\n4. It resolves the target and calls `target.update(true, callerHost)`.\n\nOn an ordinary element:\n\n1. The containing host completes its update cycle.\n2. The host scans ordinary descendant elements, skipping nested Sercrod hosts.\n3. If the element has `*updated`, Sercrod runs that handler first.\n4. If the element has `*update`, Sercrod resolves the target and calls `target.update(true, scanningHost)`.\n\nThe directive is a routing directive only:\n\n- It does not create variables.\n- It does not expose `$event`.\n- It does not read or write `data`, `_stage`, `$root`, or `$parent`.\n- It does not evaluate the attribute value as an expression.\n\n#### Target Specs\n\nThe value of `*update` is interpreted in this order.\n\n##### Empty\n\nAn empty attribute is treated as `\"1\"`:\n\n```html\n<button *update>Refresh nearest host</button>\n```\n\n##### Parenthesized Selector\n\n`\"(selector)\"` uses the closest matching Sercrod host:\n\n```html\n<button *update=\"(#parent)\">Refresh #parent</button>\n<button *update=\"(.panel)\">Refresh nearest .panel host</button>\n```\n\nSercrod strips the outer parentheses and calls composed-tree `closest(selector)`. The match must be a Sercrod host.\n\n##### root\n\n`\"root\"` updates the outermost Sercrod host around the attribute location:\n\n```html\n<button *update=\"root\">Refresh root</button>\n```\n\nUse this when the top-level UI container should recompute after a nested host or element updates.\n\n##### Numeric Depth\n\nA number counts Sercrod hosts only:\n\n```html\n<button *update=\"1\">Refresh nearest host</button>\n<button *update=\"2\">Refresh parent host</button>\n```\n\nNormal elements such as `div`, `p`, `button`, and `span` are skipped.\n\nOn an ordinary element:\n\n- `\"1\"` targets the nearest containing Sercrod host.\n- `\"2\"` targets the next outer Sercrod host.\n\nOn a Sercrod host:\n\n- `\"1\"` targets that host itself.\n- `\"2\"` targets its parent Sercrod host.\n\n##### Bare Selector\n\nAny other value is treated as a bare CSS selector and resolved with composed-tree `closest(spec)`:\n\n```html\n<button *update=\".panel-root\">Refresh nearest .panel-root host</button>\n```\n\nParenthesized selectors are usually clearer because they make it obvious that the value is a selector, not an expression.\n\n#### Timing With *updated\n\n`*updated` and `*update` have different jobs.\n\n- `*updated` runs local post-render code.\n- `*update` routes a forced update to another Sercrod host.\n\nWhen both are present on the same host or ordinary element, `*updated` runs first and `*update` runs after it.\n\n```html\n<button\n  type=\"button\"\n  *updated=\"afterButtonUpdated()\"\n  *update=\"root\">\n  Save\n</button>\n```\n\nThe containing host first runs `afterButtonUpdated()`. Then `*update=\"root\"` forces the root Sercrod host to update.\n\n#### Error Handling\n\nIf the target cannot be resolved, no update is performed.\n\nThis can happen when:\n\n- The selector is invalid.\n- The selector matches an element that is not a Sercrod host.\n- The numeric depth is larger than the available Sercrod ancestor chain.\n- The attribute value is a data expression by mistake, such as `*update=\"targetName\"`.\n\nRuntime errors are caught. When warnings are enabled, Sercrod logs a warning and continues.\n\n#### Examples\n\nRefresh the root after nested state changes:\n\n```html\n<serc-rod id=\"root\" data='{\"saved\": false}'>\n  <serc-rod data='{\"label\":\"Save\"}'>\n    <button\n      type=\"button\"\n      @click=\"$parent.saved = true\"\n      *update=\"root\">\n      %label%\n    </button>\n  </serc-rod>\n\n  <p *if=\"saved\">Saved</p>\n</serc-rod>\n```\n\nRefresh a named parent host:\n\n```html\n<serc-rod id=\"profile-panel\" data='{\"name\":\"Ada\"}'>\n  <serc-rod data='{\"draft\":\"Grace\"}'>\n    <button\n      type=\"button\"\n      @click=\"$parent.name = draft\"\n      *update=\"(#profile-panel)\">\n      Apply\n    </button>\n  </serc-rod>\n\n  <p *print=\"name\"></p>\n</serc-rod>\n```\n\nUse the compatibility alias only for older templates:\n\n```html\n<button *updated-propagate=\"root\">Refresh root</button>\n```\n\nIn new code, write:\n\n```html\n<button *update=\"root\">Refresh root</button>\n```\n\n#### Notes\n\n- Only one target host is updated per directive evaluation.\n- Multiple specs separated by spaces or commas are not supported.\n- The value is literal. `*update=\"root\"` means the keyword `root`, but `*update=\"state.target\"` means the CSS selector `state.target`.\n- Use `root` or an explicit parenthesized selector when possible.\n- Avoid unnecessary propagation. Forced updates are useful for nested-host coordination, but overusing them can create expensive update cascades.\n"
}
