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.
@@ -787,8 +787,8 @@ function parseProvide(s, px) {
787
787
  function parseMacroAttr(s, px) {
788
788
  return _parseSingle(s, px, G_ALL);
789
789
  }
790
- function parseInputHandler(s, px) {
791
- return _parseHandler(s, px, "input", true, true);
790
+ function parseReceiveHandler(s, px) {
791
+ return _parseHandler(s, px, "receive", true, true);
792
792
  }
793
793
  function parseAlterHandler(s, px) {
794
794
  const r = _parseHandler(s, px, "alter", false, false);
@@ -1331,11 +1331,11 @@ var EventHandler = class _EventHandler {
1331
1331
  return [this.handlerVal.evalAsHandler(stack), argValues];
1332
1332
  }
1333
1333
  static parse(s, px) {
1334
- const r = parseInputHandler(s, px);
1334
+ const r = parseReceiveHandler(s, px);
1335
1335
  return r === null ? null : new _EventHandler(r.handlerVal, r.args);
1336
1336
  }
1337
1337
  };
1338
- var RequestHandler = class {
1338
+ var IntentHandler = class {
1339
1339
  constructor(name, fn) {
1340
1340
  this.name = name;
1341
1341
  this.fn = fn;
@@ -5031,7 +5031,7 @@ var KNOWN_COMPONENT_SPEC_KEYS = new Set(
5031
5031
  )
5032
5032
  );
5033
5033
  var EMPTY_SET = /* @__PURE__ */ new Set();
5034
- var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["requestOverridesField"]);
5034
+ var FRAMEWORK_WELL_KNOWN_EXTRAS = /* @__PURE__ */ new Set(["intentOverridesField"]);
5035
5035
  var KNOWN_DIRECTIVE_NAMES = /* @__PURE__ */ new Set([
5036
5036
  "dangerouslysetinnerhtml",
5037
5037
  "slot",
@@ -5056,11 +5056,13 @@ var LOOKUP_TARGET_MALFORMED = "LOOKUP_TARGET_MALFORMED";
5056
5056
  var RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP";
5057
5057
  var UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER";
5058
5058
  var UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME";
5059
- var INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED";
5060
- var INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED";
5061
- var INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED";
5062
- var INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD";
5063
- var INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER";
5059
+ var RECEIVE_HANDLER_NOT_IMPLEMENTED = "RECEIVE_HANDLER_NOT_IMPLEMENTED";
5060
+ var RETIRED_HANDLER_BUCKET = "RETIRED_HANDLER_BUCKET";
5061
+ var RETIRED_CTX_VERB = "RETIRED_CTX_VERB";
5062
+ var HANDLER_NAME_COLLISION = "HANDLER_NAME_COLLISION";
5063
+ var RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED = "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED";
5064
+ var RECEIVE_HANDLER_FOR_METHOD = "RECEIVE_HANDLER_FOR_METHOD";
5065
+ var METHOD_FOR_RECEIVE_HANDLER = "METHOD_FOR_RECEIVE_HANDLER";
5064
5066
  var FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED";
5065
5067
  var FIELD_VAL_IS_METHOD = "FIELD_VAL_IS_METHOD";
5066
5068
  var METHOD_VAL_NOT_DEFINED = "METHOD_VAL_NOT_DEFINED";
@@ -5178,6 +5180,9 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
5178
5180
  checkProvidesAreAddressable(lx, Comp);
5179
5181
  checkLookupShapes(lx, Comp);
5180
5182
  checkHandlersNotAsync(lx, Comp);
5183
+ checkRetiredBuckets(lx, Comp);
5184
+ checkRetiredCtxVerbs(lx, Comp);
5185
+ checkHandlerNameCollisions(lx, Comp);
5181
5186
  checkScopedStyleTopLevel(lx, Comp);
5182
5187
  const referencedAlters = /* @__PURE__ */ new Set();
5183
5188
  const referencedInputs = /* @__PURE__ */ new Set();
@@ -5191,7 +5196,6 @@ function checkComponent(Comp, lx = new LintContext(), { wellKnownExtras = EMPTY_
5191
5196
  );
5192
5197
  }
5193
5198
  checkUnreferencedAlterHandlers(lx, Comp, referencedAlters);
5194
- checkUnreferencedInputHandlers(lx, Comp, referencedInputs);
5195
5199
  checkUnreferencedDynamics(lx, Comp, referencedDynamics);
5196
5200
  return lx;
5197
5201
  });
@@ -5461,7 +5465,7 @@ function mkAttrValEnv(Comp, referencedAlters, referencedDynamics) {
5461
5465
  return { fields, proto, scope, alter, referencedAlters, dynamicMap, referencedDynamics };
5462
5466
  }
5463
5467
  function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
5464
- const { input, views, Class } = Comp;
5468
+ const { receive: input, views, Class } = Comp;
5465
5469
  const { prototype: proto } = Class;
5466
5470
  for (const viewName in views) {
5467
5471
  lx.push({ viewName }, () => {
@@ -5478,13 +5482,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
5478
5482
  if (input[name] === void 0) {
5479
5483
  const isMethodFix = protoHasMethod(proto, name);
5480
5484
  lx.error(
5481
- INPUT_HANDLER_NOT_IMPLEMENTED,
5485
+ RECEIVE_HANDLER_NOT_IMPLEMENTED,
5482
5486
  { name, handler, event, eventName, originAttr },
5483
5487
  isMethodFix ? { kind: "add-prefix", from: name, to: `$${name}` } : replaceNameSuggestion(name, Object.keys(input))
5484
5488
  );
5485
5489
  if (isMethodFix) {
5486
5490
  lx.hint(
5487
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
5491
+ METHOD_FOR_RECEIVE_HANDLER,
5488
5492
  { name, handler, event, eventName, originAttr },
5489
5493
  { kind: "add-prefix", from: name, to: `$${name}` }
5490
5494
  );
@@ -5496,13 +5500,13 @@ function checkEventHandlersHaveImpls(lx, Comp, referencedInputs) {
5496
5500
  if (!protoHasMethod(proto, name)) {
5497
5501
  const isInputFix = input[name] !== void 0;
5498
5502
  lx.error(
5499
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
5503
+ RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
5500
5504
  { name, handler, event, eventName, originAttr },
5501
5505
  isInputFix ? { kind: "drop-prefix", from: `$${name}`, to: name } : replaceNameSuggestion(name, collectProtoMethodNames(proto))
5502
5506
  );
5503
5507
  if (isInputFix) {
5504
5508
  lx.hint(
5505
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
5509
+ RECEIVE_HANDLER_FOR_METHOD,
5506
5510
  { name, handler, event, eventName, originAttr },
5507
5511
  { kind: "drop-prefix", from: `$${name}`, to: name }
5508
5512
  );
@@ -5751,12 +5755,83 @@ function checkConsistentAttrs(lx, Comp, referencedAlters, referencedDynamics) {
5751
5755
  });
5752
5756
  }
5753
5757
  }
5758
+ var isPlainObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
5759
+ var RETIRED_BUCKETS = {
5760
+ input: {
5761
+ to: "receive",
5762
+ 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"
5763
+ },
5764
+ bubble: {
5765
+ to: "intent",
5766
+ 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"
5767
+ },
5768
+ response: {
5769
+ to: "receive",
5770
+ 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"
5771
+ }
5772
+ };
5773
+ function checkRetiredBuckets(lx, Comp) {
5774
+ const extra = Comp.extra;
5775
+ if (!extra) return;
5776
+ for (const key of Object.keys(extra)) {
5777
+ if (!Object.hasOwn(RETIRED_BUCKETS, key)) continue;
5778
+ const retired = RETIRED_BUCKETS[key];
5779
+ const names = isPlainObj(extra[key]) ? Object.keys(extra[key]) : [];
5780
+ lx.error(RETIRED_HANDLER_BUCKET, { key, to: retired.to, how: retired.how, names });
5781
+ }
5782
+ }
5783
+ var RETIRED_CTX_VERBS = {
5784
+ bubble: 'ctx.intent(name, args, { route: ["dyn"] }) - the route says it walks the ancestors, where the verb used to',
5785
+ 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',
5786
+ stopPropagation: "ctx.stop()",
5787
+ inputAtPath: "ctx.sendAtPath(path, name, args)",
5788
+ requestAtPath: 'ctx.intentAtPath(path, name, args, { route: ["lex"] })'
5789
+ };
5790
+ var RETIRED_CTX_RE = new RegExp(
5791
+ `\\bctx\\.(${Object.keys(RETIRED_CTX_VERBS).join("|")})\\s*\\(`,
5792
+ "g"
5793
+ );
5794
+ function checkRetiredCtxVerbs(lx, Comp) {
5795
+ for (const bucket of ["receive", "intent", "alter", "methods", "statics"]) {
5796
+ const block = bucket === "methods" || bucket === "statics" ? Comp.spec?.[bucket] : Comp[bucket];
5797
+ if (!isPlainObj(block)) continue;
5798
+ for (const name in block) {
5799
+ const fn = block[name];
5800
+ if (typeof fn !== "function") continue;
5801
+ const src = String(fn);
5802
+ for (const m of src.matchAll(RETIRED_CTX_RE)) {
5803
+ const verb = m[1];
5804
+ lx.error(RETIRED_CTX_VERB, { verb, to: RETIRED_CTX_VERBS[verb], bucket, name });
5805
+ }
5806
+ }
5807
+ }
5808
+ }
5809
+ function checkHandlerNameCollisions(lx, Comp) {
5810
+ const receive = Comp.receive ?? {};
5811
+ const intent = Comp.intent ?? {};
5812
+ for (const name in intent)
5813
+ if (receive[name] !== void 0)
5814
+ lx.error(HANDLER_NAME_COLLISION, {
5815
+ name,
5816
+ problem: `"${name}" is declared in both \`receive\` and \`intent\``,
5817
+ fix: "one is addressed and one is routed; give them different names so a reader can tell which a call site meant"
5818
+ });
5819
+ for (const name in receive)
5820
+ for (const suffix of ["Ok", "Error", "Unhandled"])
5821
+ if (receive[name + suffix] !== void 0 && intent[name] === void 0)
5822
+ lx.warn(HANDLER_NAME_COLLISION, {
5823
+ name,
5824
+ problem: `\`receive\` declares both "${name}" and its answer name "${name + suffix}"`,
5825
+ fix: `an intent named "${name}" would dispatch its answer to "${name + suffix}" - rename one unless that is what you meant`
5826
+ });
5827
+ }
5754
5828
  function checkUnknownSpecKeys(lx, Comp, wellKnownExtras) {
5755
5829
  const extra = Comp.extra;
5756
5830
  if (!extra) return;
5757
5831
  let candidates = null;
5758
5832
  for (const key of Object.keys(extra)) {
5759
5833
  if (FRAMEWORK_WELL_KNOWN_EXTRAS.has(key) || wellKnownExtras.has(key)) continue;
5834
+ if (Object.hasOwn(RETIRED_BUCKETS, key)) continue;
5760
5835
  candidates ??= [
5761
5836
  ...KNOWN_COMPONENT_SPEC_KEYS,
5762
5837
  ...FRAMEWORK_WELL_KNOWN_EXTRAS,
@@ -5804,15 +5879,8 @@ function checkUnreferencedAlterHandlers(lx, Comp, referencedAlters) {
5804
5879
  }
5805
5880
  }
5806
5881
  }
5807
- function checkUnreferencedInputHandlers(lx, Comp, referencedInputs) {
5808
- for (const name in Comp.input) {
5809
- if (!referencedInputs.has(name)) {
5810
- lx.hint(INPUT_HANDLER_NOT_REFERENCED, { name });
5811
- }
5812
- }
5813
- }
5814
- var HANDLER_CHANNELS = ["input", "receive", "bubble", "response", "alter"];
5815
- var ASYNC_HANDLER_HELP = "Move the async work into a request handler and trigger it with ctx.request('name', args), then handle the result in a synchronous response handler. To coordinate other components, keep the handler synchronous and use ctx.send to deliver a message or ctx.bubble to raise an event.";
5882
+ var HANDLER_CHANNELS = ["receive", "intent", "alter"];
5883
+ var ASYNC_HANDLER_HELP = `Move the async work into a scope-registered intent handler and trigger it with ctx.intent("name", args, { route: ["lex"] }), then read the outcome in the synchronous \`receive\` arms named after it: <name>Ok, <name>Error and <name>Unhandled. To coordinate other components, keep the handler synchronous and use ctx.send to address one, or ctx.intent to let a route find one.`;
5816
5884
  function checkHandlersNotAsync(lx, Comp) {
5817
5885
  for (const channel of HANDLER_CHANNELS) {
5818
5886
  const block = Comp[channel];
@@ -6008,8 +6076,11 @@ var Components = class {
6008
6076
  getHandlerFor(v, name, key) {
6009
6077
  return this.getCompFor(v)?.[key][name] ?? null;
6010
6078
  }
6011
- getRequestFor(v, name) {
6012
- return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
6079
+ // The `lex` leg of an intent's route: the handlers registered on the scope chain of
6080
+ // the component that raised it, innermost first. A list, not a first match — see
6081
+ // ComponentStack.lookupIntentChain.
6082
+ getIntentChainFor(v, name) {
6083
+ return this.getCompFor(v)?.scope.lookupIntentChain(name) ?? [];
6013
6084
  }
6014
6085
  compileStyles() {
6015
6086
  const styles = [];
@@ -6022,7 +6093,7 @@ var ComponentStack = class _ComponentStack {
6022
6093
  this.comps = comps;
6023
6094
  this.parent = parent;
6024
6095
  this.byName = {};
6025
- this.reqsByName = {};
6096
+ this.intentsByName = {};
6026
6097
  this.macros = {};
6027
6098
  }
6028
6099
  enter() {
@@ -6054,11 +6125,21 @@ var ComponentStack = class _ComponentStack {
6054
6125
  getCompFor(v) {
6055
6126
  return this.comps.getCompFor(v);
6056
6127
  }
6057
- registerRequestHandlers(handlers) {
6058
- for (const name in handlers) this.reqsByName[name] = new RequestHandler(name, handlers[name]);
6128
+ // Handlers that answer an intent's `lex` leg. A name maps to a LIST, because the leg
6129
+ // walks: a handler that returns PASS declines and hands the intent to the next one.
6130
+ // A bare function is accepted as a one-element list.
6131
+ registerIntentHandlers(handlers) {
6132
+ for (const name in handlers) {
6133
+ const fns = Array.isArray(handlers[name]) ? handlers[name] : [handlers[name]];
6134
+ this.intentsByName[name] = fns.map((fn) => new IntentHandler(name, fn));
6135
+ }
6059
6136
  }
6060
- lookupRequest(name) {
6061
- return this.reqsByName[name] ?? this.parent?.lookupRequest(name) ?? null;
6137
+ // Innermost scope first, then outward. Concatenated rather than first-match, so a
6138
+ // declining handler can hand the intent to one further up.
6139
+ lookupIntentChain(name) {
6140
+ const here = this.intentsByName[name] ?? [];
6141
+ const up = this.parent?.lookupIntentChain(name) ?? [];
6142
+ return up.length === 0 ? here : here.concat(up);
6062
6143
  }
6063
6144
  lookupComponent(name) {
6064
6145
  return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
@@ -6089,7 +6170,7 @@ var LookupInfo = class {
6089
6170
  }
6090
6171
  };
6091
6172
  var isString = (v) => typeof v === "string";
6092
- var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
6173
+ var _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
6093
6174
  var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
6094
6175
  var _compId = 0;
6095
6176
  var Component = class {
@@ -6100,10 +6181,8 @@ var Component = class {
6100
6181
  this.views = { main: new View("main", o.view, o.style) };
6101
6182
  this.commonStyle = o.commonStyle ?? "";
6102
6183
  this.globalStyle = o.globalStyle ?? "";
6103
- this.input = o.input ?? {};
6104
6184
  this.receive = o.receive ?? {};
6105
- this.bubble = o.bubble ?? {};
6106
- this.response = o.response ?? {};
6185
+ this.intent = o.intent ?? {};
6107
6186
  this.alter = o.alter ?? {};
6108
6187
  for (const name in o.views ?? {}) {
6109
6188
  const v = o.views[name];
@@ -6165,7 +6244,7 @@ var Component = class {
6165
6244
  };
6166
6245
 
6167
6246
  // src/on.js
6168
- var OP_KINDS = ["send", "bubble", "request", "input"];
6247
+ var OP_KINDS = ["send", "intent"];
6169
6248
  function phaseOps(phase) {
6170
6249
  const ops = [];
6171
6250
  for (const type of OP_KINDS) for (const a of phase[type] ?? []) ops.push({ type, ...a });
@@ -6175,11 +6254,6 @@ function phaseOps(phase) {
6175
6254
  function resolveArgs(args, self) {
6176
6255
  return typeof args === "function" ? args(self) ?? [] : args ?? [];
6177
6256
  }
6178
- function phaseHasBubble(phase) {
6179
- if (!phase) return false;
6180
- if (phase.bubble?.length) return true;
6181
- return (phase.do ?? []).some((op) => op.type === "bubble");
6182
- }
6183
6257
  function dispatchPhase(dispatcher, targetPath, phase, self) {
6184
6258
  if (!phase) return;
6185
6259
  for (const op of phaseOps(phase)) {
@@ -6188,18 +6262,8 @@ function dispatchPhase(dispatcher, targetPath, phase, self) {
6188
6262
  case "send":
6189
6263
  dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
6190
6264
  break;
6191
- case "bubble":
6192
- dispatcher.sendAtPath(targetPath, op.name, args, {
6193
- skipSelf: true,
6194
- bubbles: true,
6195
- ...op.opts
6196
- });
6197
- break;
6198
- case "request":
6199
- dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
6200
- break;
6201
- case "input":
6202
- dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
6265
+ case "intent":
6266
+ dispatcher.intentAtPath(targetPath, op.name, args, op.opts);
6203
6267
  break;
6204
6268
  }
6205
6269
  }
@@ -6371,8 +6435,11 @@ var Transactor = class {
6371
6435
  this.transactions.push(t);
6372
6436
  this.onTransactionPushed(t);
6373
6437
  }
6374
- // Subscribe to a normalized record for every handler invocation (send/receive,
6375
- // bubble, request/response, input). Returns an unsubscribe function. Records
6438
+ // Subscribe to a normalized record for every handler invocation. `kind` is the
6439
+ // handler bucket (`receive` / `intent`), plus `answer` — an observation-only kind for
6440
+ // a message the runtime wrote to answer an intent. The BUCKET does not know about
6441
+ // that distinction, and must not: a handler cannot tell an answer from a message its
6442
+ // parent sent. Returns an unsubscribe function. Records
6376
6443
  // carry: kind, name, args, path, pathKeys, targetPath, handler, handlerName,
6377
6444
  // matched, before, after, parent, timestamp. Purely observational.
6378
6445
  observe(cb) {
@@ -6425,23 +6492,14 @@ var Transactor = class {
6425
6492
  this.pushTransaction(t);
6426
6493
  return this._link(t, parent);
6427
6494
  }
6428
- pushInput(path, name, args = [], opts = {}, parent = null) {
6429
- const t = new InputDispatchEvent(path, this, name, args, parent, opts);
6430
- this.pushTransaction(t);
6431
- return this._link(t, parent);
6432
- }
6433
- pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
6434
- const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
6435
- const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
6436
- this.pushTransaction(t);
6437
- return this._link(t, parent);
6438
- }
6439
- pushRequest(path, name, args = [], opts = {}, parent = null) {
6495
+ // Raise an intent: a job the sender did not address, walked along a route until
6496
+ // something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
6497
+ // `opts.livePath` opts out of pinning the answer's destination keys.
6498
+ pushIntent(path, name, args = [], opts = {}, parent = null) {
6440
6499
  const release = parent ? parent.completion.track() : null;
6441
- const p = this._runRequest(path, name, args, opts, parent, release);
6442
- this._inflight.add(p);
6443
- p.finally(() => this._inflight.delete(p));
6444
- return p;
6500
+ const walk = new IntentWalk(this, path, name, args, opts, parent, release);
6501
+ walk.advance();
6502
+ return walk;
6445
6503
  }
6446
6504
  // Drain queued transactions and await in-flight requests until quiescent. Each
6447
6505
  // awaited request enqueues a ResponseEvent, which may dispatch more work, so we
@@ -6452,57 +6510,6 @@ var Transactor = class {
6452
6510
  if (this._inflight.size) await Promise.allSettled([...this._inflight]);
6453
6511
  }
6454
6512
  }
6455
- async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
6456
- let released = false;
6457
- const transfer = (t) => {
6458
- if (release) {
6459
- released = true;
6460
- t.completion.whenSubtreeSettled().then(release);
6461
- }
6462
- };
6463
- try {
6464
- const curRoot = this.state.val;
6465
- const txnPath = path.toTransactionPath();
6466
- const curLeaf = txnPath.lookup(curRoot);
6467
- const found = this.comps.getRequestFor(curLeaf, name);
6468
- const handler = found ?? mkReq404(name);
6469
- if (this._observers.length > 0) {
6470
- const reqPath = txnPath.pinKeys(curRoot);
6471
- this._emit({
6472
- kind: "request",
6473
- name,
6474
- args,
6475
- path: reqPath,
6476
- pathKeys: reqPath.toKeys(),
6477
- targetPath: path,
6478
- handler: found?.fn ?? null,
6479
- handlerName: found?.fn?.name || name,
6480
- matched: found ? "exact" : "none",
6481
- before: curLeaf,
6482
- after: void 0,
6483
- parent,
6484
- timestamp: Date.now()
6485
- });
6486
- }
6487
- const reqCtx = new RequestContext(path, this, parent, curRoot);
6488
- const resHandlerName = opts?.onResName ?? name;
6489
- const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
6490
- const push = (specificName, baseName, singleArg, result, error) => {
6491
- const resArgs = specificName ? [singleArg] : [result, error];
6492
- const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
6493
- transfer(t);
6494
- this.pushTransaction(t);
6495
- };
6496
- try {
6497
- const result = await handler.fn.apply(null, [...args, reqCtx]);
6498
- push(opts?.onOkName, resHandlerName, result, result, null);
6499
- } catch (error) {
6500
- push(opts?.onErrorName, resHandlerName, error, null, error);
6501
- }
6502
- } finally {
6503
- if (release && !released) release();
6504
- }
6505
- }
6506
6513
  get hasPendingTransactions() {
6507
6514
  return this.transactions.length > 0;
6508
6515
  }
@@ -6519,6 +6526,7 @@ var Transactor = class {
6519
6526
  this._emitTransaction(transaction, curState);
6520
6527
  } else console.warn("undefined new state", { curState, transaction });
6521
6528
  } finally {
6529
+ transaction.ensureWalkAdvanced?.();
6522
6530
  transaction._completion?.ensureSelfSettled();
6523
6531
  transaction._completion?.releaseSelf();
6524
6532
  }
@@ -6527,12 +6535,6 @@ var Transactor = class {
6527
6535
  this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
6528
6536
  }
6529
6537
  };
6530
- function mkReq404(name) {
6531
- const fn = () => {
6532
- throw new Error(`Request not found: ${name}`);
6533
- };
6534
- return { fn };
6535
- }
6536
6538
  function nullHandler() {
6537
6539
  return this;
6538
6540
  }
@@ -6563,6 +6565,15 @@ var Transaction = class {
6563
6565
  }
6564
6566
  afterTransaction() {
6565
6567
  }
6568
+ // Ending a walk and forwarding belong to the two dispatch buckets; the base warns
6569
+ // rather than throwing, so a stray call from the wrong place is a message and not a
6570
+ // crash. `walk` is undefined here, which is how ctx.reply/ctx.fail tell the two apart.
6571
+ stop() {
6572
+ console.warn('ctx.stop() is only meaningful in an "intent" handler - ignored');
6573
+ }
6574
+ forward(_opts) {
6575
+ console.warn('ctx.forward() needs a "receive" or "intent" handler - ignored');
6576
+ }
6566
6577
  buildRootStack(root, comps) {
6567
6578
  return Stack.root(comps, root);
6568
6579
  }
@@ -6624,18 +6635,40 @@ var InputEvent = class extends Transaction {
6624
6635
  this._dispatchPath ??= this.path.compact();
6625
6636
  return this._dispatchPath;
6626
6637
  }
6638
+ // A DOM event is an ADDRESSED message like any other — it reaches the component that
6639
+ // owns the view and stops. It keeps its own class only because it resolves its handler
6640
+ // from the compiled view and runs synchronously, not because it is a different channel.
6627
6641
  get observeKind() {
6628
- return "input";
6642
+ return "receive";
6629
6643
  }
6630
6644
  get observeName() {
6631
6645
  return this.e?.type ?? null;
6632
6646
  }
6647
+ // A view's name is a message. `forward` is where that name can LEAVE the component:
6648
+ // it becomes an intent with the same name and the same arguments, so a view that says
6649
+ // `@on.click="saveDraft .text"` never has to change when an ancestor takes the job over.
6650
+ // (A `$method` handler has a name too, so forwarding one raises an intent under it.)
6651
+ forward(opts) {
6652
+ this._forward = opts ?? {};
6653
+ }
6654
+ afterTransaction() {
6655
+ const f = this._forward;
6656
+ if (f === void 0) return;
6657
+ this._forward = void 0;
6658
+ const { args = this._handlerArgs ?? [], ...rest } = f;
6659
+ const hv = this.handler?.handlerCall?.handlerVal ?? this.handler?.handlerVal;
6660
+ const name = hv?.name;
6661
+ if (name === void 0)
6662
+ return console.warn("ctx.forward() from a handler with no name - ignored");
6663
+ this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
6664
+ }
6633
6665
  buildRootStack(root, comps) {
6634
6666
  return Stack.root(comps, root, this);
6635
6667
  }
6636
6668
  getHandlerAndArgs(root, _instance, comps) {
6637
6669
  const stack = this.buildStack(root, comps);
6638
6670
  const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
6671
+ this._handlerArgs = [...args];
6639
6672
  const path = this.dispatchPath;
6640
6673
  args.push(new EventContext(path, this.transactor, this));
6641
6674
  return [handler, args];
@@ -6720,39 +6753,190 @@ var NameArgsTransaction = class extends Transaction {
6720
6753
  return [handler, [...this.args, new EventContext(this.path, this.transactor, this)]];
6721
6754
  }
6722
6755
  };
6723
- var ResponseEvent = class extends NameArgsTransaction {
6724
- handlerProp = "response";
6725
- constructor(path, transactor, name, args, parent, txnPath = null) {
6726
- super(path, transactor, name, args, parent);
6727
- this._txnPath = txnPath;
6728
- }
6729
- getTransactionPath() {
6730
- return this._txnPath ?? super.getTransactionPath();
6731
- }
6732
- };
6733
6756
  var SendEvent = class extends NameArgsTransaction {
6734
6757
  handlerProp = "receive";
6735
- run(rootVal, comps) {
6736
- return this.opts.skipSelf ? rootVal : this.updateRootValue(rootVal, comps);
6758
+ // `receive` is the ADDRESSED bucket, and it is the only one: a view's `@on.click`,
6759
+ // a parent's ctx.send, the host's sendAtRoot and an answer to an intent all arrive
6760
+ // here, and a handler cannot tell them apart. Splitting on where a message came from
6761
+ // would let the view's name through and turn the parent's away, though the two say
6762
+ // the same thing — and neither a test nor a parent could drive the component.
6763
+ get observeKind() {
6764
+ return this._isAnswer ? "answer" : "receive";
6765
+ }
6766
+ // `forward` in a RECEIVE body starts a walk: the message that arrived becomes an
6767
+ // intent, keeping its name and payload. This is what lets a view's name leave the
6768
+ // component without the view changing.
6769
+ forward(opts) {
6770
+ this._forward = opts ?? {};
6737
6771
  }
6738
6772
  afterTransaction() {
6739
- const { path, name, args, opts, targetPath } = this;
6740
- if (opts.bubbles && path.steps.length > 0)
6741
- this.transactor.pushBubble(path.popStep(), name, args, opts, this, targetPath);
6773
+ const f = this._forward;
6774
+ if (f === void 0) return;
6775
+ this._forward = void 0;
6776
+ const { args = this.args, ...rest } = f;
6777
+ this.transactor.pushIntent(this.path, this.name, args, rest, this);
6742
6778
  }
6743
6779
  };
6744
- var BubbleEvent = class extends SendEvent {
6745
- handlerProp = "bubble";
6746
- constructor(path, transactor, name, args, parent, opts, targetPath) {
6747
- super(path, transactor, name, args, parent, opts);
6748
- this.targetPath = targetPath ?? path;
6780
+ var IntentEvent = class extends NameArgsTransaction {
6781
+ handlerProp = "intent";
6782
+ constructor(path, transactor, walk) {
6783
+ super(path, transactor, walk.name, walk.args, walk.parent, {});
6784
+ this.walk = walk;
6785
+ this.targetPath = walk.origin;
6749
6786
  }
6750
- stopPropagation() {
6751
- this.opts.bubbles = false;
6787
+ // `forward` in an INTENT body amends the hop about to be pushed — it does not push
6788
+ // one itself, because a walk advances on its own.
6789
+ forward(opts) {
6790
+ this.walk.amend(opts, this.path);
6791
+ }
6792
+ stop() {
6793
+ this.walk.finish(null, null);
6794
+ }
6795
+ afterTransaction() {
6796
+ this.ensureWalkAdvanced();
6797
+ }
6798
+ // Idempotent, because both afterTransaction and the transactor's `finally` call it —
6799
+ // whichever gets there first advances the walk exactly once.
6800
+ ensureWalkAdvanced() {
6801
+ if (this._advanced) return;
6802
+ this._advanced = true;
6803
+ this.walk.advance();
6752
6804
  }
6753
6805
  };
6754
- var InputDispatchEvent = class extends NameArgsTransaction {
6755
- handlerProp = "input";
6806
+ var PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
6807
+ var DEFAULT_ROUTE = ["dyn", "lex"];
6808
+ var INTENT_DEPTH = 64;
6809
+ var IntentWalk = class {
6810
+ constructor(transactor, path, name, args, opts, parent, release) {
6811
+ this.transactor = transactor;
6812
+ this.name = name;
6813
+ this.args = args;
6814
+ this.route = opts?.route ?? DEFAULT_ROUTE;
6815
+ this.parent = parent;
6816
+ this.release = release;
6817
+ this.origin = path;
6818
+ this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
6819
+ this.legIndex = 0;
6820
+ this.dynAt = path;
6821
+ this.hops = 0;
6822
+ this.ended = false;
6823
+ }
6824
+ // Offer the intent to the next hop. Called once at dispatch, and again by every hop
6825
+ // that ran without answering.
6826
+ advance() {
6827
+ if (this.ended) return;
6828
+ if (this.hops++ >= INTENT_DEPTH) return this.exhaust("intentDepth");
6829
+ while (this.legIndex < this.route.length) {
6830
+ const leg = this.route[this.legIndex];
6831
+ if (leg === "dyn") {
6832
+ if (this.dynAt.steps.length === 0) {
6833
+ this.legIndex++;
6834
+ continue;
6835
+ }
6836
+ this.dynAt = this.dynAt.popStep();
6837
+ this.transactor.pushTransaction(new IntentEvent(this.dynAt, this.transactor, this));
6838
+ return;
6839
+ }
6840
+ if (leg === "lex") {
6841
+ this.legIndex++;
6842
+ return this._tryLex();
6843
+ }
6844
+ console.warn("unknown intent route leg", leg, '- expected "dyn" or "lex"');
6845
+ this.legIndex++;
6846
+ }
6847
+ this.exhaust("noHandler");
6848
+ }
6849
+ // The `lex` leg: the handlers registered on the scope chain of the component that
6850
+ // raised the intent, in order.
6851
+ _tryLex() {
6852
+ const root = this.transactor.state.val;
6853
+ const txnPath = this.origin.toTransactionPath();
6854
+ const leaf = txnPath.lookup(root);
6855
+ const chain = this.transactor.comps.getIntentChainFor(leaf, this.name);
6856
+ if (this.transactor._observers.length > 0) {
6857
+ const path = txnPath.pinKeys(root);
6858
+ this.transactor._emit({
6859
+ kind: "intent",
6860
+ name: this.name,
6861
+ args: this.args,
6862
+ path,
6863
+ pathKeys: path.toKeys(),
6864
+ targetPath: this.origin,
6865
+ handler: chain[0]?.fn ?? null,
6866
+ handlerName: chain[0]?.fn?.name || this.name,
6867
+ matched: chain.length > 0 ? "exact" : "none",
6868
+ before: leaf,
6869
+ after: void 0,
6870
+ parent: this.parent,
6871
+ timestamp: Date.now()
6872
+ });
6873
+ }
6874
+ if (chain.length === 0) return this.advance();
6875
+ const p = this._runLex(chain, 0);
6876
+ this.transactor._inflight.add(p);
6877
+ p.finally(() => this.transactor._inflight.delete(p));
6878
+ }
6879
+ async _runLex(chain, i) {
6880
+ if (this.ended) return;
6881
+ if (i >= chain.length) return this.advance();
6882
+ const ctx = new IntentContext(this.origin, this.transactor, this.parent);
6883
+ try {
6884
+ const res = await chain[i].fn.apply(null, [...this.args, ctx]);
6885
+ if (res === PASS) return this._runLex(chain, i + 1);
6886
+ this.answer("Ok", res);
6887
+ } catch (error) {
6888
+ this.answer("Error", error);
6889
+ }
6890
+ }
6891
+ // A hop answered. Ends the walk.
6892
+ answer(suffix, value) {
6893
+ this.finish(`${this.name}${suffix}`, [value]);
6894
+ }
6895
+ // `forward` from an intent body: keep walking, optionally with new arguments or a
6896
+ // narrowed route.
6897
+ amend(opts, from) {
6898
+ if (opts?.args !== void 0) this.args = opts.args;
6899
+ if (opts?.route !== void 0) {
6900
+ this.route = opts.route;
6901
+ this.legIndex = 0;
6902
+ this.dynAt = from ?? this.dynAt;
6903
+ }
6904
+ }
6905
+ // The route ran out. What the sender hears depends only on what it DECLARES, which is
6906
+ // how "a sender expects an answer if and only if it declares an answer arm" becomes
6907
+ // code — nobody writes it down twice, and there is no schema to keep in step.
6908
+ exhaust(reason) {
6909
+ if (this.ended) return;
6910
+ const { name, args } = this;
6911
+ const comp = this.transactor.comps.getCompFor(
6912
+ this.origin.toTransactionPath().lookup(this.transactor.state.val)
6913
+ );
6914
+ const declares = (n) => comp?.receive?.[n] !== void 0;
6915
+ if (declares(`${name}Unhandled`)) {
6916
+ this.finish(`${name}Unhandled`, args);
6917
+ } else if (declares(`${name}Error`)) {
6918
+ this.finish(`${name}Error`, [reason]);
6919
+ } else {
6920
+ if (declares(`${name}Ok`))
6921
+ console.warn(
6922
+ `intent "${name}" was not answered (${reason}) and this component declares only "${name}Ok" - add "${name}Unhandled" or "${name}Error" to handle it`
6923
+ );
6924
+ this.finish(null, null);
6925
+ }
6926
+ }
6927
+ // End the walk. `name === null` means no answer goes out, which is what `stop` and a
6928
+ // fire-and-forget notification both are — the tracked unit is released here instead
6929
+ // of following an answer's subtree.
6930
+ finish(name, args) {
6931
+ if (this.ended) return;
6932
+ this.ended = true;
6933
+ if (name === null) return this.release?.();
6934
+ const path = this.answerPath ?? this.origin;
6935
+ const t = new SendEvent(path, this.transactor, name, args, this.parent, {});
6936
+ t._isAnswer = true;
6937
+ this.transactor.pushTransaction(t);
6938
+ if (this.release) t.completion.whenSubtreeSettled().then(this.release);
6939
+ }
6756
6940
  };
6757
6941
  var Completion = class {
6758
6942
  constructor() {
@@ -6833,23 +7017,21 @@ var Dispatcher = class {
6833
7017
  get at() {
6834
7018
  return new PathChanges(this);
6835
7019
  }
7020
+ // A message is ADDRESSED: it names one component and stops there.
6836
7021
  send(name, args, opts) {
6837
7022
  return this.sendAtPath(this.path, name, args, opts);
6838
7023
  }
6839
- bubble(name, args, opts) {
6840
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
6841
- }
6842
7024
  sendAtPath(path, name, args, opts) {
6843
7025
  return this.transactor.pushSend(path, name, args, opts, this.parent);
6844
7026
  }
6845
- request(name, args, opts) {
6846
- return this.requestAtPath(this.path, name, args, opts);
6847
- }
6848
- requestAtPath(path, name, args, opts) {
6849
- return this.transactor.pushRequest(path, name, args, opts, this.parent);
7027
+ // An intent is ROUTED: it names a job and walks until something answers. The verb
7028
+ // does not decide which scope answers — `opts.route` does, written here at the call
7029
+ // site, which is where the decision actually is.
7030
+ intent(name, args, opts) {
7031
+ return this.intentAtPath(this.path, name, args, opts);
6850
7032
  }
6851
- inputAtPath(path, name, args, opts) {
6852
- return this.transactor.pushInput(path, name, args, opts, this.parent);
7033
+ intentAtPath(path, name, args, opts) {
7034
+ return this.transactor.pushIntent(path, name, args, opts, this.parent);
6853
7035
  }
6854
7036
  lookupTypeFor(name, inst) {
6855
7037
  return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
@@ -6862,12 +7044,30 @@ var EventContext = class extends Dispatcher {
6862
7044
  get targetPath() {
6863
7045
  return this.parent.targetPath;
6864
7046
  }
6865
- stopPropagation() {
6866
- return this.parent.stopPropagation();
7047
+ // Answer the intent being handled, with a result or with an error. Either ENDS the
7048
+ // walk. Legal only in an `intent` handler: a message has no sender waiting on it.
7049
+ reply(value) {
7050
+ return this.parent.walk === void 0 ? warnNotIntent("reply") : this.parent.walk.answer("Ok", value);
7051
+ }
7052
+ fail(error) {
7053
+ return this.parent.walk === void 0 ? warnNotIntent("fail") : this.parent.walk.answer("Error", error);
7054
+ }
7055
+ // End the walk answering nothing — "served, and no answer".
7056
+ stop() {
7057
+ return this.parent.stop();
7058
+ }
7059
+ // From an `intent` body: hand the intent to the next hop, optionally amending it.
7060
+ // From a `receive` body: turn the message that arrived into an intent. One word from
7061
+ // both ends of a walk; which one you get depends on which bucket you are in.
7062
+ forward(opts) {
7063
+ return this.parent.forward(opts);
6867
7064
  }
6868
7065
  };
6869
- var RequestContext = class extends Dispatcher {
7066
+ var IntentContext = class extends Dispatcher {
6870
7067
  };
7068
+ function warnNotIntent(verb) {
7069
+ console.warn(`ctx.${verb}() is only meaningful in an "intent" handler - ignored`);
7070
+ }
6871
7071
  var PathChanges = class extends PathBuilder {
6872
7072
  constructor(dispatcher) {
6873
7073
  super();
@@ -6876,8 +7076,8 @@ var PathChanges = class extends PathBuilder {
6876
7076
  send(name, args, opts) {
6877
7077
  return this.dispatcher.sendAtPath(this.buildPath(), name, args, opts);
6878
7078
  }
6879
- bubble(name, args, opts) {
6880
- return this.send(name, args, { skipSelf: true, bubbles: true, ...opts });
7079
+ intent(name, args, opts) {
7080
+ return this.dispatcher.intentAtPath(this.buildPath(), name, args, opts);
6881
7081
  }
6882
7082
  buildPath() {
6883
7083
  return this.dispatcher.path.concat(this.pathChanges);
@@ -7149,29 +7349,24 @@ function captureError(e) {
7149
7349
  if ("actual" in e) out.actual = e.actual;
7150
7350
  return out;
7151
7351
  }
7152
- function buildStack({ components = [], macros = null, requestHandlers = null } = {}) {
7352
+ function buildStack({ components = [], macros = null, intentHandlers = null } = {}) {
7153
7353
  const stack = new ComponentStack();
7154
7354
  stack.registerComponents(components);
7155
7355
  if (macros) stack.registerMacros(macros);
7156
- if (requestHandlers) stack.registerRequestHandlers(requestHandlers);
7356
+ if (intentHandlers) stack.registerIntentHandlers(intentHandlers);
7157
7357
  return stack;
7158
7358
  }
7159
7359
  async function driveStack(stack, value, phase, opts = {}) {
7160
- const warn = opts.warn ?? console.warn;
7161
7360
  const transactor = new Transactor(stack.comps, value);
7162
7361
  if (opts.onMessage)
7163
7362
  transactor.state.onChange(({ val, old, info }) => {
7164
7363
  const t = info?.transaction;
7165
7364
  opts.onMessage(
7166
- { kind: t?.handlerProp ?? "input", name: t?.name, args: t?.args, path: t?.path },
7365
+ { kind: t?.handlerProp ?? "receive", name: t?.name, args: t?.args, path: t?.path },
7167
7366
  old,
7168
7367
  val
7169
7368
  );
7170
7369
  });
7171
- if (phaseHasBubble(phase))
7172
- warn(
7173
- "drive(): a `bubble` action is a no-op here — drive originates at the root and bubbles travel child→parent, so there is no ancestor to receive it (and the root's own bubble handler is skipped). To exercise a bubble handler, call it directly."
7174
- );
7175
7370
  dispatchPhase(rootDispatcher(transactor), new Path([]), phase, value);
7176
7371
  await transactor.settle();
7177
7372
  return transactor.state.val;
@@ -7187,7 +7382,7 @@ async function runTests({
7187
7382
  name = null,
7188
7383
  grep = null,
7189
7384
  bail = false,
7190
- requestHandlers = null,
7385
+ intentHandlers = null,
7191
7386
  macros = null
7192
7387
  } = {}) {
7193
7388
  const counts = { pass: 0, fail: 0, skip: 0, total: 0 };
@@ -7201,7 +7396,7 @@ async function runTests({
7201
7396
  }
7202
7397
  const { describe, test: test2, moduleTests } = makeCollector({ path, components });
7203
7398
  let _stack = null;
7204
- const getStack = () => _stack ??= buildStack({ components, macros, requestHandlers });
7399
+ const getStack = () => _stack ??= buildStack({ components, macros, intentHandlers });
7205
7400
  const driveLocal = (value, phase, opts = {}) => driveStack(getStack(), value, phase, opts);
7206
7401
  await getTests({ describe, test: test2, expect: expect3, drive: driveLocal });
7207
7402
  let bailed = false;
@@ -7267,7 +7462,7 @@ async function runTests({
7267
7462
  }
7268
7463
 
7269
7464
  // tools/format/console.js
7270
- var PASS = "color: #0a0; font-weight: bold";
7465
+ var PASS2 = "color: #0a0; font-weight: bold";
7271
7466
  var FAIL = "color: #c00; font-weight: bold";
7272
7467
  var SKIP = "color: #888";
7273
7468
  var DIM = "color: #888";
@@ -7282,7 +7477,7 @@ function reportTestNode(node) {
7282
7477
  }
7283
7478
  const dur = node.status === "skip" ? "" : ` (${Math.round(node.durationMs)}ms)`;
7284
7479
  if (node.status === "pass") {
7285
- console.log(`%c✓%c ${node.title}%c${dur}`, PASS, RESET, DIM);
7480
+ console.log(`%c✓%c ${node.title}%c${dur}`, PASS2, RESET, DIM);
7286
7481
  } else if (node.status === "skip") {
7287
7482
  console.log(`%c○ ${node.title}%c (skipped)`, SKIP, RESET);
7288
7483
  } else {
@@ -7309,7 +7504,7 @@ function reportTestReportToConsole(report) {
7309
7504
  const summary = `${c.pass} passed, ${c.fail} failed, ${c.skip} skipped (${c.total} total)`;
7310
7505
  if (c.fail > 0) console.error(`%c${summary}`, FAIL);
7311
7506
  else if (c.total === 0) console.log(`%c${summary}`, DIM);
7312
- else console.log(`%c${summary}`, PASS);
7507
+ else console.log(`%c${summary}`, PASS2);
7313
7508
  console.groupEnd();
7314
7509
  }
7315
7510
  return report;
@@ -7399,16 +7594,22 @@ function lintIdToMessage(id, info) {
7399
7594
  }
7400
7595
  case "UNKNOWN_HANDLER_ARG_NAME":
7401
7596
  return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
7402
- case "INPUT_HANDLER_NOT_IMPLEMENTED":
7403
- return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
7404
- case "INPUT_HANDLER_NOT_REFERENCED":
7405
- return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
7406
- case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
7597
+ case "RETIRED_HANDLER_BUCKET": {
7598
+ const which = info.names?.length ? ` (${info.names.join(", ")})` : "";
7599
+ return `\`${info.key}\` is no longer a handler bucket${which} — ${info.how}`;
7600
+ }
7601
+ case "RETIRED_CTX_VERB":
7602
+ return `ctx.${info.verb}() no longer exists — use ${info.to} (in \`${info.bucket}.${info.name}\`)`;
7603
+ case "HANDLER_NAME_COLLISION":
7604
+ return `${info.problem} — ${info.fix}`;
7605
+ case "RECEIVE_HANDLER_NOT_IMPLEMENTED":
7606
+ return `Receive handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
7607
+ case "RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED":
7407
7608
  return `Method '$${info.name}' is not implemented${fmtEventSuffix(info)}`;
7408
- case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
7409
- return `'$${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
7410
- case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
7411
- return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
7609
+ case "RECEIVE_HANDLER_FOR_METHOD":
7610
+ return `'$${info.name}' is a method reference, but '${info.name}' is defined as a receive handler${fmtEventSuffix(info)}`;
7611
+ case "METHOD_FOR_RECEIVE_HANDLER":
7612
+ return `'${info.name}' is a receive handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
7412
7613
  case "FIELD_NAME_RESERVED_BY_RECORD":
7413
7614
  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}'))`;
7414
7615
  case "FIELD_VAL_NOT_DEFINED":
@@ -8512,10 +8713,9 @@ function getComponentDoc(comp) {
8512
8713
  name: k,
8513
8714
  sig: getSignature(k, methods[k])
8514
8715
  }));
8515
- const inputHandlers = Object.keys(comp.input).map((k) => ({
8516
- name: k,
8517
- sig: getSignature(k, comp.input[k])
8518
- }));
8716
+ const sigs = (bucket) => Object.keys(bucket ?? {}).map((k) => ({ name: k, sig: getSignature(k, bucket[k]) }));
8717
+ const receiveHandlers = sigs(comp.receive);
8718
+ const intentHandlers = sigs(comp.intent);
8519
8719
  const fieldDocs = [];
8520
8720
  for (const fieldName in fields) {
8521
8721
  const field = fields[fieldName];
@@ -8526,7 +8726,13 @@ function getComponentDoc(comp) {
8526
8726
  methods: getFieldMethods(field)
8527
8727
  });
8528
8728
  }
8529
- return { name, methods: userMethods, input: inputHandlers, fields: fieldDocs };
8729
+ return {
8730
+ name,
8731
+ methods: userMethods,
8732
+ receive: receiveHandlers,
8733
+ intent: intentHandlers,
8734
+ fields: fieldDocs
8735
+ };
8530
8736
  }
8531
8737
  function docComponents(normalized, { name = null } = {}) {
8532
8738
  const comps = normalized.components;
@@ -8634,13 +8840,9 @@ export {
8634
8840
  FIELD_VAL_IS_METHOD,
8635
8841
  FIELD_VAL_NOT_DEFINED,
8636
8842
  GLOBAL_SELECTOR_IN_SCOPED_STYLE,
8843
+ HANDLER_NAME_COLLISION,
8637
8844
  IF_NO_BRANCH_SET,
8638
8845
  Map3 as IMap,
8639
- INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD,
8640
- INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER,
8641
- INPUT_HANDLER_METHOD_NOT_IMPLEMENTED,
8642
- INPUT_HANDLER_NOT_IMPLEMENTED,
8643
- INPUT_HANDLER_NOT_REFERENCED,
8644
8846
  Set3 as ISet,
8645
8847
  LOOKUP_BAD_SHAPE,
8646
8848
  LOOKUP_TARGET_MALFORMED,
@@ -8653,6 +8855,7 @@ export {
8653
8855
  List2 as List,
8654
8856
  MAYBE_ADD_AT_PREFIX,
8655
8857
  MAYBE_DROP_AT_PREFIX,
8858
+ METHOD_FOR_RECEIVE_HANDLER,
8656
8859
  METHOD_VAL_IS_FIELD,
8657
8860
  METHOD_VAL_NOT_DEFINED,
8658
8861
  Map2 as Map,
@@ -8662,12 +8865,18 @@ export {
8662
8865
  OrderedMap3 as OMap,
8663
8866
  OrderedMap2 as OrderedMap,
8664
8867
  OrderedSet,
8868
+ PASS,
8665
8869
  PLACEHOLDERLESS_TEMPLATE_STRING,
8666
8870
  PROVIDE_NOT_ADDRESSABLE,
8667
8871
  PairSorting,
8668
8872
  ParseContext,
8873
+ RECEIVE_HANDLER_FOR_METHOD,
8874
+ RECEIVE_HANDLER_METHOD_NOT_IMPLEMENTED,
8875
+ RECEIVE_HANDLER_NOT_IMPLEMENTED,
8669
8876
  REDUNDANT_TEMPLATE_STRING,
8670
8877
  RENDER_IT_OUTSIDE_OF_LOOP,
8878
+ RETIRED_CTX_VERB,
8879
+ RETIRED_HANDLER_BUCKET,
8671
8880
  Range,
8672
8881
  Record2 as Record,
8673
8882
  RenderBatch,
@@ -8738,7 +8947,6 @@ export {
8738
8947
  mergeDeep,
8739
8948
  mergeDeepWith,
8740
8949
  mergeWith,
8741
- phaseHasBubble,
8742
8950
  phaseOps,
8743
8951
  remove,
8744
8952
  removeIn,