# LumenJS V2 — Complete Reference for AI Models
> **Audience note for the reader (human or AI):** this document assumes you already know Vue and/or React well, and explains LumenJS **by contrast**. Wherever LumenJS syntax visually resembles Vue or React, assume it behaves like Vue/React **unless this document explicitly says otherwise**.
> **Target version: LumenJS V2, current release `2.5.3`** (`@lmjs/cli` +
> `@lmjs/core`, released together). Upgrading notes are in §13. V2 is a ground-up rewrite of the reactive
> engine, CLI, and build pipeline (`@lmjs/core`/`@lmjs/cli`, both published on
> npm). The single biggest change from V1: **`bind=` is gone.** V1 required
> every reactive variable to be explicitly declared reactive via `bind="name"`
> on an ancestor element; V2 makes every top-level `var`/`let`/`const` in a
> view's `
```
A sub-view: `
`. A `` block still controls
chrome/auth per view (`hasNav`, `hasHeader`, `hasFooter`, `requireAuth`).
In V2, any bare attribute on a layout element (e.g. ``) declares a
widget region, filled from `src/views/widgets/.view` by default. A
widget keeps its DOM and state across navigation.
**Per-instance scoping (new in V2, real behavior change from V1).** Every
mounted instance of the same subview file gets its own independent
variable scope — `_vt.View.views[i].vars` for the `i`-th subview mounted
inside a given view (nesting recursively: `_vt.View.views[0].views[2].vars`
for a subview hosted inside another subview). Mount the same subview twice
and each instance's top-level `var`s, and even its own `function`
declarations, are fully isolated — no collision, unlike V1 where every
instance silently shared one flat scope. A subview instance can still read
any variable it doesn't declare itself from its parent's scope (same
shadow-then-fallback rule as `_vt.Global` in §5.2). You never address
`_vt.View.views[i]` directly — this is what the engine does under the hood
when it compiles a subview's `
```
The handler's return value (a plain object) is merged into that mount's
own `vars` before its first render. `@init` handlers must be synchronous —
there's no awaited variant. For the specific case of a subview mounted
once per `:for` item purely to *render*, not to hold independent state
(a product card, a list row), prefer `tpl=` instead (§4b) — it's simpler
and doesn't create an isolated instance at all.
### 4b. `tpl=` — list-row templates (new syntax restriction in V2)
**V2 allows `tpl=` only on a `:for` element** (V1 allowed it anywhere). On
any other element it renders nothing and logs a `[LumenJS]` error. The
template is rendered once per row, *inside* that row's own scope: no
isolated `vars`, no `@init`, it sees the loop item and everything the
loop can see:
```html
```
```html
{{ item.name }} — ${{ item.price }} (row {{ lok }})
```
A `.tpl` file may contain a `
```
Mutating the variable later (`name = "someone else"`) triggers a re-render
of whatever interpolates it — no explicit subscription, no `bind` list to
maintain, no silent "forgot to bind it" failure mode. Function *declarations*
(`function fn(){}`) are deliberately **not** made reactive — they stay real
globals, so `onclick="fn()"`-style inline handlers keep working.
### 5.2 `_vt.Global` — state that survives navigation
A view's own top-level variables reset on navigation away and back (a fresh
view mount). For state that must survive navigation — an app-wide `isAdmin`
flag, a service-client instance — declare it in `index.js` instead of a
view's `
```
### 5.4 `watch`, `tick`, `globals`, `cookies`, `nodes`
These work as in V1: a single global `watch = { varName: fn }` object
(reassigning it wholesale still overwrites previously registered watchers —
the same sharp edge as V1); `tick()` inside `Reactor()`; `globals`
(persisted to `localStorage`, saved on change); `cookies`
(`.setItem`/`.getItem`/`.removeItem`); `nodes` (the URL path, pre-split).
**There is no `session` global in V2.** Use the browser's `sessionStorage`
directly, with `JSON.stringify`/`JSON.parse` for objects.
**A function call inside `{{ }}` is evaluated when the block renders and is
not re-run when the data it reads changes.** `{{ total() }}` stays stale
after `items.push(...)`. Keep the value in a top-level variable and update
it, or interpolate the data directly (`{{ items.length }}`).
### 5.4b `wait()` / `resume()` and the loader
`wait()` pauses rendering and `resume()` resumes it. Neither shows or
hides the page loader. The loader is entirely under your control:
`_loader.show()` / `_loader.hide()`.
### 5.5 Layout slots and HST versioning (new in V2)
Layouts support named slots (`slot="name"` on both the layout's placeholder
and the view content that fills it), a generalization of V1's single-slot
layout model. The compiled view/layout wire format (HST) is versioned:
2.5.1+ builds emit **HST v3** (precompiled scripts). The runtime a project
ships is its own `src/js/lm.js`, so after upgrading the CLI run
**`lm update-core`** before building (§13). An older `lm.js` cannot read v3
output. Since 2.5.3 `lm build` stops with an error in that case; older
CLIs only printed a warning and produced a broken site.
---
## 6. Directives — reference
Unchanged from V1 unless noted. Two prefixes: `:` for data/structural
bindings, `@` for events.
### 6.1 `:for` — loops
```html
The number is: {{number}}
Item {{index}}
{{ item }}
{{ key }}: {{ value }}
{{ myIndex + 1 }}. {{ item }}
```
Modifiers: `:for-limit`, `:for-offset`, `:for-if` (per-item filter).
In V2, loops nest freely (an inner `:for` over `g.items` inside an outer
`:for="groups as g"`), and loop variables interpolate directly in any
attribute, including on the looping element itself
(``). V1's backtick form
(`` data-id="`{{it.id}}`" ``) is still accepted for migrated markup.
**Attribute order is priority.** When `:if` and `:for` sit on the same
element, whichever is written first applies first:
`` hides or shows the whole loop, while
`` filters individual rows.
### 6.2 `:if` / `:else-if` / `:else`
Unchanged — Vue-identical chaining semantics, DOM-structural (materialized
only when true, not merely hidden).
### 6.3 Events — `@eventName="handler"`
Unchanged — handlers receive `(ev, el)`, `el` a jQuery-shim-wrapped element
(`el.css(...)`, `el.data(...)`, `el[0]` for the raw DOM node). Every V1
event name still works: click/tap family, swipe, mouse (hyphenated —
`@mouse-over`, not `@mouseover`), scroll, `drag`/`sort` (with the matching
bare attribute on the element), forms, file-drop, modal, Select2 (`@sl-
select`/`@sl-unselect`).
**Delegation note (fixed in V2, worth knowing if you're debugging an old
build)**: every `@event` binding is delegated from a stable ancestor, so a
handler on dynamically-inserted HTML fires without any manual re-scan step.
This includes elements created by your own script and Iconify icons
(fixed in 2.5.2).
A bare `stop` attribute on an element stops propagation for every handler
on it: ``.
### 6.4–6.10 Draggable/sortable, modals, forms, Select2, DOM utility
directives, nav active-state matching, misc attributes
All unchanged from V1. See the V1 reference for the full exhaustive table if
migrating existing markup — none of these directive names or semantics
changed in V2.
---
## 7. Global built-ins
| Name | What it is |
|---|---|
| `globals` | Persisted to `localStorage`, reactive automatically (§5). |
| `wait()` / `resume()` | Pause / resume rendering. Never touch the loader (§5.4b). |
| `_loader.show()` / `_loader.hide()` | The page loader, shown and hidden only by your code. |
| `lmCheckForUpdate()` | Built pages check for a newer deploy and offer "A new version is available — Reload". Configure with `Reactor({ updateCheck: false })` or `Reactor({ updateCheck: { interval, message, buttonText, url, onUpdate } })`. Off under `lm serve`. |
| `cookies` | `.setItem`/`.getItem`/`.removeItem`/`.hasItem`/`.keys()`. |
| `nodes` | URL path split on `/`. |
| `pushURL(path, data?)` | Programmatic SPA navigation. |
| `watch` | Global watcher object (§5.4). |
| `API.get/post/put/delete/patch(path, data, headers, api_index, opts)` | Built-in HTTP client, §10. |
| `LIVE` | Global WebSocket client (`_Live`), `._emit(event, payload, isBinary)` — real-time features. |
| `cl(...)` | `console.log` shorthand. |
---
## 8. Modals, § 9. Routing
Unchanged from V1 — `modal="path"` + `modal-class`/`modal-title`/`modal-
description`, `close-modal`, the `@modal-open`/`@modal-close`/`@modal-
beforeclose` events, `view.scope(function(view, props){...})` for
parent↔modal data. Routing is still the same three primitives: ``,
`pushURL()`, `