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.
@@ -9144,8 +9144,8 @@ function parseProvide(s, px) {
9144
9144
  function parseMacroAttr(s, px) {
9145
9145
  return _parseSingle(s, px, G_ALL);
9146
9146
  }
9147
- function parseInputHandler(s, px) {
9148
- return _parseHandler(s, px, "input", true, true);
9147
+ function parseReceiveHandler(s, px) {
9148
+ return _parseHandler(s, px, "receive", true, true);
9149
9149
  }
9150
9150
  function parseAlterHandler(s, px) {
9151
9151
  const r = _parseHandler(s, px, "alter", false, false);
@@ -9688,11 +9688,11 @@ var EventHandler = class _EventHandler {
9688
9688
  return [this.handlerVal.evalAsHandler(stack), argValues];
9689
9689
  }
9690
9690
  static parse(s, px) {
9691
- const r = parseInputHandler(s, px);
9691
+ const r = parseReceiveHandler(s, px);
9692
9692
  return r === null ? null : new _EventHandler(r.handlerVal, r.args);
9693
9693
  }
9694
9694
  };
9695
- var RequestHandler = class {
9695
+ var IntentHandler = class {
9696
9696
  constructor(name, fn) {
9697
9697
  this.name = name;
9698
9698
  this.fn = fn;
@@ -13385,7 +13385,7 @@ var KNOWN_COMPONENT_SPEC_KEYS = new Set(
13385
13385
  )
13386
13386
  );
13387
13387
  var EMPTY_SET2 = /* @__PURE__ */ new Set();
13388
- var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["requestOverridesField"]);
13388
+ var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["intentOverridesField"]);
13389
13389
  var KNOWN_DIRECTIVE_NAMES = /* @__PURE__ */ new Set([
13390
13390
  "dangerouslysetinnerhtml",
13391
13391
  "slot",
@@ -13410,11 +13410,13 @@ var LOOKUP_TARGET_MALFORMED = "LOOKUP_TARGET_MALFORMED";
13410
13410
  var RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP";
13411
13411
  var UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER";
13412
13412
  var UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME";
13413
- var INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED";
13414
- var INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED";
13415
- var INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED";
13416
- var INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD";
13417
- var INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER";
13413
+ var RECEIVE_HANDLER_NOT_IMPLEMENTED = "RECEIVE_HANDLER_NOT_IMPLEMENTED";
13414
+ var RETIRED_HANDLER_BUCKET = "RETIRED_HANDLER_BUCKET";
13415
+ var RETIRED_CTX_VERB = "RETIRED_CTX_VERB";
13416
+ var HANDLER_NAME_COLLISION = "HANDLER_NAME_COLLISION";
13417
+ var RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED = "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED";
13418
+ var RECEIVE_HANDLER_FOR_METHOD = "RECEIVE_HANDLER_FOR_METHOD";
13419
+ var METHOD_FOR_RECEIVE_HANDLER = "METHOD_FOR_RECEIVE_HANDLER";
13418
13420
  var FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED";
13419
13421
  var FIELD_VAL_IS_METHOD = "FIELD_VAL_IS_METHOD";
13420
13422
  var METHOD_VAL_NOT_DEFINED = "METHOD_VAL_NOT_DEFINED";
@@ -13532,6 +13534,9 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
13532
13534
  checkProvidesAreAddressable(lx, Comp);
13533
13535
  checkLookupShapes(lx, Comp);
13534
13536
  checkHandlersNotAsync(lx, Comp);
13537
+ checkRetiredBuckets(lx, Comp);
13538
+ checkRetiredCtxVerbs(lx, Comp);
13539
+ checkHandlerNameCollisions(lx, Comp);
13535
13540
  checkScopedStyleTopLevel(lx, Comp);
13536
13541
  const referencedAlters = /* @__PURE__ */ new Set();
13537
13542
  const referencedInputs = /* @__PURE__ */ new Set();
@@ -13545,7 +13550,6 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
13545
13550
  );
13546
13551
  }
13547
13552
  checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
13548
- checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
13549
13553
  checkUnreferencedDynamics(lx, Comp, referencedDynamics);
13550
13554
  return lx;
13551
13555
  });
@@ -13815,7 +13819,7 @@ function mkAttrValEnv(Comp, referencedAlters, referencedDynamics) {
13815
13819
  return { fields, proto, scope, alter, referencedAlters, dynamicMap, referencedDynamics };
13816
13820
  }
13817
13821
  function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
13818
- const { input, views, Class } = Comp;
13822
+ const { receive: input, views, Class } = Comp;
13819
13823
  const { prototype: proto } = Class;
