tutuca 0.9.104 → 0.9.106

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/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # Tutuca
2
2
 
3
- Zero-dependency batteries included SPA framework.
3
+ Batteries included SPA framework with a dependency-free browser bundle.
4
4
 
5
5
  - **Single file, no build, no dependencies, no setup** — a script tag is all you need
6
6
  - **Batteries included** — state management, side effects, automatic memoization, drag and drop, testing, CLI tooling, LLM skills and more
7
7
  - **Fits in your head** (and the context window)
8
8
  - **View source friendly** — step through the whole stack
9
9
  - **As much HTML as possible, as little JS as needed**
10
- - ~182KB minified, ~41KB brotli compressed
10
+ - ~182KB minified, ~41KB brotli compressed (the core browser bundle; the dev build adds linting and test helpers)
11
11
 
12
12
  ## Quick Start
13
13
 
@@ -62,6 +62,30 @@ For an interactive walk-through with editable examples, see the
62
62
  </html>
63
63
  ```
64
64
 
65
+ ## Concepts at a Glance
66
+
67
+ Each concept has a tutorial section with editable live examples:
68
+
69
+ - **[Components & state](https://marianoguerra.github.io/tutuca/tutorial.html#basics)** —
70
+ typed `fields` with auto-generated mutators (`setCount`, `toggleOpen`, …),
71
+ views as pure functions of a single immutable state tree
72
+ ([state & updates](https://marianoguerra.github.io/tutuca/tutorial.html#state-and-updates))
73
+ - **[Collections](https://marianoguerra.github.io/tutuca/tutorial.html#collections)** —
74
+ iterate with `@each`, filter with `@when`, paginate with `@loop-with`
75
+ - **[Rendering components](https://marianoguerra.github.io/tutuca/tutorial.html#rendering-components)** —
76
+ compose with `<x render>`, multiple views per component, scoped styles
77
+ - **[Component communication](https://marianoguerra.github.io/tutuca/tutorial.html#component-communication)** —
78
+ bubble events up the tree, send messages to a target, run async work with
79
+ request/response
80
+ - **[Macros](https://marianoguerra.github.io/tutuca/tutorial.html#macros)** —
81
+ reusable markup fragments with parameters and slots
82
+ - **[Escape hatches](https://marianoguerra.github.io/tutuca/tutorial.html#escape-hatches)** —
83
+ drag and drop, web components, raw HTML
84
+ - **[Testing](https://marianoguerra.github.io/tutuca/tutorial.html#testing)** —
85
+ `getTests()` suites run by the CLI or in the browser, plus a
86
+ [linter](https://marianoguerra.github.io/tutuca/tutorial.html#linter-reference)
87
+ that catches template mistakes before they render blank
88
+
65
89
  ## CLI
66
90
 
67
91
  Tutuca ships a single-file CLI (`dist/tutuca-cli.js`) for inspecting, linting,
@@ -92,9 +116,9 @@ tutuca help [command]
92
116
  | `list <module> [name] [--limit n]` | List components and their fields/views (`--limit n` caps, `0` = all) |
93
117
  | `examples <module> [--limit n]` | List the examples defined in the module's section (`--limit n` caps, `0` = all) |
94
118
  | `show <module> [name]` | Component API docs — all, or one by name |
95
- | `lint <module> [name]` | Run lint checks — all, or one by name (exit 2 on errors) |
96
- | `render <module> [name] [--title t] [--view v]` | Render examples to HTML |
97
- | `test <module> [name] [--grep p] [--bail]` | Run `getTests()` (exit 4 on failures) |
119
+ | `lint <module> [name]` | Run lint checks — all, or one by name (exit `2` on errors) |
120
+ | `render <module> [name] [--title t] [--view v]` | Render examples to HTML (exit `3` on render crash) |
121
+ | `test <module> [name] [--grep p] [--bail]` | Run `getTests()` (exit `4` on failures) |
98
122
  | `storybook [dir]` | Serve a live storybook, auto-discovering co-located `*.dev.js` modules (`--port`, `--out`, `--dry-run`, `--no-margaui`, `--no-check`, `--no-tests`; no module path needed) |
99
123
  | `feedback [message]` | Append a feedback note (positional or stdin) to `~/.tutuca/feedback.jsonl` (no module path needed) |
100
124
  | `install-skill [--user\|--project] [--margaui-skill\|--immutable-skill\|--all] [--dot-agents] [--dry-run] [--force]` | Install bundled Claude Code skills (no module path needed) |
@@ -102,15 +126,11 @@ tutuca help [command]
102
126
 
103
127
  Global flags: `--json`, `-f, --format <cli\|md\|json\|html>`, `-o, --output <file>`, `--pretty`, `--module <path>`, `-h, --help`.
104
128
 
105
- Exit codes:
106
-
107
- - `0` success
108
- - `1` usage error (bad args, missing module, bad module shape)
109
- - `2` `lint` reported errors
110
- - `3` — `render` crashed while rendering
111
- - `4` — `test` reported failures
112
-
113
- Errors carry stable codes (`ERR_USAGE_*`, `ERR_FORMAT_*`, `ERR_SKILL_*`) and "did you mean" suggestions for unknown commands and flags. Under `--json`, errors are emitted as a single-line JSON envelope on stderr.
129
+ `storybook` wires [MargaUI](https://github.com/marianoguerra/margaui) — a
130
+ Tailwind v4 / daisyUI-compatible class library — by default; `--no-margaui`
131
+ skips it. For the full flag reference, error codes, and "did you mean"
132
+ behavior, run `tutuca help <command>`, or `tutuca agent-context` for a
133
+ machine-readable schema of the whole surface.
114
134
 
115
135
  ### Usage examples
116
136
 
@@ -124,27 +144,12 @@ npx tutuca show ./src/components.js Button --format md -o docs/button.md
124
144
  # Render every example to HTML, pretty-printed
125
145
  npx tutuca render ./src/components.js --format html --pretty -o dist/examples.html
126
146
 
127
- # Render a single named example
128
- npx tutuca render ./src/components.js Button --title "Disabled state"
129
-
130
- # Lint just one component (exit 2 if findings)
131
- npx tutuca lint ./src/components.js Button
132
-
133
147
  # Post-edit verification: lint, then render the example covering the
134
148
  # feature you just changed.
135
149
  npx tutuca lint ./src/components.js
136
150
  npx tutuca render ./src/components.js --title "Disabled state"
137
151
  ```
