tutuca 0.9.115 → 0.10.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.
@@ -4891,8 +4891,8 @@ function parseSequence(s, px) {
4891
4891
  function parseMacroAttr(s, px) {
4892
4892
  return _parseSingle(s, px, G_ALL);
4893
4893
  }
4894
- function parseInputHandler(s, px) {
4895
- return _parseHandler(s, px, "input", true, true);
4894
+ function parseReceiveHandler(s, px) {
4895
+ return _parseHandler(s, px, "receive", true, true);
4896
4896
  }
4897
4897
  function parseAlterHandler(s, px) {
4898
4898
  const r = _parseHandler(s, px, "alter", false, false);
@@ -5222,7 +5222,7 @@ function getAttrParser(px) {
5222
5222
  _attrParser.clear(px);
5223
5223
  return _attrParser;
5224
5224
  }
5225
- var Attributes, booleanAttrsRaw, booleanAttrs, AttrParser, ConstAttrs, DynAttrs, BaseAttr, Attr, ConstAttr, RawHtmlAttr, NOT_SET_VAL, IfAttr, _attrParser, EventHandler, RequestHandler;
5225
+ var Attributes, booleanAttrsRaw, booleanAttrs, AttrParser, ConstAttrs, DynAttrs, BaseAttr, Attr, ConstAttr, RawHtmlAttr, NOT_SET_VAL, IfAttr, _attrParser, EventHandler, IntentHandler;
5226
5226
  var init_attribute = __esm({
5227
5227
  "src/attribute.js"() {
5228
5228
  init_value();
@@ -5482,11 +5482,11 @@ var init_attribute = __esm({
5482
5482
  return [this.handlerVal.evalAsHandler(stack), argValues];
5483
5483
  }
5484
5484
  static parse(s, px) {
5485
- const r = parseInputHandler(s, px);
5485
+ const r = parseReceiveHandler(s, px);
5486
5486
  return r === null ? null : new _EventHandler(r.handlerVal, r.args);
5487
5487
  }
5488
5488
  };
5489
- RequestHandler = class {
5489
+ IntentHandler = class {
5490
5490
  constructor(name, fn) {
5491
5491
  this.name = name;
5492
5492
  this.fn = fn;
@@ -9174,6 +9174,9 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
9174
9174
  checkProvidesAreAddressable(lx, Comp);
9175
9175
  checkLookupShapes(lx, Comp);
9176
9176
  checkHandlersNotAsync(lx, Comp);
9177
+ checkRetiredBuckets(lx, Comp);
9178
+ checkRetiredCtxVerbs(lx, Comp);
9179
+ checkHandlerNameCollisions(lx, Comp);
9177
9180
  checkScopedStyleTopLevel(lx, Comp);
9178
9181
  const referencedAlters = /* @__PURE__ */ new Set();
9179
9182
  const referencedInputs = /* @__PURE__ */ new Set();
@@ -9187,7 +9190,6 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
9187
9190
  );
9188
9191
  }
9189
9192
  checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
9190
- checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
9191
9193
  checkUnreferencedDynamics(lx, Comp, referencedDynamics);
9192
9194
  return lx;
9193
9195
  });
@@ -9426,7 +9428,7 @@ function mkAttrValEnv(Comp, referencedAlters, referencedDynamics) {
9426
9428
  return { fields, proto, scope, alter, referencedAlters, dynamicMap, referencedDynamics };
9427
9429
  }
9428
9430
  function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
9429
- const { input, views, Class } = Comp;
9431
+ const { receive: input, views, Class } = Comp;
9430
9432
  const { prototype: proto } = Class;
9431
9433
  for (const viewName in views) {
9432
9434
  lx.push({ viewName }, () => {
@@ -9443,13 +9445,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
9443
9445
  if (input[name] === void 0) {
9444
9446
  const isMethodFix = protoHasMethod(proto, name);
9445
9447
  lx.error(
9446
- INPUT_HANDLER_NOT_IMPLEMENTED,
9448
+ RECEIVE_HANDLER_NOT_IMPLEMENTED,
9447
9449
  { name, handler, event, eventName, originAttr },
9448
9450
  isMethodFix ? { kind: "add-prefix", from: name, to: `$${name}` } : replaceNameSuggestion(name, Object.keys(input))
9449
9451
  );
9450
9452
  if (isMethodFix) {
9451
9453
  lx.hint(
9452
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
9454
+ METHOD_FOR_RECEIVE_HANDLER,
9453
9455
  { name, handler, event, eventName, originAttr },
9454
9456
  { kind: "add-prefix", from: name, to: `$${name}` }
9455
9457
  );
@@ -9461,13 +9463,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
9461
9463
  if (!protoHasMethod(proto, name)) {
9462
9464
  const isInputFix = input[name] !== void 0;
9463
9465
  lx.error(
9464
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
9466
+ RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
9465
9467
  { name, handler, event, eventName, originAttr },
9466
9468
  isInputFix ? { kind: "drop-prefix", from: `$${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto))
9467
9469
  );
9468
9470
  if (isInputFix) {
9469
9471
  lx.hint(
9470
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
9472
+ RECEIVE_HANDLER_FOR_METHOD,
9471
9473
  { name, handler, event, eventName, originAttr },
9472
9474
  { kind: "drop-prefix", from: `$${name}`, to: name }
9473
9475
  );
@@ -9616,12 +9618,57 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedDynamics) {
9616
9618
  });
9617
9619
  }
9618
9620
  }
9621
+ function checkRetiredBuckets(lx, Comp) {
9622
+ const extra = Comp.extra;
9623
+ if (!extra) return;
9624
+ for (const key of Object.keys(extra)) {
9625
+ if (!Object.hasOwn(RETIRED_BUCKETS, key)) continue;
9626
+ const retired = RETIRED_BUCKETS[key];
9627
+ const names = isPlainObj(extra[key]) ? Object.keys(extra[key]) : [];
9628
+ lx.error(RETIRED_HANDLER_BUCKET, { key, to: retired.to, how: retired.how, names });
9629
+ }
9630
+ }
9631
+ function checkRetiredCtxVerbs(lx, Comp) {
9632
+ for (const bucket of ["receive", "intent", "alter", "methods", "statics"]) {
9633
+ const block = bucket === "methods" || bucket === "statics" ? Comp.spec?.[bucket] : Comp[bucket];
9634
+ if (!isPlainObj(block)) continue;
9635
+ for (const name in block) {
9636
+ const fn = block[name];
9637
+ if (typeof fn !== "function") continue;
9638
+ const src = String(fn);
9639
+ for (const m of src.matchAll(RETIRED_CTX_RE)) {
9640
+ const verb = m[1];
9641
+ lx.error(RETIRED_CTX_VERB, { verb, to: RETIRED_CTX_VERBS[verb], bucket, name });
9642
+ }
9643
+ }
9644
+ }
9645
+ }
9646
+ function checkHandlerNameCollisions(lx, Comp) {
9647
+ const receive = Comp.receive ?? {};
9648
+ const intent = Comp.intent ?? {};
9649
+ for (const name in intent)
9650
+ if (receive[name] !== void 0)
9651
+ lx.error(HANDLER_NAME_COLLISION, {
9652
+ name,
9653
+ problem: `"${name}" is declared in both \`receive\` and \`intent\``,
9654
+ fix: "one is addressed and one is routed; give them different names so a reader can tell which a call site meant"
9655
+ });
9656
+ for (const name in receive)
9657
+ for (const suffix of ["Ok", "Error", "Unhandled"])
9658
+ if (receive[name + suffix] !== void 0 && intent[name] === void 0)
9659
+ lx.warn(HANDLER_NAME_COLLISION, {
9660
+ name,
9661
+ problem: `\`receive\` declares both "${name}" and its answer name "${name + suffix}"`,
9662
+ fix: `an intent named "${name}" would dispatch its answer to "${name + suffix}" - rename one unless that is what you meant`
9663
+ });
9664
+ }
9619
9665
  function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
9620
9666
  const extra = Comp.extra;
9621
9667
  if (!extra) return;
9622
9668
  let candidates = null;
9623
9669
  for (const key of Object.keys(extra)) {
9624
9670
  if (FRAMEWORK_WELL_KNOWN_EXTRAS.has(key) || wellKnownExtras.has(key)) continue;
9671
+ if (Object.hasOwn(RETIRED_BUCKETS, key)) continue;
9625
9672
  candidates ??= [
9626
9673
  ...KNOWN_COMPONENT_SPEC_KEYS,
9627
9674
  ...FRAMEWORK_WELL_KNOWN_EXTRAS,
@@ -9669,13 +9716,6 @@ function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
9669
9716
  }
9670
9717
  }
9671
9718
  }
9672
- function checkUnreferencedInputHandlers(lx, Comp, referencedInputs) {
9673
- for (const name in Comp.input) {
9674
- if (!referencedInputs.has(name)) {
9675
- lx.hint(INPUT_HANDLER_NOT_REFERENCED, { name });
9676
- }
9677
- }
9678
- }
9679
9719
  function checkHandlersNotAsync(lx, Comp) {
9680
9720
  for (const channel of HANDLER_CHANNELS) {
9681
9721
  const block = Comp[channel];
@@ -9794,7 +9834,7 @@ function checkUnreferencedDynamics(lx, Comp, referencedDynamics) {
9794
9834
  }
9795
9835
  }
9796
9836
  }
9797
- var KNOWN_COMPONENT_SPEC_KEYS, EMPTY_SET2, FRAMEWORK_WELL_KNOWN_EXTRAS, KNOWN_DIRECTIVE_NAMES, ALT_HANDLER_NOT_DEFINED, ALT_HANDLER_NOT_REFERENCED, DYN_VAL_NOT_DEFINED, DYN_ALIAS_NOT_REFERENCED, PROVIDE_NOT_ADDRESSABLE, LOOKUP_BAD_SHAPE, LOOKUP_TARGET_MALFORMED, RENDER_IT_OUTSIDE_OF_LOOP, UNKNOWN_EVENT_MODIFIER, UNKNOWN_HANDLER_ARG_NAME, INPUT_HANDLER_NOT_IMPLEMENTED, INPUT_HANDLER_NOT_REFERENCED, INPUT_HANDLER_METHOD_NOT_IMPLEMENTED, INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD, INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER, FIELD_VAL_NOT_DEFINED, FIELD_VAL_IS_METHOD, METHOD_VAL_NOT_DEFINED, METHOD_VAL_IS_FIELD, DUPLICATE_ATTR_DEFINITION, IF_NO_BRANCH_SET, UNKNOWN_COMPONENT_NAME, UNKNOWN_MACRO_ARG, UNKNOWN_DIRECTIVE, UNKNOWN_X_OP, UNKNOWN_X_ATTR, X_OP_IGNORES_CHILDREN, MAYBE_DROP_AT_PREFIX, MAYBE_ADD_AT_PREFIX, DEPRECATED_BARE_X_DIRECTIVE, BAD_VALUE, UNSUPPORTED_EXPR_SYNTAX, BINDING_MEMBER_TOO_DEEP, SUGGEST_BINDING_MEMBER, REDUNDANT_TEMPLATE_STRING, PLACEHOLDERLESS_TEMPLATE_STRING, CONSTANT_CONDITION, UNKNOWN_COMPONENT_SPEC_KEY, COMP_FIELD_BAD_SHAPE, ASYNC_HANDLER, TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE, GLOBAL_SELECTOR_IN_SCOPED_STYLE, FIELD_NAME_RESERVED_BY_RECORD, RECORD_FIELD_NAME_COLLISIONS, X_KNOWN_OP_NAMES, X_KNOWN_ATTR_NAMES, HOST_DIRECTIVE_ONLY_NAMES, LEVEL_WARN2, LEVEL_ERROR2, LEVEL_HINT, PARSE_ISSUES, BINDING_MEMBER_TOO_DEEP_RE, UNSUPPORTED_EXPR_GUIDANCE, HTML_LINT_OPTS, NO_WRAPPERS2, ANY_EVENT_MODIFIERS, KNOWN_HANDLER_NAMES, fixTo, BOOL_CONDITION_ORIGINS, isBoolConditionCtx, ATTR_VAL_CHECKERS, NODE_KIND_TO_CTX, HANDLER_CHANNELS, ASYNC_HANDLER_HELP, NON_NESTABLE_AT_RULE, GLOBAL_LEADING_SELECTOR, IGNORE_DIRECTIVE, blankRun, STYLE_TO_GLOBAL_HELP, KNOWN_LOOKUP_KEYS, LintContext, LintParseContext;
9837
+ var KNOWN_COMPONENT_SPEC_KEYS, EMPTY_SET2, FRAMEWORK_WELL_KNOWN_EXTRAS, KNOWN_DIRECTIVE_NAMES, ALT_HANDLER_NOT_DEFINED, ALT_HANDLER_NOT_REFERENCED, DYN_VAL_NOT_DEFINED, DYN_ALIAS_NOT_REFERENCED, PROVIDE_NOT_ADDRESSABLE, LOOKUP_BAD_SHAPE, LOOKUP_TARGET_MALFORMED, RENDER_IT_OUTSIDE_OF_LOOP, UNKNOWN_EVENT_MODIFIER, UNKNOWN_HANDLER_ARG_NAME, RECEIVE_HANDLER_NOT_IMPLEMENTED, RETIRED_HANDLER_BUCKET, RETIRED_CTX_VERB, HANDLER_NAME_COLLISION, RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED, RECEIVE_HANDLER_FOR_METHOD, METHOD_FOR_RECEIVE_HANDLER, FIELD_VAL_NOT_DEFINED, FIELD_VAL_IS_METHOD, METHOD_VAL_NOT_DEFINED, METHOD_VAL_IS_FIELD, DUPLICATE_ATTR_DEFINITION, IF_NO_BRANCH_SET, UNKNOWN_COMPONENT_NAME, UNKNOWN_MACRO_ARG, UNKNOWN_DIRECTIVE, UNKNOWN_X_OP, UNKNOWN_X_ATTR, X_OP_IGNORES_CHILDREN, MAYBE_DROP_AT_PREFIX, MAYBE_ADD_AT_PREFIX, DEPRECATED_BARE_X_DIRECTIVE, BAD_VALUE, UNSUPPORTED_EXPR_SYNTAX, BINDING_MEMBER_TOO_DEEP, SUGGEST_BINDING_MEMBER, REDUNDANT_TEMPLATE_STRING, PLACEHOLDERLESS_TEMPLATE_STRING, CONSTANT_CONDITION, UNKNOWN_COMPONENT_SPEC_KEY, COMP_FIELD_BAD_SHAPE, ASYNC_HANDLER, TOP_LEVEL_AT_RULE_IN_SCOPED_STYLE, GLOBAL_SELECTOR_IN_SCOPED_STYLE, FIELD_NAME_RESERVED_BY_RECORD, RECORD_FIELD_NAME_COLLISIONS, X_KNOWN_OP_NAMES, X_KNOWN_ATTR_NAMES, HOST_DIRECTIVE_ONLY_NAMES, LEVEL_WARN2, LEVEL_ERROR2, LEVEL_HINT, PARSE_ISSUES, BINDING_MEMBER_TOO_DEEP_RE, UNSUPPORTED_EXPR_GUIDANCE, HTML_LINT_OPTS, NO_WRAPPERS2, ANY_EVENT_MODIFIERS, KNOWN_HANDLER_NAMES, fixTo, BOOL_CONDITION_ORIGINS, isBoolConditionCtx, ATTR_VAL_CHECKERS, NODE_KIND_TO_CTX, isPlainObj, RETIRED_BUCKETS, RETIRED_CTX_VERBS, RETIRED_CTX_RE, HANDLER_CHANNELS, ASYNC_HANDLER_HELP, NON_NESTABLE_AT_RULE, GLOBAL_LEADING_SELECTOR, IGNORE_DIRECTIVE, blankRun, STYLE_TO_GLOBAL_HELP, KNOWN_LOOKUP_KEYS, LintContext, LintParseContext;
9798
9838
  var init_lint_check = __esm({
9799
9839
  "tools/core/lint-check.js"() {
9800
9840
  init_anode();
@@ -9806,7 +9846,7 @@ var init_lint_check = __esm({
9806
9846
  )
9807
9847
  );
9808
9848
  EMPTY_SET2 = /* @__PURE__ */ new Set();
9809
- FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["requestOverridesField"]);
9849
+ FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["intentOverridesField"]);
9810
9850
  KNOWN_DIRECTIVE_NAMES = /* @__PURE__ */ new Set([
9811
9851
  "dangerouslysetinnerhtml",
9812
9852
  "slot",
@@ -9831,11 +9871,13 @@ var init_lint_check = __esm({
9831
9871
  RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP";
9832
9872
  UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER";
9833
9873
  UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME";
9834
- INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED";
9835
- INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED";
9836
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED";
9837
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD";
9838
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER";
9874
+ RECEIVE_HANDLER_NOT_IMPLEMENTED = "RECEIVE_HANDLER_NOT_IMPLEMENTED";
9875
+ RETIRED_HANDLER_BUCKET = "RETIRED_HANDLER_BUCKET";
9876
+ RETIRED_CTX_VERB = "RETIRED_CTX_VERB";
9877
+ HANDLER_NAME_COLLISION = "HANDLER_NAME_COLLISION";
9878
+ RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED = "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED";
9879
+ RECEIVE_HANDLER_FOR_METHOD = "RECEIVE_HANDLER_FOR_METHOD";
9880
+ METHOD_FOR_RECEIVE_HANDLER = "METHOD_FOR_RECEIVE_HANDLER";
9839
9881
  FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED";
9840
9882
  FIELD_VAL_IS_METHOD = "FIELD_VAL_IS_METHOD";
9841
9883
  METHOD_VAL_NOT_DEFINED = "METHOD_VAL_NOT_DEFINED";
@@ -10028,8 +10070,34 @@ var init_lint_check = __esm({
10028
10070
  HideNode: { originAttr: "<x hide>" },
10029
10071
  PushViewNameNode: { originAttr: "<x push-view>" }
10030
10072
  };
10031
- HANDLER_CHANNELS = ["input", "receive", "bubble", "response", "alter"];
10032
- 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.";
10073
+ isPlainObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
10074
+ RETIRED_BUCKETS = {
10075
+ input: {
10076
+ to: "receive",
10077
+ 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"
10078
+ },
10079
+ bubble: {
10080
+ to: "intent",
10081
+ 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"
10082
+ },
10083
+ response: {
10084
+ to: "receive",
10085
+ 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"
10086
+ }
10087
+ };
10088
+ RETIRED_CTX_VERBS = {
10089
+ bubble: 'ctx.intent(name, args, { route: ["dyn"] }) - the route says it walks the ancestors, where the verb used to',
10090
+ 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',
10091
+ stopPropagation: "ctx.stop()",
10092
+ inputAtPath: "ctx.sendAtPath(path, name, args)",
10093
+ requestAtPath: 'ctx.intentAtPath(path, name, args, { route: ["lex"] })'
10094
+ };
10095
+ RETIRED_CTX_RE = new RegExp(
10096
+ `\\bctx\\.(${Object.keys(RETIRED_CTX_VERBS).join("|")})\\s*\\(`,
10097
+ "g"
10098
+ );
10099
+ HANDLER_CHANNELS = ["receive", "intent", "alter"];
10100
+ 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.`;
10033
10101
  NON_NESTABLE_AT_RULE = /(?:^|[\s;{}])@(?:-[a-z]+-)?(charset|import|namespace|font-face|keyframes|page|property|counter-style|font-feature-values|font-palette-values|view-transition)\b/gi;
10034
10102
  GLOBAL_LEADING_SELECTOR = /(?:^|[{}])\s*(html|body|:root)\b[^{};]*\{/gi;
10035
10103
  IGNORE_DIRECTIVE = /\/\*\s*tutuca-lint-ignore\s*\*\//g;
@@ -10135,36 +10203,30 @@ var init_lint_rules = __esm({
10135
10203
  group: "Field / method references",
10136
10204
  summary: "`$name` calls a method, but `name` is a field — use `.name`."
10137
10205
  },
10138
- // Input-handler / method confusion
10206
+ // Receive-handler / method confusion
10139
10207
  {
10140
- code: INPUT_HANDLER_NOT_IMPLEMENTED,
10208
+ code: RECEIVE_HANDLER_NOT_IMPLEMENTED,
10141
10209
  level: "error",
10142
- group: "Input-handler / method confusion",
10143
- summary: "Bare handler name has no entry in `input`."
10210
+ group: "Receive-handler / method confusion",
10211
+ summary: "Bare handler name has no entry in `receive`."
10144
10212
  },
10145
10213
  {
10146
- code: INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
10214
+ code: RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
10147
10215
  level: "error",
10148
- group: "Input-handler / method confusion",
10216
+ group: "Receive-handler / method confusion",
10149
10217
  summary: "`$handler` has no entry in `methods`."
10150
10218
  },
10151
10219
  {
10152
- code: INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
10220
+ code: RECEIVE_HANDLER_FOR_METHOD,
10153
10221
  level: "hint",
10154
- group: "Input-handler / method confusion",
10155
- summary: "`$name` is a method reference, but `name` is an input handler — drop the `$`."
10222
+ group: "Receive-handler / method confusion",
10223
+ summary: "`$name` is a method reference, but `name` is a receive handler — drop the `$`."
10156
10224
  },
10157
10225
  {
10158
- code: INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
10226
+ code: METHOD_FOR_RECEIVE_HANDLER,
10159
10227
  level: "hint",
10160
- group: "Input-handler / method confusion",
10161
- summary: "Bare `name` is an input-handler reference, but `name` is a method — add `$`."
10162
- },
10163
- {
10164
- code: INPUT_HANDLER_NOT_REFERENCED,
10165
- level: "hint",
10166
- group: "Input-handler / method confusion",
10167
- summary: "`input` entry is defined but never wired to an `@on.*` event."
10228
+ group: "Receive-handler / method confusion",
10229
+ summary: "Bare `name` is a receive-handler reference, but `name` is a method — add `$`."
10168
10230
  },
10169
10231
  // Iteration helpers (`alter`)
10170
10232
  {
@@ -10348,12 +10410,33 @@ var init_lint_rules = __esm({
10348
10410
  group: "Component spec",
10349
10411
  summary: "`component({...})` has an unrecognized key; its value is ignored at runtime."
10350
10412
  },
10413
+ // Retired four-channel vocabulary (TEMPORARY — remove a couple of releases after
10414
+ // the two-channel change). These exist to drive the migration: the linter reports
10415
+ // every site and says what replaces it, and a clean run IS the finished migration.
10416
+ {
10417
+ code: "RETIRED_HANDLER_BUCKET",
10418
+ level: "error",
10419
+ group: "Retired dispatch vocabulary",
10420
+ summary: "`input`, `bubble` and `response` are no longer handler buckets — four dispatch channels became two, `receive` (addressed) and `intent` (routed)."
10421
+ },
10422
+ {
10423
+ code: "RETIRED_CTX_VERB",
10424
+ level: "error",
10425
+ group: "Retired dispatch vocabulary",
10426
+ summary: "`ctx.bubble`, `ctx.request` and `ctx.stopPropagation` are gone — one `ctx.intent` carries a route, and `ctx.stop()` ends a walk."
10427
+ },
10428
+ {
10429
+ code: "HANDLER_NAME_COLLISION",
10430
+ level: "error",
10431
+ group: "Retired dispatch vocabulary",
10432
+ summary: "Two handlers collapse onto one name, or a `receive` arm collides with an intent's derived answer name."
10433
+ },
10351
10434
  // Handler effects
10352
10435
  {
10353
10436
  code: ASYNC_HANDLER,
10354
10437
  level: "error",
10355
10438
  group: "Handler effects",
10356
- summary: "A handler in `input`/`receive`/`bubble`/`response`/`alter` is an async function — handlers must be synchronous; use a request handler, `ctx.send`, or `ctx.bubble` instead."
10439
+ summary: "A handler in `receive`/`intent`/`alter` is an async function — handlers must be synchronous. Put the async work in a scope-registered intent handler and reach it with `ctx.intent`, or coordinate with `ctx.send`."
10357
10440
  },
10358
10441
  // Component styles
10359
10442
  {
@@ -10480,16 +10563,22 @@ function lintIdToMessage(id, info) {
10480
10563
  }
10481
10564
  case "UNKNOWN_HANDLER_ARG_NAME":
10482
10565
  return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
10483
- case "INPUT_HANDLER_NOT_IMPLEMENTED":
10484
- return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
10485
- case "INPUT_HANDLER_NOT_REFERENCED":
10486
- return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
10487
- case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
10566
+ case "RETIRED_HANDLER_BUCKET": {
10567
+ const which = info.names?.length ? ` (${info.names.join(", ")})` : "";
10568
+ return `\`${info.key}\` is no longer a handler bucket${which} — ${info.how}`;
10569
+ }
10570
+ case "RETIRED_CTX_VERB":
10571
+ return `ctx.${info.verb}() no longer exists — use ${info.to} (in \`${info.bucket}.${info.name}\`)`;
10572
+ case "HANDLER_NAME_COLLISION":
10573
+ return `${info.problem} — ${info.fix}`;
10574
+ case "RECEIVE_HANDLER_NOT_IMPLEMENTED":
10575
+ return `Receive handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
10576
+ case "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED":
10488
10577
  return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
10489
- case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
10490
- return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
10491
- case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
10492
- return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
10578
+ case "RECEIVE_HANDLER_FOR_METHOD":
10579
+ return `'$${info.name}' is a method reference, but '${info.name}' is defined as a receive handler${fmtEventSuffix(info)}`;
10580
+ case "METHOD_FOR_RECEIVE_HANDLER":
10581
+ return `'${info.name}' is a receive handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
10493
10582
  case "FIELD_NAME_RESERVED_BY_RECORD":
10494
10583
  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}'))`;
10495
10584
  case "FIELD_VAL_NOT_DEFINED":
@@ -10677,7 +10766,7 @@ function fmtModuleInfo(info) {
10677
10766
  lines.push(`Exports: ${[...info.present].join(", ") || "(none)"}`);
10678
10767
  lines.push(`Components: ${info.counts.components}`);
10679
10768
  lines.push(`Macros: ${info.counts.macros}`);
10680
- lines.push(`Request handlers: ${info.counts.requestHandlers}`);
10769
+ lines.push(`Request handlers: ${info.counts.intentHandlers}`);
10681
10770
  lines.push(`Examples: ${info.counts.examples} (sections: ${info.counts.sections})`);
10682
10771
  if (info.warnings.length) {
10683
10772
  lines.push("");
@@ -10730,9 +10819,13 @@ function fmtComponentDocs(docs) {
10730
10819
  lines.push(" methods:");
10731
10820
  for (const m of c.methods) lines.push(` ${m.sig}`);
10732
10821
  }
10733
- if (c.input.length) {
10734
- lines.push(" input:");
10735
- for (const m of c.input) lines.push(` ${m.sig}`);
10822
+ for (const [label, bucket] of [
10823
+ ["receive", c.receive],
10824
+ ["intent", c.intent]
10825
+ ]) {
10826
+ if (!bucket?.length) continue;
10827
+ lines.push(` ${label}:`);
10828
+ for (const m of bucket) lines.push(` ${m.sig}`);
10736
10829
  }
10737
10830
  if (c.fields.length) {
10738
10831
  lines.push(" fields:");
@@ -10928,9 +11021,14 @@ function fmtComponentDocs2(docs) {
10928
11021
  }
10929
11022
  lines.push("");
10930
11023
  }
10931
- if (comp.input.length > 0) {
10932
- lines.push("## Input Handlers\n");
10933
- for (const m of comp.input) {
11024
+ for (const [title, bucket] of [
11025
+ ["Receive Handlers", comp.receive],
11026
+ ["Intent Handlers", comp.intent]
11027
+ ]) {
11028
+ if (!bucket?.length) continue;
11029
+ lines.push(`## ${title}
11030
+ `);
11031
+ for (const m of bucket) {
10934
11032
  lines.push(`- \`${m.sig}\``);
10935
11033
  }
10936
11034
  lines.push("");
@@ -11016,7 +11114,7 @@ function fmtModuleInfo2(info) {
11016
11114
  lines.push(`- Exports: ${[...info.present].map((k) => `\`${k}\``).join(", ") || "(none)"}`);
11017
11115
  lines.push(`- Components: ${info.counts.components}`);
11018
11116
  lines.push(`- Macros: ${info.counts.macros}`);
11019
- lines.push(`- Request handlers: ${info.counts.requestHandlers}`);
11117
+ lines.push(`- Request handlers: ${info.counts.intentHandlers}`);
11020
11118
  lines.push(`- Examples: ${info.counts.examples} (sections: ${info.counts.sections})`);
11021
11119
  if (info.warnings.length) {
11022
11120
  lines.push("", "## Warnings", "");
@@ -15513,9 +15611,9 @@ function parseExample(raw, index, components, parentPath) {
15513
15611
  if (raw.value === void 0) {
15514
15612
  throw shapeError(`example at ${where} missing "value"`, where);
15515
15613
  }
15516
- const rh = raw.requestHandlers;
15614
+ const rh = raw.intentHandlers;
15517
15615
  if (rh != null && (typeof rh !== "object" || Array.isArray(rh))) {
15518
- throw shapeError(`example at ${where} "requestHandlers" must be an object of functions`, where);
15616
+ throw shapeError(`example at ${where} "intentHandlers" must be an object of functions`, where);
15519
15617
  }
15520
15618
  const on = raw.on;
15521
15619
  if (on != null) {
@@ -15534,7 +15632,7 @@ function parseExample(raw, index, components, parentPath) {
15534
15632
  }
15535
15633
  if (!isPlainObject2(on[key])) {
15536
15634
  throw shapeError(
15537
- `example at ${where} "on.${key}" must be an object of actions (send, bubble, request, input, do)`,
15635
+ `example at ${where} "on.${key}" must be an object of actions (send, intent, do)`,
15538
15636
  where
15539
15637
  );
15540
15638
  }
@@ -15546,7 +15644,7 @@ function parseExample(raw, index, components, parentPath) {
15546
15644
  value: raw.value,
15547
15645
  view: raw.view ?? "main",
15548
15646
  componentName: resolveComponentName(raw.value, components),
15549
- requestHandlers: rh ?? null,
15647
+ intentHandlers: rh ?? null,
15550
15648
  requestHandlerNames: rh ? Object.keys(rh) : [],
15551
15649
  on: on ?? null
15552
15650
  });
@@ -15590,7 +15688,7 @@ function normalizeModule(mod, { path = null } = {}) {
15590
15688
  for (const key of [
15591
15689
  "getComponents",
15592
15690
  "getMacros",
15593
- "getRequestHandlers",
15691
+ "getIntentHandlers",
15594
15692
  "getExamples",
15595
15693
  "getTests",
15596
15694
  "getRoot"
@@ -15599,7 +15697,7 @@ function normalizeModule(mod, { path = null } = {}) {
15599
15697
  }
15600
15698
  const components = present.has("getComponents") ? mod.getComponents() : [];
15601
15699
  const macros = present.has("getMacros") ? mod.getMacros() : null;
15602
- const requestHandlers = present.has("getRequestHandlers") ? mod.getRequestHandlers() : null;
15700
+ const intentHandlers = present.has("getIntentHandlers") ? mod.getIntentHandlers() : null;
15603
15701
  const root = present.has("getRoot") ? mod.getRoot() : null;
15604
15702
  const sections = present.has("getExamples") ? parseSections(mod.getExamples(), components) : [];
15605
15703
  return {
@@ -15608,7 +15706,7 @@ function normalizeModule(mod, { path = null } = {}) {
15608
15706
  path,
15609
15707
  components,
15610
15708
  macros,
15611
- requestHandlers,
15709
+ intentHandlers,
15612
15710
  sections,
15613
15711
  root
15614
15712
  }),
@@ -15653,7 +15751,7 @@ var init_module = __esm({
15653
15751
  value,
15654
15752
  view = "main",
15655
15753
  componentName = null,
15656
- requestHandlers = null,
15754
+ intentHandlers = null,
15657
15755
  requestHandlerNames = [],
15658
15756
  on = null
15659
15757
  }) {
@@ -15662,7 +15760,7 @@ var init_module = __esm({
15662
15760
  this.value = value;
15663
15761
  this.view = view;
15664
15762
  this.componentName = componentName;
15665
- this.requestHandlers = requestHandlers;
15763
+ this.intentHandlers = intentHandlers;
15666
15764
  this.requestHandlerNames = requestHandlerNames;
15667
15765
  this.on = on;
15668
15766
  }
@@ -15677,12 +15775,12 @@ var init_module = __esm({
15677
15775
  }
15678
15776
  };
15679
15777
  NormalizedModule = class {
15680
- constructor({ mod, path = null, components, macros, requestHandlers, sections, root }) {
15778
+ constructor({ mod, path = null, components, macros, intentHandlers, sections, root }) {
15681
15779
  this.mod = mod;
15682
15780
  this.path = path;
15683
15781
  this.components = components;
15684
15782
  this.macros = macros;
15685
- this.requestHandlers = requestHandlers;
15783
+ this.intentHandlers = intentHandlers;
15686
15784
  this.sections = sections;
15687
15785
  this.root = root;
15688
15786
  }
@@ -15840,7 +15938,7 @@ function describeModule(mod, { path = null } = {}) {
15840
15938
  const counts = {
15841
15939
  components: normalized.components.length,
15842
15940
  macros: normalized.macros ? Object.keys(normalized.macros).length : 0,
15843
- requestHandlers: normalized.requestHandlers ? Object.keys(normalized.requestHandlers).length : 0,
15941
+ intentHandlers: normalized.intentHandlers ? Object.keys(normalized.intentHandlers).length : 0,
15844
15942
  examples: normalized.sections.reduce((n, s) => n + s.items.length, 0),
15845
15943
  sections: normalized.sections.length
15846
15944
  };
@@ -16027,10 +16125,9 @@ function getComponentDoc(comp) {
16027
16125
  name: k,
16028
16126
  sig: getSignature(k, methods[k])
16029
16127
  }));
16030
- const inputHandlers = Object.keys(comp.input).map((k) => ({
16031
- name: k,
16032
- sig: getSignature(k, comp.input[k])
16033
- }));
16128
+ const sigs = (bucket) => Object.keys(bucket ?? {}).map((k) => ({ name: k, sig: getSignature(k, bucket[k]) }));
16129
+ const receiveHandlers = sigs(comp.receive);
16130
+ const intentHandlers = sigs(comp.intent);
16034
16131
  const fieldDocs = [];
16035
16132
  for (const fieldName in fields) {
16036
16133
  const field = fields[fieldName];
@@ -16041,7 +16138,13 @@ function getComponentDoc(comp) {
16041
16138
  methods: getFieldMethods(field)
16042
16139
  });
16043
16140
  }
16044
- return { name, methods: userMethods, input: inputHandlers, fields: fieldDocs };
16141
+ return {
16142
+ name,
16143
+ methods: userMethods,
16144
+ receive: receiveHandlers,
16145
+ intent: intentHandlers,
16146
+ fields: fieldDocs
16147
+ };
16045
16148
  }
16046
16149
  function docComponents(normalized, { name = null } = {}) {
16047
16150
  const comps = normalized.components;
@@ -16077,8 +16180,11 @@ var init_components = __esm({
16077
16180
  getHandlerFor(v, name, key) {
16078
16181
  return this.getCompFor(v)?.[key][name] ?? null;
16079
16182
  }
16080
- getRequestFor(v, name) {
16081
- return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
16183
+ // The `lex` leg of an intent's route: the handlers registered on the scope chain of
16184
+ // the component that raised it, innermost first. A list, not a first match — see
16185
+ // ComponentStack.lookupIntentChain.
16186
+ getIntentChainFor(v, name) {
16187
+ return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
16082
16188
  }
16083
16189
  compileStyles() {
16084
16190
  const styles2 = [];
@@ -16091,7 +16197,7 @@ var init_components = __esm({
16091
16197
  this.comps = comps;
16092
16198
  this.parent = parent;
16093
16199
  this.byName = {};
16094
- this.reqsByName = {};
16200
+ this.intentsByName = {};
16095
16201
  this.macros = {};
16096
16202
  }
16097
16203
  enter() {
@@ -16123,11 +16229,21 @@ var init_components = __esm({
16123
16229
  getCompFor(v) {
16124
16230
  return this.comps.getCompFor(v);
16125
16231
  }
16126
- registerRequestHandlers(handlers) {
16127
- for (const name in handlers) this.reqsByName[name] = new RequestHandler(name, handlers[name]);
16232
+ // Handlers that answer an intent's `lex` leg. A name maps to a LIST, because the leg
16233
+ // walks: a handler that returns PASS declines and hands the intent to the next one.
16234
+ // A bare function is accepted as a one-element list.
16235
+ registerIntentHandlers(handlers) {
16236
+ for (const name in handlers) {
16237
+ const fns = Array.isArray(handlers[name]) ? handlers[name] : [handlers[name]];
16238
+ this.intentsByName[name] = fns.map((fn) => new IntentHandler(name, fn));
16239
+ }
16128
16240
  }
16129
- lookupRequest(name) {
16130
- return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
16241
+ // Innermost scope first, then outward. Concatenated rather than first-match, so a
16242
+ // declining handler can hand the intent to one further up.
16243
+ lookupIntentChain(name) {
16244
+ const here = this.intentsByName[name] ?? [];
16245
+ const up = this.parent?.lookupIntentChain(name) ?? [];
16246
+ return up.length === 0 ? here : here.concat(up);
16131
16247
  }
16132
16248
  lookupComponent(name) {
16133
16249
  return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
@@ -16136,7 +16252,7 @@ var init_components = __esm({
16136
16252
  return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
16137
16253
  }
16138
16254
  };
16139
- _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
16255
+ _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
16140
16256
  KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
16141
16257
  }
16142
16258
  });
@@ -16148,7 +16264,7 @@ function lintComponents(normalized, { name = null, LintParseContextClass, wellKn
16148
16264
  const stack = new ComponentStack();
16149
16265
  stack.registerComponents(comps);
16150
16266
  if (normalized.macros) stack.registerMacros(normalized.macros);
16151
- if (normalized.requestHandlers) stack.registerRequestHandlers(normalized.requestHandlers);
16267
+ if (normalized.intentHandlers) stack.registerIntentHandlers(normalized.intentHandlers);
16152
16268
  const results = [];
16153
16269
  for (const comp of picked) {
16154
16270
  comp.compile(LintParseContextClass);
@@ -16361,22 +16477,19 @@ var init_stack = __esm({
16361
16477
  });
16362
16478
 
16363
16479
  // src/transactor.js
16364
- function mkReq404(name) {
16365
- const fn = () => {
16366
- throw new Error(`Request not found: ${name}`);
16367
- };
16368
- return { fn };
16369
- }
16370
16480
  function nullHandler() {
16371
16481
  return this;
16372
16482
  }
16373
16483
  function getValue(e) {
16374
16484
  return e.target.type === "checkbox" ? e.target.checked : (e instanceof CustomEvent ? e.detail : e.target.value) ?? null;
16375
16485
  }
16486
+ function warnNotIntent(verb) {
16487
+ console.warn(`ctx.${verb}() is only meaningful in an "intent" handler - ignored`);
16488
+ }
16376
16489
  function rootDispatcher(transactor) {
16377
16490
  return new Dispatcher(new Path([]), transactor, null);
16378
16491
  }
16379
- var State2, Transactor, Transaction, toNullIfNaN, InputEvent, NameArgsTransaction, ResponseEvent, SendEvent, BubbleEvent, InputDispatchEvent, Completion, Dispatcher, EventContext, RequestContext, PathChanges;
16492
+ var State2, Transactor, Transaction, toNullIfNaN, InputEvent, NameArgsTransaction, SendEvent, IntentEvent, PASS, DEFAULT_ROUTE, INTENT_DEPTH, IntentWalk, Completion, Dispatcher, EventContext, IntentContext, PathChanges;
16380
16493
  var init_transactor = __esm({
16381
16494
  "src/transactor.js"() {
16382
16495
  init_path();
@@ -16413,8 +16526,11 @@ var init_transactor = __esm({
16413
16526
  this.transactions.push(t);
16414
16527
  this.onTransactionPushed(t);
16415
16528
  }
16416
- // Subscribe to a normalized record for every handler invocation (send/receive,
16417
- // bubble, request/response, input). Returns an unsubscribe function. Records
16529
+ // Subscribe to a normalized record for every handler invocation. `kind` is the
16530
+ // handler bucket (`receive` / `intent`), plus `answer` — an observation-only kind for
16531
+ // a message the runtime wrote to answer an intent. The BUCKET does not know about
16532
+ // that distinction, and must not: a handler cannot tell an answer from a message its
16533
+ // parent sent. Returns an unsubscribe function. Records
16418
16534
  // carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
16419
16535
  // matched, before, after, parent, timestamp. Purely observational.
16420
16536
  observe(cb) {
@@ -16467,23 +16583,14 @@ var init_transactor = __esm({
16467
16583
  this.pushTransaction(t);
16468
16584
  return this._link(t, parent);
16469
16585
  }
16470
- pushInput(path, name, args = [], opts = {}, parent = null) {
16471
- const t = new InputDispatchEvent(path, this, name, args, parent, opts);
16472
- this.pushTransaction(t);
16473
- return this._link(t, parent);
16474
- }
16475
- pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
16476
- const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
16477
- const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
16478
- this.pushTransaction(t);
16479
- return this._link(t, parent);
16480
- }
16481
- pushRequest(path, name, args = [], opts = {}, parent = null) {
16586
+ // Raise an intent: a job the sender did not address, walked along a route until
16587
+ // something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
16588
+ // `opts.livePath` opts out of pinning the answer's destination keys.
16589
+ pushIntent(path, name, args = [], opts = {}, parent = null) {
16482
16590
  const release = parent ? parent.completion.track() : null;
16483
- const p = this._runRequest(path, name, args, opts, parent, release);
16484
- this._inflight.add(p);
16485
- p.finally(() => this._inflight.delete(p));
16486
- return p;
16591
+ const walk = new IntentWalk(this, path, name, args, opts, parent, release);
16592
+ walk.advance();
16593
+ return walk;
16487
16594
  }
16488
16595
  // Drain queued transactions and await in-flight requests until quiescent. Each
16489
16596
  // awaited request enqueues a ResponseEvent, which may dispatch more work, so we
@@ -16494,57 +16601,6 @@ var init_transactor = __esm({
16494
16601
  if (this._inflight.size) await Promise.allSettled([...this._inflight]);
16495
16602
  }
16496
16603
  }
16497
- async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
16498
- let released = false;
16499
- const transfer = (t) => {
16500
- if (release) {
16501
- released = true;
16502
- t.completion.whenSubtreeSettled().then(release);
16503
- }
16504
- };
16505
- try {
16506
- const curRoot = this.state.val;
16507
- const txnPath = path.toTransactionPath();
16508
- const curLeaf = txnPath.lookup(curRoot);
16509
- const found = this.comps.getRequestFor(curLeaf, name);
16510
- const handler = found ?? mkReq404(name);
16511
- if (this._observers.length > 0) {
16512
- const reqPath = txnPath.pinKeys(curRoot);
16513
- this._emit({
16514
- kind: "request",
16515
- name,
16516
- args,
16517
- path: reqPath,
16518
- pathKeys: reqPath.toKeys(),
16519
- targetPath: path,
16520
- handler: found?.fn ?? null,
16521
- handlerName: found?.fn?.name || name,
16522
- matched: found ? "exact" : "none",
16523
- before: curLeaf,
16524
- after: void 0,
16525
- parent,
16526
- timestamp: Date.now()
16527
- });
16528
- }
16529
- const reqCtx = new RequestContext(path, this, parent, curRoot);
16530
- const resHandlerName = opts?.onResName ?? name;
16531
- const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
16532
- const push = (specificName, baseName, singleArg, result, error) => {
16533
- const resArgs = specificName ? [singleArg] : [result, error];
16534
- const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
16535
- transfer(t);
16536
- this.pushTransaction(t);
16537
- };
16538
- try {
16539
- const result = await handler.fn.apply(null, [...args, reqCtx]);
16540
- push(opts?.onOkName, resHandlerName, result, result, null);
16541
- } catch (error) {
16542
- push(opts?.onErrorName, resHandlerName, error, null, error);
16543
- }
16544
- } finally {
16545
- if (release && !released) release();
16546
- }
16547
- }
16548
16604
  get hasPendingTransactions() {
16549
16605
  return this.transactions.length > 0;
16550
16606
  }
@@ -16561,6 +16617,7 @@ var init_transactor = __esm({
16561
16617
  this._emitTransaction(transaction, curState);
16562
16618
  } else console.warn("undefined new state", { curState, transaction });
16563
16619
  } finally {
16620
+ transaction.ensureWalkAdvanced?.();
16564
16621
  transaction._completion?.ensureSelfSettled();
16565
16622
  transaction._completion?.releaseSelf();
16566
16623
  }
@@ -16596,6 +16653,15 @@ var init_transactor = __esm({
16596
16653
  }
16597
16654
  afterTransaction() {
16598
16655
  }
16656
+ // Ending a walk and forwarding belong to the two dispatch buckets; the base warns
16657
+ // rather than throwing, so a stray call from the wrong place is a message and not a
16658
+ // crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
16659
+ stop() {
16660
+ console.warn('ctx.stop() is only meaningful in an "intent" handler - ignored');
16661
+ }
16662
+ forward(_opts) {
16663
+ console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
16664
+ }
16599
16665
  buildRootStack(root, comps) {
16600
16666
  return Stack2.root(comps, root);
16601
16667
  }
@@ -16654,18 +16720,40 @@ var init_transactor = __esm({
16654
16720
  this._dispatchPath ??= this.path.compact();
16655
16721
  return this._dispatchPath;
16656
16722
  }
16723
+ // A DOM event is an ADDRESSED message like any other — it reaches the component that
16724
+ // owns the view and stops. It keeps its own class only because it resolves its handler
16725
+ // from the compiled view and runs synchronously, not because it is a different channel.
16657
16726
  get observeKind() {
16658
- return "input";
16727
+ return "receive";
16659
16728
  }
16660
16729
  get observeName() {
16661
16730
  return this.e?.type ?? null;
16662
16731
  }
16732
+ // A view's name is a message. `forward` is where that name can LEAVE the component:
16733
+ // it becomes an intent with the same name and the same arguments, so a view that says
16734
+ // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
16735
+ // (A `$method` handler has a name too, so forwarding one raises an intent under it.)
16736
+ forward(opts) {
16737
+ this._forward = opts ?? {};
16738
+ }
16739
+ afterTransaction() {
16740
+ const f = this._forward;
16741
+ if (f === void 0) return;
16742
+ this._forward = void 0;
16743
+ const { args = this._handlerArgs ?? [], ...rest } = f;
16744
+ const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
16745
+ const name = hv?.name;
16746
+ if (name === void 0)
16747
+ return console.warn("ctx.forward() from a handler with no name - ignored");
16748
+ this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
16749
+ }
16663
16750
  buildRootStack(root, comps) {
16664
16751
  return Stack2.root(comps, root, this);
16665
16752
  }
16666
16753
  getHandlerAndArgs(root, _instance, comps) {
16667
16754
  const stack = this.buildStack(root, comps);
16668
16755
  const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
16756
+ this._handlerArgs = [...args];
16669
16757
  const path = this.dispatchPath;
16670
16758
  args.push(new EventContext(path, this.transactor, this));
16671
16759
  return [handler, args];
@@ -16750,39 +16838,190 @@ var init_transactor = __esm({
16750
16838
  return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
16751
16839
  }
16752
16840
  };
16753
- ResponseEvent = class extends NameArgsTransaction {
16754
- handlerProp = "response";
16755
- constructor(path, transactor, name, args, parent, txnPath = null) {
16756
- super(path, transactor, name, args, parent);
16757
- this._txnPath = txnPath;
16758
- }
16759
- getTransactionPath() {
16760
- return this._txnPath ?? super.getTransactionPath();
16761
- }
16762
- };
16763
16841
  SendEvent = class extends NameArgsTransaction {
16764
16842
  handlerProp = "receive";
16765
- run(rootVal, comps) {
16766
- return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
16843
+ // `receive` is the ADDRESSED bucket, and it is the only one: a view's `@on.click`,
16844
+ // a parent's ctx.send, the host's sendAtRoot and an answer to an intent all arrive
16845
+ // here, and a handler cannot tell them apart. Splitting on where a message came from
16846
+ // would let the view's name through and turn the parent's away, though the two say
16847
+ // the same thing — and neither a test nor a parent could drive the component.
16848
+ get observeKind() {
16849
+ return this._isAnswer ? "answer" : "receive";
16850
+ }
16851
+ // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
16852
+ // intent, keeping its name and payload. This is what lets a view's name leave the
16853
+ // component without the view changing.
16854
+ forward(opts) {
16855
+ this._forward = opts ?? {};
16767
16856
  }
16768
16857
  afterTransaction() {
16769
- const { path, name, args, opts, targetPath } = this;
16770
- if (opts.bubbles && path.steps.length > 0)
16771
- this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
16858
+ const f = this._forward;
16859
+ if (f === void 0) return;
16860
+ this._forward = void 0;
16861
+ const { args = this.args, ...rest } = f;
16862
+ this.transactor.pushIntent(this.path, this.name, args, rest, this);
16772
16863
  }
16773
16864
  };
16774
- BubbleEvent = class extends SendEvent {
16775
- handlerProp = "bubble";
16776
- constructor(path, transactor, name, args, parent, opts, targetPath) {
16777
- super(path, transactor, name, args, parent, opts);
16778
- this.targetPath = targetPath ?? path;
16865
+ IntentEvent = class extends NameArgsTransaction {
16866
+ handlerProp = "intent";
16867
+ constructor(path, transactor, walk) {
16868
+ super(path, transactor, walk.name, walk.args, walk.parent, {});
16869
+ this.walk = walk;
16870
+ this.targetPath = walk.origin;
16871
+ }
16872
+ // `forward` in an INTENT body amends the hop about to be pushed — it does not push
16873
+ // one itself, because a walk advances on its own.
16874
+ forward(opts) {
16875
+ this.walk.amend(opts, this.path);
16779
16876
  }
16780
- stopPropagation() {
16781
- this.opts.bubbles = false;
16877
+ stop() {
16878
+ this.walk.finish(null, null);
16879
+ }
16880
+ afterTransaction() {
16881
+ this.ensureWalkAdvanced();
16882
+ }
16883
+ // Idempotent, because both afterTransaction and the transactor's `finally` call it —
16884
+ // whichever gets there first advances the walk exactly once.
16885
+ ensureWalkAdvanced() {
16886
+ if (this._advanced) return;
16887
+ this._advanced = true;
16888
+ this.walk.advance();
16782
16889
  }
16783
16890
  };
16784
- InputDispatchEvent = class extends NameArgsTransaction {
16785
- handlerProp = "input";
16891
+ PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
16892
+ DEFAULT_ROUTE = ["dyn", "lex"];
16893
+ INTENT_DEPTH = 64;
16894
+ IntentWalk = class {
16895
+ constructor(transactor, path, name, args, opts, parent, release) {
16896
+ this.transactor = transactor;
16897
+ this.name = name;
16898
+ this.args = args;
16899
+ this.route = opts?.route ?? DEFAULT_ROUTE;
16900
+ this.parent = parent;
16901
+ this.release = release;
16902
+ this.origin = path;
16903
+ this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
16904
+ this.legIndex = 0;
16905
+ this.dynAt = path;
16906
+ this.hops = 0;
16907
+ this.ended = false;
16908
+ }
16909
+ // Offer the intent to the next hop. Called once at dispatch, and again by every hop
16910
+ // that ran without answering.
16911
+ advance() {
16912
+ if (this.ended) return;
16913
+ if (this.hops++ >= INTENT_DEPTH) return this.exhaust("intentDepth");
16914
+ while (this.legIndex < this.route.length) {
16915
+ const leg = this.route[this.legIndex];
16916
+ if (leg === "dyn") {
16917
+ if (this.dynAt.steps.length === 0) {
16918
+ this.legIndex++;
16919
+ continue;
16920
+ }
16921
+ this.dynAt = this.dynAt.popStep();
16922
+ this.transactor.pushTransaction(new IntentEvent(this.dynAt, this.transactor, this));
16923
+ return;
16924
+ }
16925
+ if (leg === "lex") {
16926
+ this.legIndex++;
16927
+ return this._tryLex();
16928
+ }
16929
+ console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
16930
+ this.legIndex++;
16931
+ }
16932
+ this.exhaust("noHandler");
16933
+ }
16934
+ // The `lex` leg: the handlers registered on the scope chain of the component that
16935
+ // raised the intent, in order.
16936
+ _tryLex() {
16937
+ const root = this.transactor.state.val;
16938
+ const txnPath = this.origin.toTransactionPath();
16939
+ const leaf = txnPath.lookup(root);
16940
+ const chain = this.transactor.comps.getIntentChainFor(leaf, this.name);
16941
+ if (this.transactor._observers.length > 0) {
16942
+ const path = txnPath.pinKeys(root);
16943
+ this.transactor._emit({
16944
+ kind: "intent",
16945
+ name: this.name,
16946
+ args: this.args,
16947
+ path,
16948
+ pathKeys: path.toKeys(),
16949
+ targetPath: this.origin,
16950
+ handler: chain[0]?.fn ?? null,
16951
+ handlerName: chain[0]?.fn?.name || this.name,
16952
+ matched: chain.length > 0 ? "exact" : "none",
16953
+ before: leaf,
16954
+ after: void 0,
16955
+ parent: this.parent,
16956
+ timestamp: Date.now()
16957
+ });
16958
+ }
16959
+ if (chain.length === 0) return this.advance();
16960
+ const p = this._runLex(chain, 0);
16961
+ this.transactor._inflight.add(p);
16962
+ p.finally(() => this.transactor._inflight.delete(p));
16963
+ }
16964
+ async _runLex(chain, i) {
16965
+ if (this.ended) return;
16966
+ if (i >= chain.length) return this.advance();
16967
+ const ctx = new IntentContext(this.origin, this.transactor, this.parent);
16968
+ try {
16969
+ const res = await chain[i].fn.apply(null, [...this.args, ctx]);
16970
+ if (res === PASS) return this._runLex(chain, i + 1);
16971
+ this.answer("Ok", res);
16972
+ } catch (error) {
16973
+ this.answer("Error", error);
16974
+ }
16975
+ }
16976
+ // A hop answered. Ends the walk.
16977
+ answer(suffix, value) {
16978
+ this.finish(`${this.name}${suffix}`, [value]);
16979
+ }
16980
+ // `forward` from an intent body: keep walking, optionally with new arguments or a
16981
+ // narrowed route.
16982
+ amend(opts, from) {
16983
+ if (opts?.args !== void 0) this.args = opts.args;
16984
+ if (opts?.route !== void 0) {
16985
+ this.route = opts.route;
16986
+ this.legIndex = 0;
16987
+ this.dynAt = from ?? this.dynAt;
16988
+ }
16989
+ }
16990
+ // The route ran out. What the sender hears depends only on what it DECLARES, which is
16991
+ // how "a sender expects an answer if and only if it declares an answer arm" becomes
16992
+ // code — nobody writes it down twice, and there is no schema to keep in step.
16993
+ exhaust(reason) {
16994
+ if (this.ended) return;
16995
+ const { name, args } = this;
16996
+ const comp = this.transactor.comps.getCompFor(
16997
+ this.origin.toTransactionPath().lookup(this.transactor.state.val)
16998
+ );
16999
+ const declares = (n) => comp?.receive?.[n] !== void 0;
17000
+ if (declares(`${name}Unhandled`)) {
17001
+ this.finish(`${name}Unhandled`, args);
17002
+ } else if (declares(`${name}Error`)) {
17003
+ this.finish(`${name}Error`, [reason]);
17004
+ } else {
17005
+ if (declares(`${name}Ok`))
17006
+ console.warn(
17007
+ `intent "${name}" was not answered (${reason}) and this component declares only "${name}Ok" - add "${name}Unhandled" or "${name}Error" to handle it`
17008
+ );
17009
+ this.finish(null, null);
17010
+ }
17011
+ }
17012
+ // End the walk. `name === null` means no answer goes out, which is what `stop` and a
17013
+ // fire-and-forget notification both are — the tracked unit is released here instead
17014
+ // of following an answer's subtree.
17015
+ finish(name, args) {
17016
+ if (this.ended) return;
17017
+ this.ended = true;
17018
+ if (name === null) return this.release?.();
17019
+ const path = this.answerPath ?? this.origin;
17020
+ const t = new SendEvent(path, this.transactor, name, args, this.parent, {});
17021
+ t._isAnswer = true;
17022
+ this.transactor.pushTransaction(t);
17023
+ if (this.release) t.completion.whenSubtreeSettled().then(this.release);
17024
+ }
16786
17025
  };
16787
17026
  Completion = class {
16788
17027
  constructor() {
@@ -16863,23 +17102,21 @@ var init_transactor = __esm({
16863
17102
  get at() {
16864
17103
  return new PathChanges(this);
16865
17104
  }
17105
+ // A message is ADDRESSED: it names one component and stops there.
16866
17106
  send(name, args, opts) {
16867
17107
  return this.sendAtPath(this.path, name, args, opts);
16868
17108
  }
16869
- bubble(name, args, opts) {
16870
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
16871
- }
16872
17109
  sendAtPath(path, name, args, opts) {
16873
17110
  return this.transactor.pushSend(path, name, args, opts, this.parent);
16874
17111
  }
16875
- request(name, args, opts) {
16876
- return this.requestAtPath(this.path, name, args, opts);
17112
+ // An intent is ROUTED: it names a job and walks until something answers. The verb
17113
+ // does not decide which scope answers — `opts.route` does, written here at the call
17114
+ // site, which is where the decision actually is.
17115
+ intent(name, args, opts) {
17116
+ return this.intentAtPath(this.path, name, args, opts);
16877
17117
  }
16878
- requestAtPath(path, name, args, opts) {
16879
- return this.transactor.pushRequest(path, name, args, opts, this.parent);
16880
- }
16881
- inputAtPath(path, name, args, opts) {
16882
- return this.transactor.pushInput(path, name, args, opts, this.parent);
17118
+ intentAtPath(path, name, args, opts) {
17119
+ return this.transactor.pushIntent(path, name, args, opts, this.parent);
16883
17120
  }
16884
17121
  lookupTypeFor(name, inst) {
16885
17122
  return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
@@ -16892,11 +17129,26 @@ var init_transactor = __esm({
16892
17129
  get targetPath() {
16893
17130
  return this.parent.targetPath;
16894
17131
  }
16895
- stopPropagation() {
16896
- return this.parent.stopPropagation();
17132
+ // Answer the intent being handled, with a result or with an error. Either ENDS the
17133
+ // walk. Legal only in an `intent` handler: a message has no sender waiting on it.
17134
+ reply(value) {
17135
+ return this.parent.walk === void 0 ? warnNotIntent("reply") : this.parent.walk.answer("Ok", value);
17136
+ }
17137
+ fail(error) {
17138
+ return this.parent.walk === void 0 ? warnNotIntent("fail") : this.parent.walk.answer("Error", error);
17139
+ }
17140
+ // End the walk answering nothing — "served, and no answer".
17141
+ stop() {
17142
+ return this.parent.stop();
17143
+ }
17144
+ // From an `intent` body: hand the intent to the next hop, optionally amending it.
17145
+ // From a `receive` body: turn the message that arrived into an intent. One word from
17146
+ // both ends of a walk; which one you get depends on which bucket you are in.
17147
+ forward(opts) {
17148
+ return this.parent.forward(opts);
16897
17149
  }
16898
17150
  };
16899
- RequestContext = class extends Dispatcher {
17151
+ IntentContext = class extends Dispatcher {
16900
17152
  };
16901
17153
  PathChanges = class extends PathBuilder {
16902
17154
  constructor(dispatcher) {
@@ -16906,8 +17158,8 @@ var init_transactor = __esm({
16906
17158
  send(name, args, opts) {
16907
17159
  return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
16908
17160
  }
16909
- bubble(name, args, opts) {
16910
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
17161
+ intent(name, args, opts) {
17162
+ return this.dispatcher.intentAtPath(this.buildPath(), name, args, opts);
16911
17163
  }
16912
17164
  buildPath() {
16913
17165
  return this.dispatcher.path.concat(this.pathChanges);
@@ -17185,11 +17437,6 @@ function phaseOps(phase) {
17185
17437
  function resolveArgs(args, self) {
17186
17438
  return typeof args === "function" ? args(self) ?? [] : args ?? [];
17187
17439
  }
17188
- function phaseHasBubble(phase) {
17189
- if (!phase) return false;
17190
- if (phase.bubble?.length) return true;
17191
- return (phase.do ?? []).some((op) => op.type === "bubble");
17192
- }
17193
17440
  function dispatchPhase(dispatcher, targetPath, phase, self) {
17194
17441
  if (!phase) return;
17195
17442
  for (const op of phaseOps(phase)) {
@@ -17198,18 +17445,8 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
17198
17445
  case "send":
17199
17446
  dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
17200
17447
  break;
17201
- case "bubble":
17202
- dispatcher.sendAtPath(targetPath, op.name, args, {
17203
- skipSelf: true,
17204
- bubbles: true,
17205
- ...op.opts
17206
- });
17207
- break;
17208
- case "request":
17209
- dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
17210
- break;
17211
- case "input":
17212
- dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
17448
+ case "intent":
17449
+ dispatcher.intentAtPath(targetPath, op.name, args, op.opts);
17213
17450
  break;
17214
17451
  }
17215
17452
  }
@@ -17217,7 +17454,7 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
17217
17454
  var OP_KINDS;
17218
17455
  var init_on = __esm({
17219
17456
  "src/on.js"() {
17220
- OP_KINDS = ["send", "bubble", "request", "input"];
17457
+ OP_KINDS = ["send", "intent"];
17221
17458
  }
17222
17459
  });
17223
17460
 
@@ -17243,7 +17480,7 @@ function renderToHTMLNode(document2, components, macros, rootState, ParseContext
17243
17480
  const app = new App(container, comps, renderer, ParseContext2);
17244
17481
  const scope = app.registerComponents(components);
17245
17482
  if (macros) scope.registerMacros(macros);
17246
- if (opts.requestHandlers) scope.registerRequestHandlers(opts.requestHandlers);
17483
+ if (opts.intentHandlers) scope.registerIntentHandlers(opts.intentHandlers);
17247
17484
  app.rootViewName = opts.view ?? null;
17248
17485
  app.transactor.state.set(rootState);
17249
17486
  app.start(opts);
@@ -17256,21 +17493,17 @@ function renderToHTMLNode(document2, components, macros, rootState, ParseContext
17256
17493
  }
17257
17494
  };
17258
17495
  }
17259
- async function renderToHTMLDriven(document2, components, macros, rootState, ParseContext2, { phase = null, requestHandlers = null, view = null, warn = console.warn } = {}) {
17496
+ async function renderToHTMLDriven(document2, components, macros, rootState, ParseContext2, { phase = null, intentHandlers = null, view = null } = {}) {
17260
17497
  const { container, app, cleanup } = renderToHTMLNode(
17261
17498
  document2,
17262
17499
  components,
17263
17500
  macros,
17264
17501
  rootState,
17265
17502
  ParseContext2,
17266
- { noCache: true, requestHandlers, view }
17503
+ { noCache: true, intentHandlers, view }
17267
17504
  );
17268
17505
  try {
17269
17506
  if (phase) {
17270
- if (phaseHasBubble(phase))
17271
- warn(
17272
- "render: a `bubble` action is a no-op here — the example's value is the render root, so there is no ancestor to receive it. Use send/request/input to drive a preset state."
17273
- );
17274
17507
  dispatchPhase(rootDispatcher(app.transactor), new Path([]), phase, app.state.val);
17275
17508
  await app.transactor.settle();
17276
17509
  }
@@ -17299,7 +17532,7 @@ function checkView2(value, components, viewName) {
17299
17532
  return new Error(`view "${viewName}" is not defined on ${comp.name} (has: ${known})`);
17300
17533
  }
17301
17534
  function handlersFor(normalized, example) {
17302
- const merged = { ...normalized.requestHandlers ?? {}, ...example.requestHandlers ?? {} };
17535
+ const merged = { ...normalized.intentHandlers ?? {}, ...example.intentHandlers ?? {} };
17303
17536
  return Object.keys(merged).length > 0 ? merged : null;
17304
17537
  }
17305
17538
  async function renderExamples(normalized, env, { name = null, title = null, view = null } = {}) {
@@ -17324,7 +17557,7 @@ async function renderExamples(normalized, env, { name = null, title = null, view
17324
17557
  env.ParseContext,
17325
17558
  {
17326
17559
  phase: example.on?.init ?? null,
17327
- requestHandlers: handlersFor(normalized, example),
17560
+ intentHandlers: handlersFor(normalized, example),
17328
17561
  view: viewName
17329
17562
  }
17330
17563
  );
@@ -17488,29 +17721,24 @@ function captureError(e) {
17488
17721
  if ("actual" in e) out.actual = e.actual;
17489
17722
  return out;
17490
17723
  }
17491
- function buildStack({ components = [], macros = null, requestHandlers = null } = {}) {
17724
+ function buildStack({ components = [], macros = null, intentHandlers = null } = {}) {
17492
17725
  const stack = new ComponentStack();
17493
17726
  stack.registerComponents(components);
17494
17727
  if (macros) stack.registerMacros(macros);
17495
- if (requestHandlers) stack.registerRequestHandlers(requestHandlers);
17728
+ if (intentHandlers) stack.registerIntentHandlers(intentHandlers);
17496
17729
  return stack;
17497
17730
  }
17498
17731
  async function driveStack(stack, value, phase, opts = {}) {
17499
- const warn = opts.warn ?? console.warn;
17500
17732
  const transactor = new Transactor(stack.comps, value);
17501
17733
  if (opts.onMessage)
17502
17734
  transactor.state.onChange(({ val, old, info }) => {
17503
17735
  const t = info?.transaction;
17504
17736
  opts.onMessage(
17505
- { kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path },
17737
+ { kind: t?.handlerProp ?? "receive", name: t?.name, args: t?.args, path: t?.path },
17506
17738
  old,
17507
17739
  val
17508
17740
  );
17509
17741
  });
17510
- if (phaseHasBubble(phase))
17511
- warn(
17512
- "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."
17513
- );
17514
17742
  dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
17515
17743
  await transactor.settle();
17516
17744
  return transactor.state.val;
@@ -17523,7 +17751,7 @@ async function runTests({
17523
17751
  name = null,
17524
17752
  grep = null,
17525
17753
  bail = false,
17526
- requestHandlers = null,
17754
+ intentHandlers = null,
17527
17755
  macros = null
17528
17756
  } = {}) {
17529
17757
  const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
@@ -17537,7 +17765,7 @@ async function runTests({
17537
17765
  }
17538
17766
  const { describe: describe6, test: test2, moduleTests } = makeCollector({ path, components });
17539
17767
  let _stack = null;
17540
- const getStack = () => _stack ??= buildStack({ components, macros, requestHandlers });
17768
+ const getStack = () => _stack ??= buildStack({ components, macros, intentHandlers });
17541
17769
  const driveLocal = (value, phase, opts = {}) => driveStack(getStack(), value, phase, opts);
17542
17770
  await getTests({ describe: describe6, test: test2, expect: expect2, drive: driveLocal });
17543
17771
  let bailed = false;
@@ -17706,7 +17934,7 @@ var init_registry = __esm({
17706
17934
  name: positionals[0] ?? null,
17707
17935
  grep: values.grep ?? null,
17708
17936
  bail: values.bail ?? false,
17709
- requestHandlers: normalized.requestHandlers,
17937
+ intentHandlers: normalized.intentHandlers,
17710
17938
  macros: normalized.macros
17711
17939
  }),
17712
17940
  exitOn: (result) => result.hasFailures ? 4 : 0
@@ -18008,7 +18236,7 @@ MODULE CONVENTION
18008
18236
  export function getMacros() // optional
18009
18237
  -> Record<string, Macro> // from tutuca's macro()
18010
18238
 
18011
- export function getRequestHandlers() // optional
18239
+ export function getIntentHandlers() // optional
18012
18240
  -> Record<string, Function>
18013
18241
 
18014
18242
  export function getTests({ describe, test, expect }) // required for test
@@ -18530,7 +18758,7 @@ async function runDevTests(projectDir, devModuleUrls) {
18530
18758
  components: normalized.components,
18531
18759
  path: abs,
18532
18760
  expect,
18533
- requestHandlers: normalized.requestHandlers,
18761
+ intentHandlers: normalized.intentHandlers,
18534
18762
  macros: normalized.macros
18535
18763
  });
18536
18764
  const m = report.modules[0];
@@ -18577,11 +18805,11 @@ async function discoverModules(projectDir, devModuleUrls) {
18577
18805
  title: it.title,
18578
18806
  view: it.view,
18579
18807
  componentName: it.componentName,
18580
- requestHandlers: it.requestHandlerNames
18808
+ intentHandlers: it.requestHandlerNames
18581
18809
  }))
18582
18810
  })),
18583
18811
  macros: normalized.macros ? Object.keys(normalized.macros) : [],
18584
- requestHandlers: normalized.requestHandlers ? Object.keys(normalized.requestHandlers) : [],
18812
+ intentHandlers: normalized.intentHandlers ? Object.keys(normalized.intentHandlers) : [],
18585
18813
  error: null
18586
18814
  });
18587
18815
  } catch (e) {