13820
13824
  for (const viewName in views) {
13821
13825
  lx.push({ viewName }, () => {
@@ -13832,13 +13836,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
13832
13836
  if (input[name] === void 0) {
13833
13837
  const isMethodFix = protoHasMethod(proto, name);
13834
13838
  lx.error(
13835
- INPUT_HANDLER_NOT_IMPLEMENTED,
13839
+ RECEIVE_HANDLER_NOT_IMPLEMENTED,
13836
13840
  { name, handler, event, eventName, originAttr },
13837
13841
  isMethodFix ? { kind: "add-prefix", from: name, to: `$${name}` } : replaceNameSuggestion(name, Object.keys(input))
13838
13842
  );
13839
13843
  if (isMethodFix) {
13840
13844
  lx.hint(
13841
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
13845
+ METHOD_FOR_RECEIVE_HANDLER,
13842
13846
  { name, handler, event, eventName, originAttr },
13843
13847
  { kind: "add-prefix", from: name, to: `$${name}` }
13844
13848
  );
@@ -13850,13 +13854,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
13850
13854
  if (!protoHasMethod(proto, name)) {
13851
13855
  const isInputFix = input[name] !== void 0;
13852
13856
  lx.error(
13853
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
13857
+ RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
13854
13858
  { name, handler, event, eventName, originAttr },
13855
13859
  isInputFix ? { kind: "drop-prefix", from: `$${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto))
13856
13860
  );
13857
13861
  if (isInputFix) {
13858
13862
  lx.hint(
13859
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
13863
+ RECEIVE_HANDLER_FOR_METHOD,
13860
13864
  { name, handler, event, eventName, originAttr },
13861
13865
  { kind: "drop-prefix", from: `$${name}`, to: name }
13862
13866
  );
@@ -14105,12 +14109,83 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedDynamics) {
14105
14109
  });
14106
14110
  }
14107
14111
  }
14112
+ var isPlainObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
14113
+ var RETIRED_BUCKETS = {
14114
+ input: {
14115
+ to: "receive",
14116
+ 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"
14117
+ },
14118
+ bubble: {
14119
+ to: "intent",
14120
+ 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"
14121
+ },
14122
+ response: {
14123
+ to: "receive",
14124
+ 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"
14125
+ }
14126
+ };
14127
+ function checkRetiredBuckets(lx, Comp) {
14128
+ const extra = Comp.extra;
14129
+ if (!extra) return;
14130
+ for (const key of Object.keys(extra)) {
14131
+ if (!Object.hasOwn(RETIRED_BUCKETS, key)) continue;
14132
+ const retired = RETIRED_BUCKETS[key];
14133
+ const names = isPlainObj(extra[key]) ? Object.keys(extra[key]) : [];
14134
+ lx.error(RETIRED_HANDLER_BUCKET, { key, to: retired.to, how: retired.how, names });
14135
+ }
14136
+ }
14137
+ var RETIRED_CTX_VERBS = {
14138
+ bubble: 'ctx.intent(name, args, { route: ["dyn"] }) - the route says it walks the ancestors, where the verb used to',
14139
+ 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',
14140
+ stopPropagation: "ctx.stop()",
14141
+ inputAtPath: "ctx.sendAtPath(path, name, args)",
14142
+ requestAtPath: 'ctx.intentAtPath(path, name, args, { route: ["lex"] })'
14143
+ };
14144
+ var RETIRED_CTX_RE = new RegExp(
14145
+ `\\bctx\\.(${Object.keys(RETIRED_CTX_VERBS).join("|")})\\s*\\(`,
14146
+ "g"
14147
+ );
14148
+ function checkRetiredCtxVerbs(lx, Comp) {
14149
+ for (const bucket of ["receive", "intent", "alter", "methods", "statics"]) {
14150
+ const block = bucket === "methods" || bucket === "statics" ? Comp.spec?.[bucket] : Comp[bucket];
14151
+ if (!isPlainObj(block)) continue;
14152
+ for (const name in block) {
14153
+ const fn = block[name];
14154
+ if (typeof fn !== "function") continue;
14155
+ const src = String(fn);
14156
+ for (const m of src.matchAll(RETIRED_CTX_RE)) {
14157
+ const verb = m[1];
14158
+ lx.error(RETIRED_CTX_VERB, { verb, to: RETIRED_CTX_VERBS[verb], bucket, name });
14159
+ }
14160
+ }
14161
+ }
14162
+ }
14163
+ function checkHandlerNameCollisions(lx, Comp) {
14164
+ const receive = Comp.receive ?? {};
14165
+ const intent = Comp.intent ?? {};
14166
+ for (const name in intent)
14167
+ if (receive[name] !== void 0)
14168
+ lx.error(HANDLER_NAME_COLLISION, {
14169
+ name,
14170
+ problem: `"${name}" is declared in both \`receive\` and \`intent\``,
14171
+ fix: "one is addressed and one is routed; give them different names so a reader can tell which a call site meant"
14172
+ });
14173
+ for (const name in receive)
14174
+ for (const suffix of ["Ok", "Error", "Unhandled"])
14175
+ if (receive[name + suffix] !== void 0 && intent[name] === void 0)
14176
+ lx.warn(HANDLER_NAME_COLLISION, {
14177
+ name,
14178
+ problem: `\`receive\` declares both "${name}" and its answer name "${name + suffix}"`,
14179
+ fix: `an intent named "${name}" would dispatch its answer to "${name + suffix}" - rename one unless that is what you meant`
14180
+ });
14181
+ }
14108
14182
  function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
14109
14183
  const extra = Comp.extra;
14110
14184
  if (!extra) return;
14111
14185
  let candidates = null;
14112
14186
  for (const key of Object.keys(extra)) {
14113
14187
  if (FRAMEWORK_WELL_KNOWN_EXTRAS.has(key) || wellKnownExtras.has(key)) continue;
14188
+ if (Object.hasOwn(RETIRED_BUCKETS, key)) continue;
14114
14189
  candidates ??= [
14115
14190
  ...KNOWN_COMPONENT_SPEC_KEYS,
14116
14191
  ...FRAMEWORK_WELL_KNOWN_EXTRAS,
@@ -14158,15 +14233,8 @@ function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
14158
14233
  }
14159
14234
  }
14160
14235
  }
14161
- function checkUnreferencedInputHandlers(lx, Comp, referencedInputs) {
14162
- for (const name in Comp.input) {
14163
- if (!referencedInputs.has(name)) {
14164
- lx.hint(INPUT_HANDLER_NOT_REFERENCED, { name });
14165
- }
14166
- }
14167
- }
14168
- var HANDLER_CHANNELS = ["input", "receive", "bubble", "response", "alter"];
14169
- 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.";
14236
+ var HANDLER_CHANNELS = ["receive", "intent", "alter"];
14237
+ 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.`;
14170
14238
  function checkHandlersNotAsync(lx, Comp) {
14171
14239
  for (const channel of HANDLER_CHANNELS) {
14172
14240
  const block = Comp[channel];
@@ -14362,8 +14430,11 @@ var Components = class {
14362
14430
  getHandlerFor(v, name, key) {
14363
14431
  return this.getCompFor(v)?.[key][name] ?? null;
14364
14432
  }
14365
- getRequestFor(v, name) {
14366
- return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
14433
+ // The `lex` leg of an intent's route: the handlers registered on the scope chain of
14434
+ // the component that raised it, innermost first. A list, not a first match — see
14435
+ // ComponentStack.lookupIntentChain.
14436
+ getIntentChainFor(v, name) {
14437
+ return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
14367
14438
  }
14368
14439
  compileStyles() {
14369
14440
  const styles2 = [];
@@ -14376,7 +14447,7 @@ var ComponentStack = class _ComponentStack {
14376
14447
  this.comps = comps;
14377
14448
  this.parent = parent;
14378
14449
  this.byName = {};
14379
- this.reqsByName = {};
14450
+ this.intentsByName = {};
14380
14451
  this.macros = {};
14381
14452
  }
14382
14453
  enter() {
@@ -14408,11 +14479,21 @@ var ComponentStack = class _ComponentStack {
14408
14479
  getCompFor(v) {
14409
14480
  return this.comps.getCompFor(v);
14410
14481
  }
14411
- registerRequestHandlers(handlers) {
14412
- for (const name in handlers) this.reqsByName[name] = new RequestHandler(name, handlers[name]);
14482
+ // Handlers that answer an intent's `lex` leg. A name maps to a LIST, because the leg
14483
+ // walks: a handler that returns PASS declines and hands the intent to the next one.
14484
+ // A bare function is accepted as a one-element list.
14485
+ registerIntentHandlers(handlers) {
14486
+ for (const name in handlers) {
14487
+ const fns = Array.isArray(handlers[name]) ? handlers[name] : [handlers[name]];
14488
+ this.intentsByName[name] = fns.map((fn) => new IntentHandler(name, fn));
14489
+ }
14413
14490
  }
14414
- lookupRequest(name) {
14415
- return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
14491
+ // Innermost scope first, then outward. Concatenated rather than first-match, so a
14492
+ // declining handler can hand the intent to one further up.
14493
+ lookupIntentChain(name) {
14494
+ const here = this.intentsByName[name] ?? [];
14495
+ const up = this.parent?.lookupIntentChain(name) ?? [];
14496
+ return up.length === 0 ? here : here.concat(up);
14416
14497
  }
14417
14498
  lookupComponent(name) {
14418
14499
  return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
@@ -14443,7 +14524,7 @@ var LookupInfo = class {
14443
14524
  }
14444
14525
  };
14445
14526
  var isString = (v) => typeof v === "string";
14446
- var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
14527
+ var _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
14447
14528
  var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
14448
14529
  var _compId = 0;
14449
14530
  var Component = class {
@@ -14454,10 +14535,8 @@ var Component = class {
14454
14535
  this.views = { main: new View("main", o.view, o.style) };
14455
14536
  this.commonStyle = o.commonStyle ?? "";
14456
14537
  this.globalStyle = o.globalStyle ?? "";
14457
- this.input = o.input ?? {};
14458
14538
  this.receive = o.receive ?? {};
14459
- this.bubble = o.bubble ?? {};
14460
- this.response = o.response ?? {};
14539
+ this.intent = o.intent ?? {};
14461
14540
  this.alter = o.alter ?? {};
14462
14541
  for (const name in o.views ?? {}) {
14463
14542
  const v = o.views[name];
@@ -14519,7 +14598,7 @@ var Component = class {
14519
14598
  };
14520
14599
 
14521
14600
  // src/on.js
14522
- var OP_KINDS = ["send", "bubble", "request", "input"];
14601
+ var OP_KINDS = ["send", "intent"];
14523
14602
  function phaseOps(phase) {
14524
14603
  const ops = [];
14525
14604
  for (const type3 of OP_KINDS) for (const a of phase[type3] ?? []) ops.push({ type: type3, ...a });
@@ -14529,11 +14608,6 @@ function phaseOps(phase) {
14529
14608
  function resolveArgs(args, self) {
14530
14609
  return typeof args === "function" ? args(self) ?? [] : args ?? [];
14531
14610
  }
14532
- function phaseHasBubble(phase) {
14533
- if (!phase) return false;
14534
- if (phase.bubble?.length) return true;
14535
- return (phase.do ?? []).some((op) => op.type === "bubble");
14536
- }
14537
14611
  function dispatchPhase(dispatcher, targetPath, phase, self) {
14538
14612
  if (!phase) return;
14539
14613
  for (const op of phaseOps(phase)) {
@@ -14542,18 +14616,8 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
14542
14616
  case "send":
14543
14617
  dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
14544
14618
  break;
14545
- case "bubble":
14546
- dispatcher.sendAtPath(targetPath, op.name, args, {
14547
- skipSelf: true,
14548
- bubbles: true,
14549
- ...op.opts
14550
- });
14551
- break;
14552
- case "request":
14553
- dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
14554
- break;
14555
- case "input":
14556
- dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
14619
+ case "intent":
14620
+ dispatcher.intentAtPath(targetPath, op.name, args, op.opts);
14557
14621
  break;
14558
14622
  }
14559
14623
  }
@@ -14725,8 +14789,11 @@ var Transactor = class {
14725
14789
  this.transactions.push(t);
14726
14790
  this.onTransactionPushed(t);
14727
14791
  }
14728
- // Subscribe to a normalized record for every handler invocation (send/receive,
14729
- // bubble, request/response, input). Returns an unsubscribe function. Records
14792
+ // Subscribe to a normalized record for every handler invocation. `kind` is the
14793
+ // handler bucket (`receive` / `intent`), plus `answer` — an observation-only kind for
14794
+ // a message the runtime wrote to answer an intent. The BUCKET does not know about
14795
+ // that distinction, and must not: a handler cannot tell an answer from a message its
14796
+ // parent sent. Returns an unsubscribe function. Records
14730
14797
  // carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
14731
14798
  // matched, before, after, parent, timestamp. Purely observational.
14732
14799
  observe(cb) {
@@ -14779,23 +14846,14 @@ var Transactor = class {
14779
14846
  this.pushTransaction(t);
14780
14847
  return this._link(t, parent);
14781
14848
  }
14782
- pushInput(path, name, args = [], opts = {}, parent = null) {
14783
- const t = new InputDispatchEvent(path, this, name, args, parent, opts);
14784
- this.pushTransaction(t);
14785
- return this._link(t, parent);
14786
- }
14787
- pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
14788
- const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
14789
- const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
14790
- this.pushTransaction(t);
14791
- return this._link(t, parent);
14792
- }
14793
- pushRequest(path, name, args = [], opts = {}, parent = null) {
14849
+ // Raise an intent: a job the sender did not address, walked along a route until
14850
+ // something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
14851
+ // `opts.livePath` opts out of pinning the answer's destination keys.
14852
+ pushIntent(path, name, args = [], opts = {}, parent = null) {
14794
14853
  const release = parent ? parent.completion.track() : null;
14795
- const p = this._runRequest(path, name, args, opts, parent, release);
14796
- this._inflight.add(p);
14797
- p.finally(() => this._inflight.delete(p));
14798
- return p;
14854
+ const walk = new IntentWalk(this, path, name, args, opts, parent, release);
14855
+ walk.advance();
14856
+ return walk;
14799
14857
  }
14800
14858
  // Drain queued transactions and await in-flight requests until quiescent. Each
14801
14859
  // awaited request enqueues a ResponseEvent, which may dispatch more work, so we
@@ -14806,57 +14864,6 @@ var Transactor = class {
14806
14864
  if (this._inflight.size) await Promise.allSettled([...this._inflight]);
14807
14865
  }
14808
14866
  }
14809
- async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
14810
- let released = false;
14811
- const transfer = (t) => {
14812
- if (release) {
14813
- released = true;
14814
- t.completion.whenSubtreeSettled().then(release);
14815
- }
14816
- };
14817
- try {
14818
- const curRoot = this.state.val;
14819
- const txnPath = path.toTransactionPath();
14820
- const curLeaf = txnPath.lookup(curRoot);
14821
- const found = this.comps.getRequestFor(curLeaf, name);
14822
- const handler = found ?? mkReq404(name);
14823
- if (this._observers.length > 0) {
14824
- const reqPath = txnPath.pinKeys(curRoot);
14825
- this._emit({
14826
- kind: "request",
14827
- name,
14828
- args,
14829
- path: reqPath,
14830
- pathKeys: reqPath.toKeys(),
14831
- targetPath: path,
14832
- handler: found?.fn ?? null,
14833
- handlerName: found?.fn?.name || name,
14834
- matched: found ? "exact" : "none",
14835
- before: curLeaf,
14836
- after: void 0,
14837
- parent,
14838
- timestamp: Date.now()
14839
- });
14840
- }
14841
- const reqCtx = new RequestContext(path, this, parent, curRoot);
14842
- const resHandlerName = opts?.onResName ?? name;
14843
- const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
14844
- const push = (specificName, baseName, singleArg, result, error) => {
14845
- const resArgs = specificName ? [singleArg] : [result, error];
14846
- const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
14847
- transfer(t);
14848
- this.pushTransaction(t);
14849
- };
14850
- try {
14851
- const result = await handler.fn.apply(null, [...args, reqCtx]);
14852
- push(opts?.onOkName, resHandlerName, result, result, null);
14853
- } catch (error) {
14854
- push(opts?.onErrorName, resHandlerName, error, null, error);
14855
- }
14856
- } finally {
14857
- if (release && !released) release();
14858
- }
14859
- }
14860
14867
  get hasPendingTransactions() {
14861
14868
  return this.transactions.length > 0;
14862
14869
  }
@@ -14873,6 +14880,7 @@ var Transactor = class {
14873
14880
  this._emitTransaction(transaction, curState);
14874
14881
  } else console.warn("undefined new state", { curState, transaction });
14875
14882
  } finally {
14883
+ transaction.ensureWalkAdvanced?.();
14876
14884
  transaction._completion?.ensureSelfSettled();
14877
14885
  transaction._completion?.releaseSelf();
14878
14886
  }
@@ -14881,12 +14889,6 @@ var Transactor = class {
14881
14889
  this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
14882
14890
  }
14883
14891
  };
14884
- function mkReq404(name) {
14885
- const fn = () => {
14886
- throw new Error(`Request not found: ${name}`);
14887
- };
14888
- return { fn };
14889
- }
14890
14892
  function nullHandler() {
14891
14893
  return this;
14892
14894
  }
@@ -14917,6 +14919,15 @@ var Transaction = class {
14917
14919
  }
14918
14920
  afterTransaction() {
14919
14921
  }
14922
+ // Ending a walk and forwarding belong to the two dispatch buckets; the base warns
14923
+ // rather than throwing, so a stray call from the wrong place is a message and not a
14924
+ // crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
14925
+ stop() {
14926
+ console.warn('ctx.stop() is only meaningful in an "intent" handler - ignored');
14927
+ }
14928
+ forward(_opts) {
14929
+ console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
14930
+ }
14920
14931
  buildRootStack(root, comps) {
14921
14932
  return Stack2.root(comps, root);
14922
14933
  }
@@ -14978,18 +14989,40 @@ var InputEvent = class extends Transaction {
14978
14989
  this._dispatchPath ??= this.path.compact();
14979
14990
  return this._dispatchPath;
14980
14991
  }
14992
+ // A DOM event is an ADDRESSED message like any other — it reaches the component that
14993
+ // owns the view and stops. It keeps its own class only because it resolves its handler
14994
+ // from the compiled view and runs synchronously, not because it is a different channel.
14981
14995
  get observeKind() {
14982
- return "input";
14996
+ return "receive";
14983
14997
  }
14984
14998
  get observeName() {
14985
14999
  return this.e?.type ?? null;
14986
15000
  }
15001
+ // A view's name is a message. `forward` is where that name can LEAVE the component:
15002
+ // it becomes an intent with the same name and the same arguments, so a view that says
15003
+ // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
15004
+ // (A `$method` handler has a name too, so forwarding one raises an intent under it.)
15005
+ forward(opts) {
15006
+ this._forward = opts ?? {};
15007
+ }
15008
+ afterTransaction() {
15009
+ const f = this._forward;
15010
+ if (f === void 0) return;
15011
+ this._forward = void 0;
15012
+ const { args = this._handlerArgs ?? [], ...rest } = f;
15013
+ const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
15014
+ const name = hv?.name;
15015
+ if (name === void 0)
15016
+ return console.warn("ctx.forward() from a handler with no name - ignored");
15017
+ this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
15018
+ }
14987
15019
  buildRootStack(root, comps) {
14988
15020
  return Stack2.root(comps, root, this);
14989
15021
  }
14990
15022
  getHandlerAndArgs(root, _instance, comps) {
14991
15023
  const stack = this.buildStack(root, comps);
14992
15024
  const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
15025
+ this._handlerArgs = [...args];
14993
15026
  const path = this.dispatchPath;
14994
15027
  args.push(new EventContext(path, this.transactor, this));
14995
15028
  return [handler, args];
@@ -15074,39 +15107,190 @@ var NameArgsTransaction = class extends Transaction {
15074
15107
  return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
15075
15108
  }
15076
15109
  };
15077
- var ResponseEvent = class extends NameArgsTransaction {
15078
- handlerProp = "response";
15079
- constructor(path, transactor, name, args, parent, txnPath = null) {
15080
- super(path, transactor, name, args, parent);
15081
- this._txnPath = txnPath;
15082
- }
15083
- getTransactionPath() {
15084
- return this._txnPath ?? super.getTransactionPath();
15085
- }
15086
- };
15087
15110
  var SendEvent = class extends NameArgsTransaction {
15088
15111
  handlerProp = "receive";
15089
- run(rootVal, comps) {
15090
- return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
15112
+ // `receive` is the ADDRESSED bucket, and it is the only one: a view's `@on.click`,
15113
+ // a parent's ctx.send, the host's sendAtRoot and an answer to an intent all arrive
15114
+ // here, and a handler cannot tell them apart. Splitting on where a message came from
15115
+ // would let the view's name through and turn the parent's away, though the two say
15116
+ // the same thing — and neither a test nor a parent could drive the component.
15117
+ get observeKind() {
15118
+ return this._isAnswer ? "answer" : "receive";
15119
+ }
15120
+ // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
15121
+ // intent, keeping its name and payload. This is what lets a view's name leave the
15122
+ // component without the view changing.
15123
+ forward(opts) {
15124
+ this._forward = opts ?? {};
15091
15125
  }
15092
15126
  afterTransaction() {
15093
- const { path, name, args, opts, targetPath } = this;
15094
- if (opts.bubbles && path.steps.length > 0)
15095
- this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
15127
+ const f = this._forward;
15128
+ if (f === void 0) return;
15129
+ this._forward = void 0;
15130
+ const { args = this.args, ...rest } = f;
15131
+ this.transactor.pushIntent(this.path, this.name, args, rest, this);
15096
15132
  }
15097
15133
  };
15098
- var BubbleEvent = class extends SendEvent {
15099
- handlerProp = "bubble";
15100
- constructor(path, transactor, name, args, parent, opts, targetPath) {
15101
- super(path, transactor, name, args, parent, opts);
15102
- this.targetPath = targetPath ?? path;
15134
+ var IntentEvent = class extends NameArgsTransaction {
15135
+ handlerProp = "intent";
15136
+ constructor(path, transactor, walk) {
15137
+ super(path, transactor, walk.name, walk.args, walk.parent, {});
15138
+ this.walk = walk;
15139
+ this.targetPath = walk.origin;
15140
+ }
15141
+ // `forward` in an INTENT body amends the hop about to be pushed — it does not push
15142
+ // one itself, because a walk advances on its own.
15143
+ forward(opts) {
15144
+ this.walk.amend(opts, this.path);
15103
15145
  }
15104
- stopPropagation() {
15105
- this.opts.bubbles = false;
15146
+ stop() {
15147
+ this.walk.finish(null, null);
15148
+ }
15149
+ afterTransaction() {
15150
+ this.ensureWalkAdvanced();
15151
+ }
15152
+ // Idempotent, because both afterTransaction and the transactor's `finally` call it —
15153
+ // whichever gets there first advances the walk exactly once.
15154
+ ensureWalkAdvanced() {
15155
+ if (this._advanced) return;
15156
+ this._advanced = true;
15157
+ this.walk.advance();
15106
15158
  }
15107
15159
  };
15108
- var InputDispatchEvent = class extends NameArgsTransaction {
15109
- handlerProp = "input";
15160
+ var PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
15161
+ var DEFAULT_ROUTE = ["dyn", "lex"];
15162
+ var INTENT_DEPTH = 64;
15163
+ var IntentWalk = class {
15164
+ constructor(transactor, path, name, args, opts, parent, release) {
15165
+ this.transactor = transactor;
15166
+ this.name = name;
15167
+ this.args = args;
15168
+ this.route = opts?.route ?? DEFAULT_ROUTE;
15169
+ this.parent = parent;
15170
+ this.release = release;
15171
+ this.origin = path;
15172
+ this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
15173
+ this.legIndex = 0;
15174
+ this.dynAt = path;
15175
+ this.hops = 0;
15176
+ this.ended = false;
15177
+ }
15178
+ // Offer the intent to the next hop. Called once at dispatch, and again by every hop
15179
+ // that ran without answering.
15180
+ advance() {
15181
+ if (this.ended) return;
15182
+ if (this.hops++ >= INTENT_DEPTH) return this.exhaust("intentDepth");
15183
+ while (this.legIndex < this.route.length) {
15184
+ const leg = this.route[this.legIndex];
15185
+ if (leg === "dyn") {
15186
+ if (this.dynAt.steps.length === 0) {
15187
+ this.legIndex++;
15188
+ continue;
15189
+ }
15190
+ this.dynAt = this.dynAt.popStep();
15191
+ this.transactor.pushTransaction(new IntentEvent(this.dynAt, this.transactor, this));
15192
+ return;
15193
+ }
15194
+ if (leg === "lex") {
15195
+ this.legIndex++;
15196
+ return this._tryLex();
15197
+ }
15198
+ console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
15199
+ this.legIndex++;
15200
+ }
15201
+ this.exhaust("noHandler");
15202
+ }
15203
+ // The `lex` leg: the handlers registered on the scope chain of the component that
15204
+ // raised the intent, in order.
15205
+ _tryLex() {
15206
+ const root = this.transactor.state.val;
15207
+ const txnPath = this.origin.toTransactionPath();
15208
+ const leaf = txnPath.lookup(root);
15209
+ const chain = this.transactor.comps.getIntentChainFor(leaf, this.name);
15210
+ if (this.transactor._observers.length > 0) {
15211
+ const path = txnPath.pinKeys(root);
15212
+ this.transactor._emit({
15213
+ kind: "intent",
15214
+ name: this.name,
15215
+ args: this.args,
15216
+ path,
15217
+ pathKeys: path.toKeys(),
15218
+ targetPath: this.origin,
15219
+ handler: chain[0]?.fn ?? null,
15220
+ handlerName: chain[0]?.fn?.name || this.name,
15221
+ matched: chain.length > 0 ? "exact" : "none",
15222
+ before: leaf,
15223
+ after: void 0,
15224
+ parent: this.parent,
15225
+ timestamp: Date.now()
15226
+ });
15227
+ }
15228
+ if (chain.length === 0) return this.advance();
15229
+ const p = this._runLex(chain, 0);
15230
+ this.transactor._inflight.add(p);
15231
+ p.finally(() => this.transactor._inflight.delete(p));
15232
+ }
15233
+ async _runLex(chain, i) {
15234
+ if (this.ended) return;
15235
+ if (i >= chain.length) return this.advance();
15236
+ const ctx = new IntentContext(this.origin, this.transactor, this.parent);
15237
+ try {
15238
+ const res = await chain[i].fn.apply(null, [...this.args, ctx]);
15239
+ if (res === PASS) return this._runLex(chain, i + 1);
15240
+ this.answer("Ok", res);
15241
+ } catch (error) {
15242
+ this.answer("Error", error);
15243
+ }
15244
+ }
15245
+ // A hop answered. Ends the walk.
15246
+ answer(suffix, value) {
15247
+ this.finish(`${this.name}${suffix}`, [value]);
15248
+ }
15249
+ // `forward` from an intent body: keep walking, optionally with new arguments or a
15250
+ // narrowed route.
15251
+ amend(opts, from) {
15252
+ if (opts?.args !== void 0) this.args = opts.args;
15253
+ if (opts?.route !== void 0) {
15254
+ this.route = opts.route;
15255
+ this.legIndex = 0;
15256
+ this.dynAt = from ?? this.dynAt;
15257
+ }
15258
+ }
15259
+ // The route ran out. What the sender hears depends only on what it DECLARES, which is
15260
+ // how "a sender expects an answer if and only if it declares an answer arm" becomes
15261
+ // code — nobody writes it down twice, and there is no schema to keep in step.
15262
+ exhaust(reason) {
15263
+ if (this.ended) return;
15264
+ const { name, args } = this;
15265
+ const comp = this.transactor.comps.getCompFor(
15266
+ this.origin.toTransactionPath().lookup(this.transactor.state.val)
15267
+ );
15268
+ const declares = (n) => comp?.receive?.[n] !== void 0;
15269
+ if (declares(`${name}Unhandled`)) {
15270
+ this.finish(`${name}Unhandled`, args);
15271
+ } else if (declares(`${name}Error`)) {
15272
+ this.finish(`${name}Error`, [reason]);
15273
+ } else {
15274
+ if (declares(`${name}Ok`))
15275
+ console.warn(
15276
+ `intent "${name}" was not answered (${reason}) and this component declares only "${name}Ok" - add "${name}Unhandled" or "${name}Error" to handle it`
15277
+ );
15278
+ this.finish(null, null);
15279
+ }
15280
+ }
15281
+ // End the walk. `name === null` means no answer goes out, which is what `stop` and a
15282
+ // fire-and-forget notification both are — the tracked unit is released here instead
15283
+ // of following an answer's subtree.
15284
+ finish(name, args) {
15285
+ if (this.ended) return;
15286
+ this.ended = true;
15287
+ if (name === null) return this.release?.();
15288
+ const path = this.answerPath ?? this.origin;
15289
+ const t = new SendEvent(path, this.transactor, name, args, this.parent, {});
15290
+ t._isAnswer = true;
15291
+ this.transactor.pushTransaction(t);
15292
+ if (this.release) t.completion.whenSubtreeSettled().then(this.release);
15293
+ }
15110
15294
  };
15111
15295
  var Completion = class {
15112
15296
  constructor() {
@@ -15187,23 +15371,21 @@ var Dispatcher = class {
15187
15371
  get at() {
15188
15372
  return new PathChanges(this);
15189
15373
  }
15374
+ // A message is ADDRESSED: it names one component and stops there.
15190
15375
  send(name, args, opts) {
15191
15376
  return this.sendAtPath(this.path, name, args, opts);
15192
15377
  }
15193
- bubble(name, args, opts) {
15194
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
15195
- }
15196
15378
  sendAtPath(path, name, args, opts) {
15197
15379
  return this.transactor.pushSend(path, name, args, opts, this.parent);
15198
15380
  }
15199
- request(name, args, opts) {
15200
- return this.requestAtPath(this.path, name, args, opts);
15381
+ // An intent is ROUTED: it names a job and walks until something answers. The verb
15382
+ // does not decide which scope answers — `opts.route` does, written here at the call
15383
+ // site, which is where the decision actually is.
15384
+ intent(name, args, opts) {
15385
+ return this.intentAtPath(this.path, name, args, opts);
15201
15386
  }
15202
- requestAtPath(path, name, args, opts) {
15203
- return this.transactor.pushRequest(path, name, args, opts, this.parent);
15204
- }
15205
- inputAtPath(path, name, args, opts) {
15206
- return this.transactor.pushInput(path, name, args, opts, this.parent);
15387
+ intentAtPath(path, name, args, opts) {
15388
+ return this.transactor.pushIntent(path, name, args, opts, this.parent);
15207
15389
  }
15208
15390
  lookupTypeFor(name, inst) {
15209
15391
  return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
@@ -15216,12 +15398,30 @@ var EventContext = class extends Dispatcher {
15216
15398
  get targetPath() {
15217
15399
  return this.parent.targetPath;
15218
15400
  }
15219
- stopPropagation() {
15220
- return this.parent.stopPropagation();
15401
+ // Answer the intent being handled, with a result or with an error. Either ENDS the
15402
+ // walk. Legal only in an `intent` handler: a message has no sender waiting on it.
15403
+ reply(value) {
15404
+ return this.parent.walk === void 0 ? warnNotIntent("reply") : this.parent.walk.answer("Ok", value);
15405
+ }
15406
+ fail(error) {
15407
+ return this.parent.walk === void 0 ? warnNotIntent("fail") : this.parent.walk.answer("Error", error);
15408
+ }
15409
+ // End the walk answering nothing — "served, and no answer".
15410
+ stop() {
15411
+ return this.parent.stop();
15412
+ }
15413
+ // From an `intent` body: hand the intent to the next hop, optionally amending it.
15414
+ // From a `receive` body: turn the message that arrived into an intent. One word from
15415
+ // both ends of a walk; which one you get depends on which bucket you are in.
15416
+ forward(opts) {
15417
+ return this.parent.forward(opts);
15221
15418
  }
15222
15419
  };
15223
- var RequestContext = class extends Dispatcher {
15420
+ var IntentContext = class extends Dispatcher {
15224
15421
  };
15422
+ function warnNotIntent(verb) {
15423
+ console.warn(`ctx.${verb}() is only meaningful in an "intent" handler - ignored`);
15424
+ }
15225
15425
  var PathChanges = class extends PathBuilder {
15226
15426
  constructor(dispatcher) {
15227
15427
  super();
@@ -15230,8 +15430,8 @@ var PathChanges = class extends PathBuilder {
15230
15430
  send(name, args, opts) {
15231
15431
  return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
15232
15432
  }
15233
- bubble(name, args, opts) {
15234
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
15433
+ intent(name, args, opts) {
15434
+ return this.dispatcher.intentAtPath(this.buildPath(), name, args, opts);
15235
15435
  }
15236
15436
  buildPath() {
15237
15437
  return this.dispatcher.path.concat(this.pathChanges);
@@ -15503,29 +15703,24 @@ function captureError(e) {
15503
15703
  if ("actual" in e) out.actual = e.actual;
15504
15704
  return out;
15505
15705
  }
15506
- function buildStack({ components = [], macros = null, requestHandlers = null } = {}) {
15706
+ function buildStack({ components = [], macros = null, intentHandlers = null } = {}) {
15507
15707
  const stack = new ComponentStack();
15508
15708
  stack.registerComponents(components);
15509
15709
  if (macros) stack.registerMacros(macros);
15510
- if (requestHandlers) stack.registerRequestHandlers(requestHandlers);
15710
+ if (intentHandlers) stack.registerIntentHandlers(intentHandlers);
15511
15711
  return stack;
15512
15712
  }
15513
15713
  async function driveStack(stack, value, phase, opts = {}) {
15514
- const warn = opts.warn ?? console.warn;
15515
15714
  const transactor = new Transactor(stack.comps, value);
15516
15715
  if (opts.onMessage)
15517
15716
  transactor.state.onChange(({ val, old, info }) => {
15518
15717
  const t = info?.transaction;
15519
15718
  opts.onMessage(
15520
- { kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path },
15719
+ { kind: t?.handlerProp ?? "receive", name: t?.name, args: t?.args, path: t?.path },
15521
15720
  old,
15522
15721
  val
15523
15722
  );
15524
15723
  });
15525
- if (phaseHasBubble(phase))
15526
- warn(
15527
- "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."
15528
- );
15529
15724
  dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
15530
15725
  await transactor.settle();
15531
15726
  return transactor.state.val;
@@ -15541,7 +15736,7 @@ async function runTests({
15541
15736
  name = null,
15542
15737
  grep = null,
15543
15738
  bail = false,
15544
- requestHandlers = null,
15739
+ intentHandlers = null,
15545
15740
  macros = null
15546
15741
  } = {}) {
15547
15742
  const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
@@ -15555,7 +15750,7 @@ async function runTests({
15555
15750
  }
15556
15751
  const { describe, test: test3, moduleTests } = makeCollector({ path, components });
15557
15752
  let _stack = null;
15558
- const getStack = () => _stack ??= buildStack({ components, macros, requestHandlers });
15753
+ const getStack = () => _stack ??= buildStack({ components, macros, intentHandlers });
15559
15754
  const driveLocal = (value, phase, opts = {}) => driveStack(getStack(), value, phase, opts);
15560
15755
  await getTests({ describe, test: test3, expect: expect2, drive: driveLocal });
15561
15756
  let bailed = false;
@@ -15621,7 +15816,7 @@ async function runTests({
15621
15816
  }
15622
15817
 
15623
15818
  // tools/format/console.js
15624
- var PASS = "color: #0a0; font-weight: bold";
15819
+ var PASS2 = "color: #0a0; font-weight: bold";
15625
15820
  var FAIL = "color: #c00; font-weight: bold";
15626
15821
  var SKIP = "color: #888";
15627
15822
  var DIM = "color: #888";
@@ -15636,7 +15831,7 @@ function reportTestNode(node) {
15636
15831
  }
15637
15832
  const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
15638
15833
  if (node.status === "pass") {
15639
- console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
15834
+ console.log(`%c✓%c ${node.title}%c${dur}`, PASS2, RESET, DIM);
15640
15835
  } else if (node.status === "skip") {
15641
15836
  console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
15642
15837
  } else {
@@ -15663,7 +15858,7 @@ function reportTestReportToConsole(report) {
15663
15858
  const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
15664
15859
  if (c.fail > 0) console.error(`%c${summary}`, FAIL);
15665
15860
  else if (c.total === 0) console.log(`%c${summary}`, DIM);
15666
- else console.log(`%c${summary}`, PASS);
15861
+ else console.log(`%c${summary}`, PASS2);
15667
15862
  console.groupEnd();
15668
15863
  }
15669
15864
  return report;
@@ -15753,16 +15948,22 @@ function lintIdToMessage(id, info) {
15753
15948
  }
15754
15949
  case "UNKNOWN_HANDLER_ARG_NAME":
15755
15950
  return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
15756
- case "INPUT_HANDLER_NOT_IMPLEMENTED":
15757
- return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
15758
- case "INPUT_HANDLER_NOT_REFERENCED":
15759
- return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
15760
- case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
15951
+ case "RETIRED_HANDLER_BUCKET": {
15952
+ const which = info.names?.length ? ` (${info.names.join(", ")})` : "";
15953
+ return `\`${info.key}\` is no longer a handler bucket${which} — ${info.how}`;
15954
+ }
15955
+ case "RETIRED_CTX_VERB":
15956
+ return `ctx.${info.verb}() no longer exists — use ${info.to} (in \`${info.bucket}.${info.name}\`)`;
15957
+ case "HANDLER_NAME_COLLISION":
15958
+ return `${info.problem} — ${info.fix}`;
15959
+ case "RECEIVE_HANDLER_NOT_IMPLEMENTED":
15960
+ return `Receive handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
15961
+ case "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED":
15761
15962
  return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
15762
- case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
15763
- return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
15764
- case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
15765
- return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
15963
+ case "RECEIVE_HANDLER_FOR_METHOD":
15964
+ return `'$${info.name}' is a method reference, but '${info.name}' is defined as a receive handler${fmtEventSuffix(info)}`;
15965
+ case "METHOD_FOR_RECEIVE_HANDLER":
15966
+ return `'${info.name}' is a receive handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
15766
15967
  case "FIELD_NAME_RESERVED_BY_RECORD":
15767
15968
  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}'))`;
15768
15969
  case "FIELD_VAL_NOT_DEFINED":
@@ -16810,10 +17011,9 @@ function getComponentDoc(comp) {
16810
17011
  name: k,
16811
17012
  sig: getSignature(k, methods[k])
16812
17013
  }));
16813
- const inputHandlers = Object.keys(comp.input).map((k) => ({
16814
- name: k,
16815
- sig: getSignature(k, comp.input[k])
16816
- }));
17014
+ const sigs = (bucket) => Object.keys(bucket ?? {}).map((k) => ({ name: k, sig: getSignature(k, bucket[k]) }));
17015
+ const receiveHandlers = sigs(comp.receive);
17016
+ const intentHandlers = sigs(comp.intent);
16817
17017
  const fieldDocs = [];
16818
17018
  for (const fieldName in fields) {
16819
17019
  const field = fields[fieldName];
@@ -16824,7 +17024,13 @@ function getComponentDoc(comp) {
16824
17024
  methods: getFieldMethods(field)
16825
17025
  });
16826
17026
  }
