create-muten 0.0.9 → 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 (34) hide show
  1. package/README.md +13 -5
  2. package/index.js +57 -10
  3. package/package.json +2 -2
  4. package/template/src/pages/home/home.muten +1 -8
  5. package/template-tauri/muten-logo.png +0 -0
  6. package/template-tauri/src-tauri/Cargo.toml +15 -0
  7. package/template-tauri/src-tauri/build.rs +3 -0
  8. package/template-tauri/src-tauri/capabilities/default.json +7 -0
  9. package/template-tauri/src-tauri/icons/128x128.png +0 -0
  10. package/template-tauri/src-tauri/icons/128x128@2x.png +0 -0
  11. package/template-tauri/src-tauri/icons/32x32.png +0 -0
  12. package/template-tauri/src-tauri/icons/64x64.png +0 -0
  13. package/template-tauri/src-tauri/icons/Square107x107Logo.png +0 -0
  14. package/template-tauri/src-tauri/icons/Square142x142Logo.png +0 -0
  15. package/template-tauri/src-tauri/icons/Square150x150Logo.png +0 -0
  16. package/template-tauri/src-tauri/icons/Square284x284Logo.png +0 -0
  17. package/template-tauri/src-tauri/icons/Square30x30Logo.png +0 -0
  18. package/template-tauri/src-tauri/icons/Square310x310Logo.png +0 -0
  19. package/template-tauri/src-tauri/icons/Square44x44Logo.png +0 -0
  20. package/template-tauri/src-tauri/icons/Square71x71Logo.png +0 -0
  21. package/template-tauri/src-tauri/icons/Square89x89Logo.png +0 -0
  22. package/template-tauri/src-tauri/icons/StoreLogo.png +0 -0
  23. package/template-tauri/src-tauri/icons/icon.icns +0 -0
  24. package/template-tauri/src-tauri/icons/icon.ico +0 -0
  25. package/template-tauri/src-tauri/icons/icon.png +0 -0
  26. package/template-tauri/src-tauri/src/lib.rs +6 -0
  27. package/template-tauri/src-tauri/src/main.rs +6 -0
  28. package/template-tauri/src-tauri/tauri.conf.json +29 -0
  29. package/overlays/full/src/app.muten +0 -21
  30. package/overlays/full/src/cart.store +0 -9
  31. package/overlays/full/src/pages/products/products.muten +0 -26
  32. package/overlays/routing/src/app.muten +0 -19
  33. package/overlays/routing/src/pages/about/about.muten +0 -15
  34. package/template/src/components/Snippet.js +0 -19
package/README.md CHANGED
@@ -61,14 +61,20 @@ In an interactive terminal it prompts for a few things (defaults in parentheses)
61
61
  |---|---|---|
62
62
  | **Project name** | any valid folder name | `muten-app` |
63
63
  | **Template** | `muten` / `muten + React` / `muten + Svelte` | `muten` |
64
- | **Styling** | `css` / `scss` | `css` |
65
- | **Add Tailwind CSS?** | `Y` / `n` (CSS only) | `n` |
66
- | **Add DaisyUI?** | `Y` / `n` (needs Tailwind) | `n` |
64
+ | **Styling** | `CSS` / `SCSS` / `Tailwind CSS` / `DaisyUI` (brings Tailwind) | `CSS` |
65
+ | **Add Vercel deploy config?** | `Y` / `n` | `n` |
66
+ | **Desktop app (Tauri)?** | `Y` / `n` | `n` |
67
67
  | **Package manager** | `npm` / `pnpm` / `yarn` / `bun` | the one that launched it |
68
68
  | **Install deps and start dev now?** | `Y` / `n` | `Y` |
69
69
 
