create-muten 0.0.8 → 0.0.9
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 +15 -15
- package/index.js +54 -37
- package/package.json +1 -1
- package/template/package.json +1 -1
- package/template/public/muten.svg +4 -0
- package/template/src/components/Snippet.js +19 -0
- package/template/src/pages/home/home.muten +42 -6
package/README.md
CHANGED
|
@@ -60,35 +60,36 @@ In an interactive terminal it prompts for a few things (defaults in parentheses)
|
|
|
60
60
|
| Prompt | Options | Default |
|
|
61
61
|
|---|---|---|
|
|
62
62
|
| **Project name** | any valid folder name | `muten-app` |
|
|
63
|
-
| **Template** | `
|
|
64
|
-
| **
|
|
63
|
+
| **Template** | `muten` / `muten + React` / `muten + Svelte` | `muten` |
|
|
64
|
+
| **Styling** | `css` / `scss` | `css` |
|
|
65
65
|
| **Add Tailwind CSS?** | `Y` / `n` (CSS only) | `n` |
|
|
66
66
|
| **Add DaisyUI?** | `Y` / `n` (needs Tailwind) | `n` |
|
|
67
|
-
| **Framework islands?** | `none` / `svelte` / `react` / `both` | `none` |
|
|
68
67
|
| **Package manager** | `npm` / `pnpm` / `yarn` / `bun` | the one that launched it |
|
|
69
68
|
| **Install deps and start dev now?** | `Y` / `n` | `Y` |
|
|
70
69
|
|
|
71
70
|
Tailwind is an optional add-on **on top of** CSS (the look layer; you still style via `class("…")`) — it
|
|
72
71
|
wires `@tailwindcss/vite` + an `@import "tailwindcss"` and notes the setup in the app's `.claude/` guide.
|
|
73
72
|
|
|
74
|
-
## Templates
|
|
73
|
+
## Templates (flavors)
|
|
74
|
+
|
|
75
|
+
Every flavor scaffolds the **same** welcome page and the same `.muten` workflow — the only difference is
|
|
76
|
+
whether a framework's island plugin is pre-wired:
|
|
75
77
|
|
|
76
78
|
| Template | What you get |
|
|
77
79
|
|---|---|
|
|
78
|
-
| **
|
|
79
|
-
| **
|
|
80
|
-
| **
|
|
80
|
+
| **muten** | pure muten — the AI-first DSL, zero framework runtime |
|
|
81
|
+
| **muten + React** | same, plus `@vitejs/plugin-react` + React, so you can drop in a **React island** (shadcn/Radix, any React lib) |
|
|
82
|
+
| **muten + Svelte** | same, plus `@sveltejs/vite-plugin-svelte` + Svelte, for **Svelte islands** (a lighter runtime) |
|
|
83
|
+
|
|
84
|
+
An *island* is a real framework component used as a node — `use X from "react:./X.jsx"` →
|
|
85
|
+
`X(value: @s, onChange: act) client:visible` (props ↓ + events ↑, lazy + code-split). Default to `.muten`;
|
|
86
|
+
reach for an island only for a widget muten can't express.
|
|
81
87
|
|
|
82
|
-
When **Tailwind or DaisyUI** is
|
|
88
|
+
When **Tailwind or DaisyUI** is added, `theme.muten` is centralized to **match Tailwind's scale** (so
|
|
83
89
|
`style()` tokens and Tailwind utilities share one scale, e.g. `style(gap.md)` == `gap-4`); plain CSS/SCSS
|
|
84
90
|
keeps the default scale. **DaisyUI** adds component classes (`btn`, `card`, `modal`) usable in `class("…")` —
|
|
85
91
|
pure classes, no React; behavior is Muten state + `on()`.
|
|
86
92
|
|
|
87
|
-
**Framework islands** (`svelte` / `react` / `both`) wire `@sveltejs/vite-plugin-svelte` / `@vitejs/plugin-react`
|
|
88
|
-
into `vite.config.mjs` so you can drop a real Svelte/React component (incl. React libs like **shadcn/Radix**)
|
|
89
|
-
into a page as an *island* — `use X from "react:./X.jsx"` → `X(value: @s, onChange: act) client:visible`
|
|
90
|
-
(props ↓ + events ↑, lazy + code-split). Default to `.muten`; reach for an island only for the hard widget.
|
|
91
|
-
|
|
92
93
|
If you accept the last prompt it runs `<pm> install` followed by `<pm> run dev` — your app is live in a
|
|
93
94
|
single step. Choosing SCSS also adds `sass` and switches the stylesheet to `.scss` automatically.
|
|
94
95
|
|
|
@@ -105,11 +106,10 @@ create-muten my-app --css --no-install # just scaffold, decide later
|
|
|
105
106
|
| Flag | Effect |
|
|
106
107
|
|---|---|
|
|
107
108
|
| `<name>` | the project folder (positional argument) |
|
|
108
|
-
| `--template <
|
|
109
|
+
| `--template <muten\|react\|svelte>` | flavor (default: `muten`); `--react` / `--svelte` are shortcuts |
|
|
109
110
|
| `--css` / `--scss` | pick the stylesheet (default: `css`) |
|
|
110
111
|
| `--tailwind` | add Tailwind CSS v4 on top of CSS (forces `--css`) |
|
|
111
112
|
| `--daisyui` | add DaisyUI component classes (implies `--tailwind`) |
|
|
112
|
-
| `--svelte` / `--react` | wire the Svelte / React island plugin (drop in framework components, e.g. shadcn) |
|
|
113
113
|
| `--pm <npm\|pnpm\|yarn\|bun>` | package manager to use (default: detected) |
|
|
114
114
|
| `--no-install` | scaffold only — don't install or start the dev server |
|
|
115
115
|
| `--help` | print usage and exit |
|
package/index.js
CHANGED
|
@@ -15,8 +15,7 @@ import color from 'picocolors';
|
|
|
15
15
|
|
|
16
16
|
const SELF = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const TEMPLATE = join(SELF, 'template');
|
|
18
|
-
const
|
|
19
|
-
const TEMPLATES = ['basic', 'routing', 'full'];
|
|
18
|
+
const TEMPLATES = ['muten', 'react', 'svelte']; // the "template" IS the flavor: pure muten, or muten + a framework for islands
|
|
20
19
|
const PKG = JSON.parse(readFileSync(join(SELF, 'package.json'), 'utf8'));
|
|
21
20
|
const PMS = ['npm', 'pnpm', 'yarn', 'bun'];
|
|
22
21
|
|
|
@@ -37,6 +36,31 @@ const tailwindStyles = (daisyui) => `@import "tailwindcss";${daisyui ? '\n@plugi
|
|
|
37
36
|
/* Muten layout primitive Tailwind doesn't define */
|
|
38
37
|
.stack { display: flex; flex-direction: column; }
|
|
39
38
|
`;
|
|
39
|
+
// Starter welcome page styles (used by the scaffolded home.muten). Self-contained plain CSS — looks good
|
|
40
|
+
// with or without Tailwind; delete it (and the page) when you build your own.
|
|
41
|
+
const WELCOME_CSS = `
|
|
42
|
+
/* — starter welcome page (src/pages/home/home.muten) — delete when you build your own — */
|
|
43
|
+
.welcome { background: #fafafa; color: #18181b; padding: 64px 24px; }
|
|
44
|
+
.wrap { max-width: 720px; margin: 0 auto; display: flex; flex-direction: column; gap: 52px; }
|
|
45
|
+
.hero { text-align: center; }
|
|
46
|
+
.logo { width: 64px; height: 64px; border-radius: 16px; margin: 0 auto; box-shadow: 0 6px 20px rgba(255,94,0,.28); }
|
|
47
|
+
.brand { font-size: clamp(40px, 8vw, 58px); font-weight: 800; letter-spacing: -.04em; line-height: 1; margin-top: 22px; background: linear-gradient(135deg, #ff5e00, #ff9a00); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
|
48
|
+
.tagline { font-size: 18px; font-weight: 600; color: #27272a; margin-top: 10px; }
|
|
49
|
+
.lead { max-width: 580px; margin: 14px auto 0; color: #52525b; font-size: 16px; line-height: 1.65; }
|
|
50
|
+
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
|
|
51
|
+
.stat { border: 1px solid #e4e4e7; border-radius: 14px; padding: 22px 16px; text-align: center; background: #fff; }
|
|
52
|
+
.stat-n { font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: #ff5e00; }
|
|
53
|
+
.stat-l { color: #71717a; font-size: 12px; line-height: 1.45; margin-top: 6px; }
|
|
54
|
+
.section { display: flex; flex-direction: column; gap: 14px; }
|
|
55
|
+
.h2 { font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
|
|
56
|
+
.snippet { background: #18181b; color: #e4e4e7; border-radius: 14px; padding: 20px 22px; margin: 0; overflow-x: auto; white-space: pre; font: 13px/1.7 ui-monospace, 'SF Mono', Menlo, Consolas, monospace; }
|
|
57
|
+
.note { color: #71717a; font-size: 13px; line-height: 1.55; }
|
|
58
|
+
.cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
|
|
59
|
+
.card { border: 1px solid #e4e4e7; border-radius: 14px; padding: 18px; background: #fff; }
|
|
60
|
+
.card-title { font-size: 14px; font-weight: 600; margin-bottom: 5px; }
|
|
61
|
+
.card-text { color: #71717a; font-size: 13px; line-height: 1.5; }
|
|
62
|
+
@media (max-width: 560px) { .stats, .cards { grid-template-columns: 1fr; } }
|
|
63
|
+
`;
|
|
40
64
|
// vite.config composed from the chosen options — muten always; svelte/react add island plugins; tailwind last.
|
|
41
65
|
const viteConfig = ({ tailwind, svelte, react }) => {
|
|
42
66
|
const imports = [`import muten from '@muten/core/vite-plugin-muten.js';`];
|
|
@@ -95,20 +119,26 @@ const detectPM = () => { const ua = process.env.npm_config_user_agent || ''; ret
|
|
|
95
119
|
const validName = (n) => /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(n);
|
|
96
120
|
const keep = (v) => { if (isCancel(v)) { cancel('Cancelled.'); process.exit(0); } return v; };
|
|
97
121
|
|
|
122
|
+
// The muten mark + wordmark, in the brand orange (#FF5E00) — shown before the prompts. Truecolor ANSI
|
|
123
|
+
// (modern terminals); degrades to plain text where unsupported.
|
|
124
|
+
const logo = () => {
|
|
125
|
+
const o = '\x1b[38;2;255;94;0m', tile = '\x1b[48;2;255;94;0m\x1b[1m\x1b[97m', b = '\x1b[1m', d = '\x1b[2m', r = '\x1b[0m';
|
|
126
|
+
console.log(`\n ${tile} M ${r} ${b}${o}muten${r}`);
|
|
127
|
+
console.log(` ${d} the AI-first frontend framework${r}\n`);
|
|
128
|
+
};
|
|
129
|
+
|
|
98
130
|
async function main() {
|
|
99
131
|
const argv = process.argv.slice(2);
|
|
100
132
|
const has = (f) => argv.includes(f);
|
|
101
133
|
const val = (f) => { const i = argv.indexOf(f); return i >= 0 ? argv[i + 1] : undefined; };
|
|
102
134
|
if (has('-v') || has('--version')) { console.log(PKG.version); return; }
|
|
103
|
-
if (has('-h') || has('--help')) { console.log('Usage: create-muten [name] [--template
|
|
135
|
+
if (has('-h') || has('--help')) { console.log('Usage: create-muten [name] [--template muten|react|svelte] [--css|--scss] [--tailwind] [--daisyui] [--pm npm|pnpm|yarn|bun] [--no-install]'); return; }
|
|
104
136
|
|
|
105
137
|
let name = argv.filter((a, i) => !a.startsWith('-') && argv[i - 1] !== '--pm' && argv[i - 1] !== '--template')[0];
|
|
106
|
-
let template = val('--template') || (has('--
|
|
138
|
+
let template = val('--template') || (has('--react') ? 'react' : has('--svelte') ? 'svelte' : has('--muten') ? 'muten' : undefined); // flavor: muten | react | svelte
|
|
107
139
|
let style = has('--scss') ? 'scss' : has('--css') ? 'css' : undefined; // the base stylesheet
|
|
108
140
|
let tailwind = has('--tailwind') ? true : undefined; // optional add-on (CSS only)
|
|
109
141
|
let daisyui = has('--daisyui') ? true : undefined; // component classes on Tailwind
|
|
110
|
-
let svelte = has('--svelte') ? true : undefined; // island: Svelte components
|
|
111
|
-
let react = has('--react') ? true : undefined; // island: React components (shadcn/Radix)
|
|
112
142
|
let pm = val('--pm');
|
|
113
143
|
let install = has('--no-install') ? false : undefined;
|
|
114
144
|
if (name && !validName(name)) { console.error(`Invalid name: "${name}" (letters, digits, . _ -)`); process.exit(1); }
|
|
@@ -118,51 +148,39 @@ async function main() {
|
|
|
118
148
|
|
|
119
149
|
// Styled prompts only with a real TTY (piped/CI input would hang); otherwise use flags + defaults.
|
|
120
150
|
if (process.stdin.isTTY) {
|
|
121
|
-
|
|
151
|
+
logo();
|
|
152
|
+
intro(color.dim(`create-muten v${PKG.version}`));
|
|
122
153
|
if (!name) name = keep(await text({ message: 'Project name', placeholder: 'muten-app', defaultValue: 'muten-app', validate: (v) => (v && !validName(v)) ? 'Use letters, digits, . _ - (start alphanumeric).' : undefined }));
|
|
123
154
|
if (!template) template = keep(await select({ message: 'Template', options: [
|
|
124
|
-
{ value: '
|
|
125
|
-
{ value: '
|
|
126
|
-
{ value: '
|
|
155
|
+
{ value: 'muten', label: 'muten', hint: 'pure — the AI-first DSL, zero framework runtime' },
|
|
156
|
+
{ value: 'react', label: 'muten + React', hint: 'React islands: shadcn, Radix, any React lib' },
|
|
157
|
+
{ value: 'svelte', label: 'muten + Svelte', hint: 'Svelte islands: a lighter runtime' },
|
|
127
158
|
] }));
|
|
128
|
-
if (
|
|
129
|
-
// Tailwind is an add-on on top of CSS (Tailwind v4 is CSS-native; Sass isn't recommended).
|
|
130
|
-
if (!style && !tailwind) style = keep(await select({ message: 'Stylesheet', options: [
|
|
159
|
+
if (!style) style = keep(await select({ message: 'Styling', options: [
|
|
131
160
|
{ value: 'css', label: 'CSS', hint: 'plain, zero deps' },
|
|
132
161
|
{ value: 'scss', label: 'SCSS', hint: 'adds sass' },
|
|
133
162
|
] }));
|
|
134
|
-
if (tailwind === undefined) tailwind = style === 'css' ? keep(await confirm({ message: 'Add Tailwind CSS?
|
|
135
|
-
if (tailwind && daisyui === undefined) daisyui = keep(await confirm({ message: 'Add DaisyUI? (component classes: btn, card, modal…)', initialValue:
|
|
136
|
-
if (svelte === undefined && react === undefined) {
|
|
137
|
-
const islands = keep(await select({ message: 'Framework islands? (drop in Svelte/React components for hard widgets)', options: [
|
|
138
|
-
{ value: 'none', label: 'None', hint: 'pure Muten' },
|
|
139
|
-
{ value: 'svelte', label: 'Svelte', hint: 'lighter to embed' },
|
|
140
|
-
{ value: 'react', label: 'React', hint: 'shadcn / Radix ecosystem' },
|
|
141
|
-
{ value: 'both', label: 'Both' },
|
|
142
|
-
] }));
|
|
143
|
-
svelte = islands === 'svelte' || islands === 'both';
|
|
144
|
-
react = islands === 'react' || islands === 'both';
|
|
145
|
-
}
|
|
163
|
+
if (tailwind === undefined) tailwind = style === 'css' ? keep(await confirm({ message: 'Add Tailwind CSS?', initialValue: false })) : false;
|
|
164
|
+
if (tailwind && daisyui === undefined) daisyui = keep(await confirm({ message: 'Add DaisyUI? (component classes: btn, card, modal…)', initialValue: false }));
|
|
146
165
|
}
|
|
147
166
|
name = name || 'muten-app';
|
|
148
|
-
template = template || '
|
|
149
|
-
if (template === 'full') tailwind = true; // full implies Tailwind
|
|
150
|
-
if (daisyui) tailwind = true; // DaisyUI is a Tailwind plugin
|
|
167
|
+
template = template || 'muten';
|
|
151
168
|
style = style || 'css';
|
|
169
|
+
if (daisyui) tailwind = true; // DaisyUI is a Tailwind plugin
|
|
152
170
|
if (tailwind === undefined) tailwind = false;
|
|
153
171
|
if (daisyui === undefined) daisyui = false;
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
172
|
+
if (tailwind) style = 'css'; // Tailwind v4 is CSS-native (not SCSS)
|
|
173
|
+
const svelte = template === 'svelte'; // the flavor IS the islands choice
|
|
174
|
+
const react = template === 'react';
|
|
157
175
|
pm = pm || dpm;
|
|
158
176
|
if (install === undefined) install = false;
|
|
159
177
|
|
|
160
178
|
const target = resolve(name);
|
|
161
179
|
if (existsSync(target)) { (process.stdin.isTTY ? cancel : console.error)(`"${name}" already exists.`); process.exit(1); }
|
|
162
180
|
|
|
163
|
-
//
|
|
181
|
+
// EVERY flavor scaffolds the SAME base template (identical welcome page); react/svelte only add the
|
|
182
|
+
// island plugin + deps below, and tailwind/daisyui only swap the stylesheet.
|
|
164
183
|
cpSync(TEMPLATE, target, { recursive: true });
|
|
165
|
-
if (template !== 'basic') cpSync(join(OVERLAYS, template), target, { recursive: true }); // routing / full overlay
|
|
166
184
|
const ignore = join(target, '_gitignore');
|
|
167
185
|
if (existsSync(ignore)) renameSync(ignore, join(target, '.gitignore'));
|
|
168
186
|
|
|
@@ -174,13 +192,13 @@ async function main() {
|
|
|
174
192
|
const appendAgents = (text) => { const f = join(target, '.claude', 'AGENTS.md'); if (existsSync(f)) writeFileSync(f, readFileSync(f, 'utf8') + text); };
|
|
175
193
|
|
|
176
194
|
if (tailwind) {
|
|
177
|
-
writeFileSync(join(target, 'src', 'styles.css'), tailwindStyles(daisyui));
|
|
195
|
+
writeFileSync(join(target, 'src', 'styles.css'), tailwindStyles(daisyui) + WELCOME_CSS);
|
|
178
196
|
writeFileSync(join(target, 'theme.muten'), TAILWIND_THEME); // scale centralized to Tailwind's
|
|
179
197
|
addDev({ tailwindcss: '^4.0.0', '@tailwindcss/vite': '^4.0.0' });
|
|
180
198
|
if (daisyui) addDev({ daisyui: '^5.0.0' });
|
|
181
199
|
appendAgents(TAILWIND_NOTE + (daisyui ? DAISY_NOTE : '')); // tell the AI what styling is available
|
|
182
200
|
} else {
|
|
183
|
-
writeFileSync(join(target, 'src', style === 'scss' ? 'styles.scss' : 'styles.css'), RESET);
|
|
201
|
+
writeFileSync(join(target, 'src', style === 'scss' ? 'styles.scss' : 'styles.css'), RESET + WELCOME_CSS);
|
|
184
202
|
}
|
|
185
203
|
if (style === 'scss') addDev({ sass: '^1.101.0' });
|
|
186
204
|
if (svelte) { addDep({ svelte: '^5.0.0' }); addDev({ '@sveltejs/vite-plugin-svelte': '^7.0.0' }); }
|
|
@@ -189,8 +207,7 @@ async function main() {
|
|
|
189
207
|
writeFileSync(join(target, 'vite.config.mjs'), viteConfig({ tailwind, svelte, react })); // composed: muten + chosen plugins
|
|
190
208
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
191
209
|
|
|
192
|
-
const
|
|
193
|
-
const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}${islandDesc ? ' + ' + islandDesc + ' islands' : ''}`;
|
|
210
|
+
const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}`;
|
|
194
211
|
if (!install) {
|
|
195
212
|
if (process.stdin.isTTY) { note(`cd ${name}\n${pm} install\n${pm} run dev`, 'Next steps'); outro(color.green(`Created ${name} (${desc})`)); }
|
|
196
213
|
else console.log(`\n Created ${name} (${desc}, ${pm})\n cd ${name} && ${pm} install && ${pm} run dev\n`);
|
package/package.json
CHANGED
package/template/package.json
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="157" height="157" viewBox="0 0 157 157" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M0 12C0 5.37258 5.37258 0 12 0H145C151.627 0 157 5.37258 157 12V145C157 151.627 151.627 157 145 157H12C5.37258 157 0 151.627 0 145V12Z" fill="#FF5E00"/>
|
|
3
|
+
<path d="M73.2841 144V91.6364H89.1364V101.25H89.7159C90.8068 98.0682 92.6477 95.5568 95.2386 93.7159C97.8295 91.875 100.92 90.9545 104.511 90.9545C108.148 90.9545 111.261 91.8864 113.852 93.75C116.443 95.6136 118.091 98.1136 118.795 101.25H119.341C120.318 98.1364 122.227 95.6477 125.068 93.7841C127.909 91.8977 131.261 90.9545 135.125 90.9545C140.08 90.9545 144.102 92.5455 147.193 95.7273C150.284 98.8864 151.83 103.227 151.83 108.75V144H135.159V112.568C135.159 109.955 134.489 107.966 133.148 106.602C131.807 105.216 130.068 104.523 127.932 104.523C125.636 104.523 123.83 105.273 122.511 106.773C121.216 108.25 120.568 110.239 120.568 112.739V144H104.545V112.398C104.545 109.966 103.886 108.045 102.568 106.636C101.25 105.227 99.5114 104.523 97.3523 104.523C95.8977 104.523 94.6136 104.875 93.5 105.58C92.3864 106.261 91.5114 107.239 90.875 108.511C90.2614 109.784 89.9545 111.284 89.9545 113.011V144H73.2841Z" fill="white"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Welcome-page code block (a `Custom` host component — the escape hatch for anything muten can't express).
|
|
2
|
+
// The snippet lives here in JS so it can contain quotes and { } that a .muten string can't. Delete with
|
|
3
|
+
// the welcome page. Used from home.muten as: Custom Snippet
|
|
4
|
+
const CODE = `screen home
|
|
5
|
+
|
|
6
|
+
state { count = 0 : number }
|
|
7
|
+
action inc mutates count { count.set(count + 1) }
|
|
8
|
+
|
|
9
|
+
Page style(padding.lg, gap.md) {
|
|
10
|
+
Title "Count: {count}"
|
|
11
|
+
Button "+1" -> inc
|
|
12
|
+
}`;
|
|
13
|
+
|
|
14
|
+
function mount(el) {
|
|
15
|
+
const pre = document.createElement('pre');
|
|
16
|
+
pre.className = 'snippet';
|
|
17
|
+
pre.textContent = CODE;
|
|
18
|
+
el.appendChild(pre);
|
|
19
|
+
}
|
|
@@ -1,9 +1,45 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
1
|
+
# The starter welcome page. Skin with class(…) + src/styles.css. Delete it and build your own — or just
|
|
2
|
+
# ask your AI assistant; it has the full muten reference in .claude/. The code sample is a Custom component
|
|
3
|
+
# (src/components/Snippet.js) because a .muten string can't hold the quotes/braces of muten source.
|
|
3
4
|
screen home
|
|
4
5
|
|
|
5
|
-
Page
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Page class("welcome") {
|
|
7
|
+
Stack class("wrap") {
|
|
8
|
+
|
|
9
|
+
Stack class("hero") {
|
|
10
|
+
Image "/muten.svg" alt "muten logo" class("logo")
|
|
11
|
+
Title "muten" class("brand")
|
|
12
|
+
Text "An AI-first frontend framework." class("tagline")
|
|
13
|
+
Text "You write small .muten files. muten compiles them to vanilla JS plus fine-grained signals — no virtual DOM, no runtime to ship. The language stays small and analyzable, so an AI can locate and mutate your app cheaply." class("lead")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Stack class("stats") {
|
|
17
|
+
Stack class("stat") { Title "2.8 KB" h3 class("stat-n") Text "muten ships, gzipped" class("stat-l") }
|
|
18
|
+
Stack class("stat") { Title "5 to 21x" h3 class("stat-n") Text "less JS than React, Vue, Svelte" class("stat-l") }
|
|
19
|
+
Stack class("stat") { Title "0 KB" h3 class("stat-n") Text "on a static page" class("stat-l") }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Stack class("section") {
|
|
23
|
+
Title "A whole page, in muten" h2 class("h2")
|
|
24
|
+
Custom Snippet
|
|
25
|
+
Text "Reactivity is automatic — read a state and only that spot re-renders. No hooks, no effects to wire." class("note")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Stack class("section") {
|
|
29
|
+
Title "Where to go next" h2 class("h2")
|
|
30
|
+
Stack class("cards") {
|
|
31
|
+
Stack class("card") { Title "Edit this page" h3 class("card-title") Text "src/pages/home/home.muten" class("card-text") }
|
|
32
|
+
Stack class("card") { Title "Add a route" h3 class("card-title") Text "Map URLs to pages in src/app.muten" class("card-text") }
|
|
33
|
+
Stack class("card") { Title "Style it" h3 class("card-title") Text "class(…) plus your CSS in src/styles.css — Tailwind optional" class("card-text") }
|
|
34
|
+
Stack class("card") { Title "Need React or Svelte?" h3 class("card-title") Text "Drop a real component in as an island, lazy and code-split" class("card-text") }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Stack class("section") {
|
|
39
|
+
Title "Your AI already knows muten" h2 class("h2")
|
|
40
|
+
Text "The full language reference ships in this project under .claude — an AGENTS guide plus a Claude skill. Ask your assistant to build a page; it reads that instead of guessing." class("lead")
|
|
41
|
+
Text "More Claude skills: docs.claude.com — Claude Code, Skills." class("note")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
9
45
|
}
|