# Kopular — LLM reference

Complete reference for generating correct Kopular code. This is a spec, not a tutorial —
see `README.md` for narrative/rationale, or this package's own **`GUIDE.md`** for a short,
task-oriented page covering everything a typical app needs (start there for most tasks;
come back here for anything it doesn't cover). Kopular is 10 files total; this covers all
of them. For the host language, see KopScript's own `LLM.md` in the `KopScript` repo (or
its published `LLM.md` on the `kopscript` npm package, `kopscript@1.1.0`+) — that
reference is a prerequisite, not repeated here.

Published as npm `kopular`. Entry points: `kopular` / `kopular/component` (Component),
`kopular/velement` (VElement — what `Render()` returns; see "Component" below),
`kopular/router` (Router), `kopular/dom` (ambient DOM bindings), `kopular/directives`
(If), `kopular/http` (Http), `kopular/forms` (FormField, Validators), `kopular/computed`
(Computed1, Computed2), `kopular/resource` (Resource, AsyncStatus), `kopular/vdom`
(`ScopedStyles` — the runtime half of `styles from`; everything else in this file, the
diff/patch engine behind `Update()`, is internal, nothing else here needs importing
directly), `kopular/timers` (`Delay`), `kopular/testing` (runKopularApp,
runKopularFixture — see below). Also ships a bin, `kp` — `npx kp new <dir>` scaffolds a
new project (an `index.html` with every module already wired, a `Counter` component, and
the vendor/serve scripts needed to run in a browser). Templates (`template from
"./x.html";`, see below) are a KopScript language feature, not a Kopular export — there is
no `kopular/template` entry point to import.

## Consuming Kopular from your own KopScript project

`using "kopular";` (`kopscript@1.1.0`+) resolves this package's own declarations —
everything in the "Entry points" list above — through `node_modules`, the same way `npm
install` already makes them available to plain JS. One line, every Kopular type in scope,
each consuming file's own compiled output importing only the bindings it actually
references (never the whole surface). This is the normal way to consume Kopular now; `kp
new` generates it by default.

```ks
using "kopular";
using "./counter";

Counter app = new Counter();
app.Mount(document.body);
```

`document`/`Element`/`Event`/etc. come from the same `using "kopular";` — no separate ambient
block needed. The rest of this file describes every one of those bindings' real shapes, for
when you need something `GUIDE.md` doesn't cover, or need to know exactly what a type looks
like.

### The hand-copied `extern` block (older projects, or trimming what you import)