70
- Tailwind is an optional add-on **on top of** CSS (the look layer; you still style via `class("…")`) it
71
- wires `@tailwindcss/vite` + an `@import "tailwindcss"` and notes the setup in the app's `.claude/` guide.
70
+ **Styling is one explicit choice** each is opt-in, nothing is bundled by default: `CSS` (plain) or `SCSS`
71
+ ship no framework; `Tailwind CSS` adds `@tailwindcss/vite` + `@import "tailwindcss"`; `DaisyUI` adds its
72
+ component classes on top (and brings Tailwind). You always style via `class("…")`.
73
+
74
+ **Targets are independent opt-ins** — web, desktop, both, or neither, from the same `.muten` source:
75
+ - **Vercel** writes a `vercel.json` so muten's real-path routes don't 404 on a hard refresh (SPA fallback to `index.html`).
76
+ - **Tauri** adds `src-tauri/` (a native desktop app — ships the OS webview, *not* a browser) + a `tauri` script:
77
+ `npm run tauri dev` / `tauri build`. Needs the [Rust toolchain](https://rustup.rs) installed (not auto-installed).
72
78
 
73
79
  ## Templates (flavors)
74
80
 
@@ -110,6 +116,8 @@ create-muten my-app --css --no-install # just scaffold, decide later
110
116
  | `--css` / `--scss` | pick the stylesheet (default: `css`) |
111
117
  | `--tailwind` | add Tailwind CSS v4 on top of CSS (forces `--css`) |
112
118
  | `--daisyui` | add DaisyUI component classes (implies `--tailwind`) |
119
+ | `--vercel` | add `vercel.json` (SPA fallback so real-path routes work on Vercel) |
120
+ | `--tauri` | add `src-tauri/` — a native desktop app (needs the Rust toolchain) |
113
121
  | `--pm <npm\|pnpm\|yarn\|bun>` | package manager to use (default: detected) |
114
122
  | `--no-install` | scaffold only — don't install or start the dev server |
115
123
  | `--help` | print usage and exit |
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // create-muten — scaffold a new Muten app, with modern interactive prompts (@clack/prompts).
3
3
  //
4
4
  // npm create muten@latest [name] (or: npx create-muten)
5
- // create-muten [name] [--template basic|routing|full] [--css|--scss] [--tailwind] [--daisyui] [--svelte] [--react] [--pm npm|pnpm|yarn|bun] [--no-install]
5
+ // create-muten [name] [--template muten|react|svelte] [--css|--scss] [--tailwind] [--daisyui] [--vercel] [--tauri] [--pm npm|pnpm|yarn|bun] [--no-install]
6
6
  //
7
7
  // Stylesheet (CSS or SCSS) is the base; Tailwind is an optional add-on ON TOP of CSS (it's a styling
8
8
  // library, not a stylesheet replacement). Interactive in a TTY; flags / non-TTY make it scriptable.
@@ -15,6 +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 TAURI_TEMPLATE = join(SELF, 'template-tauri'); // src-tauri/ overlay, copied only when --tauri
18
19
  const TEMPLATES = ['muten', 'react', 'svelte']; // the "template" IS the flavor: pure muten, or muten + a framework for islands
19
20
  const PKG = JSON.parse(readFileSync(join(SELF, 'package.json'), 'utf8'));
20
21
  const PMS = ['npm', 'pnpm', 'yarn', 'bun'];
@@ -53,7 +54,6 @@ const WELCOME_CSS = `
53
54
  .stat-l { color: #71717a; font-size: 12px; line-height: 1.45; margin-top: 6px; }
54
55
  .section { display: flex; flex-direction: column; gap: 14px; }
55
56
  .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
57
  .note { color: #71717a; font-size: 13px; line-height: 1.55; }
58
58
  .cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
59
59
  .card { border: 1px solid #e4e4e7; border-radius: 14px; padding: 18px; background: #fff; }
@@ -113,6 +113,25 @@ DaisyUI adds **component classes** on top of Tailwind — use them in \`class("
113
113
  \`@plugin "daisyui";\` is already in \`src/styles.css\`. Interactive behavior (toggle a modal/dropdown) you build
114
114
  with Muten: \`state\` + \`class(active when isOpen)\` + \`on(click: …)\`.
115
115
  `;
116
+ // Tauri = the SAME web build wrapped in a native OS-webview window (no browser bundled). Desktop target.
117
+ const TAURI_NOTE = (pm) => `
118
+ ## Desktop app (Tauri)
119
+ This app also ships as a native desktop app via Tauri (\`src-tauri/\`). The SAME \`.muten\` frontend runs in
120
+ an OS-webview window — build the UI exactly like the web app (routing works as-is: the webview runs the SPA,
121
+ no server, no URL bar, no fallback needed).
122
+ - \`${pm} run tauri:dev\` — run the desktop app (opens the native window; hot-reloads the frontend).
123
+ - \`${pm} run tauri:build\` — standalone native installer in \`src-tauri/target/release/bundle/\` (frontend embedded, no server).
124
+ - (\`${pm} run tauri\` alone does nothing — the Tauri CLI needs a subcommand like \`dev\`/\`build\`.)
125
+ - Needs the **Rust toolchain** on the machine (https://rustup.rs) — Tauri compiles a small native shell. Not auto-installed.
126
+ - Custom icon: \`${pm} run tauri icon path/to/logo.png\` regenerates \`src-tauri/icons/\`.
127
+ `;
128
+
129
+ // Deploy on Vercel: muten routes are real paths (History API), so an unmatched path must fall back to
130
+ // index.html (else a hard refresh of /about 404s). Static assets are served first; only routes rewrite.
131
+ const VERCEL_JSON = `{
132
+ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
133
+ }
134
+ `;
116
135
 
117
136
  // Which PM launched us? npm/pnpm/yarn/bun set npm_config_user_agent — the idiomatic default.
118
137
  const detectPM = () => { const ua = process.env.npm_config_user_agent || ''; return PMS.find((p) => ua.startsWith(p + '/')) || 'npm'; };
@@ -132,13 +151,15 @@ async function main() {
132
151
  const has = (f) => argv.includes(f);
133
152
  const val = (f) => { const i = argv.indexOf(f); return i >= 0 ? argv[i + 1] : undefined; };
134
153
  if (has('-v') || has('--version')) { console.log(PKG.version); return; }
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; }
154
+ if (has('-h') || has('--help')) { console.log('Usage: create-muten [name] [--template muten|react|svelte] [--css|--scss] [--tailwind] [--daisyui] [--vercel] [--tauri] [--pm npm|pnpm|yarn|bun] [--no-install]'); return; }
136
155
 
137
156
  let name = argv.filter((a, i) => !a.startsWith('-') && argv[i - 1] !== '--pm' && argv[i - 1] !== '--template')[0];
138
157
  let template = val('--template') || (has('--react') ? 'react' : has('--svelte') ? 'svelte' : has('--muten') ? 'muten' : undefined); // flavor: muten | react | svelte
139
158
  let style = has('--scss') ? 'scss' : has('--css') ? 'css' : undefined; // the base stylesheet
140
159
  let tailwind = has('--tailwind') ? true : undefined; // optional add-on (CSS only)
141
160
  let daisyui = has('--daisyui') ? true : undefined; // component classes on Tailwind
161
+ let vercel = has('--vercel') ? true : undefined; // a vercel.json with the SPA fallback rewrite
162
+ let tauri = has('--tauri') ? true : undefined; // src-tauri/ → native desktop app
142
163
  let pm = val('--pm');
143
164
  let install = has('--no-install') ? false : undefined;
144
165
  if (name && !validName(name)) { console.error(`Invalid name: "${name}" (letters, digits, . _ -)`); process.exit(1); }
@@ -156,12 +177,19 @@ async function main() {
156
177
  { value: 'react', label: 'muten + React', hint: 'React islands: shadcn, Radix, any React lib' },
157
178
  { value: 'svelte', label: 'muten + Svelte', hint: 'Svelte islands: a lighter runtime' },
158
179
  ] }));
