tutuca 0.9.45 → 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/dist/tutuca-cli.js +77 -31
- package/dist/tutuca-dev.js +81 -32
- package/dist/tutuca-dev.min.js +2 -2
- package/dist/tutuca-extra.js +81 -32
- package/dist/tutuca-extra.min.js +2 -2
- package/dist/tutuca.js +79 -30
- package/dist/tutuca.min.js +2 -2
- package/package.json +1 -1
- package/skill/tutuca/advanced.md +15 -0
- package/skill/tutuca/cli.md +1 -1
- package/skill/tutuca/core.md +67 -9
package/package.json
CHANGED
package/skill/tutuca/advanced.md
CHANGED
|
@@ -148,3 +148,18 @@ with `injectCss(scopeName, css)` to install the result before `start()`.
|
|
|
148
148
|
If a margaui skill is available, load it alongside this one when
|
|
149
149
|
authoring class lists — it lists the available components and their
|
|
150
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.
|
package/skill/tutuca/cli.md
CHANGED
|
@@ -4,7 +4,7 @@ The `tutuca` CLI inspects, documents, lints, tests, and renders any
|
|
|
4
4
|
module that follows the *Conventional Module Exports* shape (see
|
|
5
5
|
`core.md`). Reach this file when you need command/flag/exit-code
|
|
6
6
|
details, or when reading a lint code out of `lint` output. Otherwise
|
|
7
|
-
|
|
7
|
+
*Verifying changes* in `core.md` (run `lint`, then `test` for
|
|
8
8
|
behavior changes, then `render --title "<your example>"`) is enough.
|
|
9
9
|
|
|
10
10
|
## Install / invoke
|
package/skill/tutuca/core.md
CHANGED
|
@@ -83,11 +83,7 @@ Full reference: [cli.md](./cli.md).
|
|
|
83
83
|
newline / indent before the first element renders blank silently.
|
|
84
84
|
Use `view: html\`<el ...>` (or `html\`<el<newline> attr<newline>>...`),
|
|
85
85
|
never `view: html\`<newline> <el ...>`. Same applies to macro bodies.
|
|
86
|
-
- **Macro registry keys are
|
|
87
|
-
lowercases custom tag names, so `<x:Card>` is read as `<x:card>`.
|
|
88
|
-
`registerMacros({ Card })` is fine — the key is stored as `card`.
|
|
89
|
-
Registering two different macros under the same lowercased name (e.g.
|
|
90
|
-
`Card` and `card`) warns via `console.assert`; the later one wins.
|
|
86
|
+
- **Macro registry keys are lowercased.** `<x:Card>` becomes `<x:card>` — see *Macros*.
|
|
91
87
|
|
|
92
88
|
## Bootstrap
|
|
93
89
|
|
|
@@ -114,7 +110,63 @@ app.start();
|
|
|
114
110
|
`app.onChange((info) => ...)` fires after every state change with
|
|
115
111
|
`{ val, old, info, timestamp }` (logging, persistence). `app.stop()`
|
|
116
112
|
removes all listeners and cancels cache eviction; pair with
|
|
117
|
-
`app.start()`
|
|
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.
|
|
118
170
|
|
|
119
171
|
## Notation Reference
|
|
120
172
|
|
|
@@ -330,7 +382,7 @@ consequences:
|
|
|
330
382
|
custom elements with kebab-case attributes plus lowercased property
|
|
331
383
|
setters (or aliases), and bind via `:kebab-name` from Tutuca templates.
|
|
332
384
|
- Macro registry keys are lowercased on insert for the same reason
|
|
333
|
-
(see
|
|
385
|
+
(see *Macros* below).
|
|
334
386
|
|
|
335
387
|
## Event Handling
|
|
336
388
|
|
|
@@ -398,7 +450,7 @@ fails. `:mapId=".id"` does *not* invoke a `set mapId` setter
|
|
|
398
450
|
— the HTML parser lowercased the attribute name, so the framework assigns
|
|
399
451
|
to `node.mapid` instead, creating an own property and bypassing the
|
|
400
452
|
setter. Use kebab-case attributes / lowercased setters when authoring
|
|
401
|
-
custom elements for use with Tutuca. See
|
|
453
|
+
custom elements for use with Tutuca. See *Attribute Binding* above.
|
|
402
454
|
|
|
403
455
|
## Conditional Display
|
|
404
456
|
|
|
@@ -590,6 +642,10 @@ Every handler is called as `handler(...args, ctx)` and returns a
|
|
|
590
642
|
returned value into the dispatch path. The four sections below cover
|
|
591
643
|
each channel in turn.
|
|
592
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
|
+
|
|
593
649
|
## Bubble Events
|
|
594
650
|
|
|
595
651
|
```js
|
|
@@ -643,7 +699,8 @@ ctx.bubble("name", [arg]); // bubble up
|
|
|
643
699
|
`ctx.at` returns a `PathBuilder` with `.field(name)`, `.index(name, i)`,
|
|
644
700
|
and `.key(name, k)`. Each call appends a step to the path before
|
|
645
701
|
`.send(...)` / `.bubble(...)` fires; the handler runs inside the child
|
|
646
|
-
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.
|
|
647
704
|
|
|
648
705
|
When to send: bubble emits an *event* that any ancestor with a
|
|
649
706
|
matching handler can observe; send delivers a *message* to one
|
|
@@ -810,4 +867,5 @@ export function getExamples() {
|
|
|
810
867
|
items: [{ title, description, value, view }], // value = Comp.make(...)
|
|
811
868
|
};
|
|
812
869
|
}
|
|
870
|
+
export function getTests({ describe, test, expect }) { /*...*/ } // optional — see cli.md
|
|
813
871
|
```
|