tutuca 0.9.90 → 0.9.92
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/package.json +1 -1
- package/skill/tutuca/SKILL.md +5 -2
- package/skill/tutuca/advanced.md +6 -37
- package/skill/tutuca/cli.md +4 -26
- package/skill/tutuca/core.md +29 -5
- package/skill/tutuca/margaui.md +124 -0
- package/skill/tutuca/patterns/README.md +4 -0
- package/skill/tutuca/patterns/add-a-story.md +26 -0
- package/skill/tutuca/storybook.md +151 -0
package/package.json
CHANGED
package/skill/tutuca/SKILL.md
CHANGED
|
@@ -21,7 +21,8 @@ When authoring tutuca code, also load these if available:
|
|
|
21
21
|
values.
|
|
22
22
|
- **margaui** — the Tailwind v4 / daisyUI-compatible class library.
|
|
23
23
|
Reach for it when the project uses MargaUI / Tailwind class lists in
|
|
24
|
-
`class=` / `:class=`.
|
|
24
|
+
`class=` / `:class=`. See [margaui.md](./margaui.md) to set it up in a
|
|
25
|
+
project and wire it into tutuca.
|
|
25
26
|
|
|
26
27
|
## Routing
|
|
27
28
|
|
|
@@ -30,8 +31,10 @@ When authoring tutuca code, also load these if available:
|
|
|
30
31
|
| Authoring `component({...})`, `html`...`` views, macros, fields, events, lists, styles | [core.md](./core.md) |
|
|
31
32
|
| Designing components — responsibilities, state ownership, channel choice, do's & don'ts | [component-design.md](./component-design.md) |
|
|
32
33
|
| CLI commands, flags, exit codes, full linter rule list | [cli.md](./cli.md) |
|
|
34
|
+
| Authoring `*.dev.js` story modules, `getExamples()` sections, per-example request mocking, running `tutuca storybook` | [storybook.md](./storybook.md) |
|
|
33
35
|
| `bubble` / `send`-`receive` / async `request`-`response` channels, `$unknown`, request-handler registration | [request-response.md](./request-response.md) |
|
|
34
|
-
| Drag & drop, dynamic bindings (`*x`), pseudo-`x`, custom seq types
|
|
36
|
+
| Drag & drop, dynamic bindings (`*x`), pseudo-`x`, custom seq types | [advanced.md](./advanced.md) |
|
|
37
|
+
| Setting up MargaUI styling — install (CDN / npm / vendoring), theme CSS, and the `compileClassesToStyleText` wiring | [margaui.md](./margaui.md) |
|
|
35
38
|
| Runtime semantics — path steps, transaction lifecycle, dyn-var teleporting, async key pinning (`livePath`) | [semantics.md](./semantics.md) |
|
|
36
39
|
| Authoring tests — `getTests` shape, calling methods/input/receive/bubble/response/alter handlers, designing handlers for testability | [testing.md](./testing.md) |
|
|
37
40
|
| Task-oriented recipes — iteration, filtering, conditional content, conditional attributes, dynamic vars, composition, events | [patterns/README.md](./patterns/README.md) |
|
package/skill/tutuca/advanced.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Reach this file only when the task touches drag & drop, context-style
|
|
4
4
|
"dynamic bindings", pseudo-`x` (the `<x>`-stripping workaround inside
|
|
5
|
-
`<select>`/`<table>`/`<tr>`), registering a custom seq type
|
|
6
|
-
compiling Tailwind / MargaUI classes
|
|
7
|
-
is the right place.
|
|
5
|
+
`<select>`/`<table>`/`<tr>`), or registering a custom seq type. For
|
|
6
|
+
compiling Tailwind / MargaUI classes see [margaui.md](./margaui.md); for
|
|
7
|
+
everything else, `core.md` is the right place.
|
|
8
8
|
|
|
9
9
|
## Drag and Drop
|
|
10
10
|
|
|
@@ -190,37 +190,6 @@ whose entries resolve `@key` in event handlers.
|
|
|
190
190
|
|
|
191
191
|
## Tailwind / MargaUI Class Compilation (extra build)
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const app = tutuca("#app");
|
|
198
|
-
app.registerComponents([Comp]);
|
|
199
|
-
const css = await compileClassesToStyleText(app, compile);
|
|
200
|
-
injectCss("myapp", css);
|
|
201
|
-
app.start();
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
`compileClassesToStyleText` walks every registered component's templates,
|
|
205
|
-
collects the `class=` and `:class=` literals, hands them to a `compile`
|
|
206
|
-
function (any margaui-compatible signature), and returns CSS text. Pair
|
|
207
|
-
with `injectCss(scopeName, css)` to install the result before `start()`.
|
|
208
|
-
|
|
209
|
-
If a margaui skill is available, load it alongside this one when
|
|
210
|
-
authoring class lists — it lists the available components and their
|
|
211
|
-
canonical class strings, which is what the `compile` step expects.
|
|
212
|
-
|
|
213
|
-
**Pitfall: `@if.class` payloads are invisible to the scanner.** Classes
|
|
214
|
-
inside `@then` / `@else` (e.g. `@if.class=".active" @then="'btn-success'"
|
|
215
|
-
@else="'btn-ghost'"`) are not literals in `class=` / `:class=`, so
|
|
216
|
-
`compileClassesToStyleText` skips them and the margaui CSS for those
|
|
217
|
-
classes is never emitted — the conditional class renders unstyled.
|
|
218
|
-
Workaround: add a hidden "decoy" view on the component that lists every
|
|
219
|
-
conditional class as a real literal, so the walker picks them up:
|
|
220
|
-
|
|
221
|
-
```js
|
|
222
|
-
_margauiClasses: html`<p class="btn-success btn-ghost on off"></p>`,
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
The view does not need to be rendered anywhere — registration is enough
|
|
226
|
-
for the template walker to find it.
|
|
193
|
+
Moved to [margaui.md](./margaui.md) — installing margaui (CDN / npm /
|
|
194
|
+
vendoring), the theme CSS, the `compileClassesToStyleText` + `injectCss`
|
|
195
|
+
wiring, and the `@if.class` decoy-view pitfall.
|
package/skill/tutuca/cli.md
CHANGED
|
@@ -217,33 +217,11 @@ version-pinned CDN. All tutuca specifiers resolve to a single runtime, which
|
|
|
217
217
|
component scope/identity requires. `--out` always pins the CDN so the static
|
|
218
218
|
artifact is portable (host it from the project root so `/*.dev.js` paths resolve).
|
|
219
219
|
|
|
220
|
-
###
|
|
220
|
+
### Authoring `.dev.js` story modules
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
production or the UI**. The `.dev.js` suffix is the contract — your app imports
|
|
226
|
-
its real components directly and never a `.dev.js`, and a production build glob
|
|
227
|
-
can exclude `**/*.dev.js`. Because they follow the full module convention, the
|
|
228
|
-
same files are valid targets for `tutuca test`/`lint`/`render` too.
|
|
229
|
-
|
|
230
|
-
```js
|
|
231
|
-
// counter.dev.js — lives next to counter.js
|
|
232
|
-
import { component, html } from "tutuca";
|
|
233
|
-
import { Counter } from "./counter.js";
|
|
234
|
-
|
|
235
|
-
export function getComponents() {
|
|
236
|
-
return [Counter];
|
|
237
|
-
}
|
|
238
|
-
export function getExamples() {
|
|
239
|
-
return { title: "Counter", items: [{ title: "Basic", value: Counter.make({}) }] };
|
|
240
|
-
}
|
|
241
|
-
export function getTests({ describe, test, expect }) {
|
|
242
|
-
describe(Counter, () => {
|
|
243
|
-
test("starts at zero", () => expect(Counter.make({}).count).toBe(0));
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
```
|
|
222
|
+
The `*.dev.js` convention (a dev-only module holding `getComponents()` +
|
|
223
|
+
`getExamples()` + `getTests()`, never shipped), the example/section shape, and
|
|
224
|
+
per-example request mocking are covered in [storybook.md](./storybook.md).
|
|
247
225
|
|
|
248
226
|
## Install skill assets
|
|
249
227
|
|
package/skill/tutuca/core.md
CHANGED
|
@@ -11,8 +11,9 @@ the `tutuca` CLI.
|
|
|
11
11
|
> `request`/`response`, the `$unknown` fallback, and request-handler
|
|
12
12
|
> registration: see [request-response.md](./request-response.md).
|
|
13
13
|
> Advanced topics (drag & drop, dynamic bindings `*x`, pseudo-`x` for
|
|
14
|
-
> `<select>`/`<table>`/`<tr>`, custom seq types
|
|
15
|
-
>
|
|
14
|
+
> `<select>`/`<table>`/`<tr>`, custom seq types): see
|
|
15
|
+
> [advanced.md](./advanced.md). Setting up Tailwind/MargaUI styling: see
|
|
16
|
+
> [margaui.md](./margaui.md). CLI commands, flags,
|
|
16
17
|
> exit codes, and full linter rule list: see [cli.md](./cli.md).
|
|
17
18
|
> Authoring tests — `getTests` shape, calling methods/input/receive/
|
|
18
19
|
> bubble/response/alter handlers, designing for testability: see
|
|
@@ -827,6 +828,9 @@ component({
|
|
|
827
828
|
Tagged templates `html` and `css` are just `String.raw` (editor hinting
|
|
828
829
|
only). Plain strings work too.
|
|
829
830
|
|
|
831
|
+
For Tailwind / MargaUI utility classes (compiling `class=` literals into
|
|
832
|
+
CSS via the extra build), see [margaui.md](./margaui.md).
|
|
833
|
+
|
|
830
834
|
## Triggers and Handlers
|
|
831
835
|
|
|
832
836
|
Tutuca has four orchestration channels. Each one pairs a trigger with
|
|
@@ -1005,12 +1009,31 @@ export function getExamples() {
|
|
|
1005
1009
|
return {
|
|
1006
1010
|
title: "...",
|
|
1007
1011
|
description: "...",
|
|
1008
|
-
|
|
1012
|
+
// value = Comp.make(...); requestHandlers (optional) mocks this example's requests
|
|
1013
|
+
items: [{ title, description, value, view, requestHandlers }],
|
|
1009
1014
|
};
|
|
1010
1015
|
}
|
|
1011
1016
|
export function getTests({ describe, test, expect }) { /*...*/ } // optional — see cli.md
|
|
1012
1017
|
```
|
|
1013
1018
|
|
|
1019
|
+
An example item may carry an optional **`requestHandlers`** map — per-example
|
|
1020
|
+
mocks for the request handlers its component triggers, used by the storybook
|
|
1021
|
+
only. Each is a plain async function keyed by request name; it overrides the
|
|
1022
|
+
module's real `getRequestHandlers()` handler **for that example instance only**,
|
|
1023
|
+
so two examples of the same component can show different responses side by side.
|
|
1024
|
+
Return a fixture, `throw` to exercise the error path, or never resolve to hold a
|
|
1025
|
+
loading state:
|
|
1026
|
+
|
|
1027
|
+
```js
|
|
1028
|
+
items: [
|
|
1029
|
+
{ title: "Loaded", value: Widget.make(),
|
|
1030
|
+
requestHandlers: { async load() { return [{ id: 1, name: "Ada" }]; } } },
|
|
1031
|
+
{ title: "Error", value: Widget.make(),
|
|
1032
|
+
requestHandlers: { async load() { throw new Error("boom"); } } },
|
|
1033
|
+
{ title: "Default", value: Widget.make() }, // no mock → real handler / 404
|
|
1034
|
+
]
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1014
1037
|
Best practice: have `getComponents()` return **every** component the module
|
|
1015
1038
|
defines — child and helper components included — and give each one at least
|
|
1016
1039
|
one item in `getExamples()`. A component left out of `getComponents()` is
|
|
@@ -1037,8 +1060,9 @@ consumed by the shipped `tutuca/storybook` library (`mountStorybook`,
|
|
|
1037
1060
|
`request`-`response` channels, the `ctx.at` `PathBuilder`, `$unknown`, and
|
|
1038
1061
|
request-handler registration.
|
|
1039
1062
|
- [advanced.md](./advanced.md) — dynamic bindings (`*x`), pseudo-`@x` for
|
|
1040
|
-
`<select>` / `<table>` / `<tr>`, drag & drop, custom seq types
|
|
1041
|
-
|
|
1063
|
+
`<select>` / `<table>` / `<tr>`, drag & drop, custom seq types.
|
|
1064
|
+
- [margaui.md](./margaui.md) — setting up MargaUI styling: install
|
|
1065
|
+
(CDN / npm / vendoring), theme CSS, and `compileClassesToStyleText`.
|
|
1042
1066
|
- [semantics.md](./semantics.md) — runtime semantics: path steps, the
|
|
1043
1067
|
transaction lifecycle, dyn-var teleporting, and async key pinning
|
|
1044
1068
|
(`livePath`).
|
|
@@ -0,0 +1,124 @@
|
|
|
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, the `## Styles` section in [core.md](./core.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
|
+
## Wire it into tutuca
|
|
72
|
+
|
|
73
|
+
However you obtained `compile`, the integration is the same: register
|
|
74
|
+
your components, compile the classes their views reference, inject the
|
|
75
|
+
resulting CSS, then start.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
import { compileClassesToStyleText, injectCss, tutuca } from "tutuca/extra";
|
|
79
|
+
import { compile } from "margaui"; // or the CDN / vendored path
|
|
80
|
+
|
|
81
|
+
const app = tutuca("#app");
|
|
82
|
+
app.registerComponents([Comp]);
|
|
83
|
+
const css = await compileClassesToStyleText(app, compile);
|
|
84
|
+
injectCss("myapp", css);
|
|
85
|
+
app.start();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`compileClassesToStyleText` walks every registered component's templates,
|
|
89
|
+
collects the `class=` and `:class=` literals, hands them to a `compile`
|
|
90
|
+
function (any margaui-compatible signature), and returns CSS text. Pair
|
|
91
|
+
with `injectCss(scopeName, css)` to install the result before `start()`.
|
|
92
|
+
|
|
93
|
+
When authoring class lists, load the margaui skill alongside this one if
|
|
94
|
+
available (`npx tutuca install-skill --margaui-skill`) — it lists the
|
|
95
|
+
available components and their canonical class strings, which is what the
|
|
96
|
+
`compile` step expects.
|
|
97
|
+
|
|
98
|
+
## Pitfall: @if.class is invisible to the scanner
|
|
99
|
+
|
|
100
|
+
Classes inside `@then` / `@else` (e.g.
|
|
101
|
+
`@if.class=".active" @then="'btn-success'" @else="'btn-ghost'"`) are not
|
|
102
|
+
literals in `class=` / `:class=`, so `compileClassesToStyleText` skips
|
|
103
|
+
them and the margaui CSS for those classes is never emitted — the
|
|
104
|
+
conditional class renders unstyled. Workaround: add a hidden "decoy" view
|
|
105
|
+
on the component that lists every conditional class as a real literal, so
|
|
106
|
+
the walker picks them up:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
_margauiClasses: html`<p class="btn-success btn-ghost on off"></p>`,
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The view does not need to be rendered anywhere — registration is enough
|
|
113
|
+
for the template walker to find it. (This is the same rule
|
|
114
|
+
[component-design.md](./component-design.md) gives for runtime-assembled
|
|
115
|
+
margaui classes.)
|
|
116
|
+
|
|
117
|
+
## See also
|
|
118
|
+
|
|
119
|
+
- [core.md](./core.md) — `## Styles` for scoped/global component CSS.
|
|
120
|
+
- [advanced.md](./advanced.md) — dynamic bindings, drag & drop, custom
|
|
121
|
+
seq types, and other advanced view features.
|
|
122
|
+
- [cli.md](./cli.md) — `tutuca storybook` wires margaui by default
|
|
123
|
+
(`--no-margaui` to skip); `install-skill --margaui-skill` installs the
|
|
124
|
+
margaui skill.
|
|
@@ -41,3 +41,7 @@ task.
|
|
|
41
41
|
## Component communication
|
|
42
42
|
|
|
43
43
|
- [Coordinate components](coordinate-components.md) — `bubble`, `send`/`receive`, async `request`/`response`.
|
|
44
|
+
|
|
45
|
+
## Stories & catalog
|
|
46
|
+
|
|
47
|
+
- [Add a story for a component](add-a-story.md) — a `*.dev.js` with `getComponents()` + `getExamples()`, optional per-example request mocks.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Add a story for a component
|
|
2
|
+
|
|
3
|
+
**Problem:** show a component (and its states) in the storybook.
|
|
4
|
+
|
|
5
|
+
Create `foo.dev.js` next to `foo.js`:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { Foo } from "./foo.js";
|
|
9
|
+
|
|
10
|
+
export function getComponents() {
|
|
11
|
+
return [Foo];
|
|
12
|
+
}
|
|
13
|
+
export function getExamples() {
|
|
14
|
+
return { title: "Foo", items: [
|
|
15
|
+
{ title: "Empty", value: Foo.make({}) },
|
|
16
|
+
{ title: "Loaded", value: Foo.make({ isLoading: true }),
|
|
17
|
+
requestHandlers: { async load() { return [{ id: 1 }]; } } },
|
|
18
|
+
] };
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`value` must be a real `Foo.make(...)` instance, not a plain object. Add a
|
|
23
|
+
`requestHandlers` map to an item to mock that example's requests
|
|
24
|
+
(fixture / `throw` / never-resolve) — these are storybook-only. Run
|
|
25
|
+
`tutuca storybook` to view, or `--dry-run --json` to smoke-test. See
|
|
26
|
+
[storybook.md](../storybook.md).
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Storybook
|
|
2
|
+
|
|
3
|
+
Reach this file when authoring `*.dev.js` story modules or running
|
|
4
|
+
`tutuca storybook` — defining `getExamples()` sections, mocking requests per
|
|
5
|
+
example, or rendering a live component catalog. For the framework primer see
|
|
6
|
+
[core.md](./core.md); for the full CLI flag/exit-code table see
|
|
7
|
+
[cli.md](./cli.md); for the `getTests` shape see [testing.md](./testing.md).
|
|
8
|
+
|
|
9
|
+
## Mental model
|
|
10
|
+
|
|
11
|
+
`tutuca storybook [dir]` recursively discovers co-located `*.dev.js` modules,
|
|
12
|
+
mounts them via the shipped `tutuca/storybook` library, and serves an ephemeral
|
|
13
|
+
page — no config, no HTML to write. It is **batteries-included by default**:
|
|
14
|
+
before serving it runs each module's `getTests()` in the terminal, the page
|
|
15
|
+
wires margaui styling, and the browser runs `check(app)`. Each is individually
|
|
16
|
+
disablable with a `--no-*` flag. All tutuca specifiers resolve to **one
|
|
17
|
+
runtime** — component scope and identity require it.
|
|
18
|
+
|
|
19
|
+
## The `.dev.js` module
|
|
20
|
+
|
|
21
|
+
A `*.dev.js` file is a **dev-only module**: it holds stories, tests, and
|
|
22
|
+
development-time helpers for nearby components, and is **never shipped to
|
|
23
|
+
production or the UI**. The `.dev.js` suffix is the contract — your app imports
|
|
24
|
+
its real components directly and never a `.dev.js`, and a production build glob
|
|
25
|
+
can exclude `**/*.dev.js`. Because it follows the full module convention, the
|
|
26
|
+
same file is a valid target for `tutuca lint` / `test` / `render` too.
|
|
27
|
+
|
|
28
|
+
| Export | Returns | Used for |
|
|
29
|
+
| ------ | ------- | -------- |
|
|
30
|
+
| `getComponents()` | `[Comp, ...]` | stories — return **every** component the module defines, children and helpers included |
|
|
31
|
+
| `getExamples()` | one section, or an array of sections | the catalog cards |
|
|
32
|
+
| `getTests({ describe, test, expect })` | tests | the pre-serve test run (optional) |
|
|
33
|
+
| `getMacros()` | `{ name: macro }` | macros referenced in views (optional) |
|
|
34
|
+
| `getRequestHandlers()` | `{ name: async fn }` | the module's **real** request handlers (optional) |
|
|
35
|
+
| `getRoot()` | `Root.make({...})` | root state when examples need it (optional) |
|
|
36
|
+
|
|
37
|
+
## Authoring stories (`getExamples`)
|
|
38
|
+
|
|
39
|
+
Return one section, or an array of sections to group examples under multiple
|
|
40
|
+
headings. A section is `{ title, description?, items: [...] }`:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { component, html } from "tutuca";
|
|
44
|
+
import { Counter } from "./counter.js";
|
|
45
|
+
|
|
46
|
+
export function getComponents() {
|
|
47
|
+
return [Counter];
|
|
48
|
+
}
|
|
49
|
+
export function getExamples() {
|
|
50
|
+
return { // one section, or an array of these
|
|
51
|
+
title: "Counter",
|
|
52
|
+
description: "A button that counts clicks.", // optional
|
|
53
|
+
items: [
|
|
54
|
+
{ title: "Basic", description: "starts at zero", value: Counter.make({ count: 0 }) },
|
|
55
|
+
{ title: "Pre-filled", value: Counter.make({ count: 5 }) },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Item fields:
|
|
62
|
+
|
|
63
|
+
- `title` — required.
|
|
64
|
+
- `description?` — shown under the card title.
|
|
65
|
+
- `value` — required, the instance to render, usually `Comp.make({...})`.
|
|
66
|
+
- `view?` — selects a pushed named view, rendered via `@push-view` in the card.
|
|
67
|
+
- `requestHandlers?` — per-example request mocks (next section).
|
|
68
|
+
|
|
69
|
+
The storybook sorts sections by title and renders a sidebar with a filter, so
|
|
70
|
+
one example item per meaningful state reads as a state matrix.
|
|
71
|
+
|
|
72
|
+
## Mocking requests per example
|
|
73
|
+
|
|
74
|
+
An item's optional `requestHandlers` map holds async functions keyed by request
|
|
75
|
+
name that override the module's real `getRequestHandlers()` handler **for that
|
|
76
|
+
one example instance only** — so two examples of the same component show
|
|
77
|
+
different responses side by side. The three idioms:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
items: [
|
|
81
|
+
{ title: "Loaded", value: Widget.make({ isLoading: true }),
|
|
82
|
+
requestHandlers: { async load() { return [{ id: 1, name: "Ada" }]; } } }, // fixture
|
|
83
|
+
{ title: "Error", value: Widget.make({ isLoading: true }),
|
|
84
|
+
requestHandlers: { async load() { throw new Error("boom"); } } }, // error path
|
|
85
|
+
{ title: "Loading", value: Widget.make({ isLoading: true }),
|
|
86
|
+
requestHandlers: { load() { return new Promise(() => {}); } } }, // never resolves
|
|
87
|
+
{ title: "Default", value: Widget.make() }, // no mock → real handler / "Request not found"
|
|
88
|
+
]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
How it resolves: the storybook registers one meta-handler per request name. On
|
|
92
|
+
dispatch it walks the issuing component's path leaf→root to find the nearest
|
|
93
|
+
example carrying a mock for that name (**nearest example wins**), else falls
|
|
94
|
+
back to the module's real handler, else surfaces `Request not found: <name>`.
|
|
95
|
+
This is **storybook-only** — at runtime your real `getRequestHandlers()` apply.
|
|
96
|
+
See [request-response.md](./request-response.md) for the handler contract (the
|
|
97
|
+
`ctx` is the handler's final argument). `tutuca storybook --dry-run --json`
|
|
98
|
+
lists each example's mocked names.
|
|
99
|
+
|
|
100
|
+
## Stories as tests (`getTests`)
|
|
101
|
+
|
|
102
|
+
`getTests` runs through the same machinery as `tutuca test`; the storybook runs
|
|
103
|
+
it in the terminal before serving (skip with `--no-tests`). `describe(Comp, fn)`
|
|
104
|
+
auto-tags the suite by `Comp.name`:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
export function getTests({ describe, test, expect }) {
|
|
108
|
+
describe(Counter, () => {
|
|
109
|
+
test("starts at zero", () => expect(Counter.make({}).count).toBe(0));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
See [testing.md](./testing.md) for the full `getTests` shape and how to call
|
|
115
|
+
methods / input / receive / bubble / response / alter handlers.
|
|
116
|
+
|
|
117
|
+
## Running it
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
tutuca storybook # scan + serve the current directory
|
|
121
|
+
tutuca storybook ./packages/ui # scan + serve another directory
|
|
122
|
+
tutuca storybook --dry-run # prep + print what would be shown, don't serve (smoke test)
|
|
123
|
+
tutuca storybook --dry-run --json # same, machine-readable for agents
|
|
124
|
+
tutuca storybook --out ./_site # write a static index.html + bootstrap instead of serving
|
|
125
|
+
tutuca storybook --no-tests # skip the pre-serve getTests() run
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Runtime resolution (convention over configuration): a local
|
|
129
|
+
`node_modules/tutuca` install if present, else the CLI's own `dist`, else the
|
|
130
|
+
version-pinned CDN. `--out` always pins the CDN so the artifact is portable —
|
|
131
|
+
host it from the project root so `/*.dev.js` paths resolve. See [cli.md](./cli.md)
|
|
132
|
+
for the exhaustive flag list and exit codes.
|
|
133
|
+
|
|
134
|
+
## Footguns
|
|
135
|
+
|
|
136
|
+
- ⚠️ `value` must be a real instance (`Comp.make(...)`), not a plain object or
|
|
137
|
+
the class itself — examples need an addressable instance for event dispatch.
|
|
138
|
+
- ⚠️ `requestHandlers` mocks are **storybook-only** and per-instance; don't rely
|
|
139
|
+
on them in `getTests` or production code.
|
|
140
|
+
- ⚠️ Never import a `.dev.js` from app/production code — the suffix is the
|
|
141
|
+
ship / no-ship boundary.
|
|
142
|
+
- ⚠️ An example whose component triggers a request with no real handler and no
|
|
143
|
+
per-example mock surfaces `Request not found: <name>`.
|
|
144
|
+
- ⚠️ Keep one tutuca runtime — mixed specifiers or installs break scope identity.
|
|
145
|
+
|
|
146
|
+
## Verify
|
|
147
|
+
|
|
148
|
+
After editing a `*.dev.js`: `tutuca lint <module>.dev.js` →
|
|
149
|
+
`tutuca test <module>.dev.js` → `tutuca storybook --dry-run --json <dir>`
|
|
150
|
+
(smoke-test discovery, counts, and mocked names without serving), then
|
|
151
|
+
`tutuca storybook <dir>` to view it live.
|