tutuca 0.13.3 → 0.14.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.
@@ -76,8 +76,11 @@ var Step = class {
76
76
  }
77
77
  setDraftValue(_root, _v) {
78
78
  }
79
- enterFrame(stack, next) {
80
- return stack.enter(next, {}, true);
79
+ // Re-enter this step while rebuilding a stack. `renderPath` is the dispatch
80
+ // position AFTER this step: a rebuilt frame must carry the same render path
81
+ // the renderer had there, or the provides it publishes would be located wrong.
82
+ enterFrame(stack, next, renderPath) {
83
+ return stack.enter(next, {}, true, renderPath);
81
84
  }
82
85
  toAbstractPathStep() {
83
86
  return this;
@@ -102,8 +105,8 @@ var BindStep = class _BindStep extends Step {
102
105
  lookup(v, _dval) {
103
106
  return v;
104
107
  }
105
- enterFrame(stack, next) {
106
- return stack.enter(next, { ...this.binds }, false);
108
+ enterFrame(stack, next, renderPath) {
109
+ return stack.enter(next, { ...this.binds }, false, renderPath);
107
110
  }
108
111
  withIndex(i) {
109
112
  return new _BindStep({ ...this.binds, key: i });
@@ -120,9 +123,9 @@ var ScopeBindStep = class _ScopeBindStep extends BindStep {
120
123
  super(binds);
121
124
  this.val = val;
122
125
  }
123
- enterFrame(stack, next) {
126
+ enterFrame(stack, next, renderPath) {
124
127
  const dyn = this.val.evalAsHandler(stack)?.call(stack.it) ?? {};
125
- return stack.enter(next, { ...this.binds, ...dyn }, false);
128
+ return stack.enter(next, { ...this.binds, ...dyn }, false, renderPath);
126
129
  }
127
130
  withIndex(i) {
128
131
  return new _ScopeBindStep(this.val, { ...this.binds, key: i });
@@ -165,8 +168,8 @@ var SeqStep = class extends Step {
165
168
  const seq = readKey(root, this.field, null);
166
169
  if (seq != null) writeSeqKey(seq, this.key, v);
167
170
  }
168
- enterFrame(stack, next) {
169
- return stack.enter(next, { key: this.key }, true);
171
+ enterFrame(stack, next, renderPath) {
172
+ return stack.enter(next, { key: this.key }, true, renderPath);
170
173
  }
171
174
  toKey() {
172
175
  return { field: this.field, key: this.key };
@@ -212,60 +215,52 @@ var EachBindStep = class extends Step {
212
215
  }
213
216
  // Replay the renderer's per-item binds (key, value + any @enrich-with binds)
214
217
  // so a rebuilt stack matches the one @each rendered with.
215
- enterFrame(stack, next) {
216
- return stack.enter(next, this.iterInfo.enrichBinds(stack, this.key), false);
218
+ enterFrame(stack, next, renderPath) {
219
+ return stack.enter(next, this.iterInfo.enrichBinds(stack, this.key), false, renderPath);
217
220
  }
218
221
  toAbstractPathStep() {
219
222
  return null;
220
223
  }
221
224
  };
222
225
  var EachRenderItStep = class extends SeqStep {
223
- enterFrame(stack, next) {
224
- return stack.enter(next, { key: this.key, value: next }, false).enter(next, {}, true);
226
+ enterFrame(stack, next, renderPath) {
227
+ return stack.enter(next, { key: this.key, value: next }, false, renderPath).enter(next, {}, true, renderPath);
225
228
  }
226
229
  toAbstractPathStep() {
227
230
  return new SeqStep(this.field, this.key);
228
231
  }
229
232
  };
230
- function warnRawDynStep(op, step) {
231
- console.warn(`Path.${op} reached a DynStep: call toTransactionPath() first`, step);
233
+ function stepToJson(step) {
234
+ if (step instanceof SeqStep) return { f: step.field, k: step.key };
235
+ if (step instanceof FieldStep) return { f: step.field };
236
+ if (step instanceof SeqAccessStep) return { f: step.seqField, a: step.keyField };
237
+ return null;
232
238
  }
233
- var DynStep = class extends Step {
234
- constructor(producerCompId, producerSteps) {
235
- super();
236
- this.producerCompId = producerCompId;
237
- this.producerSteps = producerSteps;
238
- this.interiorCids = /* @__PURE__ */ new Set();
239
- }
240
- // Steps spliced into the transaction path in place of this marker.
241
- teleportSteps() {
242
- return this.producerSteps;
243
- }
244
- lookup(_v, dval = null) {
245
- warnRawDynStep("lookup", this);
246
- return dval;
247
- }
248
- enterFrame(stack, _next) {
249
- warnRawDynStep("enterFrame", this);
250
- return stack;
251
- }
252
- };
253
- var DynEachStep = class extends DynStep {
254
- constructor(producerCompId, producerSteps, key) {
255
- super(producerCompId, producerSteps);
256
- this.key = key;
257
- }
258
- teleportSteps() {
259
- const { producerSteps, key } = this;
260
- if (producerSteps.length === 0) return producerSteps;
261
- const last = producerSteps[producerSteps.length - 1];
262
- if (!(last instanceof FieldStep)) {
263
- console.warn("DynEachStep: seq-access dynamic cannot be iterated", this);
264
- return producerSteps;
265
- }
266
- return producerSteps.slice(0, -1).concat(new SeqStep(last.field, key));
239
+ function stepFromJson(j) {
240
+ if (j == null || typeof j.f !== "string") return null;
241
+ if (j.k !== void 0) return new SeqStep(j.f, j.k);
242
+ if (j.a !== void 0) return new SeqAccessStep(j.f, j.a);
243
+ return new FieldStep(j.f);
244
+ }
245
+ function pathToJson(path2) {
246
+ const out = [];
247
+ for (const step of path2.steps) {
248
+ const j = stepToJson(step);
249
+ if (j === null) return null;
250
+ out.push(j);
267
251
  }
268
- };
252
+ return out;
253
+ }
254
+ function pathFromJson(j) {
255
+ if (!Array.isArray(j)) return null;
256
+ const steps = [];
257
+ for (const item of j) {
258
+ const step = stepFromJson(item);
259
+ if (step === null) return null;
260
+ steps.push(step);
261
+ }
262
+ return new Path(steps);
263
+ }
269
264
  var Path = class _Path {
270
265
  constructor(steps = []) {
271
266
  this.steps = steps;
@@ -276,40 +271,13 @@ var Path = class _Path {
276
271
  popStep() {
277
272
  return new _Path(this.steps.slice(0, -1));
278
273
  }
279
- // The dispatch path: frame-only steps removed, one step per crossed component
280
- // (DynStep included). `popStep` over it bubbles through every component.
274
+ // Frame-only steps removed, one step per crossed component: `popStep` over the
275
+ // result bubbles through every component.
281
276
  compact() {
282
277
  const out = [];
283
278
  for (const step of this.steps) {
284
279
  const s = step.toAbstractPathStep();
285
- if (s !== null) {
286
- if (s !== step) s._originCid = step._originCid;
287
- out.push(s);
288
- }
289
- }
290
- return new _Path(out);
291
- }
292
- // The abstract path used to apply a transaction: every DynStep is teleported —
293
- // the steps interior to its producer..consumer span are dropped and the
294
- // producer's own path spliced in — so a mutation lands on the data's real
295
- // location. A path with no DynStep is returned unchanged.
296
- toTransactionPath() {
297
- let hasDyn = false;
298
- for (const step of this.steps)
299
- if (step instanceof DynStep) {
300
- hasDyn = true;
301
- break;
302
- }
303
- if (!hasDyn) return this;
304
- const out = [];
305
- for (const step of this.steps) {
306
- if (step instanceof DynStep) {
307
- while (out.length > 0 && step.interiorCids.has(out[out.length - 1]._originCid)) out.pop();
308
- for (const ts of step.teleportSteps()) {
309
- ts._originCid = step.producerCompId;
310
- out.push(ts);
311
- }
312
- } else out.push(step);
280
+ if (s !== null) out.push(s);
313
281
  }
314
282
  return new _Path(out);
315
283
  }
@@ -317,7 +285,6 @@ var Path = class _Path {
317
285
  // key as it is *now* so a later lookup/setValue lands on the same item even if the
318
286
  // keyField changed meanwhile (e.g. the selected tab moved while an intent was in
319
287
  // flight). Returns a new Path with those steps replaced; `this` if nothing pinned.
320
- // Must be called on a transaction path (no DynSteps — call toTransactionPath first).
321
288
  pinKeys(root) {
322
289
  let curVal = root;
323
290
  let out = null;
@@ -340,8 +307,8 @@ var Path = class _Path {
340
307
  }
341
308
  // The values entered along the path, root→leaf (root included): index 0 is `root`,
342
309
  // the last entry is the leaf this path resolves to. Stops early at the first
343
- // unresolvable step. Call on a transaction path (no DynSteps). Used to walk the
344
- // component instances on a dispatch path (filter via Components.getCompFor).
310
+ // unresolvable step. Used to walk the component instances on a dispatch path
311
+ // (filter via Components.getCompFor).
345
312
  resolveChain(root) {
346
313
  const out = [root];
347
314
  let curVal = root;
@@ -355,7 +322,7 @@ var Path = class _Path {
355
322
  // A flat `[{ field, key? }]` list of the addressing steps, skipping frame-only
356
323
  // steps (binds). Generic path introspection so tooling (e.g. the storybook
357
324
  // activity log) can identify which subtree a transaction touched without
358
- // depending on Step internals. Call on a transaction path (no DynSteps).
325
+ // depending on Step internals.
359
326
  toKeys() {
360
327
  const out = [];
361
328
  for (const step of this.steps) {
@@ -376,62 +343,147 @@ var Path = class _Path {
376
343
  });
377
344
  }
378
345
  buildStack(stack) {
379
- let prev = stack.it;
380
- for (const step of this.steps) {
381
- const next = step.lookup(prev, NONE);
382
- if (next === NONE) {
383
- console.warn("bad PathItem", { root: stack.it, step, path: this });
384
- return null;
346
+ return walkItems(stack, this.steps, stack.renderPath ?? new DispatchPath(), this)?.[0] ?? null;
347
+ }
348
+ };
349
+ function walkItems(stack, items, renderPath, path2) {
350
+ let prev = stack.it;
351
+ for (const step of items) {
352
+ const next = step.lookup(prev, NONE);
353
+ if (next === NONE) {
354
+ console.warn("bad PathItem", { root: stack.it, step, path: path2 });
355
+ return null;
356
+ }
357
+ renderPath = renderPath.pushItem(step);
358
+ stack = step.enterFrame(stack, next, renderPath);
359
+ prev = next;
360
+ }
361
+ return [stack, renderPath];
362
+ }
363
+ var EMPTY_PATH = new Path([]);
364
+ var DispatchPath = class _DispatchPath {
365
+ constructor(frames = [{ base: EMPTY_PATH, items: [] }]) {
366
+ this.frames = frames;
367
+ }
368
+ // Plain addressing steps in one frame based at the root: what a caller means
369
+ // by "this position" when it has no continuation of its own.
370
+ static ofSteps(steps) {
371
+ return new _DispatchPath([{ base: EMPTY_PATH, items: steps.slice() }]);
372
+ }
373
+ get top() {
374
+ return this.frames[this.frames.length - 1];
375
+ }
376
+ _withTopItems(items) {
377
+ const frames = this.frames.slice();
378
+ frames[frames.length - 1] = { base: this.top.base, items };
379
+ return new _DispatchPath(frames);
380
+ }
381
+ concat(steps) {
382
+ if (this.frames.length === 0) return _DispatchPath.ofSteps(steps);
383
+ return this._withTopItems(this.top.items.concat(steps));
384
+ }
385
+ pushItem(step) {
386
+ return this.concat([step]);
387
+ }
388
+ pushFrame(base) {
389
+ return new _DispatchPath(this.frames.concat({ base, items: [] }));
390
+ }
391
+ // Whether bubbling has anywhere left to go: another step in this frame, or a
392
+ // visual caller underneath it.
393
+ canPop() {
394
+ const n = this.frames.length;
395
+ return n > 1 || n === 1 && this.frames[0].items.length > 0;
396
+ }
397
+ isRoot() {
398
+ return this.toTransactionPath().steps.length === 0;
399
+ }
400
+ // One component closer to the root. At the top of a frame that is popping back
401
+ // to the visual caller, not to the producer's own parent — the caller is where
402
+ // the `*name` was written, and where an unhandled message should keep going.
403
+ popStep() {
404
+ const n = this.frames.length;
405
+ if (n === 0) return this;
406
+ const top = this.frames[n - 1];
407
+ if (top.items.length > 0) return this._withTopItems(top.items.slice(0, -1));
408
+ if (n > 1) return new _DispatchPath(this.frames.slice(0, -1));
409
+ return this;
410
+ }
411
+ // Drop frame-only steps inside every frame independently; a frame's base is
412
+ // already addressing-only.
413
+ compact() {
414
+ return new _DispatchPath(
415
+ this.frames.map(({ base, items }) => ({ base, items: new Path(items).compact().steps }))
416
+ );
417
+ }
418
+ // A stable string for the ADDRESS this path denotes, for the render cache. The
419
+ // same immutable value can sit at two places in the tree, and a subtree rendered
420
+ // at one of them bakes that address in — the provides it publishes are located
421
+ // there. Frame-only steps address nothing, so they are compacted out: two sites
422
+ // that differ only in binds do render the same subtree.
423
+ get addressKey() {
424
+ this._addressKey ??= renderPathText(this.toTransactionPath().compact());
425
+ return this._addressKey;
426
+ }
427
+ // The active transaction address: the top frame's absolute base followed by
428
+ // its ordinary descendant steps. This is where a mutation lands.
429
+ toTransactionPath() {
430
+ const top = this.top;
431
+ return top === void 0 ? EMPTY_PATH : top.base.concat(top.items);
432
+ }
433
+ // Rebuild the render stack this path was dispatched from. A frame with a base
434
+ // re-enters at that absolute value first — replaying the resume a `*name`
435
+ // render performed — and then walks its ordinary items.
436
+ buildStack(stack) {
437
+ let renderPath = new _DispatchPath();
438
+ for (let i = 0; i < this.frames.length; i++) {
439
+ const { base, items } = this.frames[i];
440
+ if (i > 0 || base.steps.length > 0) {
441
+ const baseValue = base.lookup(stack.root, NONE);
442
+ if (baseValue === NONE) {
443
+ console.warn("bad frame base", { base, path: this });
444
+ return null;
445
+ }
446
+ renderPath = renderPath.pushFrame(base);
447
+ stack = stack.enter(baseValue, {}, true, renderPath);
385
448
  }
386
- stack = step.enterFrame(stack, next);
387
- prev = next;
449
+ const walked = walkItems(stack, items, renderPath, this);
450
+ if (walked === null) return null;
451
+ [stack, renderPath] = walked;
388
452
  }
389
453
  return stack;
390
454
  }
391
455
  static fromNodeAndEventName(node, eventName, rootNode, maxDepth, comps, stopOnNoEvent = true) {
392
- const pathSteps = [];
393
- const pendingDyns = [];
456
+ const parts = [];
394
457
  const bubbles = BUBBLING_EVENTS.has(eventName);
395
458
  let depth = 0;
396
459
  let eventIds = [];
397
460
  let handlers = null;
398
- let nodeIds = [];
461
+ let nodeRefs = [];
399
462
  let isLeafComponent = true;
400
463
  const crossComponent = (cidNum, vid) => {
401
464
  const comp = comps.getComponentForId(cidNum);
402
- let pushStep = true;
465
+ let pushPart = true;
403
466
  if (handlers === null && (isLeafComponent || bubbles)) {
404
467
  handlers = findHandlers(comp, eventIds, vid, eventName);
405
468
  if (handlers === null) {
406
469
  if (isLeafComponent && stopOnNoEvent && !bubbles) return false;
407
470
  } else if (!isLeafComponent) {
408
- pathSteps.length = 0;
409
- pendingDyns.length = 0;
410
- pushStep = false;
471
+ parts.length = 0;
472
+ pushPart = false;
411
473
  }
412
474
  }
413
475
  isLeafComponent = false;
414
- for (const dyn of pendingDyns) dyn.interiorCids.add(cidNum);
415
- if (pushStep) {
416
- const step = resolvePathStep(comp, nodeIds, vid);
417
- if (step) {
418
- step._originCid = cidNum;
419
- pathSteps.push(step);
420
- if (step instanceof DynStep) {
421
- step.interiorCids.add(cidNum);
422
- pendingDyns.push(step);
423
- }
424
- }
476
+ if (pushPart) {
477
+ const part = resolvePathPart(comp, nodeRefs, vid);
478
+ if (part) parts.push(part);
425
479
  }
426
- for (let i = pendingDyns.length - 1; i >= 0; i--)
427
- if (pendingDyns[i].producerCompId === cidNum) pendingDyns.splice(i, 1);
428
480
  eventIds = [];
429
- nodeIds = [];
481
+ nodeRefs = [];
430
482
  return true;
431
483
  };
432
484
  while (node && node !== rootNode && depth < maxDepth) {
433
485
  if (node?.dataset) {
434
- const { eid, cid, vid } = node.dataset;
486
+ const { eid, cid, vid, rp } = node.dataset;
435
487
  if (eid !== void 0) eventIds.push(eid);
436
488
  const metas = metaChain(node.previousSibling);
437
489
  let sawComp = false;
@@ -439,21 +491,47 @@ var Path = class _Path {
439
491
  if (m.$ === "Comp") {
440
492
  sawComp = true;
441
493
  if (!crossComponent(m.cid, m.vid)) return NO_EVENT_INFO;
442
- nodeIds.push({ nid: m.nid });
494
+ nodeRefs.push({ nid: m.nid, base: pathFromJson(m.base) });
443
495
  } else {
444
- nodeIds.push({ nid: m.nid, si: m.si, sk: m.sk });
496
+ nodeRefs.push({ nid: m.nid, si: m.si, sk: m.sk });
497
+ }
498
+ }
499
+ if (!sawComp && cid !== void 0) {
500
+ if (!crossComponent(+cid, vid)) return NO_EVENT_INFO;
501
+ if (rp !== void 0) {
502
+ const base = parseRenderPath(rp);
503
+ if (base !== null) nodeRefs.push({ base });
445
504
  }
446
505
  }
447
- if (!sawComp && cid !== void 0 && !crossComponent(+cid, vid)) return NO_EVENT_INFO;
448
506
  }
449
507
  depth += 1;
450
508
  node = node.parentNode;
451
509
  }
452
- if (pendingDyns.length > 0)
453
- console.warn("event reconstruction: dynamic-var producer not found", pendingDyns);
454
- return [new _Path(pathSteps.reverse()), handlers];
510
+ parts.reverse();
511
+ let path2 = new _DispatchPath();
512
+ for (const part of parts)
513
+ path2 = part.base !== void 0 ? path2.pushFrame(part.base) : path2.pushItem(part.step);
514
+ return [path2, handlers];
515
+ }
516
+ };
517
+ function keyedPath(path2, key) {
518
+ const steps = path2.steps;
519
+ const last = steps[steps.length - 1];
520
+ if (last instanceof SeqStep || last instanceof FieldStep)
521
+ return new Path(steps.slice(0, -1).concat(new SeqStep(last.field, key)));
522
+ return null;
523
+ }
524
+ function renderPathText(path2) {
525
+ return JSON.stringify(pathToJson(path2));
526
+ }
527
+ function parseRenderPath(text) {
528
+ try {
529
+ return pathFromJson(JSON.parse(text));
530
+ } catch (err) {
531
+ console.warn("bad render path", err, text);
532
+ return null;
455
533
  }
456
- };
534
+ }
457
535
  function metaChain(n) {
458
536
  const out = [];
459
537
  while (n?.nodeType === 8 && n.textContent[0] === "§") {
@@ -506,20 +584,27 @@ var StepCtx = class _StepCtx {
506
584
  return pi;
507
585
  }
508
586
  };
509
- function resolvePathStep(comp, nodeIds, vid) {
510
- for (let i = 0; i < nodeIds.length; i++) {
511
- const ctx = new StepCtx(comp, nodeIds, i, vid);
512
- const step = ctx.resolveNode().toPathStep(ctx);
513
- if (step !== null) return step;
587
+ function resolvePathPart(comp, nodeRefs, vid) {
588
+ for (let i = 0; i < nodeRefs.length; i++) {
589
+ const ref = nodeRefs[i];
590
+ if (ref.base != null) return { base: ref.base };
591
+ if (ref.nid === void 0 || ref.nid === null) continue;
592
+ const ctx = new StepCtx(comp, nodeRefs, i, vid);
593
+ const step = ctx.resolveNode()?.toPathStep(ctx) ?? null;
594
+ if (step !== null) return { step };
514
595
  }
515
596
  return null;
516
597
  }
517
598
  var NO_EVENT_INFO = [null, null];
518
599
  var BUBBLING_EVENTS = /* @__PURE__ */ new Set(["drop"]);
600
+ var path = () => new PathBuilder();
519
601
  var PathBuilder = class {
520
602
  constructor() {
521
603
  this.pathChanges = [];
522
604
  }
605
+ toPath() {
606
+ return new Path(this.pathChanges);
607
+ }
523
608
  add(pathChange) {
524
609
  this.pathChanges.push(pathChange);
525
610
  return this;
@@ -1676,13 +1761,16 @@ function h(tagName, properties, children, namespace) {
1676
1761
  }
1677
1762
 
1678
1763
  // src/anode.js
1679
- function resolveDynProducer(comp, name) {
1680
- const own = comp?.provide?.[name];
1681
- const producerComp = own != null ? comp : comp?.scope?.lookupProvider(name) ?? null;
1682
- const producerProvide = own ?? producerComp?.provide?.[name];
1683
- if (producerComp == null || producerProvide == null) return null;
1684
- const pi = producerProvide.val?.toPathItem?.() ?? null;
1685
- return { producerCompId: producerComp.id, producerSteps: pi ? [pi] : [] };
1764
+ function renderTarget(val, stack) {
1765
+ if (val instanceof DynVal) {
1766
+ const loc = stack.lookupDynamicLocated(val.name);
1767
+ if (loc?.path == null) return [loc?.value ?? null, stack.renderPath, null];
1768
+ return [loc.value, stack.renderPath.pushFrame(loc.path), loc.path];
1769
+ }
1770
+ const step = val.toPathItem?.() ?? null;
1771
+ if (step === null) return [val.eval(stack), stack.renderPath, null];
1772
+ const path2 = stack.renderPath.pushItem(step);
1773
+ return [val.eval(stack), path2, stack.pendingFrame ? path2.toTransactionPath() : null];
1686
1774
  }
1687
1775
  var BaseNode = class {
1688
1776
  render(_stack, _rx) {
@@ -2004,33 +2092,31 @@ var RenderViewId = class extends ANode {
2004
2092
  setDataAttr(_key, _val) {
2005
2093
  }
2006
2094
  };
2007
- function dynRenderStep(comp, name, key) {
2008
- const p = resolveDynProducer(comp, name);
2009
- if (!p) return null;
2010
- return key === void 0 ? new DynStep(p.producerCompId, p.producerSteps) : new DynEachStep(p.producerCompId, p.producerSteps, key);
2011
- }
2012
2095
  var RenderNode = class extends RenderViewId {
2013
2096
  render(stack, rx) {
2014
- const newStack = stack.enter(this.val.eval(stack), {}, true);
2015
- return rx.renderIt(newStack, this, "", this.evalViewName(stack));
2097
+ const [value, renderPath, base] = renderTarget(this.val, stack);
2098
+ const newStack = stack.enter(value, {}, true, renderPath, false);
2099
+ return rx.renderIt(newStack, this, "", this.evalViewName(stack), base);
2016
2100
  }
2101
+ // A `*name` target contributes no step: the site recorded the absolute base it
2102
+ // resumed at, and event reconstruction turns that into a continuation frame.
2017
2103
  toPathStep(ctx) {
2018
- if (this.val instanceof DynVal) return dynRenderStep(ctx.comp, this.val.name);
2104
+ if (this.val instanceof DynVal) return null;
2019
2105
  return super.toPathStep(ctx);
2020
2106
  }
2021
2107
  };
2022
2108
  var RenderItNode = class extends RenderViewId {
2023
2109
  render(stack, rx) {
2024
- const newStack = stack.enter(stack.it, {}, true);
2025
- return rx.renderIt(newStack, this, "", this.evalViewName(stack));
2110
+ const base = stack.pendingFrame ? stack.renderPath.toTransactionPath() : null;
2111
+ const newStack = stack.enter(stack.it, {}, true, stack.renderPath, false);
2112
+ return rx.renderIt(newStack, this, "", this.evalViewName(stack), base);
2026
2113
  }
2027
2114
  toPathStep(ctx) {
2028
2115
  const next = ctx.next();
2029
2116
  if (next === null) return null;
2030
2117
  const nextNode = next.resolveNode();
2031
2118
  if (nextNode instanceof EachNode && next.hasKey) {
2032
- if (nextNode.val instanceof DynVal)
2033
- return dynRenderStep(ctx.comp, nextNode.val.name, next.key);
2119
+ if (nextNode.val instanceof DynVal) return null;
2034
2120
  return new EachRenderItStep(nextNode.val.name, next.key);
2035
2121
  }
2036
2122
  return null;
@@ -2132,11 +2218,19 @@ var EachNode = class extends WrapperNode {
2132
2218
  this.iterInfo = new IterInfo(val, null, null, null);
2133
2219
  }
2134
2220
  render(stack, rx) {
2135
- return rx.renderEachWhen(stack, this.iterInfo, this.node, this.nodeId);
2221
+ return rx.renderEachWhen(stack, this);
2136
2222
  }
2137
2223
  toPathStep(ctx) {
2138
2224
  return ctx.hasKey ? new EachBindStep(this.iterInfo, ctx.key) : null;
2139
2225
  }
2226
+ // Where one item lives, for the render path. `@each` re-binds `it` to the item
2227
+ // whether or not the body is a component, so the position moves either way and
2228
+ // the step has to address it — `.rows` iterated at `key` IS `rows[key]`.
2229
+ // Null for a dynamic sequence: `*rows` carries its own absolute path and enters
2230
+ // a continuation frame instead (see Renderer.renderEachWhen).
2231
+ itemStep(key) {
2232
+ return this.val instanceof FieldVal ? new SeqStep(this.val.name, key) : null;
2233
+ }
2140
2234
  static register = true;
2141
2235
  };
2142
2236
  var IterInfo = class {
@@ -2397,6 +2491,215 @@ function compileModifiers(eventName, names) {
2397
2491
  };
2398
2492
  }
2399
2493
 
2494
+ // src/stack.js
2495
+ var STOP = /* @__PURE__ */ Symbol("STOP");
2496
+ var NEXT = /* @__PURE__ */ Symbol("NEXT");
2497
+ var DEFAULT_ROUTE = ["dyn", "lex"];
2498
+ function routeLookup(route, lex, dyn) {
2499
+ for (let i = 0; i < route.length; i++) {
2500
+ const leg = route[i];
2501
+ if (leg === "dyn") {
2502
+ const v = dyn();
2503
+ if (v != null) return v;
2504
+ } else if (leg === "lex") {
2505
+ const v = lex();
2506
+ if (v != null) return v;
2507
+ } else {
2508
+ console.warn("unknown lookup route leg", leg, '- expected "dyn" or "lex"');
2509
+ }
2510
+ }
2511
+ return null;
2512
+ }
2513
+ var isTypeName = (s) => {
2514
+ const c = s.charCodeAt(0);
2515
+ return c >= 65 && c <= 90;
2516
+ };
2517
+ function lookup(chain, name, dv = null) {
2518
+ let n = chain;
2519
+ while (n !== null) {
2520
+ const r = n[0].lookup(name);
2521
+ if (r === STOP) return dv;
2522
+ if (r !== NEXT) return r;
2523
+ n = n[1];
2524
+ }
2525
+ return dv;
2526
+ }
2527
+ var BindFrame = class {
2528
+ constructor(it, binds, isFrame) {
2529
+ this.it = it;
2530
+ this.binds = binds;
2531
+ this.isFrame = isFrame;
2532
+ }
2533
+ lookup(name) {
2534
+ const v = this.binds[name];
2535
+ return v === void 0 ? this.isFrame ? STOP : NEXT : v;
2536
+ }
2537
+ };
2538
+ var DynFrame = class {
2539
+ constructor(binds, types) {
2540
+ this.binds = binds;
2541
+ this.types = types;
2542
+ }
2543
+ lookup(name) {
2544
+ const v = (isTypeName(name) ? this.types : this.binds)[name];
2545
+ return v === void 0 ? NEXT : v;
2546
+ }
2547
+ };
2548
+ function computeViewsId(views) {
2549
+ let s = "";
2550
+ let n = views;
2551
+ while (n !== null) {
2552
+ s += n[0];
2553
+ n = n[1];
2554
+ }
2555
+ return s === "main" ? "" : s;
2556
+ }
2557
+ var Stack = class _Stack {
2558
+ constructor(fields) {
2559
+ Object.assign(this, fields);
2560
+ }
2561
+ _with(patch) {
2562
+ return new _Stack({ ...this, ...patch });
2563
+ }
2564
+ // Evaluate every provide the entered component publishes and push them as one
2565
+ // dynBinds frame, keyed by NAME. A value is published together with the absolute
2566
+ // path it lives at: the same declaration is read as `*name` AND resumed at by
2567
+ // `<x render="*name">`, so a consumer needs both halves. Types go in the same
2568
+ // frame's other map. No-op with no provides.
2569
+ _pushProvides() {
2570
+ const comp = this.comps.getCompFor(this.it);
2571
+ if (comp == null) return this;
2572
+ const { provide, provideType } = comp;
2573
+ const binds = {};
2574
+ const types = {};
2575
+ let has = false;
2576
+ const base = this._publishBase();
2577
+ for (const k in provide) {
2578
+ const step = provide[k].val.toPathItem?.() ?? null;
2579
+ if (step === null) continue;
2580
+ const value = provide[k].val.eval(this);
2581
+ binds[k] = { value, path: base === null ? null : base.concat([step]) };
2582
+ has = true;
2583
+ }
2584
+ for (const k in provideType) {
2585
+ types[k] = provideType[k];
2586
+ has = true;
2587
+ }
2588
+ if (!has) return this;
2589
+ return this._with({ dynBinds: [new DynFrame(binds, types), this.dynBinds] });
2590
+ }
2591
+ // The absolute address of the component being entered, or null when this
2592
+ // position cannot be written down as one. Frame-only steps carry bindings and
2593
+ // address nothing, so they are compacted away first; what is left is checked
2594
+ // against the value actually being rendered, because a scope CAN move the
2595
+ // render position without contributing an addressing step (a plain `@each`
2596
+ // body re-binds `it` to the item while its rebuild step is an identity).
2597
+ //
2598
+ // A null base still publishes the VALUE — `*name` reads it fine — but there is
2599
+ // nowhere to resume, so `<x render="*name">` renders nothing rather than
2600
+ // editing whatever happens to live at the address we guessed.
2601
+ _publishBase() {
2602
+ const base = this.renderPath.toTransactionPath().compact();
2603
+ return base.lookup(this.root, NOT_FOUND) === this.it ? base : null;
2604
+ }
2605
+ static root(comps, it, ctx = null) {
2606
+ return new _Stack({
2607
+ comps,
2608
+ root: it,
2609
+ it,
2610
+ binds: [new BindFrame(it, {}, true), null],
2611
+ dynBinds: [new DynFrame({}, {}), null],
2612
+ views: ["main", null],
2613
+ viewsId: "",
2614
+ renderPath: new DispatchPath(),
2615
+ pendingFrame: false,
2616
+ ctx
2617
+ })._pushProvides();
2618
+ }
2619
+ // `renderPath` defaults to this stack's own: an ordinary scope does not move.
2620
+ // `pendingFrame` clears on a component frame (which emits the base) and is
2621
+ // inherited by transparent scopes, which have to carry it to the next boundary.
2622
+ enter(it, bindings = {}, isFrame = true, renderPath = this.renderPath, pendingFrame = null) {
2623
+ const stack = this._with({
2624
+ it,
2625
+ binds: [new BindFrame(it, bindings, isFrame), this.binds],
2626
+ renderPath,
2627
+ pendingFrame: pendingFrame ?? (isFrame ? false : this.pendingFrame)
2628
+ });
2629
+ return isFrame ? stack._pushProvides() : stack;
2630
+ }
2631
+ pushViewName(name) {
2632
+ const views = [name, this.views];
2633
+ return this._with({ views, viewsId: computeViewsId(views) });
2634
+ }
2635
+ // Published types are stable per scope and would only churn the render cache, so
2636
+ // the cache key covers values alone.
2637
+ _pushDynBindValuesToArray(arr, comp) {
2638
+ for (const k in comp.provide) arr.push(this.lookupDynamic(k));
2639
+ for (const k in comp.lookup) arr.push(this.lookupDynamic(k));
2640
+ }
2641
+ // `*name`: the nearest binding above (including this component's own provides,
2642
+ // pushed on entering it), else a path registered in the component's lexical
2643
+ // scope, else this component's declared default, else null.
2644
+ //
2645
+ // One chain walk and no producer resolution: a lookup names what it WANTS, so the
2646
+ // frame it wants is keyed by that name. The default belongs to the CONSUMER's
2647
+ // declaration and is evaluated against the consumer's stack, which is why it is
2648
+ // consulted only after the whole chain has missed.
2649
+ lookupDynamicLocated(name) {
2650
+ if (isTypeName(name)) return null;
2651
+ const v = lookup(this.dynBinds, name);
2652
+ if (v != null) return v;
2653
+ const comp = this.comps.getCompFor(this.it);
2654
+ if (comp == null) return null;
2655
+ const path2 = comp.scope?.lookupPath?.(name) ?? null;
2656
+ if (path2 !== null) {
2657
+ const value2 = path2.lookup(this.root, NOT_FOUND);
2658
+ if (value2 !== NOT_FOUND) return { value: value2, path: path2 };
2659
+ }
2660
+ const dval = comp.lookup[name]?.val ?? null;
2661
+ if (dval === null) return null;
2662
+ const step = dval.toPathItem?.() ?? null;
2663
+ const value = dval.eval(this);
2664
+ return step === null ? { value, path: null } : { value, path: this.renderPath.toTransactionPath().concat([step]) };
2665
+ }
2666
+ lookupDynamic(name) {
2667
+ if (isTypeName(name)) return lookup(this.dynBinds, name);
2668
+ return this.lookupDynamicLocated(name)?.value ?? null;
2669
+ }
2670
+ lookupBind(name) {
2671
+ return lookup(this.binds, name);
2672
+ }
2673
+ lookupFieldRaw(name) {
2674
+ return this.it[name] ?? null;
2675
+ }
2676
+ lookupMethod(name) {
2677
+ const fn = this.it[name];
2678
+ return fn instanceof Function ? fn.call(this.it) : null;
2679
+ }
2680
+ // The dispatched DOM event / drag info, read only by EventMemberVal's
2681
+ // `e.<member>` handler args. Null outside a live event transaction.
2682
+ lookupEvent() {
2683
+ return this.ctx?.event ?? null;
2684
+ }
2685
+ lookupDragInfo() {
2686
+ return this.ctx?.dragInfo ?? null;
2687
+ }
2688
+ getHandlerFor(name, key) {
2689
+ return this.comps.getHandlerFor(this.it, name, key);
2690
+ }
2691
+ lookupBestView(views, defaultViewName) {
2692
+ let n = this.views;
2693
+ while (n !== null) {
2694
+ const view = views[n[0]];
2695
+ if (view !== void 0) return view;
2696
+ n = n[1];
2697
+ }
2698
+ return views[defaultViewName];
2699
+ }
2700
+ };
2701
+ var NOT_FOUND = /* @__PURE__ */ Symbol("NOT_FOUND");
2702
+
2400
2703
  // src/components.js
2401
2704
  var COMPONENT = /* @__PURE__ */ Symbol.for("tutuca.component");
2402
2705
  var Components = class {
@@ -2435,12 +2738,14 @@ var ComponentStack = class _ComponentStack {
2435
2738
  this.byName = {};
2436
2739
  this.intentsByName = {};
2437
2740
  this.macros = {};
2741
+ this.paths = {};
2438
2742
  }
2439
2743
  enter() {
2440
2744
  return new _ComponentStack(this.comps, this);
2441
2745
  }
2442
2746
  registerComponents(comps, opts) {
2443
- const { aliases = {} } = opts ?? {};
2747
+ const { aliases = {}, paths } = opts ?? {};
2748
+ if (paths) this.registerPaths(paths);
2444
2749
  for (let i = 0; i < comps.length; i++) {
2445
2750
  const Comp = comps[i];
2446
2751
  Comp[COMPONENT].scope = this.enter();
@@ -2454,6 +2759,27 @@ var ComponentStack = class _ComponentStack {
2454
2759
  else console.warn("alias", alias, "to inexistent component", aliases[alias]);
2455
2760
  }
2456
2761
  }
2762
+ // Register lowercase names as absolute paths from the app state root. A
2763
+ // descendant that declares one in its `lookup` reads and renders `*name` without
2764
+ // anything above it publishing one — which is what makes a session, a theme or a
2765
+ // host-owned value available in its natural registration scope, instead of forcing
2766
+ // an application root whose only job is to `provide` it. Register on a nested
2767
+ // scope to narrow a name; nearest registration wins.
2768
+ //
2769
+ // Uppercase names are ignored: a component TYPE is what `lookupComponent` already
2770
+ // answers, and a type has no path.
2771
+ registerPaths(paths) {
2772
+ for (const name in paths) {
2773
+ if (isTypeName(name)) {
2774
+ console.warn("registerPaths: a type name has no path", name);
2775
+ continue;
2776
+ }
2777
+ this.paths[name] = paths[name].toPath?.() ?? paths[name];
2778
+ }
2779
+ }
2780
+ lookupPath(name) {
2781
+ return this.paths[name] ?? this.parent?.lookupPath(name) ?? null;
2782
+ }
2457
2783
  registerMacros(macros) {
2458
2784
  for (const key in macros) {
2459
2785
  const lower = key.toLowerCase();
@@ -2485,17 +2811,16 @@ var ComponentStack = class _ComponentStack {
2485
2811
  lookupComponent(name) {
2486
2812
  return this.byName[name] ?? this.parent?.lookupComponent(name) ?? null;
2487
2813
  }
2488
- // The component in this scope chain that provides `name`, innermost first. A lookup
2489
- // names a value without naming its producer, so the render-target teleport
2490
- // (resolveDynProducer) recovers the producer here instead. Sound because
2491
- // PROVIDE_NAME_COLLISION keeps a provide name to one producer per chain; with two,
2492
- // this returns the innermost and the linter has already reported the ambiguity.
2493
- lookupProvider(name) {
2814
+ // Whether anything in this scope chain provides `name`. Existence only: a lookup
2815
+ // names what it WANTS and takes whoever is nearest above it at render time, so
2816
+ // there is no producer to identify — several components may publish one name and
2817
+ // the live render ancestry decides. Used by the linter to tell a lookup that can
2818
+ // be satisfied from one that never will be.
2819
+ hasProvider(name) {
2494
2820
  for (const compName in this.byName) {
2495
- const Comp = this.byName[compName];
2496
- if (Comp.provide?.[name] !== void 0) return Comp;
2821
+ if (this.byName[compName].provide?.[name] !== void 0) return true;
2497
2822
  }
2498
- return this.parent?.lookupProvider(name) ?? null;
2823
+ return this.parent?.hasProvider(name) ?? false;
2499
2824
  }
2500
2825
  lookupMacro(name) {
2501
2826
  return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
@@ -2512,10 +2837,6 @@ var LookupInfo = class {
2512
2837
  }
2513
2838
  };
2514
2839
  var isString = (v) => typeof v === "string";
2515
- var isTypeName = (s) => {
2516
- const c = s.charCodeAt(0);
2517
- return c >= 65 && c <= 90;
2518
- };
2519
2840
  var _rawSpecKeys = "name view style commonStyle globalStyle receive intent alter views provide lookup fields methods statics";
2520
2841
  var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
2521
2842
  var _compId = 0;
@@ -2588,160 +2909,6 @@ var Component = class {
2588
2909
  }
2589
2910
  };
2590
2911
 
2591
- // src/stack.js
2592
- var STOP = /* @__PURE__ */ Symbol("STOP");
2593
- var NEXT = /* @__PURE__ */ Symbol("NEXT");
2594
- var DEFAULT_ROUTE = ["dyn", "lex"];
2595
- function routeLookup(route, lex, dyn) {
2596
- for (let i = 0; i < route.length; i++) {
2597
- const leg = route[i];
2598
- if (leg === "dyn") {
2599
- const v = dyn();
2600
- if (v != null) return v;
2601
- } else if (leg === "lex") {
2602
- const v = lex();
2603
- if (v != null) return v;
2604
- } else {
2605
- console.warn("unknown lookup route leg", leg, '- expected "dyn" or "lex"');
2606
- }
2607
- }
2608
- return null;
2609
- }
2610
- function lookup(chain, name, dv = null) {
2611
- let n = chain;
2612
- while (n !== null) {
2613
- const r = n[0].lookup(name);
2614
- if (r === STOP) return dv;
2615
- if (r !== NEXT) return r;
2616
- n = n[1];
2617
- }
2618
- return dv;
2619
- }
2620
- var BindFrame = class {
2621
- constructor(it, binds, isFrame) {
2622
- this.it = it;
2623
- this.binds = binds;
2624
- this.isFrame = isFrame;
2625
- }
2626
- lookup(name) {
2627
- const v = this.binds[name];
2628
- return v === void 0 ? this.isFrame ? STOP : NEXT : v;
2629
- }
2630
- };
2631
- var ObjectFrame = class {
2632
- constructor(binds) {
2633
- this.binds = binds;
2634
- }
2635
- lookup(key) {
2636
- const v = this.binds[key];
2637
- return v === void 0 ? NEXT : v;
2638
- }
2639
- };
2640
- function computeViewsId(views) {
2641
- let s = "";
2642
- let n = views;
2643
- while (n !== null) {
2644
- s += n[0];
2645
- n = n[1];
2646
- }
2647
- return s === "main" ? "" : s;
2648
- }
2649
- var Stack = class _Stack {
2650
- constructor(comps, it, binds, dynBinds, views, viewsId, ctx = null) {
2651
- this.comps = comps;
2652
- this.it = it;
2653
- this.binds = binds;
2654
- this.dynBinds = dynBinds;
2655
- this.views = views;
2656
- this.viewsId = viewsId;
2657
- this.ctx = ctx;
2658
- }
2659
- // Evaluate every provide the entered component publishes and push them as one
2660
- // dynBinds frame, keyed by NAME. Published types go in the same frame: a type name
2661
- // starts A-Z and a value name does not, so the two namespaces cannot collide and
2662
- // nearest-ancestor-wins falls out of frame order for both. No-op with no provides.
2663
- _pushProvides() {
2664
- const comp = this.comps.getCompFor(this.it);
2665
- if (comp == null) return this;
2666
- const { provide, provideType } = comp;
2667
- const dynObj = {};
2668
- let has = false;
2669
- for (const k in provide) {
2670
- dynObj[k] = provide[k].val.eval(this);
2671
- has = true;
2672
- }
2673
- for (const k in provideType) {
2674
- dynObj[k] = provideType[k];
2675
- has = true;
2676
- }
2677
- if (!has) return this;
2678
- const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
2679
- const { comps, it, binds, views, viewsId, ctx } = this;
2680
- return new _Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
2681
- }
2682
- static root(comps, it, ctx) {
2683
- const binds = [new BindFrame(it, {}, true), null];
2684
- const dynBinds = [new ObjectFrame({}), null];
2685
- const views = ["main", null];
2686
- return new _Stack(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
2687
- }
2688
- enter(it, bindings = {}, isFrame = true) {
2689
- const { comps, binds, dynBinds, views, viewsId, ctx } = this;
2690
- const newBinds = [new BindFrame(it, bindings, isFrame), binds];
2691
- const stack = new _Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx);
2692
- return isFrame ? stack._pushProvides() : stack;
2693
- }
2694
- pushViewName(name) {
2695
- const { comps, it, binds, dynBinds, views, ctx } = this;
2696
- const newViews = [name, views];
2697
- return new _Stack(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
2698
- }
2699
- // Published types are stable per scope and would only churn the render cache, so
2700
- // the cache key covers values alone.
2701
- _pushDynBindValuesToArray(arr, comp) {
2702
- for (const k in comp.provide) arr.push(this.lookupDynamic(k));
2703
- for (const k in comp.lookup) arr.push(this.lookupDynamic(k));
2704
- }
2705
- // `*name`: the nearest binding above (including this component's own provides,
2706
- // pushed on entering it), else this component's declared default, else null.
2707
- lookupDynamic(name) {
2708
- const v = lookup(this.dynBinds, name);
2709
- if (v != null) return v;
2710
- const comp = this.comps.getCompFor(this.it);
2711
- return comp?.lookup[name]?.val?.eval(this) ?? null;
2712
- }
2713
- lookupBind(name) {
2714
- return lookup(this.binds, name);
2715
- }
2716
- lookupFieldRaw(name) {
2717
- return this.it[name] ?? null;
2718
- }
2719
- lookupMethod(name) {
2720
- const fn = this.it[name];
2721
- return fn instanceof Function ? fn.call(this.it) : null;
2722
- }
2723
- // The dispatched DOM event / drag info, read only by EventMemberVal's
2724
- // `e.<member>` handler args. Null outside a live event transaction.
2725
- lookupEvent() {
2726
- return this.ctx?.event ?? null;
2727
- }
2728
- lookupDragInfo() {
2729
- return this.ctx?.dragInfo ?? null;
2730
- }
2731
- getHandlerFor(name, key) {
2732
- return this.comps.getHandlerFor(this.it, name, key);
2733
- }
2734
- lookupBestView(views, defaultViewName) {
2735
- let n = this.views;
2736
- while (n !== null) {
2737
- const view = views[n[0]];
2738
- if (view !== void 0) return view;
2739
- n = n[1];
2740
- }
2741
- return views[defaultViewName];
2742
- }
2743
- };
2744
-
2745
2912
  // src/oo.js
2746
2913
  var BAD_VALUE = /* @__PURE__ */ Symbol("BadValue");
2747
2914
  var nullCoercer = (v) => v;
@@ -3087,9 +3254,9 @@ var Transactor = class {
3087
3254
  // `.a[.selId]` render target reconstructed from a DOM event) against the root the
3088
3255
  // handler read, so pathKeys carries concrete keys instead of dropping the dynamic
3089
3256
  // step. No-op when nobody is observing.
3090
- _emitRecord(root, { kind, name, args, path, targetPath, handler, handlerName, matched, before, after, parent }) {
3257
+ _emitRecord(root, { kind, name, args, path: path2, targetPath, handler, handlerName, matched, before, after, parent }) {
3091
3258
  if (this._observers.length === 0) return;
3092
- const pinned = path.pinKeys(root);
3259
+ const pinned = path2.pinKeys(root);
3093
3260
  this._emit({
3094
3261
  kind,
3095
3262
  name,
@@ -3140,18 +3307,20 @@ var Transactor = class {
3140
3307
  // intent's answerPath is: a reply must reach the sender that asked even if a key
3141
3308
  // moved while the message was in flight. Null when nobody is waiting — a host
3142
3309
  // sendAtRoot or a view's own `@on.*` — and ctx.sendReply refuses on that.
3143
- pushSend(path, name, args = [], parent = null, origin = null) {
3144
- const t = new SendEvent(path, this, name, args, parent);
3145
- t.origin = origin === null ? null : origin.toTransactionPath().pinKeys(this.state.val);
3310
+ pushSend(path2, name, args = [], parent = null, origin = null, txnPath = null) {
3311
+ const t = new SendEvent(path2, this, name, args, parent);
3312
+ t.txnPath = txnPath;
3313
+ t.origin = origin;
3314
+ t.originPinned = origin === null ? null : origin.toTransactionPath().pinKeys(this.state.val);
3146
3315
  this.pushTransaction(t);
3147
3316
  return this._link(t, parent);
3148
3317
  }
3149
3318
  // Raise an intent: a job the sender did not address, walked along a route until
3150
3319
  // something answers. `opts.route` is a list of legs (see DEFAULT_ROUTE) and
3151
3320
  // `opts.livePath` opts out of pinning the answer's destination keys.
3152
- pushIntent(path, name, args = [], opts = {}, parent = null) {
3321
+ pushIntent(path2, name, args = [], opts = {}, parent = null) {
3153
3322
  const release = parent ? parent.completion.track() : null;
3154
- const walk = new IntentWalk(this, path, name, args, opts, parent, release);
3323
+ const walk = new IntentWalk(this, path2, name, args, opts, parent, release);
3155
3324
  walk.advance();
3156
3325
  return walk;
3157
3326
  }
@@ -3185,16 +3354,17 @@ var Transactor = class {
3185
3354
  transaction._completion?.releaseSelf();
3186
3355
  }
3187
3356
  }
3188
- transactInputNow(path, event, eventHandler, dragInfo) {
3189
- this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
3357
+ transactInputNow(path2, event, eventHandler, dragInfo) {
3358
+ this.transact(new InputEvent(path2, event, eventHandler, this, dragInfo));
3190
3359
  }
3191
3360
  };
3192
3361
  function nullHandler() {
3193
3362
  return this;
3194
3363
  }
3195
3364
  var Transaction = class {
3196
- constructor(path, transactor, parentTransaction = null) {
3197
- this.path = path;
3365
+ constructor(path2, transactor, parentTransaction = null) {
3366
+ this.path = path2;
3367
+ this.txnPath = null;
3198
3368
  this.transactor = transactor;
3199
3369
  this.parentTransaction = parentTransaction;
3200
3370
  this._completion = null;
@@ -3243,9 +3413,11 @@ var Transaction = class {
3243
3413
  getHandlerAndArgs(_root, _instance, _comps) {
3244
3414
  return null;
3245
3415
  }
3246
- // The path used to apply the mutation. Teleports dynamic-var renders so it lands on
3247
- // the data's real location (the dispatch `this.path` keeps intermediates).
3416
+ // The path used to apply the mutation: the ACTIVE frame of the dispatch path, so
3417
+ // an event inside a `<x render="*name">` subtree updates the value where it
3418
+ // really lives (the dispatch `this.path` keeps the visual callers, for bubbling).
3248
3419
  getTransactionPath() {
3420
+ if (this.txnPath !== null) return this.txnPath;
3249
3421
  return this.path.toTransactionPath().compact();
3250
3422
  }
3251
3423
  run(curRoot, comps) {
@@ -3263,15 +3435,16 @@ var Transaction = class {
3263
3435
  }
3264
3436
  };
3265
3437
  var InputEvent = class extends Transaction {
3266
- constructor(path, e, handler, transactor, dragInfo) {
3267
- super(path, transactor);
3438
+ constructor(path2, e, handler, transactor, dragInfo) {
3439
+ super(path2, transactor);
3268
3440
  this.e = e;
3269
3441
  this.handler = handler;
3270
3442
  this.dragInfo = dragInfo;
3271
3443
  this._dispatchPath = null;
3272
3444
  }
3273
- // Frame steps removed, DynStep + one step per crossed component kept: bubbling
3274
- // it visits every component (including intermediates of a dynamic-var render).
3445
+ // Frame-only steps removed, one step per crossed component kept — inside every
3446
+ // continuation frame independently, so bubbling it visits every component and
3447
+ // then returns to the caller that wrote the `*name`.
3275
3448
  get dispatchPath() {
3276
3449
  this._dispatchPath ??= this.path.compact();
3277
3450
  return this._dispatchPath;
@@ -3306,11 +3479,11 @@ var InputEvent = class extends Transaction {
3306
3479
  this.transactor.pushIntent(this.dispatchPath, name, args, rest, this);
3307
3480
  }
3308
3481
  getHandlerAndArgs(root, _instance, comps) {
3309
- const stack = this.path.toTransactionPath().buildStack(Stack.root(comps, root, this));
3482
+ const stack = this.path.buildStack(Stack.root(comps, root, this));
3310
3483
  const [handler, args] = this.handler.getHandlerAndArgs(stack, this);
3311
3484
  this._handlerArgs = [...args];
3312
- const path = this.dispatchPath;
3313
- args.push(new EventContext(path, this.transactor, this));
3485
+ const path2 = this.dispatchPath;
3486
+ args.push(new EventContext(path2, this.transactor, this));
3314
3487
  return [handler, args];
3315
3488
  }
3316
3489
  // The dispatched DOM event, read only through `e.<member>` handler args
@@ -3321,11 +3494,11 @@ var InputEvent = class extends Transaction {
3321
3494
  }
3322
3495
  };
3323
3496
  var NameArgsTransaction = class extends Transaction {
3324
- constructor(path, transactor, name, args, parentTransaction) {
3325
- super(path, transactor, parentTransaction);
3497
+ constructor(path2, transactor, name, args, parentTransaction) {
3498
+ super(path2, transactor, parentTransaction);
3326
3499
  this.name = name;
3327
3500
  this.args = args;
3328
- this.targetPath = path;
3501
+ this.targetPath = path2;
3329
3502
  }
3330
3503
  handlerProp = null;
3331
3504
  // NameArgs verbs map their handler bucket straight to the observed kind:
@@ -3382,8 +3555,8 @@ var SendEvent = class extends NameArgsTransaction {
3382
3555
  };
3383
3556
  var IntentEvent = class extends NameArgsTransaction {
3384
3557
  handlerProp = "intent";
3385
- constructor(path, transactor, walk) {
3386
- super(path, transactor, walk.name, walk.args, walk.parent);
3558
+ constructor(path2, transactor, walk) {
3559
+ super(path2, transactor, walk.name, walk.args, walk.parent);
3387
3560
  this.walk = walk;
3388
3561
  this.targetPath = walk.origin;
3389
3562
  }
@@ -3410,17 +3583,17 @@ var PASS = /* @__PURE__ */ Symbol.for("tutuca.intent.pass");
3410
3583
  var REFUSAL_RING_CAP = 200;
3411
3584
  var INTENT_DEPTH = 64;
3412
3585
  var IntentWalk = class {
3413
- constructor(transactor, path, name, args, opts, parent, release) {
3586
+ constructor(transactor, path2, name, args, opts, parent, release) {
3414
3587
  this.transactor = transactor;
3415
3588
  this.name = name;
3416
3589
  this.args = args;
3417
3590
  this.route = opts?.route ?? DEFAULT_ROUTE;
3418
3591
  this.parent = parent;
3419
3592
  this.release = release;
3420
- this.origin = path;
3421
- this.answerPath = opts?.livePath ? null : path.toTransactionPath().pinKeys(transactor.state.val);
3593
+ this.origin = path2;
3594
+ this.answerPath = opts?.livePath ? null : path2.toTransactionPath().pinKeys(transactor.state.val);
3422
3595
  this.legIndex = 0;
3423
- this.dynAt = path;
3596
+ this.dynAt = path2;
3424
3597
  this.hops = 0;
3425
3598
  this.ended = false;
3426
3599
  }
@@ -3432,7 +3605,7 @@ var IntentWalk = class {
3432
3605
  while (this.legIndex < this.route.length) {
3433
3606
  const leg = this.route[this.legIndex];
3434
3607
  if (leg === "dyn") {
3435
- if (this.dynAt.steps.length === 0) {
3608
+ if (!this.dynAt.canPop()) {
3436
3609
  this.legIndex++;
3437
3610
  continue;
3438
3611
  }
@@ -3529,8 +3702,8 @@ var IntentWalk = class {
3529
3702
  if (this.ended) return;
3530
3703
  this.ended = true;
3531
3704
  if (name === null) return this.release?.();
3532
- const path = this.answerPath ?? this.origin;
3533
- const t = new SendEvent(path, this.transactor, name, args, this.parent);
3705
+ const t = new SendEvent(this.origin, this.transactor, name, args, this.parent);
3706
+ t.txnPath = this.answerPath;
3534
3707
  t._isAnswer = true;
3535
3708
  this.transactor.pushTransaction(t);
3536
3709
  if (this.release) t.completion.whenSubtreeSettled().then(this.release);
@@ -3596,8 +3769,8 @@ var Completion = class {
3596
3769
  }
3597
3770
  };
3598
3771
  var Dispatcher = class {
3599
- constructor(path, transactor, parentTransaction, root = transactor.state.val) {
3600
- this.path = path;
3772
+ constructor(path2, transactor, parentTransaction, root = transactor.state.val) {
3773
+ this.path = path2;
3601
3774
  this.transactor = transactor;
3602
3775
  this.parent = parentTransaction;
3603
3776
  this.root = root;
@@ -3625,7 +3798,9 @@ var Dispatcher = class {
3625
3798
  // replayed; provides are pushed on every component frame either way, so a provide
3626
3799
  // that reads a loop binding is the one case this cannot reproduce faithfully.
3627
3800
  _stack() {
3628
- this._stackMemo ??= this.path.toTransactionPath().buildStack(Stack.root(this.transactor.comps, this.root, this.parent));
3801
+ this._stackMemo ??= this.path.buildStack(
3802
+ Stack.root(this.transactor.comps, this.root, this.parent)
3803
+ );
3629
3804
  return this._stackMemo;
3630
3805
  }
3631
3806
  // Resolve a name the way the renderer would. `opts.route` takes the same legs in
@@ -3639,14 +3814,19 @@ var Dispatcher = class {
3639
3814
  );
3640
3815
  }
3641
3816
  // The `lex` leg without a Stack: the scope of the component whose handler is
3642
- // running, which is the leaf of this ctx's path.
3817
+ // running, which is the leaf of this ctx's path. A type name resolves to the
3818
+ // component registered under it; a value name to a path registered under it
3819
+ // (see ComponentStack.registerPaths), read against the current root.
3643
3820
  _lookupLex(name) {
3644
3821
  let Comp = null;
3645
3822
  this.walkPath((c) => {
3646
3823
  Comp = c;
3647
3824
  return false;
3648
3825
  });
3649
- return Comp?.scope?.lookupComponent(name) ?? null;
3826
+ const scope = Comp?.scope;
3827
+ if (scope == null) return null;
3828
+ if (isTypeName(name)) return scope.lookupComponent(name) ?? null;
3829
+ return scope.lookupPath(name)?.lookup(this.root) ?? null;
3650
3830
  }
3651
3831
  // A component lookup is a value lookup constrained to a component. The `lex` leg
3652
3832
  // can only ever answer with one; the `dyn` leg reads a binding an ancestor
@@ -3668,8 +3848,8 @@ var Dispatcher = class {
3668
3848
  send(name, args) {
3669
3849
  return this.sendAtPath(this.path, name, args);
3670
3850
  }
3671
- sendAtPath(path, name, args) {
3672
- return this.transactor.pushSend(path, name, args, this.parent, this.path);
3851
+ sendAtPath(path2, name, args) {
3852
+ return this.transactor.pushSend(path2, name, args, this.parent, this.path);
3673
3853
  }
3674
3854
  // An intent is ROUTED: it names a job and walks until something answers. The verb
3675
3855
  // does not decide which scope answers — `opts.route` does, written here at the call
@@ -3677,8 +3857,8 @@ var Dispatcher = class {
3677
3857
  intent(name, args, opts) {
3678
3858
  return this.intentAtPath(this.path, name, args, opts);
3679
3859
  }
3680
- intentAtPath(path, name, args, opts) {
3681
- return this.transactor.pushIntent(path, name, args, opts, this.parent);
3860
+ intentAtPath(path2, name, args, opts) {
3861
+ return this.transactor.pushIntent(path2, name, args, opts, this.parent);
3682
3862
  }
3683
3863
  };
3684
3864
  var EventContext = class extends Dispatcher {
@@ -3707,7 +3887,14 @@ var EventContext = class extends Dispatcher {
3707
3887
  this.transactor.refuse("NO_SENDER", { name });
3708
3888
  return null;
3709
3889
  }
3710
- return this.sendAtPath(origin, name, args);
3890
+ return this.transactor.pushSend(
3891
+ origin,
3892
+ name,
3893
+ args,
3894
+ this.parent,
3895
+ this.path,
3896
+ this.parent.originPinned
3897
+ );
3711
3898
  }
3712
3899
  // End the walk answering nothing — "served, and no answer".
3713
3900
  stop() {
@@ -3739,7 +3926,7 @@ var PathChanges = class extends PathBuilder {
3739
3926
  }
3740
3927
  };
3741
3928
  function rootDispatcher(transactor) {
3742
- return new Dispatcher(new Path([]), transactor, null);
3929
+ return new Dispatcher(new DispatchPath(), transactor, null);
3743
3930
  }
3744
3931
 
3745
3932
  // src/app.js
@@ -3780,7 +3967,7 @@ var App = class {
3780
3967
  const { type } = e;
3781
3968
  const isDrag = type === "dragover" || type === "dragstart" || type === "dragend" || type === "drop";
3782
3969
  const { rootNode: root, maxEventNodeDepth: maxDepth, comps, transactor } = this;
3783
- const [path, handlers] = Path.fromNodeAndEventName(
3970
+ const [path2, handlers] = DispatchPath.fromNodeAndEventName(
3784
3971
  e.target,
3785
3972
  type,
3786
3973
  root,
@@ -3788,9 +3975,9 @@ var App = class {
3788
3975
  comps,
3789
3976
  !isDrag
3790
3977
  );
3791
- if (isDrag) this._handleDragEvent(e, type, path);
3792
- if (path !== null && handlers !== null)
3793
- for (const handler of handlers) transactor.transactInputNow(path, e, handler, this.dragInfo);
3978
+ if (isDrag) this._handleDragEvent(e, type, path2);
3979
+ if (path2 !== null && handlers !== null)
3980
+ for (const handler of handlers) transactor.transactInputNow(path2, e, handler, this.dragInfo);
3794
3981
  }
3795
3982
  _handleTouchEvent(e) {
3796
3983
  const { type } = e;
@@ -3833,7 +4020,7 @@ var App = class {
3833
4020
  this._touch = null;
3834
4021
  }
3835
4022
  }
3836
- _handleDragEvent(e, type, path) {
4023
+ _handleDragEvent(e, type, path2) {
3837
4024
  if (type === "dragover") {
3838
4025
  const dropTarget = getClosestDropTarget(e.target, this.rootNode, Infinity);
3839
4026
  if (dropTarget !== null) {
@@ -3845,10 +4032,10 @@ var App = class {
3845
4032
  } else if (type === "dragstart") {
3846
4033
  e.target.dataset.dragging = 1;
3847
4034
  const rootValue = this.state.val;
3848
- const txnPath = path.compact().toTransactionPath();
4035
+ const txnPath = path2.compact().toTransactionPath();
3849
4036
  const value = txnPath.lookup(rootValue);
3850
4037
  const dragType = e.target.dataset.dragtype ?? "?";
3851
- const stack = path.toTransactionPath().buildStack(this.makeStack(rootValue));
4038
+ const stack = path2.buildStack(this.makeStack(rootValue));
3852
4039
  this.dragInfo = new DragInfo(stack, value, dragType, e.target);
3853
4040
  } else if (type === "drop") {
3854
4041
  e.preventDefault();
@@ -3923,7 +4110,7 @@ var App = class {
3923
4110
  this.rootNode.removeEventListener(name, this, listenerOpts(name));
3924
4111
  }
3925
4112
  sendAtRoot(name, args) {
3926
- this.transactor.pushSend(new Path([]), name, args);
4113
+ this.transactor.pushSend(new DispatchPath(), name, args);
3927
4114
  }
3928
4115
  registerComponents(comps, opts) {
3929
4116
  const scope = this.compStack.enter();
@@ -4141,7 +4328,20 @@ var WeakMapDomCache = class {
4141
4328
  };
4142
4329
 
4143
4330
  // src/renderer.js
4144
- var DATASET_ATTRS = ["nid", "cid", "eid", "vid", "si", "sk"];
4331
+ var DATASET_ATTRS = ["nid", "cid", "eid", "vid", "si", "sk", "rp"];
4332
+ function stampRenderBase(vdom, text) {
4333
+ if (vdom instanceof VNode)
4334
+ return new VNode(
4335
+ vdom.tag,
4336
+ { ...vdom.attrs, "data-rp": text },
4337
+ vdom.childs,
4338
+ vdom.key,
4339
+ vdom.namespace
4340
+ );
4341
+ if (vdom instanceof VFragment)
4342
+ return new VFragment(vdom.childs.map((c) => stampRenderBase(c, text)));
4343
+ return vdom;
4344
+ }
4145
4345
  var Renderer = class {
4146
4346
  constructor(comps) {
4147
4347
  this.comps = comps;
@@ -4178,17 +4378,17 @@ var Renderer = class {
4178
4378
  if (comp === null) return null;
4179
4379
  return this._rValComp(stack, val, comp, comp.getView(viewName).anode, "ROOT", viewName);
4180
4380
  }
4181
- renderIt(stack, node, key, viewName) {
4381
+ renderIt(stack, node, key, viewName, base = null) {
4182
4382
  const comp = this.comps.getCompFor(stack.it);
4183
- return comp ? this._rValComp(stack, stack.it, comp, node, key, viewName) : null;
4383
+ return comp ? this._rValComp(stack, stack.it, comp, node, key, viewName, base) : null;
4184
4384
  }
4185
4385
  // `node` is the parse node of the render site (`<x render>` / `render-it` /
4186
4386
  // `render-each`, or the view's root anode for the app root). It keys the
4187
4387
  // cache as a globally-unique object: node ids alone are unique only within a
4188
4388
  // single view, so the same value rendered by two components (e.g. through a
4189
4389
  // shared dynamic-var sequence) would otherwise collide in the cache.
4190
- _rValComp(stack, val, comp, node, key, viewName) {
4191
- const cacheKey = `${viewName ?? ""}${stack.viewsId ?? ""}${key}`;
4390
+ _rValComp(stack, val, comp, node, key, viewName, base = null) {
4391
+ const cacheKey = `${viewName ?? ""}${stack.viewsId ?? ""}${key}${stack.renderPath.addressKey}`;
4192
4392
  const cachePath = [node, val];
4193
4393
  stack._pushDynBindValuesToArray(cachePath, comp);
4194
4394
  const cachedNode = this.cache.get(cachePath, cacheKey);
@@ -4196,30 +4396,43 @@ var Renderer = class {
4196
4396
  const view = viewName ? comp.getView(viewName) : stack.lookupBestView(comp.views, "main");
4197
4397
  const body = this.renderView(view, stack);
4198
4398
  if (body == null) return null;
4399
+ const baseJson = base === null ? null : pathToJson(base);
4199
4400
  const meta = this._renderMetadata({
4200
4401
  $: "Comp",
4201
4402
  nid: node?.nodeId ?? null,
4202
4403
  cid: comp.id,
4203
- vid: view.name
4404
+ vid: view.name,
4405
+ ...baseJson === null ? null : { base: baseJson }
4204
4406
  });
4205
- const dom = new VFragment([meta, body]);
4407
+ const dom = new VFragment([
4408
+ meta,
4409
+ baseJson === null ? body : stampRenderBase(body, JSON.stringify(baseJson))
4410
+ ]);
4206
4411
  this.cache.set(cachePath, cacheKey, dom);
4207
4412
  return dom;
4208
4413
  }
4209
4414
  pushEachEntry(r, nid, attrName, key, dom) {
4210
4415
  r.push(this._renderMetadata({ $: "Each", nid, [attrName]: key }), dom);
4211
4416
  }
4212
- renderEachWhen(stack, iterInfo, view, nid) {
4417
+ renderEachWhen(stack, each) {
4418
+ const { iterInfo, node: view, nodeId: nid } = each;
4213
4419
  const { seq, filter, loopWith, enricher } = iterInfo.eval(stack);
4420
+ const seqPath = iterInfo.val instanceof DynVal ? stack.lookupDynamicLocated(iterInfo.val.name)?.path ?? null : null;
4214
4421
  const r = [];
4215
4422
  const it = stack.it;
4216
4423
  const renderOne = (key, value, attrName, binds) => {
4424
+ const itemBase = seqPath === null ? null : keyedPath(seqPath, key);
4425
+ const itemStep = itemBase === null ? each.itemStep(key) : null;
4426
+ const itemPath = itemBase !== null ? stack.renderPath.pushFrame(itemBase) : itemStep !== null ? stack.renderPath.pushItem(itemStep) : stack.renderPath;
4217
4427
  const cachePath = enricher ? [view, it, value] : [view, value];
4218
- const cacheKey = `${stack.viewsId ?? ""}${nid}${key}`;
4428
+ const cacheKey = `${stack.viewsId ?? ""}${nid}${key}${itemPath.addressKey}`;
4219
4429
  const cachedNode = this.cache.get(cachePath, cacheKey);
4220
4430
  if (cachedNode) this.pushEachEntry(r, nid, attrName, key, cachedNode);
4221
4431
  else {
4222
- const dom = this.renderView(view, stack.enter(value, binds, false));
4432
+ const dom = this.renderView(
4433
+ view,
4434
+ stack.enter(value, binds, false, itemPath, itemBase !== null)
4435
+ );
4223
4436
  if (dom != null) this.pushEachEntry(r, nid, attrName, key, dom);
4224
4437
  this.cache.set(cachePath, cacheKey, dom);
4225
4438
  }
@@ -4331,6 +4544,7 @@ export {
4331
4544
  html,
4332
4545
  injectCss,
4333
4546
  macro,
4547
+ path,
4334
4548
  phaseOps,
4335
4549
  resolveArgs,
4336
4550
  rootDispatcher,