
# Mode JS

**Mode JS** is a low-level interaction engine for the web — the grammar of the
**Microdom** ecosystem. One function, `µ`, reads and writes the DOM through a
small, composable command vocabulary.

```js
µ("#price", { text: 42 })        // writes once
µ("#price", { text: priceAtom }) // keeps writing
```

That second line is the whole idea. Pass a plain value and µ writes it. Pass a
**reactive source** and µ stays subscribed — the DOM tracks the source until it
is re-bound or the element is gone.

---

## The atom protocol

A reactive source is any object with two methods:

```
get()                    → the current value
sub(fn, runNow = true)   → subscribe; returns an unsubscribe function
```

That's the entire contract. Anything that speaks it — a Mode Atom, a Mode Data
cell, your own object — can be handed to any helper (`text`, `html`, `value`,
`css`, `classes`, `attr`) and µ keeps that helper in sync. Re-binding the same
helper on an element (through any element of a bound set) releases the previous
subscription first: no leaks, no double writers.

```js
import { atom, computed } from "@microdom/mode/atom"

const price = atom(9.99)
µ("#price", { text: computed(() => `$${price.get()}`, [price]) })
price.set(12.50)                 // #price updates on its own

µ("#qty", { value: qtyAtom })    // two-way for form controls
```

---

## The family

| Module     | Import                    | One line |
|------------|---------------------------|----------|
| mode.js    | `@microdom/mode`          | The grammar: select, read, write, traverse, bind. |
| Mode Atom  | `@microdom/mode/atom`     | The unit of state: `atom`, `computed`, `effect`. Zero DOM. |
| Mode List  | `@microdom/mode/list`     | Keyed list rendering with lifecycle — nodes keep their identity. |
| Mode Data  | `@microdom/mode/data`     | Columnar store for high-frequency feeds; cells speak the atom protocol. |
| Mode Move  | `@microdom/mode/move`     | Animation and movement, chained through the same `µ(...)` call. |

Every module is independent and **zero-dependency**.

---

## Install

```bash
npm install @microdom/mode
```

```js
import µ from "@microdom/mode"
// or: import { µ, html } from "@microdom/mode"
```

**Browser — native ES module (CDN, pinned):**
```html
<script type="module">
  import µ from "https://cdn.jsdelivr.net/npm/@microdom/mode@2.0.0/dist/mode.esm.min.js"
  µ("body", { /* ... */ })
</script>
```

**Browser — classic `<script>` (global `µ`):**
```html
<script src="https://cdn.jsdelivr.net/npm/@microdom/mode@2.0.0/dist/mode.min.js"></script>
<script>
  const html = String.raw   // optional: editor highlighting for µ templates
  µ("body", { html: html`<p>hello</p>` })
</script>
```

`html` is `String.raw` — a naming convenience so editors colorize your template
strings. µ takes plain strings; the tag does not process escapes, so write real
newlines.

---

## Extensions — the import is the installation

Mode List and Mode Move extend the core. Importing them installs them:

```js
import µ from "@microdom/mode"
import "@microdom/mode/move"    // registers the animation vocabulary
import "@microdom/mode/list"    // registers the list vocabulary

µ("#panel", { slideUp: { t: 300 } })
µ("#books", { list: { source: booksAtom, key: b => b.id, mount } })
```

Import the core before any extension — extensions register against it at import time.

Registration is **composable**: each extension wraps the previous `µ._ext`
instead of replacing it, so several can coexist in one dispatch. It is also
**guarded** — loaded without the core present, an extension warns and no-ops
rather than throwing. For script tags, load `mode.min.js` first, then the
extension's `.min.js`.

---

## Reading values

On a dispatch key, `null` or the string `'get'` triggers read mode:

```js
µ("#price", { text: "get" })          // read textContent
µ("#name",  { value: null })          // read a control's value
µ("#el",    { attr: { id: null } })   // read one attribute
```

The sentinel applies only at the dispatch-key level, never inside nested option
objects — so `µ(form, { attr: { method: "get" } })` still *sets* `method="get"`.
Finite numbers and bigints passed to `text`/`html`/`value` are coerced to
strings; `NaN` and `±Infinity` are ignored.

---

## Philosophy

> **Most of the web is content. It shouldn't cost application infrastructure.**
>
> Not a smaller version of the app stack — a different model for the pages that
> never needed one.

Read more in [`docs/philosophy.md`](docs/philosophy.md),
[`docs/mental-models.md`](docs/mental-models.md) and
[`docs/why-not-x.md`](docs/why-not-x.md).

## License

MIT
