# Kopular quick guide

Everything you need for typical app work, in one page. For anything not covered here —
generics, nullable types, advanced routing, the full diagnostic list — see this package's
own `LLM.md` and `node_modules/kopscript/LLM.md`.

## Setup

Every file starts with `using "kopular";` for the DOM and every Kopular type, plus
`using "./other_file";` per project file it needs (`using` isn't transitive — list every
file you reference, not just direct dependencies' dependencies).

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

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

Run `npm run build && npm run serve`, or `npm start` (a scaffolded project already has
both scripts). `ks check src/app.ks` type-checks without building, if you just want errors.

## The language you need

- `Type name = value;` for every local — no `let`/`var`, no inference on declarations.
- No implicit `this` — every member reference is `this.Field`/`this.Method()`, always,
  including inside a lambda.
- `if`/`else`/`while`/`for`/`foreach (Type x in xs)` — all statements, no ternary. Use
  `match` for a conditional value: `match x { "a" => "one", _ => "other" }`. **The subject
  must be `string` or an `enum` — never `bool`/`number`/anything else, and every pattern
  must be a string literal (or, for an enum subject, an `Enum.Member` name) — not a number,
  not `true`/`false`.** The `_` arm is required unless every case is covered (only possible
  for an enum subject, by naming every member). For a `bool`, use `if`/`else` instead —
  there's no equivalent shorthand.
- Lambdas need explicit parameter types: `(number x) => x * 2`.
- Operators: `+ - * / %`, `== != < > <= >=`, `&& || !` — `!` is a real unary operator on a
  `bool` (`if (!done)`, `bool flipped = !done;`). Precedence, low → high: `=` → `||` →
  `&&` → `==` `!=` → `<` `>` `<=` `>=` → `+` `-` → `*` `/` `%` → unary `-` `!` →
  `.member`/`(call)`/`[index]`. **No `++`/`--`/`+=`** — write `i = i + 1;`.
- **number → string**: `+` with a string on either side (`"count: " + n`), or interpolation
  (`$"{n} items left"`). There is no `.ToString()` — calling it is `KS4085`.
- `try { } catch (string e) { } finally { }` and `throw "message";` all exist — one `catch`
  per `try`, and its parameter type is your choice (it is *not* checked against what was
  actually thrown).
- **Comments are `//` only** — there is no `/* ... */` block comment, and a `/*` is a parse
  error (`KS2020`), not an ignored region.
- **Reserved words**: naming a local or parameter one of these is a parse error, not
  shadowing — `raw`, `state`, `task`, `from`, `as`, `get`, `set`, `in`, `base` and `match`
  are the ones easy to pick by accident. Full list: `using extern raw template styles from
  as const class interface enum constructor public private protected static virtual override
  get set return if else while for foreach in break continue match this base new void true
  false null task state async await try catch finally throw`.
- Nullable: `string? name` — a nullable field forces `if (x != null) { ... }` before use;
  narrowing is scoped to that `if` block, not reachability-based (an early
  `if (x == null) { return; }` does NOT narrow `x` afterward — wrap the rest in
  `if (x != null) { ... }` instead).
- Strings: `s.Length`, `.Contains()/.StartsWith()/.EndsWith()`, `.Trim()/.ToUpper()/.ToLower()`,
  `.Split(sep)`, `.Replace(from, to)`, `.CompareTo(other)` (sort comparator), `+` concatenates.
  Both `"double"` and `'single'` quotes work — use whichever the other isn't, e.g. inside
  a template attribute (`*if="Name != ''"`).
- Arrays are created with a literal and nothing else: `number[] xs = [];`, `[1, 2, 3]`,
  `[new Todo("a")]`. **There is no `new number[3]` form** — that's a parse error, not an
  empty array.
- Arrays: `xs.Length`, `.Map()/.Filter()/.ForEach()/.Find()/.FindIndex()/.Includes()/.IndexOf()`,
  `.Sort(cmp)/.Reverse()/.Push(x)` (all three **non-mutating**, return a new array),
  `.Slice(a, b)/.Concat(ys)/.Join(sep)/.Reduce(fn, initial)`.
- `x == null` / `x != null` also match `undefined` — a missed `Array.Find`, an absent
  optional field, etc. all read as `null`.
- **No `.Match()`/`.Test()`/`.Exec()` on `string` — there is no direct regex-execute method
  at all.** A regex literal (`r"^[a-z]+$"`) only means something as a `match` *pattern*.
  The idiomatic way to classify or extract by character class is `.Split("")` (splits into
  a `string[]` of single characters) plus `match` per character:
  ```ks
  bool isLetter = match c { r"^[a-z]$" => true, _ => false };
  ```
  Build up runs (words, tokens, ...) by iterating those characters and appending to an
  accumulator string, flushing it whenever a non-matching character (or the end) is
  reached — this covers tokenizing/extracting by character class without ever needing a
  hand-written JS shim. Reach for a real shim (below) only for something a `match` pattern
  genuinely can't express, like capturing a submatch.
- No object-literal syntax anywhere (`{ key: value }` doesn't exist as a value). A JS API
  that needs one (rare — `fetch`'s options, `addEventListener`'s options object) needs a
  small hand-written `.js` shim; Kopular's own `Http`/`FormField` cover the common cases so
  you'll rarely hit this.

## Components

```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
<button (click)="Increment()">Count: {{ Count.Value }}</button>
```

`state<T>` referenced directly in a template auto-subscribes — no manual `Subscribe`
needed. State reached *indirectly* (through a method, or `this.SomeService.Count`) does
need one: `this.SomeService.Count.Subscribe((v) => this.Update());` in the constructor.

**Template bindings**: `{{ expr }}` text, `[prop]="expr"` (real fields for
`id`/`className`/`value`/`disabled`/`checked`; anything else is a plain attribute),
`(click)`/`(input)`/`(blur)`/`(change)` events, `*if="expr"`, `*for="Type v of expr"`
(element type required, no inference), `*mount="expr"` (embeds a live child component,
composes with `*for`).

**Two-way binding, `[(value)]="Field"`** — `Field` can be a bare name, `this.Field`, or a
member path; it's assigned back directly (`Field = e.target.value`), so it must resolve
to something assignable, never a method call. **A `state<T>` field needs `.Value` on the
end** — `[(value)]="Qty.Value"`, not `[(value)]="Qty"` (a bare `state<T>` isn't itself a
`string`, so binding it directly is a type error):
```ks
public state<string> Qty;
constructor() : base() { this.Qty = state("1"); }
```
```html
<input [(value)]="Qty.Value" />
```

**Exactly one top-level element per template — no auto-wrapping, a hard compile error
otherwise.** Wrap multiple top-level pieces in one real container element:
```html
<div>
  <input id="text" [(value)]="Draft" />
  <button (click)="Submit()">Go</button>
</div>
```

**Hand-written `Render()`** (needed when logic is too dynamic for a template, or a template
would obscure more than it clarifies):

```ks
public override VElement Render() {
  VElement button = VElement.Create("button");
  button.TextContent = "Count: " + this.Count.Value;
  button.OnClick = (Event e) => { this.Increment(); };
  return button;
}
```
`VElement.Create(tag)`, `.TextContent/.ClassName/.Id/.Value/.RawHtml/.Disabled/.Checked`,
`.OnClick/.OnInput/.OnBlur/.OnChange`, `.AppendChild(child)`, `.SetAttr(name, value)` (the
escape hatch for anything without a named field). Both styles compile to the same thing and
mix freely across a project.

**Conditional value**: there's no ternary, so to pick between two elements use `If()` (from
`kopular/directives`, already covered by `using "kopular"`):
```ks
root.AppendChild(If(this.On.Value, () => this.Yes(), () => this.No()));
```

**Error boundary**: override `RenderError(string message)` to show a fallback instead of an
uncaught crash if `Render()` throws.

## Nested components & content projection

```ks
this.Items.ForEach((Item item) => {
  VElement slot = VElement.Mount(item);  // item : Component, e.g. its own `public string Id;`
  slot.Id = item.Id;                     // stable key for reordering
  list.AppendChild(slot);
});
```
A template does the same via `<li *for="Item i of Items" *mount="i"></li>`.

Content projection (React's `children`): pass a `() => VElement` into a constructor, call
it from `Render()` — no separate mechanism needed.

## Services — no DI container

A service is a plain class; "injecting" it is a constructor argument. Wire everything once
in a composition root:

```ks
class AppContainer {
  public Router Nav;
  constructor() {
    CounterService counter = new CounterService();
    this.Nav = new Router(new NotFoundPage());
    this.Nav.AddRoute("/", new HomePage(this.Nav, counter));
  }
}
```

## Router

```ks
Router nav = new Router(new NotFoundPage());  // fallback page, required
nav.AddRoute("/", new HomePage(nav));
nav.AddRoute("/dogs/:id", new DogDetailPage(nav));  // nav.Param inside that page
nav.SetGuard("/login", (string path) => {
  if (path == "/admin") { return auth.LoggedIn.Value; }  // .Value — a state<bool> is not a bool
  return true;
});
nav.Navigate("/dogs/1");
nav.Mount(document.body);
```
- Routes hold already-built `Component`s, not factories — built once, state survives
  navigating away and back.
- `nav.Param` — the first `:name` segment, a plain `string` (not `state<T>`; the outlet
  re-renders on every navigation already).
- `SetGuard(redirectPath, guard)`: one guard for the whole router, called with the target
  path before **every** navigation — in-app, a direct load/refresh, and back/forward alike,
  so a guarded page is covered however it's reached. Returning `false` redirects to
  `redirectPath` (via `pushState`, so the URL changes too). `guard` itself decides which
  paths it cares about, and `redirectPath` is never guard-checked, so pick one the guard
  always allows. Defaults to always-allow.
- `AddLazyRoute(path, loader)` for real code-splitting — see "Lazy routes" below.
- Every deploy target needs its own SPA fallback (serve `index.html` for any route with no
  matching file) — a plain HTTP limitation, not a Kopular one.

## Forms

```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 = "not-an-email";
print(email.Error.Value);  // "Must be a valid email"
```
`Validators`: `Required/MinLength/MaxLength/Email/Min/Max`, each returns an error message
or `null`. No array-of-validators param — chain checks in one lambda, as above.

## HTTP

```ks
Response r = await Http.Get("/api/dogs");
if (r.ok) { string body = await r.text(); }
await Http.Post("/api/dogs", "{\"name\":\"Rex\"}");
```
No typed JSON deserialization built in — describe the shape as an `extern class` and bind
`JSON.parse`. Note the `;` after an `extern class` body, and never write `async` on an
`extern` signature (give it a `task<T>` return type instead):

```ks
extern class User {
  number id { get; }
  string name { get; }
  string email { get; }
};
extern User[] ParseUsers(string json) as "JSON.parse";
```

A complete load-with-states method. `async` methods return `task` or `task<T>`, never a
bare value type:

```ks
public async task Load() {
  this.Status.Value = "loading";
  try {
    Response r = await Http.Get("/api/users");
    if (r.ok) {
      string body = await r.text();
      this.Users.Value = ParseUsers(body);
      this.Status.Value = "ok";
    } else {
      this.Status.Value = "error";
    }
  } catch (string e) {
    this.Status.Value = "error";
  }
}
```

## Async: `Delay`, `Computed`, `Resource<T>`

```ks
await Delay(500);  // real setTimeout-backed delay

Computed2<number, number, number> total = new Computed2<number, number, number>(
  qty, price, (number q, number p) => q * p
);
total.Value.Subscribe((number v) => this.Update());  // total.Value is itself state<number>

Resource<Response> r = new Resource<Response>(Http.Get(url));  // task already in flight
// Data is state<T?> and Error is state<string?> — both null until the task settles, so
// whatever these arms call has to accept the nullable type (or null-check first).
match r.Status.Value {
  AsyncStatus.Loading => BuildSpinner(),
  AsyncStatus.Success => BuildContent(r.Data.Value),
  AsyncStatus.Failure => BuildError(r.Error.Value)
};
```

## Scoped styles

```ks
class Widget : Component {
  constructor() : base() { }   // required: 'styles from' has to have one to initialize from
  template from "./widget.html";
  styles from "./widget.css";
}
```
Every selector in `widget.css` is rewritten to only match this class's own elements —
never a sibling's or child's. Needs a constructor to exist on the class.

## Lazy routes (real code-splitting)

```js
// admin_page_loader.js — hand-written, not compiled from .ks
export async function LoadAdminPage() {
  const { AdminPage } = await import("./admin_page.js");
  return new AdminPage();
}
```
```ks
extern task<Component> LoadAdminPage() from "./admin_page_loader";
nav.AddLazyRoute("/admin", LoadAdminPage);
```
The lazy page (`admin_page.ks`) must be built as its own entry too — it's deliberately not
`using`'d from your app's entry (that's what keeps it out of the eager bundle), so add a
second build line: `ks build src/app.ks && ks build src/admin_page.ks`. Testing it via
`kopular/testing`'s `runKopularApp` needs no extra setup — it compiles any file in your
`srcDir` the entry doesn't reach.

## Testing

```js
import { runKopularApp } from "kopular/testing";
const { window, cleanup } = await runKopularApp(srcDir, "app.ks", { includeKopularPackage: true });
// assert against window.document, then:
cleanup();
```
Real jsdom, real compile, real DOM assertions — not a mock.

## Common mistakes

- Forgetting `this.` on a member reference inside a lambda — it's a real undefined-identifier
  error, not automatic.
- A `state<T>` used where a plain `T` is expected needs `.Value` — including a `bool` one
  returned from a router guard or passed to `If()` (`KS4038`).
- `if (x == null) { return; } use(x);` does NOT narrow `x` — see "Nullable" above.
- A two-way binding target must be a field path (`Field`, `this.Field`, `Field.Value`), never
  a method call.
- `*mount` accepts only `id`/`[id]` on its element — any other attr/binding is a compile
  error, since the mounted child's own `Render()` owns all of its content.
