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.
- package/dist/tutuca-cli.js +130 -104
- package/dist/tutuca-dev.ext.js +121 -90
- package/dist/tutuca-dev.js +121 -90
- package/dist/tutuca-dev.min.js +3 -3
- package/dist/tutuca-extra.ext.js +116 -59
- package/dist/tutuca-extra.js +116 -59
- package/dist/tutuca-extra.min.js +2 -2
- package/dist/tutuca-storybook.js +3 -3
- package/dist/tutuca.ext.js +116 -59
- package/dist/tutuca.js +116 -59
- package/dist/tutuca.min.js +2 -2
- package/package.json +1 -1
- package/skill/margaui/SKILL.md +105 -0
- package/skill/margaui/components/accordion.md +127 -0
- package/skill/margaui/components/alert.md +174 -0
- package/skill/margaui/components/aura.md +97 -0
- package/skill/margaui/components/avatar.md +220 -0
- package/skill/margaui/components/badge.md +193 -0
- package/skill/margaui/components/breadcrumbs.md +103 -0
- package/skill/margaui/components/button.md +322 -0
- package/skill/margaui/components/calendar.md +67 -0
- package/skill/margaui/components/card.md +373 -0
- package/skill/margaui/components/carousel.md +387 -0
- package/skill/margaui/components/chat.md +171 -0
- package/skill/margaui/components/checkbox.md +101 -0
- package/skill/margaui/components/collapse.md +172 -0
- package/skill/margaui/components/countdown.md +165 -0
- package/skill/margaui/components/diff.md +53 -0
- package/skill/margaui/components/divider.md +107 -0
- package/skill/margaui/components/dock.md +173 -0
- package/skill/margaui/components/drawer.md +184 -0
- package/skill/margaui/components/dropdown.md +388 -0
- package/skill/margaui/components/fab.md +346 -0
- package/skill/margaui/components/fieldset.md +88 -0
- package/skill/margaui/components/file-input.md +84 -0
- package/skill/margaui/components/filter.md +52 -0
- package/skill/margaui/components/footer.md +583 -0
- package/skill/margaui/components/hero.md +135 -0
- package/skill/margaui/components/hover-3d.md +129 -0
- package/skill/margaui/components/hover-gallery.md +49 -0
- package/skill/margaui/components/indicator.md +265 -0
- package/skill/margaui/components/input.md +389 -0
- package/skill/margaui/components/join.md +100 -0
- package/skill/margaui/components/kbd.md +127 -0
- package/skill/margaui/components/label.md +102 -0
- package/skill/margaui/components/link.md +96 -0
- package/skill/margaui/components/list.md +182 -0
- package/skill/margaui/components/loading.md +105 -0
- package/skill/margaui/components/mask.md +168 -0
- package/skill/margaui/components/megamenu.md +131 -0
- package/skill/margaui/components/menu.md +887 -0
- package/skill/margaui/components/mockup-browser.md +39 -0
- package/skill/margaui/components/mockup-code.md +81 -0
- package/skill/margaui/components/mockup-phone.md +39 -0
- package/skill/margaui/components/mockup-window.md +33 -0
- package/skill/margaui/components/modal.md +196 -0
- package/skill/margaui/components/navbar.md +282 -0
- package/skill/margaui/components/otp.md +171 -0
- package/skill/margaui/components/pagination.md +122 -0
- package/skill/margaui/components/progress.md +135 -0
- package/skill/margaui/components/radial-progress.md +67 -0
- package/skill/margaui/components/radio.md +133 -0
- package/skill/margaui/components/range.md +134 -0
- package/skill/margaui/components/rating.md +170 -0
- package/skill/margaui/components/select.md +225 -0
- package/skill/margaui/components/skeleton.md +64 -0
- package/skill/margaui/components/stack.md +142 -0
- package/skill/margaui/components/stat.md +254 -0
- package/skill/margaui/components/status.md +73 -0
- package/skill/margaui/components/steps.md +138 -0
- package/skill/margaui/components/swap.md +152 -0
- package/skill/margaui/components/tab.md +248 -0
- package/skill/margaui/components/table.md +1018 -0
- package/skill/margaui/components/text-rotate.md +91 -0
- package/skill/margaui/components/textarea.md +85 -0
- package/skill/margaui/components/theme-controller.md +266 -0
- package/skill/margaui/components/timeline.md +1356 -0
- package/skill/margaui/components/toast.md +165 -0
- package/skill/margaui/components/toggle.md +135 -0
- package/skill/margaui/components/tooltip.md +181 -0
- package/skill/margaui/components/validator.md +163 -0
- package/skill/tutuca/SKILL.md +56 -0
- package/skill/tutuca/advanced.md +212 -0
- package/skill/tutuca/cli.md +239 -0
- package/skill/tutuca/component-design.md +168 -0
- package/skill/tutuca/core.md +918 -0
- package/skill/tutuca/iteration.md +207 -0
- package/skill/tutuca/macros.md +86 -0
- package/skill/tutuca/margaui.md +175 -0
- package/skill/tutuca/messages-and-intents.md +399 -0
- package/skill/tutuca/patterns/README.md +48 -0
- package/skill/tutuca/patterns/add-a-story.md +26 -0
- package/skill/tutuca/patterns/bind-text-and-attributes.md +30 -0
- package/skill/tutuca/patterns/conditional-attribute-value.md +29 -0
- package/skill/tutuca/patterns/coordinate-components.md +54 -0
- package/skill/tutuca/patterns/edit-through-a-dynamic-target.md +27 -0
- package/skill/tutuca/patterns/enrich-each-item.md +25 -0
- package/skill/tutuca/patterns/file-input.md +39 -0
- package/skill/tutuca/patterns/filter-a-list.md +25 -0
- package/skill/tutuca/patterns/filter-and-paginate.md +60 -0
- package/skill/tutuca/patterns/handle-events.md +44 -0
- package/skill/tutuca/patterns/iterate-a-list.md +18 -0
- package/skill/tutuca/patterns/paginate-a-list.md +29 -0
- package/skill/tutuca/patterns/render-a-child-component.md +21 -0
- package/skill/tutuca/patterns/reuse-markup-with-macros.md +36 -0
- package/skill/tutuca/patterns/share-state-across-the-tree.md +38 -0
- package/skill/tutuca/patterns/show-or-hide-content.md +23 -0
- package/skill/tutuca/patterns/switch-between-views.md +30 -0
- package/skill/tutuca/patterns/tabbed-interface.md +43 -0
- package/skill/tutuca/semantics.md +195 -0
- package/skill/tutuca/storybook.md +270 -0
- package/skill/tutuca/styles.md +48 -0
- package/skill/tutuca/testing.md +345 -0
- package/skill/tutuca-source/SKILL.md +33 -0
- 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.
|