159
- if (!style) style = keep(await select({ message: 'Styling', options: [
160
- { value: 'css', label: 'CSS', hint: 'plain, zero deps' },
161
- { value: 'scss', label: 'SCSS', hint: 'adds sass' },
162
- ] }));
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 }));
180
+ if (!style && tailwind === undefined) { // ONE explicit styling choice — each is opt-in, "CSS" = nothing extra
181
+ const styling = keep(await select({ message: 'Styling', options: [
182
+ { value: 'css', label: 'CSS', hint: 'plain — no framework, zero deps' },
183
+ { value: 'scss', label: 'SCSS', hint: 'adds sass' },
184
+ { value: 'tailwind', label: 'Tailwind CSS', hint: 'utility classes on top of CSS' },
185
+ { value: 'daisyui', label: 'DaisyUI', hint: 'component classes (btn, card, modal) brings Tailwind with it' },
186
+ ] }));
187
+ style = styling === 'scss' ? 'scss' : 'css';
188
+ tailwind = styling === 'tailwind' || styling === 'daisyui';
189
+ daisyui = styling === 'daisyui';
190
+ }
191
+ if (vercel === undefined) vercel = keep(await confirm({ message: 'Add Vercel deploy config? (vercel.json — fixes real-path routing on Vercel)', initialValue: false }));
192
+ if (tauri === undefined) tauri = keep(await confirm({ message: 'Desktop app? (Tauri — native window, ships the OS webview, needs Rust)', initialValue: false }));
165
193
  }
