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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tutuca",
3
- "version": "0.9.90",
3
+ "version": "0.9.92",
4
4
  "type": "module",
5
5
  "description": "Zero-dependency SPA framework with immutable state and virtual DOM",
6
6
  "main": "./dist/tutuca.js",
@@ -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, Tailwind/MargaUI | [advanced.md](./advanced.md) |
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) |
@@ -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, or
6
- compiling Tailwind / MargaUI classes. For everything else, `core.md`
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
- ```js
194
- import { compileClassesToStyleText, injectCss, tutuca } from "tutuca/extra";
195
- import { compile } from "https://cdn.jsdelivr.net/npm/margaui/+esm";
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.
@@ -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
- ### The `.dev.js` convention
220
+ ### Authoring `.dev.js` story modules
221
221
 
222
- A `*.dev.js` file is a **dev-only module**: it holds stories
223
- (`getComponents()` + `getExamples()`), tests (`getTests()`), and any other
224
- development-time helpers for nearby components, and is **never shipped to
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
 
@@ -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, Tailwind/MargaUI
15
- > compilation): see [advanced.md](./advanced.md). CLI commands, flags,
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
- items: [{ title, description, value, view }], // value = Comp.make(...)
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, Tailwind /
1041
- MargaUI compilation.
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.