@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.
- package/README.md +116 -95
- 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
|
|
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*
|
|
21
|
-
|
|
22
|
-
- **Almost nothing to ship
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
70
|
+
## muten vs React / Vue / Svelte - the honest version
|
|
36
71
|
|
|
37
|
-
They are general-purpose: they build *anything*, with
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
thumb
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
252
|
-
The bounded list toolkit
|
|
253
|
-
`sort`/`sortDesc`, and page→store action composition
|
|
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
|
|
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`
|
|
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.
|