138
152
 
139
- ### Wrapping
140
-
141
- The invocation stays short even without wrapping, but common patterns:
142
-
143
- - **`package.json` scripts** — `"docs": "tutuca show ./src/components.js"`
144
- - **Shell alias** — `tut() { npx tutuca "$1" ./src/components.js "${@:2}"; }`, then `tut render Button`
145
- - **`justfile` / `Makefile`** — one recipe per subcommand, passing through positionals
146
- - **Programmatic** — `import "tutuca/cli"` (the bundled entry) for custom build integration
147
-
148
153
  ## Use with Claude Code
149
154
 
150
155
  Tutuca ships an LLM-facing reference (`SKILL.md` plus topic files such as
@@ -160,7 +165,8 @@ npx tutuca install-skill
160
165
  # or user-scoped: writes ~/.claude/skills/tutuca/
161
166
  npx tutuca install-skill --user
162
167
 
163
- # pick a different bundled skill, or install all three
168
+ # companion skills: MargaUI class lists, or Immutable.js (the values
169
+ # tutuca state is made of) — or install all three
164
170
  npx tutuca install-skill --margaui-skill
165
171
  npx tutuca install-skill --immutable-skill
166
172
  npx tutuca install-skill --all
@@ -176,12 +182,12 @@ The skill content is generated from `docs/skill/`, so the same reference
176
182
  runs locally (`tutuca lint <module>` + `tutuca render <module> --title …`)
177
183
  and inside Claude.
178
184
 
179
- ## License
180
-
181
- MIT
182
-
183
185
  ## Links
184
186
 
185
187
  - [Documentation & Playground](https://marianoguerra.github.io/tutuca/)
186
188
  - [Tutorial](https://marianoguerra.github.io/tutuca/tutorial.html)
187
189
  - [GitHub](https://github.com/marianoguerra/tutuca)
190
+
191
+ ## License
192
+
193
+ MIT
package/dist/immutable.js CHANGED
@@ -1952,21 +1952,10 @@ const Map = value => value === undefined || value === null ? emptyMap() : isMap(
1952
1952
  class MapImpl extends KeyedCollectionImpl {
1953
1953
  static {
1954
1954
  mixin(this, {
1955
- asImmutable,
1956
- asMutable,
1957
- deleteIn,
1958
- merge,
1959
- mergeWith,
1960
- mergeDeep,
1961
- mergeDeepWith,
1962
- mergeDeepIn,
1963
- mergeIn,
1964
- setIn,
1965
- update,
1966
- updateIn,
1955
+ ...mutatorMethods(),
1956
+ ...deepPathMethods(),
1957
+ ...keyedMergeMethods(),
1967
1958
  wasAltered,
1968
- withMutations,
1969
- removeIn: deleteIn,
1970
1959
  concat: merge,
1971
1960
  [IS_MAP_SYMBOL]: true,
1972
1961
  [DELETE]: this.prototype.remove,
@@ -2748,6 +2737,32 @@ function mergeDeepIn(keyPath, ...iters) {
2748
2737
  function mixin(Class, methods) {
2749
2738
  Object.assign(Class.prototype, methods);
2750
2739
  }
2740
+ function mutatorMethods() {
2741
+ return {
2742
+ asImmutable,
2743
+ asMutable,
2744
+ withMutations
2745
+ };
2746
+ }
2747
+ function deepPathMethods() {
2748
+ return {
2749
+ deleteIn,
2750
+ removeIn: deleteIn,
2751
+ mergeDeepIn,
2752
+ mergeIn,
2753
+ setIn,
2754
+ update,
2755
+ updateIn
2756
+ };
2757
+ }
2758
+ function keyedMergeMethods() {
2759
+ return {
2760
+ merge,
2761
+ mergeWith,
2762
+ mergeDeep,
2763
+ mergeDeepWith
2764
+ };
2765
+ }
2751
2766
  const List = value => {
2752
2767
  const empty = emptyList();
2753
2768
  if (value === undefined || value === null) {
@@ -2774,17 +2789,9 @@ List.of = (...values) => List(values);
2774
2789
  class ListImpl extends IndexedCollectionImpl {
2775
2790
  static {
2776
2791
  mixin(this, {
2777
- asImmutable,
2778
- asMutable,
2779
- deleteIn,
2780
- mergeDeepIn,
2781
- mergeIn,
2782
- setIn,
2783
- update,
2784
- updateIn,
2792
+ ...mutatorMethods(),
2793
+ ...deepPathMethods(),
2785
2794
  wasAltered,
2786
- withMutations,
2787
- removeIn: deleteIn,
2788
2795
  [IS_LIST_SYMBOL]: true,
2789
2796
  [DELETE]: this.prototype.remove,
2790
2797
  merge: this.prototype.concat,
@@ -3319,6 +3326,8 @@ class OrderedMapImpl extends MapImpl {
3319
3326
  __iterate: CollectionImpl.prototype.__iterate
3320
3327
  });
3321
3328
  }
3329
+ _map;
3330
+ _list;
3322
3331
  constructor(map, list, ownerID, hash) {
3323
3332
  super(map ? map.size : 0, undefined, ownerID, hash);
3324
3333
  this._map = map;
@@ -3455,10 +3464,8 @@ Stack.of = (...values) => Stack(values);
3455
3464
  class StackImpl extends IndexedCollectionImpl {
3456
3465
  static {
3457
3466
  mixin(this, {
3458
- asImmutable,
3459
- asMutable,
3467
+ ...mutatorMethods(),
3460
3468
  wasAltered,
3461
- withMutations,
3462
3469
  [IS_STACK_SYMBOL]: true,
3463
3470
  shift: this.prototype.pop,
3464
3471
  unshift: this.prototype.push,
@@ -3467,6 +3474,9 @@ class StackImpl extends IndexedCollectionImpl {
3467
3474
  [Symbol.iterator]: this.prototype.values
3468
3475
  });
3469
3476
  }
3477
+ _head;
3478
+ __ownerID;
3479
+ __altered;
3470
3480
  constructor(size, head, ownerID, hash) {
3471
3481
  super();
3472
3482
  this.size = size;
@@ -3507,17 +3517,17 @@ class StackImpl extends IndexedCollectionImpl {
3507
3517
  return returnStack(this, newSize, head);
3508
3518
  }
3509
3519
  pushAll(iter) {
3510
- iter = IndexedCollection(iter);
3511
- if (iter.size === 0) {
3520
+ const collection = IndexedCollection(iter);
3521
+ if (collection.size === 0) {
3512
3522
  return this;
3513
3523
  }
3514
- if (this.size === 0 && isStack(iter)) {
3515
- return iter;
3524
+ if (this.size === 0 && isStack(collection)) {
3525
+ return collection;
3516
3526
  }
3517
- assertNotInfinite(iter.size);
3527
+ assertNotInfinite(collection.size);
3518
3528
  let newSize = this.size;
3519
3529
  let head = this._head;
3520
- iter.__iterate(value => {
3530
+ collection.__iterate(value => {
3521
3531
  newSize++;
3522
3532
  head = {
3523
3533
  value,
@@ -3554,7 +3564,7 @@ class StackImpl extends IndexedCollectionImpl {
3554
3564
  const newSize = this.size - resolvedBegin;
3555
3565
  let head = this._head;
3556
3566
  while (resolvedBegin--) {
3557
- head = head.next;
3567
+ head = head?.next;
3558
3568
  }
3559
3569
  return returnStack(this, newSize, head);
3560
3570
  }
@@ -3572,7 +3582,7 @@ class StackImpl extends IndexedCollectionImpl {
3572
3582
  }
3573
3583
  return makeStack(this.size, this._head, ownerID, this.__hash);
3574
3584
  }
3575
- __iterate(fn, reverse) {
3585
+ __iterate(fn, reverse = false) {
3576
3586
  if (reverse) {
3577
3587
  const arr = this.toArray();
3578
3588
  const size = arr.length;
@@ -3594,7 +3604,7 @@ class StackImpl extends IndexedCollectionImpl {
3594
3604
  }
3595
3605
  return iterations;
3596
3606
  }
3597
- __iterator(reverse) {
3607
+ __iterator(reverse = false) {
3598
3608
  if (reverse) {
3599
3609
  const arr = this.toArray();
3600
3610
  const size = arr.length;
@@ -3660,8 +3670,8 @@ const Set = value => value === undefined || value === null ? emptySet() : isSet(
3660
3670
  Set.of = (...values) => Set(values);
3661
3671
  Set.fromKeys = value => Set(KeyedCollection(value).keySeq());
3662
3672
  Set.intersect = sets => {
3663
- sets = Collection(sets).toArray();
3664
- return sets.length ? Set(sets.pop()).intersect(...sets) : emptySet();
3673
+ const setArray = Collection(sets).toArray();
3674
+ return setArray.length ? Set(setArray.pop()).intersect(...setArray) : emptySet();
3665
3675
  };
3666
3676
  Set.union = sets => {
3667
3677
  const setArray = Collection(sets).toArray();
@@ -3670,9 +3680,7 @@ Set.union = sets => {
3670
3680
  class SetImpl extends SetCollectionImpl {
3671
3681
  static {
3672
3682
  mixin(this, {
3673
- withMutations,
3674
- asImmutable,
3675
- asMutable,
3683
+ ...mutatorMethods(),
3676
3684
  [IS_SET_SYMBOL]: true,
3677
3685
  [DELETE]: this.prototype.remove,
3678
3686
  merge: this.prototype.union,
@@ -3680,6 +3688,8 @@ class SetImpl extends SetCollectionImpl {
3680
3688
  [Symbol.toStringTag]: "Immutable.Set"
3681
3689
  });
3682
3690
  }
3691
+ _map;
3692
+ __ownerID;
3683
3693
  constructor(map, ownerID) {
3684
3694
  super();
3685
3695
  this.size = map ? map.size : 0;
@@ -3720,7 +3730,7 @@ class SetImpl extends SetCollectionImpl {
3720
3730
  if (iters.length === 0) {
3721
3731
  return this;
3722
3732
  }
3723
- if (this.size === 0 && !this.__ownerID && iters.length === 1) {
3733
+ if (this.size === 0 && !this.__ownerID && iters.length === 1 && !isOrdered(this)) {
3724
3734
  return Set(iters[0]);
3725
3735
  }
3726
3736
  return this.withMutations(set => {
@@ -3775,10 +3785,10 @@ function filterByIters(set, iters, shouldRemove) {
3775
3785
  if (iters.length === 0) {
3776
3786
  return set;
3777
3787
  }
3778
- iters = iters.map(iter => SetCollection(iter));
3788
+ const sets = iters.map(iter => SetCollection(iter));
3779
3789
  return set.withMutations(s => {
3780
3790
  set.forEach(value => {
3781
- if (shouldRemove(value, iters)) {
3791
+ if (shouldRemove(value, sets)) {
3782
3792
  s.remove(value);
3783
3793
  }
3784
3794
  });
@@ -3887,23 +3897,12 @@ const Record = (defaultValues, name) => {
3887
3897
  class RecordImpl {
3888
3898
  static {
3889
3899
  mixin(this, {
3890
- asImmutable,
3891
- asMutable,
3892
- deleteIn,
3900
+ ...mutatorMethods(),
3901
+ ...deepPathMethods(),
3902
+ ...keyedMergeMethods(),
3893
3903
  getIn,
3894
3904
  hasIn,
3895
- merge,
3896
- mergeWith,
3897
- mergeDeep,
3898
- mergeDeepWith,
3899
- mergeDeepIn,
3900
- mergeIn,
3901
- setIn,
3902
3905
  toObject,
3903
- update,
3904
- updateIn,
3905
- withMutations,
3906
- removeIn: deleteIn,
3907
3906
  toJSON: toObject,
3908
3907
  [IS_RECORD_SYMBOL]: true,
3909
3908
  [DELETE]: this.prototype.remove,
@@ -4163,6 +4162,7 @@ const Repeat = (value, times) => {
4163
4162
  return new RepeatImpl(value, size);
4164
4163
  };
4165
4164
  class RepeatImpl extends IndexedSeqImpl {
4165
+ _value;
4166
4166
  constructor(value, size) {
4167
4167
  super();
4168
4168
  this._value = value;
@@ -4199,7 +4199,7 @@ class RepeatImpl extends IndexedSeqImpl {
4199
4199
  }
4200
4200
  return -1;
4201
4201
  }
4202
- __iterateUncached(fn, reverse) {
4202
+ __iterateUncached(fn, reverse = false) {
4203
4203
  const size = this.size;
4204
4204
  let i = 0;
4205
4205
  while (i !== size) {
@@ -4209,7 +4209,7 @@ class RepeatImpl extends IndexedSeqImpl {
4209
4209
  }
4210
4210
  return i;
4211
4211
  }
4212
- __iteratorUncached(reverse) {
4212
+ __iteratorUncached(reverse = false) {
4213
4213
  const size = this.size;
4214
4214
  const val = this._value;
4215
4215
  let i = 0;
@@ -4247,7 +4247,7 @@ class RepeatImpl extends IndexedSeqImpl {
4247
4247
  this.prototype[Symbol.iterator] = this.prototype.values;
4248
4248
  }
4249
4249
  }
4250
- const fromJS = (value, converter) => fromJSWith([], converter ?? defaultConverter, value, "", converter?.length > 2 ? [] : undefined, {
4250
+ const fromJS = (value, converter) => fromJSWith([], converter ?? defaultConverter, value, "", converter && converter.length > 2 ? [] : undefined, {
4251
4251
  "": value
4252
4252
  });
4253
4253
  function fromJSWith(stack, converter, value, key, keyPath, parentValue) {
@@ -4311,18 +4311,16 @@ function initCollectionConversions() {
4311
4311
  IndexedCollectionImpl.prototype.keySeq = function keySeq() {
4312
4312
  return Range(0, this.size);
4313
4313
  };
4314
- MapImpl.prototype.sort = function sort(comparator) {
4315
- return OrderedMap(sortFactory(this, comparator));
4316
- };
4317
- MapImpl.prototype.sortBy = function sortBy(mapper, comparator) {
4318
- return OrderedMap(sortFactory(this, comparator, mapper));
4319
- };
4320
- SetImpl.prototype.sort = function sort(comparator) {
4321
- return OrderedSet(sortFactory(this, comparator));
4322
- };
4323
- SetImpl.prototype.sortBy = function sortBy(mapper, comparator) {
4324
- return OrderedSet(sortFactory(this, comparator, mapper));
4314
+ const patchSort = (Impl, OrderedCtor) => {
4315
+ Impl.prototype.sort = function sort(comparator) {
4316
+ return OrderedCtor(sortFactory(this, comparator));
4317
+ };
4318
+ Impl.prototype.sortBy = function sortBy(mapper, comparator) {
4319
+ return OrderedCtor(sortFactory(this, comparator, mapper));
4320
+ };
4325
4321
  };
4322
+ patchSort(MapImpl, OrderedMap);
4323
+ patchSort(SetImpl, OrderedSet);
4326
4324
  }
4327
4325
  var version$1 = "7.0.0";
4328
4326
  var pkg = {