tutuca 0.9.115 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +16 -16
  2. package/dist/immer.js +1261 -0
  3. package/dist/tutuca-cli.js +2056 -4798
  4. package/dist/tutuca-components.js +206 -442
  5. package/dist/tutuca-dev.ext.js +878 -944
  6. package/dist/tutuca-dev.js +1679 -5298
  7. package/dist/tutuca-dev.min.js +5 -5
  8. package/dist/tutuca-extra.ext.js +740 -695
  9. package/dist/tutuca-extra.js +1403 -4925
  10. package/dist/tutuca-extra.min.js +3 -3
  11. package/dist/tutuca-storybook.js +226 -210
  12. package/dist/tutuca.ext.js +740 -695
  13. package/dist/tutuca.js +1403 -4925
  14. package/dist/tutuca.min.js +3 -3
  15. package/package.json +7 -11
  16. package/skill/tutuca/SKILL.md +4 -8
  17. package/skill/tutuca/advanced.md +13 -5
  18. package/skill/tutuca/cli.md +8 -9
  19. package/skill/tutuca/component-design.md +50 -47
  20. package/skill/tutuca/core.md +134 -117
  21. package/skill/tutuca/iteration.md +2 -2
  22. package/skill/tutuca/macros.md +1 -1
  23. package/skill/tutuca/messages-and-intents.md +399 -0
  24. package/skill/tutuca/patterns/README.md +1 -1
  25. package/skill/tutuca/patterns/add-a-story.md +2 -2
  26. package/skill/tutuca/patterns/bind-text-and-attributes.md +1 -1
  27. package/skill/tutuca/patterns/conditional-attribute-value.md +1 -1
  28. package/skill/tutuca/patterns/coordinate-components.md +41 -18
  29. package/skill/tutuca/patterns/file-input.md +8 -8
  30. package/skill/tutuca/patterns/filter-and-paginate.md +1 -1
  31. package/skill/tutuca/patterns/handle-events.md +11 -9
  32. package/skill/tutuca/patterns/switch-between-views.md +1 -1
  33. package/skill/tutuca/patterns/tabbed-interface.md +10 -5
  34. package/skill/tutuca/semantics.md +41 -35
  35. package/skill/tutuca/storybook.md +34 -27
  36. package/skill/tutuca/testing.md +75 -64
  37. package/skill/tutuca-source/tutuca.ext.js +740 -695
  38. package/dist/immutable.js +0 -4331
  39. package/skill/immutable-js/SKILL.md +0 -78
  40. package/skill/immutable-js/references/collection.md +0 -346
  41. package/skill/immutable-js/references/conversions.md +0 -99
  42. package/skill/immutable-js/references/deep-updates.md +0 -172
  43. package/skill/immutable-js/references/equality.md +0 -95
  44. package/skill/immutable-js/references/list.md +0 -266
  45. package/skill/immutable-js/references/map.md +0 -300
  46. package/skill/immutable-js/references/predicates.md +0 -93
  47. package/skill/immutable-js/references/range-repeat.md +0 -55
  48. package/skill/immutable-js/references/record.md +0 -196
  49. package/skill/immutable-js/references/seq.md +0 -248
  50. package/skill/immutable-js/references/set.md +0 -270
  51. package/skill/immutable-js/references/shallow-functional.md +0 -99
  52. package/skill/immutable-js/references/stack.md +0 -210
  53. package/skill/tutuca/request-response.md +0 -341
@@ -170,11 +170,69 @@ __reExport(chai_exports, chai_star);
170
170
  import * as chai_star from "chai";
171
171
  chai.use(jestMatchers);
172
172
 
173
- // src/value.js
174
- import { is } from "immutable";
173
+ // src/collection.js
174
+ var isPlainObject = (value) => {
175
+ if (value === null || typeof value !== "object") return false;
176
+ const proto = Object.getPrototypeOf(value);
177
+ return proto === Object.prototype || proto === null;
178
+ };
179
+ var seqSize = (seq) => {
180
+ if (seq == null) return 0;
181
+ if (typeof seq.length === "number") return seq.length;
182
+ if (typeof seq.size === "number") return seq.size;
183
+ return isPlainObject(seq) ? Object.keys(seq).length : 0;
184
+ };
185
+ var seqGet = (seq, key, dval = null) => {
186
+ if (seq == null) return dval;
187
+ if (seq instanceof Map) return seq.has(key) ? seq.get(key) : dval;
188
+ if (seq instanceof Set) return seq.has(key) ? key : dval;
189
+ if (Object.hasOwn(seq, key)) return seq[key];
190
+ if (typeof seq.get === "function") return seq.get(key, dval);
191
+ return dval;
192
+ };
193
+ var isIndexedSeq = (seq) => Array.isArray(seq);
194
+ var isKeyedSeq = (seq) => seq instanceof Map || isPlainObject(seq);
195
+ var isSetSeq = (seq) => seq instanceof Set;
196
+ function* seqEntries(seq) {
197
+ if (seq instanceof Map) yield* seq.entries();
198
+ else if (seq instanceof Set) for (const value of seq) yield [value, value];
199
+ else if (isPlainObject(seq)) yield* Object.entries(seq);
200
+ else if (Array.isArray(seq)) for (let i = 0; i < seq.length; i++) yield [i, seq[i]];
201
+ }
202
+
203
+ // src/immer.js
204
+ var immer_exports = {};
205
+ __reExport(immer_exports, immer_star);
206
+ import { enableMapSet } from "immer";
207
+ import * as immer_star from "immer";
208
+ enableMapSet();
175
209
 
176
210
  // src/path.js
177
211
  var NONE = /* @__PURE__ */ Symbol("NONE");
