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,212 @@
1
+ # Tutuca — Advanced Topics
2
+
3
+ Reach this file only when the task touches drag & drop, context-style
4
+ "dynamic bindings", pseudo-`x` (the `<x>`-stripping workaround inside
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
+
9
+ ## Drag and Drop
10
+
11
+ ```html
12
+ <div
13
+ @each=".items"
14
+ draggable="true"
15
+ data-dragtype="my-item"
16
+ data-droptarget="my-item"
17
+ @on.drop="onDrop @key dragInfo event"
18
+ ></div>
19
+ ```
20
+
21
+ ```js
22
+ receive: {
23
+ onDrop(draft, targetKey, dragInfo, e) {
24
+ const sourceKey = dragInfo.lookupBind("key"); // any bind from source render
25
+ const [item] = draft.items.splice(sourceKey, 1);
26
+ draft.items.splice(targetKey, 0, item);
27
+ },
28
+ }
29
+ ```
30
+
31
+ Tutuca auto-manages two attrs during a drag — style them with CSS:
32
+
33
+ ```css
34
+ [data-dragging="1"] {
35
+ opacity: 0.5;
36
+ }
37
+ [data-draggingover="my-item"] {
38
+ outline: 1px dashed;
39
+ }
40
+ ```
41
+
42
+ Touch is wired up too (drag fires after a small move threshold).
43
+
44
+ ## Dynamic Bindings
45
+
46
+ For passing values "context-style" to a deep descendant without threading
47
+ them through every component in between. **`provide`** on the producer;
48
+ **`lookup`** on consumers; resolve as `*name`.
49
+
50
+ > **Best practice:** keep state local to the component and reach for
51
+ > `provide` / `lookup` only when it is genuinely the only solution. Dynamic
52
+ > bindings couple a consumer to a producer that may not be in scope — prefer
53
+ > keeping components as self-contained as possible: let a child render the
54
+ > field it needs from its owner, and lift state only as far up the tree as it
55
+ > actually needs to live.
56
+
57
+ ```js
58
+ const Theme = component({
59
+ name: "Theme",
60
+ fields: { color: "blue" },
61
+ provide: { color: ".color" },
62
+ });
63
+ const Child = component({
64
+ lookup: { color: { for: "Theme.color", default: "'gray'" } },
65
+ view: html`<p :style="$'color: {*color}'"></p>`,
66
+ });
67
+ ```
68
+
69
+ A **`provide`** maps an exported name to a field expression. Every
70
+ provide is evaluated and pushed onto the dynamic stack automatically
71
+ when the producer is entered during render — there is no hook to opt in.
72
+
73
+ A **`lookup`** reads a value the `*name` way: the key is the name used
74
+ in views (`*color`), the value is `"Producer.provideName"`, or
75
+ `{ for: "Producer.provideName", default: ".field" }` to supply a
76
+ fallback when no producer is in scope (default is optional — without it
77
+ a miss resolves to `null`). A `*name` that names the component's own
78
+ `provide` resolves to the nearest provided value (including its own).
79
+
80
+ ### Dynamic vars as render targets
81
+
82
+ A `*name` dynamic var resolves to a value, so it works anywhere a value
83
+ is read — not just in `:style` / `:class`. In particular it can be a
84
+ component-render target and an iteration source:
85
+
86
+ ```html
87
+ <x render="*selected"></x> <!-- render the dynamic's component -->
88
+ <x render="*selected" as="edit"></x> <!-- a specific view of it -->
89
+ <div @each="*items"><x render-it></x></div> <!-- iterate a dynamic seq -->
90
+ ```
91
+
92
+ A `provide` value must be **addressable** — a `.field` or a `.seq[.key]`
93
+ seq-access, nothing else. (It is both read as `*name` *and* used as a
94
+ render-target / teleport path, so a method or constant — which has no
95
+ path — is a lint error.) A `lookup` `default`, by contrast, is only a
96
+ value fallback and accepts the full grammar, including constants like
97
+ `'gray'`. A `provide` can be a sequence/map item access:
98
+
99
+ ```js
100
+ const Root = component({
101
+ name: "Root",
102
+ fields: { items: new Map(), selectedKey: "" },
103
+ provide: {
104
+ items: ".items", // the whole sequence
105
+ selected: ".items[.selectedKey]", // seq-access to one entry
106
+ },
107
+ });
108
+ ```
109
+
110
+ There is **no `*name[.key]` form** — a consumer never indexes a dynamic
111
+ var. The seq-access lives in the producer's `provide` declaration; the
112
+ consumer just reads the resolved value as `*name`.
113
+
114
+ **Teleporting.** The component rendered via `<x render="*selected">`
115
+ physically lives at the producer (e.g. `Root.items`), not under the
116
+ consumer. When an event fires inside that dynamically-rendered subtree,
117
+ the runtime expands the *render* path (consumer → … → the rendered
118
+ node) to reconstruct the handler, but the *transaction* is teleported:
119
+ the mutation skips the intermediate components and lands on the
120
+ producer's data. Editing the entry in the consumer and the same entry
121
+ in the producer's own view update in lock-step.
122
+
123
+ ## Pseudo-`x` (`@x`)
124
+
125
+ Tutuca's special operations (`render`, `render-it`, `render-each`, `text`,
126
+ `show`, `hide`, `slot`) live on the `<x>` tag. That works almost
127
+ everywhere, but the browser's HTML parser refuses to keep `<x>` (or any
128
+ unknown tag) as a child of certain elements. Drop `<x render-each>`
129
+ inside one of those and the parser silently strips it.
130
+
131
+ The parser strips `<x>` only inside the **table family** and **`<select>`**.
132
+ Use pseudo-`@x` when the parent is one of:
133
+
134
+ `table`, `thead`, `tbody`, `tfoot`, `tr`, `colgroup`, `select`, `optgroup`.
135
+
136
+ Everywhere else `<x>` is kept and needs no workaround — including `ul`, `ol`,
137
+ `li`, `dl`, `dt`, `dd`, `details`, `summary`, `caption`, `td`, `th`. So
138
+ `<ul><x render-each=".items">…</x></ul>` is fine. (When in doubt, the rule of
139
+ thumb is: any element whose HTML content model only permits *specific* child
140
+ tags — table sections and `<select>` — strips `<x>`.)
141
+
142
+ The escape hatch: prefix the **first** attribute on a *legal* tag with
143
+ `@x`. Tutuca treats that tag as if it were `<x>` and reads the next
144
+ attribute as the special op.
145
+
146
+ ```html
147
+ <!-- ❌ <x> stripped by the HTML parser inside <select> -->
148
+ <select>
149
+ <x render-each=".items" as="option"></x>
150
+ </select>
151
+
152
+ <!-- ✅ pseudo-x: <option @x render-each=".items" as="option"> -->
153
+ <select>
154
+ <option @x render-each=".items" as="option"></option>
155
+ </select>
156
+ ```
157
+
158
+ Notes:
159
+
160
+ - `@x` must be the **first** attribute; the special op (`render-each`,
161
+ `render`, `text`, `show`, ...) is the second.
162
+ - The host tag (here `<option>`) is otherwise ignored — only the special
163
+ op runs. Tutuca produces the rendered children directly.
164
+ - Same trick works inside any of the stripping parents listed above
165
+ (`<table>`/`<tr>`/`<colgroup>`/`<select>`/…).
166
+
167
+ ## Registering a custom seq type
168
+
169
+ To make `@each` recognize your own collection class, install a
170
+ `SEQ_INFO` walker on its prototype:
171
+
172
+ ```js
173
+ import { SEQ_INFO } from "tutuca";
174
+ import { immerable } from "tutuca/immer";
175
+
176
+ class MyClass {
177
+ static [immerable] = true;
178
+ // ...
179
+ }
180
+ MyClass.prototype[SEQ_INFO] = (seq, visit, start, end) => {
181
+ for (const [k, v] of seq.entries()) visit(k, v, "sk");
182
+ };
183
+ ```
184
+
185
+ `SEQ_INFO` is `Symbol.for("tutuca.seqInfo")`, so the same identity
186
+ is shared across module graphs (source vs. bundled tutuca). The
187
+ renderer reads `seq[SEQ_INFO]` directly (no `.constructor` lookup),
188
+ which is why the walker goes on the prototype, not as a static.
189
+ The `immerable` marker lets handlers mutate instances of the custom class
190
+ through a draft while preserving its prototype.
191
+ If rendered entries can receive updates, also implement `get(key, fallback)`
192
+ and a `set(key, value)` method that mutates the drafted instance. Tutuca uses
193
+ those methods to look up and graft a finalized child through a keyed path.
194
+
195
+ The third arg to `visit` must be `"sk"` (keyed entries) or `"si"`
196
+ (positional indexes): it is the meta key event-path reconstruction
197
+ reads to resolve an event back to its entry — any other value means
198
+ `@key` and keyed steps silently stop resolving inside `@each`.
199
+
200
+ `start`/`end` are an optional `[start, end)` slice produced by
201
+ `@loop-with` (Array.slice semantics). Walkers may ignore them, in
202
+ which case `@loop-with` ranges simply don't apply to that type.
203
+
204
+ See `docs/examples/custom-collection.js` for a complete worked
205
+ example: an Immer-draftable keyed list whose walker supports slicing and
206
+ whose entries resolve `@key` in event handlers.
207
+
208
+ ## Tailwind / MargaUI Class Compilation (extra build)
209
+
210
+ Moved to [margaui.md](./margaui.md) — installing margaui (CDN / npm /
211
+ vendoring), the theme CSS, the `compileClassesToStyleText` + `injectCss`
212
+ wiring, and the assembled-class-names decoy-view pitfall.
@@ -0,0 +1,239 @@
1
+ # Tutuca — CLI Reference
2
+
3
+ The `tutuca` CLI inspects, documents, lints, tests, and renders any
4
+ module that follows the
5
+ [Conventional Module Exports](./core.md#conventional-module-exports)
6
+ shape. Reach this file when you need command/flag/exit-code
7
+ details, or when reading a lint code out of `lint` output. The
8
+ post-edit verification recipe is in
9
+ [Verifying changes](./core.md#verifying-changes).
10
+
11
+ ## Install / invoke
12
+
13
+ ```sh
14
+ # project local
15
+ npm i --save-dev tutuca
16
+ npx tutuca <command> <module-path> [name] [flags]
17
+
18
+ # global
19
+ npm i -g tutuca
20
+ tutuca <command> <module-path> [name] [flags]
21
+
22
+ # from a checkout of this repo
23
+ node tools/tutuca.js <command> <module-path> [name] [flags]
24
+ ```
25
+
26
+ The command comes **first**, the module path second, an optional component
27
+ name third. `tutuca help` prints the full reference; `tutuca help <command>`
28
+ prints a one-liner. `tutuca` ↔ `tutuca -h` prints overview.
29
+ Use `--module=<path>` if the path conflicts with positional parsing.
30
+
31
+ ## Commands
32
+
33
+ | Command | Purpose |
34
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
35
+ | `get <module>` | Summarize which `getX()` exports are present and counts |
36
+ | `list <module> [name] [--limit n]` | List components with their views and fields (name + type). `--limit n` caps; `0` = all |
37
+ | `examples <module> [--limit n]` | Print `getExamples()` content (title, items, per section). `--limit n` caps total items; `0` = all |
38
+ | `show <module> [name]` | Show API docs (methods, receive/intent handlers, and fields) — all or one |
39
+ | `lint <module> [name]` | Run the linter; exits **2** on any error-level finding |
40
+ | `render <module> [name]` | Render examples to HTML in a headless DOM. Filter by component name or `--title`/`--view`. Exits **3** on render crash |
41
+ | `test <module> [name]` | Run tests defined by `getTests({ describe, test, expect })`. Filter by component name, `--grep <pattern>`, or `--bail`. Exits **4** on any failure |
42
+ | `storybook [dir]` | Serve a live storybook for the project, auto-discovering co-located `*.dev.js` modules. Flags: `--port`, `--out`, `--dry-run` (prep + print, don't serve), `--no-margaui`, `--no-check`, `--no-tests`. No module path needed |
43
+ | `help [cmd]` | Show usage. No module path needed |
44
+ | `feedback [message]` | Append a feedback note (positional or stdin) to `~/.tutuca/feedback.jsonl`. No module path needed |
45
+ | `install-skill [name]` | Copy a bundled skill (`tutuca`, `margaui`, or `--all`) into `.claude/skills/`. No module path needed |
46
+ | `agent-context` | Print a versioned JSON schema of every command, flag, exit code, and error code. No module path needed |
47
+
48
+ ## Global flags
49
+
50
+ ```
51
+ --json shorthand for `--format=json`. Recommended
52
+ for agent/script callers — error envelopes
53
+ are also JSON on stderr (see "Errors" below)
54
+ -f, --format <cli|md|json|html> output format
55
+ defaults: get/list/examples/lint → cli
56
+ show/render → md
57
+ html only valid for render
58
+ json works for every command
59
+ -o, --output <file> write to file instead of stdout
60
+ --pretty pretty-print HTML (md/html) via prettier;
61
+ JSON formatter uses indent 2
62
+ --module <path> alternative to second-positional module path
63
+ -h, --help show help (overview, or for one command)
64
+ ```
65
+
66
+ ## Exit codes
67
+
68
+ | Code | Meaning |
69
+ | ---- | -------------------------------------------------------- |
70
+ | `0` | success |
71
+ | `1` | usage error (bad args, missing module, bad module shape) |
72
+ | `2` | lint findings at error level |
73
+ | `3` | render crash |
74
+ | `4` | one or more tests failed |
75
+
76
+ ## Errors
77
+
78
+ Diagnostics go to **stderr**; structured output goes to **stdout**. Errors
79
+ include "did you mean" suggestions for unknown commands and unknown flags
80
+ (same shape as lint suggestions).
81
+
82
+ Under `--json`, errors are emitted as a single-line JSON envelope on stderr:
83
+
84
+ ```json
85
+ {"error":{"code":"ERR_USAGE_UNKNOWN_FLAG","message":"Unknown flag '--titel'","suggestion":{"kind":"replace-name","from":"--titel","to":"--title"},"hint":"Valid flags: ..."}}
86
+ ```
87
+
88
+ Stable error codes:
89
+
90
+ | Code | When |
91
+ | ----------------------------- | --------------------------------------------- |
92
+ | `ERR_USAGE_UNKNOWN_COMMAND` | command name not recognized |
93
+ | `ERR_USAGE_UNKNOWN_FLAG` | flag not recognized for the command |
94
+ | `ERR_USAGE_BAD_FLAG_VALUE` | flag rejected the value (e.g. wrong type) |
95
+ | `ERR_USAGE_MISSING_MODULE` | command needs a module path but none was given |
96
+ | `ERR_USAGE_MISSING_ARGUMENT` | required positional/stdin missing |
97
+ | `ERR_USAGE_MUTUALLY_EXCLUSIVE`| conflicting flags |
98
+ | `ERR_FORMAT_UNKNOWN` | `--format` value not in {cli,md,json,html} |
99
+ | `ERR_FORMAT_UNSUPPORTED` | format chosen doesn't support the result kind |
100
+ | `EXAMPLES_SHAPE_MISMATCH` | module returned a non-conforming shape |
101
+ | `ERR_SKILL_ASSETS_MISSING` | bundled skill assets not found |
102
+ | `ERR_SKILL_TARGET_EXISTS` | install-skill target exists; use `--force` |
103
+
104
+ ## Examples
105
+
106
+ ```sh
107
+ tutuca get ./src/components.js # quick overview
108
+ tutuca list ./src/components.js # components, views, fields
109
+ tutuca show ./src/components.js Button --json # one component, JSON
110
+ tutuca render ./src/components.js -f html --pretty -o out/examples.html
111
+ tutuca render ./src/components.js Button --title "Disabled state"
112
+
113
+ # Post-edit verification: lint, then render the example for the feature
114
+ # you just changed (add the example first if none covers it). Add
115
+ # --pretty when you need to read the HTML to verify structure.
116
+ tutuca lint ./src/components.js
117
+ tutuca render ./src/components.js --title "Disabled state"
118
+ tutuca render ./src/components.js --title "Disabled state" --pretty
119
+
120
+ # Component-behavior verification: run the suite for one component, or
121
+ # narrow further with --grep. Add tests next to the component (the
122
+ # getTests() export) when the change isn't observable from render alone.
123
+ tutuca test ./src/components.js Counter
124
+ tutuca test ./src/components.js Counter --grep "inc()"
125
+ tutuca test ./src/components.js --bail
126
+ ```
127
+
128
+ ## `test` — running component tests
129
+
130
+ Use `test` after edits that change attributes, instance methods, receive
131
+ handlers, or static factories — anything observable from JS rather than
132
+ from rendered HTML. The module opts in by exporting
133
+ `getTests({ describe, test, expect })`:
134
+
135
+ - `describe(Component, fn)` tags the suite with `Component.name` so
136
+ the positional `[name]` filter can pick it.
137
+ - `describe(title, fn)` is untagged; reachable only via `--grep`.
138
+ - `describe(title, { component }, fn)` tags an explicit title with a
139
+ custom component name.
140
+ - `test(title, fn)` — `fn` may be async; assertions use the injected
141
+ chai `expect`.
142
+
143
+ Filters:
144
+
145
+ - `[name]` — only tests whose tagged `componentName` equals `<name>`.
146
+ - `--grep <p>` — substring match against the full path
147
+ (e.g. `"Counter > inc() > works on a negative counter"`).
148
+ - `--bail` — stop on first failure; remaining tests reported as `skip`.
149
+
150
+ Default format is `cli` (a tree with ✓/✗/○ and per-test durations);
151
+ `-f md` and `-f json` work too.
152
+
153
+ The `getTests` shape and the calling conventions (`inst.computedMethod()`,
154
+ `Comp.receive.x.call(inst, draft, …)`, the `drive` cascade helper, iteration
155
+ handlers) are in [testing.md](./testing.md).
156
+
157
+ ## storybook — live component catalog
158
+
159
+ `tutuca storybook [dir]` serves a browser storybook with no setup — it
160
+ discovers co-located `*.dev.js` modules, runs their `getTests()`, wires
161
+ margaui, and serves an ephemeral page. Its flags (`--port`, `--out`,
162
+ `--dry-run`, `--no-margaui`, `--no-check`, `--no-tests`) are in the
163
+ Commands table above. Authoring `.dev.js` modules, the example/section shape,
164
+ per-example request mocking, and runtime resolution are all in
165
+ [storybook.md](./storybook.md).
166
+
167
+ ## Install skill assets
168
+
169
+ `tutuca install-skill` copies bundled Claude Code skill files into
170
+ `.claude/skills/<name>/` so a session in this directory picks them up.
171
+
172
+ ```sh
173
+ tutuca install-skill # tutuca skill (default), into ./.claude/skills/tutuca/
174
+ tutuca install-skill --user # ~/.claude/skills/tutuca/
175
+ tutuca install-skill --margaui-skill # margaui skill instead of tutuca
176
+ tutuca install-skill --all # all bundled skills (tutuca + margaui)
177
+ tutuca install-skill --dot-agents # install into ./.agents/skills/ instead of ./.claude/skills/
178
+ tutuca install-skill --all --force # overwrite existing files
179
+ tutuca install-skill --dry-run # print files that would be written, don't touch disk
180
+ ```
181
+
182
+ `--user`/`--project` choose scope (default `--project`).
183
+ `--margaui-skill` and `--all` are mutually exclusive.
184
+ `--dot-agents` swaps the `.claude` base for `.agents` (combines with any scope/selection).
185
+
186
+ ## Record feedback
187
+
188
+ `tutuca feedback` appends a freeform feedback record to
189
+ `~/.tutuca/feedback.jsonl` (created on first use). Record a note
190
+ whenever the CLI, the bundled skills, this reference, or the library
191
+ itself was confusing, broken, or surprising — capture it in the
192
+ moment instead of reconstructing it later.
193
+
194
+ ```sh
195
+ tutuca feedback "lint code FIELD_VAL_NOT_DEFINED didn't suggest the missing field"
196
+ echo "render --pretty produced different output than -f html --pretty" | tutuca feedback
197
+ tutuca feedback < notes.txt
198
+ ```
199
+
200
+ Each record is one JSON object per line: `{ts, version, message}`.
201
+ No module path is required. Empty input (no positional, no piped
202
+ stdin) exits **1** with a usage error.
203
+
204
+ ## Linter Rules
205
+
206
+ `lint` reports findings at three levels — **error**, **warn**, **hint** —
207
+ and exits `2` if any finding is at error level.
208
+
209
+ The codes are not duplicated here, to keep this file from drifting out of
210
+ sync with the implementation. The authoritative, always-current list of
211
+ component-linter codes (code, level, one-line description, grouped by
212
+ category) is available straight from the CLI:
213
+
214
+ - `tutuca help lint` — human-readable table.
215
+ - `tutuca agent-context` — machine-readable: the `lintCodes` array, each
216
+ entry `{ code, level, group, summary }`.
217
+
218
+ Categories include field/method references, receive handlers, iteration
219
+ helpers (`alter`), dynamic bindings (`*name`),
220
+ template/event issues, value-expression errors, and unregistered names.
221
+ Representative codes: `FIELD_VAL_NOT_DEFINED`, `METHOD_VAL_IS_FIELD`,
222
+ `ALT_HANDLER_NOT_DEFINED`, `DYN_VAL_NOT_DEFINED`, `UNKNOWN_DIRECTIVE`,
223
+ `UNSUPPORTED_EXPR_SYNTAX`.
224
+
225
+ `lint` also runs an HTML structural linter (fragment mode) that emits
226
+ `HTML_*` codes for malformed or misnested template markup; those are
227
+ reported through the same channel. Its messages use WHATWG parser
228
+ vocabulary:
229
+
230
+ - **foster-parenting** — the parser moves content that isn't allowed
231
+ inside a table out in front of it.
232
+ - **adoption agency** — the algorithm that reorders misnested formatting
233
+ tags (`<b><i></b></i>`).
234
+ - **void element** — an element with no close tag (`<br>`, `<img>`); an
235
+ explicit `</br>` is flagged.
236
+ - **insertion mode** — the parser context named in "not allowed in …"
237
+ messages (e.g. "in table body").
238
+ - **bogus comment** — malformed markup the parser reinterprets as a
239
+ comment, dropping its content.
@@ -0,0 +1,168 @@
1
+ # Tutuca — Component Design
2
+
3
+ How to *shape* a feature into one or more tutuca components — responsibilities,
4
+ where state lives, which channel to reach for — before you reach for syntax. The
5
+ mechanics live elsewhere: component skeleton, fields, directives, and the
6
+ post-edit verification recipe in [core.md](./core.md); the orchestration channels
7
+ in [messages-and-intents.md](./messages-and-intents.md); dynamic bindings (`provide` /
8
+ `lookup` / `*x`) in [advanced.md](./advanced.md); task recipes in
9
+ [patterns/README.md](./patterns/README.md). This file is a router with judgment
10
+ attached — every rule points at its canonical home rather than restating it.
11
+ Read it when deciding how to split a feature into components, where a piece of
12
+ state should live, how two components should talk, or when reviewing a component
13
+ for design smells.
14
+
15
+ ## Decide in this order
16
+
17
+ Walk these top-down whenever you add or reshape a component:
18
+
19
+ 1. **What single responsibility is this?** If the answer has an "and" in it, split
20
+ it into separate components.
21
+ 2. **Who owns each piece of state?** The component that reads and mutates a value
22
+ owns it. Put the field there; let others render or message it.
23
+ 3. **How do these components talk?** Pick the narrowest channel that reaches the
24
+ owner — see the ladder below.
25
+ 4. **Where does the outside world cross the boundary?** Outbound I/O goes through
26
+ `ctx.intent(..., { route: ["lex"] })`; inbound external events go through
27
+ `app.sendAtRoot` to the root. Keep the logic inside tutuca on both sides.
28
+
29
+ ## Communication decision ladder
30
+
31
+ This ladder is about *acting across* a component boundary — reaching up,
32
+ messaging a target, doing async work, or mutating state someone else owns. It is
33
+ **not** about merely *reading* a child's state: an ancestor already holds its
34
+ children as frozen fields and can read them directly in any handler or method
35
+ (`this.items[i].done`) — no channel needed. See [core.md](./core.md) "The
36
+ value tree".
37
+
38
+ Reach for the *narrowest* channel that does the job, and only move further down
39
+ the ladder when the one above can't express it:
40
+
41
+ - **The component owns the state needed to respond** → mutate its handler
42
+ **draft**, optionally via a helper method passed that draft. See
43
+ [core.md](./core.md) Methods.
44
+ - **You know exactly which component must be told** → **`ctx.send` / `receive`**,
45
+ addressing a specific path with `ctx.at` (defaults to self). See
46
+ [messages-and-intents.md](./messages-and-intents.md) "When to send".
47
+ - **You do not know who should answer** → **`ctx.intent`**, and the *route* says
48
+ where to look: `["dyn"]` up the ancestors (an ancestor owning aggregate state —
49
+ a log, a selection, a total), `["lex"]` the registered scope handlers (async or
50
+ host-side work — fetch, timer, IndexedDB, an SDK), or the default `["dyn","lex"]`
51
+ for both. The answer comes back as `<name>Ok` / `<name>Error` /
52
+ `<name>Unhandled`. See [messages-and-intents.md](./messages-and-intents.md).
53
+ - **An external event pushes *into* the app** (WebSocket, `postMessage`, …) →
54
+ **`app.sendAtRoot`**, which lands the inbound event on the root. See
55
+ [messages-and-intents.md](./messages-and-intents.md) "Integrating with the outside world".
56
+ - **A deep descendant needs a value owned far away** and nothing in between should
57
+ know about it → **`provide` / `lookup` (`*name`)** across the tree — the last
58
+ resort. See [advanced.md](./advanced.md).
59
+
60
+ Note what the ladder does **not** ask: *who answers*. That is the route's job,
61
+ written at the call site, so a name can move from being answered locally to being
62
+ answered by an ancestor without the view or the sender changing.
63
+
64
+ A compact worked version of the first three (`method`, `send`/`receive`, and
65
+ `intent` on each leg) lives in
66
+ [patterns/coordinate-components.md](./patterns/coordinate-components.md).
67
+
68
+ ## Do's & Don'ts
69
+
70
+ - **Do create single-purpose components. Don't pack multiple responsibilities
71
+ into one.** A component that draws its own view from its own fields is the unit
72
+ of reuse and the unit of testing. → [patterns/render-a-child-component.md](./patterns/render-a-child-component.md)
73
+
74
+ - **Don't add a `kind` / `type` field and branch the view on it. Do make one
75
+ component per kind and render it with `<x render=".item">`.** Conditional-on-kind
76
+ views grow into tangled `@if` chains; a component per kind keeps each view flat
77
+ and each concern isolated. This is also why pathing into nested data is barred —
78
+ model the nested thing as a component instead. → [core.md](./core.md) "Common
79
+ pitfalls" (Paths are not allowed in values) and
80
+ [patterns/render-a-child-component.md](./patterns/render-a-child-component.md)
81
+
82
+ - **Do keep state in the component that owns and uses it, and lift it only as far
83
+ up the tree as it needs to live. Don't thread a value through every component in
84
+ between** (the React "prop drilling" reflex) — let a child render the field it
85
+ needs from its owner. → [patterns/share-state-across-the-tree.md](./patterns/share-state-across-the-tree.md)
86
+
87
+ - **Do read a child's state directly when an ancestor needs it for an aggregate
88
+ decision.** A parent holds its children as frozen fields, so a handler or
89
+ method can read `this.items[i].done` straight off — children don't have to
90
+ raise an intent just to be *read*. **Don't reach for a channel to read
91
+ downward**; `intent` / `send` are for asking someone to act, not for inspecting
92
+ state you already own. (And don't reach in to mutate a child around the model —
93
+ that still goes through the owner's draft or `ctx.send`.)
94
+ → [core.md](./core.md) "The value tree" and
95
+ [messages-and-intents.md](./messages-and-intents.md) "When to send"
96
+
97
+ - **Do reach for `provide` / `lookup` (`*name`) last** — only when a deep
98
+ descendant needs a value owned far away and nothing in between should know about
99
+ it. Dynamic bindings couple a consumer to a producer that may not be in scope.
100
+ → [advanced.md](./advanced.md)
101
+
102
+ - **Do pick the channel by whether you know the answerer (the ladder above).
103
+ Don't raise an intent nothing consumes, and don't `send` to self when a plain
104
+ method call would do.** A message is *addressed* and stops at one component; an
105
+ intent is *routed* and walks until something answers.
106
+ → [messages-and-intents.md](./messages-and-intents.md) "The two channels"
107
+
108
+ - **Do keep logic inside the tutuca app when integrating with the outside world.**
109
+ Route outbound work through `ctx.intent(..., { route: ["lex"] })` and inbound external
110
+ events through `app.sendAtRoot` to the root (which forwards deeper with `ctx.at`),
111
+ so handlers stay the single owner of state changes. **Don't mutate `app.state`
112
+ directly or `addEventListener` outside the model** — state changed that way
113
+ bypasses the draft-first transaction discipline and is invisible to the
114
+ component that owns it. → [messages-and-intents.md](./messages-and-intents.md)
115
+ "Integrating with the outside world" (and its ⚠️ note)
116
+
117
+ - **Do handle every DOM event with tutuca's built-in `@on.` handlers — including
118
+ custom events fired by web components.** `@on.click`, `@on.input`,
119
+ `@on.<custom-event>` (the event `detail` surfaces as `value`) keep the event
120
+ inside the model, so it flows through a draft-first handler. **Don't
121
+ reach in from the outside with `addEventListener`** — a listener attached out of
122
+ band mutates state the owning component can't see and bypasses the transactor.
123
+ → [core.md](./core.md) "Event Handling" and "Web Components & Custom Events"
124
+
125
+ - **Do use inline predicates and small explicit handlers.** A single field plus
126
+ `equals? .activeSection 'todo'` / `empty?` and a handler that assigns
127
+ `draft.activeSection` often is the whole state machine. → [core.md](./core.md)
128
+ "Methods as Predicates & Computed Values" and "Field Types"
129
+
130
+ - **Do remember a rendered child gets a clean namespace.** Parent `@` bindings
131
+ (`@each`, `@enrich-with`) don't cross a `<x render>` boundary — pass a value
132
+ across it with `*name`, not by assuming the binding leaks in. → [advanced.md](./advanced.md)
133
+
134
+ - **Do add a decoy view when a margaui class is assembled at runtime.** The margaui
135
+ compiler only scans constant class literals — a class built by interpolation or
136
+ in a method emits no CSS and renders unstyled. → the workaround in
137
+ [margaui.md](./margaui.md) "Pitfall: assembled class names are invisible to the
138
+ scanner", and the worked decoy view in `personal-site.js`.
139
+
140
+ - **Do close the loop after every change** with `tutuca lint <module>` → `test` →
141
+ `render`. → [core.md](./core.md) "Verifying changes"
142
+
143
+ ## Smells & refactors
144
+
145
+ - **`is*Selected()` methods → predicate + one explicit handler.** Replace
146
+ `$isTodoSelected` with `equals? .activeSection 'todo'`; let the click call a
147
+ small handler that assigns `draft.activeSection`.
148
+ - **A view that `@if`-branches on a `kind` field → one component per kind**, each
149
+ rendered with `<x render>`.
150
+ - **A value passed down through three components that don't use it → move the
151
+ state up to the nearest common owner** and let the leaf render it directly; only
152
+ if nothing in between should know it, use `provide` / `lookup`.
153
+ - **Host code poking `app.state` or attaching a listener → an `app.sendAtRoot`
154
+ handler on the root**, with the mutation expressed on its draft.
155
+
156
+ ## See also
157
+
158
+ - [core.md](./core.md) — component skeleton, fields, directives, predicates, the
159
+ verification recipe, and the "Common pitfalls" list.
160
+ - [messages-and-intents.md](./messages-and-intents.md) — the channels in depth
161
+ (`send`/`receive`, `intent` and its routes, `sendAtRoot`) and integrating
162
+ with the outside world.
163
+ - [advanced.md](./advanced.md) — `provide` / `lookup` / `*name` and the
164
+ clean-namespace boundary.
165
+ - [patterns/coordinate-components.md](./patterns/coordinate-components.md),
166
+ [patterns/share-state-across-the-tree.md](./patterns/share-state-across-the-tree.md),
167
+ [patterns/render-a-child-component.md](./patterns/render-a-child-component.md) —
168
+ runnable recipes for the rules above.