16827
- return { name, methods: userMethods, input: inputHandlers, fields: fieldDocs };
17027
+ return {
17028
+ name,
17029
+ methods: userMethods,
17030
+ receive: receiveHandlers,
17031
+ intent: intentHandlers,
17032
+ fields: fieldDocs
17033
+ };
16828
17034
  }
16829
17035
  function docComponents(normalized, { name = null } = {}) {
16830
17036
  const comps = normalized.components;
@@ -16931,13 +17137,9 @@ export {
16931
17137
  FIELD_VAL_IS_METHOD,
16932
17138
  FIELD_VAL_NOT_DEFINED,
16933
17139
  GLOBAL_SELECTOR_IN_SCOPED_STYLE,
17140
+ HANDLER_NAME_COLLISION,
16934
17141
  IF_NO_BRANCH_SET,
16935
17142
  Map2 as IMap,
16936
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
16937
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
16938
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
16939
- INPUT_HANDLER_NOT_IMPLEMENTED,
16940
- INPUT_HANDLER_NOT_REFERENCED,
16941
17143
  Set2 as ISet,
16942
17144
  LOOKUP_BAD_SHAPE,
16943
17145
  LOOKUP_TARGET_MALFORMED,
@@ -16950,6 +17152,7 @@ export {
16950
17152
  List,
16951
17153
  MAYBE_ADD_AT_PREFIX,
16952
17154
  MAYBE_DROP_AT_PREFIX,
17155
+ METHOD_FOR_RECEIVE_HANDLER,
16953
17156
  METHOD_VAL_IS_FIELD,
16954
17157
  METHOD_VAL_NOT_DEFINED,
16955
17158
  Map2 as Map,
@@ -16959,12 +17162,18 @@ export {
16959
17162
  OrderedMap as OMap,
16960
17163
  OrderedMap,
16961
17164
  OrderedSet,
17165
+ PASS,
16962
17166
  PLACEHOLDERLESS_TEMPLATE_STRING,
16963
17167
  PROVIDE_NOT_ADDRESSABLE,
16964
17168
  PairSorting,
16965
17169
  ParseContext,
17170
+ RECEIVE_HANDLER_FOR_METHOD,
17171
+ RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
17172
+ RECEIVE_HANDLER_NOT_IMPLEMENTED,
16966
17173
  REDUNDANT_TEMPLATE_STRING,
16967
17174
  RENDER_IT_OUTSIDE_OF_LOOP,
17175
+ RETIRED_CTX_VERB,
17176
+ RETIRED_HANDLER_BUCKET,
16968
17177
  Range,
16969
17178
  Record,
16970
17179
  RenderBatch,
@@ -17035,7 +17244,6 @@ export {
17035
17244
  mergeDeep$1 as mergeDeep,
17036
17245
  mergeDeepWith$1 as mergeDeepWith,
17037
17246
  mergeWith$1 as mergeWith,
17038
- phaseHasBubble,
17039
17247
  phaseOps,
17040
17248
  remove,
17041
17249
  removeIn,