212
+ var readKey = (value, key, dval = null) => {
213
+ if (value == null) return dval;
214
+ if (value instanceof Map) return value.has(key) ? value.get(key) : dval;
215
+ if (value instanceof Set) return value.has(key) ? key : dval;
216
+ return Object.hasOwn(value, key) ? value[key] : dval;
217
+ };
218
+ var writeKey = (value, key, next) => {
219
+ if (value instanceof Map) value.set(key, next);
220
+ else if (value instanceof Set) {
221
+ value.delete(key);
222
+ value.add(next);
223
+ } else value[key] = next;
224
+ };
225
+ var readSeqKey = (value, key, dval = null) => {
226
+ const direct = readKey(value, key, NONE);
227
+ if (direct !== NONE) return direct;
228
+ return typeof value?.get === "function" ? value.get(key, dval) : dval;
229
+ };
230
+ var writeSeqKey = (value, key, next) => {
231
+ if (value instanceof Map || value instanceof Set || Array.isArray(value) || Object.hasOwn(value, key))
232
+ writeKey(value, key, next);
233
+ else if (typeof value?.set === "function") value.set(key, next);
234
+ else writeKey(value, key, next);
235
+ };
178
236
  var Step = class {
179
237
  lookup(_v, dval = null) {
180
238
  return dval;
@@ -182,6 +240,8 @@ var Step = class {
182
240
  setValue(root, _v) {
183
241
  return root;
184
242
  }
243
+ setDraftValue(_root, _v) {
244
+ }
185
245
  enterFrame(stack, _prev, next) {
186
246
  return stack.enter(next, {}, true);
187
247
  }
@@ -246,10 +306,13 @@ var FieldStep = class extends Step {
246
306
  this.field = field;
247
307
  }
248
308
  lookup(v, dval = null) {
249
- return v?.get ? v.get(this.field, dval) : dval;
309
+ return readKey(v, this.field, dval);
250
310
  }
251
311
  setValue(root, v) {
252
- return root.set(this.field, v);
312
+ return (0, immer_exports.produce)(root, (draft) => this.setDraftValue(draft, v));
313
+ }
314
+ setDraftValue(root, v) {
315
+ writeKey(root, this.field, v);
253
316
  }
254
317
  withIndex(i) {
255
318
  return new SeqStep(this.field, i);
@@ -268,12 +331,14 @@ var SeqStep = class extends Step {
268
331
  this.key = key;
269
332
  }
270
333
  lookup(v, dval = null) {
271
- const o = v?.get(this.field, null);
272
- return o?.get ? o.get(this.key, dval) : dval;
334
+ return readSeqKey(readKey(v, this.field, null), this.key, dval);
273
335
  }
274
336
  setValue(root, v) {
275
- const seq = root?.get(this.field, null);
276
- return seq ? root.set(this.field, seq.set(this.key, v)) : root;
337
+ return (0, immer_exports.produce)(root, (draft) => this.setDraftValue(draft, v));
338
+ }
339
+ setDraftValue(root, v) {
340
+ const seq = readKey(root, this.field, null);
341
+ if (seq != null) writeSeqKey(seq, this.key, v);
277
342
  }
278
343
  enterFrame(stack, _prev, next) {
279
344
  return stack.enter(next, { key: this.key }, true);
@@ -289,19 +354,22 @@ var SeqAccessStep = class extends Step {
289
354
  this.keyField = keyField;
290
355
  }
291
356
  lookup(v, dval = null) {
292
- const seq = v?.get(this.seqField, NONE);
293
- const key = v?.get(this.keyField, NONE);
294
- return key !== NONE && seq?.get ? seq.get(key, dval) : dval;
357
+ const seq = readKey(v, this.seqField, NONE);
358
+ const key = readKey(v, this.keyField, NONE);
359
+ return key !== NONE && seq !== NONE ? readSeqKey(seq, key, dval) : dval;
295
360
  }
296
361
  setValue(root, v) {
297
- const seq = root?.get(this.seqField, NONE);
298
- const key = root?.get(this.keyField, NONE);
299
- return seq === NONE || key === NONE ? root : root.set(this.seqField, seq.set(key, v));
362
+ return (0, immer_exports.produce)(root, (draft) => this.setDraftValue(draft, v));
363
+ }
364
+ setDraftValue(root, v) {
365
+ const seq = readKey(root, this.seqField, NONE);
366
+ const key = readKey(root, this.keyField, NONE);
367
+ if (seq !== NONE && key !== NONE) writeSeqKey(seq, key, v);
300
368
  }
301
369
  // Resolve `keyField` against `v` now and freeze it as a literal-key `SeqStep`, so a
302
370
  // later lookup/setValue lands on this same item even if `keyField` changes meanwhile.
303
371
  pinKey(v) {
304
- const key = v?.get(this.keyField, NONE);
372
+ const key = readKey(v, this.keyField, NONE);
305
373
  return key === NONE ? this : new SeqStep(this.seqField, key);
306
374
  }
307
375
  // The key is a *field reference* resolved live, so it is unknown without a value;
@@ -482,19 +550,15 @@ var Path = class _Path {
482
550
  return out;
483
551
  }
484
552
  setValue(root, v) {
485
- const intermediates = new Array(this.steps.length);
486
- let curVal = root;
487
- for (let i = 0; i < this.steps.length; i++) {
488
- intermediates[i] = curVal;
489
- curVal = this.steps[i].lookup(curVal, NONE);
490
- if (curVal === NONE) return root;
491
- }
492
- let newVal = v;
493
- for (let i = this.steps.length - 1; i >= 0; i--) {
494
- newVal = this.steps[i].setValue(intermediates[i], newVal);
495
- intermediates[i] = newVal;
496
- }
497
- return newVal;
553
+ if (this.steps.length === 0) return v;
554
+ return (0, immer_exports.produce)(root, (draft) => {
555
+ let parent = draft;
556
+ for (let i = 0; i < this.steps.length - 1; i++) {
557
+ parent = this.steps[i].lookup(parent, NONE);
558
+ if (parent === NONE) return;
559
+ }
560
+ this.steps.at(-1).setDraftValue(parent, v);
561
+ });
498
562
  }
499
563
  buildStack(stack) {
500
564
  let prev = stack.it;
@@ -691,7 +755,8 @@ function sizeOf(v) {
691
755
  const s = v.size;
692
756
  if (typeof s === "number") return s;
693
757
  const l = v.length;
694
- return typeof l === "number" ? l : null;
758
+ if (typeof l === "number") return l;
759
+ return isPlainObject(v) ? Object.keys(v).length : null;
695
760
  }
696
761
  var predTruthy = (v) => {
697
762
  const n = sizeOf(v);
@@ -702,7 +767,7 @@ var PREDICATES = {
702
767
  "truthy?": { name: "truthy?", arity: 1, fn: predTruthy },
703
768
  "falsy?": { name: "falsy?", arity: 1, fn: (v) => !predTruthy(v) },
704
769
  "null?": { name: "null?", arity: 1, fn: (v) => v == null },
705
- "equals?": { name: "equals?", arity: 2, fn: (a, b) => is(a, b) }
770
+ "equals?": { name: "equals?", arity: 2, fn: (a, b) => Object.is(a, b) }
706
771
  };
707
772
  function parseToken(s, px) {
708
773
  const c0 = s.charCodeAt(0);
@@ -787,21 +852,29 @@ function parseProvide(s, px) {
787
852
  function parseMacroAttr(s, px) {
788
853
  return _parseSingle(s, px, G_ALL);
789
854
  }
790
- function parseInputHandler(s, px) {
791
- return _parseHandler(s, px, "input", true, true);
855
+ function parseReceiveHandler(s, px) {
856
+ return _parseHandler(s, px, "receive", true, true, false);
792
857
  }
793
858
  function parseAlterHandler(s, px) {
794
- const r = _parseHandler(s, px, "alter", false, false);
859
+ const r = _parseHandler(s, px, "alter", false, false, true);
795
860
  return r === null ? null : r.handlerVal;
796
861
  }
797
- function _parseHandler(s, px, namespace, allowArgs, report) {
862
+ function _parseHandler(s, px, namespace, allowArgs, report, allowMethod) {
798
863
  const tokens = tokenizeValue(s.trim());
799
864
  const headTok = tokens[0] ?? "";
800
865
  const head = headTok === "" ? null : parseToken(headTok, px);
801
866
  const hk = kindOf(head);
802
867
  let handlerVal;
803
- if (hk & K_METHOD) handlerVal = head;
804
- else if (hk & K_NAME) handlerVal = new HandlerNameVal(head.name, namespace);
868
+ if (hk & K_METHOD && allowMethod) handlerVal = head;
869
+ else if (hk & K_METHOD) {
870
+ if (report)
871
+ px.onParseIssue("event-method-handler", {
872
+ name: head.name,
873
+ role: "handler-name",
874
+ value: headTok
875
+ });
876
+ return null;
877
+ } else if (hk & K_NAME) handlerVal = new HandlerNameVal(head.name, namespace);
805
878
  else {
806
879
  if (report) px.onParseIssue("bad-value", { role: "handler-name", value: headTok });
807
880
  return null;
@@ -1015,7 +1088,7 @@ var BindMemberVal = class extends BindVal {
1015
1088
  }
1016
1089
  eval(stack) {
1017
1090
  const v = stack.lookupBind(this.name);
1018
- return typeof v?.get === "function" ? v.get(this.member, null) : v?.[this.member] ?? null;
1091
+ return seqGet(v, this.member, null);
1019
1092
  }
1020
1093
  toString() {
1021
1094
  return `@${this.name}.${this.member}`;
@@ -1062,7 +1135,7 @@ var SeqAccessVal = class extends RenderVal {
1062
1135
  }
1063
1136
  eval(stack) {
1064
1137
  const key = this.keyVal.eval(stack);
1065
- return this.seqVal.eval(stack)?.get(key, null);
1138
+ return seqGet(this.seqVal.eval(stack), key, null);
1066
1139
  }
1067
1140
  toString() {
1068
1141
  return `${this.seqVal}[${this.keyVal}]`;
@@ -1331,20 +1404,17 @@ var EventHandler = class _EventHandler {
1331
1404
  return [this.handlerVal.evalAsHandler(stack), argValues];
1332
1405
  }
1333
1406
  static parse(s, px) {
1334
- const r = parseInputHandler(s, px);
1407
+ const r = parseReceiveHandler(s, px);
1335
1408
  return r === null ? null : new _EventHandler(r.handlerVal, r.args);
1336
1409
  }
1337
1410
  };
1338
- var RequestHandler = class {
1411
+ var IntentHandler = class {
1339
1412
  constructor(name, fn) {
1340
1413
  this.name = name;
1341
1414
  this.fn = fn;
1342
1415
  }
1343
1416
  };
1344
1417
 
1345
- // src/renderer.js
1346
- import { isIndexed, isKeyed } from "immutable";
1347
-
1348
1418
  // src/cache.js
1349
1419
  var isWeakKey = (k) => k !== null && (typeof k === "object" || typeof k === "function");
1350
1420
  var NullDomCache = class {
@@ -1841,7 +1911,7 @@ var Renderer = class {
1841
1911
  this.cache.set(cachePath, cacheKey, dom);
1842
1912
  }
1843
1913
  };
1844
- if (keys) imKeysIter(seq, renderOne, keys);
1914
+ if (keys) keysIter(seq, renderOne, keys);
1845
1915
  else
1846
1916
  getSeqInfo(seq)(
1847
1917
  seq,
@@ -1875,7 +1945,7 @@ var Renderer = class {
1875
1945
  return new VFragment([this._renderMetadata({ $: "Scope", nid }), dom]);
1876
1946
  }
1877
1947
  };
1878
- var getSeqInfo = (seq) => isIndexed(seq) ? imIndexedIter : isKeyed(seq) ? imKeyedIter : seq?.[SEQ_INFO] ?? unkIter;
1948
+ var getSeqInfo = (seq) => isIndexedSeq(seq) ? nativeIndexedIter : isKeyedSeq(seq) || isSetSeq(seq) ? nativeKeyedIter : seq?.[SEQ_INFO] ?? unkIter;
1879
1949
  var normalizeRange = (start, end, size) => {
1880
1950
  let s = start == null ? 0 : start < 0 ? size + start : start;
1881
1951
  let e = end == null ? size : end < 0 ? size + end : end;
@@ -1898,22 +1968,22 @@ var unpackLoopResult = (result, seq) => {
1898
1968
  const r = result ?? {};
1899
1969
  return { iterData: r.iterData ?? { seq }, start: r.start, end: r.end, keys: r.keys };
1900
1970
  };
1901
- var imKeysIter = (seq, visit, keys) => {
1902
- const attrName = isIndexed(seq) ? "si" : "sk";
1903
- for (const key of keys) visit(key, seq.get(key), attrName);
1971
+ var keysIter = (seq, visit, keys) => {
1972
+ const attrName = isIndexedSeq(seq) ? "si" : "sk";
1973
+ for (const key of keys) visit(key, seqGet(seq, key), attrName);
1904
1974
  };
1905
1975
  var makeLoopCtx = (stack, filter) => ({
1906
1976
  lookup: (name) => stack.lookupBind(name),
1907
1977
  filter: (key, value, iterData) => filter.call(stack.it, key, value, iterData)
1908
1978
  });
1909
- var imIndexedIter = (seq, visit, start, end) => {
1910
- const [s, e] = normalizeRange(start, end, seq.size);
1911
- for (let i = s; i < e; i++) visit(i, seq.get(i), "si");
1979
+ var nativeIndexedIter = (seq, visit, start, end) => {
1980
+ const [s, e] = normalizeRange(start, end, seqSize(seq));
1981
+ for (let i = s; i < e; i++) visit(i, seq[i], "si");
1912
1982
  };
1913
- var imKeyedIter = (seq, visit, start, end) => {
1914
- const [s, e] = normalizeRange(start, end, seq.size);
1983
+ var nativeKeyedIter = (seq, visit, start, end) => {
1984
+ const [s, e] = normalizeRange(start, end, seqSize(seq));
1915
1985
  let i = 0;
1916
- for (const [k, v] of seq.toSeq().entries()) {
1986
+ for (const [k, v] of seqEntries(seq)) {
1917
1987
  if (i >= e) break;
1918
1988
  if (i >= s) visit(k, v, "sk");
1919
1989
  i++;
@@ -2433,7 +2503,7 @@ var IterInfo = class {
2433
2503
  // { key, value }, then run @enrich-with (with @loop-with's iterData) if any.
2434
2504
  enrichBinds(stack, key) {
2435
2505
  const { seq, filter, loopWith, enricher } = this.eval(stack);
2436
- const value = seq?.get ? seq.get(key, null) : null;
2506
+ const value = seqGet(seq, key, null);
2437
2507
  const binds = { key, value };
2438
2508
  if (enricher) {
2439
2509
  const { iterData } = unpackLoopResult(
@@ -5031,7 +5101,7 @@ var KNOWN_COMPONENT_SPEC_KEYS = new Set(
5031
5101
  )
5032
5102
  );
5033
5103
  var EMPTY_SET = /* @__PURE__ */ new Set();
5034
- var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["requestOverridesField"]);
5104
+ var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["intentOverridesField"]);
5035
5105
  var KNOWN_DIRECTIVE_NAMES = /* @__PURE__ */ new Set([
5036
5106
  "dangerouslysetinnerhtml",
5037
5107
  "slot",
@@ -5056,11 +5126,11 @@ var LOOKUP_TARGET_MALFORMED = "LOOKUP_TARGET_MALFORMED";
5056
5126
  var RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP";
5057
5127
  var UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER";
5058
5128
  var UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME";
5059
- var INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED";
5060
- var INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED";
5061
- var INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED";
5062
- var INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD";
5063
- var INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER";
5129
+ var RECEIVE_HANDLER_NOT_IMPLEMENTED = "RECEIVE_HANDLER_NOT_IMPLEMENTED";
5130
+ var EVENT_HANDLER_METHOD_NOT_ALLOWED = "EVENT_HANDLER_METHOD_NOT_ALLOWED";
5131
+ var RETIRED_HANDLER_BUCKET = "RETIRED_HANDLER_BUCKET";
5132
+ var RETIRED_CTX_VERB = "RETIRED_CTX_VERB";
5133
+ var HANDLER_NAME_COLLISION = "HANDLER_NAME_COLLISION";
5064
5134
  var FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED";
5065
5135
  var FIELD_VAL_IS_METHOD = "FIELD_VAL_IS_METHOD";
5066
5136
  var METHOD_VAL_NOT_DEFINED = "METHOD_VAL_NOT_DEFINED";
@@ -5088,8 +5158,7 @@ var COMP_FIELD_BAD_SHAPE = "COMP_FIELD_BAD_SHAPE";
5088
5158
  var ASYNC_HANDLER = "ASYNC_HANDLER";
5089
5159
  var TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE = "TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE";
5090
5160
  var GLOBAL_SELECTOR_IN_SCOPED_STYLE = "GLOBAL_SELECTOR_IN_SCOPED_STYLE";
5091
- var FIELD_NAME_RESERVED_BY_RECORD = "FIELD_NAME_RESERVED_BY_RECORD";
5092
- var RECORD_FIELD_NAME_COLLISIONS = /* @__PURE__ */ new Set(["entries", "hashCode"]);
5161
+ var FIELD_METHOD_NAME_COLLISION = "FIELD_METHOD_NAME_COLLISION";
5093
5162
  var X_KNOWN_OP_NAMES = /* @__PURE__ */ new Set([
5094
5163
  "slot",
5095
5164
  "text",
@@ -5112,7 +5181,8 @@ var PARSE_ISSUES = {
5112
5181
  candidates: X_KNOWN_ATTR_NAMES,
5113
5182
  atPrefix: X_KNOWN_ATTR_NAMES
5114
5183
  },
5115
- "bad-value": { id: BAD_VALUE }
5184
+ "bad-value": { id: BAD_VALUE },
5185
+ "event-method-handler": { id: EVENT_HANDLER_METHOD_NOT_ALLOWED }
5116
5186
  };
5117
5187
  function protoHasMethod(proto, name) {
5118
5188
  return protoMethodValue(proto, name) !== null;
@@ -5174,10 +5244,13 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
5174
5244
  return lx.push({ componentName: Comp.name }, () => {
5175
5245
  checkUnknownSpecKeys(lx, Comp, wellKnownExtras);
5176
5246
  checkFieldDeclarations(lx, Comp);
5177
- checkRecordFieldNameCollisions(lx, Comp);
5247
+ checkFieldMethodNameCollisions(lx, Comp);
5178
5248
  checkProvidesAreAddressable(lx, Comp);
5179
5249
  checkLookupShapes(lx, Comp);
5180
5250
  checkHandlersNotAsync(lx, Comp);
5251
+ checkRetiredBuckets(lx, Comp);
5252
+ checkRetiredCtxVerbs(lx, Comp);
5253
+ checkHandlerNameCollisions(lx, Comp);
5181
5254
  checkScopedStyleTopLevel(lx, Comp);
5182
5255
  const referencedAlters = /* @__PURE__ */ new Set();
5183
5256
  const referencedInputs = /* @__PURE__ */ new Set();
@@ -5191,7 +5264,6 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
5191
5264
  );
5192
5265
  }
5193
5266
  checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
5194
- checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
5195
5267
  checkUnreferencedDynamics(lx, Comp, referencedDynamics);
5196
5268
  return lx;
5197
5269
  });
@@ -5235,6 +5307,14 @@ function checkParseIssues(lx, view) {
5235
5307
  lx.warn(X_OP_IGNORES_CHILDREN, info, { kind: "remove", what: "the ignored children" });
5236
5308
  continue;
5237
5309
  }
5310
+ if (kind === "event-method-handler") {
5311
+ lx.error(EVENT_HANDLER_METHOD_NOT_ALLOWED, info, {
5312
+ kind: "rephrase",
5313
+ from: info.value,
5314
+ text: `Move '${info.name}' to receive and reference it as '${info.name}'.`
5315
+ });
5316
+ continue;
5317
+ }
5238
5318
  const rule = PARSE_ISSUES[kind];
5239
5319
  if (!rule) continue;
5240
5320
  const id = rule.id;
@@ -5461,7 +5541,7 @@ function mkAttrValEnv(Comp, referencedAlters, referencedDynamics) {
5461
5541
  return { fields, proto, scope, alter, referencedAlters, dynamicMap, referencedDynamics };
5462
5542
  }
5463
5543
  function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
5464
- const { input, views, Class } = Comp;
5544
+ const { receive: input, views, Class } = Comp;
5465
5545
  const { prototype: proto } = Class;
5466
5546
  for (const viewName in views) {
5467
5547
  lx.push({ viewName }, () => {
@@ -5469,44 +5549,22 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
5469
5549
  for (const event of view.ctx.events) {
5470
5550
  for (const handler of event.handlers) {
5471
5551
  const { handlerVal } = handler.handlerCall;
5472
- const hvName = handlerVal?.constructor.name;
5473
5552
  const eventName = handler.name;
5474
5553
  const originAttr = `@on.${eventName}`;
5475
- if (hvName === "HandlerNameVal") {
5554
+ if (handlerVal?.constructor.name === "HandlerNameVal") {
5476
5555
  referencedInputs?.add(handlerVal.name);
5477
5556
  const { name } = handlerVal;
5478
5557
  if (input[name] === void 0) {
5479
- const isMethodFix = protoHasMethod(proto, name);
5558
+ const suggestion = protoHasMethod(proto, name) ? {
5559
+ kind: "rephrase",
5560
+ from: name,
5561
+ text: `Move '${name}' from methods to receive; event handlers are receive handlers.`
5562
+ } : replaceNameSuggestion(name, Object.keys(input));
5480
5563
  lx.error(
5481
- INPUT_HANDLER_NOT_IMPLEMENTED,
5564
+ RECEIVE_HANDLER_NOT_IMPLEMENTED,
5482
5565
  { name, handler, event, eventName, originAttr },
5483
- isMethodFix ? { kind: "add-prefix", from: name, to: `$${name}` } : replaceNameSuggestion(name, Object.keys(input))
5566
+ suggestion
5484
5567
  );
5485
- if (isMethodFix) {
5486
- lx.hint(
5487
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
5488
- { name, handler, event, eventName, originAttr },
5489
- { kind: "add-prefix", from: name, to: `$${name}` }
5490
- );
5491
- }
5492
- }
5493
- } else if (hvName === "MethodVal") {
5494
- referencedInputs?.add(handlerVal.name);
5495
- const { name } = handlerVal;
5496
- if (!protoHasMethod(proto, name)) {
5497
- const isInputFix = input[name] !== void 0;
5498
- lx.error(
5499
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
5500
- { name, handler, event, eventName, originAttr },
5501
- isInputFix ? { kind: "drop-prefix", from: `$${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto))
5502
- );
5503
- if (isInputFix) {
5504
- lx.hint(
5505
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
5506
- { name, handler, event, eventName, originAttr },
5507
- { kind: "drop-prefix", from: `$${name}`, to: name }
5508
- );
5509
- }
5510
5568
  }
5511
5569
  }
5512
5570
  }
@@ -5610,10 +5668,10 @@ var ATTR_VAL_CHECKERS = {
5610
5668
  }
5611
5669
  };
5612
5670
  function checkConsistentAttrVal(lx, val, env, skipNameVal = false, errCtx = null, isRoot = true) {
5613
- const check2 = ATTR_VAL_CHECKERS[val?.constructor.name];
5614
- if (check2 === void 0) return;
5671
+ const check3 = ATTR_VAL_CHECKERS[val?.constructor.name];
5672
+ if (check3 === void 0) return;
5615
5673
  const recurse = (sub) => checkConsistentAttrVal(lx, sub, env, skipNameVal, errCtx, false);
5616
- check2({ lx, val, env, errCtx, skipNameVal, recurse, isRoot });
5674
+ check3({ lx, val, env, errCtx, skipNameVal, recurse, isRoot });
5617
5675
  }
5618
5676
  var NODE_KIND_TO_CTX = {
5619
5677
  RenderTextNode: { originAttr: "<x text>" },
@@ -5751,12 +5809,83 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedDynamics) {
5751
5809
  });
5752
5810
  }
5753
5811
  }
5812
+ var isPlainObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
5813
+ var RETIRED_BUCKETS = {
5814
+ input: {
5815
+ to: "receive",
5816
+ how: "rename the bucket to `receive` - a view's own @on.* name and a parent's ctx.send are the same message now, and nothing can tell them apart"
5817
+ },
5818
+ bubble: {
5819
+ to: "intent",
5820
+ how: "rename the bucket to `intent` - and note the walk changed: a handler that calls ctx.reply() ENDS it, where ctx.stopPropagation() used to be the only way to stop"
5821
+ },
5822
+ response: {
5823
+ to: "receive",
5824
+ how: "split each arm into `receive` arms named after the intent: <name>Ok takes the result alone, <name>Error takes the error alone, and the optional <name>Unhandled means nothing claimed it"
5825
+ }
5826
+ };
5827
+ function checkRetiredBuckets(lx, Comp) {
5828
+ const extra = Comp.extra;
5829
+ if (!extra) return;
5830
+ for (const key of Object.keys(extra)) {
5831
+ if (!Object.hasOwn(RETIRED_BUCKETS, key)) continue;
5832
+ const retired = RETIRED_BUCKETS[key];
5833
+ const names = isPlainObj(extra[key]) ? Object.keys(extra[key]) : [];
5834
+ lx.error(RETIRED_HANDLER_BUCKET, { key, to: retired.to, how: retired.how, names });
5835
+ }
5836
+ }
5837
+ var RETIRED_CTX_VERBS = {
5838
+ bubble: 'ctx.intent(name, args, { route: ["dyn"] }) - the route says it walks the ancestors, where the verb used to',
5839
+ request: 'ctx.intent(name, args, { route: ["lex"] }) - and the outcome comes back as a `receive` arm named <name>Ok / <name>Error / <name>Unhandled, so onOkName and onErrorName are gone',
5840
+ stopPropagation: "ctx.stop()",
5841
+ inputAtPath: "ctx.sendAtPath(path, name, args)",
5842
+ requestAtPath: 'ctx.intentAtPath(path, name, args, { route: ["lex"] })'
5843
+ };
5844
+ var RETIRED_CTX_RE = new RegExp(
5845
+ `\\bctx\\.(${Object.keys(RETIRED_CTX_VERBS).join("|")})\\s*\\(`,
5846
+ "g"
5847
+ );
5848
+ function checkRetiredCtxVerbs(lx, Comp) {
5849
+ for (const bucket of ["receive", "intent", "alter", "methods", "statics"]) {
5850
+ const block = bucket === "methods" || bucket === "statics" ? Comp.spec?.[bucket] : Comp[bucket];
5851
+ if (!isPlainObj(block)) continue;
5852
+ for (const name in block) {
5853
+ const fn = block[name];
5854
+ if (typeof fn !== "function") continue;
5855
+ const src = String(fn);
5856
+ for (const m of src.matchAll(RETIRED_CTX_RE)) {
5857
+ const verb = m[1];
5858
+ lx.error(RETIRED_CTX_VERB, { verb, to: RETIRED_CTX_VERBS[verb], bucket, name });
5859
+ }
5860
+ }
5861
+ }
5862
+ }
5863
+ function checkHandlerNameCollisions(lx, Comp) {
5864
+ const receive = Comp.receive ?? {};
5865
+ const intent = Comp.intent ?? {};
5866
+ for (const name in intent)
5867
+ if (receive[name] !== void 0)
5868
+ lx.error(HANDLER_NAME_COLLISION, {
5869
+ name,
5870
+ problem: `"${name}" is declared in both \`receive\` and \`intent\``,
5871
+ fix: "one is addressed and one is routed; give them different names so a reader can tell which a call site meant"
5872
+ });
5873
+ for (const name in receive)
5874
+ for (const suffix of ["Ok", "Error", "Unhandled"])
5875
+ if (receive[name + suffix] !== void 0 && intent[name] === void 0)
5876
+ lx.warn(HANDLER_NAME_COLLISION, {
5877
+ name,
5878
+ problem: `\`receive\` declares both "${name}" and its answer name "${name + suffix}"`,
5879
+ fix: `an intent named "${name}" would dispatch its answer to "${name + suffix}" - rename one unless that is what you meant`
5880
+ });
5881
+ }
5754
5882
  function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
5755
5883
  const extra = Comp.extra;
5756
5884
  if (!extra) return;
5757
5885
  let candidates = null;
5758
5886
  for (const key of Object.keys(extra)) {
5759
5887
  if (FRAMEWORK_WELL_KNOWN_EXTRAS.has(key) || wellKnownExtras.has(key)) continue;
5888
+ if (Object.hasOwn(RETIRED_BUCKETS, key)) continue;
5760
5889
  candidates ??= [
5761
5890
  ...KNOWN_COMPONENT_SPEC_KEYS,
5762
5891
  ...FRAMEWORK_WELL_KNOWN_EXTRAS,
@@ -5788,14 +5917,11 @@ function checkFieldDeclarations(lx, Comp) {
5788
5917
  }
5789
5918
  }
5790
5919
  }
5791
- function checkRecordFieldNameCollisions(lx, Comp) {
5792
- const fields = Comp.Class?.getMetaClass?.().fields;
5793
- if (!fields) return;
5794
- for (const name in fields) {
5795
- if (RECORD_FIELD_NAME_COLLISIONS.has(name)) {
5796
- lx.error(FIELD_NAME_RESERVED_BY_RECORD, { name });
5797
- }
5798
- }
5920
+ function checkFieldMethodNameCollisions(lx, Comp) {
5921
+ const meta = Comp.Class?.getMetaClass?.();
5922
+ if (!meta) return;
5923
+ for (const name in meta.fields)
5924
+ if (Object.hasOwn(meta.methods, name)) lx.error(FIELD_METHOD_NAME_COLLISION, { name });
5799
5925
  }
5800
5926
  function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
5801
5927
  for (const name in Comp.alter) {
@@ -5804,15 +5930,8 @@ function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
5804
5930
  }
5805
5931
  }
5806
5932
  }
5807
- function checkUnreferencedInputHandlers(lx, Comp, referencedInputs) {
5808
- for (const name in Comp.input) {
5809
- if (!referencedInputs.has(name)) {
5810
- lx.hint(INPUT_HANDLER_NOT_REFERENCED, { name });
5811
- }
5812
- }
5813
- }
5814
- var HANDLER_CHANNELS = ["input", "receive", "bubble", "response", "alter"];
5815
- var ASYNC_HANDLER_HELP = "Move the async work into a request handler and trigger it with ctx.request('name', args), then handle the result in a synchronous response handler. To coordinate other components, keep the handler synchronous and use ctx.send to deliver a message or ctx.bubble to raise an event.";
5933
+ var HANDLER_CHANNELS = ["receive", "intent", "alter"];
5934
+ var ASYNC_HANDLER_HELP = `Move the async work into a scope-registered intent handler and trigger it with ctx.intent("name", args, { route: ["lex"] }), then read the outcome in the synchronous \`receive\` arms named after it: <name>Ok, <name>Error and <name>Unhandled. To coordinate other components, keep the handler synchronous and use ctx.send to address one, or ctx.intent to let a route find one.`;
5816
5935
  function checkHandlersNotAsync(lx, Comp) {
5817
5936
  for (const channel of HANDLER_CHANNELS) {
5818
5937
  const block = Comp[channel];
@@ -6008,8 +6127,11 @@ var Components = class {
6008
6127
  getHandlerFor(v, name, key) {
6009
6128
  return this.getCompFor(v)?.[key][name] ?? null;
6010
6129
  }
6011
- getRequestFor(v, name) {
6012
- return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
6130
+ // The `lex` leg of an intent's route: the handlers registered on the scope chain of
6131
+ // the component that raised it, innermost first. A list, not a first match — see
6132
+ // ComponentStack.lookupIntentChain.
6133
+ getIntentChainFor(v, name) {
6134
+ return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
6013
6135
  }
6014
6136
  compileStyles() {
6015
6137
  const styles = [];
@@ -6022,7 +6144,7 @@ var ComponentStack = class _ComponentStack {
6022
6144
  this.comps = comps;
6023
6145
  this.parent = parent;
6024
6146
  this.byName = {};
6025
- this.reqsByName = {};
6147
+ this.intentsByName = {};
6026
6148
  this.macros = {};
6027
6149
  }
6028
6150
  enter() {
@@ -6054,11 +6176,21 @@ var ComponentStack = class _ComponentStack {
6054
6176
  getCompFor(v) {
6055
6177
  return this.comps.getCompFor(v);
6056
6178
  }
6057
- registerRequestHandlers(handlers) {
6058
- for (const name in handlers) this.reqsByName[name] = new RequestHandler(name, handlers[name]);
6179
+ // Handlers that answer an intent's `lex` leg. A name maps to a LIST, because the leg
6180
+ // walks: a handler that returns PASS declines and hands the intent to the next one.
6181
+ // A bare function is accepted as a one-element list.
6182
+ registerIntentHandlers(handlers) {
6183
+ for (const name in handlers) {
6184
+ const fns = Array.isArray(handlers[name]) ? handlers[name] : [handlers[name]];
6185
+ this.intentsByName[name] = fns.map((fn) => new IntentHandler(name, fn));
6186
+ }
6059
6187
  }
6060
- lookupRequest(name) {
6061
- return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
6188
+ // Innermost scope first, then outward. Concatenated rather than first-match, so a
6189
+ // declining handler can hand the intent to one further up.
6190
+ lookupIntentChain(name) {
6191
+ const here = this.intentsByName[name] ?? [];
6192
+ const up = this.parent?.lookupIntentChain(name) ?? [];
6193
+ return up.length === 0 ? here : here.concat(up);
6062
6194
  }
6063
6195
  lookupComponent(name) {
6064
6196
  return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
@@ -6089,7 +6221,7 @@ var LookupInfo = class {
6089
6221
  }
6090
6222
  };
6091
6223
  var isString = (v) => typeof v === "string";
6092
- var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
6224
+ var _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
6093
6225
  var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
6094
6226
  var _compId = 0;
6095
6227
  var Component = class {
@@ -6100,10 +6232,8 @@ var Component = class {
6100
6232
  this.views = { main: new View("main", o.view, o.style) };
6101
6233
  this.commonStyle = o.commonStyle ?? "";
6102
6234
  this.globalStyle = o.globalStyle ?? "";
6103
- this.input = o.input ?? {};
6104
6235
  this.receive = o.receive ?? {};
6105
- this.bubble = o.bubble ?? {};
6106
- this.response = o.response ?? {};
6236
+ this.intent = o.intent ?? {};
6107
6237
  this.alter = o.alter ?? {};
6108
6238
  for (const name in o.views ?? {}) {
6109
6239
  const v = o.views[name];
@@ -6165,7 +6295,7 @@ var Component = class {
6165
6295
  };
6166
6296
 
6167
6297
  // src/on.js
6168
- var OP_KINDS = ["send", "bubble", "request", "input"];
6298
+ var OP_KINDS = ["send", "intent"];
6169
6299
  function phaseOps(phase) {
6170
6300
  const ops = [];
6171
6301
  for (const type of OP_KINDS) for (const a of phase[type] ?? []) ops.push({ type, ...a });
@@ -6175,11 +6305,6 @@ function phaseOps(phase) {
6175
6305
  function resolveArgs(args, self) {
6176
6306
  return typeof args === "function" ? args(self) ?? [] : args ?? [];
6177
6307
  }
6178
- function phaseHasBubble(phase) {
6179
- if (!phase) return false;
6180
- if (phase.bubble?.length) return true;
6181
- return (phase.do ?? []).some((op) => op.type === "bubble");
6182
- }
6183
6308
  function dispatchPhase(dispatcher, targetPath, phase, self) {
6184
6309
  if (!phase) return;
6185
6310
  for (const op of phaseOps(phase)) {
@@ -6188,23 +6313,323 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
6188
6313
  case "send":
6189
6314
  dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
6190
6315
  break;
6191
- case "bubble":
6192
- dispatcher.sendAtPath(targetPath, op.name, args, {
6193
- skipSelf: true,
6194
- bubbles: true,
6195
- ...op.opts
6196
- });
6197
- break;
6198
- case "request":
6199
- dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
6200
- break;
6201
- case "input":
6202
- dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
6316
+ case "intent":
6317
+ dispatcher.intentAtPath(targetPath, op.name, args, op.opts);
6203
6318
  break;
6204
6319
  }
6205
6320
  }
6206
6321
  }
6207
6322
 
6323
+ // src/oo.js
6324
+ var BAD_VALUE2 = /* @__PURE__ */ Symbol("BadValue");
6325
+ var nullCoercer = (v) => v;
6326
+ var isPlainObject2 = (v) => {
6327
+ if (v === null || typeof v !== "object") return false;
6328
+ const proto = Object.getPrototypeOf(v);
6329
+ return proto === Object.prototype || proto === null;
6330
+ };
6331
+ function defaultToData(v) {
6332
+ if (v instanceof Map) return [...v.entries()];
6333
+ if (v instanceof Set) return [...v.values()];
6334
+ return v;
6335
+ }
6336
+ var Field = class {
6337
+ constructor(type, name, typeCheck, coercer, defaultValue = null) {
6338
+ this.type = type;
6339
+ this.name = name;
6340
+ this.typeCheck = typeCheck;
6341
+ this.coercer = coercer;
6342
+ this.checks = [];
6343
+ this.defaultValue = defaultValue;
6344
+ }
6345
+ toDataDef() {
6346
+ return { type: this.type, defaultValue: defaultToData(this.defaultValue) };
6347
+ }
6348
+ getFirstFailingCheck(v) {
6349
+ if (!this.typeCheck.isValid(v)) return this.typeCheck;
6350
+ for (const check3 of this.checks) if (!check3.isValid(v)) return check3;
6351
+ return null;
6352
+ }
6353
+ isValid(v) {
6354
+ return this.getFirstFailingCheck(v) === null;
6355
+ }
6356
+ addCheck(check3) {
6357
+ this.checks.push(check3);
6358
+ return this;
6359
+ }
6360
+ coerceOr(v, defaultValue = null) {
6361
+ if (this.isValid(v)) return v;
6362
+ const v1 = this.coercer(v);
6363
+ return this.isValid(v1) ? v1 : defaultValue;
6364
+ }
6365
+ coerceOrDefault(v) {
6366
+ return this.coerceOr(v, this.defaultValue);
6367
+ }
6368
+ };
6369
+ var Check = class {
6370
+ isValid(_v) {
6371
+ return true;
6372
+ }
6373
+ getMessage(_v) {
6374
+ return "Invalid";
6375
+ }
6376
+ };
6377
+ var CheckTypeAny = class extends Check {
6378
+ };
6379
+ var CHECK_TYPE_ANY = new CheckTypeAny();
6380
+ var FnCheck = class extends Check {
6381
+ constructor(isValidFn, getMessageFn) {
6382
+ super();
6383
+ this._isValid = isValidFn;
6384
+ this._getMessage = getMessageFn;
6385
+ }
6386
+ isValid(v) {
6387
+ return this._isValid(v);
6388
+ }
6389
+ getMessage(v) {
6390
+ return this._getMessage(v);
6391
+ }
6392
+ };
6393
+ var check = (fn, message) => new FnCheck(fn, () => message);
6394
+ var CHECK_TYPE_INT = check(Number.isInteger, "Integer expected");
6395
+ var CHECK_TYPE_FLOAT = check(Number.isFinite, "Float expected");
6396
+ var CHECK_TYPE_BOOL = check((v) => typeof v === "boolean", "Boolean expected");
6397
+ var CHECK_TYPE_STRING = check((v) => typeof v === "string", "String expected");
6398
+ var CHECK_TYPE_LIST = check(Array.isArray, "Array expected");
6399
+ var CHECK_TYPE_OBJECT = check(isPlainObject2, "Plain object expected");
6400
+ var CHECK_TYPE_MAP = check((v) => v instanceof Map, "Map expected");
6401
+ var CHECK_TYPE_SET = check((v) => v instanceof Set, "Set expected");
6402
+ var FieldBool = class extends Field {
6403
+ constructor(name, defaultValue = false) {
6404
+ super("bool", name, CHECK_TYPE_BOOL, (v) => !!v, defaultValue);
6405
+ }
6406
+ };
6407
+ var FieldAny = class extends Field {
6408
+ constructor(name, defaultValue = null) {
6409
+ super("any", name, CHECK_TYPE_ANY, nullCoercer, defaultValue);
6410
+ }
6411
+ toDataDef() {
6412
+ return { type: getTypeName(this.defaultValue) ?? "any", defaultValue: this.defaultValue };
6413
+ }
6414
+ };
6415
+ var FieldString = class extends Field {
6416
+ constructor(name, defaultValue = "") {
6417
+ super("text", name, CHECK_TYPE_STRING, (v) => v?.toString?.() ?? "", defaultValue);
6418
+ }
6419
+ };
6420
+ var FieldInt = class extends Field {
6421
+ constructor(name, defaultValue = 0) {
6422
+ super(
6423
+ "int",
6424
+ name,
6425
+ CHECK_TYPE_INT,
6426
+ (v) => Number.isFinite(v) ? Math.trunc(v) : null,
6427
+ defaultValue
6428
+ );
6429
+ }
6430
+ };
6431
+ var FieldFloat = class extends Field {
6432
+ constructor(name, defaultValue = 0) {
6433
+ super("float", name, CHECK_TYPE_FLOAT, (_) => null, defaultValue);
6434
+ }
6435
+ };
6436
+ var getTypeName = (v) => v?.constructor?.getMetaClass?.()?.name;
6437
+ var CheckTypeName = class {
6438
+ constructor(typeName) {
6439
+ this.typeName = typeName;
6440
+ }
6441
+ isValid(v) {
6442
+ return getTypeName(v) === this.typeName;
6443
+ }
6444
+ getMessage(v) {
6445
+ return `Expected "${this.typeName}", got "${getTypeName(v)}"`;
6446
+ }
6447
+ };
6448
+ var FieldComp = class extends Field {
6449
+ constructor(type, name, args) {
6450
+ super(type, name, new CheckTypeName(type), nullCoercer, null);
6451
+ this.args = args;
6452
+ }
6453
+ toDataDef() {
6454
+ return { component: this.type, args: this.args };
6455
+ }
6456
+ };
6457
+ var FieldList = class extends Field {
6458
+ constructor(name, defaultValue = []) {
6459
+ super("list", name, CHECK_TYPE_LIST, (v) => Array.isArray(v) ? [...v] : null, defaultValue);
6460
+ }
6461
+ };
6462
+ var FieldObject = class extends Field {
6463
+ constructor(name, defaultValue = {}) {
6464
+ super(
6465
+ "object",
6466
+ name,
6467
+ CHECK_TYPE_OBJECT,
6468
+ (v) => isPlainObject2(v) ? { ...v } : null,
6469
+ defaultValue
6470
+ );
6471
+ }
6472
+ };
6473
+ var FieldMap = class extends Field {
6474
+ constructor(name, defaultValue = /* @__PURE__ */ new Map()) {
6475
+ super(
6476
+ "map",
6477
+ name,
6478
+ CHECK_TYPE_MAP,
6479
+ (v) => {
6480
+ if (v instanceof Map) return new Map(v);
6481
+ if (Array.isArray(v) || isPlainObject2(v))
6482
+ return new Map(Array.isArray(v) ? v : Object.entries(v));
6483
+ return null;
6484
+ },
6485
+ defaultValue
6486
+ );
6487
+ }
6488
+ };
6489
+ var FieldSet = class extends Field {
6490
+ constructor(name, defaultValue = /* @__PURE__ */ new Set()) {
6491
+ super(
6492
+ "set",
6493
+ name,
6494
+ CHECK_TYPE_SET,
6495
+ (v) => v instanceof Set || Array.isArray(v) ? new Set(v) : null,
6496
+ defaultValue
6497
+ );
6498
+ }
6499
+ };
6500
+ function mkCompField(field, scope, args) {
6501
+ const Comp = scope?.lookupComponent(field.type) ?? null;
6502
+ if (Comp === null)
6503
+ console.warn(
6504
+ scope ? `component field "${field.name}": component "${field.type}" not found in scope` : `component field "${field.name}": cannot resolve component "${field.type}" — built without a registered scope (use ${field.type}.make({}) as the default, or build via a registered component)`
6505
+ );
6506
+ return Comp?.make({ ...field.args, ...args }, { scope }) ?? null;
6507
+ }
6508
+ var ClassBuilder = class {
6509
+ constructor(name) {
6510
+ this.name = name;
6511
+ this.fields = {};
6512
+ this.compFields = /* @__PURE__ */ new Set();
6513
+ this._methods = {};
6514
+ this._statics = {};
6515
+ }
6516
+ build() {
6517
+ const { name, fields, compFields, _methods } = this;
6518
+ const defaults = Object.fromEntries(
6519
+ Object.entries(fields).map(([fieldName, field]) => [fieldName, field.defaultValue])
6520
+ );
6521
+ const Class = {
6522
+ [name]: class {
6523
+ constructor(values = {}) {
6524
+ Object.assign(this, defaults, values);
6525
+ }
6526
+ }
6527
+ }[name];
6528
+ Class[immer_exports.immerable] = true;
6529
+ Object.assign(Class.prototype, _methods);
6530
+ const metaClass = { fields, name, methods: _methods };
6531
+ Object.assign(
6532
+ Class,
6533
+ {
6534
+ getMetaClass: () => metaClass,
6535
+ make(inArgs = {}, opts = {}) {
6536
+ const args = {};
6537
+ const scope = opts.scope ?? this.scope;
6538
+ for (const key in inArgs) {
6539
+ const field = fields[key];
6540
+ if (compFields.has(key)) args[key] = mkCompField(field, scope, inArgs[key]);
6541
+ else if (field === void 0)
6542
+ console.warn("extra argument to constructor:", name, key, inArgs);
6543
+ else args[key] = field.coerceOrDefault(inArgs[key]);
6544
+ }
6545
+ for (const key of compFields)
6546
+ if (args[key] === void 0) args[key] = mkCompField(fields[key], scope, inArgs[key]);
6547
+ return (0, immer_exports.freeze)(new this(args), true);
6548
+ }
6549
+ },
6550
+ this._statics
6551
+ );
6552
+ return Class;
6553
+ }
6554
+ methods(proto) {
6555
+ for (const k in proto) this._methods[k] = proto[k];
6556
+ }
6557
+ statics(proto) {
6558
+ for (const k in proto) this._statics[k] = proto[k];
6559
+ }
6560
+ addField(name, dval, FieldCls) {
6561
+ const field = new FieldCls(name, dval);
6562
+ this.fields[name] = field;
6563
+ return field;
6564
+ }
6565
+ addCompField(name, type, args) {
6566
+ const field = new FieldComp(type, name, args);
6567
+ this.compFields.add(name);
6568
+ this.fields[name] = field;
6569
+ return field;
6570
+ }
6571
+ };
6572
+ var FIELD_CLASS = /* @__PURE__ */ Symbol.for("tutuca.fieldClass");
6573
+ var fieldsByTypeName = {
6574
+ text: FieldString,
6575
+ int: FieldInt,
6576
+ float: FieldFloat,
6577
+ bool: FieldBool,
6578
+ list: FieldList,
6579
+ object: FieldObject,
6580
+ map: FieldMap,
6581
+ set: FieldSet,
6582
+ any: FieldAny
6583
+ };
6584
+ function fieldFromDescriptor(name, value) {
6585
+ const FieldCls = fieldsByTypeName[value.type] ?? FieldAny;
6586
+ const probe = new FieldCls(name);
6587
+ return [FieldCls, probe.coerceOr(value.defaultValue, probe.defaultValue)];
6588
+ }
6589
+ function classFromData(name, { fields = {}, methods, statics }) {
6590
+ const b = new ClassBuilder(name);
6591
+ for (const field in fields) {
6592
+ const value = fields[field];
6593
+ const type = typeof value;
6594
+ if (type === "string") b.addField(field, value, FieldString);
6595
+ else if (type === "number")
6596
+ b.addField(field, value, Number.isInteger(value) ? FieldInt : FieldFloat);
6597
+ else if (type === "boolean") b.addField(field, value, FieldBool);
6598
+ else if (Array.isArray(value)) b.addField(field, [...value], FieldList);
6599
+ else if (value instanceof Set) b.addField(field, new Set(value), FieldSet);
6600
+ else if (value instanceof Map) b.addField(field, new Map(value), FieldMap);
6601
+ else if (value?.type && Object.hasOwn(value, "defaultValue")) {
6602
+ const [FieldCls, dval] = fieldFromDescriptor(field, value);
6603
+ b.addField(field, dval, FieldCls);
6604
+ } else if (value?.component && value?.args !== void 0)
6605
+ b.addCompField(field, value.component, value.args);
6606
+ else if (isPlainObject2(value)) b.addField(field, { ...value }, FieldObject);
6607
+ else {
6608
+ const FieldCls = value?.[FIELD_CLASS] ?? FieldAny;
6609
+ b.addField(field, value, FieldCls);
6610
+ }
6611
+ }
6612
+ if (methods) b.methods(methods);
6613
+ if (statics) b.statics(statics);
6614
+ return b.build();
6615
+ }
6616
+ function validateDraftFields(current, draft) {
6617
+ const meta = current?.constructor?.getMetaClass?.();
6618
+ if (!meta) return;
6619
+ for (const [name, field] of Object.entries(meta.fields)) {
6620
+ const value = draft[name];
6621
+ if (field.isValid(value)) continue;
6622
+ const coerced = field.coerceOr(value, BAD_VALUE2);
6623
+ if (coerced !== BAD_VALUE2) draft[name] = coerced;
6624
+ else {
6625
+ console.warn(`invalid value for ${meta.name}.${name}`, value);
6626
+ draft[name] = current[name];
6627
+ }
6628
+ }
6629
+ }
6630
+ Component.fromSpec = (opts) => new Component(classFromData(opts.name, opts), opts);
6631
+ var component = (opts) => Component.fromSpec(opts);
6632
+
6208
6633
  // src/stack.js
6209
6634
  var STOP = /* @__PURE__ */ Symbol("STOP");
6210
6635
  var NEXT = /* @__PURE__ */ Symbol("NEXT");
@@ -6263,12 +6688,12 @@ var Stack = class _Stack {
6263
6688
  const provide = this.comps.getCompFor(this.it)?.provide;
6264
6689
  if (provide == null) return this;
6265
6690
  const dynObj = {};
6266
- let has2 = false;
6691
+ let has = false;
6267
6692
  for (const k in provide) {
6268
6693
  dynObj[provide[k].symbol] = provide[k].val.eval(this);
6269
- has2 = true;
6694
+ has = true;
6270
6695
  }
6271
- if (!has2) return this;
6696
+ if (!has) return this;
6272
6697
  const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
6273
6698
  const { comps, it, binds, views, viewsId, ctx } = this;
6274
6699
  return new _Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
@@ -6371,8 +6796,11 @@ var Transactor = class {
6371
6796
  this.transactions.push(t);
6372
6797
  this.onTransactionPushed(t);
6373
6798
  }
6374
- // Subscribe to a normalized record for every handler invocation (send/receive,
6375
- // bubble, request/response, input). Returns an unsubscribe function. Records
6799
+ // Subscribe to a normalized record for every handler invocation. `kind` is the
6800
+ // handler bucket (`receive` / `intent`), plus `answer` — an observation-only kind for
6801
+ // a message the runtime wrote to answer an intent. The BUCKET does not know about
6802
+ // that distinction, and must not: a handler cannot tell an answer from a message its
6803
+ // parent sent. Returns an unsubscribe function. Records
6376
6804
  // carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
6377
6805
  // matched, before, after, parent, timestamp. Purely observational.
6378
6806
  observe(cb) {
@@ -6425,23 +6853,14 @@ var Transactor = class {
6425
6853
  this.pushTransaction(t);
6426
6854
  return this._link(t, parent);
6427
6855
  }
6428
- pushInput(path, name, args = [], opts = {}, parent = null) {
6429
- const t = new InputDispatchEvent(path, this, name, args, parent, opts);
6430
- this.pushTransaction(t);
6431
- return this._link(t, parent);
6432
- }
6433
- pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
6434
- const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
6435
- const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
6436
- this.pushTransaction(t);
6437
- return this._link(t, parent);
6438
- }
6439
- pushRequest(path, name, args = [], opts = {}, parent = null) {
6856
+ // Raise an intent: a job the sender did not address, walked along a route until
6857
+ // something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
6858
+ // `opts.livePath` opts out of pinning the answer's destination keys.
6859
+ pushIntent(path, name, args = [], opts = {}, parent = null) {
6440
6860
  const release = parent ? parent.completion.track() : null;
6441
- const p = this._runRequest(path, name, args, opts, parent, release);
6442
- this._inflight.add(p);
6443
- p.finally(() => this._inflight.delete(p));
6444
- return p;
6861
+ const walk = new IntentWalk(this, path, name, args, opts, parent, release);
6862
+ walk.advance();
6863
+ return walk;
6445
6864
  }
6446
6865
  // Drain queued transactions and await in-flight requests until quiescent. Each
6447
6866
  // awaited request enqueues a ResponseEvent, which may dispatch more work, so we
@@ -6452,57 +6871,6 @@ var Transactor = class {
6452
6871
  if (this._inflight.size) await Promise.allSettled([...this._inflight]);
6453
6872
  }
6454
6873
  }
6455
- async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
6456
- let released = false;
6457
- const transfer = (t) => {
6458
- if (release) {
6459
- released = true;
6460
- t.completion.whenSubtreeSettled().then(release);
6461
- }
6462
- };
6463
- try {
6464
- const curRoot = this.state.val;
6465
- const txnPath = path.toTransactionPath();
6466
- const curLeaf = txnPath.lookup(curRoot);
6467
- const found = this.comps.getRequestFor(curLeaf, name);
6468
- const handler = found ?? mkReq404(name);
6469
- if (this._observers.length > 0) {
6470
- const reqPath = txnPath.pinKeys(curRoot);
6471
- this._emit({
6472
- kind: "request",
6473
- name,
6474
- args,
6475
- path: reqPath,
6476
- pathKeys: reqPath.toKeys(),
6477
- targetPath: path,
6478
- handler: found?.fn ?? null,
6479
- handlerName: found?.fn?.name || name,
6480
- matched: found ? "exact" : "none",
6481
- before: curLeaf,
6482
- after: void 0,
6483
- parent,
6484
- timestamp: Date.now()
6485
- });
6486
- }
6487
- const reqCtx = new RequestContext(path, this, parent, curRoot);
6488
- const resHandlerName = opts?.onResName ?? name;
6489
- const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
6490
- const push = (specificName, baseName, singleArg, result, error) => {
6491
- const resArgs = specificName ? [singleArg] : [result, error];
6492
- const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
6493
- transfer(t);
6494
- this.pushTransaction(t);
6495
- };
6496
- try {
6497
- const result = await handler.fn.apply(null, [...args, reqCtx]);
6498
- push(opts?.onOkName, resHandlerName, result, result, null);
6499
- } catch (error) {
6500
- push(opts?.onErrorName, resHandlerName, error, null, error);
6501
- }
6502
- } finally {
6503
- if (release && !released) release();
6504
- }
6505
- }
6506
6874
  get hasPendingTransactions() {
6507
6875
  return this.transactions.length > 0;
6508
6876
  }
@@ -6519,6 +6887,7 @@ var Transactor = class {
6519
6887
  this._emitTransaction(transaction, curState);
6520
6888
  } else console.warn("undefined new state", { curState, transaction });
6521
6889
  } finally {
6890
+ transaction.ensureWalkAdvanced?.();
6522
6891
  transaction._completion?.ensureSelfSettled();
6523
6892
  transaction._completion?.releaseSelf();
6524
6893
  }
@@ -6527,12 +6896,6 @@ var Transactor = class {
6527
6896
  this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
6528
6897
  }
6529
6898
  };
6530
- function mkReq404(name) {
6531
- const fn = () => {
6532
- throw new Error(`Request not found: ${name}`);
6533
- };
6534
- return { fn };
6535
- }
6536
6899
  function nullHandler() {
6537
6900
  return this;
6538
6901
  }
@@ -6563,6 +6926,15 @@ var Transaction = class {
6563
6926
  }
6564
6927
  afterTransaction() {
6565
6928
  }
6929
+ // Ending a walk and forwarding belong to the two dispatch buckets; the base warns
6930
+ // rather than throwing, so a stray call from the wrong place is a message and not a
6931
+ // crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
6932
+ stop() {
6933
+ console.warn('ctx.stop() is only meaningful in an "intent" handler - ignored');
6934
+ }
6935
+ forward(_opts) {
6936
+ console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
6937
+ }
6566
6938
  buildRootStack(root, comps) {
6567
6939
  return Stack.root(comps, root);
6568
6940
  }
@@ -6579,10 +6951,10 @@ var Transaction = class {
6579
6951
  get observeName() {
6580
6952
  return null;
6581
6953
  }
6582
- callHandler(root, instance, comps) {
6954
+ callHandler(root, instance, draft, comps) {
6583
6955
  const [handler, args] = this.getHandlerAndArgs(root, instance, comps);
6584
6956
  this._resolvedHandler = handler;
6585
- return handler.apply(instance, args);
6957
+ return handler.apply(instance, [draft, ...args]);
6586
6958
  }
6587
6959
  getHandlerAndArgs(_root, _instance, _comps) {
6588
6960
  return null;
@@ -6591,12 +6963,16 @@ var Transaction = class {
6591
6963
  // the data's real location (the dispatch `this.path` keeps intermediates). A subclass
6592
6964
  // may override to supply a pre-resolved path (see ResponseEvent's pinned keys).
6593
6965
  getTransactionPath() {
6594
- return this.path.toTransactionPath();
6966
+ return this.path.toTransactionPath().compact();
6595
6967
  }
6596
6968
  updateRootValue(curRoot, comps) {
6597
6969
  const txnPath = this.getTransactionPath();
6598
6970
  const curLeaf = txnPath.lookup(curRoot);
6599
- const newLeaf = this.callHandler(curRoot, curLeaf, comps);
6971
+ const newLeaf = (0, immer_exports.produce)(curLeaf, (draft) => {
6972
+ const result = this.callHandler(curRoot, curLeaf, draft, comps);
6973
+ if (result === void 0 || result === draft) validateDraftFields(curLeaf, draft);
6974
+ return result;
6975
+ });
6600
6976
  this._before = curLeaf;
6601
6977
  this._after = newLeaf;
6602
6978
  this._completion?.markSelfSettled({ value: newLeaf, old: curLeaf });
@@ -6624,18 +7000,40 @@ var InputEvent = class extends Transaction {
6624
7000
  this._dispatchPath ??= this.path.compact();
6625
7001
  return this._dispatchPath;
6626
7002
  }
7003
+ // A DOM event is an ADDRESSED message like any other — it reaches the component that
7004
+ // owns the view and stops. It keeps its own class only because it resolves its handler
7005
+ // from the compiled view and runs synchronously, not because it is a different channel.
6627
7006
  get observeKind() {
6628
- return "input";
7007
+ return "receive";
6629
7008
  }
6630
7009
  get observeName() {
6631
7010
  return this.e?.type ?? null;
6632
7011
  }
7012
+ // A view's name is a message. `forward` is where that name can LEAVE the component:
7013
+ // it becomes an intent with the same name and the same arguments, so a view that says
7014
+ // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
7015
+ // (A `$method` handler has a name too, so forwarding one raises an intent under it.)
7016
+ forward(opts) {
7017
+ this._forward = opts ?? {};
7018
+ }
7019
+ afterTransaction() {
7020
+ const f = this._forward;
7021
+ if (f === void 0) return;
7022
+ this._forward = void 0;
7023
+ const { args = this._handlerArgs ?? [], ...rest } = f;
7024
+ const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
7025
+ const name = hv?.name;
7026
+ if (name === void 0)
7027
+ return console.warn("ctx.forward() from a handler with no name - ignored");
7028
+ this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
7029
+ }
6633
7030
  buildRootStack(root, comps) {
6634
7031
  return Stack.root(comps, root, this);
6635
7032
  }
6636
7033
  getHandlerAndArgs(root, _instance, comps) {
6637
7034
  const stack = this.buildStack(root, comps);
6638
7035
  const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
7036
+ this._handlerArgs = [...args];
6639
7037
  const path = this.dispatchPath;
6640
7038
  args.push(new EventContext(path, this.transactor, this));
6641
7039
  return [handler, args];
@@ -6720,39 +7118,190 @@ var NameArgsTransaction = class extends Transaction {
6720
7118
  return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
6721
7119
  }
6722
7120
  };
6723
- var ResponseEvent = class extends NameArgsTransaction {
6724
- handlerProp = "response";
6725
- constructor(path, transactor, name, args, parent, txnPath = null) {
6726
- super(path, transactor, name, args, parent);
6727
- this._txnPath = txnPath;
6728
- }
6729
- getTransactionPath() {
6730
- return this._txnPath ?? super.getTransactionPath();
6731
- }
6732
- };
6733
7121
  var SendEvent = class extends NameArgsTransaction {
6734
7122
  handlerProp = "receive";
6735
- run(rootVal, comps) {
6736
- return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
7123
+ // `receive` is the ADDRESSED bucket, and it is the only one: a view's `@on.click`,
7124
+ // a parent's ctx.send, the host's sendAtRoot and an answer to an intent all arrive
7125
+ // here, and a handler cannot tell them apart. Splitting on where a message came from
7126
+ // would let the view's name through and turn the parent's away, though the two say
7127
+ // the same thing — and neither a test nor a parent could drive the component.
7128
+ get observeKind() {
7129
+ return this._isAnswer ? "answer" : "receive";
7130
+ }
7131
+ // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
7132
+ // intent, keeping its name and payload. This is what lets a view's name leave the
7133
+ // component without the view changing.
7134
+ forward(opts) {
7135
+ this._forward = opts ?? {};
6737
7136
  }
6738
7137
  afterTransaction() {
6739
- const { path, name, args, opts, targetPath } = this;
6740
- if (opts.bubbles && path.steps.length > 0)
6741
- this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
7138
+ const f = this._forward;
7139
+ if (f === void 0) return;
7140
+ this._forward = void 0;
7141
+ const { args = this.args, ...rest } = f;
7142
+ this.transactor.pushIntent(this.path, this.name, args, rest, this);
6742
7143
  }
6743
7144
  };
6744
- var BubbleEvent = class extends SendEvent {
6745
- handlerProp = "bubble";
6746
- constructor(path, transactor, name, args, parent, opts, targetPath) {
6747
- super(path, transactor, name, args, parent, opts);
6748
- this.targetPath = targetPath ?? path;
7145
+ var IntentEvent = class extends NameArgsTransaction {
7146
+ handlerProp = "intent";
7147
+ constructor(path, transactor, walk) {
7148
+ super(path, transactor, walk.name, walk.args, walk.parent, {});
7149
+ this.walk = walk;
7150
+ this.targetPath = walk.origin;
7151
+ }
7152
+ // `forward` in an INTENT body amends the hop about to be pushed — it does not push
7153
+ // one itself, because a walk advances on its own.
7154
+ forward(opts) {
7155
+ this.walk.amend(opts, this.path);
7156
+ }
7157
+ stop() {
7158
+ this.walk.finish(null, null);
7159
+ }
7160
+ afterTransaction() {
7161
+ this.ensureWalkAdvanced();
6749
7162
  }
6750
- stopPropagation() {
6751
- this.opts.bubbles = false;
7163
+ // Idempotent, because both afterTransaction and the transactor's `finally` call it —
7164
+ // whichever gets there first advances the walk exactly once.
7165
+ ensureWalkAdvanced() {
7166
+ if (this._advanced) return;
7167
+ this._advanced = true;
7168
+ this.walk.advance();
6752
7169
  }
6753
7170
  };
6754
- var InputDispatchEvent = class extends NameArgsTransaction {
6755
- handlerProp = "input";
7171
+ var PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
7172
+ var DEFAULT_ROUTE = ["dyn", "lex"];
7173
+ var INTENT_DEPTH = 64;
7174
+ var IntentWalk = class {
7175
+ constructor(transactor, path, name, args, opts, parent, release) {
7176
+ this.transactor = transactor;
7177
+ this.name = name;
7178
+ this.args = args;
7179
+ this.route = opts?.route ?? DEFAULT_ROUTE;
7180
+ this.parent = parent;
7181
+ this.release = release;
7182
+ this.origin = path;
7183
+ this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
7184
+ this.legIndex = 0;
7185
+ this.dynAt = path;
7186
+ this.hops = 0;
7187
+ this.ended = false;
7188
+ }
7189
+ // Offer the intent to the next hop. Called once at dispatch, and again by every hop
7190
+ // that ran without answering.
7191
+ advance() {
7192
+ if (this.ended) return;
7193
+ if (this.hops++ >= INTENT_DEPTH) return this.exhaust("intentDepth");
7194
+ while (this.legIndex < this.route.length) {
7195
+ const leg = this.route[this.legIndex];
7196
+ if (leg === "dyn") {
7197
+ if (this.dynAt.steps.length === 0) {
7198
+ this.legIndex++;
7199
+ continue;
7200
+ }
7201
+ this.dynAt = this.dynAt.popStep();
7202
+ this.transactor.pushTransaction(new IntentEvent(this.dynAt, this.transactor, this));
7203
+ return;
7204
+ }
7205
+ if (leg === "lex") {
7206
+ this.legIndex++;
7207
+ return this._tryLex();
7208
+ }
7209
+ console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
7210
+ this.legIndex++;
7211
+ }
7212
+ this.exhaust("noHandler");
7213
+ }
7214
+ // The `lex` leg: the handlers registered on the scope chain of the component that
7215
+ // raised the intent, in order.
7216
+ _tryLex() {
7217
+ const root = this.transactor.state.val;
7218
+ const txnPath = this.origin.toTransactionPath();
7219
+ const leaf = txnPath.lookup(root);
7220
+ const chain = this.transactor.comps.getIntentChainFor(leaf, this.name);
7221
+ if (this.transactor._observers.length > 0) {
7222
+ const path = txnPath.pinKeys(root);
7223
+ this.transactor._emit({
7224
+ kind: "intent",
7225
+ name: this.name,
7226
+ args: this.args,
7227
+ path,
7228
+ pathKeys: path.toKeys(),
7229
+ targetPath: this.origin,
7230
+ handler: chain[0]?.fn ?? null,
7231
+ handlerName: chain[0]?.fn?.name || this.name,
7232
+ matched: chain.length > 0 ? "exact" : "none",
7233
+ before: leaf,
7234
+ after: void 0,
7235
+ parent: this.parent,
7236
+ timestamp: Date.now()
7237
+ });
7238
+ }
7239
+ if (chain.length === 0) return this.advance();
7240
+ const p = this._runLex(chain, 0);
7241
+ this.transactor._inflight.add(p);
7242
+ p.finally(() => this.transactor._inflight.delete(p));
7243
+ }
7244
+ async _runLex(chain, i) {
7245
+ if (this.ended) return;
7246
+ if (i >= chain.length) return this.advance();
7247
+ const ctx = new IntentContext(this.origin, this.transactor, this.parent);
7248
+ try {
7249
+ const res = await chain[i].fn.apply(null, [...this.args, ctx]);
7250
+ if (res === PASS) return this._runLex(chain, i + 1);
7251
+ this.answer("Ok", res);
7252
+ } catch (error) {
7253
+ this.answer("Error", error);
7254
+ }
7255
+ }
7256
+ // A hop answered. Ends the walk.
7257
+ answer(suffix, value) {
7258
+ this.finish(`${this.name}${suffix}`, [value]);
7259
+ }
7260
+ // `forward` from an intent body: keep walking, optionally with new arguments or a
7261
+ // narrowed route.
7262
+ amend(opts, from) {
7263
+ if (opts?.args !== void 0) this.args = opts.args;
7264
+ if (opts?.route !== void 0) {
7265
+ this.route = opts.route;
7266
+ this.legIndex = 0;
7267
+ this.dynAt = from ?? this.dynAt;
7268
+ }
7269
+ }
7270
+ // The route ran out. What the sender hears depends only on what it DECLARES, which is
7271
+ // how "a sender expects an answer if and only if it declares an answer arm" becomes
7272
+ // code — nobody writes it down twice, and there is no schema to keep in step.
7273
+ exhaust(reason) {
7274
+ if (this.ended) return;
7275
+ const { name, args } = this;
7276
+ const comp = this.transactor.comps.getCompFor(
7277
+ this.origin.toTransactionPath().lookup(this.transactor.state.val)
7278
+ );
7279
+ const declares = (n) => comp?.receive?.[n] !== void 0;
7280
+ if (declares(`${name}Unhandled`)) {
7281
+ this.finish(`${name}Unhandled`, args);
7282
+ } else if (declares(`${name}Error`)) {
7283
+ this.finish(`${name}Error`, [reason]);
7284
+ } else {
7285
+ if (declares(`${name}Ok`))
7286
+ console.warn(
7287
+ `intent "${name}" was not answered (${reason}) and this component declares only "${name}Ok" - add "${name}Unhandled" or "${name}Error" to handle it`
7288
+ );
7289
+ this.finish(null, null);
7290
+ }
7291
+ }
7292
+ // End the walk. `name === null` means no answer goes out, which is what `stop` and a
7293
+ // fire-and-forget notification both are — the tracked unit is released here instead
7294
+ // of following an answer's subtree.
7295
+ finish(name, args) {
7296
+ if (this.ended) return;
7297
+ this.ended = true;
7298
+ if (name === null) return this.release?.();
7299
+ const path = this.answerPath ?? this.origin;
7300
+ const t = new SendEvent(path, this.transactor, name, args, this.parent, {});
7301
+ t._isAnswer = true;
7302
+ this.transactor.pushTransaction(t);
7303
+ if (this.release) t.completion.whenSubtreeSettled().then(this.release);
7304
+ }
6756
7305
  };
6757
7306
  var Completion = class {
6758
7307
  constructor() {
@@ -6833,23 +7382,21 @@ var Dispatcher = class {
6833
7382
  get at() {
6834
7383
  return new PathChanges(this);
6835
7384
  }
7385
+ // A message is ADDRESSED: it names one component and stops there.
6836
7386
  send(name, args, opts) {
6837
7387
  return this.sendAtPath(this.path, name, args, opts);
6838
7388
  }
6839
- bubble(name, args, opts) {
6840
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
6841
- }
6842
7389
  sendAtPath(path, name, args, opts) {
6843
7390
  return this.transactor.pushSend(path, name, args, opts, this.parent);
6844
7391
  }
6845
- request(name, args, opts) {
6846
- return this.requestAtPath(this.path, name, args, opts);
6847
- }
6848
- requestAtPath(path, name, args, opts) {
6849
- return this.transactor.pushRequest(path, name, args, opts, this.parent);
7392
+ // An intent is ROUTED: it names a job and walks until something answers. The verb
7393
+ // does not decide which scope answers — `opts.route` does, written here at the call
7394
+ // site, which is where the decision actually is.
7395
+ intent(name, args, opts) {
7396
+ return this.intentAtPath(this.path, name, args, opts);
6850
7397
  }
6851
- inputAtPath(path, name, args, opts) {
6852
- return this.transactor.pushInput(path, name, args, opts, this.parent);
7398
+ intentAtPath(path, name, args, opts) {
7399
+ return this.transactor.pushIntent(path, name, args, opts, this.parent);
6853
7400
  }
6854
7401
  lookupTypeFor(name, inst) {
6855
7402
  return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
@@ -6862,12 +7409,30 @@ var EventContext = class extends Dispatcher {
6862
7409
  get targetPath() {
6863
7410
  return this.parent.targetPath;
6864
7411
  }
6865
- stopPropagation() {
6866
- return this.parent.stopPropagation();
7412
+ // Answer the intent being handled, with a result or with an error. Either ENDS the
7413
+ // walk. Legal only in an `intent` handler: a message has no sender waiting on it.
7414
+ reply(value) {
7415
+ return this.parent.walk === void 0 ? warnNotIntent("reply") : this.parent.walk.answer("Ok", value);
7416
+ }
7417
+ fail(error) {
7418
+ return this.parent.walk === void 0 ? warnNotIntent("fail") : this.parent.walk.answer("Error", error);
7419
+ }
7420
+ // End the walk answering nothing — "served, and no answer".
7421
+ stop() {
7422
+ return this.parent.stop();
7423
+ }
7424
+ // From an `intent` body: hand the intent to the next hop, optionally amending it.
7425
+ // From a `receive` body: turn the message that arrived into an intent. One word from
7426
+ // both ends of a walk; which one you get depends on which bucket you are in.
7427
+ forward(opts) {
7428
+ return this.parent.forward(opts);
6867
7429
  }
6868
7430
  };
6869
- var RequestContext = class extends Dispatcher {
7431
+ var IntentContext = class extends Dispatcher {
6870
7432
  };
7433
+ function warnNotIntent(verb) {
7434
+ console.warn(`ctx.${verb}() is only meaningful in an "intent" handler - ignored`);
7435
+ }
6871
7436
  var PathChanges = class extends PathBuilder {
6872
7437
  constructor(dispatcher) {
6873
7438
  super();
@@ -6876,8 +7441,8 @@ var PathChanges = class extends PathBuilder {
6876
7441
  send(name, args, opts) {
6877
7442
  return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
6878
7443
  }
6879
- bubble(name, args, opts) {
6880
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
7444
+ intent(name, args, opts) {
7445
+ return this.dispatcher.intentAtPath(this.buildPath(), name, args, opts);
6881
7446
  }
6882
7447
  buildPath() {
6883
7448
  return this.dispatcher.path.concat(this.pathChanges);
@@ -7149,29 +7714,24 @@ function captureError(e) {
7149
7714
  if ("actual" in e) out.actual = e.actual;
7150
7715
  return out;
7151
7716
  }
7152
- function buildStack({ components = [], macros = null, requestHandlers = null } = {}) {
7717
+ function buildStack({ components = [], macros = null, intentHandlers = null } = {}) {
7153
7718
  const stack = new ComponentStack();
7154
7719
  stack.registerComponents(components);
7155
7720
  if (macros) stack.registerMacros(macros);
7156
- if (requestHandlers) stack.registerRequestHandlers(requestHandlers);
7721
+ if (intentHandlers) stack.registerIntentHandlers(intentHandlers);
7157
7722
  return stack;
7158
7723
  }
7159
7724
  async function driveStack(stack, value, phase, opts = {}) {
7160
- const warn = opts.warn ?? console.warn;
7161
7725
  const transactor = new Transactor(stack.comps, value);
7162
7726
  if (opts.onMessage)
7163
7727
  transactor.state.onChange(({ val, old, info }) => {
7164
7728
  const t = info?.transaction;
7165
7729
  opts.onMessage(
7166
- { kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path },
7730
+ { kind: t?.handlerProp ?? "receive", name: t?.name, args: t?.args, path: t?.path },
7167
7731
  old,
7168
7732
  val
7169
7733
  );
7170
7734
  });
7171
- if (phaseHasBubble(phase))
7172
- warn(
7173
- "drive(): a `bubble` action is a no-op here — drive originates at the root and bubbles travel child→parent, so there is no ancestor to receive it (and the root's own bubble handler is skipped). To exercise a bubble handler, call it directly."
7174
- );
7175
7735
  dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
7176
7736
  await transactor.settle();
7177
7737
  return transactor.state.val;
@@ -7187,7 +7747,7 @@ async function runTests({
7187
7747
  name = null,
7188
7748
  grep = null,
7189
7749
  bail = false,
7190
- requestHandlers = null,
7750
+ intentHandlers = null,
7191
7751
  macros = null
7192
7752
  } = {}) {
7193
7753
  const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
@@ -7201,7 +7761,7 @@ async function runTests({
7201
7761
  }
7202
7762
  const { describe, test: test2, moduleTests } = makeCollector({ path, components });
7203
7763
  let _stack = null;
7204
- const getStack = () => _stack ??= buildStack({ components, macros, requestHandlers });
7764
+ const getStack = () => _stack ??= buildStack({ components, macros, intentHandlers });
7205
7765
  const driveLocal = (value, phase, opts = {}) => driveStack(getStack(), value, phase, opts);
7206
7766
  await getTests({ describe, test: test2, expect: expect3, drive: driveLocal });
7207
7767
  let bailed = false;
@@ -7267,7 +7827,7 @@ async function runTests({
7267
7827
  }
7268
7828
 
7269
7829
  // tools/format/console.js
7270
- var PASS = "color: #0a0; font-weight: bold";
7830
+ var PASS2 = "color: #0a0; font-weight: bold";
7271
7831
  var FAIL = "color: #c00; font-weight: bold";
7272
7832
  var SKIP = "color: #888";
7273
7833
  var DIM = "color: #888";
@@ -7282,7 +7842,7 @@ function reportTestNode(node) {
7282
7842
  }
7283
7843
  const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
7284
7844
  if (node.status === "pass") {
7285
- console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
7845
+ console.log(`%c✓%c ${node.title}%c${dur}`, PASS2, RESET, DIM);
7286
7846
  } else if (node.status === "skip") {
7287
7847
  console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
7288
7848
  } else {
@@ -7309,7 +7869,7 @@ function reportTestReportToConsole(report) {
7309
7869
  const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
7310
7870
  if (c.fail > 0) console.error(`%c${summary}`, FAIL);
7311
7871
  else if (c.total === 0) console.log(`%c${summary}`, DIM);
7312
- else console.log(`%c${summary}`, PASS);
7872
+ else console.log(`%c${summary}`, PASS2);
7313
7873
  console.groupEnd();
7314
7874
  }
7315
7875
  return report;
@@ -7399,18 +7959,20 @@ function lintIdToMessage(id, info) {
7399
7959
  }
7400
7960
  case "UNKNOWN_HANDLER_ARG_NAME":
7401
7961
  return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
7402
- case "INPUT_HANDLER_NOT_IMPLEMENTED":
7403
- return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
7404
- case "INPUT_HANDLER_NOT_REFERENCED":
7405
- return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
7406
- case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
7407
- return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
7408
- case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
7409
- return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
7410
- case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
7411
- return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
7412
- case "FIELD_NAME_RESERVED_BY_RECORD":
7413
- return `Field '${info.name}' collides with the Immutable Record API: '.${info.name}' reads the Record member, not your value — rename the field (its value is only reachable via .get('${info.name}'))`;
7962
+ case "RETIRED_HANDLER_BUCKET": {
7963
+ const which = info.names?.length ? ` (${info.names.join(", ")})` : "";
7964
+ return `\`${info.key}\` is no longer a handler bucket${which} — ${info.how}`;
7965
+ }
7966
+ case "RETIRED_CTX_VERB":
7967
+ return `ctx.${info.verb}() no longer exists — use ${info.to} (in \`${info.bucket}.${info.name}\`)`;
7968
+ case "HANDLER_NAME_COLLISION":
7969
+ return `${info.problem} — ${info.fix}`;
7970
+ case "RECEIVE_HANDLER_NOT_IMPLEMENTED":
7971
+ return `Receive handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
7972
+ case "EVENT_HANDLER_METHOD_NOT_ALLOWED":
7973
+ return `Event handler '$${info.name}' is a method reference — move it to receive and use '${info.name}'`;
7974
+ case "FIELD_METHOD_NAME_COLLISION":
7975
+ return `Field '${info.name}' shadows a method with the same name — rename either the field or method`;
7414
7976
  case "FIELD_VAL_NOT_DEFINED":
7415
7977
  return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
7416
7978
  case "FIELD_VAL_IS_METHOD":
@@ -7821,440 +8383,6 @@ var DragInfo = class {
7821
8383
  }
7822
8384
  };
7823
8385
 
7824
- // index.js
7825
- import {
7826
- Collection,
7827
- fromJS,
7828
- get,
7829
- getIn,
7830
- has,
7831
- hash,
7832
- hasIn,
7833
- is as is2,
7834
- isAssociative,
7835
- isCollection,
7836
- isImmutable,
7837
- isIndexed as isIndexed2,
7838
- isKeyed as isKeyed2,
7839
- isList,
7840
- isMap,
7841
- isMap as isMap2,
7842
- isOrdered,
7843
- isOrderedMap,
7844
- isOrderedMap as isOrderedMap2,
7845
- isOrderedSet,
7846
- isPlainObject,
7847
- isRecord,
7848
- isSeq,
7849
- isSet,
7850
- isStack,
7851
- isValueObject,
7852
- List as List2,
7853
- Map as Map2,
7854
- Map as Map3,
7855
- merge,
7856
- mergeDeep,
7857
- mergeDeepWith,
7858
- mergeWith,
7859
- OrderedMap as OrderedMap2,
7860
- OrderedMap as OrderedMap3,
7861
- OrderedSet,
7862
- PairSorting,
7863
- Range,
7864
- Record as Record2,
7865
- Repeat,
7866
- remove,
7867
- removeIn,
7868
- Seq,
7869
- Set as Set2,
7870
- Set as Set3,
7871
- Stack as Stack2,
7872
- set,
7873
- setIn,
7874
- update,
7875
- updateIn,
7876
- version
7877
- } from "immutable";
7878
-
7879
- // src/oo.js
7880
- import { Map as IMap, Set as ISet, List, OrderedMap, Record } from "immutable";
7881
- var BAD_VALUE2 = /* @__PURE__ */ Symbol("BadValue");
7882
- var nullCoercer = (v) => v;
7883
- var Field = class {
7884
- constructor(type, name, typeCheck, coercer, defaultValue = null) {
7885
- this.type = type;
7886
- this.name = name;
7887
- this.typeCheck = typeCheck;
7888
- this.coercer = coercer;
7889
- this.checks = [];
7890
- this.defaultValue = defaultValue;
7891
- }
7892
- toDataDef() {
7893
- const { type, defaultValue: dv } = this;
7894
- return { type, defaultValue: dv?.toJS ? dv.toJS() : dv };
7895
- }
7896
- getFirstFailingCheck(v) {
7897
- if (!this.typeCheck.isValid(v)) return this.typeCheck;
7898
- for (const check2 of this.checks) if (!check2.isValid(v)) return check2;
7899
- return null;
7900
- }
7901
- isValid(v) {
7902
- return this.getFirstFailingCheck(v) === null;
7903
- }
7904
- addCheck(check2) {
7905
- this.checks.push(check2);
7906
- return this;
7907
- }
7908
- coerceOr(v, defaultValue = null) {
7909
- if (this.isValid(v)) return v;
7910
- const v1 = this.coercer(v);
7911
- return this.isValid(v1) ? v1 : defaultValue;
7912
- }
7913
- coerceOrDefault(v) {
7914
- return this.coerceOr(v, this.defaultValue);
7915
- }
7916
- extendProtoForType(_proto, _uname) {
7917
- }
7918
- extendProto(proto) {
7919
- const { name } = this;
7920
- const uname = name[0].toUpperCase() + name.slice(1);
7921
- const setName = `set${uname}`;
7922
- const that = this;
7923
- proto[setName] = function(v) {
7924
- const v1 = that.coerceOr(v, BAD_VALUE2);
7925
- if (v1 === BAD_VALUE2) {
7926
- console.warn("invalid value", v);
7927
- return this;
7928
- }
7929
- return this.set(name, v1);
7930
- };
7931
- proto[`update${uname}`] = function(fn) {
7932
- return this[setName](fn(this.get(name)));
7933
- };
7934
- proto[`reset${uname}`] = function() {
7935
- return this.set(name, that.defaultValue);
7936
- };
7937
- this.extendProtoForType(proto, uname);
7938
- }
7939
- };
7940
- var Check = class {
7941
- isValid(_v) {
7942
- return true;
7943
- }
7944
- getMessage(_v) {
7945
- return "Invalid";
7946
- }
7947
- };
7948
- var CheckTypeAny = class extends Check {
7949
- };
7950
- var CHECK_TYPE_ANY = new CheckTypeAny();
7951
- var FnCheck = class extends Check {
7952
- constructor(isValidFn, getMessageFn) {
7953
- super();
7954
- this._isValid = isValidFn;
7955
- this._getMessage = getMessageFn;
7956
- }
7957
- isValid(v) {
7958
- return this._isValid(v);
7959
- }
7960
- getMessage(v) {
7961
- return this._getMessage(v);
7962
- }
7963
- };
7964
- var CHECK_TYPE_INT = new FnCheck(
7965
- (v) => Number.isInteger(v),
7966
- () => "Integer expected"
7967
- );
7968
- var CHECK_TYPE_FLOAT = new FnCheck(
7969
- (v) => Number.isFinite(v),
7970
- () => "Float expected"
7971
- );
7972
- var CHECK_TYPE_BOOL = new FnCheck(
7973
- (v) => typeof v === "boolean",
7974
- () => "Boolean expected"
7975
- );
7976
- var CHECK_TYPE_STRING = new FnCheck(
7977
- (v) => typeof v === "string",
7978
- () => "String expected"
7979
- );
7980
- var CHECK_TYPE_LIST = new FnCheck(
7981
- (v) => List.isList(v),
7982
- () => "List expected"
7983
- );
7984
- var CHECK_TYPE_MAP = new FnCheck(
7985
- (v) => IMap.isMap(v),
7986
- () => "Map expected"
7987
- );
7988
- var CHECK_TYPE_OMAP = new FnCheck(
7989
- (v) => OrderedMap.isOrderedMap(v),
7990
- () => "OrderedMap expected"
7991
- );
7992
- var CHECK_TYPE_SET = new FnCheck(
7993
- (v) => ISet.isSet(v),
7994
- () => "Set expected"
7995
- );
7996
- var boolCoercer = (v) => !!v;
7997
- var FieldBool = class extends Field {
7998
- constructor(name, defaultValue = false) {
7999
- super("bool", name, CHECK_TYPE_BOOL, boolCoercer, defaultValue);
8000
- }
8001
- extendProtoForType(proto, uname) {
8002
- const { name } = this;
8003
- proto[`toggle${uname}`] = function() {
8004
- return this.set(name, !this.get(name, false));
8005
- };
8006
- proto[`set${uname}`] = function(v) {
8007
- return this.set(name, !!v);
8008
- };
8009
- }
8010
- };
8011
- var FieldAny = class extends Field {
8012
- constructor(name, defaultValue = null) {
8013
- super("any", name, CHECK_TYPE_ANY, nullCoercer, defaultValue);
8014
- }
8015
- toDataDef() {
8016
- const { defaultValue: dv } = this;
8017
- const type = getTypeName(dv) ?? "any";
8018
- return { type, defaultValue: dv?.toJS ? dv.toJS() : dv };
8019
- }
8020
- };
8021
- var stringCoercer = (v) => v?.toString?.() ?? "";
8022
- var FieldString = class extends Field {
8023
- constructor(name, defaultValue = "") {
8024
- super("text", name, CHECK_TYPE_STRING, stringCoercer, defaultValue);
8025
- }
8026
- extendProtoForType(proto, _uname) {
8027
- extendProtoSized(proto, this.name, "", "length");
8028
- }
8029
- };
8030
- var intCoercer = (v) => Number.isFinite(v) ? Math.trunc(v) : null;
8031
- var FieldInt = class extends Field {
8032
- constructor(name, defaultValue = 0) {
8033
- super("int", name, CHECK_TYPE_INT, intCoercer, defaultValue);
8034
- }
8035
- };
8036
- var floatCoercer = (_) => null;
8037
- var FieldFloat = class extends Field {
8038
- constructor(name, defaultValue = 0) {
8039
- super("float", name, CHECK_TYPE_FLOAT, floatCoercer, defaultValue);
8040
- }
8041
- };
8042
- var getTypeName = (v) => v?.constructor?.getMetaClass?.()?.name;
8043
- var CheckTypeName = class {
8044
- constructor(typeName) {
8045
- this.typeName = typeName;
8046
- }
8047
- isValid(v) {
8048
- return getTypeName(v) === this.typeName;
8049
- }
8050
- getMessage(v) {
8051
- const got = getTypeName(v);
8052
- return `Expected "${this.typeName}", got "${got}"`;
8053
- }
8054
- };
8055
- var FieldComp = class extends Field {
8056
- constructor(type, name, args) {
8057
- super(type, name, new CheckTypeName(type), nullCoercer, null);
8058
- this.args = args;
8059
- }
8060
- toDataDef() {
8061
- return { component: this.typeName, args: this.args };
8062
- }
8063
- };
8064
- var NONE2 = /* @__PURE__ */ Symbol("NONE");
8065
- function extendProtoForKeyed(proto, name, uname) {
8066
- extendProtoSized(proto, name, EMPTY_LIST);
8067
- proto[`setIn${uname}At`] = function(i, v) {
8068
- return this.set(name, this.get(name).set(i, v));
8069
- };
8070
- proto[`getIn${uname}At`] = function(i, dval) {
8071
- return this.get(name).get(i, dval);
8072
- };
8073
- proto[`updateIn${uname}At`] = function(i, fn) {
8074
- const col = this.get(name);
8075
- const v = col.get(i, NONE2);
8076
- if (v !== NONE2) return this.set(name, col.set(i, fn(v)));
8077
- console.warn("key", i, "not found in", name, col);
8078
- return this;
8079
- };
8080
- extendDeleteInAt(proto, name, `${uname}At`);
8081
- }
8082
- function extendDeleteInAt(proto, name, uname) {
8083
- proto[`deleteIn${uname}`] = function(v) {
8084
- return this.set(name, this.get(name).delete(v));
8085
- };
8086
- proto[`removeIn${uname}`] = proto[`deleteIn${uname}`];
8087
- }
8088
- var EMPTY_LIST = List();
8089
- var listCoercer = (v) => Array.isArray(v) ? List(v) : null;
8090
- var FieldList = class extends Field {
8091
- constructor(name, defaultValue = EMPTY_LIST) {
8092
- super("list", name, CHECK_TYPE_LIST, listCoercer, defaultValue);
8093
- }
8094
- extendProtoForType(proto, uname) {
8095
- const { name } = this;
8096
- extendProtoForKeyed(proto, name, uname);
8097
- proto[`pushIn${uname}`] = function(v) {
8098
- return this.set(name, this.get(name).push(v));
8099
- };
8100
- proto[`insertIn${uname}At`] = function(i, v) {
8101
- return this.set(name, this.get(name).insert(i, v));
8102
- };
8103
- }
8104
- };
8105
- var imapCoercer = (v) => IMap(v);
8106
- var FieldMap = class extends Field {
8107
- constructor(name, defaultValue = IMap()) {
8108
- super("map", name, CHECK_TYPE_MAP, imapCoercer, defaultValue);
8109
- }
8110
- extendProtoForType(proto, uname) {
8111
- extendProtoForKeyed(proto, this.name, uname);
8112
- }
8113
- };
8114
- var omapCoercer = (v) => OrderedMap(v);
8115
- var FieldOMap = class extends Field {
8116
- constructor(name, defaultValue = OrderedMap()) {
8117
- super("omap", name, CHECK_TYPE_OMAP, omapCoercer, defaultValue);
8118
- }
8119
- extendProtoForType(proto, uname) {
8120
- extendProtoForKeyed(proto, this.name, uname);
8121
- }
8122
- };
8123
- function extendProtoSized(proto, name, defaultEmpty, propName = "size") {
8124
- proto[`${name}Len`] = function() {
8125
- return this.get(name, defaultEmpty)[propName];
8126
- };
8127
- }
8128
- var EMPTY_SET2 = ISet();
8129
- var isetCoercer = (v) => Array.isArray(v) ? ISet(v) : v instanceof Set ? ISet(v) : null;
8130
- var FieldSet = class extends Field {
8131
- constructor(name, defaultValue = EMPTY_SET2) {
8132
- super("set", name, CHECK_TYPE_SET, isetCoercer, defaultValue);
8133
- }
8134
- extendProtoForType(proto, uname) {
8135
- const { name } = this;
8136
- extendProtoSized(proto, name, EMPTY_SET2);
8137
- proto[`addIn${uname}`] = function(v) {
8138
- return this.set(name, this.get(name).add(v));
8139
- };
8140
- extendDeleteInAt(proto, name, uname);
8141
- proto[`hasIn${uname}`] = function(v) {
8142
- return this.get(name).has(v);
8143
- };
8144
- proto[`toggleIn${uname}`] = function(v) {
8145
- const current = this.get(name);
8146
- return this.set(name, current.has(v) ? current.delete(v) : current.add(v));
8147
- };
8148
- }
8149
- };
8150
- function mkCompField(field, scope, args) {
8151
- const Comp = scope?.lookupComponent(field.type) ?? null;
8152
- if (Comp === null)
8153
- console.warn(
8154
- scope ? `component field "${field.name}": component "${field.type}" not found in scope` : `component field "${field.name}": cannot resolve component "${field.type}" — built without a registered scope (use ${field.type}.make({}) as the default, or build via a registered component)`
8155
- );
8156
- return Comp?.make({ ...field.args, ...args }, { scope }) ?? null;
8157
- }
8158
- var ClassBuilder = class {
8159
- constructor(name) {
8160
- const fields = {};
8161
- const compFields = /* @__PURE__ */ new Set();
8162
- this.name = name;
8163
- this.fields = fields;
8164
- this.compFields = compFields;
8165
- this._methods = {};
8166
- this._statics = {
8167
- make: function(inArgs = {}, opts = {}) {
8168
- const args = {};
8169
- const scope = opts.scope ?? this.scope;
8170
- for (const key in inArgs) {
8171
- const field = fields[key];
8172
- if (compFields.has(key)) args[key] = mkCompField(field, scope, inArgs[key]);
8173
- else if (field === void 0)
8174
- console.warn("extra argument to constructor:", name, key, inArgs);
8175
- else args[key] = field.coerceOrDefault(inArgs[key]);
8176
- }
8177
- for (const key of compFields)
8178
- if (args[key] === void 0) args[key] = mkCompField(fields[key], scope, inArgs[key]);
8179
- return this(args);
8180
- }
8181
- };
8182
- }
8183
- build() {
8184
- const fieldVals = {};
8185
- const proto = {};
8186
- const { name, _methods, fields } = this;
8187
- for (const fieldName in fields) {
8188
- const field = fields[fieldName];
8189
- fieldVals[fieldName] = field.defaultValue;
8190
- field.extendProto(proto);
8191
- }
8192
- const Class = { [name]: Record(fieldVals, name) }[name];
8193
- Object.assign(Class.prototype, proto, _methods);
8194
- const metaClass = { fields, name, methods: _methods };
8195
- Object.assign(Class, this._statics, { getMetaClass: () => metaClass });
8196
- return Class;
8197
- }
8198
- methods(proto) {
8199
- for (const k in proto) this._methods[k] = proto[k];
8200
- }
8201
- statics(proto) {
8202
- for (const k in proto) this._statics[k] = proto[k];
8203
- }
8204
- addField(name, dval, FieldCls) {
8205
- const field = new FieldCls(name, dval);
8206
- this.fields[name] = field;
8207
- return field;
8208
- }
8209
- addCompField(name, type, args) {
8210
- const field = new FieldComp(type, name, args);
8211
- this.compFields.add(name);
8212
- this.fields[name] = field;
8213
- return field;
8214
- }
8215
- };
8216
- var FIELD_CLASS = /* @__PURE__ */ Symbol.for("tutuca.fieldClass");
8217
- var fieldsByTypeName = {
8218
- text: FieldString,
8219
- int: FieldInt,
8220
- float: FieldFloat,
8221
- bool: FieldBool,
8222
- list: FieldList,
8223
- map: FieldMap,
8224
- omap: FieldOMap,
8225
- set: FieldSet,
8226
- any: FieldAny
8227
- };
8228
- function classFromData(name, { fields = {}, methods, statics }) {
8229
- const b = new ClassBuilder(name);
8230
- for (const field in fields) {
8231
- const value = fields[field];
8232
- const type = typeof value;
8233
- if (type === "string") b.addField(field, value, FieldString);
8234
- else if (type === "number") b.addField(field, value, FieldFloat);
8235
- else if (type === "boolean") b.addField(field, value, FieldBool);
8236
- else if (List.isList(value) || Array.isArray(value)) b.addField(field, List(value), FieldList);
8237
- else if (ISet.isSet(value) || value instanceof Set) b.addField(field, ISet(value), FieldSet);
8238
- else if (OrderedMap.isOrderedMap(value)) b.addField(field, value, FieldOMap);
8239
- else if (value?.type && value?.defaultValue !== void 0) {
8240
- const Field2 = fieldsByTypeName[value.type] ?? FieldAny;
8241
- b.addField(field, new Field2().coerceOr(value.defaultValue), Field2);
8242
- } else if (value?.component && value?.args !== void 0)
8243
- b.addCompField(field, value.component, value.args);
8244
- else if (IMap.isMap(value) || value?.constructor === Object)
8245
- b.addField(field, IMap(value), FieldMap);
8246
- else {
8247
- const Field2 = value?.[FIELD_CLASS] ?? FieldAny;
8248
- b.addField(field, value, Field2);
8249
- }
8250
- }
8251
- if (methods) b.methods(methods);
8252
- if (statics) b.statics(statics);
8253
- return b.build();
8254
- }
8255
- Component.fromSpec = (opts) => new Component(classFromData(opts.name, opts), opts);
8256
- var component = (opts) => Component.fromSpec(opts);
8257
-
8258
8386
  // index.js
8259
8387
  var css = String.raw;
8260
8388
  var html = String.raw;
@@ -8307,7 +8435,6 @@ function resolveAlter(Comp, name) {
8307
8435
  }
8308
8436
  return fn;
8309
8437
  }
8310
- var seqGet = (seq, key) => Array.isArray(seq) ? seq[key] : seq.get ? seq.get(key) : seq[key];
8311
8438
  function collectIterBindings(Comp, compInstance, seq, opts = {}) {
8312
8439
  const whenFn = resolveAlter(Comp, opts.when) ?? filterAlwaysTrue;
8313
8440
  const loopWithFn = resolveAlter(Comp, opts.loopWith) ?? nullLoopWith;
@@ -8352,157 +8479,10 @@ function getSignature(name, fn) {
8352
8479
  const params = m ? m[1].trim() : "";
8353
8480
  return `${name}(${params})`;
8354
8481
  }
8355
- function getFieldMethods(field) {
8356
- const { name, type } = field;
8357
- const uname = name[0].toUpperCase() + name.slice(1);
8358
- const methods = [
8359
- { name: `set${uname}`, sig: `set${uname}(v)`, desc: "Set value" },
8360
- {
8361
- name: `update${uname}`,
8362
- sig: `update${uname}(fn)`,
8363
- desc: "Update value with function"
8364
- },
8365
- {
8366
- name: `reset${uname}`,
8367
- sig: `reset${uname}()`,
8368
- desc: "Reset to default value"
8369
- }
8370
- ];
8371
- switch (type) {
8372
- case "bool":
8373
- methods[0].desc = "Set value (coerces to boolean)";
8374
- methods.push({
8375
- name: `toggle${uname}`,
8376
- sig: `toggle${uname}()`,
8377
- desc: "Toggle boolean value"
8378
- });
8379
- break;
8380
- case "int":
8381
- case "float":
8382
- case "any":
8383
- break;
8384
- case "text":
8385
- methods.push({
8386
- name: `${name}Len`,
8387
- sig: `${name}Len()`,
8388
- desc: "Get string length"
8389
- });
8390
- break;
8391
- case "list":
8392
- methods.push(
8393
- { name: `${name}Len`, sig: `${name}Len()`, desc: "Get list size" },
8394
- {
8395
- name: `setIn${uname}At`,
8396
- sig: `setIn${uname}At(i, v)`,
8397
- desc: "Set item at index"
8398
- },
8399
- {
8400
- name: `getIn${uname}At`,
8401
- sig: `getIn${uname}At(i, defaultValue)`,
8402
- desc: "Get item at index"
8403
- },
8404
- {
8405
- name: `updateIn${uname}At`,
8406
- sig: `updateIn${uname}At(i, fn)`,
8407
- desc: "Update item at index with function"
8408
- },
8409
- {
8410
- name: `deleteIn${uname}At`,
8411
- sig: `deleteIn${uname}At(i)`,
8412
- desc: "Delete item at index"
8413
- },
8414
- {
8415
- name: `removeIn${uname}At`,
8416
- sig: `removeIn${uname}At(i)`,
8417
- desc: "Delete item at index (alias)"
8418
- },
8419
- {
8420
- name: `pushIn${uname}`,
8421
- sig: `pushIn${uname}(v)`,
8422
- desc: "Push item to end"
8423
- },
8424
- {
8425
- name: `insertIn${uname}At`,
8426
- sig: `insertIn${uname}At(i, v)`,
8427
- desc: "Insert item at index"
8428
- }
8429
- );
8430
- break;
8431
- case "map":
8432
- case "omap": {
8433
- const label = type === "omap" ? "ordered map" : "map";
8434
- methods.push(
8435
- {
8436
- name: `${name}Len`,
8437
- sig: `${name}Len()`,
8438
- desc: `Get ${label} size`
8439
- },
8440
- {
8441
- name: `setIn${uname}At`,
8442
- sig: `setIn${uname}At(key, v)`,
8443
- desc: "Set value at key"
8444
- },
8445
- {
8446
- name: `getIn${uname}At`,
8447
- sig: `getIn${uname}At(key, defaultValue)`,
8448
- desc: "Get value at key"
8449
- },
8450
- {
8451
- name: `updateIn${uname}At`,
8452
- sig: `updateIn${uname}At(key, fn)`,
8453
- desc: "Update value at key with function"
8454
- },
8455
- {
8456
- name: `deleteIn${uname}At`,
8457
- sig: `deleteIn${uname}At(key)`,
8458
- desc: "Delete entry at key"
8459
- },
8460
- {
8461
- name: `removeIn${uname}At`,
8462
- sig: `removeIn${uname}At(key)`,
8463
- desc: "Delete entry at key (alias)"
8464
- }
8465
- );
8466
- break;
8467
- }
8468
- case "set":
8469
- methods.push(
8470
- { name: `${name}Len`, sig: `${name}Len()`, desc: "Get set size" },
8471
- {
8472
- name: `addIn${uname}`,
8473
- sig: `addIn${uname}(v)`,
8474
- desc: "Add value to set"
8475
- },
8476
- {
8477
- name: `deleteIn${uname}`,
8478
- sig: `deleteIn${uname}(v)`,
8479
- desc: "Remove value from set"
8480
- },
8481
- {
8482
- name: `removeIn${uname}`,
8483
- sig: `removeIn${uname}(v)`,
8484
- desc: "Remove value from set (alias)"
8485
- },
8486
- {
8487
- name: `hasIn${uname}`,
8488
- sig: `hasIn${uname}(v)`,
8489
- desc: "Check if value is in set"
8490
- },
8491
- {
8492
- name: `toggleIn${uname}`,
8493
- sig: `toggleIn${uname}(v)`,
8494
- desc: "Toggle value in set"
8495
- }
8496
- );
8497
- break;
8498
- default:
8499
- break;
8500
- }
8501
- return methods;
8502
- }
8503
8482
  function serializeDefault(v) {
8504
8483
  if (v === null || v === void 0) return v;
8505
- if (v?.toJS) return v.toJS();
8484
+ if (v instanceof Map) return Object.fromEntries(v);
8485
+ if (v instanceof Set) return [...v];
8506
8486
  return v;
8507
8487
  }
8508
8488
  function getComponentDoc(comp) {
@@ -8512,10 +8492,9 @@ function getComponentDoc(comp) {
8512
8492
  name: k,
8513
8493
  sig: getSignature(k, methods[k])
8514
8494
  }));
8515
- const inputHandlers = Object.keys(comp.input).map((k) => ({
8516
- name: k,
8517
- sig: getSignature(k, comp.input[k])
8518
- }));
8495
+ const sigs = (bucket) => Object.keys(bucket ?? {}).map((k) => ({ name: k, sig: getSignature(k, bucket[k]) }));
8496
+ const receiveHandlers = sigs(comp.receive);
8497
+ const intentHandlers = sigs(comp.intent);
8519
8498
  const fieldDocs = [];
8520
8499
  for (const fieldName in fields) {
8521
8500
  const field = fields[fieldName];
@@ -8523,10 +8502,16 @@ function getComponentDoc(comp) {
8523
8502
  name: fieldName,
8524
8503
  type: field.type,
8525
8504
  default: serializeDefault(field.defaultValue),
8526
- methods: getFieldMethods(field)
8505
+ methods: []
8527
8506
  });
8528
8507
  }
8529
- return { name, methods: userMethods, input: inputHandlers, fields: fieldDocs };
8508
+ return {
8509
+ name,
8510
+ methods: userMethods,
8511
+ receive: receiveHandlers,
8512
+ intent: intentHandlers,
8513
+ fields: fieldDocs
8514
+ };
8530
8515
  }
8531
8516
  function docComponents(normalized, { name = null } = {}) {
8532
8517
  const comps = normalized.components;
@@ -8553,7 +8538,7 @@ function shadowCheckComponent(Comp) {
8553
8538
  shadowComp.views = shadowViews;
8554
8539
  return checkComponent(shadowComp).reports;
8555
8540
  }
8556
- function check(app) {
8541
+ function check2(app) {
8557
8542
  const counts = { error: 0, warn: 0, hint: 0 };
8558
8543
  for (const Comp of app.comps.byId.values()) {
8559
8544
  const reports = shadowCheckComponent(Comp);
@@ -8618,7 +8603,6 @@ export {
8618
8603
  BINDING_MEMBER_TOO_DEEP,
8619
8604
  COMP_FIELD_BAD_SHAPE,
8620
8605
  CONSTANT_CONDITION,
8621
- Collection,
8622
8606
  ComponentDocs,
8623
8607
  ComponentList,
8624
8608
  ComponentSummary,
@@ -8628,20 +8612,15 @@ export {
8628
8612
  DYN_VAL_NOT_DEFINED,
8629
8613
  Describe,
8630
8614
  DescribeResult,
8615
+ EVENT_HANDLER_METHOD_NOT_ALLOWED,
8631
8616
  ExampleIndex,
8632
8617
  FIELD_CLASS,
8633
- FIELD_NAME_RESERVED_BY_RECORD,
8618
+ FIELD_METHOD_NAME_COLLISION,
8634
8619
  FIELD_VAL_IS_METHOD,
8635
8620
  FIELD_VAL_NOT_DEFINED,
8636
8621
  GLOBAL_SELECTOR_IN_SCOPED_STYLE,
8622
+ HANDLER_NAME_COLLISION,
8637
8623
  IF_NO_BRANCH_SET,
8638
- Map3 as IMap,
8639
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
8640
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
8641
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
8642
- INPUT_HANDLER_NOT_IMPLEMENTED,
8643
- INPUT_HANDLER_NOT_REFERENCED,
8644
- Set3 as ISet,
8645
8624
  LOOKUP_BAD_SHAPE,
8646
8625
  LOOKUP_TARGET_MALFORMED,
8647
8626
  LintClassCollectorCtx,
@@ -8650,35 +8629,27 @@ export {
8650
8629
  LintFinding,
8651
8630
  LintParseContext,
8652
8631
  LintReport,
8653
- List2 as List,
8654
8632
  MAYBE_ADD_AT_PREFIX,
8655
8633
  MAYBE_DROP_AT_PREFIX,
8656
8634
  METHOD_VAL_IS_FIELD,
8657
8635
  METHOD_VAL_NOT_DEFINED,
8658
- Map2 as Map,
8659
8636
  ModuleInfo,
8660
8637
  ModuleTestReport,
8661
8638
  ModuleTests,
8662
- OrderedMap3 as OMap,
8663
- OrderedMap2 as OrderedMap,
8664
- OrderedSet,
8639
+ PASS,
8665
8640
  PLACEHOLDERLESS_TEMPLATE_STRING,
8666
8641
  PROVIDE_NOT_ADDRESSABLE,
8667
- PairSorting,
8668
8642
  ParseContext,
8643
+ RECEIVE_HANDLER_NOT_IMPLEMENTED,
8669
8644
  REDUNDANT_TEMPLATE_STRING,
8670
8645
  RENDER_IT_OUTSIDE_OF_LOOP,
8671
- Range,
8672
- Record2 as Record,
8646
+ RETIRED_CTX_VERB,
8647
+ RETIRED_HANDLER_BUCKET,
8673
8648
  RenderBatch,
8674
8649
  RenderedExample,
8675
8650
  RenderedSection,
8676
- Repeat,
8677
8651
  SEQ_INFO,
8678
8652
  SUGGEST_BINDING_MEMBER,
8679
- Seq,
8680
- Set2 as Set,
8681
- Stack2 as Stack,
8682
8653
  TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE,
8683
8654
  Test,
8684
8655
  TestReport,
@@ -8693,7 +8664,7 @@ export {
8693
8664
  UNKNOWN_X_OP,
8694
8665
  UNSUPPORTED_EXPR_SYNTAX,
8695
8666
  X_OP_IGNORES_CHILDREN,
8696
- check,
8667
+ check2 as check,
8697
8668
  checkComponent,
8698
8669
  collectIterBindings,
8699
8670
  compileClassesToStyle,
@@ -8704,55 +8675,18 @@ export {
8704
8675
  docComponents,
8705
8676
  drive,
8706
8677
  export_expect as expect,
8707
- fromJS,
8708
- get,
8709
- getIn,
8710
- has,
8711
- hasIn,
8712
- hash,
8713
8678
  html,
8714
8679
  injectCss,
8715
- is2 as is,
8716
- isAssociative,
8717
- isCollection,
8718
- isMap2 as isIMap,
8719
- isImmutable,
8720
- isIndexed2 as isIndexed,
8721
- isKeyed2 as isKeyed,
8722
- isList,
8723
- isMap,
8724
- isOrderedMap2 as isOMap,
8725
- isOrdered,
8726
- isOrderedMap,
8727
- isOrderedSet,
8728
- isPlainObject,
8729
- isRecord,
8730
- isSeq,
8731
- isSet,
8732
- isStack,
8733
- isValueObject,
8734
8680
  lintIdToMessage,
8735
8681
  macro,
8736
8682
  makeCollector,
8737
- merge,
8738
- mergeDeep,
8739
- mergeDeepWith,
8740
- mergeWith,
8741
- phaseHasBubble,
8742
8683
  phaseOps,
8743
- remove,
8744
- removeIn,
8745
8684
  reportTestReportToConsole,
8746
8685
  resolveArgs,
8747
8686
  rootDispatcher,
8748
8687
  runTests,
8749
- set,
8750
- setIn,
8751
8688
  shadowCheckComponent,
8752
8689
  suggestionToMessage,
8753
8690
  test,
8754
- tutuca,
8755
- update,
8756
- updateIn,
8757
- version
8691
+ tutuca
8758
8692
  };