tutuca 0.11.2 → 0.12.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.
Files changed (115) hide show
  1. package/dist/tutuca-cli.js +130 -104
  2. package/dist/tutuca-dev.ext.js +121 -90
  3. package/dist/tutuca-dev.js +121 -90
  4. package/dist/tutuca-dev.min.js +3 -3
  5. package/dist/tutuca-extra.ext.js +116 -59
  6. package/dist/tutuca-extra.js +116 -59
  7. package/dist/tutuca-extra.min.js +2 -2
  8. package/dist/tutuca-storybook.js +3 -3
  9. package/dist/tutuca.ext.js +116 -59
  10. package/dist/tutuca.js +116 -59
  11. package/dist/tutuca.min.js +2 -2
  12. package/package.json +1 -1
  13. package/skill/margaui/SKILL.md +105 -0
  14. package/skill/margaui/components/accordion.md +127 -0
  15. package/skill/margaui/components/alert.md +174 -0
  16. package/skill/margaui/components/aura.md +97 -0
  17. package/skill/margaui/components/avatar.md +220 -0
  18. package/skill/margaui/components/badge.md +193 -0
  19. package/skill/margaui/components/breadcrumbs.md +103 -0
  20. package/skill/margaui/components/button.md +322 -0
  21. package/skill/margaui/components/calendar.md +67 -0
  22. package/skill/margaui/components/card.md +373 -0
  23. package/skill/margaui/components/carousel.md +387 -0
  24. package/skill/margaui/components/chat.md +171 -0
  25. package/skill/margaui/components/checkbox.md +101 -0
  26. package/skill/margaui/components/collapse.md +172 -0
  27. package/skill/margaui/components/countdown.md +165 -0
  28. package/skill/margaui/components/diff.md +53 -0
  29. package/skill/margaui/components/divider.md +107 -0
  30. package/skill/margaui/components/dock.md +173 -0
  31. package/skill/margaui/components/drawer.md +184 -0
  32. package/skill/margaui/components/dropdown.md +388 -0
  33. package/skill/margaui/components/fab.md +346 -0
  34. package/skill/margaui/components/fieldset.md +88 -0
  35. package/skill/margaui/components/file-input.md +84 -0
  36. package/skill/margaui/components/filter.md +52 -0
  37. package/skill/margaui/components/footer.md +583 -0
  38. package/skill/margaui/components/hero.md +135 -0
  39. package/skill/margaui/components/hover-3d.md +129 -0
  40. package/skill/margaui/components/hover-gallery.md +49 -0
  41. package/skill/margaui/components/indicator.md +265 -0
  42. package/skill/margaui/components/input.md +389 -0
  43. package/skill/margaui/components/join.md +100 -0
  44. package/skill/margaui/components/kbd.md +127 -0
  45. package/skill/margaui/components/label.md +102 -0
  46. package/skill/margaui/components/link.md +96 -0
  47. package/skill/margaui/components/list.md +182 -0
  48. package/skill/margaui/components/loading.md +105 -0
  49. package/skill/margaui/components/mask.md +168 -0
  50. package/skill/margaui/components/megamenu.md +131 -0
  51. package/skill/margaui/components/menu.md +887 -0
  52. package/skill/margaui/components/mockup-browser.md +39 -0
  53. package/skill/margaui/components/mockup-code.md +81 -0
  54. package/skill/margaui/components/mockup-phone.md +39 -0
  55. package/skill/margaui/components/mockup-window.md +33 -0
  56. package/skill/margaui/components/modal.md +196 -0
  57. package/skill/margaui/components/navbar.md +282 -0
  58. package/skill/margaui/components/otp.md +171 -0
  59. package/skill/margaui/components/pagination.md +122 -0
  60. package/skill/margaui/components/progress.md +135 -0
  61. package/skill/margaui/components/radial-progress.md +67 -0
  62. package/skill/margaui/components/radio.md +133 -0
  63. package/skill/margaui/components/range.md +134 -0
  64. package/skill/margaui/components/rating.md +170 -0
  65. package/skill/margaui/components/select.md +225 -0
  66. package/skill/margaui/components/skeleton.md +64 -0
  67. package/skill/margaui/components/stack.md +142 -0
  68. package/skill/margaui/components/stat.md +254 -0
  69. package/skill/margaui/components/status.md +73 -0
  70. package/skill/margaui/components/steps.md +138 -0
  71. package/skill/margaui/components/swap.md +152 -0
  72. package/skill/margaui/components/tab.md +248 -0
  73. package/skill/margaui/components/table.md +1018 -0
  74. package/skill/margaui/components/text-rotate.md +91 -0
  75. package/skill/margaui/components/textarea.md +85 -0
  76. package/skill/margaui/components/theme-controller.md +266 -0
  77. package/skill/margaui/components/timeline.md +1356 -0
  78. package/skill/margaui/components/toast.md +165 -0
  79. package/skill/margaui/components/toggle.md +135 -0
  80. package/skill/margaui/components/tooltip.md +181 -0
  81. package/skill/margaui/components/validator.md +163 -0
  82. package/skill/tutuca/SKILL.md +56 -0
  83. package/skill/tutuca/advanced.md +212 -0
  84. package/skill/tutuca/cli.md +239 -0
  85. package/skill/tutuca/component-design.md +168 -0
  86. package/skill/tutuca/core.md +918 -0
  87. package/skill/tutuca/iteration.md +207 -0
  88. package/skill/tutuca/macros.md +86 -0
  89. package/skill/tutuca/margaui.md +175 -0
  90. package/skill/tutuca/messages-and-intents.md +399 -0
  91. package/skill/tutuca/patterns/README.md +48 -0
  92. package/skill/tutuca/patterns/add-a-story.md +26 -0
  93. package/skill/tutuca/patterns/bind-text-and-attributes.md +30 -0
  94. package/skill/tutuca/patterns/conditional-attribute-value.md +29 -0
  95. package/skill/tutuca/patterns/coordinate-components.md +54 -0
  96. package/skill/tutuca/patterns/edit-through-a-dynamic-target.md +27 -0
  97. package/skill/tutuca/patterns/enrich-each-item.md +25 -0
  98. package/skill/tutuca/patterns/file-input.md +39 -0
  99. package/skill/tutuca/patterns/filter-a-list.md +25 -0
  100. package/skill/tutuca/patterns/filter-and-paginate.md +60 -0
  101. package/skill/tutuca/patterns/handle-events.md +44 -0
  102. package/skill/tutuca/patterns/iterate-a-list.md +18 -0
  103. package/skill/tutuca/patterns/paginate-a-list.md +29 -0
  104. package/skill/tutuca/patterns/render-a-child-component.md +21 -0
  105. package/skill/tutuca/patterns/reuse-markup-with-macros.md +36 -0
  106. package/skill/tutuca/patterns/share-state-across-the-tree.md +38 -0
  107. package/skill/tutuca/patterns/show-or-hide-content.md +23 -0
  108. package/skill/tutuca/patterns/switch-between-views.md +30 -0
  109. package/skill/tutuca/patterns/tabbed-interface.md +43 -0
  110. package/skill/tutuca/semantics.md +195 -0
  111. package/skill/tutuca/storybook.md +270 -0
  112. package/skill/tutuca/styles.md +48 -0
  113. package/skill/tutuca/testing.md +345 -0
  114. package/skill/tutuca-source/SKILL.md +33 -0
  115. package/skill/tutuca-source/tutuca.ext.js +4301 -0