166
194
  name = name || 'muten-app';
167
195
  template = template || 'muten';
@@ -169,6 +197,8 @@ async function main() {
169
197
  if (daisyui) tailwind = true; // DaisyUI is a Tailwind plugin
170
198
  if (tailwind === undefined) tailwind = false;
171
199
  if (daisyui === undefined) daisyui = false;
200
+ if (vercel === undefined) vercel = false;
201
+ if (tauri === undefined) tauri = false;
172
202
  if (tailwind) style = 'css'; // Tailwind v4 is CSS-native (not SCSS)
173
203
  const svelte = template === 'svelte'; // the flavor IS the islands choice
174
204
  const react = template === 'react';
@@ -183,6 +213,7 @@ async function main() {
183
213
  cpSync(TEMPLATE, target, { recursive: true });
184
214
  const ignore = join(target, '_gitignore');
185
215
  if (existsSync(ignore)) renameSync(ignore, join(target, '.gitignore'));
216
+ if (vercel) writeFileSync(join(target, 'vercel.json'), VERCEL_JSON); // SPA fallback so real-path routes work on Vercel
186
217
 
187
218
  const pkgPath = join(target, 'package.json');
188
219
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
@@ -204,10 +235,26 @@ async function main() {
204
235
  if (svelte) { addDep({ svelte: '^5.0.0' }); addDev({ '@sveltejs/vite-plugin-svelte': '^7.0.0' }); }
205
236
  if (react) { addDep({ react: '^19.0.0', 'react-dom': '^19.0.0' }); addDev({ '@vitejs/plugin-react': '^6.0.0' }); }
206
237
  if (svelte || react) appendAgents(ISLANDS_NOTE({ svelte, react }));
238
+ if (tauri) { // native desktop wrapper around the same web build (dist)
239
+ cpSync(join(TAURI_TEMPLATE, 'src-tauri'), join(target, 'src-tauri'), { recursive: true });
240
+ writeFileSync(join(target, 'src-tauri', '.gitignore'), '/target\n/gen/schemas\n'); // npm strips real .gitignore from packages
241
+ const confPath = join(target, 'src-tauri', 'tauri.conf.json');
242
+ const conf = JSON.parse(readFileSync(confPath, 'utf8'));
243
+ conf.productName = name;
244
+ conf.identifier = `com.muten.${name.replace(/[^a-z0-9]/gi, '').toLowerCase() || 'app'}`; // reverse-DNS, no dashes/underscores
245
+ conf.app.windows[0].title = name;
246
+ conf.build.beforeDevCommand = `${pm} run dev`;
247
+ conf.build.beforeBuildCommand = `${pm} run build`;
248
+ writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
249
+ addDev({ '@tauri-apps/cli': '^2.0.0' });
250
+ // `tauri` alone errors (it needs a subcommand) → ship explicit run scripts so `tauri dev`/`build` are obvious.
251
+ pkg.scripts = { ...pkg.scripts, tauri: 'tauri', 'tauri:dev': 'tauri dev', 'tauri:build': 'tauri build' };
252
+ appendAgents(TAURI_NOTE(pm));
253
+ }
207
254
  writeFileSync(join(target, 'vite.config.mjs'), viteConfig({ tailwind, svelte, react })); // composed: muten + chosen plugins
208
255
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
209
256
 
210
- const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}`;
257
+ const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}${vercel ? ' + Vercel' : ''}${tauri ? ' + Tauri' : ''}`;
211
258
  if (!install) {
212
259
  if (process.stdin.isTTY) { note(`cd ${name}\n${pm} install\n${pm} run dev`, 'Next steps'); outro(color.green(`Created ${name} (${desc})`)); }
213
260
  else console.log(`\n Created ${name} (${desc}, ${pm})\n cd ${name} && ${pm} install && ${pm} run dev\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-muten",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Scaffold a new Muten app.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,5 +19,5 @@
19
19
  "picocolors": "^1.0.1"
20
20
  },
21
21
  "keywords": ["muten", "create-muten", "scaffold", "starter", "cli", "frontend"],
22
- "files": ["index.js", "template", "overlays"]
22
+ "files": ["index.js", "template", "template-tauri"]
23
23
  }
@@ -1,6 +1,5 @@
1
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.
2
+ # ask your AI assistant; it has the full muten reference in .claude/. Pure muten no JS, no escape hatch.
4
3
  screen home
5
4
 
6
5
  Page class("welcome") {
@@ -19,12 +18,6 @@ Page class("welcome") {
19
18
  Stack class("stat") { Title "0 KB" h3 class("stat-n") Text "on a static page" class("stat-l") }
20
19
  }
21
20
 
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
21
  Stack class("section") {
29
22
  Title "Where to go next" h2 class("h2")
30
23
  Stack class("cards") {
Binary file
@@ -0,0 +1,15 @@
1
+ [package]
2
+ name = "muten-app"
3
+ version = "0.1.0"
4
+ description = "A muten desktop app"
5
+ edition = "2021"
6
+
7
+ [lib]
8
+ name = "app_lib"
9
+ crate-type = ["staticlib", "cdylib", "rlib"]
10
+
11
+ [build-dependencies]
12
+ tauri-build = { version = "2", features = [] }
13
+
14
+ [dependencies]
15
+ tauri = { version = "2", features = [] }
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../gen/schemas/desktop-schema.json",
3
+ "identifier": "default",
4
+ "description": "enables the default permissions",
5
+ "windows": ["main"],
6
+ "permissions": ["core:default"]
7
+ }
@@ -0,0 +1,6 @@
1
+ #[cfg_attr(mobile, tauri::mobile_entry_point)]
2
+ pub fn run() {
3
+ tauri::Builder::default()
4
+ .run(tauri::generate_context!())
5
+ .expect("error while running tauri application");
6
+ }
@@ -0,0 +1,6 @@
1
+ // Prevents an extra console window on Windows in release, do NOT remove!!
2
+ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
+
4
+ fn main() {
5
+ app_lib::run()
6
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://schema.tauri.app/config/2",
3
+ "productName": "muten-app",
4
+ "version": "0.1.0",
5
+ "identifier": "com.muten.app",
6
+ "build": {
7
+ "frontendDist": "../dist",
8
+ "devUrl": "http://localhost:5173",
9
+ "beforeDevCommand": "npm run dev",
10
+ "beforeBuildCommand": "npm run build"
11
+ },
12
+ "app": {
13
+ "windows": [
14
+ { "title": "muten-app", "width": 1000, "height": 700 }
15
+ ],
16
+ "security": { "csp": null }
17
+ },
18
+ "bundle": {
19
+ "active": true,
20
+ "targets": "all",
21
+ "icon": [
22
+ "icons/32x32.png",
23
+ "icons/128x128.png",
24
+ "icons/128x128@2x.png",
25
+ "icons/icon.icns",
26
+ "icons/icon.ico"
27
+ ]
28
+ }
29
+ }
@@ -1,21 +0,0 @@
1
- # The app ROOT. `api` is the backend config (one place: base URL + default headers) — every `sources`
2
- # inherits it. The shell's navbar reads the cart store (global) and shows a live count.
3
- api {
4
- base: "https://fakestoreapi.com"
5
- }
6
-
7
- shell {
8
- Header style(row, between, center) class("p-4 shadow bg-white") {
9
- Link "Shop" -> / class("text-xl font-bold")
10
- Nav "Main" style(row, gap.md, center) {
11
- Link "Products" -> /products
12
- Span "🛒 {cart.count}" class("font-medium")
13
- }
14
- }
15
- slot
16
- }
17
-
18
- routes {
19
- / -> home
20
- /products -> products
21
- }
@@ -1,9 +0,0 @@
1
- # An app-global store (any `*.store` under src/). State is shared across pages + the shell, no imports.
2
- # `get` is a derived value; `action`s are the only way to mutate.
3
- state { items = [] : list<text> }
4
-
5
- get count = items.length
6
-
7
- action add mutates items <- id {
8
- items.push(id)
9
- }
@@ -1,26 +0,0 @@
1
- # A real data page: `query` state backed by a `sources` URL (relative → joined to the app's `api.base`).
2
- # `muten build` fetches it at build and bakes the rows into the HTML (SSG); the runtime then takes over.
3
- # Clicking "Add" calls the cart store action — the navbar count updates reactively.
4
- screen products
5
-
6
- meta { title "Products" }
7
-
8
- entity Product { title text price text }
9
-
10
- state { products = query products : list<Product> }
11
-
12
- sources { products: "/products" }
13
-
14
- Page style(padding.lg, gap.md) {
15
- Title "Products" class("text-2xl font-bold")
16
- when products.loading { Text "Loading…" class("opacity-60") }
17
- each products as p {
18
- Stack style(gap.sm) class("p-4 rounded-lg shadow bg-white") {
19
- Text "{p.title}" class("font-semibold")
20
- Stack style(row, between, center) {
21
- Span "$ {p.price}" class("text-lg")
22
- Button "Add to cart" -> cart.add(p.id) class("px-3 py-1 rounded bg-black text-white")
23
- }
24
- }
25
- }
26
- }
@@ -1,19 +0,0 @@
1
- # The app ROOT. A persistent `shell { … slot … }` wraps every page (navbar here); `slot` is where the
2
- # active page mounts. `routes` maps a real-path URL to a page (the folder under src/pages/).
3
- shell {
4
- Header style(row, between, center) class("nav") {
5
- Link "Home" -> /
6
- Nav "Main" {
7
- Link "About" -> /about
8
- }
9
- }
10
- slot
11
- Footer style(padding.md) {
12
- Text "Built with Muten"
13
- }
14
- }
15
-
16
- routes {
17
- / -> home
18
- /about -> about
19
- }
@@ -1,15 +0,0 @@
1
- # A second page. `meta { … }` sets the <head> title/description (og:* auto-derived) — applied on
2
- # navigation and baked into the static HTML at build. This page has no reactivity, so `muten build`
3
- # pre-renders it to zero-JS HTML (crawlable).
4
- screen about
5
-
6
- meta {
7
- title "About"
8
- description "About this Muten app."
9
- }
10
-
11
- Page style(padding.lg, gap.md) {
12
- Title "About"
13
- Text "Static pages compile to zero-JS HTML at build — crawlable and instant."
14
- Link "← Home" -> /
15
- }
@@ -1,19 +0,0 @@
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
- }