<img src="https://cdn.jsdelivr.net/npm/kopular@latest/assets/logo.png" width="96" height="96" alt="Kopular logo">

# Kopular

Kopular is a small component framework for [KopScript](https://dev.azure.com/koppinator/Koppindependence/_git/KopScript),
built to give Angular's separation of concerns — components own UI, services own logic,
a router owns navigation — without Angular's steepest learning-curve pieces: no RxJS, no
dependency-injection container, and templates that are real, compiled, type-checked
KopScript rather than a separate interpreted template language.

Generating Kopular code with an AI coding assistant? Point it at **[`LLM.md`](./LLM.md)**
— a dense, complete reference designed to be loaded straight into an LLM's context.

## Highlights

- **`Component`, with real vdom diffing**: a base class with `virtual Render()` (describes
  the current state as a `VElement` tree — a lightweight description of a DOM element, not
  a real one) and `Update()` (diffs the new tree against the previous one and patches only
  what changed, reusing a real DOM node wherever its tag stays the same — not a full
  subtree rebuild). `Render()` is provided as a real, separate markup file compiled by
  KopScript's `template from` (see "Templates" below), or written by hand building a
  `VElement` tree against `kopular/velement`, the way you'd write careful vanilla-JS UI
  code — your choice, and both compile to the exact same thing. An optional `virtual
  RenderError(message)` renders a fallback instead of a hard crash if `Render()` throws —
  purely additive; not overriding it keeps today's exact (uncaught) behavior. Every real DOM
  event handler Kopular attaches (`OnClick`/`OnInput`/`OnBlur`/`OnChange`) batches the
  `Update()` calls made while it runs into one render per component, applied once the
  handler returns, fully synchronously — a click handler that mutates more than one field
  only ever renders once, reading every field's final value, regardless of which statement
  happens to trigger it. See `LLM.md`'s "Component" section for the full rationale and the
  real bug this closes.
- **Templates, compiled and type-checked, not interpreted**: markup lives in its own
  `.html` file — interpolation (`{{ }}`), event/property bindings (`(click)="..."`,
  `[prop]="..."`), and `*if`/`*for` structural directives — desugared by the KopScript
  compiler into the exact same code a hand-written `Render()` would produce, with
  automatic `Subscribe`/`Update()` wiring for `state<T>` fields referenced directly in the
  markup. See "Templates" below.
- **Reactive state, no RxJS**: components hold `state<number>`/`state<string>`/... (a
  KopScript language feature — see the [KopScript](https://dev.azure.com/koppinator/Koppindependence/_git/KopScript)
  repo) and subscribe once, in their constructor, to call `Update()` on change. No
  Observables, no operators, no manual unsubscribe bookkeeping.
- **Services, no DI container**: "injecting" a service is just passing it as a
  constructor argument. No injector hierarchy, no provider tokens, no decorators — and a
  service stays fully testable with zero `Component`/DOM involvement, since it's just a
  class.
- **`Router`**: real URLs (`/about`, not `#/about`) via the History API
  (`pushState`/`popstate`), with route registration as plain method calls, not a config
  DSL. See "Router, and deploying it" below — every deployment target needs its own
  SPA-fallback config, not just local dev.
- **Structural directives**: `*ngIf`/`*ngFor`/`*ngSwitch`'s job — build a subtree
  conditionally, repeat one per item, pick one of several cases. In a template, that's
  `*if`/`*for` (real `if`/`for` under the hood — see "Templates"); in a hand-written
  `Render()`, the same job is a plain function call (`If(...)`) or existing KopScript
  expression (`array.ForEach(...)`, `match`) — no special syntax needed there either way.
  See "Structural directives" below.
- **`Http`**: a thin, static wrapper over the real Fetch API (`Http.Get(url)`,
  `Http.Post(url, jsonBody)`, ...) — no HttpClient injection tokens, no RxJS
  observables/operators. See "HTTP" below.
- **`FormField<T>`**: a single input's value/error/touched state, built on `state<T>` —
  no two-way-binding magic, no `FormGroup` config object. See "Forms" below.

## What's here

- `src/dom.ks` — ambient DOM bindings (`document`, `Element`, `Event`, `window`,
  `location`) that `component.ks`/`router.ks`/`directives.ks` are built on.
- `src/velement.ks` — `VElement`, the lightweight description of a DOM element `Render()`
  returns.
- `src/vdom.ks` — the diff/patch engine (`Materialize`/`Patch`/`PatchChildren`) behind real
  vdom diffing — see "Component" above.
- `src/component.ks` — the `Component` base class.
- `src/router.ks` — the `Router`.
- `src/directives.ks` — `If()`, the structural-directive equivalents' one genuinely new
  piece (see below).
- `src/http.ks` — `Http`, a thin wrapper over `fetch` (see below). `src/http_runtime.js`
  is its one companion file — the single hand-written (not compiled from `.ks`) file in
  Kopular, and why is explained in its own header comment.
- `src/forms.ks` — `FormField<T>` and `Validators` (see "Forms" below).
- `src/testing.js` — `runKopularApp`/`runKopularFixture` (see "Testing your own app"
  below); hand-written for the same reason as `http_runtime.js` — filesystem/process
  orchestration isn't a Kopular `Component`.
- `bin/kp.mjs` — the `kp new` scaffolding CLI (see "Starting a new project" below); also
  hand-written, same reason.

That's the whole framework — ten files, plus the scaffolding CLI. Everything else (a
real app built on top of it)
lives in a separate consumer repo, [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo).

## Templates

A component's `Render()` can be a real markup file instead of hand-written imperative DOM
code — `template from "./x.html";` in the class body, a KopScript language feature (see
[KopScript](https://dev.azure.com/koppinator/Koppindependence/_git/KopScript)'s own README/LLM.md for
the full syntax reference). Kopular itself needed **zero framework code changes** for
this — the compiler desugars a template straight into calls against the same
`VElement.Create`/`.AppendChild`/`.TextContent`/named event fields `velement.ks` already
declares, so a template-generated `Render()` is indistinguishable from one you'd write by
hand:

```ks
// counter.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";
}
```

```html
<!-- counter.html -->
<button (click)="Increment()">Count: {{ Count.Value }}</button>
```

Note there's no `this.Count.Subscribe(...)` anywhere — a `state<T>` field referenced
directly in the template (`Count.Value` above) gets it wired automatically. The exact
same component, hand-written, needs that `Subscribe` call itself:

```ks
class Counter : Component {
  private state<number> Count;

  constructor() : base() {
    this.Count = state(0);
    this.Count.Subscribe((number v) => this.Update());
  }

  public override VElement Render() {
    VElement button = VElement.Create("button");
    button.TextContent = "Count: " + this.Count.Value;
    button.OnClick = (Event e) => {
      this.Count.Value = this.Count.Value + 1;
    };
    return button;
  }
}
```

Both produce the same `Render()`, and can be mixed freely across a codebase — nothing
about `Component`, `Update()`, or any other Kopular API differs between them. The manual
`Subscribe` call is still exactly what you need the moment state is reached *indirectly*
— through a method call, or through an injected service's own state
(`this.Service.Count`, say) — which is why the real, production `Counter` on the
[KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo) site
(it injects a `CounterService` rather than holding `Count` itself) uses a template but
still has one manual `Subscribe`. `*if`/`*for` in a template are covered under
"Structural directives" below, alongside their hand-written-`Render()` equivalents.

## Scoped component styles

A class-body `styles from "./x.css";` — also a KopScript language feature (see
[KopScript](https://dev.azure.com/koppinator/Koppindependence/_git/KopScript)'s own
README/LLM.md for the full syntax/CSS-parsing reference) — scopes a real stylesheet so it
only matches elements *that class itself* renders, never a sibling's or a child's:

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

```css
/* widget.css */
.title { color: royalblue; }
```

Unlike `template from`, this **did** need real framework code — `ScopedStyles.Inject`
(`vdom.ks`) is the runtime half, and `using "kopular";` brings it in along with everything
else — do **not** write your own `extern class ScopedStyles ... from "kopular/vdom";`, which
is now a real `KS4002 Declaration 'ScopedStyles' conflicts with a name brought in by
'using'`. (Before package `using` existed, every Kopular export did need a hand-written
`extern` in your project; that is what `using "kopular";` replaced.) It's idempotent,
injecting one real `<style>` per component *type* into `document.head` the first time any
instance of that type is constructed
(dedup is per-type, not per-instance — every instance's constructor calls `Inject` with
the same compile-time `id`/rewritten-`css`, so only the first actually creates a tag).
Never removed once injected — a scoped stylesheet is global infrastructure for as long as
the page lives, not per-instance content `Teardown()` would ever clean up. `document.head`
(`dom.ks`) is new too, added specifically for this.

Independent of `template from` — works with a hand-written `Render()` as well, which gets
a `protected string ScopeId;` field to apply manually (`template from` elements get their
`data-kop-scope` attribute automatically, so this is only needed by hand-written code):

```ks
public override VElement Render() {
  VElement el = VElement.Create("div");
  el.ClassName = "title";
  el.SetAttr("data-kop-scope", this.ScopeId);
  return el;
}
```

## Dependency injection: the composition root pattern

Kopular has no injector because KopScript has nothing for one to hook into — no
decorators, no reflection, and no *generic functions* (KopScript's generics are
classes/interfaces only — see the KopScript repo) for a type-safe `Resolve<T>()`. Instead, the
whole app's service/page graph gets built exactly once, by hand, in one place: a plain
class with no `Component` base and no framework code in it at all, sometimes called an
**app container** or (in the wider DI literature) a **composition root**. Everything
else just takes what it needs as constructor arguments and never constructs its own
dependencies.

```ks
// app_container.ks — the one place that decides what's shared and builds
// the graph, in dependency order.
class AppContainer {
  public Router Nav;

  constructor() {
    // Built once, passed to every page that needs it below — that's the
    // whole mechanism for a shared singleton. A page that constructed its
    // own `new CounterService()` instead would get an independent one; the
    // difference is which variable gets passed in, not a config flag.
    CounterService counter = new CounterService();

    this.Nav = new Router(new NotFoundPage());
    this.Nav.AddRoute("/", new HomePage(this.Nav, counter));
    this.Nav.AddRoute("/about", new AboutPage(this.Nav));
  }
}

// routed_app.ks — the root Component. Takes the already-built graph; never
// builds one of its own.
class RoutedApp : Component {
  private Router Nav;

  constructor(AppContainer services) : base() {
    this.Nav = services.Nav;
  }

  public override VElement Render() {
    return VElement.Create("div");
  }

  // VElement.Mount(this.Nav) in Render() (see "Nested component composition"
  // below) would work here too, but a single, always-present root slot has
  // no reordering or removal to get right, so the older, simpler
  // AfterRender is just as correct — it's called with the real DOM node
  // Render()'s tree just became, once Mount()/Update() has actually
  // materialized/patched it, here that's the container itself.
  protected override void AfterRender(Element root) {
    this.Nav.Mount(root);
  }
}

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

This is sometimes called "Pure DI" — the same benefits a container gives you (nothing
hardcodes its own dependencies, everything is swappable in a test) with none of a
container's cost:

- **Compile-time checked.** A missing or mistyped dependency is `expected N arguments,
  got M` from the KopScript compiler, not a `NullInjectorError` your users hit at
  runtime after a container fails to resolve something.
- **Fully legible.** The entire dependency graph is ordinary, readable code in one file
  — grep for `new` in the composition root and you've read the whole wiring diagram.
  Nothing is constructed by a framework inspecting metadata behind the scenes.
- **No new concepts.** If you already know how to call a constructor, you already know
  Kopular's DI story — there's no separate injector API, provider syntax, or
  injection-token vocabulary to learn.

See [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo)'s
`src/app_container.ks` and `src/routed_app.ks` for the real, working version this
example is drawn from.

## Router, and deploying it

```ks
Router nav = new Router(new NotFoundPage());   // fallback page 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
```

Real URLs via the History API (`pushState`/`popstate`), not `#/about` hash routing.
`AddRoute` takes an already-constructed `Component`, not a factory, so a page's own
`state<T>` survives navigating away and back — see "Dependency injection" above for how
the whole page graph typically gets built once, in a composition root.

A trailing slash matches the same route as without one (`/about/` == `/about`, `/` is left
alone) — real-world links and directory-style static hosting (a build-time prerendered
route's own `/about/index.html`, most concretely — see KopularDemo's own
`scripts/prerender.mjs`) routinely produce trailing-slash URLs, and every mainstream
router normalizes this the same way.

**Dynamic route segments**: a path segment written `:name` (e.g. `/dogs/:id`) matches any
single non-empty segment. With just one per route, its value is captured into
`Router.Param`:

```ks
nav.AddRoute("/dogs/:id", new DogPage(nav));
// inside DogPage.Render():
el.TextContent = "Dog #" + this.Nav.Param;
```

`Param` is a plain `string`, deliberately not `state<T>` — `Router`'s own `Render()` already
re-embeds the matched page into its outlet (via `VElement.Mount`) on every
`Navigate()`/`popstate`, which re-runs that page's `Render()` (reading the fresh `Param`)
with no extra step. No `Subscribe()` needed on it.

**More than one dynamic segment**, and a trailing **wildcard** segment, both work too —
read each by name via `Router.Params(name)` instead (`Param` above still holds the
*first* captured value either way, so existing single-segment code needs no change):

```ks
nav.AddRoute("/dogs/:id/toys/:toyId", new ToyPage(nav));
// inside ToyPage.Render():
el.TextContent = "Dog " + this.Nav.Params("id") + " / Toy " + this.Nav.Params("toyId");

nav.AddRoute("/files/*", new FilesPage(nav));
// "/files/2026/reports/q1.pdf" -> Params("*") == "2026/reports/q1.pdf"
```

**Query strings** are always available via `Router.Query(key)`, independent of which
route matched (never part of route *matching* itself):

```ks
// "/search?sort=name&order=asc"
this.Nav.Query("sort")   // "name"
this.Nav.Query("missing") // "" — not present
```

Query values are **not percent-decoded** — a deliberate v1 cut (no `decodeURIComponent`
binding exists yet); a value containing `%20` or `+` for a space arrives exactly as
written in the URL.

**Navigation guards**: protect a route (or any set of routes) behind a check —
`SetGuard` takes a redirect path plus a single `(string) => bool` checked before every
navigation, including a direct load/refresh:

```ks
nav.SetGuard("/login", (string path) => {
  if (path == "/admin") { return authService.IsLoggedIn.Value; }
  return true;
});
```

One guard for the whole `Router`, not per-route — the guard function itself decides which
paths it cares about, the same "a function, not a config object" style `Http`/DI already
use. Defaults to always-allow when `SetGuard` is never called. Redirecting updates the URL
too (via `pushState`), so refreshing a blocked path lands on the redirect again rather than
back on the page the guard just rejected — pick a `redirectPath` the guard itself always
allows, or it loops.

**Lazy routes (real code-splitting)**: `AddRoute` takes an already-built page — its whole
JS chunk loads eagerly, with everything else, at startup. `AddLazyRoute` takes a *loader*
instead, so that chunk is only ever fetched the first time its route actually matches:

```ks
nav.AddLazyRoute("/dogs", LoadDogsPage);
```

`LoadDogsPage` is a `() => task<Component>` — a real dynamic `import()`, since KopScript's
own syntax has no expression for one. Write a tiny hand-written loader (not compiled from
`.ks`) and reach it via a relative `extern` (see KopScript's own `README.md`/`LLM.md`
"extern" section for the full mechanics — this needed a real compiler fix,
`kopscript@0.23.0`, to work reliably):

```js
// dogs_page_loader.js — hand-written
export async function LoadDogsPage() {
  const { DogsPage } = await import("./dogs_page.js");
  return new DogsPage();
}
```
```ks
extern task<Component> LoadDogsPage() from "./dogs_page_loader";
```

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 it) while the fetch is in flight, then the real page once
it resolves — and, like an eager page, it's only ever fetched once: navigating away and
back reuses the same already-loaded instance, keeping whatever state it built up. A lazy
and an eager route mix freely in the same `Router`; nothing about `AddRoute`'s own existing
signature changes.

**Building and testing a lazy route**: `dogs_page.ks` is deliberately *not* `using`'d from
your app's own entry — that's what keeps it out of the eager bundle — which also means your
normal `ks build src/app.ks` never compiles it, since `ks build` only walks the `using`
graph reachable from the entry you give it. Build it as its own separate entry too:
`ks build src/app.ks && ks build src/dogs_page.ks` (already-compiled shared dependencies
just get written again with identical output, so this is safe to add). For tests,
`kopular/testing`'s `runKopularApp` (0.24.0+) compiles any other real `.ks` file present in
your `srcDir` that the entry doesn't reach — a lazy-route target is exactly that — so
`AddLazyRoute` needs no special test setup at all.

**`AllPaths()`** returns every registered path (both `AddRoute` and `AddLazyRoute`), in
registration order — for a caller that needs to enumerate real routes (a build-time static
prerender step, most likely) without hand-maintaining a second, driftable list next to the
composition root's own `AddRoute` calls.

**Deploying a Router-based app needs SPA/history-fallback configured on whatever you
deploy to — this is true of every client-side router in every framework, not a Kopular
gap.** A direct load or a refresh at `/about` is a plain HTTP request that reaches your
host before any JS has run, so nothing client-side (Router included) can intercept it;
the host has to serve the app shell itself for any route it has no literal file for.
KopularDemo hit exactly this in production (worked when navigated to via a link, 404'd on
refresh) before its Cloudflare Workers config had this set:

```jsonc
// wrangler.jsonc
"assets": {
  "directory": "./public",
  "not_found_handling": "single-page-application"
}
```

Every static host has an equivalent option (Netlify, Vercel, nginx, ...) — search that
host's docs for "SPA fallback" or "single-page application routing", the terminology is
standard. For local dev, see KopularDemo's `scripts/serve.mjs`.

## Structural directives

Angular's `*ngIf`/`*ngFor`/`*ngSwitch` are template syntax that expands, at compile time,
into imperative view-container calls. In a Kopular **template**, `*if`/`*for` are exactly
that — real KopScript `if`/`for` statements underneath (see "Templates" above), compiled
by KopScript itself, not interpreted by Kopular at runtime. In a **hand-written**
`Render()`, there's no separate directive syntax to reach for: each job maps onto a plain
expression, and two of the three need nothing new at all.

| Angular          | Kopular template | Kopular hand-written `Render()`             | New code? |
| ----------------- | ---------------- | -------------------------------------------- | :-------: |
| `*ngFor`          | `*for="Type v of expr"` | `array.ForEach((item) => ...)`         | none — already a KopScript array method |
| `*ngSwitch`       | *(not supported — use `*if`, or switch in the backing class)* | `match value { ... }` | none — already a KopScript expression, and exhaustiveness-checked (`*ngSwitch` isn't) |
| `*ngIf` / `*ngIf-else` | `*if="expr"` | `If(condition, () => ..., () => ...)`  | `directives.ks` (hand-written form only — a template's `*if` needs no helper, it's a real `if`) |

The rest of this section is about the **hand-written `Render()`** column above — a
template's `*if`/`*for` need no further explanation, they're covered under "Templates".
`*ngIf` is the one case in hand-written `Render()` that needs something new: `if` is a
*statement* in KopScript, so without a helper you'd need a throwaway mutable local just
to get a conditional value out of it. `If()` is that helper — nothing more than:

```ks
VElement If(bool condition, () => VElement whenTrue, () => VElement whenFalse) {
  if (condition) {
    return whenTrue();
  }
  return whenFalse();
}
```

Both branches are required (same reasoning `Router` uses for requiring a `NotFoundPage`
up front — see `router.ks`): v1 has no nullable types, so "render nothing" has no value
to hand back. Only the branch actually taken runs — the other lambda is never called, so
an explicit empty branch (`() => VElement.Create("span")`) costs nothing when there's
genuinely nothing to show.

All three read the same way, right inside a hand-written `Render()` — no directive
registration, nothing to import beyond the function itself:

```ks
public override VElement Render() {
  VElement root = VElement.Create("div");

  // *ngIf
  root.AppendChild(If(this.User.IsLoggedIn, () => this.BuildProfile(), () => this.BuildLoginButton()));

  // *ngFor
  VElement list = VElement.Create("ul");
  this.Items.ForEach((Item item) => { list.AppendChild(this.BuildItemRow(item)); });
  root.AppendChild(list);

  // *ngSwitch
  root.AppendChild(match this.Status {
    "loading" => this.BuildSpinner(),
    "error" => this.BuildError(),
    _ => this.BuildContent()
  });

  return root;
}
```

Give each `Item`'s `VElement` a stable `.Id` (e.g. the item's own id) to make `*ngFor
trackBy`-style row reuse automatic — `Update()`'s diff engine matches children by `Id`
across a re-render, reusing a matched child's real DOM node (and anything stateful
attached to it, like focus) rather than rebuilding it, the same way `*ngFor`'s own
`trackBy` avoids rebuilding unchanged rows. Without a stable `Id`, a list still renders
correctly on reorder, but a given item's real node isn't guaranteed to follow its data.

## Nested component composition

Everything above builds *content* into a `VElement` tree — a real, independent child
`Component` (its own fields, its own `Render()`, its own reactive `state<T>`) is a
different thing to embed than a plain element. `VElement.Mount(component)` wraps one as a
slot the diff engine treats like any other content: created on first render, patched in
place (not torn down and rebuilt) across a re-render as long as the *same* instance still
occupies that slot, reordered by `.Id` exactly like any other keyed child, and torn down
— calling `OnUnmount()` — the moment it's replaced or removed:

```ks
class TodoItem : Component {
  public string Id;
  private string Text;
  constructor(string id, string text) : base() {
    this.Id = id;
    this.Text = text;
  }

  public override VElement Render() {
    VElement li = VElement.Create("li");
    li.TextContent = this.Text;
    return li;
  }
}

class TodoList : Component {
  private TodoItem[] Items;
  constructor() : base() { this.Items = []; }

  public override VElement Render() {
    VElement ul = VElement.Create("ul");
    this.Items.ForEach((TodoItem item) => {
      VElement slot = VElement.Mount(item);
      slot.Id = item.Id; // stable key — the same convention as any other list, above
      ul.AppendChild(slot);
    });
    return ul;
  }

  public void Add(string id, string text) {
    this.Items = this.Items.Push(new TodoItem(id, text));
    this.Update();
  }

  public void Remove(string id) {
    this.Items = this.Items.Filter((TodoItem i) => i.Id != id);
    this.Update(); // the removed TodoItem's OnUnmount() fires here
  }
}
```

Each `TodoItem` is a genuinely independent `Component` — it can hold its own local
`state<T>`, subscribe to a shared service, or mount further children of its own (`Update()`
called from inside one re-renders just that item's own subtree, without disturbing its
siblings, exactly like the plain-`VElement` keyed-list case above). Override `OnUnmount()`
to release anything a removed instance was holding onto — most commonly, calling the
unsubscribe handle `state<T>.Subscribe` now returns (see `kopscript`'s own `LLM.md`) for a
subscription made in the constructor:

```ks
class TodoItem : Component {
  private () => void UnsubscribeShared;
  constructor(SharedService shared) : base() {
    this.UnsubscribeShared = shared.Count.Subscribe((number v) => this.Update());
  }
  protected override void OnUnmount() {
    this.UnsubscribeShared();
  }
}
```

A component built once at app startup and never removed (every page `Router` manages
today, most real apps' top-level structure) never needs `OnUnmount()` at all — it's there
for the case a real dynamic list like `TodoList` above actually needs: a subscription that
must stop, not just outlive an app that was going to unload anyway. 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.

`Router`'s own outlet uses this mechanism too — its `Render()` embeds the matched page via
`VElement.Mount`, not the older `AfterRender`-based pattern still shown in "Router, and
deploying it" below for mounting the `Router` itself into an app's root container (a
single, always-present slot, where there's nothing this mechanism would add). Reach for
`VElement.Mount` for anything with real add/remove/reorder: a list of components, a modal
that comes and goes, anything genuinely dynamic. A template gets the same capability via
`*mount="expr"` — see "Templates" in `kopscript`'s own README/LLM.md for the syntax; it
desugars to exactly this call.

## Content projection — a parent giving a child what to render

React's `children` prop and Angular's `<ng-content>` both let a parent hand a child
arbitrary markup to render at a spot the child itself decides. Kopular needs no separate
mechanism for this at all: pass a `() => VElement` into the child's constructor, the same
as any other constructor argument, and have the child call it from inside its own
`Render()`:

```ks
class Card : Component {
  private () => VElement ContentBuilder;
  constructor(() => VElement contentBuilder) : base() {
    this.ContentBuilder = contentBuilder;
  }
  public override VElement Render() {
    VElement div = VElement.Create("div");
    div.ClassName = "card";
    div.AppendChild(this.ContentBuilder());
    return div;
  }
}

// Usage, from inside the parent's own Render() — the parent decides what's inside the
// card, Card decides the chrome around it. `this` only means anything in a method, so
// that is where the callback is built:
class Page : Component {
  constructor() : base() { }
  public override VElement Render() {
    Card card = new Card(() => this.BuildCardBody());
    return VElement.Mount(card);
  }
  private VElement BuildCardBody() { return VElement.Create("p"); }
}
```

Because the callback is invoked fresh on every one of `Card`'s own renders (not a value
captured once and frozen), and it's a closure over the *parent's* own `this`, the projected
content reflects the parent's current state every time — bump a field the parent's own
`BuildCardBody()` reads, call the parent's `Update()`, and `Card`'s own next patch shows the
new value, patched in place like any other content. No new API: this is just
`VElement.Mount` plus an ordinary function-typed constructor argument, the same "no hidden
magic, explicit constructor args" pattern dependency injection above already uses.

More than one projection point works the same way — one named callback per slot, the same
fixed-named-slot convention `VElement`'s own `OnClick`/`OnInput`/`OnBlur`/`OnChange` already
use rather than a generic, unordered bag:

```ks
class Panel : Component {
  private () => VElement HeaderBuilder;
  private () => VElement BodyBuilder;
  constructor(() => VElement headerBuilder, () => VElement bodyBuilder) : base() {
    this.HeaderBuilder = headerBuilder;
    this.BodyBuilder = bodyBuilder;
  }
  public override VElement Render() {
    VElement div = VElement.Create("div");
    VElement header = VElement.Create("header");
    header.AppendChild(this.HeaderBuilder());
    div.AppendChild(header);
    VElement body = VElement.Create("section");
    body.AppendChild(this.BodyBuilder());
    div.AppendChild(body);
    return div;
  }
}
```

## Raw HTML, and its real security boundary

`VElement.RawHtml` sets a real, opaque `innerHTML` — an undiffed leaf, in place of
`TextContent`/children, for a raw-markup-plus-one-delegated-listener pattern (this site's
own `header.ks`/`nav.ks` use it exactly this way):

```ks
VElement header = VElement.Create("header");
header.RawHtml = SiteHeaderHtml; // a raw string ... from "./header.html"; constant
header.OnClick = (Event e) => { };  // one delegated listener over the whole subtree
```

**It's unescaped, real `innerHTML` — never assign it anything reachable from user input.**
Every real use in this ecosystem is a `raw string ... from "<path>.html";` compile-time
constant (see KopScript's own docs) — genuinely static markup, baked in at build time,
never a runtime value. Nothing in the type system stops `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.

## HTTP

```ks
using "kopular";

Response r = await Http.Get("/api/dogs");
if (r.ok) {
  string body = await r.text();
  print(body);
}

await Http.Post("/api/dogs", "{\"name\":\"Rex\"}");
await Http.Put("/api/dogs/1", "{\"name\":\"Rexy\"}");
await Http.Patch("/api/dogs/1", "{\"name\":\"Max\"}");
await Http.Delete("/api/dogs/1");
```

`Http` is a thin, static wrapper over the real Fetch API — `Get`/`Post`/`Put`/`Patch`/
`Delete`, each returning `task<Response>` (`.ok`, `.status`, `async text()`). No
`HttpClient` to inject, no RxJS `Observable`/operators, no interceptors — call it from
anywhere, including straight out of a service's own methods.

**No typed JSON deserialization** — `Response.text()` gets you the raw body, nothing
more. This isn't a corner cut for v1; it's a direct consequence of two things KopScript
doesn't have: generic *functions/methods* (KopScript's generics are classes/interfaces
only, so there's no safe way to write a general `task<T> Get<T>(string url)`) and
object-literal syntax (`{ ... }` as a value — see below). If you want a
typed response, describe its shape as its own `extern class` and parse it yourself with
a per-shape `extern ... as "JSON.parse"` declaration — the same trust-based approach
`extern` already uses for everything else, not a new mechanism:

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

string body = await (await Http.Get("/api/dogs/1")).text();
DogDto dog = ParseDog(body);   // unchecked, like a TypeScript `as DogDto` cast
```

**Why `Post`/`Put`/`Patch`/`Delete` aren't just `extern` bindings straight to `fetch`,
the way `Get` is**: setting a request method/body/headers means passing `fetch` a second
argument that's a plain JS object literal (`{ method, headers, body }`) — and KopScript
has no object-literal syntax at all, so it can't construct one. `src/http_runtime.js` is
one small hand-written function that does, and `Get`/`Delete`-with-no-body skip it
entirely (`fetch(url)` alone needs no options object, so `Get` binds straight to the
real global). It's the one file in this package not compiled from `.ks` — everywhere
else avoids the problem by only wrapping JS APIs that take plain positional arguments
(see `dom.ks`'s `addEventListener(string, handler)`, never an options-object-taking API).

## Forms

```ks
using "kopular";

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 = "not-an-email";
print(email.Error.Value);   // "Must be a valid email"
print(email.Valid());       // false

// inside a hand-written Render(), building emailInput as a VElement:
VElement emailInput = VElement.Create("input");
emailInput.OnInput = (Event e) => {
  email.Value.Value = e.target.value;   // revalidates automatically
};
emailInput.OnBlur = (Event e) => { email.Touch(); };
```

`FormField<T>` holds one input's value, error, and touched state as three ordinary
`state<T>` boxes — `.Value` (the input's current value, revalidating on every
assignment), `.Error` (`string?`, the current validator's message or `null`), and
`.Touched` (`bool`, set by calling `.Touch()` — typically on blur, so a fresh field with
an invalid initial value like an empty required field doesn't show an error before the
user has typed anything). Subscribe to any of the three from your `Component`'s
constructor exactly like `Counter`'s own `state<number>`, to re-render when they change.

A validator is a plain `(T) => string?` — `null` means valid, the same convention
KopScript's own nullable types use elsewhere. **There's no array-of-validators
constructor parameter** — KopScript has no syntax for an array of function values — so
combining more than one check (as `email` does above) is an `if`-chain in one lambda, or,
for the common case of just chaining a couple of already-built validators with no custom
logic of their own, the `CombineValidators2`/`CombineValidators3` free functions:

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

Free functions, not `Validators` methods, and fixed-arity (2 and 3) rather than a general
`Validators.All(...)` — a class's own static method can't introduce a new type parameter
beyond the class's own (see "Generics" in `KopScript`'s own docs), so a *generic* combinator has
to live as a free function instead. `Validators` ships the handful of checks almost every
form needs (`Required`, `MinLength`, `MaxLength`, `Email`, `Min`, `Max`), each returning
its own message; write your own validator function for anything more specific.

**No two-way data binding in a hand-written `Render()`** — wiring `Value` to a real
`<input>` is the `OnInput` assignment shown above, the same manual pattern `Counter`
already uses for its click handler (`VElement.Value` itself is one-way, host-to-DOM
only — reading the *current* DOM value back out is always the real event's
`e.target.value`, not something Kopular mirrors into `Value` for you). This is
deliberate, not a missing feature: hand-written code already has direct field/handler
access, so there's nothing for a magic binding to save you from writing. A **template**
gets real sugar for exactly this — `[(value)]="Field"` desugars to `[value]="Field"` +
`(input)="Field = e.target.value"` (see KopScript's own "Templates" docs) — since a
markup file has no equivalent direct access to fall back on.

## Computed values

React's `useMemo`/Angular signals' `computed()` equivalent — a read-only value derived
from one or more `state<T>` sources, recomputed and re-notified whenever a source changes:

```ks
using "kopular";

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);

print(total.Value.Value);              // 20
total.Value.Subscribe((number v) => print("total: " + v));
price.Value = 15;                      // prints "total: 30"
```

`Value` is `state<R>`, not a bare `R` — KopScript's property grammar has no custom-getter
syntax (only auto-implemented `{ get; }`/`{ get; set; }`), so a computed value can't expose
a property that recomputes itself on read. `state<R>` already **is** the right shape for
"read the current value, or subscribe to it changing" — `total.Value.Value`/
`total.Value.Subscribe(...)` are the exact same two members any other `state<T>` has, not a
parallel API to learn.

Dependencies are explicit, not automatically tracked the way Angular signals infer their
own by watching which signals get read during evaluation — that needs a live "currently
evaluating" context threaded through every read, a different kind of feature from anything
else here. Naming each source directly is one honest step short of automatic, consistent
with explicit constructor arguments everywhere else in Kopular (dependency injection,
routing). `Computed1<A, R>` covers one source; `Computed2<A, B, R>` covers two — fixed
arity for the same reason `CombineValidators2`/`CombineValidators3` (above) are: KopScript
has no array-of-function-values type, and no way to express "N sources of N different
types" without one type parameter per source. Add a `Computed3<A, B, C, R>` the same way if
a real derivation ever needs three.

## Async data — `Resource<T>`

The loading/success/failure shape almost every real app needs around a `task<T>` — most
commonly wrapping an `Http` call — as reactive `state<T>` a `Component` can render and
`Subscribe` to, instead of hand-rolling the same three fields and try/catch every time:

```ks
using "kopular";

class DogsPage : Component {
  private Resource<Response> dogs;
  constructor() : base() {
    this.dogs = new Resource<Response>(Http.Get("https://dog.ceo/api/breeds/list/all"));
    this.dogs.Status.Subscribe((AsyncStatus s) => this.Update());
  }
}
```

```ks
public override VElement Render() {
  return match this.dogs.Status.Value {
    AsyncStatus.Loading => this.BuildSpinner(),
    AsyncStatus.Success => this.BuildList(this.dogs.Data.Value),
    AsyncStatus.Failure => this.BuildError(this.dogs.Error.Value)
  };
}
```

`Status` starts at `AsyncStatus.Loading` the moment `Resource` is constructed — the
operation is already in flight (`Http.Get(...)` above is called before `Resource` ever
sees it; a `task<T>` value can only be produced by calling an `async` function, and
`Resource`'s own constructor isn't one, so it always receives an already-started
operation, never starts one itself). Its internal handling of that task is genuinely
fire-and-forget from the constructor's point of view (there's no way to construct a
`task` value outside an async function body to await it there instead — see KopScript's
own "Async" docs) — `Status`/`Data`/`Error` transition together, exactly once, whichever
branch the `task` actually takes. `match` over `AsyncStatus` gets the same real
exhaustiveness checking any other enum `match` does — leaving out a case is a compile
error, not a runtime blank screen.

## Starting a new project: `kp new`

`npx kp new my-app` scaffolds a real, working project: a `Counter` component
(`src/counter.ks`), an `index.html` with every Kopular module already mapped, and the
vendor/serve scripts needed to run in a browser (a browser can't resolve a bare specifier
like `"kopular/component"` the way Node's own module resolution does, so `vendor-kopular.mjs`
copies Kopular's runtime into `vendor/` and `index.html`'s import map points there).

```bash
npx kp new my-app
cd my-app
npm install
npm start   # builds, vendors kopular's browser files, and serves at :8080
```

Its `README.md` points an AI agent at this package's own `GUIDE.md` before it starts
generating code. `kp` ships from this package (not from `kopscript`'s own `ks` CLI) since
scaffolding a *Kopular* app is a framework concern, not a language one — `ks` stays a
pure-language tool with no framework knowledge baked in.

## Using Kopular from another KopScript project

`using "kopular";` (`kopscript@1.1.0`+) resolves this package's own KopScript declarations
through `node_modules` — one line, every Kopular type in scope, with each file's own
compiled output importing only what it actually references:

```ks
using "kopular";

class MyWidget : Component {
  public override VElement Render() {
    VElement el = VElement.Create("div");
    el.TextContent = "Hello from MyWidget";
    return el;
  }
}
```

See [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo) for
a full working example (components, a service, and routing).

Before `kopscript@1.1.0`, every project hand-declared its own copy of Kopular's `extern`
bindings instead — `using` couldn't reach into a package yet. That still works (`kp new`
above can generate it), and is still the right call if you want a visibly trimmed subset —
see `LLM.md`'s "Consuming Kopular from your own KopScript project" for the complete,
copy-ready block, `Element`/`Document`/`Event` included.

`extern class` can carry its own `<T>` (kopscript >= 0.5.0), so a generic export like
`FormField<T>` describes the same way a real generic class does — see `LLM.md`'s
`FormField<T>`/`Validators` section for the full example.

## Testing your own app: `kopular/testing`

A `Component`/`Router` graph can only be exercised end-to-end by actually compiling its
`.ks` sources and running the result against a real DOM — there's no way to unit test one
otherwise. Doing that by hand is a real ~50-line dance (a fresh temp dir per test, since
Node's ESM module cache means re-importing the same compiled path twice never re-runs an
ambient extern binding's top-level code — silently binding every later test to the first
test's jsdom instance — compiling via `kopscript`'s `compileGraph`, binding jsdom onto
`globalThis` for `document`/`Element`/... to find, then restoring it). Both Kopular's own
test suite and KopularDemo's used to hand-roll this independently; `kopular/testing` is
that dance, written once:

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

const { window, cleanup } = await runKopularApp(join(__dirname, "..", "src"), "app.ks", {
  includeKopularPackage: true, // your app consumes Kopular via `extern`, not relative `using`
});
try {
  expect(window.document.querySelector("h1")?.textContent).toBe("Hello");
} finally {
  cleanup(); // always — even on a thrown assertion — or the next test inherits these globals
}
```

`jsdom` is an **optional peer dependency** — installing `kopular` alone doesn't pull it
in; only a project that actually calls `runKopularApp` needs it added too. See
`Kopular/test/kopular.test.ts` (`runKopularFixture`, the sibling export used for testing
Kopular's own source against inline fixtures) and KopularDemo's
`test/routed_app.test.ts` for two real, different call sites.

## Getting started (developing Kopular itself)

```bash
npm install   # pulls in kopscript as a devDependency
npm run build # compiles src/*.ks -> src/*.js (compiled output is gitignored)
npm test      # runs test/kopular.test.ts against a real DOM via jsdom
```

`kopscript` is a real published dependency (see `package.json`'s own `devDependencies` for
the exact version this repo currently requires — not restated by hand here, since it
changes far more often than this paragraph does) — this repo doesn't need KopScript
checked out as a sibling directory or anything else local to build or test.

## Status

v1 / hobby-project scope, same as KopScript itself. `Update()` diffs and patches real DOM
(see "Component" above) rather than replacing a whole subtree on every re-render, and a
live child Component can be embedded directly in a parent's own tree via
`VElement.Mount(child)` — see "Nested component composition" above — so a parent's
re-render really can create/patch/reorder/tear down nested children declaratively now,
closing what used to be documented here as the framework's biggest reconciliation gap. A
template (`.html`) embeds one too, via `*mount="expr"` (`kopscript@0.25.0`+), the same way
`*if`/`*for` embed structural logic — see "Templates" in `kopscript`'s own docs. A list
child (`Mounted` or plain) without a stable
`VElement.Id` still renders correctly across a reorder, but isn't guaranteed to keep the
same real DOM node (see "Structural directives" above). `Update()` called before `Mount()`
is a safe no-op — see `LLM.md`'s `Component` section for exactly when that happens
(sibling pages sharing one injected service's `state<T>`).