@@ -0,0 +1,207 @@
1
+ # Tutuca — List Iteration & Enrichment
2
+
3
+ Read this file when a view iterates a sequence (`@each`,
4
+ `render-each`), filters (`@when`), enriches items or scopes
5
+ (`@enrich-with`), or paginates (`@loop-with`).
6
+
7
+ ## List Iteration
8
+
9
+ `@each` accepts: `.field`, `*dynamic`.
10
+
11
+ ```html
12
+ <!-- iterate plain values -->
13
+ <li @each=".items"><span @text="@key"></span>: <x text="@value"></x></li>
14
+
15
+ <!-- filter -->
16
+ <li @each=".items" @when="filterItem">...</li>
17
+
18
+ <!-- per-item enrichment via the enrich handler (binds.X => @X in template) -->
19
+ <li @each=".items" @enrich-with="enrichItem">
20
+ <x text="@count"></x>
21
+ </li>
22
+
23
+ <!-- shared per-loop data + slicing (computed once before iteration) -->
24
+ <li @each=".items" @loop-with="getIterData" @when="filterItem">...</li>
25
+
26
+ <!-- render a list of components -->
27
+ <x render-each=".items"></x>
28
+ <x render-each=".items" as="edit"></x> <!-- specific view -->
29
+ <x render-each=".items" @when="filterItem"></x> <!-- with filter -->
30
+ <x render-each=".items" @loop-with="getIterData" @when="filterItem"></x>
31
+ <x render-each=".items" @show=".isOpen"></x> <!-- wrap in show -->
32
+ ```
33
+
34
+ Directives carry the `@` prefix everywhere — on `<li @each>` / `<div @each>`
35
+ host-element loops and on `<x render-each>` alike. Only `as=` is bare, because
36
+ it is an argument to the op rather than a directive. Both forms share the
37
+ handler-name resolution rules below.
38
+
39
+ `@enrich-with` is **not** supported on `<x render-each>`: the op renders
40
+ each item as a component in its own frame and drops child content, so
41
+ nothing is left to read the `@X` binds an enricher would set. Reach for a
42
+ host-element `@each` loop when you need enrichment.
43
+
44
+ ```js
45
+ alter: {
46
+ filterItem(_key, item, iterData) { return item.includes(iterData.q); },
47
+ enrichItem(binds, _key, item, iterData) { binds.count = item.length; },
48
+ // `@loop-with` is `(seq, ctx)` and returns { iterData?, start?, end?, keys? }.
49
+ getIterData(seq, ctx) {
50
+ const start = this.page * this.pageSize;
51
+ return { iterData: { q: this.query.toLowerCase() }, start, end: start + this.pageSize };
52
+ },
53
+ }
54
+ ```
55
+
56
+ ### `@loop-with` return shape — `iterData` + slicing
57
+
58
+ A `@loop-with` handler returns an object with up to four optional keys:
59
+
60
+ - **`iterData`** — the shared per-loop value handed to `@when` /
61
+ `@enrich-with`. Defaults to `{ seq }` when omitted. Inside the loop a
62
+ binding may read one **binding member** directly (`@value.title`) — if
63
+ an enrich handler only copies members of the loop value, the linter
64
+ hints to drop it and read the members instead.
65
+ - **`start`, `end`** — a positional slice of the iteration, with
66
+ `Array.prototype.slice` semantics: `end` is exclusive, negatives count
67
+ from the end (`end: -3` drops the last 3), `undefined` means the
68
+ natural bound. Use this to **paginate** — skip a prefix and/or suffix
69
+ without iterating or rendering it.
70
+ - **`keys`** — an explicit, ordered array of **original keys** to visit,
71
+ for **filter-then-paginate**. The handler filters/sorts/slices the full
72
+ sequence itself and returns the current page's slice of original keys;
73
+ the renderer visits exactly those (`seq.get(key)`), in order. Takes
74
+ precedence over `start`/`end` when both are present.
75
+
76
+ Slicing is positional but **preserves each item's original key**: a List
77
+ sliced to `start: 2` still binds `@key` to `2, 3, …`, so events, drag,
78
+ and two-way binding keep their identity. With `start`/`end`, `@when` then
79
+ filters *within* the window, so a page may yield fewer than `end - start`
80
+ items — to filter *before* paging (so the page count reflects the filtered
81
+ total), return `keys` instead. `keys` are original keys, so identity is
82
+ preserved there too: editing or deleting a row on page 2 of a filtered view
83
+ hits the right item. A `keys` return is **authoritative** — the renderer
84
+ visits exactly those keys and does **not** re-apply `@when` (the handler has
85
+ already decided what renders).
86
+
87
+ ### `@loop-with` handler context — `(seq, ctx)`
88
+
89
+ The handler's second argument is `ctx = { lookup, filter }` (an object so it
90
+ can grow):
91
+
92
+ - **`ctx.lookup(name)`** — reads a scope `@`-binding, e.g. one published by an
93
+ ancestor scope `@enrich-with`. Lets the handler **reuse a value the enrich
94
+ already computed** instead of recomputing it.
95
+ - **`ctx.filter(key, value, iterData)`** — wraps the declared `@when` predicate
96
+ (always callable; a no-op that returns `true` when there is no `@when`). Lets
97
+ the handler apply the *declared* filter while building its `keys` slice,
98
+ rather than re-implementing the match test.
99
+
100
+ ### Lifecycle of `@each`
101
+
102
+ For each render of an element with `@each=".items"`:
103
+
104
+ 1. **Resolve sequence** — evaluate `.items`. Native Arrays and Maps, plus any
105
+ class declaring a `SEQ_INFO` walker, are recognized.
106
+ 2. **`@loop-with`** (once per render) — `getIterData.call(this, seq, ctx)`
107
+ is called with the full sequence and the `{ lookup, filter }` context;
108
+ its `iterData` becomes the shared per-loop value and its `start`/`end`
109
+ slice the iteration. Skipped if no `@loop-with`; then `iterData` is
110
+ `{ seq }` and the whole sequence is iterated. If it returns `keys`,
111
+ those exact keys are visited in order (filter-then-paginate) and
112
+ `start`/`end` are ignored.
113
+ 3. For each `(key, value)` pair in the sliced sequence (or each `key` in
114
+ `keys`):
115
+ 1. **`@when`** — `filterItem.call(this, key, value, iterData)`; if it
116
+ returns `false`, the item is skipped. **Not applied** when the
117
+ handler returned `keys` (those are authoritative).
118
+ 2. **`@enrich-with`** — `enrichItem.call(this, binds, key, value, iterData)`.
119
+ `binds` is a **mutable object** seeded with `{ key, value }`;
120
+ mutating it (`binds.count = ...`) creates `@`-prefixed bindings
121
+ available in the templated children. The return value is ignored.
122
+ 3. **Render** the element with the new bindings on the stack.
123
+
124
+ Auto-bound names inside the loop are always `@key` and `@value` (or
125
+ whatever you wrote into `binds`).
126
+
127
+ ### Handler resolution
128
+
129
+ `@when` / `@enrich-with` / `@loop-with` resolve like event handler names:
130
+ bare `filterItem` → `alter.filterItem` (idiomatic); `$filterItem` →
131
+ method on `this` (works, not idiomatic — `alter` keeps iteration helpers
132
+ grouped).
133
+
134
+ ## Scope Enrichment
135
+
136
+ Without an `@each` on the same element, `@enrich-with` becomes a scope
137
+ enricher: it takes no `binds` arg, and its **return value** is the
138
+ bindings object whose keys become `@`-prefixed bindings for descendants.
139
+
140
+ ```js
141
+ alter: { enrichScope() { return { len: this.text.length }; } }
142
+ ```
143
+
144
+ ```html
145
+ <div @enrich-with="enrichScope">Length: <x text="@len"></x></div>
146
+ ```
147
+
148
+ ## Filter-then-paginate strategies
149
+
150
+ The recipe form is in
151
+ [patterns/filter-and-paginate.md](./patterns/filter-and-paginate.md).
152
+ There are three ways to wire it, trading simplicity for scans-per-render
153
+ (all return `keys`, so all keep identity):
154
+
155
+ **1. Naive — two independent scans.** The loop scans + slices the whole
156
+ list itself; a separate `@enrich-with` scans again for the pager labels.
157
+ Simplest, nothing shared:
158
+
159
+ ```js
160
+ naiveTablePage(seq, { filter }) { // builds the WHOLE matching list…
161
+ const all = [];
162
+ for (let i = 0; i < seq.size; i++) if (filter(i, seq.get(i))) all.push(i);
163
+ const start = clamp(this.page, all.length, this.pageSize).currentPage * this.pageSize;
164
+ return { keys: all.slice(start, start + this.pageSize) }; // …just to slice it
165
+ },
166
+ ```
167
+
168
+ **2. Shared — one count + one partial collect** (the recipe's default).
169
+ A scope `@enrich-with` on an ancestor does **one** counting scan and
170
+ publishes the clamped page + pager labels (which the page controls,
171
+ sitting outside the loop, read as `@`-bindings); the `@loop-with` handler
172
+ reads the clamped page via `ctx.lookup`, reuses the predicate via
173
+ `ctx.filter`, and collects only the current page's keys — early-exiting
174
+ once the page is full.
175
+
176
+ **3. Coupled — one scan.** The enrich does *everything*, including the
177
+ page keys, and stashes them in a binding only the loop reads. Fastest,
178
+ but the two handlers are welded together — name them so it shows:
179
+
180
+ ```js
181
+ enrichBuildsKeysForTheLoopBelow() { // the only scan: count + labels + keys
182
+ const all = []; /* …collect matching indices… */
183
+ const { pageCount, currentPage } = clamp(this.page, all.length, this.pageSize);
184
+ const start = currentPage * this.pageSize;
185
+ return { __keys__: all.slice(start, start + this.pageSize), /* …labels… */ };
186
+ },
187
+ loopJustForwardsTheEnrichsKeys(_seq, { lookup }) { // useless without the enrich
188
+ return { keys: lookup("__keys__") };
189
+ },
190
+ ```
191
+
192
+ Test any strategy with `collectIterBindings(Comp, instance, seq, opts)`,
193
+ which drives a loop exactly like the renderer — map `when` → `@when`,
194
+ `loopWith` → `@loop-with`, `scopeEnrich` → the ancestor scope
195
+ `@enrich-with` the loop reads via `ctx.lookup`. Mechanics and the
196
+ dev-build caveat in [testing.md](./testing.md).
197
+
198
+ ## See also
199
+
200
+ - [patterns/iterate-a-list.md](./patterns/iterate-a-list.md),
201
+ [patterns/filter-a-list.md](./patterns/filter-a-list.md),
202
+ [patterns/paginate-a-list.md](./patterns/paginate-a-list.md),
203
+ [patterns/enrich-each-item.md](./patterns/enrich-each-item.md) — minimal
204
+ recipes for each half.
205
+ - [core.md](./core.md) — the component primer, notation, and the
206
+ frame/scope stack model these directives build on.
207
+ - [advanced.md](./advanced.md) — custom seq types (`SEQ_INFO`).
@@ -0,0 +1,86 @@
1
+ # Tutuca — Macros
2
+
3
+ Macros are pure template expansion — no state, no methods. Calls inside
4
+ a macro resolve against the *host* component. Read this file when
5
+ authoring `macro({...}, html)` definitions, `<x:name>` calls, or slots.
6
+
7
+ ```js
8
+ import { macro, html } from "tutuca";
9
+
10
+ const badge = macro(
11
+ { label: "'New'", kind: "'info'" }, // defaults are *expressions*
12
+ html`<span :class="$'badge badge-{^kind}'" @text="^label"></span>`,
13
+ );
14
+
15
+ export function getMacros() {
16
+ return { badge };
17
+ }
18
+ ```
19
+
20
+ ```html
21
+ <x:badge></x:badge> <!-- defaults -->
22
+ <x:badge label="Sale"></x:badge> <!-- static string (no quotes needed) -->
23
+ <x:badge :label="'Sale'"></x:badge> <!-- dynamic literal -->
24
+ <x:badge :label=".status"></x:badge> <!-- field reference -->
25
+ ```
26
+
27
+ Inside the macro body, `^param` reads a parameter. Static attributes
28
+ (`label="Sale"`) pass the raw string; dynamic attributes (`:label=…`)
29
+ take the same value forms as any binding — see *Quoting & String
30
+ Literals* in [core.md](./core.md) for the literal-vs-template rules.
31
+
32
+ Register macros at the same scope as components:
33
+
34
+ ```js
35
+ const scope = app.registerComponents([Comp]);
36
+ scope.registerMacros(getMacros());
37
+ ```
38
+
39
+ Registry keys are lowercased on insert because the HTML parser already
40
+ lowercases `<x:Tag>` to `<x:tag>`. `{ Card }` and `{ card }` both register
41
+ under `card`; registering two *different* macros under the same lowercased
42
+ name warns via `console.assert`.
43
+
44
+ ## Slots
45
+
46
+ ```js
47
+ const card = macro(
48
+ { title: "'Card'" },
49
+ html`<div class="card">
50
+ <h2 @text="^title"></h2>
51
+ <x:slot></x:slot>
52
+ </div>`,
53
+ );
54
+ ```
55
+
56
+ ```html
57
+ <x:card title="Hi"><p>body</p></x:card> <!-- default slot -->
58
+ ```
59
+
60
+ ## Named Slots
61
+
62
+ ```js
63
+ const panel = macro(
64
+ {},
65
+ html`<div>
66
+ <header><x:slot name="actions"></x:slot></header>
67
+ <main><x:slot></x:slot></main> <!-- default == name="_" -->
68
+ <footer><x:slot name="footer"></x:slot></footer>
69
+ </div>`,
70
+ );
71
+ ```
72
+
73
+ ```html
74
+ <x:panel>
75
+ <x slot="actions"><button @on.click="inc">+</button></x>
76
+ <p>default slot content</p>
77
+ <x slot="footer">© 2026</x>
78
+ </x:panel>
79
+ ```
80
+
81
+ ## See also
82
+
83
+ - [patterns/reuse-markup-with-macros.md](./patterns/reuse-markup-with-macros.md) —
84
+ the minimal recipe form of the badge example.
85
+ - [core.md](./core.md) — notation, quoting rules, and the component
86
+ primer the macro body plugs into.
@@ -0,0 +1,175 @@
1
+ # Tutuca — MargaUI Styling
2
+
3
+ Reach this file to add **MargaUI** (the Tailwind v4 / daisyUI-compatible
4
+ class library) styling to a tutuca app: get margaui into the project,
5
+ link its theme, and let tutuca's extra build compile the utility classes
6
+ it finds in your views into CSS. If you only need scoped/global component
7
+ CSS, [styles.md](./styles.md) is enough.
8
+
9
+ ## Get margaui
10
+
11
+ margaui ships two pieces: a `compile` function (class names → CSS text)
12
+ and a `theme.css` stylesheet. Pick one of three ways to obtain them.
13
+
14
+ ### CDN (no install)
15
+
16
+ Nothing to install — import from jsDelivr and link the theme. tutuca's
17
+ extra build is on the CDN too:
18
+
19
+ ```html
20
+ <link
21
+ rel="stylesheet"
22
+ href="https://marianoguerra.github.io/margaui/themes/theme.css"
23
+ />
24
+ <script type="module">
25
+ import { compile } from "https://cdn.jsdelivr.net/npm/margaui/+esm";
26
+ import {
27
+ compileClassesToStyleText,
28
+ injectCss,
29
+ tutuca,
30
+ } from "https://cdn.jsdelivr.net/npm/tutuca/dist/tutuca-extra.js/+esm";
31
+ // …wire it up (see below)
32
+ </script>
33
+ ```
34
+
35
+ See `docs/examples/getting-started-margaui.html` for a complete runnable page.
36
+
37
+ ### npm
38
+
39
+ Install both as dev dependencies and import via bare specifiers:
40
+
41
+ ```sh
42
+ npm i --save-dev tutuca margaui
43
+ ```
44
+
45
+ ```js
46
+ import { compileClassesToStyleText, injectCss, tutuca } from "tutuca/extra";
47
+ import { compile } from "margaui";
48
+ ```
49
+
50
+ Serve or copy the theme from `node_modules/margaui` into your build, or
51
+ keep linking the GitHub Pages `theme.css` shown above.
52
+
53
+ ### Vendoring
54
+
55
+ Copy a prebuilt `margaui.min.js` and a `theme.css` into the project and
56
+ import from the local path — useful for offline builds or pinning an
57
+ exact version (this repo vendors `docs/deps/margaui.min.js` for exactly
58
+ that reason):
59
+
60
+ ```html
61
+ <link rel="stylesheet" href="./vendor/theme.css" />
62
+ <script type="module">
63
+ import { compile } from "./vendor/margaui.min.js";
64
+ // …
65
+ </script>
66
+ ```
67
+
68
+ Trade-off: no runtime network dependency and a frozen version, at the
69
+ cost of updating the vendored files by hand.
70
+
71
+ ## Dark mode and the other palettes
72
+
73
+ A margaui theme is a block of CSS custom properties (`--color-*`, `--radius-*`,
74
+ …) under a `[data-theme="<name>"]` selector, and every class it compiles reads
75
+ those through `var(--color-*)`. So switching theme is **one attribute flip on
76
+ `<html>`** — the compiled stylesheet never changes:
77
+
78
+ ```js
79
+ document.documentElement.dataset.theme = "dark";
80
+ ```
81
+
82
+ Two things to know, both of which bite if you assume otherwise:
83
+
84
+ - **Dark mode never turns itself on.** `dark.css` is keyed on
85
+ `[data-theme="dark"]` alone — there is no `prefers-color-scheme` fallback and
86
+ no `.dark` class. Link `theme.css` and do nothing else and the page is light
87
+ forever, on every machine. Following the OS is your job:
88
+
89
+ ```js
90
+ const dark = matchMedia("(prefers-color-scheme: dark)");
91
+ document.documentElement.dataset.theme = dark.matches ? "dark" : "light";
92
+ ```
93
+
94
+ - **`theme.css` is only light + dark.** It is literally
95
+ `@import"./light.css";@import"./dark.css";`. margaui ships ~33 more palettes
96
+ (dracula, nord, cyberpunk, …) as sibling files, each linked separately and
97
+ each cheap:
98
+
99
+ ```html
100
+ <link rel="stylesheet" href="https://marianoguerra.github.io/margaui/themes/dracula.css" />
101
+ ```
102
+
103
+ Link it **after** `theme.css`: `light.css` claims plain `:root` as well as
104
+ `[data-theme=light]`, which ties on specificity with `[data-theme=dracula]`,
105
+ so a palette only wins by coming later in the cascade.
106
+
107
+ `tutuca storybook` does all of this for you — see the Themes section of
108
+ [storybook.md](./storybook.md).
109
+
110
+ ## Wire it into tutuca
111
+
112
+ However you obtained `compile`, the integration is the same: register
113
+ your components, compile the classes their views reference, inject the
114
+ resulting CSS, then start.
115
+
116
+ ```js
117
+ import { compileClassesToStyleText, injectCss, tutuca } from "tutuca/extra";
118
+ import { compile } from "margaui"; // or the CDN / vendored path
119
+
120
+ const app = tutuca("#app");
121
+ app.registerComponents([Comp]);
122
+ const css = await compileClassesToStyleText(app, compile);
123
+ injectCss("myapp", css);
124
+ app.start();
125
+ ```
126
+
127
+ `compileClassesToStyleText` walks every registered component's templates,
128
+ collects the `class=` and `:class=` literals, hands them to a `compile`
129
+ function (any margaui-compatible signature), and returns CSS text. Pair
130
+ with `injectCss(scopeName, css)` to install the result before `start()`.
131
+
132
+ When authoring class lists, load the margaui skill alongside this one if
133
+ available (`npx tutuca install-skill --margaui-skill`) — it lists the
134
+ available components and their canonical class strings, which is what the
135
+ `compile` step expects.
136
+
137
+ ## Pitfall: assembled class names are invisible to the scanner
138
+
139
+ The scanner only reads **constant** class literals out of parsed templates. It
140
+ cannot see a class name that is assembled rather than written out verbatim, so
141
+ the margaui CSS for that class is never emitted and it renders unstyled. Two
142
+ cases:
143
+
144
+ - **Interpolated templates** — `:class="$'bg-{.color}'"` contributes only the
145
+ constant prefix `bg-`, never `bg-red` / `bg-blue`. Same for any `${…}` segment.
146
+ - **Classes built in a method** — anything a method returns (e.g. a `headerClass()`
147
+ that builds `` `progress-${this.color}` ``) is never scanned at all; the walker
148
+ only reads view templates, not JS bodies.
149
+
150
+ (Literal `@then` / `@else` strings on `@if.class` — e.g.
151
+ `@if.class=".active" @then="'btn-success'" @else="'btn-ghost'"` — **are** now
152
+ collected, so those don't need the workaround.)
153
+
154
+ Workaround: add a hidden "decoy"/palette view on the component that lists every
155
+ possible assembled class as a real literal, so the walker picks them up:
156
+
157
+ ```js
158
+ // enumerate color × utility so each full class name appears verbatim
159
+ _margauiClasses: html`<p class="bg-red bg-blue progress-red progress-blue"></p>`,
160
+ ```
161
+
162
+ The view does not need to be rendered anywhere — registration is enough for the
163
+ template walker to find it. (This is the same rule
164
+ [component-design.md](./component-design.md) gives for runtime-assembled margaui
165
+ classes.) The cost is that the palette and the methods can drift apart with no
166
+ check catching it; keep them adjacent and update both together.
167
+
168
+ ## See also
169
+
170
+ - [styles.md](./styles.md) — scoped/global component CSS.
171
+ - [advanced.md](./advanced.md) — dynamic bindings, drag & drop, custom
172
+ seq types, and other advanced view features.
173
+ - [cli.md](./cli.md) — `tutuca storybook` wires margaui by default
174
+ (`--no-margaui` to skip); `install-skill --margaui-skill` installs the
175
+ margaui skill.