@muten/core 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +116 -95
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  Muten is still under active development. We are currently in the alpha stage and are working on training models with Muten. Please keep in mind that improvements are being made gradually, and version 1.0 has not been released yet.
7
7
 
8
8
  An **AI-first** frontend framework. You write `.muten` files; muten compiles them to vanilla JS
9
- with fine-grained signals **no virtual DOM, no framework runtime to ship**. The language is small,
9
+ with fine-grained signals - **no virtual DOM, no framework runtime to ship**. The language is small,
10
10
  semantic and analyzable on purpose: an AI (or a person) can **locate and mutate** an app cheaply.
11
11
 
12
12
  ```sh
@@ -14,106 +14,127 @@ npm create muten@latest my-app # scaffold a new app (cross-platform: Windows +
14
14
  cd my-app && npm install && npm run dev
15
15
  ```
16
16
 
17
+ ## What a page looks like
18
+
19
+ One file, declarative, no imports or boilerplate. This is the whole thing:
20
+
21
+ ```
22
+ screen home
23
+
24
+ entity Product {
25
+ name text
26
+ price number
27
+ }
28
+
29
+ state {
30
+ products = [] : list<Product>
31
+ draft = {} : Product
32
+ }
33
+
34
+ action add mutates products, draft <- p {
35
+ products.push({ name: p.name, price: p.price }) # build a record inline
36
+ draft.reset() # then clear the form
37
+ }
38
+
39
+ Page style(column, gap.md) {
40
+ Form bind @draft submit add "Add product"
41
+
42
+ each products.sortDesc(p => p.price) as p { # render the list, sorted
43
+ Text "{p.name} - ${p.price}"
44
+ }
45
+
46
+ Text "Total: ${products.sum(p => p.price)}" # a live aggregate, no JS
47
+ }
48
+ ```
49
+
50
+ No `useState`, no component tree, no build wiring, and `muten check` validates every reference and type before
51
+ it ever runs in a browser.
52
+
17
53
  ## Why muten
18
54
 
19
55
  For an AI the cost of working on a codebase is **context + mistakes + edit-radius**. muten is built to cut
20
- all three *by construction* these are properties of how it compiles, not marketing:
21
-
22
- - **Almost nothing to ship** no virtual DOM, no framework runtime. The *same* todo app, scaffolded by each
23
- framework's official CLI and built, ships **~2.8 KB gzip** of JS in muten vs **~14 KB (Svelte) · ~24 KB (Vue)
24
- · ~59 KB (React)** — **5–21× less** (a static page ships *zero*). Source is the most compact too (445 B), on
25
- par with Svelte. *(Reproducible: the `bench/` folder + `node bench.mjs` in the source repo.)*
26
- - **A deterministic oracle** `muten check --json` validates every page at compile time (unknown
27
- state/action/part, bad style token, illegal mutation) in milliseconds, no browser — a feedback loop the
56
+ all three *by construction* - these are properties of how it compiles, not marketing:
57
+
58
+ - **Almost nothing to ship**: no virtual DOM, no framework runtime. The same todo app ships a small fraction of
59
+ the JS the big frameworks do, and a static page ships *zero*. (See the size table in *muten vs React / Vue /
60
+ Svelte* below.)
61
+ - **A deterministic oracle**: `muten check --json` validates every page at compile time (unknown
62
+ state/action/part, bad style token, illegal mutation) in milliseconds, no browser - a feedback loop the
28
63
  others don't have. A *bounded* language is what makes that possible.
29
- - **The whole app as data** `app.map.json` is a compact index of routes + structure an agent reads first,
64
+ - **The whole app as data**: `app.map.json` is a compact index of routes + structure an agent reads first,
30
65
  instead of grepping a component tree.
31
- - **Small edit radius** the UI is declarative, so a change is usually a few lines in one file.
66
+ - **Small edit radius**: the UI is declarative, so a change is usually a few lines in one file.
32
67
 
