Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions content/contributing/livetemplate.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Contributing to LiveTemplate Core Library"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "CONTRIBUTING.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Contributing to LiveTemplate Core Library
Expand Down Expand Up @@ -213,7 +213,7 @@ livetemplate/
│ ├── parse/ # Phase 1: Template parsing (AST evaluation)
│ ├── build/ # Phase 2: Tree types, fingerprinting, wrapper injection
│ ├── diff/ # Phase 3: Tree comparison and update generation
│ ├── render/ # Phase 4: HTML rendering and minification
│ ├── render/ # Phase 4: HTML rendering
│ ├── send/ # Phase 5: Message parsing and serialization
│ ├── session/ # WebSocket connection registry
│ ├── observe/ # Metrics and Prometheus export
Expand Down
4 changes: 2 additions & 2 deletions content/guides/ephemeral-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Ephemeral Components Guide"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/guides/ephemeral-components.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Ephemeral Components Guide
Expand Down
4 changes: 2 additions & 2 deletions content/guides/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "LiveTemplate Observability Guide"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/guides/OBSERVABILITY.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# LiveTemplate Observability Guide
Expand Down
25 changes: 20 additions & 5 deletions content/guides/progressive-complexity.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Progressive Complexity Guide"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/guides/progressive-complexity.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Progressive Complexity Guide
Expand Down Expand Up @@ -116,7 +116,7 @@ The button's `name` routes to the corresponding Go method. Button `value` and `d

## 4. Validation from HTML Attributes

> **Note:** Auto-wiring the form schema from template statics is not yet implemented. Currently you must call `ctx.WithFormSchema(ExtractFormSchema(statics))` manually. For production validation, use `ctx.BindAndValidate()` with struct tags. `formnovalidate` on buttons is not yet respected server-side.
> **Note:** The form schema is auto-wired from your template's HTML attributes, so `ctx.ValidateForm()` works without calling `WithFormSchema` manually. For production validation with custom rules, use `ctx.BindAndValidate()` with struct tags.

HTML validation attributes (`required`, `pattern`, `min`, `max`, `minlength`, `maxlength`, `type`) can be extracted by the framework. Use `ctx.ValidateForm()` instead of writing Go struct tags:

Expand Down Expand Up @@ -152,13 +152,28 @@ func (c *Controller) Submit(state State, ctx *livetemplate.Context) (State, erro

No Go struct tags needed. The `required`, `type="email"`, `minlength="5"`, `min="18"` attributes are the validation rules.

Use `formnovalidate` on buttons that should skip validation:
Use `formnovalidate` on a submit button to skip `ctx.ValidateForm()` for that
submit path — the canonical "save draft" flow:

```html
<button type="submit">Save</button>
<button name="save">Save</button>
<button name="save-draft" formnovalidate>Save Draft</button>
```

The framework reads the `formnovalidate` button's `name` from your template and
skips validation when that button is the submitter — on every tier (WebSocket,
HTTP-fetch, and no-JS native POST). Three things to know:

- **Name it.** The skip is matched by the submitter's `name`; a dynamic
(`{{...}}`) name can't be detected.
- **No-JS: no `value`.** On the pure no-JS tier the server identifies the
submitter by its empty-value form field, so a `formnovalidate` button that
carries a `value` won't be recognized as the submitter there (JS tiers send an
explicit submitter and are unaffected).
- **Not a security boundary.** Skipping is client-controlled convenience for
draft flows — enforce server-authoritative rules unconditionally where it
matters.

---

## 5. Dialogs
Expand Down
4 changes: 2 additions & 2 deletions content/guides/scaling.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "LiveTemplate Scaling Guide"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/guides/SCALING.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# LiveTemplate Scaling Guide
Expand Down
4 changes: 2 additions & 2 deletions content/guides/standard-html-reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Standard HTML Reactivity"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/guides/standard-html-reactivity.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Standard HTML Reactivity
Expand Down
4 changes: 2 additions & 2 deletions content/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Go Library API Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/api-reference.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Go Library API Reference
Expand Down
4 changes: 2 additions & 2 deletions content/reference/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Authentication Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/authentication.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Authentication Reference
Expand Down
20 changes: 13 additions & 7 deletions content/reference/client-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Client Attributes Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/client-attributes.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Client Attributes Reference
Expand Down Expand Up @@ -819,7 +819,16 @@ User-toggled `checked` state on `<input type="checkbox">` and `<input type="radi
<label><input type="checkbox" name="select" value="item-1"> Item 1</label>
```