Before `kopscript@1.1.0`, every consuming project hand-declared its own copy of Kopular's
`extern` bindings (`using` couldn't reach into a package). That still works — a real,
hand-written `extern` block is exactly as valid as one resolved via `using "kopular";",
just more to maintain — and is occasionally still the right call if you want a visibly
trimmed subset. **Don't reach for `Element`/`Document`/`Event` via `extern ... from
"kopular/dom";`, even though that actually works** (`kopular/dom` genuinely re-exports real
`globalThis` bindings, same as any other Kopular export) — Kopular's own internal `dom.ks`
only declares what Kopular's own framework code itself touches, a smaller surface than a
real app typically needs. `using "kopular";`'s own declarations (`src/kopular.ks` in this
package) don't have that problem — they're written for consumers, not just for Kopular's
own internals — so prefer it over a hand-copied ambient block for new code. If you do
hand-copy one anyway, copy the block below rather than hand-rolling a smaller one from
scratch and adding members as compile errors demand them — a real, complete first-attempt
implementation of a Kopular app hit the exact same missing property (`Element.value`)
twice from two independently-trimmed subsets, because the compile error only ever names
the one member actually touched, never warns that a *sibling* feature (a template's
`placeholder="..."` attribute, a `[(value)]` binding's generated `e.target.value` read)
will need one you didn't happen to write by hand:

```ks
extern class Event {
  Element target { get; }
  void preventDefault();
};

extern class Element {
  string textContent { get; set; }
  string innerHTML { get; set; }
  string id { get; set; }
  string className { get; set; }
  string href { get; set; }
  string src { get; set; }
  string alt { get; set; }
  // Every `[(value)]="Field"` template binding and any handler reading
  // `e.target.value` needs this — the single most commonly missing member
  // when a hand-trimmed subset breaks.
  string value { get; set; }
  string placeholder { get; set; }
  void appendChild(Element child);
  void replaceChild(Element newChild, Element oldChild);
  void insertBefore(Element newChild, Element? referenceChild);
  void removeChild(Element child);
  void setAttribute(string name, string value);
  void addEventListener(string eventType, (Event) => void handler);
  void removeEventListener(string eventType, (Event) => void handler);
  Element querySelector(string selector);
  Element? closest(string selector);
};

extern class Document {
  Element createElement(string tagName);
  Element getElementById(string id);
  Element body { get; }
};

extern Document document;
```

Trim what you genuinely never use (this is still "describe exactly the members you
use," not "always paste everything") — but trim it *after* writing the app, not before,
so the trim is informed by what actually compiled, not a guess at what a template will
eventually need.

```ks
extern class VElement {
  static VElement Create(string tag);
  string TextContent { get; set; }
  string ClassName { get; set; }
  string Id { get; set; }
  string Value { get; set; }
  string RawHtml { get; set; }
  // Real bool properties, not attribute strings — a boolean attribute is on
  // whenever it's present at all, so only a real property can turn it back off.
  bool Disabled { get; set; }
  bool Checked { get; set; }
  (Event) => void OnClick { get; set; }
  (Event) => void OnInput { get; set; }
  (Event) => void OnBlur { get; set; }
  (Event) => void OnChange { get; set; }
  void AppendChild(VElement child);
  void SetAttr(string name, string value);
  // Embeds a live child Component as this slot's content — see "Nested
  // component composition" below. Declared taking a concrete Component
  // rather than Kopular's own Mountable interface: extern declarations
  // don't model inheritance/interface conformance between separate extern
  // classes, and every real caller passes a Component subclass anyway.
  static VElement Mount(Component component);
} from "kopular/velement";

// The runtime half of `styles from "./x.css";` (see "Scoped styles" below)
// — the compiler splices a call to this into the constructor. Listed here
// for reference: `using "kopular";` already brings it in, and declaring it
// yourself on top of that is a real KS4002 conflict.
extern class ScopedStyles {
  static void Inject(string id, string css);
} from "kopular/vdom";

extern class Component {
  constructor();
  virtual VElement Render();      // `virtual` here is what lets your subclass `override` it
  // Renders a fallback UI instead of an uncaught crash if Render() throws
  // — see "Component" below. Purely additive; not overriding it keeps
  // today's exact (uncaught) behavior.
  virtual VElement RenderError(string message);
  virtual void AfterRender(Element root); // see "Component" below
  void Mount(Element parent);
  void Update();
  // Called once when this component is removed from its parent's tree via
  // VElement.Mount above — see "Nested component composition" below.
  virtual void OnUnmount();
} from "kopular/component";

extern class Router {
  constructor(Component notFoundPage);
  void AddRoute(string path, Component page);
  // Real code-splitting — see "Router" below. loader is a real dynamic
  // import(), reached via a hand-written loader shim.
  void AddLazyRoute(string path, () => task<Component> loader);
  // Every registered path (AddRoute + AddLazyRoute), in registration
  // order — see "Router" below.
  string[] AllPaths();
  // Overridable outlet content shown while an AddLazyRoute page's loader
  // is in flight — default: `<div class="router-loading">Loading...</div>`.
  // Only declare this if you actually override it (a class extending
  // Router) — see "Lazy routes" below.
  virtual VElement BuildLoadingPlaceholder();
  void Navigate(string path);
  void Mount(Element parent);
  // One guard for the whole Router, not per-route — see "Router" below.
  void SetGuard(string redirectPath, (string) => bool guard);
  // The FIRST captured :name segment from whatever route just matched —
  // "" if the matched route has no dynamic segment. Not state<T>; no
  // Subscribe() needed — see "Common mistakes" below for why.
  string Param { get; }
  // Any captured :name segment (or a trailing * wildcard, as "*") by
  // name, for a route with more than one — see "Router" below.
  string Params(string name);
  // A query-string value by key ("" if absent), independent of which
  // route matched.
  string Query(string key);
} from "kopular/router";

extern VElement If(bool condition, () => VElement whenTrue, () => VElement whenFalse) from "kopular/directives";

extern class Response {
  bool ok { get; }
  number status { get; }
  task<string> text();          // no `async` on an extern signature — see KopScript's own LLM.md
};
extern class Http {
  static task<Response> Get(string url);
  static task<Response> Post(string url, string jsonBody);
  static task<Response> Put(string url, string jsonBody);
  static task<Response> Patch(string url, string jsonBody);
  static task<Response> Delete(string url);
} from "kopular/http";

extern class Validators {
  static string? Required(string value);
  static string? MinLength(string value, number min);
  static string? MaxLength(string value, number max);
  static string? Email(string value);
  static string? Min(number value, number min);
  static string? Max(number value, number max);
} from "kopular/forms";
```

`extern class` supports its own `<T>` (kopscript >= 0.5.0), the same rules as a real
generic class — describe `FormField<T>` generically instead of per concrete type:

```ks
extern class FormField<T> {
  constructor(T initial, (T) => string? validate);
  state<T> Value;
  state<string?> Error;
  state<bool> Touched;
  void Touch();
  bool Valid();
} from "kopular/forms";

FormField<string> email = new FormField<string>("", (string v) => Validators.Email(v));
```

`Value`/`Error`/`Touched` are declared as bare properties (`state<T> Value;`, no
`{ get; }`) — `extern class` supports a plain field declaration for exactly this case,
not just get/set accessor pairs.

`Computed1`/`Computed2`/`Resource<T>` (needs `kopscript >= 0.24.0` for `extern enum`,
which `AsyncStatus` requires):

```ks
extern class Computed1<A, R> {
  constructor(state<A> source, (A) => R compute);
  state<R> Value;
} from "kopular/computed";
extern class Computed2<A, B, R> {
  constructor(state<A> a, state<B> b, (A, B) => R compute);
  state<R> Value;
} from "kopular/computed";

extern enum AsyncStatus { Loading, Success, Failure } from "kopular/resource";
extern class Resource<T> {
  constructor(task<T> operation);
  state<AsyncStatus> Status;
  state<T?> Data;
  state<string?> Error;
} from "kopular/resource";
```

You also need your own ambient DOM `extern` block (`document`, `Element`, `Event`, ...) —
Kopular's own copy in `dom.ks` isn't reachable across the package boundary; redeclare the
handful of members you actually use. See KopularDemo's `src/kopular_bindings.ks` for a
complete, real example of both.

## `Component` (`component.ks`, `velement.ks`, `vdom.ks`) — real vdom diffing

```ks
class MyWidget : Component {
  private number count;

  constructor() : base() {
    this.count = 0;
  }

  public override VElement Render() {
    VElement el = VElement.Create("div");
    el.TextContent = "Count: " + this.count;
    return el;
  }

  public void Bump() {
    this.count = this.count + 1;
    this.Update();       // re-runs Render(), diffs it against the previous tree, patches real DOM
  }
}

MyWidget w = new MyWidget();
w.Mount(document.body);  // first Render() + materialize + append
w.Bump();                // re-render + diff + patch
```

- `Render()`: `virtual`, override it to describe the current state as a `VElement` tree —
  a lightweight description of a DOM element (`kopular/velement`), not a real one. Called
  by both `Mount()` and `Update()`. Provide it as a real markup file via KopScript's
  `template from "./x.html";` (see "Templates" below) instead of hand-writing it — both
  produce the exact same method; Kopular needed no framework code changes to support this.
- `VElement`: `Tag`, `TextContent`/`ClassName`/`Id`/`Value` (direct fields — `Value` is the
  one that must be a live DOM *property*, not an attribute: `SetAttr("value", x)` sets the
  default value, not the current one), `RawHtml` (an opaque, undiffed leaf — set instead of
  `TextContent`/children, for a raw-HTML-then-wire-handlers pattern — **sets real
  `innerHTML`, unescaped: only ever assign static/developer-authored content — a `raw
  string ... from "<path>.html";` compile-time constant, in every real use in this
  ecosystem today — never anything reachable from user input, a fetched `Http` response
  body, or a `FormField<T>`'s `.Value`, or it's a real XSS hole**), the four fixed named
  event fields `OnClick`/`OnInput`/`OnBlur`/`OnChange` (each a real no-op by default, never
  null — no nullable function type to fall back on), `AppendChild(child)`, and
  `SetAttr(name, value)` (the escape hatch for any other real HTML attribute — `href`,
  `src`, `alt`, `placeholder`, ...; never `Value`, see above). `Id` doubles as a stable key
  for list-child reconciliation — see `PatchChildren` below.
- `Mount(parent)`: calls `Render()` once, materializes the returned tree into real DOM
  (`Materialize` in `vdom.ks`), appends it to `parent`, then calls `AfterRender(root)`.
- `Update()` (protected — called from within the component, not externally): calls
  `Render()` again and `Patch`es the new tree against the previous one (`vdom.ks`), reusing
  a real DOM node wherever a node's tag stays the same instead of rebuilding it — sibling
  nodes untouched by the change keep their exact identity (`===`), not just their content.
  A list child without a stable `Id` still ends up correct after a reorder, but isn't
  guaranteed to keep its own real node (see `PatchChildren`'s keyed-vs-positional matching
  in `vdom.ks`). Then calls `AfterRender(root)`.
- **Batching**: `Update()` calls made while a real DOM event handler Kopular itself
  attached is still running (any `OnClick`/`OnInput`/`OnBlur`/`OnChange` set on a
  `VElement`) don't each render immediately — they coalesce into ONE render per affected
  component, applied once the handler returns:
  ```ks
  private void Save() {
    Item newItem = new Item(this.Draft);      // whatever this handler just built
    this.Draft = "";                          // a plain field Render() also reads
    this.Items.Value = this.Items.Value.Push(newItem);  // triggers the ONE render, via Subscribe
  }
  ```
  Before batching, whichever line triggered `Update()` first would render with whatever
  the OTHER field held at that exact moment — a handler mutating a plain field AFTER the
  line that happens to trigger a `state<T>`-driven `Update()` would render with that
  field's STALE value, since nothing re-rendered again afterward to pick up the correction.
  With batching, a handler's own statement order no longer matters for what its eventual
  render sees — both statements above render correctly regardless of which comes first.
  Fully synchronous, no microtask: by the time a real `dispatchEvent` call returns, every
  affected component (including a shared service's OTHER `Subscribe`d sibling components)
  has already re-rendered, so a test's very next assertion still sees the final result. A
  `state<T>` write from OUTSIDE a Kopular-attached handler (a `setTimeout`/`setInterval`
  callback, an awaited `Http`/`task` continuation, a direct top-level call) is never
  batched — `Update()` still renders immediately there, exactly as before batching existed.
- `AfterRender(root)`: `virtual`, a no-op by default, called at the end of both `Mount()`
  and `Update()` with the real, now-materialized/patched root `Element`. For a component
  that needs to do further imperative work against its own real DOM — `Router`'s own use
  of this to mount its matched page into a single, always-present outlet is the one real
  user today (see "Dependency injection" below for `RoutedApp` doing the same with `Router`
  itself); for a *dynamic* parent/child relationship, use `VElement.Mount` below instead.
- **Nested component composition — `VElement.Mount(component)`**: embeds a live child
  `Component` directly as a `VElement` tree's own content, so a parent's re-render creates/
  patches/reorders/tears it down declaratively, the same as any other content mode:
  ```ks
  public override VElement Render() {
    VElement ul = VElement.Create("ul");
    this.Items.ForEach((TodoItem item) => {
      VElement slot = VElement.Mount(item);   // item is a Component
      slot.Id = item.Id;                       // stable key, same convention as any list
      ul.AppendChild(slot);
    });
    return ul;
  }
  ```
  `item.Id` above means exactly what it looks like: for a keyed list of mounted children,
  the mounted `Component` itself needs its own public `Id` (or similarly-named) field/
  property to copy onto `slot.Id` — `VElement.Id` lives on the wrapper slot, not on the
  component, so there's nothing to key by without one. Set it however suits the type
  (constructor param, or a plain field assigned right after construction).
  The SAME `Mountable` instance still in a slot across a re-render is patched in place
  (`Update()` inside that child re-renders just its own subtree, siblings untouched); a
  DIFFERENT instance (or the slot disappearing) tears the old one down first — calling its
  `OnUnmount()` — then mounts the new one fresh. Removing a component that itself mounted
  further children tears the whole subtree down, however many levels deep; a middle
  component overriding its own `OnUnmount()` doesn't skip its children's.
  - `OnUnmount()`: `virtual`, a no-op by default, called once when a mounted component is
    removed or replaced. Override it to release anything held onto that would otherwise
    outlive the removal — most commonly, calling the unsubscribe handle `state<T>.Subscribe`
    now returns (see `kopscript`'s own `LLM.md`) for a subscription made in the constructor.
    A component built once at app startup and never removed (every `Router` page, most real
    apps' top-level structure) never needs this at all.
  - `Router`'s own outlet uses this mechanism too — `Render()` embeds the matched page via
    `VElement.Mount`, not the older `AfterRender` pattern above (that migration landed once
    this existed; a single, always-present slot never needed the reordering/removal part,
    but there was no reason not to use the same real mechanism everywhere).
  - Templates get the same capability via `*mount="expr"` — see `kopscript`'s own `LLM.md`
    for the syntax; it desugars to exactly this `VElement.Mount` call.
- **Content projection (React's `children`/Angular's `<ng-content>`) needs no separate
  mechanism** — pass a `() => VElement` into a child's constructor and call it from inside
  its own `Render()`; it's invoked fresh every render and closes over the parent's `this`,
  so projected content reflects the parent's current state on every patch:
  ```ks
  class Card : Component {
    private () => VElement ContentBuilder;
    constructor(() => VElement contentBuilder) : base() { this.ContentBuilder = contentBuilder; }
    public override VElement Render() {
      VElement div = VElement.Create("div");
      div.AppendChild(this.ContentBuilder());
      return div;
    }
  }
  ```
  More than one slot: one named `() => VElement` callback per slot (same fixed-named-slot
  convention as `VElement`'s own `OnClick`/etc.), not a generic bag.
- **`Update()` before `Mount()` is a safe no-op**, not an error. This matters for two
  sibling `Component`s (e.g. two `Router` pages) that share one injected service's
  `state<T>` and both `Subscribe()` it — every route's page is constructed eagerly (see
  `Router.AddRoute`), so at any given time most of them are constructed but never
  `Mount()`ed. Changing that shared state fires `Subscribe` on all of them, including the
  ones that aren't the currently-routed page — `Update()` just skips the render/patch for
  those, since `Mount()` will run a fresh `Render()` anyway whenever one of them actually
  becomes routed.
- `RenderError(string message)`: `virtual`, called by `Mount()`/`Update()` (via a private
  `SafeRender()` wrapper) when `Render()` throws, instead of letting the exception
  propagate uncaught and crash whatever triggered the render (a click handler, a `Router`
  navigation). The base implementation just `throw`s `message` again — **purely additive,
  opt-in error recovery**; a `Component` that never overrides `RenderError` behaves
  exactly as before this existed. Override it to show a fallback UI instead:
  ```ks
  protected override VElement RenderError(string message) {
    VElement el = VElement.Create("div");
    el.TextContent = "Something went wrong: " + message;
    return el;
  }
  ```
  A later successful `Update()` (e.g. from a "Retry" button in that fallback calling back
  into the component) renders normally again — there's no separate "broken" state to
  reset, `SafeRender()` just tries `Render()` again like any other `Update()`.

## Templates — `template from` (see KopScript's own `LLM.md` for the full syntax)

```ks
class Counter : Component {
  public state<number> Count;
  constructor() : base() { this.Count = state(0); }
  public void Increment() { this.Count.Value = this.Count.Value + 1; }
  template from "./counter.html";   // replaces Render() entirely — cannot coexist with a hand-written one
}
```
```html
<!-- counter.html -->
<button (click)="Increment()">Count: {{ Count.Value }}</button>
```

- `{{ expr }}` interpolation, `(event)="stmt"`, `[prop]="expr"`, `*if="expr"`,
  `*for="Type varName of expr"` — all real KopScript, checked at compile time, desugared
  to the exact same `VElement.Create`/`.AppendChild`/`.TextContent`/named-event-field
  calls a hand-written `Render()` would use. `(event)` only accepts `click`/`input`/
  `blur`/`change` — `VElement`'s own fixed set — anything else is a compile error
  (`KS5016`). `[prop]`/static `attr="..."` assign directly for `id`/`className`/`value`
  **and the two real bool fields, `disabled`/`checked`** (the compiler's own
  `BOOL_FIELD_NAMES` map — a boolean attribute is present-or-absent, so only a real
  property can turn one back off); anything else goes through `SetAttr` instead.
- **`*mount="expr"`** embeds a live child Component declaratively — desugars to
  `VElement.Mount(expr)`, the same mechanism a hand-written `Render()` uses (see "Nested
  component composition" below). Unlike `*if`/`*for`, it composes with either — `*for="Row
  r of Rows" *mount="r"` (one mounted child per loop item) is the headline case, not an
  error. See kopscript's own `LLM.md`/`README.md` for the full syntax.
- A `state<T>` field declared directly on the class and referenced directly in the
  template (`Count` above) gets `Subscribe((v) => this.Update())` wired automatically —
  no manual `Subscribe` in the constructor for that field. State reached indirectly
  (through a method, or `this.SomeService.Count`) still needs a manual `Subscribe`, same
  as a hand-written `Render()` always has.
- **Two-way binding**: `[(value)]="Field"` desugars to `[value]="Field"` +
  `(input)="Field = e.target.value"` — `value` only, and `Field` must be a bare name or
  `this.Field` (see KopScript's own LLM.md `KS5017`/`KS5018` for the two rejected cases).
- One top-level element per template (hard error otherwise); no mixing text and element
  children under one element (`VElement` has no text-node sibling concept, only
  `.TextContent`); no pipes, at most one of `*if`/`*for` per element (`*mount` is
  orthogonal to both — see above — and isn't included in that limit).
- This is entirely a KopScript compiler feature (parsed/desugared before type-checking
  runs) — Kopular's own framework code (`component.ks`, `dom.ks`) is unmodified and
  unaware templates exist; a template-generated `Render()` is indistinguishable from a
  hand-written one to every other part of the framework.

## Scoped styles — `styles from` (see KopScript's own `LLM.md` for the CSS-parsing reference)

```ks
class Widget : Component {
  constructor() : base() { }
  template from "./widget.html";
  styles from "./widget.css";
}
```

- Independent of `template from` — works with a hand-written `Render()` too. The compiler
  rewrites the referenced `.css` so every selector requires a per-class
  `data-kop-scope="<id>"` attribute, then splices one `ScopedStyles.Inject(id, css);` call
  into the constructor. **This means `ScopedStyles` must be `extern`-declared in your own
  project the same as `Component`/`VElement`/etc. (see the copy-paste block above) — it is
  NOT auto-imported just because you wrote `styles from`.** Forgetting it is a real `KS4048
  Undefined identifier 'ScopedStyles'` at the constructor the compiler spliced the call
  into. **Unlike `template from`, this needed real framework code**:
  `ScopedStyles` (`vdom.ks`, new) — idempotent, static-array-registry dedup shape same as
  `Batching`, injects one real `<style>` per component *type* (not per instance) into
  `document.head` (`dom.ks`'s `head { get; }`, new) the first time any instance is
  constructed, never removed afterward.
- `template from` elements get `data-kop-scope` automatically on every element
  `buildElement` produces. A hand-written `Render()` gets a `protected string ScopeId;`
  field instead — apply it manually: `el.SetAttr("data-kop-scope", this.ScopeId);`.
- `styles from` on a class with no constructor is a compile error (`KS3012` — nowhere to
  splice `Inject`), same shape as auto-subscribe's `KS3005` for a template referencing
  `state<T>` with no constructor.
- A `*mount`ed (or otherwise nested) child never inherits a parent's scope — each class's
  `styles from` gets its own independently-computed `id`, and a mounted child's elements
  come entirely from its own `Render()`/template, never the parent's.

## `Router` (`router.ks`)

Real URLs via the History API (`pushState`/`popstate`), not hash routing.

```ks
class NotFoundPage : Component {
  public override VElement Render() { return VElement.Create("h1"); }
}
class HomePage : Component {
  private Router Nav;
  constructor(Router nav) : base() { this.Nav = nav; }
  public override VElement Render() {
    VElement btn = VElement.Create("button");
    btn.OnClick = (Event e) => { this.Nav.Navigate("/about"); };
    return btn;
  }
}

Router nav = new Router(new NotFoundPage());   // NotFoundPage REQUIRED up front — no null route
nav.AddRoute("/", new HomePage(nav));           // pages built once, kept alive for Router's lifetime
nav.AddRoute("/about", new AboutPage(nav));
nav.Mount(document.body);
nav.Navigate("/about");                          // pushState + immediate re-render
```

- Constructor takes the fallback page (`Component`), required — there's no nullable
  "no match" representation.
- `AddRoute(path, page)` takes an **already-constructed `Component` instance**, not a
  factory — every registered page is built once and stays alive for the Router's whole
  lifetime, so a page's own `state<T>` fields survive navigating away and back.
- `Navigate(path)` calls `history.pushState` then re-renders immediately. A browser
  back/forward triggers re-render via a `popstate` listener registered in the
  constructor — `Navigate()` itself doesn't rely on that event.
- **A trailing slash normalizes to the same route as without one** (`/about/` == `/about`;
  `/` itself is left alone, not stripped to `""`) — matches every mainstream router's own
  default, and matters for real: a build-time prerendered static route's own
  `<path>/index.html` (see KopularDemo's `scripts/prerender.mjs`) is naturally reached via
  a trailing-slash URL.
- **Dynamic route segments**: a pattern segment written `:name` (e.g. `AddRoute("/dogs/:id",
  ...)`) matches any single non-empty path segment; every other segment must match
  literally (same segment count required too — `/dogs` does NOT match `/dogs/:id`, except
  a trailing `*` wildcard segment, see below). The FIRST captured value is always
  `Router.Param` — a plain `string` field (**not** `state<T>`; no `Subscribe()` needed —
  see below), `""` when the matched route has no `:` segment.
- **More than one dynamic segment, and a trailing wildcard**: `AddRoute("/dogs/:id/toys/:toyId",
  ...)` captures both; read either by name via `Router.Params(name)` (`Params("id")`,
  `Params("toyId")`) rather than the single `Param` field — `Param` still holds the first
  one either way. `AddRoute("/files/*", ...)` matches one or more remaining segments as a
  single captured group, joined back with `"/"`, available as `Router.Params("*")`
  (`/files/2026/reports/q1.pdf` -> `Params("*") == "2026/reports/q1.pdf"`). A route with a
  trailing `*` only needs *at least* as many path segments as its static prefix, not an
  exact count — the one exception to the "same segment count required" rule above.
- **Query strings**: `Router.Query(key)` returns the current URL's query-string value for
  `key` (`""` if absent), independent of which route matched — never part of route
  *matching* itself in v1, every route sees whatever query string is actually in the URL.
  `"/search?sort=name"` -> `Query("sort") == "name"`. Values are **not percent-decoded** —
  no `decodeURIComponent` binding exists yet, a deliberate v1 cut; `%20`/`+` arrive
  exactly as written in the URL, not converted to a space.
- **Why `Param` is a plain field, not `state<T>`**: `Router`'s own `Render()` already
  re-embeds the matched page (via `VElement.Mount`) on every `Navigate()`/`popstate`, which
  re-runs that page's own `Render()` (reading the fresh `Param`) with no extra step.
  Making it `state<T>` and having a page `Subscribe()` to it — the pattern every *other*
  piece of state in this framework uses — actually crashes: `Match()` sets it *before*
  the matched page is ever mounted into the outlet, so the very first route that matches a
  page nothing has `Mount()`ed yet fires that page's subscribed listener while its
  inherited `Update()` still has no `ParentElement` to patch into.
- **Lazy routes (real code-splitting) — `AddLazyRoute(path, loader)`**: like `AddRoute`,
  but `loader` is a `() => task<Component>` instead of an already-built page — its JS chunk
  is only ever fetched the first time the route actually matches, not eagerly with
  everything else at startup. `loader` is a real dynamic `import()`, reached via a
  hand-written loader shim and a relative `extern` (see `kopscript`'s own "extern" docs —
  `kopscript@0.23.0` fixed a real bug in this exact path):
  ```js
  // dogs_page_loader.js
  export async function LoadDogsPage() {
    const { DogsPage } = await import("./dogs_page.js");
    return new DogsPage();
  }
  ```
  ```ks
  extern task<Component> LoadDogsPage() from "./dogs_page_loader";
  nav.AddLazyRoute("/dogs", LoadDogsPage);
  ```
  The outlet shows a plain loading placeholder (`<div class="router-loading">Loading...</div>`
  by default; override `protected virtual VElement BuildLoadingPlaceholder()` on a class
  extending `Router` to customize) while the fetch is in flight; the loaded page is cached
  after the first fetch, same as an eager page — navigating away and back reuses it, no
  re-fetch. Mixes freely with `AddRoute` in the same `Router`.
  - **Building it for real**: the whole point of a lazy route is that `dogs_page.ks` is
    deliberately *not* `using`'d from your app's own entry file — that's what keeps its
    code out of the eager bundle. That also means your normal build command (`ks build
    src/app.ks`) never compiles it, since `ks build`/`compileGraph` only walk the `using`
    graph reachable from the entry you give them. Build the lazy page as its own separate
    entry too, e.g. `ks build src/app.ks && ks build src/dogs_page.ks` in your build
    script (or one `ks build` invocation per lazy route, if you have several) — each
    already-compiled shared dependency just gets written again with identical output, so
    this is safe to add without restructuring anything else.
  - **Testing it**: `kopular/testing`'s `runKopularApp` (kopular 0.24.0+) compiles any
    other real `.ks` file present in your app's own `srcDir` that the entry doesn't
    reach — a lazy-route target is exactly that — so a test exercising `AddLazyRoute`
    needs no special setup: write `dogs_page.ks`/`dogs_page_loader.js` into the same
    directory as your entry file, same as any other page, and the loader's
    `import("./dogs_page.js")` finds a real, freshly-compiled file. (`runKopularFixture`,
    used only by Kopular's own internal test suite, does not do this — it copies
    Kopular's entire framework source tree alongside a fixture, where the same behavior
    would mean recompiling most of the framework on every test.)
- **`AllPaths(): string[]`** — every registered path (`AddRoute` + `AddLazyRoute`), in
  registration order. For enumerating real routes (a build-time prerender step, most
  likely) without a second, hand-maintained list.
- **Navigation guards**: `SetGuard(redirectPath, (string) => bool guard)` — `guard` is
  called with the target path before every navigation (including a direct load/refresh);
  returning `false` redirects to `redirectPath` (via `pushState`, so the URL updates too —
  a refresh on the blocked path lands on the redirect again, not back on the rejected
  page). One guard for the whole `Router`, not per-route — `guard` itself decides which
  paths it cares about (`if (path == "/admin") { return loggedIn.Value; } return true;`).
  Defaults to always-allow (a real `(string path) => true` function, not `null` — a
  nullable *function type* has the same "can't parenthesize for postfix `?`" problem as
  an array of one) until `SetGuard` is called. `redirectPath` is never itself
  guard-checked — pick one `guard` always allows.
- **Every deployment target needs its own SPA/history-fallback config — this is
  unavoidable, not a Kopular gap.** A direct load or refresh at `/about` is a plain HTTP
  request that reaches your host *before* any JS (Router included) has run, so no
  client-side router in any language/framework can intercept it; the host itself has to
  respond with the app shell for any route it doesn't have a literal file for. Configure
  this on every host you deploy to, not just in local dev:
  - Local dev (`ks watch` + a static file server): see KopularDemo's `scripts/serve.mjs`
    — falls back to `index.html` only for an extension-less path, so a genuinely missing
    `.js`/`.css` still 404s.
  - Cloudflare Workers (what KopularDemo itself deploys to): `wrangler.jsonc`'s
    `assets.not_found_handling: "single-page-application"`. Coarser than `serve.mjs` —
    it falls back for *any* unmatched request, extension or not, so a typo'd asset URL
    silently serves the app shell instead of 404ing (confirmed via `wrangler dev`; no
    config short of a custom Worker distinguishes the two cases).
  - Any other static host (Netlify, Vercel, S3+CloudFront, nginx, ...) has an equivalent
    "SPA fallback" / "custom 404 → index.html" option — look for that host's own docs on
    single-page-application routing, the terminology is standard across all of them.

## `If` (`directives.ks`) — the `*ngIf` equivalent for a hand-written `Render()`

In a **template**, `*if="expr"`/`*for="Type v of expr"` are the direct equivalents (real
`if`/`for` under the hood — see "Templates" above), no helper needed. This section is for
a **hand-written** `Render()`, where `if` being a statement (not an expression) means a
conditional value needs a helper to get one out of it:

```ks
VElement If(bool condition, () => VElement whenTrue, () => VElement whenFalse)
```

```ks
root.AppendChild(If(this.IsLoggedIn, () => this.BuildProfile(), () => this.BuildLogin()));
```

Both branches always required (no null "nothing" value); only the branch actually taken
is called — the other lambda never runs. `*ngFor` and `*ngSwitch` need no Kopular helper
at all in hand-written `Render()` either:

```ks
// *ngFor — plain array method
this.Items.ForEach((Item item) => { list.AppendChild(this.BuildItemRow(item)); });

// *ngSwitch — plain KopScript `match` expression (exhaustiveness-checked, unlike *ngSwitch)
root.AppendChild(match this.Status {
  "loading" => this.BuildSpinner(),
  "error" => this.BuildError(),
  _ => this.BuildContent()
});
```

**Keyed/reuse-existing-DOM-nodes diffing (the `*ngFor trackBy` angle) exists** — give each
item's `VElement` a stable `.Id` (e.g. the item's own id) and `Update()`'s diff engine
(`PatchChildren` in `vdom.ks`) matches children by `Id` across a re-render, reusing a
matched child's real DOM node rather than rebuilding it. Without a stable `Id`, a
reordered list still renders correctly, but a given item's real node isn't guaranteed to
follow its data.

## `Http` (`http.ks`) — thin wrapper over `fetch`

```ks
Response r = await Http.Get(url);            // task<Response>
Response r = await Http.Post(url, jsonBody); // string body, Content-Type: application/json
Response r = await Http.Put(url, jsonBody);
Response r = await Http.Patch(url, jsonBody);
Response r = await Http.Delete(url);         // no body param — DELETE has none

r.ok       // bool
r.status   // number
await r.text();   // task<string> — the raw body, nothing more
```

**No typed JSON deserialization** — KopScript's generics are classes/interfaces only (no
generic functions/methods), so there's no safe `task<T> Get<T>(string url)`.
Get a typed response by describing its shape as its own `extern class` and parsing with
a per-shape `extern ... as "JSON.parse"` (unchecked, same trust model as every other
`extern`):

```ks
extern class DogDto { string name { get; } };
extern DogDto ParseDog(string json) as "JSON.parse";

DogDto dog = ParseDog(await (await Http.Get(url)).text());
```

`Get`/`Delete` need no request body, so they bind straight to the real global `fetch` —
no object literal involved (KopScript has none). `Post`/`Put`/`Patch` (and a
hypothetical `Delete`-with-a-body) need one for `{ method, headers, body }`, which
KopScript categorically cannot construct — Kopular ships one small hand-written JS
function (`http_runtime.js`, not compiled from `.ks`) that does, for exactly that reason.

## `FormField<T>` / `Validators` (`forms.ks`)

```ks
FormField<string> email = new FormField<string>("", (string v) => {
  string? required = Validators.Required(v);
  if (required != null) { return required; }
  return Validators.Email(v);
});

email.Value.Value = "x";        // state<T> — revalidates automatically on assignment
email.Error.Value               // string? — current validator's message, or null
email.Touched.Value             // bool — only true after Touch() (call on blur)
email.Touch();
email.Valid();                  // bool — Error.Value == null
```

`Validators.Required/MinLength/MaxLength/Email` are `(string) => string?`;
`Validators.Min/Max` are `(number) => string?`. Each returns an error message or `null`.

**No array-of-validators parameter** — KopScript has no array-of-function-values type
(`((T) => string?)[]` doesn't parse: the parser reads a second `(...)  => ...` immediately
after the first as a nested function type, not an array element type, and errors expecting
`=>`). Combine checks as an if-chain in one lambda (see the `email` example above), or via
the fixed-arity `CombineValidators2<T>`/`CombineValidators3<T>` free functions (generic,
inference-only — see `KopScript`'s own "Generics" docs for why they're *free* functions, not
`Validators` static methods):
```ks
CombineValidators2((string v) => Validators.Required(v), (string v) => Validators.Email(v))
// -> a single (string) => string? — Required first, Email only if that passes
```
Fixed-arity (2, 3 — add more the same way if a form ever needs to chain further), not a
general `Validators.All(...)`, for the same array-of-function-values reason above.

**No DOM binding in a hand-written `Render()`** — wiring `.Value` to a real `<input>` is a
plain `VElement.OnInput` assignment reading `e.target.value` (the same as any other event
handler; `VElement.Value` itself is one-way, host-to-DOM only), and reading it back out
via `.Touch()` on `OnBlur`. A **template** has real `[(value)]="Field"` sugar for the
value-binding half (see "Templates" above); `Touch()` on blur still needs its own explicit
`(blur)="Field.Touch()"` either way — `[(value)]` only ever wires `value`/`input`.

## `Computed1<A, R>` / `Computed2<A, B, R>` (`computed.ks`)

```ks
state<number> price = state(10);
state<number> qty = state(2);
Computed2<number, number, number> total = new Computed2<number, number, number>(
  price, qty, (number p, number q) => p * q);

total.Value.Value              // 20 — recomputed whenever price OR qty changes
total.Value.Subscribe((number v) => { ... });  // returns an unsubscribe handle, same as any state<T>
```

`useMemo`/Angular signals' `computed()` equivalent — built entirely on `state<T>`'s own
`.Value`/`.Subscribe`, no new reactive primitive. `Value` is `state<R>`, not a bare `R` —
KopScript's property grammar has no custom-getter syntax, so a computed value can't expose
a self-recomputing property; `state<R>` already gives the right two members instead.
Dependencies are explicit constructor arguments, not automatically tracked. Fixed arity (1
source, 2 sources) — same array-of-function-values reason `CombineValidators2/3` are fixed;
add a `Computed3<A, B, C, R>` the same way if ever needed.

## `Resource<T>` (`resource.ks`)

```ks
enum AsyncStatus { Loading, Success, Failure }

Resource<Response> r = new Resource<Response>(Http.Get(url));  // task already in flight
r.Status.Subscribe((AsyncStatus s) => this.Update());

match r.Status.Value {
  AsyncStatus.Loading => ...,
  AsyncStatus.Success => ... r.Data.Value ...,   // T?
  AsyncStatus.Failure => ... r.Error.Value ...   // string?
};
```

The loading/success/failure shape around a `task<T>`, as `state<T>` a `Component` renders
and `Subscribe`s to — most commonly wrapping an `Http` call. `Status` starts at
`AsyncStatus.Loading` immediately; the constructor's own handling of the task is
fire-and-forget (there's no way to construct a `task` value outside an `async` function
body to `await` it from the constructor itself — see "Async" above), so the caller must
pass an ALREADY-STARTED task (call the `async` function first — `Http.Get(url)`, not
something the constructor starts). `Status`/`Data`/`Error` transition together, exactly
once, inside a `try`/`catch` around the `await` — whichever branch the task actually takes.

## `kopular/testing` (`testing.js`, hand-written JS, not compiled from `.ks`)

```ts
import { runKopularApp } from "kopular/testing";

const { window, cleanup } = await runKopularApp(srcDir, "app.ks", {
  includeKopularPackage: true,   // app consumes Kopular via `extern`, not relative `using`
  extraFiles: ["config.json"],   // any sibling with an extension besides .ks/.html/.js
  fetchMock: (...args) => Promise.resolve({ ok: true, status: 200, text: () => Promise.resolve("body") }),
  url: "http://localhost/",
});
try {
  window.document.querySelector(...)
} finally {
  cleanup();  // always call, even on a thrown assertion — restores globalThis + deletes the temp dir
}
```

Compiles `entryFileName` (plus everything else in `srcDir` it `using`s) via kopscript's
`compileGraph`, binds jsdom onto `globalThis` (`document`/`Element`/`Event`/`location`/
`history`/`window`/`fetch`) for the compiled ambient `extern` declarations to find, and
runs it. Every `.ks`/`.html`/`.js`/`.css` file in `srcDir` is copied along with it — `.js`
included specifically so a hand-written (not compiled) sibling like a `Router.AddLazyRoute`
loader shim just works with no extra setup, `.css` the same way for a `styles from
"<path>.css";` stylesheet; `extraFiles` is only for a real dependency with some OTHER
extension (e.g. a `.json` config file). `runKopularFixture(source, options)` also takes
an `options.extraSource` (`Record<string, string>` — filename to inline content) for
supplying a fixture's own auxiliary `.html`/`.css` file without a real file on disk, since
`runKopularFixture` only ever writes the one entry-file string you pass it. This is the sibling export for an inline fixture
string instead of a real file (used by Kopular's own test suite; copies Kopular's *own*
`.ks` sources alongside the fixture, so `using "./component"` resolves — only meaningful
for testing Kopular itself, not an external consumer, which should use `runKopularApp`
with `includeKopularPackage: true` instead).

`jsdom` is an optional peer dependency — add it to your own project to use this.

## Dependency injection — no container, no decorators

There is no injector, no `@Injectable`, no provider tokens. "Injecting" a service is
passing it as a constructor argument — the compiler enforces it (a missing/mistyped
dependency is a build error). For an app with more than a couple of services, use one
plain "composition root" class (not a `Component`, never touches the DOM) that builds
the whole service/page graph exactly once and hands the finished pieces to whatever
needs them:

```ks
class AppContainer {
  public Router Nav;
  constructor() {
    CounterService counter = new CounterService();   // shared singleton: pass the same
    this.Nav = new Router(new NotFoundPage());        // instance wherever it should be
    this.Nav.AddRoute("/", new HomePage(this.Nav, counter));
  }
}

class RoutedApp : Component {
  private Router Nav;
  constructor(AppContainer services) : base() { this.Nav = services.Nav; }
  public override VElement Render() {
    return VElement.Create("div");
  }
  // A single, always-present slot — nothing to reorder or remove — so the
  // older AfterRender pattern is fine here; VElement.Mount (see "Component"
  // above) is for anything genuinely dynamic instead.
  protected override void AfterRender(Element root) {
    this.Nav.Mount(root);
  }
}

RoutedApp app = new RoutedApp(new AppContainer());
app.Mount(document.body);
```

A service needing its own state/logic is a plain class — no base class, no
decorators, nothing framework-specific:

```ks
class CounterService {
  public state<number> Count;
  constructor() { this.Count = state(0); }
  public void Increment() { this.Count.Value = this.Count.Value + 1; }
}
```

## Common mistakes (seeded from real generation failures)

- **`VElement.RawHtml` sets real `innerHTML`, completely unescaped — never assign it
  anything reachable from user input.** It exists for a raw-HTML-then-wire-a-delegated-
  listener pattern (see `header.ks`/`nav.ks` in KopularDemo), and every real use in this
  ecosystem is a `raw string ... from "<path>.html";` compile-time constant — genuinely
  static content, baked in at build time, never a runtime value. There is nothing in the
  type system stopping `el.RawHtml = someFetchedString;` or `el.RawHtml =
  formField.Value.Value;` from compiling — both are a real XSS hole if that content is
  ever attacker-influenced. Use `TextContent` (always escaped) for any dynamic string;
  `RawHtml` is for static markup only.
- **`Router.Param` is a plain `string`, not `state<T>` — don't `Subscribe()` to it.** A
  `state<T>`-based design for it was tried and genuinely crashes: `Navigate()` sets `Param`
  *before* the newly-matched page finishes mounting, so a page `Subscribe`-ing to it fires
  while its own `Update()` still has no `ParentElement` to patch into. `Render()` already
  re-reads the fresh `Param` on every navigation with no extra step — just read
  `this.Nav.Param` directly inside `Render()`.
- **Deploying a `Router`-based app needs SPA/history-fallback configured on the actual host**
  — this bit the real `KopularDemo` production site (worked when navigated to via a link,
  404'd on a direct refresh) before its Cloudflare Workers config had
  `not_found_handling: "single-page-application"` set. Don't assume `ks build`/`npm run
  build` alone is enough for a routed app to work on refresh at a non-root path — see
  "Router" above for the exact config.
- **Cross-package consumption always goes through `extern`, never `using`.** `using
  "kopular/component"` doesn't compile — `using` only resolves relative same-project paths.
  Redeclare the members you need as `extern ... from "kopular/component"` instead (see
  "Consuming Kopular from your own KopScript project" above) even though it looks like it
  should "just work" the way an npm `import` would.
- **A template's `*if`/`*for` and the hand-written `If()`/`.ForEach()` helpers are two
  independent mechanisms, not the same thing wired two ways** — a template never needs
  `directives.ks`'s `If()` imported or called; `*if`/`*for` compile to real `if`/`for`
  directly. Don't mix a template with a hand-written-`Render()` helper call.
- **`[(value)]` two-way binding is template-only, and `value`-only.** A hand-written
  `Render()` still always needs an explicit `OnInput` assignment reading
  `e.target.value` — there's no equivalent shorthand there, since it already has direct
  field access. `[(id)]`/`[(className)]` don't exist either, even in a template — the
  sugar only wires `value`/`input`, the one pairing with a real "user just changed this"
  event.
- **A `VElement` event binding only accepts `click`/`input`/`blur`/`change`** — both in a
  template's `(event)="..."` and a hand-written `OnClick`/`OnInput`/`OnBlur`/`OnChange`
  assignment. There's no generic `addEventListener` on `VElement` (event handlers are part
  of the tree's own data, not wired against a live DOM node until `Materialize`/`Patch`
  runs) — an event Kopular doesn't have a named field for isn't reachable from `Render()`
  at all yet.
- **A `Component` embedded via `AfterRender`/`Mount()` (imperative) is NOT the same as one
  embedded via `VElement.Mount()` (declarative, part of the tree)** — only the latter gets
  real reconciliation (patch in place, reorder, tear down); this includes `Router`'s own
  outlet, which uses `VElement.Mount` internally, not the older `AfterRender` pattern. If
  you find yourself calling `.Mount()`/`.Update()` on a child by hand from `AfterRender`
  for anything that needs to be added, removed, or reordered, use
  `VElement.Mount(component)` in `Render()` (or `*mount="expr"` in a template) instead —
  see "Component" above.

## Does not exist

DI container/injector · decorators (`@Injectable`, `@Component`, ...) · a runtime
template engine or interpreted expression language — templates compile to the same
imperative `Render()` code as the hand-written form, checked at compile time, not
interpreted at runtime (see "Templates" above) · two-way binding in a hand-written
`Render()`, or on anything but `value` even in a template (`[(value)]="Field"` exists —
see "Templates" above — but it's `value`-only, and templates-only) ·
a generic/arbitrary `VElement` event binding — only `click`/`input`/`blur`/`change` ·
pipes · animations · typed/generic HTTP responses (`Http` returns raw text — see above) ·
live, per-request SSR — no framework-level renderer ships for this, but build-time static
prerendering is a real, demonstrated pattern built entirely on existing pieces
(`Router.AllPaths()` + `kopular/testing`'s `runKopularApp`, no Kopular framework code
needed) — see KopularDemo's own `scripts/prerender.mjs`.

(`FormField<T>`/`Validators`, `kp new`, and embedding a child Component from a *template*
via `*mount="expr"` — see "FormField<T> / Validators", "Starting a new project", and
"Nested component composition" above — are all real, shipped features; they used to be
listed here as gaps before those landed and this list wasn't updated at the time. Leaving
this parenthetical rather than quietly deleting it, as a reminder to keep this list in
sync going forward.)