33
- The trade is deliberate: a small, analyzable language an AI can hold in its head not a general-purpose one it can't.
68
+ The trade is deliberate: a small, analyzable language an AI can hold in its head, not a general-purpose one it can't.
34
69
 
35
- ## muten vs React / Vue / Svelte the honest version
70
+ ## muten vs React / Vue / Svelte - the honest version
36
71
 
37
- They are general-purpose: they build *anything*, with huge ecosystems and a deep talent pool. That power has a
38
- price an AI (and your refactors) pay on every change a large surface to keep in context (hooks, reactivity rules,
39
- lifecycle, the build graph), edits that ripple across components, and a runtime that ships to the browser. **For a
40
- human team building a big, bespoke product, that trade is usually worth it — use React/Vue/Svelte there.**
72
+ They are general-purpose: they build *anything*, with mature ecosystems and a deep talent pool. **For a human team
73
+ building a big, bespoke product, that is usually the right call.** muten makes the opposite trade on purpose, and it
74
+ only wins on its own terms:
41
75
 
42
- muten makes the *opposite* trade on purpose, and it only wins on its own terms:
76
+ | | muten | React / Vue / Svelte |
77
+ |---|---|---|
78
+ | **Best for** | an **AI** builds & maintains it; the declarative 80% (CRUD, dashboards, content, internal tools) | human teams, large bespoke UIs |
79
+ | **Language surface** | small - the whole thing fits in an AI's context | large (hooks, lifecycle, reactivity rules) |
80
+ | **Catches mistakes** | `muten check` - at **compile time**, in milliseconds, no browser | at runtime / in tests |
81
+ | **A typical change** | a few lines in one file | ripples across components |
82
+ | **Ships to the browser** | ~2.8 KB gzip for a todo app - a static page ships **zero** | 14-59 KB of runtime + your app |
83
+ | **Ecosystem / maturity** | young · one maintainer · **pre-1.0** | mature · huge |
43
84
 
44
- - **The whole language fits in context** no hooks-vs-effects, no re-render rules to reason about; far fewer ways
45
- to be wrong.
46
- - **A compiler that answers before the browser does** — `muten check` validates every page (unknown ref, illegal
47
- mutation, type mismatch, "did you mean…?") in milliseconds, no run. That loop is the single biggest reason
48
- AI-written muten works on the *first* try more often.
49
- - **Small edit radius + app-as-data** — a change is a few lines in one file; `app.map.json` hands an agent the
50
- whole app instead of a component tree to grep.
51
- - **Almost nothing ships** — no VDOM, no framework runtime (~2.8 KB gzip for a todo app vs 14–59 KB).
85
+ The single biggest reason AI-written muten works on the *first* try more often is that middle row: **a compiler that
86
+ answers before the browser does.**
52
87
 
53
- **Where we're honest about the cost:** muten is small by design, so it can't express everything; the ecosystem is
54
- young, there is one maintainer, and it's pre-1.0. It shines when an **AI builds and maintains the app** and the app
55
- is the declarative 80% — CRUD, dashboards, catalogs, content, internal tools. It is **not** the right tool for a
56
- hand-crafted, highly-custom UI that needs the full React ecosystem, and it doesn't pretend to be. The honest rule of
57
- thumb: *let muten do the structure and the data; couple in other tech for the rest* (next section).
88
+ **The honest cost:** muten is small by design, so it can't express everything. It shines when an **AI builds and
89
+ maintains the app** and the app is the declarative 80%. It is **not** the tool for a hand-crafted, highly-custom UI
90
+ that needs the full React ecosystem, and it doesn't pretend to be.
91
+
92
+ > **Rule of thumb:** let muten do the structure and the data; couple in other tech for the rest (next section).
58
93
 
59
94
  ## Capabilities
60
95
 
