@madojs/mado 0.11.1 → 0.13.0
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/AGENTS.md +116 -32
- package/CHANGELOG.md +216 -1
- package/README.md +158 -173
- package/dist/src/component.d.ts +6 -3
- package/dist/src/component.d.ts.map +1 -0
- package/dist/src/component.js +163 -36
- package/dist/src/component.js.map +1 -1
- package/dist/src/context.d.ts +13 -32
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +73 -48
- package/dist/src/context.js.map +1 -1
- package/dist/src/css.d.ts +1 -0
- package/dist/src/css.d.ts.map +1 -0
- package/dist/src/css.js +34 -5
- package/dist/src/css.js.map +1 -1
- package/dist/src/devtools-hook.d.ts +14 -0
- package/dist/src/devtools-hook.d.ts.map +1 -0
- package/dist/src/devtools-hook.js +27 -0
- package/dist/src/devtools-hook.js.map +1 -0
- package/dist/src/devtools.d.ts +24 -22
- package/dist/src/devtools.d.ts.map +1 -0
- package/dist/src/devtools.js +230 -58
- package/dist/src/devtools.js.map +1 -1
- package/dist/src/diagnostics.d.ts +12 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +68 -8
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/each.d.ts +1 -5
- package/dist/src/each.d.ts.map +1 -0
- package/dist/src/each.js +0 -8
- package/dist/src/each.js.map +1 -1
- package/dist/src/forms.d.ts +27 -96
- package/dist/src/forms.d.ts.map +1 -0
- package/dist/src/forms.js +184 -394
- package/dist/src/forms.js.map +1 -1
- package/dist/src/head.d.ts +7 -6
- package/dist/src/head.d.ts.map +1 -0
- package/dist/src/head.js +8 -7
- package/dist/src/head.js.map +1 -1
- package/dist/src/html/bindings.d.ts +1 -0
- package/dist/src/html/bindings.d.ts.map +1 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +4 -5
- package/dist/src/html/parser.d.ts.map +1 -0
- package/dist/src/html/parser.js +64 -15
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/html/template-types.d.ts +1 -0
- package/dist/src/html/template-types.d.ts.map +1 -0
- package/dist/src/html/template-types.js.map +1 -1
- package/dist/src/html/template.d.ts +5 -1
- package/dist/src/html/template.d.ts.map +1 -0
- package/dist/src/html/template.js +102 -13
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +15 -12
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/json.d.ts +3 -0
- package/dist/src/json.d.ts.map +1 -0
- package/dist/src/json.js +14 -0
- package/dist/src/json.js.map +1 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.d.ts.map +1 -0
- package/dist/src/lifecycle.js +3 -2
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/page.d.ts +63 -28
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js +5 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/persisted.d.ts +6 -1
- package/dist/src/persisted.d.ts.map +1 -0
- package/dist/src/persisted.js +17 -11
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +2 -1
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +128 -50
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +66 -0
- package/dist/src/router/base.d.ts.map +1 -0
- package/dist/src/router/base.js +168 -0
- package/dist/src/router/base.js.map +1 -0
- package/dist/src/router/manifest.d.ts +1 -0
- package/dist/src/router/manifest.d.ts.map +1 -0
- package/dist/src/router/manifest.js +156 -171
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +17 -2
- package/dist/src/router/match.d.ts.map +1 -0
- package/dist/src/router/match.js +60 -6
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +1 -0
- package/dist/src/router/navigation.d.ts.map +1 -0
- package/dist/src/router/navigation.js +58 -9
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +1 -0
- package/dist/src/signal.d.ts.map +1 -0
- package/dist/src/signal.js +46 -17
- package/dist/src/signal.js.map +1 -1
- package/dist/src/static-runtime.d.ts +82 -0
- package/dist/src/static-runtime.d.ts.map +1 -0
- package/dist/src/static-runtime.js +209 -0
- package/dist/src/static-runtime.js.map +1 -0
- package/dist/src/vite/index.d.ts +32 -3
- package/dist/src/vite/index.d.ts.map +1 -0
- package/dist/src/vite/index.js +49 -17
- package/dist/src/vite/index.js.map +1 -1
- package/docs/README.md +5 -8
- package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
- package/docs/architecture/v1-roadmap.md +19 -0
- package/docs/en/00-the-mado-way.md +1 -1
- package/docs/en/01-quickstart.md +183 -0
- package/docs/en/10-pages-and-components.md +279 -0
- package/docs/en/11-templates-and-signals.md +211 -0
- package/docs/en/12-routing.md +229 -0
- package/docs/en/13-data.md +227 -0
- package/docs/en/14-forms.md +91 -0
- package/docs/en/15-static-snapshots.md +181 -0
- package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
- package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
- package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
- package/docs/en/{14-testing.md → 22-testing.md} +6 -5
- package/docs/en/23-cookbook.md +292 -0
- package/docs/en/24-devtools-and-diagnostics.md +45 -0
- package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
- package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
- package/docs/en/33-migration-0.12-0.13.md +56 -0
- package/docs/en/40-llm-guide.md +774 -0
- package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
- package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
- package/docs/en/README.md +99 -27
- package/docs/recipes/nginx/Containerfile +26 -0
- package/docs/recipes/nginx/nginx.conf +42 -0
- package/llms.txt +263 -210
- package/package.json +51 -23
- package/scripts/clean.mjs +8 -0
- package/scripts/cli/generate.mjs +57 -10
- package/scripts/cli/help.mjs +13 -5
- package/scripts/cli/index.mjs +31 -7
- package/scripts/cli/init.mjs +19 -19
- package/scripts/cli/release.mjs +38 -83
- package/scripts/cli/run.mjs +5 -3
- package/scripts/logger.mjs +117 -0
- package/scripts/output-guard.mjs +128 -0
- package/scripts/preview.mjs +105 -24
- package/scripts/static/browser.mjs +695 -0
- package/scripts/static/discover.mjs +284 -0
- package/scripts/static/output.mjs +141 -0
- package/scripts/static/serialize.mjs +212 -0
- package/scripts/static/server.mjs +167 -0
- package/scripts/static.mjs +205 -0
- package/starters/default/README.md +57 -55
- package/starters/default/package.json +6 -12
- package/starters/default/src/app.routes.ts +20 -33
- package/starters/default/src/components/feature-card.component.ts +40 -0
- package/starters/default/src/components/live-counter.component.ts +57 -0
- package/starters/default/src/content/guides.ts +55 -0
- package/starters/default/src/main.ts +9 -13
- package/starters/default/src/pages/app.page.ts +41 -0
- package/starters/default/src/pages/guide.page.ts +40 -0
- package/starters/default/src/pages/home.page.ts +49 -0
- package/starters/default/src/pages/not-found.page.ts +18 -0
- package/starters/default/src/styles/document.css +39 -0
- package/starters/default/src/styles/reset.css +37 -0
- package/starters/default/src/styles/tokens.css +30 -0
- package/starters/default/src/styles.d.ts +3 -1
- package/starters/default/src/vite-env.d.ts +1 -1
- package/starters/default/tsconfig.node.json +13 -0
- package/starters/default/vite.config.ts +14 -4
- package/starters/modular/README.md +142 -0
- package/starters/modular/index.html +13 -0
- package/starters/modular/package.json +30 -0
- package/starters/modular/src/app.routes.ts +39 -0
- package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
- package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
- package/starters/modular/src/main.ts +16 -0
- package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
- package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
- package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
- package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
- package/starters/modular/src/modules/home/not-found.page.ts +14 -0
- package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
- package/starters/modular/src/styles.d.ts +1 -0
- package/starters/modular/src/vite-env.d.ts +1 -0
- package/starters/modular/tsconfig.json +33 -0
- package/starters/modular/tsconfig.node.json +13 -0
- package/starters/modular/vite.config.ts +132 -0
- package/TODO.md +0 -79
- package/dist/src/lazy.d.ts +0 -38
- package/dist/src/lazy.js +0 -73
- package/dist/src/lazy.js.map +0 -1
- package/docs/en/01-routing.md +0 -152
- package/docs/en/02-project-layout.md +0 -124
- package/docs/en/03-static-bake.md +0 -249
- package/docs/en/04-ide-setup.md +0 -162
- package/docs/en/07-llm-pitfalls.md +0 -724
- package/docs/en/08-llm-zero-history-test.md +0 -56
- package/docs/en/09-shadow-vs-light-dom.md +0 -174
- package/docs/en/11-layouts.md +0 -113
- package/docs/en/12-auth-and-api.md +0 -124
- package/docs/en/16-bake-cookbook.md +0 -100
- package/docs/en/17-shadow-dom-forms.md +0 -192
- package/docs/fr/00-the-mado-way.md +0 -85
- package/docs/fr/01-routing.md +0 -120
- package/docs/fr/02-project-layout.md +0 -84
- package/docs/fr/03-static-bake.md +0 -289
- package/docs/fr/04-ide-setup.md +0 -162
- package/docs/fr/05-why-mado.md +0 -193
- package/docs/fr/06-for-backenders.md +0 -438
- package/docs/fr/07-llm-pitfalls.md +0 -625
- package/docs/fr/08-llm-zero-history-test.md +0 -38
- package/docs/fr/09-shadow-vs-light-dom.md +0 -65
- package/docs/fr/10-app-architecture.md +0 -138
- package/docs/fr/11-layouts.md +0 -47
- package/docs/fr/12-auth-and-api.md +0 -76
- package/docs/fr/13-deployment.md +0 -57
- package/docs/fr/14-testing.md +0 -41
- package/docs/fr/15-error-handling.md +0 -50
- package/docs/fr/16-bake-cookbook.md +0 -88
- package/docs/fr/17-shadow-dom-forms.md +0 -196
- package/docs/fr/18-api-freeze-map.md +0 -63
- package/docs/fr/19-reactivity-ordering.md +0 -97
- package/docs/fr/20-v1-stability.md +0 -88
- package/docs/fr/README.md +0 -27
- package/docs/ru/00-the-mado-way.md +0 -84
- package/docs/ru/01-routing.md +0 -119
- package/docs/ru/02-project-layout.md +0 -84
- package/docs/ru/03-static-bake.md +0 -250
- package/docs/ru/04-ide-setup.md +0 -144
- package/docs/ru/05-why-mado.md +0 -193
- package/docs/ru/06-for-backenders.md +0 -428
- package/docs/ru/07-llm-pitfalls.md +0 -624
- package/docs/ru/08-llm-zero-history-test.md +0 -57
- package/docs/ru/09-shadow-vs-light-dom.md +0 -63
- package/docs/ru/10-app-architecture.md +0 -152
- package/docs/ru/11-layouts.md +0 -47
- package/docs/ru/12-auth-and-api.md +0 -75
- package/docs/ru/13-deployment.md +0 -66
- package/docs/ru/14-testing.md +0 -50
- package/docs/ru/15-error-handling.md +0 -56
- package/docs/ru/16-bake-cookbook.md +0 -95
- package/docs/ru/17-shadow-dom-forms.md +0 -193
- package/docs/ru/18-api-freeze-map.md +0 -64
- package/docs/ru/19-reactivity-ordering.md +0 -95
- package/docs/ru/20-v1-stability.md +0 -82
- package/docs/ru/README.md +0 -25
- package/docs/uk/00-the-mado-way.md +0 -82
- package/docs/uk/01-routing.md +0 -76
- package/docs/uk/02-project-layout.md +0 -73
- package/docs/uk/03-static-bake.md +0 -48
- package/docs/uk/04-ide-setup.md +0 -26
- package/docs/uk/05-why-mado.md +0 -34
- package/docs/uk/06-for-backenders.md +0 -55
- package/docs/uk/07-llm-pitfalls.md +0 -145
- package/docs/uk/08-llm-zero-history-test.md +0 -34
- package/docs/uk/09-shadow-vs-light-dom.md +0 -58
- package/docs/uk/10-app-architecture.md +0 -97
- package/docs/uk/11-layouts.md +0 -47
- package/docs/uk/12-auth-and-api.md +0 -70
- package/docs/uk/13-deployment.md +0 -40
- package/docs/uk/14-testing.md +0 -34
- package/docs/uk/15-error-handling.md +0 -32
- package/docs/uk/16-bake-cookbook.md +0 -36
- package/docs/uk/17-shadow-dom-forms.md +0 -193
- package/docs/uk/18-api-freeze-map.md +0 -61
- package/docs/uk/19-reactivity-ordering.md +0 -95
- package/docs/uk/20-v1-stability.md +0 -83
- package/docs/uk/README.md +0 -27
- package/scripts/bake.mjs +0 -570
- package/scripts/package-smoke.mjs +0 -77
- package/scripts/release-notes.mjs +0 -66
- package/scripts/size-budget.mjs +0 -91
- package/starters/default/src/modules/home/not-found.page.ts +0 -11
- package/starters/default/src/shared/ui/x-button.component.ts +0 -49
- /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
- /package/starters/{default → modular}/.editorconfig +0 -0
- /package/starters/{default → modular}/eslint.config.mjs +0 -0
- /package/starters/{default → modular}/public/favicon.svg +0 -0
- /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
- /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
- /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
`useForm()` adds reactive state to the browser's form and constraint-validation
|
|
4
|
+
model. HTML remains the source of truth for `required`, `type`, `min`, `max`,
|
|
5
|
+
`minlength`, `maxlength` and `pattern`.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { html, useForm } from "@madojs/mado";
|
|
9
|
+
|
|
10
|
+
const form = useForm({
|
|
11
|
+
initial: { email: "", age: "" as number | "", newsletter: false },
|
|
12
|
+
validate: async (values, { signal }) => {
|
|
13
|
+
const available = await api.emailAvailable(values.email, { signal });
|
|
14
|
+
return available ? null : { email: "Email is already registered" };
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
html`
|
|
19
|
+
<form @submit=${form.onSubmit(async (values, event) => {
|
|
20
|
+
await api.save(values, new FormData(event.currentTarget as HTMLFormElement));
|
|
21
|
+
})}>
|
|
22
|
+
<label>
|
|
23
|
+
Email
|
|
24
|
+
<input name="email" type="email" required
|
|
25
|
+
@input=${form.onInput} @blur=${form.onBlur} />
|
|
26
|
+
</label>
|
|
27
|
+
${() => form.touched().email && form.errors().email
|
|
28
|
+
? html`<small role="alert">${form.errors().email}</small>`
|
|
29
|
+
: null}
|
|
30
|
+
|
|
31
|
+
<input name="age" type="number" min="18" max="120" @input=${form.onInput} />
|
|
32
|
+
<label><input name="newsletter" type="checkbox" @input=${form.onInput} /> Subscribe</label>
|
|
33
|
+
<button type="submit" ?disabled=${() => form.submitting() || form.validating()}>
|
|
34
|
+
Save
|
|
35
|
+
</button>
|
|
36
|
+
</form>
|
|
37
|
+
`;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
`useForm<T>({ initial, validate? })` returns:
|
|
43
|
+
|
|
44
|
+
| Member | Meaning |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `values()` | current typed values |
|
|
47
|
+
| `errors()` | native and custom errors by field; `$form` is available for form-wide errors |
|
|
48
|
+
| `touched()` | fields that have blurred or participated in submit |
|
|
49
|
+
| `dirty()` | whether values differ from the current initial values |
|
|
50
|
+
| `isValid()` | whether the merged error map is empty |
|
|
51
|
+
| `submitting()` / `validating()` | current async activity |
|
|
52
|
+
| `setField(name, value)` | update one field programmatically |
|
|
53
|
+
| `validate(form?)` | run native and custom validation; returns `Promise<boolean>` |
|
|
54
|
+
| `reset(nextInitial?)` | abort validation, clear state and reset native controls |
|
|
55
|
+
| `onInput`, `onBlur`, `onSubmit(handler)` | DOM event handlers |
|
|
56
|
+
|
|
57
|
+
Custom validation receives `{ signal, form }`. Starting another validation or
|
|
58
|
+
calling `reset()` aborts the previous run. Treat abort as normal cancellation.
|
|
59
|
+
|
|
60
|
+
## Native controls
|
|
61
|
+
|
|
62
|
+
- Checkbox groups produce an array when the initial field value is an array;
|
|
63
|
+
a single checkbox produces a boolean.
|
|
64
|
+
- Radio groups produce the checked value.
|
|
65
|
+
- `<select multiple>` produces a string array.
|
|
66
|
+
- Number/range inputs produce a number, or `""` when empty.
|
|
67
|
+
- File inputs produce `File | null`, or `File[]` when `multiple` is present.
|
|
68
|
+
- The submit callback receives the native `SubmitEvent`; construct `FormData`
|
|
69
|
+
from its form when the backend expects multipart or repeated fields.
|
|
70
|
+
|
|
71
|
+
Use a native `<button type="submit">`. A button inside another element's
|
|
72
|
+
Shadow DOM is not a submit control for the outer form and keyboard submission
|
|
73
|
+
will not discover it.
|
|
74
|
+
|
|
75
|
+
## Mutations
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const save = mutation((values: Profile) => api.save(values), {
|
|
79
|
+
invalidates: ["/api/profile"],
|
|
80
|
+
});
|
|
81
|
+
const form = useForm({ initial: { displayName: "" } });
|
|
82
|
+
|
|
83
|
+
html`<form @submit=${form.onSubmit((values) => save.run(values))}>
|
|
84
|
+
<input name="displayName" required minlength="2" @input=${form.onInput} />
|
|
85
|
+
<button type="submit" ?disabled=${() => form.submitting()}>Save</button>
|
|
86
|
+
</form>`;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Write failures belong to the mutation; field and cross-field validation belong
|
|
90
|
+
to the form. See the [0.12 → 0.13 migration guide](./33-migration-0.12-0.13.md)
|
|
91
|
+
for the removed schema and field-array APIs.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Static snapshots (`mado static`)
|
|
2
|
+
|
|
3
|
+
> Browser-rendered static HTML for SEO and first paint. No SSR, no
|
|
4
|
+
> hydration, no template-language bake.
|
|
5
|
+
|
|
6
|
+
`mado static` is a *snapshot capture*: at release time Mado runs your
|
|
7
|
+
app in a real headless Chromium, lets the page render to the DOM
|
|
8
|
+
(including any Shadow DOM), then serialises the result — Declarative
|
|
9
|
+
Shadow DOM and all — to one HTML file per route. On first paint the
|
|
10
|
+
live SPA atomically replaces the static tree; there is no hydration
|
|
11
|
+
protocol and no node reconciliation.
|
|
12
|
+
|
|
13
|
+
It is the right tool for content that is the same for every visitor:
|
|
14
|
+
public landing pages, product / catalogue pages, documentation, blog
|
|
15
|
+
posts. It is intentionally **not** an SSR runtime for personalised
|
|
16
|
+
data.
|
|
17
|
+
|
|
18
|
+
## When `static` is suitable
|
|
19
|
+
|
|
20
|
+
- Public pages with a single canonical representation per URL.
|
|
21
|
+
- Pages whose data is known at build time (CMS dump, file system,
|
|
22
|
+
Markdown, JSON, build-time API call).
|
|
23
|
+
- Pages that need to appear fully rendered in `view-source:`, in
|
|
24
|
+
social preview crawlers, and to JS-disabled clients.
|
|
25
|
+
- First paint of a page that *later* upgrades into a live SPA route.
|
|
26
|
+
|
|
27
|
+
## When `static` is **not** suitable
|
|
28
|
+
|
|
29
|
+
- Personalised content per visitor (auth, geo, A/B). The capture sees
|
|
30
|
+
one DOM and would ship it to everyone.
|
|
31
|
+
- Real-time data (chat, dashboards, prices). The snapshot is frozen
|
|
32
|
+
the moment of capture.
|
|
33
|
+
- Routes behind guards. Static pages are public by definition; the
|
|
34
|
+
discovery pipeline refuses to capture guarded routes.
|
|
35
|
+
|
|
36
|
+
For those cases keep the route SPA-only — Mado still serves a clean
|
|
37
|
+
`_mado/spa.html` fallback for them.
|
|
38
|
+
|
|
39
|
+
## Declaring a static page
|
|
40
|
+
|
|
41
|
+
A page opts in through the `static` field on `page({ ... })`. Two
|
|
42
|
+
shapes are accepted:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// Single static page (no params)
|
|
46
|
+
import { html, page } from "@madojs/mado";
|
|
47
|
+
|
|
48
|
+
export default page({
|
|
49
|
+
static: true,
|
|
50
|
+
title: "Pricing",
|
|
51
|
+
head: () => ({ description: "Plans and pricing." }),
|
|
52
|
+
view: () => html`<main><h1>Pricing</h1></main>`,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// Dynamic static route — one capture per `paths()` entry
|
|
58
|
+
import { html, page } from "@madojs/mado";
|
|
59
|
+
|
|
60
|
+
export default page<{ slug: string }, Guide>({
|
|
61
|
+
static: {
|
|
62
|
+
paths: async () => guides.map((g) => ({ slug: g.slug })),
|
|
63
|
+
initialData: async ({ slug }) => loadGuide(slug),
|
|
64
|
+
},
|
|
65
|
+
title: ({ slug }) => guides.find((g) => g.slug === slug)?.title ?? slug,
|
|
66
|
+
head: ({ slug }, seed) => ({
|
|
67
|
+
description: seed?.summary ?? "",
|
|
68
|
+
}),
|
|
69
|
+
view: (ctx) => html`<article>${ctx.data.body}</article>`,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The seed produced by `initialData()` is JSON-serialised into the
|
|
74
|
+
captured HTML as `<script type="application/json" data-mado-static-data="…">`.
|
|
75
|
+
On first client boot Mado consumes it once so `page.load()` and
|
|
76
|
+
`page.head()` see the same data the snapshot saw — no extra fetch.
|
|
77
|
+
|
|
78
|
+
## Running
|
|
79
|
+
|
|
80
|
+
In normal use `mado static` is invoked through `mado release`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
mado release # vite build + mado static + deployment files
|
|
84
|
+
mado preview # serve out/ like a real static host
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
You can also invoke `mado static` on its own (for tighter loops in
|
|
88
|
+
CI), but it operates on the existing `out/` artefact produced by
|
|
89
|
+
`vite build`, so the canonical command is `release`.
|
|
90
|
+
|
|
91
|
+
A public origin is required so static documents carry absolute
|
|
92
|
+
canonical URLs. Set it in `vite.config.ts`:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { defineConfig } from "vite";
|
|
96
|
+
import { mado } from "@madojs/mado/vite";
|
|
97
|
+
|
|
98
|
+
export default defineConfig({
|
|
99
|
+
plugins: [mado({ site: "https://your-app.example" })],
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
…or override per run:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
mado release --base-url https://staging.example
|
|
107
|
+
MADO_SITE=https://staging.example mado release
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## What the generated HTML contains
|
|
111
|
+
|
|
112
|
+
For every captured route:
|
|
113
|
+
|
|
114
|
+
- The full rendered light DOM.
|
|
115
|
+
- Every open shadow root serialised through Declarative Shadow DOM
|
|
116
|
+
(`<template shadowrootmode="open">`).
|
|
117
|
+
- Adopted stylesheets materialised into `<style>` tags inside the
|
|
118
|
+
shadow root.
|
|
119
|
+
- A `<link rel="canonical">` and `<meta property="og:url">` derived
|
|
120
|
+
from `site + base + pathname` (if the page did not declare its own).
|
|
121
|
+
Both are marked `data-mado-head="static"` so the runtime `applyHead`
|
|
122
|
+
removes them on SPA navigation into a page without explicit head
|
|
123
|
+
metadata.
|
|
124
|
+
- The seed `<script type="application/json" data-mado-static-data="…">`
|
|
125
|
+
for routes that declared `initialData()`.
|
|
126
|
+
- Vite assets resolved through the active `base`.
|
|
127
|
+
|
|
128
|
+
`<form>`, `<input>`, `<select>`, `<textarea>` and `<details>` state
|
|
129
|
+
is serialised through attributes so a JS-disabled browser sees the
|
|
130
|
+
same shape the snapshot captured.
|
|
131
|
+
|
|
132
|
+
## Constraints and gotchas
|
|
133
|
+
|
|
134
|
+
- **`paths()` and `initialData()` run during discovery AND ship in the
|
|
135
|
+
client bundle.** Keep them browser-safe. Never read secrets, never
|
|
136
|
+
call private services.
|
|
137
|
+
- **Static pages cannot use guards** — route-level or layout-level.
|
|
138
|
+
- **Wildcard routes (`*`) cannot be static.** They are the SPA
|
|
139
|
+
fallback.
|
|
140
|
+
- **A compatible Chromium is required.** CI should install it through
|
|
141
|
+
Playwright:
|
|
142
|
+
```bash
|
|
143
|
+
npx playwright install --with-deps chromium
|
|
144
|
+
```
|
|
145
|
+
…or via the framework's installed `playwright-core` for revision
|
|
146
|
+
parity:
|
|
147
|
+
```bash
|
|
148
|
+
node node_modules/playwright-core/cli.js install --with-deps chromium
|
|
149
|
+
```
|
|
150
|
+
- **Capture is strict by default.** Any failed fetch (script, style,
|
|
151
|
+
resource(), custom element definition) fails the snapshot. A small
|
|
152
|
+
allow-list covers `/favicon.ico`, `/favicon.svg`, `/robots.txt`
|
|
153
|
+
and `data:` URLs.
|
|
154
|
+
- **All network calls must target the internal capture origin** serving the
|
|
155
|
+
freshly built `out/` tree. Undeclared external HTTP(S) requests are blocked;
|
|
156
|
+
provide deterministic `static.initialData()` instead.
|
|
157
|
+
|
|
158
|
+
## Comparison
|
|
159
|
+
|
|
160
|
+
| Approach | Mado static snapshot |
|
|
161
|
+
| ------------------------------------- | -------------------- |
|
|
162
|
+
| Next-style SSR + hydration | not used |
|
|
163
|
+
| Astro-style island runtime | not used |
|
|
164
|
+
| Build-time HTML from templates only | not used |
|
|
165
|
+
| Browser-rendered DOM + DSD + takeover | **used** |
|
|
166
|
+
|
|
167
|
+
The result is a plain HTML file. Search engines, social preview
|
|
168
|
+
crawlers and `curl` see exactly what your app renders on first paint;
|
|
169
|
+
no JavaScript step is required to make the document meaningful.
|
|
170
|
+
|
|
171
|
+
## Cookbook
|
|
172
|
+
|
|
173
|
+
Concrete recipes for blog, product catalogue, documentation site and
|
|
174
|
+
multi-locale builds live in [23-cookbook.md](./23-cookbook.md).
|
|
175
|
+
|
|
176
|
+
## TL;DR
|
|
177
|
+
|
|
178
|
+
- Declare `static: true | { paths, initialData }` on `page({ ... })`.
|
|
179
|
+
- Run `mado release`.
|
|
180
|
+
- Ship `out/` to any static host.
|
|
181
|
+
- The live SPA takes over atomically on first paint.
|
|
@@ -4,7 +4,58 @@ The official starter is the canonical production shape for Mado apps. It is
|
|
|
4
4
|
not a demo architecture and not a framework inside the framework: it is plain
|
|
5
5
|
files, imports, Mado primitives, and ESLint boundaries.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Project layout (universal starter)
|
|
8
|
+
|
|
9
|
+
Both starters share the same top-level shape. The universal starter is the
|
|
10
|
+
minimum:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
my-app/
|
|
14
|
+
├── package.json # exactly one runtime dep: @madojs/mado
|
|
15
|
+
├── tsconfig.json # strict TS, ES2022, Bundler resolution
|
|
16
|
+
├── vite.config.ts # mado() from @madojs/mado/vite
|
|
17
|
+
├── index.html # Vite entry + SPA shell
|
|
18
|
+
├── public/ # static assets (favicons, images, robots.txt)
|
|
19
|
+
└── src/
|
|
20
|
+
├── main.ts # entry: mount router into #app
|
|
21
|
+
├── app.routes.ts # one app map (default + named `manifest`)
|
|
22
|
+
├── pages/ # *.page.ts files
|
|
23
|
+
├── components/ # reusable <x-tag> components
|
|
24
|
+
└── shared/ # http/, lib/, styles/, ui/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`index.html` belongs at the project root because Vite treats it as an entry
|
|
28
|
+
template, not a static public file. Put only copy-as-is files in `public/`.
|
|
29
|
+
|
|
30
|
+
### The three artifact states
|
|
31
|
+
|
|
32
|
+
| Folder | What it is | Who writes | Who reads | Deploy? |
|
|
33
|
+
| --------- | ----------------------------------------------------------- | --------------- | ----------------------- | ------------- |
|
|
34
|
+
| `src/` | your TypeScript source | you | Vite, `tsc --noEmit` | ❌ no |
|
|
35
|
+
| `public/` | static assets copied as-is | you | Vite build | ✅ via `out/` |
|
|
36
|
+
| `out/` | **the deploy artifact**: SPA shell + assets + snapshots | `mado release` | nginx / CDN / CF Pages | ✅ **yes** |
|
|
37
|
+
|
|
38
|
+
One-liner: develop with `mado dev`, ship with `mado release`, upload `out/`.
|
|
39
|
+
|
|
40
|
+
### Naming rules
|
|
41
|
+
|
|
42
|
+
| What | Style | Example |
|
|
43
|
+
| -------------------------- | ------------------ | ---------------------- |
|
|
44
|
+
| File | kebab-case | `user-profile.ts` |
|
|
45
|
+
| Component tag | `x-` + kebab | `<x-user-profile>` |
|
|
46
|
+
| Context | PascalCase + `Ctx` | `ThemeCtx`, `AuthCtx` |
|
|
47
|
+
| Signal | camelCase | `userId`, `isLoggedIn` |
|
|
48
|
+
| Page-internal element | `x-<route>-page` | `<x-posts-page>` |
|
|
49
|
+
|
|
50
|
+
### What does NOT belong in `src/`
|
|
51
|
+
|
|
52
|
+
- ❌ Build-tool configs beyond `vite.config.ts` with `mado()`.
|
|
53
|
+
- ❌ `.env` files — read env in `src/shared/lib/config.ts` from
|
|
54
|
+
`import.meta.env` and import that one module everywhere.
|
|
55
|
+
- ❌ Tests mixed with code — put them in `test/`.
|
|
56
|
+
- ❌ `examples/` folder — keep large demos outside the app repo.
|
|
57
|
+
|
|
58
|
+
## File tree (modular reference starter)
|
|
8
59
|
|
|
9
60
|
```txt
|
|
10
61
|
src/
|
|
@@ -68,7 +119,7 @@ export default routes(manifest);
|
|
|
68
119
|
|
|
69
120
|
Rules:
|
|
70
121
|
|
|
71
|
-
- Export `manifest` so `mado
|
|
122
|
+
- Export `manifest` so `mado static` can discover bakeable pages.
|
|
72
123
|
- Modules never call `layout()`.
|
|
73
124
|
- Layouts describe app zones, not domains.
|
|
74
125
|
- Do not hide the router inside a custom element or a second shell in
|
|
@@ -139,8 +190,9 @@ Reusable leaf components keep their own styles in ``css`...` `` inside
|
|
|
139
190
|
`component()` options. They should depend on tokens (`var(--color-text)`)
|
|
140
191
|
rather than global classes.
|
|
141
192
|
|
|
142
|
-
|
|
143
|
-
|
|
193
|
+
Vite 8's standard CSS pipeline owns transforms and minification. Mado adds no
|
|
194
|
+
CSS transformer; applications can configure Vite directly when they have a
|
|
195
|
+
measured need.
|
|
144
196
|
|
|
145
197
|
## Growth
|
|
146
198
|
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
out/
|
|
11
|
-
├── index.html ←
|
|
11
|
+
├── index.html ← captured snapshot for / (or SPA shell)
|
|
12
12
|
├── assets/ ← Vite hashed assets
|
|
13
13
|
│ ├── *.gz ← precompressed gzip (gzip_static / Accept-Encoding)
|
|
14
14
|
│ └── *.br ← precompressed brotli (brotli_static / Accept-Encoding)
|
|
15
|
-
├── <route>/index.html ←
|
|
15
|
+
├── <route>/index.html ← captured snapshot per static route
|
|
16
|
+
├── _mado/spa.html ← SPA fallback shell (noindex)
|
|
16
17
|
├── sitemap.xml ← generated sitemap
|
|
17
18
|
├── favicon.svg ← your public/ assets copied verbatim
|
|
18
19
|
├── _redirects ← Cloudflare Pages / Netlify SPA fallback
|
|
@@ -61,7 +62,7 @@ Key lines of the recipe `nginx.conf`:
|
|
|
61
62
|
- `gzip_static on;` — serves the precompressed `.gz` files written by
|
|
62
63
|
`mado release`. Zero CPU at request time.
|
|
63
64
|
- `/assets/*` should be cached immutable; Vite filenames are content hashed.
|
|
64
|
-
- `try_files $uri $uri/ /
|
|
65
|
+
- `try_files $uri $uri/ /_mado/spa.html;` — SPA fallback so deep links work
|
|
65
66
|
after a hard refresh.
|
|
66
67
|
|
|
67
68
|
Enable HTTPS with Let's Encrypt / Certbot. Add HSTS once you have it.
|
|
@@ -75,7 +76,7 @@ mado release
|
|
|
75
76
|
npx wrangler pages deploy out --project-name=myapp
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
- The generated `_redirects` (`/* /
|
|
79
|
+
- The generated `_redirects` (`/* /_mado/spa.html 200`) gives you SPA fallback.
|
|
79
80
|
- The generated `_headers` (immutable cache for `/assets/*`, `no-cache` for
|
|
80
81
|
HTML) is honored by CF Pages.
|
|
81
82
|
- Baked routes are promoted to real route files (`out/<route>/index.html`),
|
|
@@ -89,8 +90,9 @@ Build command: npm ci && npx mado release
|
|
|
89
90
|
Output directory: out
|
|
90
91
|
```
|
|
91
92
|
|
|
92
|
-
For catalogs too big to
|
|
93
|
-
the external examples workspace rather than in the core
|
|
93
|
+
For catalogs too big to snapshot at release time, keep on-demand snapshot
|
|
94
|
+
experiments in the external examples workspace rather than in the core
|
|
95
|
+
package.
|
|
94
96
|
|
|
95
97
|
---
|
|
96
98
|
|
|
@@ -114,16 +116,15 @@ aws s3 sync out/ s3://my-bucket/ \
|
|
|
114
116
|
--cache-control "no-cache, must-revalidate" --include '*.html'
|
|
115
117
|
```
|
|
116
118
|
Configure CloudFront's "Default root object" to `index.html` and add a custom
|
|
117
|
-
error response: 403/404 → `/
|
|
119
|
+
error response: 403/404 → `/_mado/spa.html` with status 200 (SPA fallback).
|
|
118
120
|
|
|
119
121
|
**GitHub Pages**
|
|
120
122
|
```bash
|
|
121
123
|
mado release
|
|
122
124
|
# Push out/ into the gh-pages branch (or use actions/upload-pages-artifact)
|
|
123
125
|
```
|
|
124
|
-
Pages handles `index.html` automatically.
|
|
125
|
-
|
|
126
|
-
[`spa-github-pages`](https://github.com/rafgraph/spa-github-pages) trick.
|
|
126
|
+
Pages handles route `index.html` files automatically. `mado release` also writes
|
|
127
|
+
`404.html` from the noindex SPA shell for client-only routes.
|
|
127
128
|
|
|
128
129
|
---
|
|
129
130
|
|
|
@@ -175,17 +176,78 @@ jobs:
|
|
|
175
176
|
|
|
176
177
|
- **404 on hard refresh of a deep link.** Your host did not pick up SPA
|
|
177
178
|
fallback. nginx: check `try_files`. CF/Netlify: `_redirects` is present?
|
|
178
|
-
S3+CloudFront: configure the 404 → `/
|
|
179
|
+
S3+CloudFront: configure the 404 → `/_mado/spa.html` (200) error response.
|
|
179
180
|
- **HTML is cached forever.** Either your host sent a default
|
|
180
181
|
`Cache-Control: public, max-age=...` or you are sitting behind a CDN that
|
|
181
182
|
ignores `no-cache`. Add an explicit rule mirroring the matrix above.
|
|
182
183
|
- **`/assets/*` files change but the browser keeps the old one.** They
|
|
183
184
|
should not — the filename is hashed by Vite during `mado release`. If you bypassed
|
|
184
185
|
build and shipped your own unhashed JS, give it a hash or short cache.
|
|
185
|
-
- **
|
|
186
|
-
|
|
187
|
-
upgrade `@madojs/mado` and re-run
|
|
186
|
+
- **Captured snapshot shows `[object Object]`.** `mado static` rejects
|
|
187
|
+
non-JSON-serialisable seeds at build time and points at the bad
|
|
188
|
+
field. If you still see it, upgrade `@madojs/mado` and re-run
|
|
189
|
+
`mado release`.
|
|
190
|
+
|
|
191
|
+
## CSP and trusted content
|
|
192
|
+
|
|
193
|
+
Mado serialises JSON-LD and static seed JSON with `<`, U+2028 and U+2029
|
|
194
|
+
escaped, so data cannot close its script element. `unsafeHTML()` is different:
|
|
195
|
+
it is an explicit trust boundary and must receive only audited or sanitised
|
|
196
|
+
markup.
|
|
197
|
+
|
|
198
|
+
Start with a restrictive policy such as `default-src 'self'; script-src
|
|
199
|
+
'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'`
|
|
200
|
+
and tighten styles with nonces/hashes when your deployment supports them.
|
|
201
|
+
Trusted Types can enforce the same application boundary around any code that
|
|
202
|
+
produces trusted HTML. The devtools overlay is development-only and should not
|
|
203
|
+
be imported in production entrypoints.
|
|
204
|
+
|
|
205
|
+
## Sub-path deployments and `base`
|
|
206
|
+
|
|
207
|
+
A deployment under a sub-path (`https://your.site/docs/`, GitHub
|
|
208
|
+
Pages project URL, an admin panel mounted under `/admin/`, …) only
|
|
209
|
+
requires two changes:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// vite.config.ts
|
|
213
|
+
import { defineConfig } from "vite";
|
|
214
|
+
import { mado } from "@madojs/mado/vite";
|
|
215
|
+
|
|
216
|
+
export default defineConfig({
|
|
217
|
+
base: "/docs/", // ← active prefix
|
|
218
|
+
plugins: [mado({ site: "https://your.site" })],
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Then make every internal anchor go through `routeUrl()` + `data-link`
|
|
223
|
+
so the URL is correct under any base:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { html, routeUrl } from "@madojs/mado";
|
|
227
|
+
|
|
228
|
+
html`<a data-link href=${routeUrl("/")}>Home</a>`; // → "/docs/"
|
|
229
|
+
html`<a data-link href=${routeUrl("/guides/intro")}>Intro</a>`;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`mado release` honours `base` end-to-end:
|
|
233
|
+
|
|
234
|
+
- captured snapshots load assets through `/docs/assets/...`;
|
|
235
|
+
- sitemap URLs are `https://your.site/docs/...`;
|
|
236
|
+
- canonicals and `og:url` include the prefix;
|
|
237
|
+
- `mado preview` redirects bare `/` to `/docs/` and serves SPA
|
|
238
|
+
fallback under `/docs/<anything>`.
|
|
239
|
+
|
|
240
|
+
On the production host:
|
|
241
|
+
|
|
242
|
+
- **nginx** — keep `try_files $uri $uri/ /_mado/spa.html;` but mount the
|
|
243
|
+
app under the matching prefix (`location /docs/ { ... }`).
|
|
244
|
+
- **Cloudflare Pages / Netlify** — `_redirects` is generated with the
|
|
245
|
+
base prefix already baked in.
|
|
246
|
+
- **GitHub Pages (project site)** — set `base: "/<repo>/"` and use
|
|
247
|
+
the `gh-pages` deploy path; canonical URLs land at
|
|
248
|
+
`https://<user>.github.io/<repo>/...`.
|
|
188
249
|
|
|
189
|
-
See also: [`
|
|
190
|
-
`src
|
|
191
|
-
for the
|
|
250
|
+
See also: [`16-app-architecture.md`](./16-app-architecture.md) for the
|
|
251
|
+
`src/` / `public/` / `out/` model and
|
|
252
|
+
[`15-static-snapshots.md`](./15-static-snapshots.md) for the static
|
|
253
|
+
snapshot mechanics.
|
|
@@ -24,7 +24,7 @@ For a specific page, `page({ errorView })` wins over the global route boundary.
|
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
26
|
export default page({
|
|
27
|
-
load:
|
|
27
|
+
load: () => resource(() => "/reports", jsonFetcher<Report[]>()),
|
|
28
28
|
errorView: (err) => html`<x-report-error .error=${err}></x-report-error>`,
|
|
29
29
|
view: ({ data }) => html`<x-report .data=${data}></x-report>`,
|
|
30
30
|
});
|
|
@@ -53,10 +53,10 @@ Validation errors belong in `useForm()`. Server errors from writes belong near
|
|
|
53
53
|
the submit button.
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
|
-
const form = useForm(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
);
|
|
56
|
+
const form = useForm({
|
|
57
|
+
initial: { email: "" },
|
|
58
|
+
validate: (values, { signal }) => api.validateUser(values, { signal }),
|
|
59
|
+
});
|
|
60
60
|
|
|
61
61
|
const save = mutation((values) => api.post("/users", values), {
|
|
62
62
|
invalidates: ["/api/users*"],
|
|
@@ -66,6 +66,7 @@ html`
|
|
|
66
66
|
<form @submit=${form.onSubmit(async values => {
|
|
67
67
|
await save.run(values);
|
|
68
68
|
})}>
|
|
69
|
+
<input name="email" type="email" required @input=${form.onInput} />
|
|
69
70
|
<button ?disabled=${() => form.validating() || form.submitting()}>
|
|
70
71
|
Save
|
|
71
72
|
</button>
|
|
@@ -97,4 +98,6 @@ component("x-online", (ctx) => {
|
|
|
97
98
|
|
|
98
99
|
Log once at the boundary that owns recovery. Avoid logging the same failure in
|
|
99
100
|
the API client, resource, page and component. The user should get one visible
|
|
100
|
-
message and developers should get one
|
|
101
|
+
message and developers should get one structured diagnostic. Runtime records
|
|
102
|
+
are visible in the devtools Timeline/Errors panel and as `mado:diagnostic`
|
|
103
|
+
events; CLI automation can select `--log-format=json`.
|
|
@@ -48,7 +48,7 @@ Test behavior, not internal implementation details:
|
|
|
48
48
|
- route guards, redirects, scroll/focus behavior, error boundaries;
|
|
49
49
|
- forms: validation, async validation races, field arrays;
|
|
50
50
|
- resources/mutations: cache keys, invalidation, lifecycle cleanup;
|
|
51
|
-
- CLI flows: `mado release`, `mado
|
|
51
|
+
- CLI flows: `mado release`, `mado static`, `mado preview`.
|
|
52
52
|
|
|
53
53
|
## Browser smoke tests
|
|
54
54
|
|
|
@@ -74,9 +74,10 @@ services from framework tests. For app tests, make the API client injectable via
|
|
|
74
74
|
npm run typecheck
|
|
75
75
|
npm run build
|
|
76
76
|
npm test
|
|
77
|
-
npm run
|
|
77
|
+
npm run release
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
`mado release` runs the production path for an app: typecheck, Vite
|
|
81
|
-
|
|
82
|
-
`npm run build` still emits `dist/src`
|
|
80
|
+
`mado release` runs the production path for an app: typecheck, Vite
|
|
81
|
+
build, static-snapshot capture, compression and deploy helper files.
|
|
82
|
+
In the framework repository, `npm run build` still emits `dist/src`
|
|
83
|
+
for package tests and publishing.
|