tutuca 0.9.44 → 0.9.46

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.44",
3
+ "version": "0.9.46",
4
4
  "type": "module",
5
5
  "description": "Zero-dependency SPA framework with immutable state and virtual DOM",
6
6
  "main": "./dist/tutuca.js",
@@ -33,7 +33,7 @@
33
33
  "fix": "bunx @biomejs/biome check --write src test/*.js",
34
34
  "tutuca": "bun tools/tutuca.js",
35
35
  "stresstest": "bun scripts/stresstest.js",
36
- "smoke-test": "bun tools/tutuca.js ./test/todo.js lint && bun tools/tutuca.js ./test/todo.js render && bun tools/tutuca.js ./test/json.js lint && bun tools/tutuca.js ./test/json.js render"
36
+ "smoke-test": "bun tools/tutuca.js ./test/todo.js lint && bun tools/tutuca.js ./test/todo.js render && bun tools/tutuca.js ./test/todo.js test && bun tools/tutuca.js ./test/json.js lint && bun tools/tutuca.js ./test/json.js render"
37
37
  },
38
38
  "sideEffects": false,
39
39
  "files": [
@@ -47,6 +47,7 @@
47
47
  "skill"
48
48
  ],
49
49
  "dependencies": {
50
+ "chai": "^6.2.2",
50
51
  "jsdom": "^28.0.0 || ^29.0.0"
51
52
  },