61
- - **UI** declarative primitives (layout, text, forms, tables, links), `when`/`each` control flow,
62
- `style()` layout tokens + `class()` look (toggle reactively: `class(active when isOpen)`), events on
63
- any element (`on(keydown: act)`).
64
- - **State** local `state`, app-global `store`, derived `get`, `action`s with `if/else`; fine-grained signals.
65
- A page `action` can **call a store action** (`cart.add(d) draft.reset()`) store + local work in one handler.
66
- - **Lists** bounded, analyzable list operations (no raw `map`/`reduce`): inline objects (`list.push({ a: x })`),
67
- in-place edit (`list.patch(x => x.id == id, { done: not x.done })`), filtered render (`each xs as x where cond`),
68
- aggregates (`list.sum(x => x.price * x.qty)` · `count` · `avg` · `min` · `max`), and `sort`/`sortDesc(x => key)`.
69
- - **Data** `query` states backed by `sources` (full HTTP: method, headers, body, nested `at`); one `api`
70
- block for base URL + auth (named clients for several backends); CRUD writes (`create`/`update`/`delete`
71
- optimistic, with `.pending`/`.error`); `refetch(q: …, page: …)` for search/pagination; a `post`/`put`/`delete`
72
- escape for non-REST APIs.
73
- - **Routing** real-path URLs, params (`/product/:id` `param id`), guards, a `/404` catch-all.
74
- - **SEO / SSR** — `muten build` pre-renders every route to real HTML (static pages ship zero JS; data-driven
75
- pages are fetched at build), with per-page `meta { title … description … }` (`og:*` auto-derived).
76
- - **Interop, lowest-tier first** — style native HTML + CSS libs with `class()`; mount **vanilla JS** libs
77
- (charts, maps, date-pickers) via `Custom`; pull JS logic into expressions with `use fmt from "./lib.ts"`;
78
- and only when you need a real **Svelte/React** component (e.g. shadcn) reach for an **island**
79
- (`use X from "react:./X.jsx"` — code-split, lazy `client:visible`). See *Three tiers* below.
80
- - **AI-native** — `lint == build`, one source of truth per concept, and the full language reference ships
81
- inside every scaffolded app under `.claude/` (an AGENTS guide + a Claude skill).
82
-
83
- ## How muten couples with the rest of the web — three tiers
96
+ | Area | What you get |
97
+ |---|---|
98
+ | **UI** | declarative primitives (layout, text, forms, tables, links) · `when`/`each` control flow · `style()` layout tokens + `class()` look (reactive: `class(active when isOpen)`) · `on(event: action)` on any element |
99
+ | **State** | local `state` · app-global `store` · derived `get` · `action`s with `if/else` · fine-grained signals. A page action can **call a store action** (`cart.add(d) draft.reset()`): store + local work in one handler |
100
+ | **Lists** | bounded ops, no raw `map`/`reduce`: inline objects (`push({…})`) · in-place `patch` · filtered `each…where` · aggregates `sum`/`count`/`avg`/`min`/`max` · `sort`/`sortDesc` |
101
+ | **Forms** | a `Form` auto-built from an entity, one input per field: `text` · `number` (coerced) · `email` · `bool` (checkbox) · `enum` (select), with built-in validation |
102
+ | **Data** | `query` states over `sources` (full HTTP: method, headers, body, nested `at`) · one `api` block (named multi-backend clients) · optimistic CRUD (`create`/`update`/`delete` + `.pending`/`.error`) · `refetch(q: …, page: …)` · a `post`/`put`/`delete` escape for non-REST |
103
+ | **Routing** | real-path URLs · params (`/product/:id` `param id`) · route guards · a `/404` catch-all |
104
+ | **SEO / SSR** | `muten build` pre-renders every route to real HTML (static pages ship zero JS; data pages fetched at build) · per-page `meta { title … description … }` with `og:*` auto-derived |
105
+ | **Interop** (lowest tier first) | `class()` for native HTML + CSS libs · `Custom` for vanilla-JS libs (charts, maps, pickers) · `use fmt from "./lib.ts"` for JS logic · Svelte/React **islands** for a real framework component |
106
+ | **AI-native** | `lint == build` · one source of truth per concept · the full language reference ships in every scaffolded app under `.claude/` (an AGENTS guide + a Claude skill) |
107
+
108
+ ## How muten couples with the rest of the web - three tiers
84
109
 