**Radio group caveat:** Browser mutual exclusion fires synchronously during the morphdom pass. If you need to force-reset a radio group from the server, add `data-lvt-force-update` to *all* radios in the group, not just the one being checked.
**Exception — `lvt-on:click` controls are server-authoritative.** A checkbox or radio with an `lvt-on:click` handler routes its own toggle to the server, which echoes back the authoritative `checked` state in its re-render. There is no pending local-only selection to protect, so the server's rendered value wins automatically — without needing `data-lvt-force-update`. This is what makes a server-driven toggle (e.g. a todo checkbox) reflect correctly.

```html
<!-- Server owns the checked state; clicking routes the toggle to the server -->
<input type="checkbox" {{if .Done}}checked{{end}} lvt-on:click="Toggle" data-id="{{.ID}}">
```

If instead you want the user's selection to win for a checkbox that *also* sends a click to the server (e.g. a click handler that only logs a side effect), use `lvt-on:change` — only `lvt-on:click` opts into server authority.

**Radio group caveat:** Browser mutual exclusion fires synchronously during the morphdom pass. If you need to force-reset a radio group from the server, make the server send the authoritative state (via `lvt-on:click` or `data-lvt-force-update`) on *all* radios in the group, not just the one being checked.

### Dialog Open State

Expand Down Expand Up @@ -873,7 +882,7 @@ All automatic preservation behaviors can be overridden by adding `data-lvt-force

| Preserved State | Mechanism | Override |
|-----------|-------------|---------|
| Checkbox/radio `checked` | Property copied to virtual DOM | `data-lvt-force-update` on the input |
| Checkbox/radio `checked` | Property copied to virtual DOM | `data-lvt-force-update` on the input, or an `lvt-on:click` handler (auto server-authoritative) |
| Dialog `open` | morphdom update skipped while dialog is open | `data-lvt-force-update` on the dialog |
| Datalist dropdown | Entire morphdom pass deferred while datalist input focused | `data-lvt-force-update` on the connected `<input>` (overrides deferral for the entire pass) |
| Focused input elements | morphdom update skipped | `data-lvt-force-update` on the input |
Expand Down Expand Up @@ -1009,9 +1018,6 @@ Complete reference of all `lvt-*` and `data-*` template attributes.
| `lvt-key` | Filter keyboard events by key | `lvt-key="Enter"` |
| `lvt-mod:debounce` | Debounce delay in milliseconds | `lvt-mod:debounce="300"` |
| `lvt-mod:throttle` | Throttle interval in milliseconds | `lvt-mod:throttle="100"` |
| `lvt-mod:skip-when-typing` | Suppress a keyboard binding while focus is in a text field | `<div lvt-on:window:keydown="nextFile" lvt-key="j" lvt-mod:skip-when-typing>` |

**`lvt-mod:skip-when-typing`** lets a global keyboard shortcut coexist with text entry. When the attribute is present, the binding does **not** fire if `document.activeElement` is (or is inside) an editable element — a text-like `<input>`, `<textarea>`, `<select>`, or `[contenteditable]`. Button/checkbox/radio inputs are not treated as editable, so shortcuts still fire when one is focused. Bindings **without** the attribute (e.g. `Escape`-to-cancel) keep firing while typing, so a cancel key still works inside a focused composer. Only applies to `keydown`/`keyup` bindings.

### Form Attributes (`lvt-form:`, `lvt-nav:`)

Expand Down
4 changes: 2 additions & 2 deletions content/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "LiveTemplate Configuration Guide"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/CONFIGURATION.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# LiveTemplate Configuration Guide
Expand Down
4 changes: 2 additions & 2 deletions content/reference/controller-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Controller+State Pattern Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/controller-pattern.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Controller+State Pattern Reference
Expand Down
4 changes: 2 additions & 2 deletions content/reference/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Error Handling Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/error-handling.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Error Handling Reference
Expand Down
8 changes: 4 additions & 4 deletions content/reference/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Current Limitations"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/current-limitations.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Current Limitations
Expand Down Expand Up @@ -89,8 +89,8 @@ Use `ctx.IsHTTP()` to check which transport is active in an action method.

| Limitation | Detail | Workaround |
|-----------|--------|-----------|
| Form schema not auto-wired from statics | `ExtractFormSchema()` exists but must be called manually via `ctx.WithFormSchema()` | Use `ctx.BindAndValidate()` with struct tags for production validation |
| `formnovalidate` not respected server-side | `ctx.ValidateForm()` validates all fields regardless of the submitting button's `formnovalidate` attribute | Skip validation manually in the action method for draft/save-without-validation flows |
| `ctx.ValidateForm()` merges all forms in a template into one schema | `ExtractFormSchema` builds a single schema for the whole template, so multiple distinct forms share one rule set | Use `ctx.BindAndValidate()` with struct tags for per-form rules |
| `formnovalidate` skip needs an empty-value button on the no-JS tier | The no-JS submitter is identified by its empty-value form field, so a `formnovalidate` button carrying a `value` isn't recognized as the submitter without JavaScript (JS tiers send an explicit submitter and are unaffected) | Omit `value` on no-JS draft buttons, or skip validation explicitly in the action |

---

Expand Down
4 changes: 2 additions & 2 deletions content/reference/navigate.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Navigate Action Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/navigate.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Navigate Action Reference
Expand Down
6 changes: 3 additions & 3 deletions content/reference/progressive-complexity.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Progressive Complexity Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/progressive-complexity-reference.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Progressive Complexity Reference
Expand Down Expand Up @@ -59,7 +59,7 @@ HTML validation attributes are extracted by `ctx.ValidateForm()`:
| `min="N"` | Numeric minimum |
| `max="N"` | Numeric maximum |
| `pattern="regex"` | Must match regex |
| `formnovalidate` on button | Skips validation for that action |
| `formnovalidate` on a named submit button | `ctx.ValidateForm()` skips validation when that button is the submitter (all tiers; no-JS needs an empty-value button) |

## Dialog Routing

Expand Down
4 changes: 2 additions & 2 deletions content/reference/pubsub.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "PubSub Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/pubsub.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# PubSub Reference
Expand Down
4 changes: 2 additions & 2 deletions content/reference/server-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Server Actions Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/server-actions.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Server Actions Reference
Expand Down
16 changes: 14 additions & 2 deletions content/reference/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "Session Reference"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/session.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# Session Reference
Expand Down Expand Up @@ -64,6 +64,16 @@ Fields tagged with `lvt:"persist"` follow this persistence schedule. Untagged fi
| Server action | Persisted (once per group) | In-memory for connection lifetime |
| Page refresh | Restored from store | Zero value, loaded by Mount() |

### Persistence and Path Navigation

`lvt:"persist"` fields survive a **same-URL page refresh** (F5) and a **WebSocket reconnect**. They do **not** survive navigation to a different path:

- Persistence is keyed by `groupID`, not by URL path.
- When a GET request arrives on a path different from the group's last-seen path, LiveTemplate resets to fresh state for the new URL (persist fields go back to their zero values, then `Mount()` runs) and **overwrites** the group's stored persist fields with that fresh state.
- Because the store is overwritten on navigation, navigating **back** to the original path does **not** restore the previously persisted values — they were replaced. Persist fields only ever round-trip across a refresh/reconnect on the *same* path.

This matches the pre-`lvt:"persist"` behavior where a path change always meant fresh state. Treat `lvt:"persist"` as "survives refresh," not "survives navigation."

### Explicit Peer Refresh

```go
Expand Down Expand Up @@ -244,6 +254,8 @@ type SessionStore interface {
}
```

**`[]byte` contract for selective persistence:** when the framework persists `lvt:"persist"` fields, it always passes `[]byte` (JSON) to `Set()` and requires `Get()` to return that same `[]byte` (or `nil`) unchanged. A custom `SessionStore` that transforms the value on the round-trip — for example, JSON-unmarshalling it into a `map` — breaks persistence: persist fields silently fall back to fresh state on every request (a warning is logged, no error returned). Both built-in stores honor this automatically; see the `SessionStore` doc comment for the full contract.

### SingleStoreSetter

An optimization interface for updating a single named store within a session group without replacing the entire state. Both `MemorySessionStore` and `RedisSessionStore` implement this interface. Note: `MemorySessionStore.SetStore()` is a no-op since in-memory references are already updated in-place; the optimization primarily benefits `RedisSessionStore` where it avoids re-serializing all stores on every action:
Expand Down
32 changes: 29 additions & 3 deletions content/reference/template-support-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: "LiveTemplate Go Template Support Matrix"
source_repo: "https://github.com/livetemplate/livetemplate"
source_path: "docs/references/template-support-matrix.md"
source_ref: "v0.13.0"
source_commit: "4c5f1c71b2de9abf1abf76d0ddcafd1ec31201dd"
source_ref: "v0.15.0"
source_commit: "7a9b692568c98566bbf14cba77448cc6ab83cc56"
---

# LiveTemplate Go Template Support Matrix
Expand Down Expand Up @@ -34,10 +34,36 @@ This document provides a comprehensive matrix of Go template patterns and their
|---------|--------|-------|---------|-------|
| Simple field output | ✅ | Baseline | `{{.Name}}` | Core feature |
| Nested field access | ✅ | Baseline | `{{.User.Name}}` | Tested in multiple contexts |
| Comments | ✅ | Phase 5 | `{{/* comment */}}` | Properly ignored during parsing |
| Template comments | ✅ | Phase 5 | `{{/* comment */}}` | Properly ignored during parsing |
| HTML comments | ✅ | Phase 1 | `<!-- comment -->` | Stripped to match `html/template` (see note below) |
| Empty templates | ✅ | Phase 5 | `` (empty string) | Handles gracefully |
| Comment-only templates | ✅ | Phase 5 | `{{/* only comment */}}` | Valid template |

#### HTML comment stripping

`html/template` removes HTML comments (`<!-- ... -->`) during its escape pass.
LiveTemplate builds its static segments by walking the raw parse tree, which
never triggers that pass, so comments are stripped explicitly at parse time
(`render.StripHTMLComments`, applied in `parseInternal`) to keep output
consistent with `html/template` and to avoid leaking developer/internal
comments to the client. A comment that wraps an action (`<!-- {{.X}} -->`) is
removed in full, action included — matching `html/template`.

Stripping uses the HTML tokenizer (not a regex), so it is context-aware:

- A literal `<!--` inside a `{{...}}` action (e.g. `{{"<!--"}}`, a
`{{/* <!-- */}}` template comment, or a quoted argument) is **preserved** and
never mistaken for an HTML comment — action spans are masked out before
stripping, matching `html/template`, which never strips inside an action.
- Comment-like text inside an attribute value (`title="<!-- x -->"`) is real
content and is **preserved**.
- Comment markup inside `<script>`, `<style>`, and `<textarea>` (RAWTEXT /
RCDATA) is **left verbatim**, since the tokenizer does not treat it as a
comment there. Note two residual divergences from `html/template` in these
contexts (both pre-existing, unchanged by the stripping): in `<script>`,
`html/template` rejects `<!--` outright; in `<textarea>`/RCDATA it escapes
`<!--` to `&lt;!--`. LiveTemplate preserves both verbatim.

### 2. Whitespace Handling

| Pattern | Status | Phase | Example | Notes |
Expand Down
Loading
Loading