···2233| Version | Milestone | Summary |
44| ------- | ---------------------------------------------------------- | ------------------------------------------------------------------------ |
55-| v0.1.0 | [Foundations](#foundations) | Initial project setup, tooling, and reactive signal prototype. |
66-| v0.2.0 | [Reactivity & Bindings](#reactivity--bindings) | Core DOM bindings (`data-x-*`) and declarative updates. |
77-| v0.3.0 | [Actions & Effects](#actions--effects) | Event system and derived reactivity primitives. |
88-| v0.4.0 | [Plugins Framework](#plugins-framework) | Modular plugin system and first built-in plugin set. |
99-| v0.5.0 | [Streaming & Patch Engine](#streaming--patch-engine) | SSE/WebSocket JSON patch streaming. |
1010-| v0.6.0 | [Persistence & Offline](#persistence--offline) | State persistence, storage sync, and fallback behaviors. |
1111-| v0.7.0 | [Animation & Transitions](#animation--transitions) | Declarative animation layer and browser View Transition API integration. |
1212-| v0.8.0 | [Inspector & Developer Tools](#inspector--developer-tools) | Built-in signal inspector, debug overlays, and dev tooling. |
1313-| v0.9.0 | [Docs & Stability](#documentation--stability-pass) | Comprehensive docs, tests, and performance review. |
55+| | [Foundations](#foundations) | Initial project setup, tooling, and reactive signal prototype. |
66+| | [Reactivity & Bindings](#reactivity--bindings) | Core DOM bindings (`data-x-*`) and declarative updates. |
77+| | [Actions & Effects](#actions--effects) | Event system and derived reactivity primitives. |
88+| | [Plugins Framework](#plugins-framework) | Modular plugin system and first built-in plugin set. |
99+| | [Streaming & Patch Engine](#streaming--patch-engine) | SSE/WebSocket JSON patch streaming. |
1010+| | [Persistence & Offline](#persistence--offline) | State persistence, storage sync, and fallback behaviors. |
1111+| v0.1.0 | [Markup Based Reactivity](#markup-based-reactivity) | Allow users to write apps without any bundled JS |
1212+| v0.2.0 | [Animation & Transitions](#animation--transitions) | Declarative animation layer and browser View Transition API integration. |
1313+| v0.3.0 | [Inspector & Developer Tools](#inspector--developer-tools) | Built-in signal inspector, debug overlays, and dev tooling. |
1414+| v0.4.0 | [Docs & Stability](#documentation--stability-pass) | Comprehensive docs, tests, and performance review. |
1515+| v0.5.0 | PWA Capabilities | TODO |
1416| v1.0.0 | [Release](#stable-release) | Public API freeze, plugin registry, and versioned documentation. |
15171618## Details
···131133 - Versioned documentation (stormlightlabs.github.io/volt)
132134 - Announcement post and release notes
133135 - Community contribution guide & governance doc
136136+137137+### Markup Based Reactivity
138138+139139+**Goal:** Allow Volt apps to declare state, bindings, and behavior entirely in HTML markup
140140+**Outcome:** Authors can ship examples without companion JavaScript bundles
141141+**Deliverables:**
142142+ - Auto-bootstrapping loader (`volt.min.js`) that detects `data-volt` roots and hydrates one scope per root.
143143+ - Declarative state primitives (`data-volt-state`, `data-volt-computed:*`) aligned with `docs/reactivity-spec.md`.
144144+ - Binding directives for text, attributes, classes, styles, and two-way form controls (`data-volt-[bind|text|model|class:*]`).
145145+ - Control-flow directives (`data-volt-for`, `data-volt-if`, `data-volt-else`) with lifecycle-safe teardown.
146146+ - Declarative event system (`data-volt-on:*`) with helper surface for list mutations and plugin hooks.
147147+ - SSR compatibility helpers and sandboxed expression evaluator per the security contract.
148148+ - Integration tests covering TodoMVC and hydration edge cases.
134149135150## Examples
136151
+27-25
cli/src/commands/example.ts
···11-import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
21import { existsSync } from "node:fs";
22+import { mkdir, readFile, writeFile } from "node:fs/promises";
33import path from "node:path";
44import { minify as terserMinify } from "terser";
55import { echo } from "../console/echo.js";
···3030 return currentDir;
3131 }
3232 } catch {
3333- // Continue searching
3333+ // No-Op: Continue searching
3434 }
3535 }
3636···4747}
48484949/**
5050- * Find the hashed build artifacts in dist/assets/
5050+ * Build the library using Vite in library mode
5151 */
5252-async function findBuildArtifacts(root: string): Promise<BuildArtifacts> {
5353- const distAssetsDir = path.join(root, "dist", "assets");
5252+async function buildLibrary(root: string): Promise<void> {
5353+ const { execSync } = await import("node:child_process");
54545555- let entries: string[];
5655 try {
5757- const dirents = await readdir(distAssetsDir, { withFileTypes: true });
5858- entries = dirents.filter((d) => d.isFile()).map((d) => d.name);
5656+ execSync("pnpm vite build --mode lib", { cwd: root, stdio: "inherit" });
5957 } catch {
6060- throw new Error("Build artifacts not found. Run 'pnpm build' first to generate dist/assets/");
5858+ throw new Error("Library build failed. Make sure Vite is configured correctly.");
6159 }
6060+}
62616363- const jsFile = entries.find((f) => f.startsWith("index-") && f.endsWith(".js"));
6464- const cssFile = entries.find((f) => f.startsWith("index-") && f.endsWith(".css"));
6262+/**
6363+ * Find the library build artifacts in dist/
6464+ */
6565+async function findBuildArtifacts(root: string): Promise<BuildArtifacts> {
6666+ const distDir = path.join(root, "dist");
65676666- if (!jsFile || !cssFile) {
6767- throw new Error("Build artifacts incomplete. Expected index-*.js and index-*.css in dist/assets/");
6868+ const jsPath = path.join(distDir, "volt.js");
6969+ const cssPath = path.join(root, "src", "styles", "base.css");
7070+7171+ if (!existsSync(jsPath)) {
7272+ throw new Error(`Library JS not found at ${jsPath}. Build may have failed.`);
6873 }
69747070- return { jsPath: path.join(distAssetsDir, jsFile), cssPath: path.join(distAssetsDir, cssFile) };
7575+ if (!existsSync(cssPath)) {
7676+ throw new Error(`Base CSS not found at ${cssPath}.`);
7777+ }
7878+7979+ return { jsPath, cssPath };
7180}
72817382/**
···200209}
201210202211function generateAppCSS(): string {
203203- return `/* Add your custom styles here */
204204-205205-/* Example:
206206-body {
207207- font-family: system-ui, sans-serif;
208208- max-width: 800px;
209209- margin: 0 auto;
210210- padding: 2rem;
211211-}
212212-*/
213213-`;
212212+ return "/* Add your custom styles here */\n";
214213}
215214216215/**
···244243 const exampleDir = path.join(examplesDir, name);
245244246245 echo.title(`\nCreating example: ${name}\n`);
246246+247247+ echo.info("Building Volt.js library...");
248248+ await buildLibrary(root);
247249248250 echo.info("Finding build artifacts...");
249251 const artifacts = await findBuildArtifacts(root);
+111
docs/reactivity-spec.md
···11+# Reactivity
22+33+Signals power Volt’s imperative runtime as plain getters/setters with pub-sub on top. The implementation lives in `src/core/signal.ts` and exposes three primitives.
44+55+## Core
66+77+### Signals
88+99+- API signature: `signal<T>(initialValue: T): Signal<T>`
1010+1111+- `get()` MUST synchronously return the last committed value.
1212+- `set(next)` MUST compare `next` to the current value using `===` and MUST skip notification when the comparison returns true.
1313+- When `set` commits a new value it MUST synchronously invoke every registered subscriber in the order they were added.
1414+ - Subscriber errors MUST be caught and logged through `console.error`.
1515+- `subscribe(listener)` MUST add the listener, MUST NOT invoke it immediately, and MUST return a teardown function that, when called, removes the listener so it receives no further notifications.
1616+- Multiple subscriptions of the same listener are allowed; each teardown MUST only remove the corresponding registration.
1717+1818+### Computed State
1919+2020+- API signature: `computed<T>(compute: () => T, deps: Array<Signal | ComputedSignal>): ComputedSignal<T>`
2121+2222+- Construction MUST synchronously evaluate `compute()` once to produce the initial value.
2323+ - Exceptions thrown by `compute` MUST propagate to the caller.
2424+- Each dependency in `deps` MUST be subscribed exactly once at construction.
2525+ - Missing dependencies result in stale values and are the caller’s responsibility.
2626+- When any dependency publishes, the computed MUST recompute immediately, compare the new value with the previous value using `===`, and MUST notify subscribers only when the value changes.
2727+- Subscribers follow the same contract as `signal.subscribe`: synchronous notifications, no immediate call on registration, teardown removes the listener, errors are logged.
2828+2929+### Effects
3030+3131+- API signature: `effect(fn: () => void | (() => void), deps: Array<Signal | ComputedSignal>): () => void`
3232+3333+- The runtime MUST execute `fn` immediately after subscription.
3434+ - Exceptions MUST be caught and logged; execution continues for subsequent notifications.
3535+- If `fn` returns a cleanup function, the runtime MUST invoke that cleanup before the next execution of `fn` and during teardown.
3636+- The runtime MUST subscribe to each dependency once. When a dependency publishes, it MUST rerun `fn` synchronously and manage cleanup as described above.
3737+- The teardown function returned by `effect` MUST unsubscribe from all dependencies and MUST invoke any pending cleanup exactly once.
3838+3939+### Gaps
4040+4141+- No automatic dependency tracking; most frameworks infer dependencies from getters.
4242+- Equality checks are strict (`===`), so structural equality or NaN handling requires user code.
4343+- Notifications run synchronously; there is no batching, scheduling, or microtask deferral.
4444+- Signals cannot be inspected for previous values or dependency graphs, limiting debugging tooling.
4545+- There is no built-in way to pause/resume computed values or effects besides manual unsubscribe.
4646+4747+## Markup Based Reactivity
4848+4949+Volt’s runtime should be able to hydrate declarative markup without developer-authored boot scripts.
5050+This section describes the contract for the markup-only mode so plugin authors and docs stay aligned while the loader is implemented.
5151+5252+### Bootstrapping
5353+5454+- `volt` MUST auto-discover mount points marked with `data-volt-root`, `data-volt`, or an equivalent attribute.
5555+- The bootstrapper MUST initialize exactly one reactive scope per root node.
5656+- A root MAY opt out of auto-init by setting `data-volt="false"`.
5757+- During hydration the loader MUST parse `data-volt-state` on the root; the attribute holds a JSON literal that seeds top-level signals.
5858+5959+### Declaring State
6060+6161+- Primitive state lives under `data-volt-state='{"newTodo":"","todos":[]}'`.
6262+ - Each key becomes a writable `signal`.
6363+- Derived values are declared with `data-volt-computed:name="expression"`; the loader builds a computed that depends on every identifier referenced in the expression.
6464+- Global helpers (e.g., `state.todos`, `helpers.length`) must be documented so HTML authors know what bindings are available without custom scripts.
6565+6666+### Binding Expressions
6767+6868+- Attribute bindings use `data-volt-bind:attr="expression"`; the runtime keeps the DOM attribute/property in sync with the expression value.
6969+- For text nodes, `data-volt-text="expression"` renders the latest scalar; internally this is just a one-off text binding.
7070+- Two-way form bindings use `data-volt-model="stateKey"`; the loader wires native input events back to the matching signal.
7171+- Class and style shorthands (`data-volt-class:active="expression"`) mirror the existing imperative helpers.
7272+7373+### Control Flow
7474+7575+- Lists are rendered with `data-volt-for="item, index in todos"` on a `<template>` element; the loader clones the template per entry and exposes loop variables plus `$parent` for outer scope access.
7676+- Conditional blocks use `data-volt-if="expression"` with optional `data-volt-else`; only the truthy branch remains in the DOM.
7777+- Loops and conditionals share cleanup semantics with imperative mounts: nodes created by the runtime must unsubscribe from signals when removed.
7878+7979+### Event Handling
8080+8181+- Event handlers are declared with `data-volt-on:event="statement"` and execute inside the reactive scope with access to helper utilities.
8282+- Mutations must target signals or proxied arrays so change tracking fires.
8383+- Custom plugins can register additional helpers accessible from markup, but they must be namespaced (e.g., `persist.save()`).
8484+8585+### Example Skeleton
8686+8787+```html
8888+<div data-volt data-volt-state='{"newTodo":"","todos":[] }'>
8989+ <form data-volt-on:submit="addTodo(newTodo)">
9090+ <input data-volt-model="newTodo" placeholder="What needs to be done?" />
9191+ </form>
9292+9393+ <ul>
9494+ <template data-volt-for="todo, idx in todos">
9595+ <li data-volt-class:completed="todo.completed">
9696+ <input type="checkbox" data-volt-model="todo.completed" />
9797+ <span data-volt-text="todo.title"></span>
9898+ <button data-volt-on:click="removeTodo(idx)">×</button>
9999+ </li>
100100+ </template>
101101+ </ul>
102102+103103+ <p data-volt-if="todos.length === 0">Everything done!</p>
104104+</div>
105105+```
106106+107107+### Security & Parsing Notes
108108+109109+- Expression strings are evaluated inside a sandboxed `Function` wrapper scoped to the current reactive state; no global objects other than the documented helper bag may leak in.
110110+- The loader must reject unparseable JSON in `data-volt-state` and surface clear warnings so authors can debug by inspecting the console.
111111+- Because hydration occurs after `DOMContentLoaded`, SSR must emit a `<script type="application/json">` fallback when markup-only authors need immediate scope initialization.
-40
examples/counter/README.md
···11# Counter
2233Simple interactive counter demonstrating Volt.js reactive primitives.
44-55-## Features
66-77-- Signal-based state with `count` tracking the current value
88-- Computed values deriving `doubled` and `squared` from count
99-- Conditional rendering showing status (Positive/Negative/Zero)
1010-- Dynamic class binding for visual feedback
1111-1212-## Running
1313-1414-1. Build the project: `pnpm build` from root
1515-2. Open `index.html` in a browser
1616-1717-## Implementation
1818-1919-The counter uses three Volt.js primitives:
2020-2121-**Signals** store reactive state:
2222-2323-```js
2424-const count = signal(0);
2525-```
2626-2727-**Computed** derive values automatically:
2828-2929-```js
3030-const doubled = computed(() => count.get() * 2, [count]);
3131-const isPositive = computed(() => count.get() > 0, [count]);
3232-```
3333-3434-**Bindings** connect state to DOM:
3535-3636-```html
3737-<span data-x-text="count">0</span>
3838-<button data-x-on-click="increment">+</button>
3939-<div data-x-if="isPositive">Positive</div>
4040-<div data-x-class="countClass"></div>
4141-```
4242-4343-When `count` changes, all dependent computed values recalculate and bindings update automatically.
-77
examples/todomvc/README.md
···11# TodoMVC
2233Complete TodoMVC implementation using Volt.js and Volt CSS (classless framework).
44-55-## Features
66-77-- Add/edit/delete todos
88-- Mark complete/incomplete
99-- Filter by All/Active/Completed
1010-- Toggle all at once
1111-- Clear completed
1212-- Persistent reactive state
1313-1414-## Running
1515-1616-1. Build the project: `pnpm build` from root
1717-2. Open `index.html` in a browser
1818-1919-## Implementation
2020-2121-### State Management
2222-2323-Two base signals control all state:
2424-2525-```js
2626-const todos = signal([...]);
2727-const filter = signal('all');
2828-```
2929-3030-Computed signals derive UI state:
3131-3232-```js
3333-const filteredTodos = computed(() => {
3434- if (filter.get() === 'active') return todos.get().filter(t => !t.completed);
3535- if (filter.get() === 'completed') return todos.get().filter(t => t.completed);
3636- return todos.get();
3737-}, [todos, filter]);
3838-3939-const activeCount = computed(() =>
4040- todos.get().filter(t => !t.completed).length,
4141- [todos]
4242-);
4343-```
4444-4545-### List Rendering
4646-4747-`data-x-for` renders todos reactively:
4848-4949-```html
5050-<li data-x-for="(todo, index) in filteredTodos" data-x-class="getTodoClass(todo)">
5151- <input type="checkbox" data-x-on-click="toggleTodo($event, index)">
5252- <label data-x-text="todo.text"></label>
5353-</li>
5454-```
5555-5656-### Event Handling
5757-5858-All interactions use declarative bindings:
5959-6060-- `data-x-on-click` for buttons and checkboxes
6161-- `data-x-on-keyup` for Enter/Escape in inputs
6262-- `data-x-on-dblclick` for editing mode
6363-- `data-x-on-blur` to save edits
6464-6565-### Index Mapping
6666-6767-Because the UI displays filtered todos but operations need the full array, handlers map filtered indices to actual positions:
6868-6969-```js
7070-const deleteTodo = (indexInFiltered) => {
7171- const filteredList = filteredTodos.get();
7272- const todoToFind = filteredList[indexInFiltered];
7373- const actualIndex = todos.get().findIndex(t => t.id === todoToFind.id);
7474- todos.set(todos.get().filter((_, i) => i !== actualIndex));
7575-};
7676-```
7777-7878-### Styling
7979-8080-Uses only Volt CSS. Semantic HTML elements are styled automatically with no custom classes needed.