85
110
  muten the *language* stays tiny on purpose; a muten *app* reaches the whole web platform through **bounded,
86
- analyzable escapes**. The point: you never *fight* the language to do something it doesn't have you drop to the
111
+ analyzable escapes**. The point: you never *fight* the language to do something it doesn't have, you drop to the
87
112
  right tier, and the compiler still checks the seam. Reach for the **lowest tier that works**:
88
113
 
89
- **1 · Pure muten** the declarative 80%, zero extra deps: pages + routing (params, guards, shell, `/404`) ·
90
- `state`/`store`/`get` signals · `action`s with `if/else`, optimistic CRUD, and **store-action composition** ·
91
- the **list toolkit** (inline objects · `patch` in-place edit · `each…where` filter · `sum`/`count`/`avg`/`min`/`max`
92
- aggregates · `sort`/`sortDesc`) · `query` over REST `sources` (`refetch`, multi-backend) · `Form` from an entity
93
- (text/number/email/bool/enum + validation) · `DataTable`, `when`/`each`, reactive `class(when )`,
94
- `on(event: action)` · SSG + SEO. → a real **CRUD / SaaS / catalog / dashboard / content** app is *100% muten*.
95
-
96
- **2 · muten + the platform** — the web, *no framework runtime*: native HTML (`<input type="date">`,
97
- `<dialog>`, `<details>`) styled with `class()` · CSS component libs (Tailwind, DaisyUI) · **vanilla JS via
98
- `Custom`** (charts chart.js, maps Leaflet, date-picker flatpickr, rich-text Quill, drag-drop
99
- SortableJS, grids → Tabulator) · web components · `use fmt from "./lib.ts"` for any JS logic (zod, date-fns).
100
- almost every "hard widget" lands here, **without React**.
101
-
102
- **3 · Svelte / React island** only when the component *is* a framework component (e.g. **shadcn/ui**, a
103
- React-only lib) with no native/vanilla equivalent. Ships that framework's runtime (lazy, code-split via
104
- `client:visible`); props + events wire it to muten state. The narrow last resort, not the default.
105
-
106
- > "Not expressible in pure muten" usually means **tier 2 (platform)**, rarely **tier 3 (React)** — and every
107
- > escape is *bounded* (the oracle still checks the border), so the language never grows into a UI kit.
108
-
109
- **The mechanism and the honest caveat.** Each escape keeps the AI-first guarantee because the compiler still
110
- validates the *seam*: the `@state` props and `action` callbacks crossing into a `Custom`/island, and the call site
111
- of a `use` function (an undeclared one is a `check` error). So coupling in chart.js, zod, or a shadcn island never
112
- costs you the oracle on the muten side. The caveat to be clear about: a `use` function or an island ships real JS,
113
- and the standalone `muten build` (static HTML, for the pure-muten content) does **not** bundle it — those deploy
114
- through **`vite build`** (the same path the dev server runs). Rule of thumb: *pure-muten static content →
115
- `muten build`; the moment you add `use`/islands/shared cross-page state → a normal `vite build`.* The dev server
116
- (`npm run dev`) handles all tiers either way.
114
+ | Tier | What it reaches | Typical examples | The trade |
115
+ |---|---|---|---|
116
+ | **1 · Pure muten** | the declarative 80% - pages, routing (params, guards, `/404`), `state`/`store`/`get`, the **list toolkit**, `Form` (+validation), `query` over REST, SSG + SEO | a whole **CRUD / SaaS / catalog / dashboard / content** app | **zero extra deps** |
117
+ | **2 · muten + the platform** | native HTML + CSS libs via `class()`, **vanilla JS via `Custom`**, JS logic via `use … from "./lib.ts"` | `<dialog>`, Tailwind/DaisyUI, chart.js, Leaflet, flatpickr, Quill, zod, date-fns | a JS dep - **no framework runtime** |
118
+ | **3 · Svelte / React island** | a real framework component used as a node - props ↓, events ↑ | **shadcn/ui**, a React-only lib (no native/vanilla equivalent) | ships that framework's runtime (lazy, code-split) |
119
+
120
+ Almost every "hard widget" lands at **tier 2, without React**; tier 3 is the narrow last resort, not the default.
121
+
122
+ > "Not expressible in pure muten" usually means **tier 2 (platform)**, rarely **tier 3 (a framework component)** -
123
+ > and every escape is *bounded*: the oracle still checks the border, so the language never grows into a UI kit.
124
+
125
+ **Why the escapes stay safe.** The compiler validates the *seam* - the `@state` props and `action` callbacks
126
+ crossing into a `Custom`/island, and the call site of a `use` function (an undeclared one is a `check` error). So
127
+ coupling in chart.js, zod, or a shadcn island never costs you the oracle on the muten side.
128
+
129
+ **Deploy - the honest caveat.** A `use` function or an island ships real JS that the static `muten build` does
130
+ **not** bundle:
131
+
132
+ | Your app uses… | Deploy with |
133
+ |---|---|
134
+ | Pure muten, static content | `muten build` (zero-JS HTML) - or `vite build` |
135
+ | `use` JS functions, islands, or shared cross-page state | **`vite build`** (it bundles them; the static build doesn't) |
136
+
137
+ `npm run dev` runs every tier regardless - this only affects the production *build*.
117
138
 
118
139
  ## The app, by convention
119
140
 
@@ -141,9 +162,9 @@ routes {
141
162
 
142
163
  ```sh
143
164
  muten build [dir] # compile → ./dist/<route>/index.html (+ app.map.json)
144
- muten check [dir] [--json] # parse + validate every page, no compile the deterministic ORACLE
165
+ muten check [dir] [--json] # parse + validate every page, no compile - the deterministic ORACLE
145
166
  # --json → structured diagnostics (code + loc + "did you mean…?") in ms, no browser
146
- muten map [dir] [--json] # emit app.map.json COLD (no build) the app graph an AI reads FIRST
167
+ muten map [dir] [--json] # emit app.map.json COLD (no build) - the app graph an AI reads FIRST
147
168
  ```
148
169
 
149
170
  `check` and `map` are the AI-first feedback loop: an agent asks the compiler "is this valid, and what
@@ -182,7 +203,7 @@ The compiler is a straight pipeline of small, single-purpose stages:
182
203
  .muten ─[lang]→ IR ─[ir: compose]→ tree ─[ir: flatten]→ Doc ─[ir: validate]→ ✓ ─[compile]→ JS
183
204
  ```
184
205
 
185
- The source is TypeScript under `src/`, organized by **domain** each has its own README:
206
+ The source is TypeScript under `src/`, organized by **domain**: each has its own README:
186
207
 
187
208
  | Domain | Role |
188
209
  |---|---|
@@ -200,7 +221,7 @@ file-level conventions (≤500 lines, honest types, data-table dispatch, no magi
200
221
  ## Build
201
222
 
202
223
  `npm run build` = `tsc` (strict type-check) + `esbuild` → `dist/**/*.js`, **minified, per-file**
203
- (modules preserved, so nothing bundles into a heavy monolith). `dist/` is generated edit `src/`.
224
+ (modules preserved, so nothing bundles into a heavy monolith). `dist/` is generated - edit `src/`.
204
225
 
205
226
  ## Styling & escape hatch
206
227
 
@@ -208,7 +229,7 @@ muten imposes no theme. A page lays itself out with `style(…)` tokens (analyza
208
229
  `theme.muten`) and skins itself via `class("…")` (your CSS / Tailwind / anything). For behavior the
209
230
  primitives can't express, drop to a `Custom` component (`src/components/<Name>.js`).
210
231
 
211
- ## Islands Svelte & React
232
+ ## Islands - Svelte & React
212
233
 
213
234
  When a page needs a genuinely interactive widget or a framework UI lib muten can't express, mount a real
214
235
  Svelte/React component as an **island**. The `svelte:` / `react:` prefix on `use … from` is the only marker;
@@ -231,7 +252,7 @@ Page style(padding.xl, gap.md) {
231
252
  ```
232
253
 
233
254
  `prop: @state` sends a value **down** (a React island re-renders when the signal changes; Svelte mounts once);
234
- `onX: action` sends a callback that fires a muten action that's how an island writes **back** to muten state.
255
+ `onX: action` sends a callback that fires a muten action - that's how an island writes **back** to muten state.
235
256
  No `client:` directive = hydrate on load. Add the framework's Vite plugin next to `muten()`:
236
257
 
237
258
  ```js
@@ -244,16 +265,16 @@ export default { plugins: [muten(), svelte(), react()] };
244
265
 
245
266
  ## Status & roadmap (honest)
246
267
 
247
- **Pre-1.0 the core is solid, the edges are young.** Build real apps with it; don't bet a critical
268
+ **Pre-1.0 - the core is solid, the edges are young.** Build real apps with it; don't bet a critical
248
269
  production system on it yet (small ecosystem, one maintainer, not yet battle-tested).
249
270
 
250
271
  **Solid today:** the language + compiler, the `check` / `build` / `map` CLI + oracle, the Vite plugin + dev
251
- server + HMR, the VS Code extension (live-lint + autocomplete), Svelte & React islands, the reproducible benchmark.
252
- The bounded list toolkit inline objects, `patch`, `each…where`, aggregates (`sum`/`count`/`avg`/`min`/`max`),
253
- `sort`/`sortDesc`, and page→store action composition so a real CRUD/dashboard app is pure muten, no JS escape.
272
+ server + HMR, the VS Code extension (live-lint + autocomplete), Svelte & React islands.
273
+ The bounded list toolkit - inline objects, `patch`, `each…where`, aggregates (`sum`/`count`/`avg`/`min`/`max`),
274
+ `sort`/`sortDesc`, and page→store action composition, so a real CRUD/dashboard app is pure muten, no JS escape.
254
275
  `Form` fields cover `text` · `number` (coerced) · `email` · `bool` (checkbox) · `enum` (select), with validation.
255
276
 
256
- **Experimental:** full island **SSR** `muten build` server-renders an island's HTML (first paint + SEO),
277
+ **Experimental:** full island **SSR**: `muten build` server-renders an island's HTML (first paint + SEO),
257
278
  but client hydration of that island still needs its framework bundled (pair the SSG HTML with the Vite client build).
258
279
 
259
280
  **Next, toward 1.0:**
@@ -263,6 +284,6 @@ but client hydration of that island still needs its framework bundled (pair the
263
284
  static `muten build`).
264
285
 
265
286
  **By design (the moat, not a bug):** muten is declarative + bounded. The list toolkit (`patch` · `sort` · the
266
- aggregates · `each…where`) gives the *common* list jobs without exposing raw `map`/`reduce` anything past that
287
+ aggregates · `each…where`) gives the *common* list jobs without exposing raw `map`/`reduce` - anything past that
267
288
  (an arbitrary transform) is a `use` JS function, and a real framework widget is a tier 2/3 escape. The ceiling is
268
289
  what keeps it small and analyzable; closing it would just make another general-purpose framework.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muten/core",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "AI-first frontend framework — compiles .muten files to vanilla JS + signals.",
5
5
  "repository": {
6
6
  "type": "git",