@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
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# LLM zero-history test
|
|
2
|
-
|
|
3
|
-
This document defines a practical validation test for Mado.
|
|
4
|
-
|
|
5
|
-
The question is not "can an LLM generate frontend code?" It can. The question is:
|
|
6
|
-
can a fresh LLM write idiomatic Mado without falling back to React-shaped code?
|
|
7
|
-
|
|
8
|
-
## Allowed context
|
|
9
|
-
|
|
10
|
-
For the first pass, give the agent only:
|
|
11
|
-
|
|
12
|
-
- `AGENTS.md`
|
|
13
|
-
- `README.md`
|
|
14
|
-
- `docs/en/07-llm-pitfalls.md`
|
|
15
|
-
- files from the external `madojs-examples` workspace only when the agent asks
|
|
16
|
-
for a larger app pattern
|
|
17
|
-
|
|
18
|
-
The agent may search targeted APIs in `src/` when blocked, but should not load
|
|
19
|
-
the whole framework into context.
|
|
20
|
-
|
|
21
|
-
## Task
|
|
22
|
-
|
|
23
|
-
Build a small ticket-admin SPA for a solo/backend developer.
|
|
24
|
-
|
|
25
|
-
Required behavior:
|
|
26
|
-
|
|
27
|
-
- routes: `/`, `/tickets`, `/tickets/new`, `/tickets/:id`, `*`;
|
|
28
|
-
- in-memory mock API with realistic async delays;
|
|
29
|
-
- list page with `resource()`, `queryParam()` search/status filters, `computed()`,
|
|
30
|
-
and keyed `each()` rows;
|
|
31
|
-
- create and edit flows with `useForm()` + `mutation()` + `invalidates`;
|
|
32
|
-
- local UI state with `signal()`;
|
|
33
|
-
- slotted shell, metric, and badge components for a more realistic admin UI;
|
|
34
|
-
- smoke test importing the built example.
|
|
35
|
-
|
|
36
|
-
## Failure checklist
|
|
37
|
-
|
|
38
|
-
Look for these after implementation:
|
|
39
|
-
|
|
40
|
-
- JSX, `useState`, `useEffect`, `ref`, `$state`, or class-style components;
|
|
41
|
-
- `${signal()}` or `${signal() + 1}` where a reactive child thunk is required;
|
|
42
|
-
- `disabled=${...}` instead of `?disabled=${...}`;
|
|
43
|
-
- dynamic lists rendered with unkeyed array mapping instead of `each()`;
|
|
44
|
-
- browser ESM imports without `.js`;
|
|
45
|
-
- `resource()` created outside component setup;
|
|
46
|
-
- new runtime dependencies or new public framework APIs.
|
|
47
|
-
|
|
48
|
-
## Result notes
|
|
49
|
-
|
|
50
|
-
The historical tickets implementation lives in the external examples workspace.
|
|
51
|
-
The core repository no longer ships that artifact or a dedicated smoke command;
|
|
52
|
-
use this document as a manual evaluation script when updating LLM guidance.
|
|
53
|
-
|
|
54
|
-
The main documentation pressure point remains lifecycle: examples should not
|
|
55
|
-
make it look acceptable to create long-lived `resource()` instances accidentally
|
|
56
|
-
at module scope or in route code that never cleans up.
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
# Shadow DOM vs Light DOM
|
|
2
|
-
|
|
3
|
-
Mado components use Shadow DOM by default. This is a good default for
|
|
4
|
-
self-contained widgets, but it is not the right default for every component in
|
|
5
|
-
an application.
|
|
6
|
-
|
|
7
|
-
## Rule of Thumb
|
|
8
|
-
|
|
9
|
-
Use Mado route layouts (`page({ view: ({ child }) => ... })`) for app zones:
|
|
10
|
-
auth shells, admin shells, public shells and embedded shells. These live in
|
|
11
|
-
`src/layouts/` and are composed from `src/app.routes.ts`.
|
|
12
|
-
|
|
13
|
-
Use Web Components registered with `component()` for reusable UI elements and
|
|
14
|
-
widgets. Use plain functions only for small inline template helpers:
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
const money = (value: number) => html`<span>${formatMoney(value)}</span>`;
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Do not hide app shells inside `main.ts` or generic helper functions. The app
|
|
21
|
-
map should show which layout wraps which route group.
|
|
22
|
-
|
|
23
|
-
Use **Shadow DOM** for leaf widgets:
|
|
24
|
-
|
|
25
|
-
- buttons, badges, cards, metrics;
|
|
26
|
-
- modals, toasts, small visual components;
|
|
27
|
-
- embed widgets that should not inherit app CSS accidentally;
|
|
28
|
-
- components whose styling should be owned by the component itself.
|
|
29
|
-
|
|
30
|
-
Use **Light DOM** for app structure that wants to share global CSS:
|
|
31
|
-
|
|
32
|
-
- route pages and route layouts;
|
|
33
|
-
- admin screens with dense table/form layouts;
|
|
34
|
-
- data-heavy screens with tables and forms;
|
|
35
|
-
- places where children should simply remain normal document DOM.
|
|
36
|
-
|
|
37
|
-
Route layouts receive `child` from Mado, so they do not need `<slot>`.
|
|
38
|
-
|
|
39
|
-
## The Footgun
|
|
40
|
-
|
|
41
|
-
Global CSS does not cross a Shadow DOM boundary.
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
// global.ts
|
|
45
|
-
export const globalStyles = css`
|
|
46
|
-
.page-head { display: flex; justify-content: space-between; }
|
|
47
|
-
.metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); }
|
|
48
|
-
`;
|
|
49
|
-
|
|
50
|
-
// ❌ .page-head and .metric-grid will not apply inside x-dashboard shadowRoot
|
|
51
|
-
component("x-dashboard", () => () => html`
|
|
52
|
-
<header class="page-head">...</header>
|
|
53
|
-
<div class="metric-grid">...</div>
|
|
54
|
-
`);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Fix it by making the route/page component Light DOM:
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
component("x-dashboard", () => () => html`
|
|
61
|
-
<header class="page-head">...</header>
|
|
62
|
-
<div class="metric-grid">...</div>
|
|
63
|
-
`, {
|
|
64
|
-
shadow: false,
|
|
65
|
-
styles: css`
|
|
66
|
-
x-dashboard { display: block; }
|
|
67
|
-
x-dashboard .panel { padding: 1rem; }
|
|
68
|
-
`,
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Now global utilities and local scoped styles both work.
|
|
73
|
-
|
|
74
|
-
## How Styles Behave
|
|
75
|
-
|
|
76
|
-
- `styles: css\`\`` in Shadow DOM is adopted into the component shadowRoot.
|
|
77
|
-
- `styles: css\`\`` with `shadow: false` is scoped to the tag name and adopted
|
|
78
|
-
globally.
|
|
79
|
-
- CSS custom properties (`--accent`, `--bg`, etc.) cross Shadow DOM boundaries.
|
|
80
|
-
- Class selectors like `.btn`, `.form-grid`, `.page-head` do **not** cross
|
|
81
|
-
Shadow DOM boundaries.
|
|
82
|
-
- Slotted children keep their own document styles; the shadow component can only
|
|
83
|
-
target them through `::slotted(...)`.
|
|
84
|
-
- `<slot>` projects children only in Shadow DOM. In a `shadow: false` component
|
|
85
|
-
it is just a normal `<slot>` element and will not move children into that
|
|
86
|
-
place in your layout.
|
|
87
|
-
|
|
88
|
-
## Recommended App Shape
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
// app zone: route layout, styled by shared/styles/shell.css
|
|
92
|
-
export default page({
|
|
93
|
-
view: ({ child }) => html`<main class="app-main">${child}</main>`,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// route page: light DOM, styled by shared/styles/content.css
|
|
97
|
-
export default page({
|
|
98
|
-
view: () => html`<section><h1>Users</h1></section>`,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// leaf widgets: Shadow DOM default
|
|
102
|
-
component("x-status-badge", setup);
|
|
103
|
-
component("x-stat-card", setup);
|
|
104
|
-
component("x-toast-stack", setup);
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
This gives admin screens predictable CSS while preserving encapsulation for
|
|
108
|
-
reusable leaf widgets.
|
|
109
|
-
|
|
110
|
-
The import model is deliberately browser-native:
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
import "./components/app-layout.js";
|
|
114
|
-
|
|
115
|
-
render(html`<x-app-layout>${router.view}</x-app-layout>`, app);
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
The import registers the custom element with `customElements.define()`. The
|
|
119
|
-
template creates an `<x-app-layout>` element. The browser connects the two.
|
|
120
|
-
There is no React-style component value being passed around.
|
|
121
|
-
|
|
122
|
-
If you do need a reusable slot-based frame, keep it as a Shadow DOM component
|
|
123
|
-
and put frame styles in that component.
|
|
124
|
-
|
|
125
|
-
## Routing and Links
|
|
126
|
-
|
|
127
|
-
`data-link` works inside Shadow DOM. The router uses `event.composedPath()`, so
|
|
128
|
-
click interception and hover-prefetch can see links from open shadow roots.
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
component("x-card-link", () => () => html`
|
|
132
|
-
<a href="/app/accounts" data-link>Accounts</a>
|
|
133
|
-
`);
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
The link can be in Shadow DOM; navigation still stays SPA.
|
|
137
|
-
|
|
138
|
-
## Where To Import Components
|
|
139
|
-
|
|
140
|
-
Custom elements are global after registration, but registration is still an
|
|
141
|
-
explicit JavaScript import.
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
// main.ts: global app frame
|
|
145
|
-
import "./components/app-shell.js";
|
|
146
|
-
|
|
147
|
-
// pages/tickets.ts: page-owned feature component
|
|
148
|
-
import "../components/ticket-list.js";
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
The browser does **not** download `ticket-list.js` just because it sees
|
|
152
|
-
`<ticket-list>`. The file must be imported somewhere first. Once imported, it
|
|
153
|
-
calls `customElements.define(...)`, and the tag becomes known in the current
|
|
154
|
-
document.
|
|
155
|
-
|
|
156
|
-
Do not bulk-import every component in `main.ts` "just in case". It works for
|
|
157
|
-
tiny demos, but it hides ownership and defeats lazy route loading. Prefer:
|
|
158
|
-
|
|
159
|
-
- global app shell/providers in `main.ts`;
|
|
160
|
-
- page-owned components in that page file;
|
|
161
|
-
- feature-owned shared components in the feature entry page;
|
|
162
|
-
- truly global leaf components in `main.ts` only when they are used everywhere.
|
|
163
|
-
|
|
164
|
-
## Starter Lesson
|
|
165
|
-
|
|
166
|
-
The default starter uses this split deliberately:
|
|
167
|
-
|
|
168
|
-
- route layouts are `page()` files under `src/layouts/`;
|
|
169
|
-
- `shell.css` owns app-zone chrome;
|
|
170
|
-
- `content.css` owns page-level tables/forms/prose;
|
|
171
|
-
- leaf components such as `x-button`, `x-spinner` and badges keep Shadow DOM.
|
|
172
|
-
|
|
173
|
-
If a page suddenly looks unstyled, check whether it uses global classes inside a
|
|
174
|
-
Shadow DOM component. That is usually the issue.
|
package/docs/en/11-layouts.md
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Layouts
|
|
2
|
-
|
|
3
|
-
> **One blessed path.** Layouts in Mado are route groups with a shared
|
|
4
|
-
> shell. There is exactly one canonical place to declare a layout — your
|
|
5
|
-
> `app.routes.ts` manifest. Putting layout code anywhere else (in `main.ts`, in a
|
|
6
|
-
> page view, in a global custom-element wrapper) is a bug pattern: the LLM and
|
|
7
|
-
> the human both produce visually broken UI when they guess differently.
|
|
8
|
-
|
|
9
|
-
## The canonical recipe
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
// src/app.routes.ts
|
|
13
|
-
import { layout, routes } from "@madojs/mado";
|
|
14
|
-
import { requireAuth } from "./modules/auth/auth.public.js";
|
|
15
|
-
import { authRoutes } from "./modules/auth/auth.routes.js";
|
|
16
|
-
import { billingRoutes } from "./modules/billing/billing.routes.js";
|
|
17
|
-
|
|
18
|
-
export const manifest = {
|
|
19
|
-
"/": () => import("./modules/home/home.page.js"),
|
|
20
|
-
"/login": layout({
|
|
21
|
-
layout: () => import("./layouts/auth-shell.layout.js"),
|
|
22
|
-
routes: authRoutes,
|
|
23
|
-
}),
|
|
24
|
-
"/billing": layout({
|
|
25
|
-
layout: () => import("./layouts/app-shell.layout.js"),
|
|
26
|
-
guard: requireAuth,
|
|
27
|
-
routes: billingRoutes,
|
|
28
|
-
}),
|
|
29
|
-
"*": () => import("./modules/home/not-found.page.js"),
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export default routes(manifest);
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
A layout is just a `page({ view })` that renders `${ctx.child}` somewhere:
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
// src/layouts/app.ts
|
|
39
|
-
import { html, page } from "@madojs/mado";
|
|
40
|
-
import "../components/app-shell.js"; // <x-app-shell> (sidebar + topbar + slot)
|
|
41
|
-
|
|
42
|
-
export default page({
|
|
43
|
-
view: ({ child }) => html`
|
|
44
|
-
<x-app-shell>${child}</x-app-shell>
|
|
45
|
-
`,
|
|
46
|
-
});
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
That is the whole API.
|
|
50
|
-
|
|
51
|
-
- **Order of layouts** matters: outer groups wrap inner groups. The order in
|
|
52
|
-
the manifest is exactly the order of rendering.
|
|
53
|
-
- **One shell per group**, not one shell per page. If you want a different
|
|
54
|
-
shell for a subtree, create a new group with its own `layout`.
|
|
55
|
-
- **Layouts can be lazy** (`() => import(...)`). They are loaded together
|
|
56
|
-
with the page.
|
|
57
|
-
|
|
58
|
-
## Why "one blessed path"
|
|
59
|
-
|
|
60
|
-
Without this convention, every page accumulates `<x-app-shell>${...}</x-app-shell>`
|
|
61
|
-
boilerplate, the LLM eventually puts the shell wrapper into `main.ts` "to make
|
|
62
|
-
it consistent", and the next refactor produces the classic
|
|
63
|
-
*"navigation appears below the page content"* screenshot. The layout-group
|
|
64
|
-
recipe makes the shell the **outer frame** structurally; there is no way to
|
|
65
|
-
re-order it by accident.
|
|
66
|
-
|
|
67
|
-
## Two acceptable alternatives (with caveats)
|
|
68
|
-
|
|
69
|
-
These exist for completeness. Reach for them only if you cannot use layout
|
|
70
|
-
groups.
|
|
71
|
-
|
|
72
|
-
### a) A single shell with the router slot in `main.ts`
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
import { html, render } from "@madojs/mado";
|
|
76
|
-
import "./components/app-shell.js";
|
|
77
|
-
import router from "./routes.js";
|
|
78
|
-
|
|
79
|
-
render(html`<x-app-shell>${router.view}</x-app-shell>`, app);
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Caveat: every route now lives inside one shell. You cannot have a centered
|
|
83
|
-
login page or a marketing landing page without the admin chrome around it.
|
|
84
|
-
Use this only for single-shell apps.
|
|
85
|
-
|
|
86
|
-
### b) Per-page wrapping inside `view`
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
export default page({
|
|
90
|
-
view: () => html`
|
|
91
|
-
<x-app-shell>
|
|
92
|
-
<h1>Orders</h1>
|
|
93
|
-
...
|
|
94
|
-
</x-app-shell>
|
|
95
|
-
`,
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Caveat: repetition. Every new page must remember the wrapper. The first time
|
|
100
|
-
someone forgets it, the layout disappears and the LLM "fixes" it in the wrong
|
|
101
|
-
place. **Do not start with this.**
|
|
102
|
-
|
|
103
|
-
## Where to find more
|
|
104
|
-
|
|
105
|
-
- `src/page.ts` defines `layout()`, `page()`, `Guard` and `LayoutRoutes`.
|
|
106
|
-
- `src/router/manifest.ts` flattens the layout manifest and applies guards
|
|
107
|
-
outer → inner before the page renders.
|
|
108
|
-
- The default starter (`mado init my-app`) ships with public, auth and
|
|
109
|
-
authenticated app zones and is the reference implementation.
|
|
110
|
-
|
|
111
|
-
If you ever feel tempted to invent a fourth pattern, write it down in your
|
|
112
|
-
project `docs/` first and discuss it with the team. The cost of inconsistency
|
|
113
|
-
in this exact spot is higher than the cost of a slightly awkward layout.
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# Auth and API
|
|
2
|
-
|
|
3
|
-
The default starter is the blessed recipe. It keeps HTTP mechanics in
|
|
4
|
-
`src/shared/http/` and auth state in `src/modules/auth/`.
|
|
5
|
-
|
|
6
|
-
## Shape
|
|
7
|
-
|
|
8
|
-
```txt
|
|
9
|
-
src/shared/http/
|
|
10
|
-
http-client.ts # one fetch wrapper + query/body/error handling
|
|
11
|
-
http-error.ts # one error shape
|
|
12
|
-
interceptors.ts # request/response hooks
|
|
13
|
-
|
|
14
|
-
src/modules/auth/
|
|
15
|
-
auth.connector.ts # /api/auth wire contract, DTO -> domain
|
|
16
|
-
auth.service.ts # token/user signals + login/logout/init
|
|
17
|
-
auth.guard.ts # requireAuth(), requirePermission()
|
|
18
|
-
auth.routes.ts # login route map
|
|
19
|
-
auth.public.ts # only public auth surface
|
|
20
|
-
_contracts/ # backend DTOs, private to connector
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Every business module follows the same flow:
|
|
24
|
-
|
|
25
|
-
```txt
|
|
26
|
-
connector -> resource/mutation -> page
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Pages do not import DTOs. Pages do not call `fetch()` directly. Connectors do
|
|
30
|
-
not import Mado reactivity or UI.
|
|
31
|
-
|
|
32
|
-
## HTTP Client
|
|
33
|
-
|
|
34
|
-
Use one small HTTP client for the app. It owns:
|
|
35
|
-
|
|
36
|
-
- base URL handling;
|
|
37
|
-
- JSON request/response defaults;
|
|
38
|
-
- query string serialization;
|
|
39
|
-
- `HttpError`;
|
|
40
|
-
- request/response interceptors.
|
|
41
|
-
|
|
42
|
-
Module connectors build on it:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { httpClient } from "../../shared/http/http-client";
|
|
46
|
-
import type { User } from "./auth.types";
|
|
47
|
-
import type { LoginResponseDTO } from "./_contracts/auth-api.types";
|
|
48
|
-
|
|
49
|
-
const toUser = (dto: LoginResponseDTO["user"]): User => ({
|
|
50
|
-
id: dto.id,
|
|
51
|
-
email: dto.email,
|
|
52
|
-
roles: dto.roles,
|
|
53
|
-
permissions: dto.permissions,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
export const authApi = {
|
|
57
|
-
me: async () => toUser(await httpClient.get<LoginResponseDTO["user"]>("/api/auth/me")),
|
|
58
|
-
};
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Auth Service
|
|
62
|
-
|
|
63
|
-
Auth state is an ES module singleton:
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
const _user = signal<User | null>(null);
|
|
67
|
-
const _token = signal<string | null>(null);
|
|
68
|
-
|
|
69
|
-
export const user = () => _user();
|
|
70
|
-
export const isAuthed = computed(() => _user() !== null);
|
|
71
|
-
|
|
72
|
-
export async function login(creds: Credentials): Promise<void> {
|
|
73
|
-
const res = await authApi.login(creds);
|
|
74
|
-
_token.set(res.token);
|
|
75
|
-
_user.set(res.user);
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Expose only what other modules need through `auth.public.ts`.
|
|
80
|
-
|
|
81
|
-
## Guards
|
|
82
|
-
|
|
83
|
-
Guards are plain functions:
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
export function requireAuth(): boolean | string {
|
|
87
|
-
if (isAuthed()) return true;
|
|
88
|
-
return "/login";
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Use them in `src/app.routes.ts`:
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
"/billing": layout({
|
|
96
|
-
layout: () => import("./layouts/app-shell.layout"),
|
|
97
|
-
guard: requireAuth,
|
|
98
|
-
routes: billingRoutes,
|
|
99
|
-
}),
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Dev Proxy
|
|
103
|
-
|
|
104
|
-
Configure proxying in `vite.config.ts`:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
import { defineConfig } from "vite";
|
|
108
|
-
import { mado } from "@madojs/mado/vite";
|
|
109
|
-
|
|
110
|
-
export default defineConfig({
|
|
111
|
-
plugins: [mado()],
|
|
112
|
-
server: {
|
|
113
|
-
proxy: { "/api": "http://localhost:3000" },
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Rule of Thumb
|
|
119
|
-
|
|
120
|
-
- `shared/http` knows HTTP.
|
|
121
|
-
- `*.connector.ts` knows one external system.
|
|
122
|
-
- `*.resource.ts` knows cache keys and invalidation.
|
|
123
|
-
- `*.page.ts` knows UI.
|
|
124
|
-
- `*.public.ts` is the only cross-module surface.
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Bake cookbook
|
|
2
|
-
|
|
3
|
-
`mado bake` renders selected routes into static HTML. It is for SEO and fast
|
|
4
|
-
first paint, not for server-side hydration.
|
|
5
|
-
|
|
6
|
-
## Minimal baked page
|
|
7
|
-
|
|
8
|
-
```ts
|
|
9
|
-
export default page({
|
|
10
|
-
head: () => ({ title: "Products", description: "Product catalog" }),
|
|
11
|
-
view: ({ data }) => html`
|
|
12
|
-
<main>
|
|
13
|
-
<h1>Products</h1>
|
|
14
|
-
${data.products.map((p) => html`<article><h2>${p.name}</h2></article>`)}
|
|
15
|
-
</main>
|
|
16
|
-
`,
|
|
17
|
-
bake: {
|
|
18
|
-
paths: () => [{}],
|
|
19
|
-
data: async () => ({ products: await api.products() }),
|
|
20
|
-
revalidate: 3600,
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
For baked pages, use plain arrays in `view()` when possible. Runtime-only
|
|
26
|
-
directives such as keyed `each()` are for the browser.
|
|
27
|
-
|
|
28
|
-
## Dynamic routes
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
export default page<{ slug: string }>({
|
|
32
|
-
head: ({ slug }, data) => ({ title: data.title, canonical: `/blog/${slug}` }),
|
|
33
|
-
view: ({ data }) => html`<article>${unsafeHTML(data.html)}</article>`,
|
|
34
|
-
bake: {
|
|
35
|
-
paths: async () => (await api.posts()).map((p) => ({ slug: p.slug })),
|
|
36
|
-
data: ({ slug }) => api.post(slug),
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
`unsafeHTML()` is allowed only for trusted or already-sanitized HTML.
|
|
42
|
-
|
|
43
|
-
## Route manifest
|
|
44
|
-
|
|
45
|
-
`mado bake` needs the source manifest:
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
export const manifest = {
|
|
49
|
-
"/": () => import("./pages/home.js"),
|
|
50
|
-
"/blog/:slug": () => import("./pages/blog-post.js"),
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export default routes(manifest);
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Output
|
|
57
|
-
|
|
58
|
-
By default standalone `mado bake` writes baked pages directly into `out/`.
|
|
59
|
-
`mado release` first lets Vite build the production shell and assets, then bake
|
|
60
|
-
replaces route HTML in place and writes `out/sitemap.xml`:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
mado release
|
|
64
|
-
tree out
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
The deployable folder is `out/`, not `dist/`.
|
|
68
|
-
|
|
69
|
-
Use `mado release --keep-bake-dir` only when you want an extra `out/baked/`
|
|
70
|
-
inspection copy during debugging.
|
|
71
|
-
|
|
72
|
-
## Client boot
|
|
73
|
-
|
|
74
|
-
Baked HTML marks `#app` with `data-mado-baked`. This is not hydration: when the
|
|
75
|
-
client app starts, `render()` replaces the baked DOM with live bindings. The
|
|
76
|
-
first response contains SEO/first-paint HTML, then the SPA takes over normally.
|
|
77
|
-
|
|
78
|
-
## Unsupported values
|
|
79
|
-
|
|
80
|
-
Bake intentionally fails loudly instead of writing `[object Object]`. If a baked
|
|
81
|
-
view throws an unsupported directive error:
|
|
82
|
-
|
|
83
|
-
- replace `each()` with `items.map(...)` in baked markup;
|
|
84
|
-
- keep interactive-only widgets behind client routes;
|
|
85
|
-
- make sure every value can be serialized to static HTML.
|
|
86
|
-
|
|
87
|
-
## Canonical links
|
|
88
|
-
|
|
89
|
-
Pass `--base-url` so generated canonical links and sitemap entries point to
|
|
90
|
-
production.
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
mado bake --base-url https://example.com
|
|
94
|
-
mado release --base-url https://example.com
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## When not to bake
|
|
98
|
-
|
|
99
|
-
Do not bake pages whose content is user-specific, permission-dependent, or
|
|
100
|
-
changes every few seconds. Use a normal SPA route with `resource()` instead.
|