52
53
  "peerDependencies": {
@@ -145,7 +145,21 @@ collects the `class=` and `:class=` literals, hands them to a `compile`
145
145
  function (any margaui-compatible signature), and returns CSS text. Pair
146
146
  with `injectCss(scopeName, css)` to install the result before `start()`.
147
147
 
148
- If a margaui skill is installed in this project (e.g.
149
- `.claude/skills/margaui/`) load it alongside this one when authoring
150
- class lists it lists the available components and their canonical
151
- class strings, which is what the `compile` step expects.
148
+ If a margaui skill is available, load it alongside this one when
149
+ authoring class lists — it lists the available components and their
150
+ canonical class strings, which is what the `compile` step expects.
151
+
152
+ **Pitfall: `@if.class` payloads are invisible to the scanner.** Classes
153
+ inside `@then` / `@else` (e.g. `@if.class=".active" @then="'btn-success'"
154
+ @else="'btn-ghost'"`) are not literals in `class=` / `:class=`, so
155
+ `compileClassesToStyleText` skips them and the margaui CSS for those
156
+ classes is never emitted — the conditional class renders unstyled.
157
+ Workaround: add a hidden "decoy" view on the component that lists every
158
+ conditional class as a real literal, so the walker picks them up:
159
+
160
+ ```js
161
+ _margauiClasses: html`<p class="btn-success btn-ghost on off"></p>`,
162
+ ```
163
+
164
+ The view does not need to be rendered anywhere — registration is enough
165
+ for the template walker to find it.
@@ -1,11 +1,11 @@
1
1
  # Tutuca CLI Reference
2
2
 
3
- The `tutuca` CLI inspects, documents, lints, and renders any module that
4
- follows the *Conventional Module Exports* shape (see `core.md`). Reach
5
- this file when you need command/flag/exit-code details, or when reading
6
- a lint code out of `lint` output. Otherwise the post-edit recipe in
7
- `core.md` (run `lint`, then `render --title "<your example>"`) is
8
- enough.
3
+ The `tutuca` CLI inspects, documents, lints, tests, and renders any
4
+ module that follows the *Conventional Module Exports* shape (see
5
+ `core.md`). Reach this file when you need command/flag/exit-code
6
+ details, or when reading a lint code out of `lint` output. Otherwise
7
+ *Verifying changes* in `core.md` (run `lint`, then `test` for
8
+ behavior changes, then `render --title "<your example>"`) is enough.
9
9
 
10
10
  ## Install / invoke
11
11
 
@@ -36,6 +36,7 @@ prints a one-liner. `tutuca` ↔ `tutuca -h` prints overview.
36
36
  | `docs [name]` | Generate API docs (methods, input handlers, fields with auto-generated accessors) — all or one |
37
37
  | `lint [name]` | Run the linter; exits **2** on any error-level finding |
38
38
  | `render [name]` | Render examples to HTML in a headless DOM. Filter by component name or `--title`/`--view`. Exits **3** on render crash |
39
+ | `test [name]` | Run tests defined by `getTests({ describe, test, expect })`. Filter by component name, `--grep <pattern>`, or `--bail`. Exits **4** on any failure |
39
40
  | `help [cmd]` | Show usage; the only command that does **not** need a module path |
40
41
 
41
42
  ## Global flags
@@ -61,6 +62,7 @@ prints a one-liner. `tutuca` ↔ `tutuca -h` prints overview.
61
62
  | `1` | usage error (bad args, missing module, bad module shape) |
62
63
  | `2` | lint findings at error level |
63
64
  | `3` | render crash |
65
+ | `4` | one or more tests failed |
64
66
 
65
67
  ## Examples
66
68
 
@@ -77,8 +79,77 @@ tutuca ./src/components.js render Button --title "Disabled state"
77
79
  tutuca ./src/components.js lint
78
80
  tutuca ./src/components.js render --title "Disabled state"
79
81
  tutuca ./src/components.js render --title "Disabled state" --pretty
82
+
83
+ # Component-behavior verification: run the suite for one component, or
84
+ # narrow further with --grep. Add tests next to the component (the
85
+ # getTests() export) when the change isn't observable from render alone.
86
+ tutuca ./src/components.js test Counter
87
+ tutuca ./src/components.js test Counter --grep "inc()"
88
+ tutuca ./src/components.js test --bail
89
+ ```
90
+
91
+ ## `test` — running component tests
92
+
93
+ Use `test` after edits that change attributes, instance methods, input
94
+ handlers, or static factories — anything observable from JS rather than
95
+ from rendered HTML. The module opts in by exporting
96
+ `getTests({ describe, test, expect })`:
97
+
98
+ - `describe(Component, fn)` tags the suite with `Component.name` so
99
+ the positional `[name]` filter can pick it.
100
+ - `describe(title, fn)` is untagged; reachable only via `--grep`.
101
+ - `describe(title, { component }, fn)` tags an explicit title with a
102
+ custom component name.
103
+ - `test(title, fn)` — `fn` may be async; assertions use the injected
104
+ chai `expect`.
105
+
106
+ Filters:
107
+
108
+ - `[name]` — only tests whose tagged `componentName` equals `<name>`.
109
+ - `--grep <p>` — substring match against the full path
110
+ (e.g. `"Counter > inc() > works on a negative counter"`).
111
+ - `--bail` — stop on first failure; remaining tests reported as `skip`.
112
+
113
+ Default format is `cli` (a tree with ✓/✗/○ and per-test durations);
114
+ `-f md` and `-f json` work too.
115
+
116
+ A worked `getTests()` export covering methods, input handlers (called
117
+ via `Comp.input.x.call(inst)`), and immutability:
118
+
119
+ ```js
120
+ export function getTests({ describe, test, expect }) {
121
+ describe(Counter, () => {
122
+ describe("inc()", () => { // method
123
+ test("returns a Counter with count + 1", () => {
124
+ const next = Counter.make().inc();
125
+ expect(next).to.be.instanceOf(Counter.Class);
126
+ expect(next.count).to.equal(1);
127
+ });
128
+ test("does not mutate the original instance", () => {
129
+ const c = Counter.make({ count: 7 });
130
+ c.inc();
131
+ expect(c.count).to.equal(7); // immutability
132
+ });
133
+ });
134
+
135
+ describe("dec()", () => { // input handler
136
+ test("returns a Counter with count - 1", () => {
137
+ const next = Counter.input.dec.call(Counter.make());
138
+ expect(next.count).to.equal(-1);
139
+ });
140
+ });
141
+
142
+ test("inc and dec round-trip", () => { // untagged path
143
+ expect(Counter.input.dec.call(Counter.make().inc()).count).to.equal(0);
144
+ });
145
+ });
146
+ }
80
147
  ```
81
148
 
149
+ `describe(Counter, fn)` auto-tags the suite path with `Counter.name`, so
150
+ `tutuca <module> test Counter` picks it up. Untagged `test(...)` at the
151
+ top of a tagged `describe` inherits the tag.
152
+
82
153
  ## Install skill assets
83
154
 
84
155
  `tutuca install-skill` copies bundled Claude Code skill files into
@@ -100,7 +171,7 @@ tutuca install-skill --all --force # overwrite existing files
100
171
 
101
172
  ## Linter Rules
102
173
 
103
- Codes emitted by `lint`. Source: `tools/core/lint-check.js`.
174
+ Codes emitted by `lint`.
104
175
 
105
176
  ### Field references
106
177
 
@@ -15,8 +15,8 @@ the `tutuca` CLI.
15
15
 
16
16
  ## Verifying changes
17
17
 
18
- After editing a Tutuca module, run two checks before declaring the edit
19
- done:
18
+ After editing a Tutuca module, run these checks before declaring the
19
+ edit done:
20
20
 
21
21
  1. **Lint the module** — catches undefined fields/handlers/macros/events
22
22
  (all the `*_NOT_DEFINED` / `*_NOT_REFERENCED` codes):
@@ -26,7 +26,21 @@ done:
26
26
  Exits `2` on any error-level finding. Pass a component name to scope
27
27
  it: `tutuca <module-path> lint Button`.
28
28
 
29
- 2. **Render the example(s) that exercise the feature you changed** —
29
+ 2. **Test component behavior** when the edit changes attributes,
30
+ instance methods, input handlers, or static factories (anything
31
+ observable from JS, not just the rendered HTML), run the test
32
+ suite. The module opts in by exporting
33
+ `getTests({ describe, test, expect })`:
34
+
35
+ tutuca <module-path> test
36
+ tutuca <module-path> test Counter # one component
37
+ tutuca <module-path> test --grep "inc()" # one path
38
+
39
+ Exits `4` on any failure. Skip this step when the change is purely
40
+ templates/styling — `render` already covers that. Full reference,
41
+ including a worked `getTests()` export, in [cli.md](./cli.md).
42
+
43
+ 3. **Render the example(s) that exercise the feature you changed** —
30
44
  confirms the component actually mounts in a headless DOM with the new
31
45
  behavior. Pick the example whose `title` matches the feature, or
32
46
  filter by component:
@@ -69,11 +83,7 @@ Full reference: [cli.md](./cli.md).
69
83
  newline / indent before the first element renders blank silently.
70
84
  Use `view: html\`<el ...>` (or `html\`<el<newline> attr<newline>>...`),
71
85
  never `view: html\`<newline> <el ...>`. Same applies to macro bodies.
72
- - **Macro registry keys are normalized to lowercase.** The HTML parser
73
- lowercases custom tag names, so `<x:Card>` is read as `<x:card>`.
74
- `registerMacros({ Card })` is fine — the key is stored as `card`.
75
- Registering two different macros under the same lowercased name (e.g.
76
- `Card` and `card`) warns via `console.assert`; the later one wins.
86
+ - **Macro registry keys are lowercased.** `<x:Card>` becomes `<x:card>` see *Macros*.
77
87
 
78
88
  ## Bootstrap
79
89
 
@@ -100,7 +110,63 @@ app.start();
100
110
  `app.onChange((info) => ...)` fires after every state change with
101
111
  `{ val, old, info, timestamp }` (logging, persistence). `app.stop()`
102
112
  removes all listeners and cancels cache eviction; pair with
103
- `app.start()` for teardown in tests or SPA navigation.
113
+ `app.start()` to remount cleanly in tests or SPA navigation.
114
+
115
+ ## Mental model
116
+
117
+ Tutuca rests on three invariants: the application state is a single
118
+ immutable root value; the view is a pure function of it; every handler
119
+ takes the old self and returns a new self. The transactor swaps the
120
+ root atomically. Identity-based caching, time-travel-style debugging,
121
+ and the entire dispatch model fall out of these three properties.
122
+
123
+ **The value tree.** Components are nested immutable Records. Children
124
+ live in fields — a list of `Item`, a map of `User`, a scalar `count`.
125
+ "Updating a deep child" means producing a new root that shares
126
+ structure with the old one along the unchanged spine; the renderer
127
+ keys its cache on `===` identity, so unchanged subtrees skip work.
128
+ Every value carries a hidden tag back to its component class, so the
129
+ runtime never needs `instanceof` — it asks the value what it is.
130
+
131
+ **Stack: frames vs scopes.** As the renderer walks the AST it pushes
132
+ `BindFrame`s. A *frame* is a barrier: name lookups (`@x`) stop at it,
133
+ so a child component view sees a clean namespace. A *scope* is
134
+ transparent: iteration `key` / `value` and `@enrich-with` binds layer
135
+ onto the surrounding frame and remain visible to handlers attached to
136
+ the same iteration. `it` (the target of `.field` lookups) is set on
137
+ both.
138
+
139
+ | pushed by | kind | shape |
140
+ | ----------------------------------- | ----- | ------------------------------------ |
141
+ | `<x render=".f">` / `<x render-it>` | frame | `it` = child, fresh binds |
142
+ | `<x render-each>` per iter | frame | `it` = item, binds `{ key }` |
143
+ | `<div @each>` per iter | scope | `it` = item, binds `{ key, value }` |
144
+ | `<x:scope @enrich-with=…>` | scope | `it` unchanged, binds = alter result |
145
+
146
+ For full mechanics see *List Iteration* and *Scope Enrichment*.
147
+ This is why a handler attached to `<div @each>` runs against the
148
+ *parent* component (the scope is transparent — the surrounding frame
149
+ still owns dispatch), while one inside `<x render-it>` runs against
150
+ the *item* (render-it pushed a fresh frame for the child).
151
+
152
+ **Paths, not references.** The DOM is the only thing that survives
153
+ between render and click, so the renderer leaves breadcrumbs:
154
+ `data-cid` / `data-nid` / `data-eid` on rendered elements, and `§…§`
155
+ HTML comments adjacent to iteration entries. On a DOM event the
156
+ runtime walks from the target up to the root, reads those breadcrumbs,
157
+ and rebuilds a *positional* `Path` — an array of steps from the root
158
+ to the value the handler should run against. The same `Path` is reused
159
+ verbatim for `ctx.send`, `ctx.bubble`, and `ctx.request` /
160
+ response: because it's positional rather than a captured reference, an
161
+ async response still lands at the right slot even after intervening
162
+ transactions have rebuilt the root. See *Bubble Events*, *Send /
163
+ Receive*, *Async Requests* for the dispatch APIs.
164
+
165
+ **Why `alter` is its own table.** Alter handlers are pure, evaluated
166
+ on every render, and produce binds (no state change). `input` /
167
+ `receive` / `bubble` / `response` are transactional and produce new
168
+ values. Same lookup mechanism, different contracts — keep them
169
+ separate.
104
170
 
105
171
  ## Notation Reference
106
172
 
@@ -316,7 +382,7 @@ consequences:
316
382
  custom elements with kebab-case attributes plus lowercased property
317
383
  setters (or aliases), and bind via `:kebab-name` from Tutuca templates.
318
384
  - Macro registry keys are lowercased on insert for the same reason
319
- (see "Macros" section below).
385
+ (see *Macros* below).
320
386
 
321
387
  ## Event Handling
322
388
 
@@ -384,7 +450,7 @@ fails. `:mapId=".id"` does *not* invoke a `set mapId` setter
384
450
  — the HTML parser lowercased the attribute name, so the framework assigns
385
451
  to `node.mapid` instead, creating an own property and bypassing the
386
452
  setter. Use kebab-case attributes / lowercased setters when authoring
387
- custom elements for use with Tutuca. See "Attribute Binding" above.
453
+ custom elements for use with Tutuca. See *Attribute Binding* above.
388
454
 
389
455
  ## Conditional Display
390
456
 
@@ -576,6 +642,10 @@ Every handler is called as `handler(...args, ctx)` and returns a
576
642
  returned value into the dispatch path. The four sections below cover
577
643
  each channel in turn.
578
644
 
645
+ `alter` is a fifth handler block, but unlike the four above it isn't
646
+ event-triggered — the renderer invokes alter handlers to produce
647
+ binds, not to update state. See *Mental model* and *Scope Enrichment*.
648
+
579
649
  ## Bubble Events
580
650
 
581
651
  ```js
@@ -629,7 +699,8 @@ ctx.bubble("name", [arg]); // bubble up
629
699
  `ctx.at` returns a `PathBuilder` with `.field(name)`, `.index(name, i)`,
630
700
  and `.key(name, k)`. Each call appends a step to the path before
631
701
  `.send(...)` / `.bubble(...)` fires; the handler runs inside the child
632
- instance with `this` bound to it.
702
+ instance with `this` bound to it. Paths are positional, not references —
703
+ see *Mental model* for why this matters across async boundaries.
633
704
 
634
705
  When to send: bubble emits an *event* that any ancestor with a
635
706
  matching handler can observe; send delivers a *message* to one
@@ -775,9 +846,8 @@ import {
775
846
  ```
776
847
 
777
848
  Because every `immutable` export is reachable through `tutuca`, if an
778
- `immutable-js` skill is installed in this project (e.g.
779
- `.claude/skills/immutable-js/`) load it alongside this one its guidance
780
- applies directly to the values you'll be reading and writing.
849
+ `immutable-js` skill is available, load it alongside this one — its
850
+ guidance applies directly to the values you'll be reading and writing.
781
851
 
782
852
  ## Conventional Module Exports
783
853
 
@@ -797,4 +867,5 @@ export function getExamples() {
797
867
  items: [{ title, description, value, view }], // value = Comp.make(...)
798
868
  };
799
869
  }
870
+ export function getTests({ describe, test, expect }) { /*...*/ } // optional — see cli.md
800
871
  ```
package/skill/SKILL.md DELETED
@@ -1,46 +0,0 @@
1
- ---
2
- name: tutuca
3
- description: Authoring or reviewing tutuca components, html`` views, macros, or running the `tutuca` CLI. Covers field types, @-directives, bubble/receive/response handlers, and the post-edit `tutuca <module> lint` + `tutuca <module> render --title …` verification recipe.
4
- ---
5
-
6
- # Tutuca
7
-
8
- Tutuca is an immutable-state SPA framework: components have typed
9
- `fields`, auto-generated mutators (`setX`, `pushInX`, …), HTML-template
10
- `view`s with `@`-prefixed directives, and `bubble` / `receive` /
11
- `response` handlers for orchestration.
12
-
13
- ## Verifying changes
14
-
15
- After editing a tutuca module, run two checks before declaring the edit
16
- done:
17
-
18
- 1. **Lint** — catches undefined fields/handlers/macros/events. Exits
19
- `2` on any error-level finding.
20
-
21
- ```sh
22
- tutuca <module-path> lint
23
- ```
24
-
25
- 2. **Render the example that exercises the feature you changed** —
26
- confirms the component mounts in a headless DOM with the new
27
- behavior. Exits `3` on render crash.
28
-
29
- ```sh
30
- tutuca <module-path> render --title "<example title>"
31
- ```
32
-
33
- If no example covers the feature, add one to `getExamples()` first —
34
- that's how the feature becomes verifiable.
35
-
36
- ## Routing
37
-
38
- | Task | File |
39
- | ---------------------------------------------------------------------------------------------- | ------------------------------- |
40
- | Authoring `component({...})`, `html\`...\`` views, macros, fields, events, lists, styles | [core.md](./core.md) |
41
- | CLI commands, flags, exit codes, full linter rule list | [cli.md](./cli.md) |
42
- | Drag & drop, dynamic bindings (`*x`), pseudo-`x`, custom seq types, Tailwind/MargaUI | [advanced.md](./advanced.md) |
43
-
44
- Read `core.md` first. Reach for `cli.md` or `advanced.md` only when the
45
- task touches them — both files are referenced inline from `core.md` so
46
- you'll be pointed there when relevant.
package/skill/advanced.md DELETED
@@ -1,146 +0,0 @@
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>`), registering a custom seq type, or
6
- compiling Tailwind / MargaUI classes. For everything else, `core.md`
7
- 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
- input: {
23
- onDrop(targetKey, dragInfo, e) {
24
- const sourceKey = dragInfo.lookupBind("key"); // any bind from source render
25
- return this.setItems(this.items.moveKeyBeforeKey(sourceKey, targetKey));
26
- },
27
- }
28
- ```
29
-
30
- Tutuca auto-manages two attrs during a drag — style them with CSS:
31
-
32
- ```css
33
- [data-dragging="1"] {
34
- opacity: 0.5;
35
- }
36
- [data-draggingover="my-item"] {
37
- outline: 1px dashed;
38
- }
39
- ```
40
-
41
- Touch is wired up too (drag fires after a small move threshold).
42
-
43
- ## Dynamic Bindings
44
-
45
- For passing values "context-style" through nested components without prop
46
- drilling. Define on the producer; alias on consumers; resolve as `*name`.
47
-
48
- ```js
49
- const Theme = component({
50
- name: "Theme",
51
- fields: { color: "blue" },
52
- dynamic: { color: ".color" },
53
- on: {
54
- stackEnter() {
55
- return ["color"];
56
- },
57
- },
58
- });
59
- const Child = component({
60
- dynamic: { color: { for: "Theme.color", default: "'gray'" } },
61
- view: html`<p :style="color: {*color}"></p>`,
62
- });
63
- ```
64
-
65
- `on.stackEnter()` is required only on the **producer** (the component
66
- declaring `dynamic: { name: ".field" }` to expose a value). It returns
67
- the list of dynamic-binding names this component pushes onto the stack
68
- when entering its render. Consumers (which only alias via
69
- `{ for: "Producer.name", default: ... }`) don't need it.
70
-
71
- ## Pseudo-`x` (`@x`)
72
-
73
- Tutuca's special operations (`render`, `render-it`, `render-each`, `text`,
74
- `show`, `hide`, `slot`) live on the `<x>` tag. That works almost
75
- everywhere, but the browser's HTML parser refuses to keep `<x>` (or any
76
- unknown tag) as a child of certain elements: `<select>` only allows
77
- `<option>` / `<optgroup>`, `<table>` only allows `<thead>` / `<tbody>` /
78
- `<tr>`, `<tr>` only allows `<th>` / `<td>`, etc. Drop `<x render-each>`
79
- inside one of those and the parser silently strips it.
80
-
81
- The escape hatch: prefix the **first** attribute on a *legal* tag with
82
- `@x`. Tutuca treats that tag as if it were `<x>` and reads the next
83
- attribute as the special op.
84
-
85
- ```html
86
- <!-- ❌ <x> stripped by the HTML parser inside <select> -->
87
- <select>
88
- <x render-each=".items" as="option"></x>
89
- </select>
90
-
91
- <!-- ✅ pseudo-x: <option @x render-each=".items" as="option"> -->
92
- <select>
93
- <option @x render-each=".items" as="option"></option>
94
- </select>
95
- ```
96
-
97
- Notes:
98
-
99
- - `@x` must be the **first** attribute; the special op (`render-each`,
100
- `render`, `text`, `show`, ...) is the second.
101
- - The host tag (here `<option>`) is otherwise ignored — only the special
102
- op runs. Tutuca produces the rendered children directly.
103
- - Same trick works inside `<tr>`, `<table>`, `<colgroup>`, `<dl>`,
104
- `<details>`, or anywhere else the parser would discard `<x>`.
105
-
106
- ## Registering a custom seq type
107
-
108
- To make `@each` recognize your own collection class, install a
109
- `SEQ_INFO` walker on its prototype:
110
-
111
- ```js
112
- import { SEQ_INFO } from "tutuca";
113
-
114
- class MyClass {
115
- // ...
116
- }
117
- MyClass.prototype[SEQ_INFO] = (seq, visit) => {
118
- for (const [k, v] of seq.entries()) visit(k, v, "data-sk");
119
- };
120
- ```
121
-
122
- `SEQ_INFO` is `Symbol.for("tutuca.seqInfo")`, so the same identity
123
- is shared across module graphs (source vs. bundled tutuca). The
124
- renderer reads `seq[SEQ_INFO]` directly (no `.constructor` lookup),
125
- which is why the walker goes on the prototype, not as a static.
126
-
127
- The third arg to `visit` is the data attribute used for stable-key
128
- diffing (typically `"data-sk"` for "sequence key").
129
-
130
- ## Tailwind / MargaUI Class Compilation (extra build)
131
-
132
- ```js
133
- import { compileClassesToStyleText, injectCss, tutuca } from "tutuca/extra";
134
- import { compile } from "https://esm.sh/margaui";
135
-
136
- const app = tutuca("#app");
137
- app.registerComponents([Comp]);
138
- const css = await compileClassesToStyleText(app, compile);
139
- injectCss("myapp", css);
140
- app.start();
141
- ```
142
-
143
- `compileClassesToStyleText` walks every registered component's templates,
144
- collects the `class=` and `:class=` literals, hands them to a `compile`
145
- function (any margaui-compatible signature), and returns CSS text. Pair
146
- with `injectCss(scopeName, css)` to install the result before `start()`.
package/skill/cli.md DELETED
@@ -1,117 +0,0 @@
1
- # Tutuca CLI Reference
2
-
3
- The `tutuca` CLI inspects, documents, lints, and renders any module that
4
- follows the *Conventional Module Exports* shape (see `core.md`). Reach
5
- this file when you need command/flag/exit-code details, or when reading
6
- a lint code out of `lint` output. Otherwise the post-edit recipe in
7
- `core.md` (run `lint`, then `render --title "<your example>"`) is
8
- enough.
9
-
10
- ## Install / invoke
11
-
12
- ```sh
13
- # project local
14
- npm i --save-dev tutuca
15
- npx tutuca <module-path> <command> [name] [flags]
16
-
17
- # global
18
- npm i -g tutuca
19
- tutuca <module-path> <command> [name] [flags]
20
-
21
- # from a checkout of this repo
22
- bun tools/tutuca.js <module-path> <command> [name] [flags]
23
- ```
24
-
25
- The module path comes **first**, the command second, an optional component
26
- name third. `tutuca help` prints the full reference; `tutuca help <command>`
27
- prints a one-liner. `tutuca` ↔ `tutuca -h` prints overview.
28
-
29
- ## Commands
30
-
31
- | Command | Purpose |
32
- | --------------- | ---------------------------------------------------------------------------------------------------------------------- |
33
- | `info` | Summarize which `getX()` exports are present and counts |
34
- | `list` | List components with their views and fields (name + type) |
35
- | `examples` | Print `getExamples()` content (title, items, per section) |
36
- | `docs [name]` | Generate API docs (methods, input handlers, fields with auto-generated accessors) — all or one |
37
- | `lint [name]` | Run the linter; exits **2** on any error-level finding |
38
- | `render [name]` | Render examples to HTML in a headless DOM. Filter by component name or `--title`/`--view`. Exits **3** on render crash |
39
- | `help [cmd]` | Show usage; the only command that does **not** need a module path |
40
-
41
- ## Global flags
42
-
43
- ```
44
- -f, --format <cli|md|json|html> output format
45
- defaults: info/list/examples/lint → cli
46
- docs/render → md
47
- html only valid for render
48
- json works for every command
49
- -o, --output <file> write to file instead of stdout
50
- --pretty pretty-print HTML (md/html) via prettier;
51
- JSON formatter uses indent 2
52
- --module <path> alternative to first-positional module path
53
- -h, --help show help (overview, or for one command)
54
- ```
55
-
56
- ## Exit codes
57
-
58
- | Code | Meaning |
59
- | ---- | -------------------------------------------------------- |
60
- | `0` | success |
61
- | `1` | usage error (bad args, missing module, bad module shape) |
62
- | `2` | lint findings at error level |
63
- | `3` | render crash |
64
-
65
- ## Examples
66
-
67
- ```sh
68
- tutuca ./src/components.js info # quick overview
69
- tutuca ./src/components.js list # components, views, fields
70
- tutuca ./src/components.js docs Button -f json # one component, JSON
71
- tutuca ./src/components.js render -f html --pretty -o out/examples.html
72
- tutuca ./src/components.js render Button --title "Disabled state"
73
-
74
- # Post-edit verification: lint, then render the example for the feature
75
- # you just changed (add the example first if none covers it). Add
76
- # --pretty when you need to read the HTML to verify structure.
77
- tutuca ./src/components.js lint
78
- tutuca ./src/components.js render --title "Disabled state"
79
- tutuca ./src/components.js render --title "Disabled state" --pretty
80
- ```
81
-
82
- ## Linter Rules
83
-
84
- Codes emitted by `lint`. Source: `tools/core/lint-check.js`.
85
-
86
- ### Field references
87
-
88
- - `FIELD_VAL_NOT_DEFINED` — `.field` not declared in `fields`.
89
-
90
- ### Input-handler / method confusion
91
-
92
- - `INPUT_HANDLER_NOT_IMPLEMENTED` — bare handler name not in `input`.
93
- - `INPUT_HANDLER_NOT_REFERENCED` — `input` entry never used.
94
- - `INPUT_HANDLER_METHOD_NOT_IMPLEMENTED` — `.handler` not in `methods`.
95
- - `INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD` — bare name resolves to `methods` (use `.name`).
96
- - `INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER` — `.name` resolves to `input` (drop the dot).
97
-
98
- ### Iteration helpers (`alter`)
99
-
100
- - `ALT_HANDLER_NOT_DEFINED` — `@when` / `@enrich-with` / `@loop-with` name not in `alter`.
101
- - `ALT_HANDLER_NOT_REFERENCED` — `alter` entry never used.
102
-
103
- ### Templates / events
104
-
105
- - `RENDER_IT_OUTSIDE_OF_LOOP` — `<x render-it>` outside `@each` / `render-each`.
106
- - `UNKNOWN_EVENT_MODIFIER` — `+mod` not in the recognized modifier set.
107
- - `UNKNOWN_HANDLER_ARG_NAME` — handler arg name not a built-in / declared component.
108
- - `DUPLICATE_ATTR_DEFINITION` — same attr set by literal + `:attr` + `@if.attr` on one element.
109
- - `UNKNOWN_DIRECTIVE` — `@name` directive not recognized (typo or unsupported).
110
- - `UNKNOWN_X_OP` — first attribute on `<x>` (or pseudo-`@x`) is not a known op.
111
- - `UNKNOWN_X_ATTR` — extra attribute on `<x op>` not consumed by the op and not a known wrapper (`show`/`hide`).
112
-
113
- ### Names registered with the app
114
-
115
- - `UNKNOWN_REQUEST_NAME` — `!name` not registered.
116
- - `UNKNOWN_COMPONENT_NAME` — component type not registered.
117
- - `UNKNOWN_MACRO_ARG` — macro attr not declared in defaults.