@nodegx/mcp 0.2.3 → 0.2.4

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.
@@ -104563,6 +104563,52 @@ var require_src3 = __commonJS({
104563
104563
  .filter((x) => x.box > 0 && x.width > x.box + 1)
104564
104564
  .map((x) => ({ text: x.el.textContent.trim().slice(0, 80), width: round(x.width), parentWidth: round(x.box), cls: cls(x.el) }));
104565
104565
 
104566
+ // P112-OWN020-OFFSYSTEM \u2014 the controls, measured against the project's type scale. A control nobody styled draws
104567
+ // the runtime's own look (a Radio Button is a 32px circle with a 2px black ring; a native select or button keeps
104568
+ // the browser's 13.33px text), and that is the "chunky" control beside a page on tokens. A radio or checkbox input
104569
+ // is opacity 0, so its box is read on the first ancestor a person can see. The scale is the project's --text-*
104570
+ // tokens, read off :root; the fake DOMs in the tests have no getPropertyValue, so it is guarded.
104571
+ const pxOf = (value) => {
104572
+ const s = String(value || '').trim();
104573
+ if (!s) return NaN;
104574
+ if (s.slice(-3) === 'rem') return parseFloat(s) * (parseFloat(styleOf(document.documentElement).fontSize) || 16);
104575
+ return parseFloat(s);
104576
+ };
104577
+ const textScale = [];
104578
+ const rootStyle = styleOf(document.documentElement);
104579
+ if (rootStyle && typeof rootStyle.getPropertyValue === 'function') {
104580
+ for (const step of ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl']) {
104581
+ const px = pxOf(rootStyle.getPropertyValue('--text-' + step));
104582
+ if (px > 0) textScale.push(Math.round(px * 100) / 100);
104583
+ }
104584
+ }
104585
+ const controlKind = (el) => {
104586
+ const type = String(el.type || '').toLowerCase();
104587
+ if (el.tagName === 'INPUT' && type === 'radio') return 'Radio Button';
104588
+ if (el.tagName === 'INPUT' && type === 'checkbox') return 'Checkbox';
104589
+ if (el.tagName === 'SELECT') return 'Dropdown';
104590
+ if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && ['', 'text', 'email', 'password', 'search', 'number', 'tel', 'url'].indexOf(type) !== -1)) return 'Text Input';
104591
+ if (el.tagName === 'BUTTON') return 'Button';
104592
+ return null;
104593
+ };
104594
+ const controls = [];
104595
+ for (const el of all) {
104596
+ const kind = controlKind(el);
104597
+ if (!kind) continue;
104598
+ if (kind === 'Radio Button' || kind === 'Checkbox') {
104599
+ let shown = el;
104600
+ for (let hops = 0; shown && hops < 3 && (parseFloat(styleOf(shown).opacity) < 0.05 || shown.getBoundingClientRect().width < 2); hops++) shown = shown.parentElement;
104601
+ if (!shown || shown.offsetParent === null) continue;
104602
+ const r = shown.getBoundingClientRect();
104603
+ let label = null;
104604
+ if (el.id && typeof document.querySelector === 'function') label = document.querySelector('label[for="' + el.id + '"]');
104605
+ controls.push({ kind, box: round(Math.max(r.width, r.height)), textSize: label ? pxOf(styleOf(label).fontSize) : NaN });
104606
+ } else {
104607
+ if (visible.indexOf(el) === -1) continue;
104608
+ controls.push({ kind, box: 0, textSize: pxOf(styleOf(el).fontSize) });
104609
+ }
104610
+ }
104611
+
104566
104612
  // AWP-004 \u2014 what is on screen, as opposed to what is in the DOM. The page is
104567
104613
  // never scrolled when this runs, so a rect is already a document position.
104568
104614
  // Kimi K3's storefront was 83 texts in the DOM and about three on screen, and
@@ -105021,6 +105067,7 @@ var require_src3 = __commonJS({
105021
105067
  overflowingCount: overflowing.length,
105022
105068
  textsWiderThanBox: textsWiderThanBox.slice(0, 10),
105023
105069
  textsWiderThanBoxCount: textsWiderThanBox.length,
105070
+ controls: { textScale, items: controls.slice(0, 60), count: controls.length },
105024
105071
  text: {
105025
105072
  elements: textEls.length,
105026
105073
  onScreen: textEls.filter(onScreen).length,
@@ -105110,6 +105157,13 @@ var require_src3 = __commonJS({
105110
105157
  * page-level finding stayed quiet because the page itself kept its width.
105111
105158
  */
105112
105159
  TextWiderThanItsBox: "text-wider-than-its-box",
105160
+ /**
105161
+ * P112-OWN020-OFFSYSTEM — a control drawn at the runtime's own size beside a page on its token scale: a radio or
105162
+ * checkbox box much bigger than its label, or control text off the project's --text-* scale (a native select or
105163
+ * button keeps the browser's 13.33px). Mistral (SP1) and DeepSeek (OpenTune) both shipped one — the "chunky"
105164
+ * Sort-by circles — because a control placed in passing is the one an agent never styles.
105165
+ */
105166
+ ControlOffScale: "control-off-scale",
105113
105167
  SingleColumnGrid: "single-column-grid",
105114
105168
  MinimumLayoutWidth: "minimum-layout-width",
105115
105169
  HorizontalOverflow: "horizontal-overflow",
@@ -105178,9 +105232,30 @@ var require_src3 = __commonJS({
105178
105232
  const named = widest.cls ? `${widest.tag.toLowerCase()}.${widest.cls.split(/\s+/)[0]}` : widest.tag.toLowerCase();
105179
105233
  return `${named} at ${widest.width}px`;
105180
105234
  }
105235
+ function controlsOffScale(controls) {
105236
+ const scale = controls.textScale || [];
105237
+ const onScale = (px3) => scale.length === 0 || !(px3 > 0) || scale.some((s) => Math.abs(s - px3) < 0.6);
105238
+ const byKind = {};
105239
+ for (const c of controls.items || []) {
105240
+ const oversized = (c.kind === "Radio Button" || c.kind === "Checkbox") && c.box > Math.max(20, 1.5 * (c.textSize > 0 ? c.textSize : 0));
105241
+ const offText = !onScale(c.textSize);
105242
+ if (!oversized && !offText) continue;
105243
+ const k = byKind[c.kind] || (byKind[c.kind] = { kind: c.kind, count: 0, box: c.box, textSize: c.textSize, oversized, offText });
105244
+ k.count += 1;
105245
+ }
105246
+ const fmt = (n) => String(Math.round(n * 100) / 100);
105247
+ return Object.keys(byKind).map((kind) => {
105248
+ const k = byKind[kind];
105249
+ const parts = [];
105250
+ if (k.oversized) parts.push(fmt(k.box) + "px box" + (k.textSize > 0 ? " beside a " + fmt(k.textSize) + "px label" : ""));
105251
+ if (k.offText) parts.push(fmt(k.textSize) + "px text, off the type scale (" + scale.map(fmt).join("/") + "px)");
105252
+ return { kind, count: k.count, box: k.box, textSize: k.textSize, sentence: kind + " x" + k.count + ": " + parts.join(", ") };
105253
+ });
105254
+ }
105181
105255
  function summarise(viewports, diagnosis, overridden = {}) {
105182
105256
  const findings = [];
105183
105257
  const add = (f) => findings.push(f);
105258
+ let controlsJudged = false;
105184
105259
  for (const [name, v] of Object.entries(viewports)) {
105185
105260
  if (!v || v.error) continue;
105186
105261
  const isDesktop = v.requested.width >= DESKTOP_WIDTH;
@@ -105329,6 +105404,19 @@ var require_src3 = __commonJS({
105329
105404
  evidence: v.textsWiderThanBox
105330
105405
  });
105331
105406
  }
105407
+ if (!controlsJudged && v.controls && v.controls.count > 0) {
105408
+ controlsJudged = true;
105409
+ const off = controlsOffScale(v.controls);
105410
+ if (off.length > 0) {
105411
+ add({
105412
+ code: RenderFinding.ControlOffScale,
105413
+ severity: "warning",
105414
+ viewport: name,
105415
+ message: "Controls drawn at a size the rest of the page is not: " + off.map((o) => o.sentence).join("; ") + `. A control nobody styled keeps the runtime's own look. Give the type one look for the whole app with set_element_style (find_tools group "theme"), or set_style_preset to add on-system styles for every control; a node that sets the parameter itself keeps its own value.`,
105416
+ evidence: off.map((o) => ({ kind: o.kind, count: o.count, box: o.box, textSize: o.textSize, textScale: v.controls.textScale }))
105417
+ });
105418
+ }
105419
+ }
105332
105420
  if (v.text.elements >= 10) {
105333
105421
  const weights = Object.keys(v.text.fontWeights);
105334
105422
  if (weights.length <= 1 || v.text.distinctFontSizes <= 2) {
@@ -105499,6 +105587,8 @@ var require_src3 = __commonJS({
105499
105587
  plural: plural3,
105500
105588
  widestOffender,
105501
105589
  describeSites,
105590
+ // P112-OWN020-OFFSYSTEM — exported so its spec grades the judgement on readings, not only through a fake DOM.
105591
+ controlsOffScale,
105502
105592
  TEXT_BEARING_PORTS,
105503
105593
  placeholderStringsFromCatalog
105504
105594
  };
@@ -105510,7 +105600,7 @@ var require_package = __commonJS({
105510
105600
  "package.json"(exports2, module2) {
105511
105601
  module2.exports = {
105512
105602
  name: "@nodegx/mcp",
105513
- version: "0.2.3",
105603
+ version: "0.2.4",
105514
105604
  description: "Model Context Protocol server exposing NodeGX (OpenNoodl) v2 projects to external AI agents.",
105515
105605
  license: "GPL-3.0-only",
105516
105606
  homepage: "https://nodegx.io",
@@ -105546,17 +105636,15 @@ var require_package = __commonJS({
105546
105636
  test: "jest",
105547
105637
  typecheck: "tsc --noEmit"
105548
105638
  },
105549
- dependencies: {
105550
- "@modelcontextprotocol/sdk": "^1.29.0",
105551
- zod: "^3.25.76"
105552
- },
105553
105639
  devDependencies: {
105640
+ "@modelcontextprotocol/sdk": "^1.29.0",
105554
105641
  "@nodegx/kit-catalog": "*",
105555
105642
  "@nodegx/kit-scaffold": "*",
105556
105643
  "@nodegx/module-inject": "*",
105557
105644
  "@types/jest": "^29.0.1",
105558
105645
  jest: "^29.7.0",
105559
- "ts-jest": "^29.4.1"
105646
+ "ts-jest": "^29.4.1",
105647
+ zod: "^3.25.76"
105560
105648
  },
105561
105649
  publishConfig: {
105562
105650
  access: "public"
@@ -121151,7 +121239,7 @@ var TOOL_GROUPS = Object.freeze([
121151
121239
  // looks — so the one instruction the design doctrine gives most often led to
121152
121240
  // an empty answer.
121153
121241
  keywords: ["theme", "colour", "color", "palette", "styling", "style guide", "branding", "appearance", "dark mode"],
121154
- tools: ["set_project_tokens", "set_style_preset"],
121242
+ tools: ["set_project_tokens", "set_style_preset", "set_element_style"],
121155
121243
  resident: false
121156
121244
  }
121157
121245
  ]);
@@ -121237,6 +121325,8 @@ var WRITE_ONLY_TOOLS = /* @__PURE__ */ new Set([
121237
121325
  "set_project_settings",
121238
121326
  "set_project_tokens",
121239
121327
  "set_style_preset",
121328
+ // P112-OWN020-OFFSYSTEM — writes nodegx.styles.json.
121329
+ "set_element_style",
121240
121330
  // P109 ISL-005.
121241
121331
  "write_script_file",
121242
121332
  "edit_script_file",
@@ -225717,13 +225807,13 @@ var styles_schema_default = {
225717
225807
  },
225718
225808
  variant: {
225719
225809
  type: "object",
225720
- required: ["name", "typename"],
225810
+ required: ["typename"],
225721
225811
  additionalProperties: true,
225722
225812
  properties: {
225723
225813
  name: {
225724
225814
  type: "string",
225725
225815
  minLength: 1,
225726
- description: "Variant name"
225816
+ description: "Variant name. Absent: the element style, which dresses every node of this type that names no variant (the node's own parameters still win)"
225727
225817
  },
225728
225818
  typename: {
225729
225819
  type: "string",
@@ -226129,6 +226219,14 @@ var V2ProjectStore = class {
226129
226219
  readStyles() {
226130
226220
  return this.readJson(STYLES_FILE);
226131
226221
  }
226222
+ /**
226223
+ * P112-OWN020-OFFSYSTEM — the styles file had a reader and no writer, so the one layer that dresses every node of
226224
+ * a type (a variant with no `name`) could not be written by anything that writes through the format. The whole
226225
+ * file is replaced: a caller reads it, changes what it owns, and writes it back.
226226
+ */
226227
+ writeStyles(styles) {
226228
+ this.writeJson(STYLES_FILE, styles);
226229
+ }
226132
226230
  readRegistry() {
226133
226231
  const registry2 = this.readJson(REGISTRY_FILE);
226134
226232
  if (!registry2) throw new FormatRefusal("io-error", "missing-registry", "Missing components/_registry.json.");
@@ -231175,6 +231273,13 @@ Two things are settled BEFORE the first component exists, and in this order:
231175
231273
  choice into the project rather than carrying it in your head \u2014 \`set_style_preset\` adopts a whole
231176
231274
  coherent identity, \`set_project_tokens\` overrides individual values. Both sit in the deferred
231177
231275
  \`theme\` group: call \`find_tools({group:"theme"})\` and they arrive in your next tool list.
231276
+ **Controls are styled once, for the whole app.** An ELEMENT STYLE is the look every node of a type
231277
+ wears unless it sets the parameter itself (\`get_style_vocabulary\` \u2192 \`elementStyles\`). A control
231278
+ type with none draws the runtime's own look, which matches no page. So before you place the first
231279
+ Radio Button, Checkbox, Text Input, Dropdown, Slider or Button, call
231280
+ \`set_element_style({ type, starter: true })\` for that type (also in \`theme\`), and change it there
231281
+ to change every one. Style a single node only for what is particular to it: a control styled by
231282
+ hand, one node at a time, is the one that drifts.
231178
231283
  2. **The screens.** Name every page the app has and say, in one sentence each, what a person does on
231179
231284
  it. That list is what the component tree is a decomposition OF.
231180
231285
 
@@ -243452,7 +243557,10 @@ var RUN_TASKS_CONTRACT = {
243452
243557
  failure: { parameter: "taskFailureOutput", default: "Failure" },
243453
243558
  error: { parameter: "taskErrorOutput", default: "Error" }
243454
243559
  };
243560
+ var SET_VARIABLE_CHAIN_OUTPUTS = ["done", "completed"];
243455
243561
  var OWN_CHAIN_OUTPUTS = {
243562
+ // P18-S127-SETDONE (EXP-011 §95).
243563
+ "Set Variable": SET_VARIABLE_CHAIN_OUTPUTS,
243456
243564
  [RUN_TASKS_TYPE]: RUN_TASKS_OUTPUTS,
243457
243565
  // EXP-011 §54. The boundary's Error is its listener, compiled by its own registration.
243458
243566
  [ON_APP_ERROR_TYPE]: ["error"],
@@ -243751,6 +243859,7 @@ var SESSION_READS = {
243751
243859
  username: { field: "username", maybeUndefined: true },
243752
243860
  email: { field: "email", maybeUndefined: true }
243753
243861
  };
243862
+ var INPUT_PULSE_TS_TYPE = "{ sent: number; taken: { current: number } }";
243754
243863
  function isDefer(value) {
243755
243864
  return typeof value === "object" && value !== null && "defer" in value;
243756
243865
  }
@@ -243840,9 +243949,11 @@ function planProject(ir, catalog2) {
243840
243949
  const child = byLegacyNow(pending.targetLegacy);
243841
243950
  const lifted6 = child?.liftedOutputProps.find((l) => l.port === pending.port);
243842
243951
  if (!lifted6) {
243843
- plan.notes.push(
243844
- `wire ${pending.connectionKey} dropped: instance output "${pending.port}" is not lifted by ${pending.targetLegacy} \u2014 its feed defers there`
243845
- );
243952
+ const reason2 = `instance output "${pending.port}" is not lifted by ${pending.targetLegacy} \u2014 its feed defers there`;
243953
+ plan.notes.push(`wire ${pending.connectionKey} dropped: ${reason2}`);
243954
+ if (pending.intoInstance === true) {
243955
+ plan.droppedWires.push({ key: pending.connectionKey, fromId: pending.instanceId, fromProperty: pending.port, toId: pending.toNodeId, toProperty: pending.toProperty, reason: reason2 });
243956
+ }
243846
243957
  continue;
243847
243958
  }
243848
243959
  const varKey = `${pending.instanceId}:${pending.port}`;
@@ -243872,6 +243983,23 @@ function planProject(ir, catalog2) {
243872
243983
  kind: "computed",
243873
243984
  expr: { kind: "state-get", name, maybeUndefined: true }
243874
243985
  };
243986
+ for (const pulse of plan.instancePulses) {
243987
+ if (pulse.nodeId !== pending.toNodeId || pulse.delivered !== void 0) continue;
243988
+ const taken = takenNamesOf(plan);
243989
+ const base = `${pulse.stateName.replace(/Pulses$/, "")}Delivered`;
243990
+ let delivered = base;
243991
+ for (let n = 2; taken.has(delivered) || taken.has(setterNameOf(delivered)); n++) delivered = `${base}${n}`;
243992
+ plan.stateVars.push({
243993
+ name: delivered,
243994
+ setterName: setterNameOf(delivered),
243995
+ tsType: "number",
243996
+ boot: 0,
243997
+ originNodeId: pulse.nodeId,
243998
+ origin: "counter",
243999
+ comment: `"${pulse.port}" as the placed component is handed it: the count, one commit after it is sent, so the values lifted with it have landed (P18-S127-SIBLINGVALUE).`
244000
+ });
244001
+ pulse.delivered = { name: delivered, setterName: setterNameOf(delivered) };
244002
+ }
243875
244003
  }
243876
244004
  plan.pendingLifted = [];
243877
244005
  };
@@ -243943,6 +244071,7 @@ function planProject(ir, catalog2) {
243943
244071
  kitDuplicates
243944
244072
  };
243945
244073
  }
244074
+ var UNCARRIED_PAGE_GROUP = "the page's own Group,";
243946
244075
  function undrawnReasonOf(plan, component, id) {
243947
244076
  const nodeById = new Map(component.nodes.map((n) => [n.id, n]));
243948
244077
  const labelOf = (nodeId) => {
@@ -243970,6 +244099,14 @@ function undrawnReasonOf(plan, component, id) {
243970
244099
  }
243971
244100
  return null;
243972
244101
  }
244102
+ function rowlessReasonOf(plan, repeaterId) {
244103
+ const repeater = plan.repeaters[repeaterId];
244104
+ if (repeater === void 0 || repeater.templatePath === null) return "it draws no rows in the export (it has no template component)";
244105
+ const fed = plan.queries.some((q) => q.nodeId === repeater.itemsQueryId) || repeater.itemsCollectionName != null || repeater.itemsExpr !== void 0 || plan.staticData.some((sd) => sd.nodeId === repeater.itemsStaticId);
244106
+ if (!fed) return "it draws no rows in the export (its items are not fed by a query, a named array, or a statically-known list)";
244107
+ if (repeater.mapping === null) return "it draws no rows in the export (its rows are mapped by a script, which is not a static map)";
244108
+ return null;
244109
+ }
243973
244110
  function unreachedTriggersOf(plan, component) {
243974
244111
  const found2 = { owners: /* @__PURE__ */ new Map(), mounts: /* @__PURE__ */ new Map(), rows: /* @__PURE__ */ new Map() };
243975
244112
  if (plan.file === null) return found2;
@@ -243982,25 +244119,22 @@ function unreachedTriggersOf(plan, component) {
243982
244119
  const owners = /* @__PURE__ */ new Set([...Object.keys(plan.handlers), ...Object.keys(plan.changeHandlers), ...plan.drags.map((d) => d.nodeId)]);
243983
244120
  for (const owner of owners) {
243984
244121
  if (plan.roleOf[owner] === void 0) continue;
243985
- const why = undrawnReasonOf(plan, component, owner);
244122
+ const why = undrawnReasonOf(plan, component, owner) ?? (nodeById.get(owner)?.type === "For Each" ? rowlessReasonOf(plan, owner) : null);
243986
244123
  if (why === null) continue;
243987
244124
  whyUndrawn.set(owner, why);
243988
244125
  found2.owners.set(owner, `its trigger, ${labelOf(owner)}, is not drawn: ${why}`);
243989
244126
  }
243990
244127
  const whyUnmounted = /* @__PURE__ */ new Map();
243991
244128
  for (const [owner, byPort] of Object.entries(plan.handlers)) {
243992
- if (byPort["didMount"] === void 0 || whyUndrawn.has(owner)) continue;
243993
- for (let at = owner; at !== void 0; at = nodeById.get(at)?.parent) {
243994
- const bound = plan.bindings[at]?.["mounted"];
243995
- if (bound !== void 0 && !(bound.kind === "computed" && bound.expr.kind === "literal")) {
243996
- const whose = at === owner ? "it is" : `an element above it, ${labelOf(at)}, is`;
243997
- const why = `${whose} mounted by a value, so its Did Mount fires when that value says, and a mount effect runs when the component mounts`;
243998
- whyUnmounted.set(owner, why);
243999
- found2.mounts.set(owner, `its trigger, the Did Mount of ${labelOf(owner)}, is not run: ${why}`);
244000
- break;
244001
- }
244002
- if (at === plan.rootId) break;
244003
- }
244129
+ if (byPort["didMount"] === void 0 || whyUndrawn.has(owner) || plan.collapsedGroupId === void 0) continue;
244130
+ const bound = plan.bindings[plan.collapsedGroupId]?.["mounted"];
244131
+ if (bound === void 0 || bound.kind === "computed" && bound.expr.kind === "literal") continue;
244132
+ let inside = false;
244133
+ for (let at = owner; at !== void 0; at = nodeById.get(at)?.parent) if (at === plan.collapsedGroupId) inside = true;
244134
+ if (!inside) continue;
244135
+ const why = `${UNCARRIED_PAGE_GROUP} ${labelOf(plan.collapsedGroupId)} is mounted by a value the export does not carry (it draws the page), so when it mounts is not known here`;
244136
+ whyUnmounted.set(owner, why);
244137
+ found2.mounts.set(owner, `its trigger, the Did Mount of ${labelOf(owner)}, is not run: ${why}`);
244004
244138
  }
244005
244139
  const writers = /* @__PURE__ */ new Map();
244006
244140
  const walk2 = (value, owner) => {
@@ -244081,6 +244215,8 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
244081
244215
  handlers: {},
244082
244216
  changeHandlers: {},
244083
244217
  receivers: [],
244218
+ inputPulses: [],
244219
+ instancePulses: [],
244084
244220
  popups: [],
244085
244221
  closesPopup: false,
244086
244222
  queries: [],
@@ -244098,6 +244234,7 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
244098
244234
  stateVars: [],
244099
244235
  syncEffects: [],
244100
244236
  pushEffects: [],
244237
+ rowRelays: [],
244101
244238
  branchEffects: [],
244102
244239
  recordEffects: [],
244103
244240
  variableSeeds: [],
@@ -244303,6 +244440,7 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
244303
244440
  if (roots.length > 1) {
244304
244441
  notes.push(`component has ${roots.length} visual roots; only the first renders in step 4`);
244305
244442
  }
244443
+ const routerMounted = (toNode, toProperty) => toProperty === "mounted" && toNode.id === plan.rootId && toNode.type === "Page";
244306
244444
  const root = roots[0];
244307
244445
  if (root !== void 0) {
244308
244446
  plan.rootId = root.id;
@@ -245279,7 +245417,17 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
245279
245417
  }
245280
245418
  };
245281
245419
  const usedStateVarNames = /* @__PURE__ */ new Set();
245282
- const stateNameTaken = (name) => usedStateVarNames.has(name) || usedJsFnNames.has(name) || plan.props.some((p) => p.name === name) || plan.outputProps.some((o) => o.prop === name) || outputInterface.valueProps.some((v) => v.prop === name) || name === plan.file?.symbol || /**
245420
+ let componentStoreNames;
245421
+ const storeNameTaken = (name) => {
245422
+ componentStoreNames ??= new Set(
245423
+ component.nodes.filter((n) => n.type === "Variable2" || n.type === "Set Variable").map((n) => {
245424
+ const variable = literalParam(n, "name");
245425
+ return typeof variable === "string" ? registry2.variables.get(variable)?.exportName : void 0;
245426
+ }).filter((n) => n !== void 0)
245427
+ );
245428
+ return componentStoreNames.has(name);
245429
+ };
245430
+ const stateNameTaken = (name) => usedStateVarNames.has(name) || storeNameTaken(name) || usedJsFnNames.has(name) || plan.props.some((p) => p.name === name) || plan.outputProps.some((o) => o.prop === name) || outputInterface.valueProps.some((v) => v.prop === name) || name === plan.file?.symbol || /**
245283
245431
  * `error` joined this list with EXP-011 Tier 1.2, and it is the only one here that is not
245284
245432
  * simply an identifier the file already prints.
245285
245433
  *
@@ -245358,6 +245506,9 @@ function planComponent(component, ir, catalog2, registry2, pageByLegacy, pageFil
245358
245506
  return stateVar;
245359
245507
  };
245360
245508
  const httpChainScope = /* @__PURE__ */ new Map();
245509
+ const rowChainScopes = [];
245510
+ const relayTouches = [];
245511
+ const relayTouchedKeys = /* @__PURE__ */ new Map();
245361
245512
  const httpNames = /* @__PURE__ */ new Map();
245362
245513
  const httpNamesOf = (node) => {
245363
245514
  let names2 = httpNames.get(node.id);
@@ -246312,7 +246463,8 @@ ${body}
246312
246463
  };
246313
246464
  const CONTROL_ACTION_PORTS = {
246314
246465
  checkbox: ["check", "uncheck"],
246315
- input: ["clear"]
246466
+ // P18-S128-FIELDSET. A Set writes the field's text as a Clear does, so it earns the row too.
246467
+ input: ["clear", "set"]
246316
246468
  };
246317
246469
  const LIST_PRODUCERS = /* @__PURE__ */ new Set(["Collection2", "Filter Collection", "Map Collection", "FilterDBModels"]);
246318
246470
  const collectionReadEligible = (node) => {
@@ -246846,8 +246998,52 @@ ${body}
246846
246998
  }
246847
246999
  return cases;
246848
247000
  };
247001
+ const relayedInputOf = (forEach, output) => {
247002
+ const repeater = plan.repeaters[forEach.id];
247003
+ const templatePath = repeater?.templatePath ?? null;
247004
+ const template = templatePath === null ? void 0 : ir.components.find((tc) => `/${tc.path}` === templatePath);
247005
+ if (repeater === void 0 || templatePath === null || template === void 0) return { defer: `the repeater relaying "${output}" has no template component` };
247006
+ if (repeater.mapping === null) return { defer: `the repeater relaying "${output}" maps its rows with a script, which is not a static map` };
247007
+ const tById = new Map(template.nodes.map((n) => [n.id, n]));
247008
+ const feeds = template.connections.filter((c) => c.toProperty === output && tById.get(c.toId)?.type === "Component Outputs");
247009
+ if (feeds.length !== 1) {
247010
+ return {
247011
+ defer: feeds.length === 0 ? `nothing in ${templatePath} feeds the row output "${output}" that the repeater relays` : `${feeds.length} wires feed ${templatePath}'s row output "${output}" \u2014 last-writer-wins is not statically ordered`
247012
+ };
247013
+ }
247014
+ const feeder = tById.get(feeds[0].fromId);
247015
+ if (feeder?.type !== "Component Inputs") {
247016
+ return {
247017
+ defer: `${templatePath}'s row output "${output}" is computed inside the row (by ${feeder?.type ?? "a missing node"}.${feeds[0].fromProperty}) \u2014 only a row input passed straight through is relayed in this slice`
247018
+ };
247019
+ }
247020
+ const input = feeds[0].fromProperty;
247021
+ if (repeater.mapping !== "template-inputs" && !repeater.mapping.some((m) => m.input === input)) {
247022
+ return { defer: `the repeater maps nothing into ${templatePath}'s input "${input}", which its row output "${output}" passes through` };
247023
+ }
247024
+ const declared = componentOutputInterface(template).valueProps.find((v) => v.port === output)?.tsType ?? "unknown";
247025
+ return { input, templatePath, declared };
247026
+ };
246849
247027
  const resolveExpr = (fromNode, fromProperty, ctx) => {
246850
247028
  if (!fromNode) return null;
247029
+ if (fromNode.type === "For Each" && fromProperty.startsWith(ITEM_OUTPUT_VALUE)) {
247030
+ const output = fromProperty.slice(ITEM_OUTPUT_VALUE.length);
247031
+ relayTouches[relayTouches.length - 1]?.add(fromNode.id);
247032
+ if (jsResolving.size > 0) {
247033
+ ctx.defer = `it reads the repeater ${fromNode.id}'s relayed row value "${output}" as a standing input, read whenever the node runs \u2014 there it is the value of whichever row fired last, which the export does not hold; it reads that value only in a chain a row's relayed signal runs, as the firing row's own`;
247034
+ return null;
247035
+ }
247036
+ if (rowChainScopes[rowChainScopes.length - 1] !== fromNode.id) {
247037
+ ctx.defer = `it reads the repeater ${fromNode.id}'s relayed row value "${output}" in a chain no row's relayed signal of that repeater starts in the export \u2014 there it is the value of whichever row fired last, which the export does not hold (in a row's chain it is the firing row's own)`;
247038
+ return null;
247039
+ }
247040
+ const relayed = relayedInputOf(fromNode, output);
247041
+ if ("defer" in relayed) {
247042
+ ctx.defer = relayed.defer;
247043
+ return null;
247044
+ }
247045
+ return { kind: "row-relay", repeaterId: fromNode.id, output, input: relayed.input, tsType: relayed.declared };
247046
+ }
246851
247047
  if (LIST_PRODUCERS.has(fromNode.type) && fromProperty === "items") return listReadOf(fromNode, ctx);
246852
247048
  if (LIST_PRODUCERS.has(fromNode.type) && fromProperty === "count") {
246853
247049
  const source = listReadOf(fromNode, ctx);
@@ -247719,6 +247915,9 @@ ${body}
247719
247915
  return expr.viaState !== void 0 && expr.seeded !== true;
247720
247916
  case "state-get":
247721
247917
  return expr.maybeUndefined === true;
247918
+ // EXP-011 §100. The row's field: a row whose item lacks it is handed nothing. Must agree with component.ts maybeUndefined.
247919
+ case "row-relay":
247920
+ return true;
247722
247921
  // FLD-015. A hoisted Static Data constant is a frozen module-scope literal: it exists from
247723
247922
  // module load and nothing writes it, so it is never undefined for a stronger reason than
247724
247923
  // the named arrays below — there is no boot value to be before.
@@ -247862,7 +248061,7 @@ ${body}
247862
248061
  reason2 = `its ${w.fromProperty} feeds ${sink?.type ?? "a missing node"}.${w.toProperty} \u2014 a snapshot of the last Evaluate lands only in a Mounted or a Visible in this slice`;
247863
248062
  } else if (!sink || !rendered2.has(sink.id)) {
247864
248063
  reason2 = `its ${w.fromProperty} feeds an element that is not drawn \u2014 a snapshot lands only in a drawn element's Mounted or Visible in this slice`;
247865
- } else if (w.toProperty === "mounted" && sink.id === plan.rootId) {
248064
+ } else if (routerMounted(sink, w.toProperty)) {
247866
248065
  reason2 = "a mounted wire into the component root is a router concern \u2014 not translated in this slice";
247867
248066
  }
247868
248067
  if (reason2 !== null) break;
@@ -248093,6 +248292,9 @@ ${body}
248093
248292
  return "unknown";
248094
248293
  case "state-get":
248095
248294
  return plan.stateVars.find((v) => v.name === expr.name)?.tsType.replace(" | undefined", "") ?? "unknown";
248295
+ // EXP-011 §100. The row template's declared output type.
248296
+ case "row-relay":
248297
+ return expr.tsType;
248096
248298
  case "control-event":
248097
248299
  return expr.form === "checked" ? "boolean" : expr.form === "number" ? "number" : "string";
248098
248300
  case "logical":
@@ -248193,15 +248395,26 @@ ${body}
248193
248395
  "net.noodl.user.SetUserProperties": "store",
248194
248396
  "net.noodl.user.RequestMagicLink": "send"
248195
248397
  };
248196
- const isTriggerWire = (type, toProperty) => TRIGGER_PORTS[type] === toProperty || type === "NavigationShowPopup" && toProperty === "show" || type === "NavigationClosePopup" && (toProperty === "close" || toProperty.startsWith("closeAction-")) || jsNodeKindOf(type) !== null && toProperty === "run" || isLatchType(type) && (LATCH_TRIGGERS[type] ?? []).includes(toProperty) || (type === "net.noodl.controls.checkbox" || type === "Checkbox") && (toProperty === "check" || toProperty === "uncheck") || isTextInputType(type) && toProperty === "clear" || // EXP-011 §39. A Delay's Start/Restart/Stop — one node, three action ports.
248398
+ const isTriggerWire = (type, toProperty) => TRIGGER_PORTS[type] === toProperty || type === "NavigationShowPopup" && toProperty === "show" || type === "NavigationClosePopup" && (toProperty === "close" || toProperty.startsWith("closeAction-")) || jsNodeKindOf(type) !== null && toProperty === "run" || isLatchType(type) && (LATCH_TRIGGERS[type] ?? []).includes(toProperty) || (type === "net.noodl.controls.checkbox" || type === "Checkbox") && (toProperty === "check" || toProperty === "uncheck") || // P18-S128-FIELDSET. A text field's four actions: Clear, Set, Focus, Blur.
248399
+ isTextInputType(type) && (toProperty === "clear" || toProperty === "set" || toProperty === "focus" || toProperty === "blur") || // EXP-011 §39. A Delay's Start/Restart/Stop — one node, three action ports.
248197
248400
  type === TIMER_TYPE && isTimerTrigger(toProperty) || // EXP-011 §49. A States' Toggle and its `to-<state>` family.
248198
248401
  type === STATES_TYPE && isStatesTrigger(toProperty) || // EXP-011 §53. A Run Tasks' Do and Abort — one node, two action ports.
248199
248402
  type === RUN_TASKS_TYPE && (toProperty === "run" || toProperty === "abort") || // EXP-011 §58. The trio's action ports — Parse/Clear, Add/Flush/Clear, Add/Clear.
248200
248403
  STREAM_NODES[type] !== void 0 && STREAM_NODES[type].actions[toProperty] !== void 0 || // EXP-011 §63. A Drag's two snap Dos — one node, two action ports.
248201
248404
  type === DRAG_TYPE && DRAG_SNAP_TRIGGERS[toProperty] !== void 0 || // EXP-011 §61. The Pop's Navigate and its `backAction-<a>` family; the stack's Reset.
248202
248405
  type === STACK_POP_TYPE && (toProperty === "navigate" || toProperty.startsWith("backAction-")) || type === PAGE_STACK_TYPE && toProperty === "reset";
248203
- const isTriggerInto = (node, toProperty) => isTriggerWire(node.type, toProperty) || node.type === SCRIPT_TYPE && scriptPortsOf(node).signalInputs.includes(toProperty) || kitSignalInputOf(node, toProperty) !== void 0;
248406
+ const isTriggerInto = (node, toProperty) => isTriggerWire(node.type, toProperty) || node.type === SCRIPT_TYPE && scriptPortsOf(node).signalInputs.includes(toProperty) || kitSignalInputOf(node, toProperty) !== void 0 || instanceSignalInputOf(node, toProperty);
248204
248407
  const kitSignalInputOf = (node, port5) => plan.roleOf[node.id] === "custom" ? plan.customNodes[node.id]?.def.inputs.find((input) => input.name === port5 && input.type === "signal") : void 0;
248408
+ const instanceSignalInputOf = (node, port5) => {
248409
+ if (plan.roleOf[node.id] !== "instance") return false;
248410
+ const target = ir.components.find((c) => `/${c.path}` === node.type);
248411
+ if (target === void 0 || !signalInputPortsOf(target).has(port5)) return false;
248412
+ return target.nodes.some((n) => {
248413
+ if (n.parent !== void 0) return false;
248414
+ const role = renderRole(n, catalog2, kits);
248415
+ return role !== null && role !== "unsupported" && role !== "radio";
248416
+ });
248417
+ };
248205
248418
  const popupTargetLegacies = /* @__PURE__ */ new Set();
248206
248419
  for (const comp of ir.components) {
248207
248420
  for (const n of comp.nodes) {
@@ -248456,6 +248669,7 @@ ${body}
248456
248669
  if ("defer" in ticked3) return { defer: ticked3.defer };
248457
248670
  for (const c of component.connections) {
248458
248671
  if (c.fromId !== node.id || c.fromProperty === "done" || isJsValueOutput(node, c.fromProperty)) continue;
248672
+ if (node.type === JS_FUNCTION && c.fromProperty === "success") continue;
248459
248673
  if (node.type === JS_FUNCTION && !c.fromProperty.startsWith("out-")) {
248460
248674
  const builtIn = ["success", "failure", "unchanged", "completed", "error"].includes(c.fromProperty);
248461
248675
  if (!builtIn) continue;
@@ -248470,19 +248684,22 @@ ${body}
248470
248684
  }
248471
248685
  const done = doneChainOf(node);
248472
248686
  if ("defer" in done) return done;
248687
+ const success = node.type === JS_FUNCTION ? doneChainOf(node, "success") : { then: [], consumes: [], collapses: [], subscribes: [] };
248688
+ if ("defer" in success) return success;
248473
248689
  const pulsed = pulseChainsOf(node, record2.def);
248474
248690
  if ("defer" in pulsed) pulseRefusals.set(node.id, pulsed.defer);
248475
248691
  const pulses = "defer" in pulsed ? { then: [], consumes: [], collapses: [], subscribes: [] } : pulsed;
248476
- if (ticked3.watch.length > 0 && !actionsValidIn([{ kind: "jsfun-run", nodeId: node.id, then: pulses.then }], { kind: "render" })) {
248692
+ const tickedThen = [...pulses.then, ...success.then];
248693
+ if (ticked3.watch.length > 0 && !actionsValidIn([{ kind: "jsfun-run", nodeId: node.id, then: tickedThen }], { kind: "render" })) {
248477
248694
  return {
248478
248695
  defer: "a ticked input runs it again from no handler, and its signals' chains read values that exist only inside a handler"
248479
248696
  };
248480
248697
  }
248481
248698
  const chain = {
248482
- then: [...pulses.then, ...done.then],
248483
- consumes: [...pulses.consumes, ...done.consumes],
248484
- collapses: [...pulses.collapses, ...done.collapses],
248485
- subscribes: [...pulses.subscribes, ...done.subscribes]
248699
+ then: [...pulses.then, ...success.then, ...done.then],
248700
+ consumes: [...pulses.consumes, ...success.consumes, ...done.consumes],
248701
+ collapses: [...pulses.collapses, ...success.collapses, ...done.collapses],
248702
+ subscribes: [...pulses.subscribes, ...success.subscribes, ...done.subscribes]
248486
248703
  };
248487
248704
  const strays = component.connections.filter(
248488
248705
  (c) => c.fromId === node.id && isJsValueOutput(node, c.fromProperty) && !chain.consumes.includes(c.key)
@@ -248551,10 +248768,10 @@ ${body}
248551
248768
  return { defer: "its Run drives nothing this slice translates \u2014 no done-chain action consumes its work" };
248552
248769
  }
248553
248770
  if (pulses.then.length > 0 || materialize !== void 0 && record2.def.kind === "function") record2.def.pulsed = true;
248554
- if (ticked3.watch.length > 0 && (pulses.then.length > 0 || materialize !== void 0)) {
248771
+ if (ticked3.watch.length > 0 && (tickedThen.length > 0 || materialize !== void 0)) {
248555
248772
  tickedRunPieces.set(node.id, {
248556
248773
  watch: ticked3.watch,
248557
- action: { kind: "jsfun-run", nodeId: node.id, then: pulses.then, ...materialize !== void 0 ? { materialize } : {}, ticked: true }
248774
+ action: { kind: "jsfun-run", nodeId: node.id, then: tickedThen, ...materialize !== void 0 ? { materialize } : {}, ticked: true }
248558
248775
  });
248559
248776
  }
248560
248777
  return {
@@ -248596,6 +248813,31 @@ ${body}
248596
248813
  }
248597
248814
  return { action: { kind: "state-set", name: stateVar.name, op: "inc" }, consumes: [] };
248598
248815
  };
248816
+ const instancePulseVars = /* @__PURE__ */ new Map();
248817
+ const compileInstanceSignal = (node, port5) => {
248818
+ const key2 = `${node.id}\0${port5}`;
248819
+ let stateVar = instancePulseVars.get(key2);
248820
+ if (stateVar === void 0) {
248821
+ stateVar = allocStateVar(
248822
+ `${node.authoredLabel ?? "instance"} ${port5} pulses`,
248823
+ `${port5}Pulses`,
248824
+ "number",
248825
+ 0,
248826
+ node.id,
248827
+ "counter",
248828
+ `How many times "${port5}" on the placed ${node.type} ${node.id} has been signalled \u2014 the count it reads (P18-S126-INSIGNAL).`
248829
+ );
248830
+ instancePulseVars.set(key2, stateVar);
248831
+ const base = `${stateVar.name.replace(/Pulses$/, "")}Taken`;
248832
+ let takenLocal = base;
248833
+ for (let n = 2; stateNameTaken(takenLocal); n++) takenLocal = `${base}${n}`;
248834
+ usedStateVarNames.add(takenLocal);
248835
+ plan.instancePulses.push({ nodeId: node.id, port: port5, stateName: stateVar.name, takenLocal });
248836
+ plan.bindings[node.id] = plan.bindings[node.id] ?? {};
248837
+ plan.bindings[node.id][port5] = { kind: "computed", expr: { kind: "state-get", name: stateVar.name } };
248838
+ }
248839
+ return { action: { kind: "state-set", name: stateVar.name, op: "inc" }, consumes: [] };
248840
+ };
248599
248841
  const compileControlAction = (node, port5) => {
248600
248842
  const consumedOutcome = component.connections.find(
248601
248843
  (c) => c.fromId === node.id && (c.fromProperty === "done" || c.fromProperty === "unchanged")
@@ -248609,12 +248851,51 @@ ${body}
248609
248851
  if (stateVar === void 0) {
248610
248852
  return { defer: `its ${port5} action writes control state nothing else observes \u2014 no state row is minted` };
248611
248853
  }
248854
+ if (port5 === "set") return compileFieldSet(node, stateVar);
248612
248855
  const action = port5 === "check" ? { kind: "state-set", name: stateVar.name, expr: { kind: "literal", value: true } } : port5 === "uncheck" ? { kind: "state-set", name: stateVar.name, expr: { kind: "literal", value: false } } : (
248613
- // clear → the field type's empty value, projected onto the DOM string (FB-026).
248614
- { kind: "state-set", name: stateVar.name, expr: { kind: "literal", value: "" } }
248856
+ // clear → the field type's empty value, projected onto the DOM string (FB-026); P18-S127-CLEAROUT: then the
248857
+ // field's change chain, with the empty text (the runtime's `clear()` calls `setText('')`).
248858
+ { kind: "state-set", name: stateVar.name, expr: { kind: "literal", value: "" }, field: { inputId: node.id, port: "clear" } }
248615
248859
  );
248616
248860
  return { action, consumes: [] };
248617
248861
  };
248862
+ const compileFieldSet = (node, stateVar) => {
248863
+ const type = literalParam(node, "type");
248864
+ if (wiredPorts.has(`${node.id}:type`) || type !== void 0 && typeof type !== "string") {
248865
+ return { defer: "its Type is wired, so whether its Set writes text or a number is not known here" };
248866
+ }
248867
+ if (type === "number") {
248868
+ return { defer: "a Number field's Set sends a number or null, and the exported field carries text (P18-S118-NUMBERFIELD)" };
248869
+ }
248870
+ const authored = literalParam(node, "startValue");
248871
+ const start3 = authored === void 0 || authored === null ? "" : typeof authored === "object" ? void 0 : String(authored);
248872
+ if (start3 === void 0) return { defer: "its typed-in Value is not text, a number or a boolean" };
248873
+ const field2 = { inputId: node.id, port: "set", start: start3 };
248874
+ const wires = component.connections.filter((c) => c.toId === node.id && c.toProperty === "startValue");
248875
+ if (wires.length > 1) return { defer: "two wires feed its Value \u2014 which one its Set writes is not statically ordered" };
248876
+ if (wires.length === 0) {
248877
+ return { action: { kind: "state-set", name: stateVar.name, expr: { kind: "literal", value: start3 }, field: field2 }, consumes: [] };
248878
+ }
248879
+ const ctx = newCtx();
248880
+ const expr = resolveExpr(nodeById.get(wires[0].fromId), wires[0].fromProperty, ctx);
248881
+ if (expr === null) return { defer: ctx.defer ?? "its Value wire has no statically known source, and its Set writes that Value" };
248882
+ if (isBooleanExpr(expr)) return { defer: "its Value wire carries a logic truth value, and its Set writes it as text" };
248883
+ return {
248884
+ action: { kind: "state-set", name: stateVar.name, expr, field: field2 },
248885
+ consumes: [...ctx.consumes],
248886
+ collapses: ctx.logicNodeIds,
248887
+ subscribes: ctx.subscriberIds
248888
+ };
248889
+ };
248890
+ const compileFieldFocus = (node, port5) => {
248891
+ const consumedOutcome = component.connections.find(
248892
+ (c) => c.fromId === node.id && (c.fromProperty === "done" || c.fromProperty === "unchanged")
248893
+ );
248894
+ if (consumedOutcome) {
248895
+ return { defer: `its ${consumedOutcome.fromProperty} outcome is consumed \u2014 change-conditional pulses are not translated in this slice` };
248896
+ }
248897
+ return { action: { kind: "field-focus", inputId: node.id, focus: port5 === "focus" }, consumes: [] };
248898
+ };
248618
248899
  const compileRecordOp = (node) => {
248619
248900
  const verb = RECORD_VERBS[node.type];
248620
248901
  const authoredOrWired = (name) => node.parameters.some((p) => p.name === name) || wiredPorts.has(`${node.id}:${name}`);
@@ -251450,9 +251731,11 @@ ${body}
251450
251731
  if (jsNodeKindOf(node.type) !== null && port5 === "run") return compileJsRun(node);
251451
251732
  if (isLatchType(node.type)) return compileLatch(node, port5);
251452
251733
  if (kitSignalInputOf(node, port5) !== void 0) return compileKitSignal(node, port5);
251734
+ if (instanceSignalInputOf(node, port5)) return compileInstanceSignal(node, port5);
251453
251735
  if ((plan.roleOf[node.id] === "checkbox" || plan.roleOf[node.id] === "input") && (CONTROL_ACTION_PORTS[plan.roleOf[node.id]] ?? []).includes(port5)) {
251454
251736
  return compileControlAction(node, port5);
251455
251737
  }
251738
+ if (plan.roleOf[node.id] === "input" && (port5 === "focus" || port5 === "blur")) return compileFieldFocus(node, port5);
251456
251739
  if (node.type === EXTERNAL_LINK_TYPE) return compileExternalLink(node);
251457
251740
  if (node.type === LOG_TYPE) return compileLog(node);
251458
251741
  if (node.type === TIMER_TYPE && isTimerTrigger(port5)) return compileDelay(node, port5);
@@ -251479,25 +251762,25 @@ ${body}
251479
251762
  if (propagation !== "global") {
251480
251763
  return { defer: `propagation "${String(propagation)}" scopes the event to the component tree` };
251481
251764
  }
251482
- const ctx2 = newCtx();
251765
+ const ctx = newCtx();
251483
251766
  const payload = [];
251484
251767
  const consumes = [];
251485
251768
  for (const key2 of payloadKeysOf(node)) {
251486
- const wire2 = component.connections.find((c) => c.toId === node.id && c.toProperty === key2);
251487
- if (!wire2) continue;
251488
- const expr2 = resolveExpr(nodeById.get(wire2.fromId), wire2.fromProperty, ctx2);
251489
- if (expr2 === null) return { defer: ctx2.defer ?? `payload "${key2}" has no statically known source` };
251490
- if (isBooleanExpr(expr2)) {
251769
+ const wire = component.connections.find((c) => c.toId === node.id && c.toProperty === key2);
251770
+ if (!wire) continue;
251771
+ const expr = resolveExpr(nodeById.get(wire.fromId), wire.fromProperty, ctx);
251772
+ if (expr === null) return { defer: ctx.defer ?? `payload "${key2}" has no statically known source` };
251773
+ if (isBooleanExpr(expr)) {
251491
251774
  return { defer: `payload "${key2}" is fed a logic truth value \u2014 only truthiness sinks take one in this slice` };
251492
251775
  }
251493
- payload.push({ key: key2, expr: expr2 });
251494
- consumes.push(wire2.key);
251776
+ payload.push({ key: key2, expr });
251777
+ consumes.push(wire.key);
251495
251778
  }
251496
251779
  return {
251497
251780
  action: { kind: "emit", channelName, payload },
251498
- consumes: [...consumes, ...ctx2.consumes],
251499
- collapses: ctx2.logicNodeIds,
251500
- subscribes: ctx2.subscriberIds
251781
+ consumes: [...consumes, ...ctx.consumes],
251782
+ collapses: ctx.logicNodeIds,
251783
+ subscribes: ctx.subscriberIds
251501
251784
  };
251502
251785
  }
251503
251786
  if (node.type === GLOBAL_STORE_SET) {
@@ -251518,26 +251801,26 @@ ${body}
251518
251801
  if (keyType === "number" || keyType === "boolean") {
251519
251802
  return { defer: `key "${key2}" is ${keyType}-typed by the initial state; only string writes translate in this slice` };
251520
251803
  }
251521
- const wire2 = lastValueWireOf(node);
251522
- if (wire2 === void 0 && valueWiresInto(node).length > 1) {
251804
+ const wire = lastValueWireOf(node);
251805
+ if (wire === void 0 && valueWiresInto(node).length > 1) {
251523
251806
  return { defer: "two wires feed its Value input \u2014 last-writer-wins is not statically ordered" };
251524
251807
  }
251525
- if (!wire2) {
251808
+ if (!wire) {
251526
251809
  const authored = literalParam(node, "value");
251527
251810
  if (authored === void 0) return { defer: "nothing is wired into value" };
251528
251811
  return { action: { kind: "globalstore-set", storeName: store2.name, key: key2, expr: { kind: "literal", value: authored } }, consumes: [], collapses: [], subscribes: [] };
251529
251812
  }
251530
- const ctx2 = newCtx();
251531
- const expr2 = resolveExpr(nodeById.get(wire2.fromId), wire2.fromProperty, ctx2);
251532
- if (expr2 === null) return { defer: ctx2.defer ?? "the value wire has no statically known source" };
251533
- if (isBooleanExpr(expr2)) {
251813
+ const ctx = newCtx();
251814
+ const expr = resolveExpr(nodeById.get(wire.fromId), wire.fromProperty, ctx);
251815
+ if (expr === null) return { defer: ctx.defer ?? "the value wire has no statically known source" };
251816
+ if (isBooleanExpr(expr)) {
251534
251817
  return { defer: "the value wire carries a logic truth value \u2014 only truthiness sinks take one in this slice" };
251535
251818
  }
251536
251819
  return {
251537
- action: { kind: "globalstore-set", storeName: store2.name, key: key2, expr: expr2, ...typedInUnderWire(node, "value", wire2, expr2) },
251538
- consumes: [wire2.key, ...ctx2.consumes],
251539
- collapses: ctx2.logicNodeIds,
251540
- subscribes: ctx2.subscriberIds
251820
+ action: { kind: "globalstore-set", storeName: store2.name, key: key2, expr, ...typedInUnderWire(node, "value", wire, expr) },
251821
+ consumes: [wire.key, ...ctx.consumes],
251822
+ collapses: ctx.logicNodeIds,
251823
+ subscribes: ctx.subscriberIds
251541
251824
  };
251542
251825
  }
251543
251826
  if (node.type === "NewModel") {
@@ -251545,16 +251828,16 @@ ${body}
251545
251828
  if (result === void 0) return { defer: "the created object is never inserted into a translated array" };
251546
251829
  if ("defer" in result) return { defer: result.defer };
251547
251830
  const chain = result.chain;
251548
- const ctx2 = newCtx();
251831
+ const ctx = newCtx();
251549
251832
  const entries2 = [];
251550
251833
  for (const property of chain.properties) {
251551
251834
  if (property.wire) {
251552
- const expr2 = resolveExpr(nodeById.get(property.wire.fromId), property.wire.fromProperty, ctx2);
251553
- if (expr2 === null) return { defer: ctx2.defer ?? `property "${property.key}" has no statically known source` };
251554
- if (isBooleanExpr(expr2)) {
251835
+ const expr = resolveExpr(nodeById.get(property.wire.fromId), property.wire.fromProperty, ctx);
251836
+ if (expr === null) return { defer: ctx.defer ?? `property "${property.key}" has no statically known source` };
251837
+ if (isBooleanExpr(expr)) {
251555
251838
  return { defer: `property "${property.key}" is fed a logic truth value \u2014 only truthiness sinks take one in this slice` };
251556
251839
  }
251557
- entries2.push({ key: property.key, expr: expr2 });
251840
+ entries2.push({ key: property.key, expr });
251558
251841
  } else if (property.literal !== void 0) {
251559
251842
  entries2.push({ key: property.key, expr: { kind: "literal", value: property.literal } });
251560
251843
  }
@@ -251563,25 +251846,25 @@ ${body}
251563
251846
  const insertNode = nodeById.get(chain.insertId);
251564
251847
  const conflict = mintScopeConflict(node, "new", { kind: "minted", nodeId: chain.minted, wireKey: "" });
251565
251848
  if (conflict !== void 0) return { defer: conflict };
251566
- for (const wire2 of component.connections.filter((c) => c.fromId === insertNode.id)) {
251567
- if (wire2.fromProperty === "failure") continue;
251568
- return { defer: `the insert's ${wire2.fromProperty} output is consumed \u2014 into a minted array this slice translates the Failure chain only` };
251849
+ for (const wire of component.connections.filter((c) => c.fromId === insertNode.id)) {
251850
+ if (wire.fromProperty === "failure") continue;
251851
+ return { defer: `the insert's ${wire.fromProperty} output is consumed \u2014 into a minted array this slice translates the Failure chain only` };
251569
251852
  }
251570
251853
  const fail2 = doneChainOf(insertNode, "failure");
251571
251854
  if ("defer" in fail2) return { defer: fail2.defer };
251572
251855
  const target = { kind: "minted", nodeId: chain.minted, wireKey: "" };
251573
251856
  return {
251574
251857
  action: { kind: "collection-add", collectionName: "", entries: entries2, minted: mintedTargetFor(target, chain.insertId, "insert", fail2.then) },
251575
- consumes: [...chain.consumes, ...ctx2.consumes, ...fail2.consumes],
251576
- collapses: [chain.insertId, ...ctx2.logicNodeIds, ...fail2.collapses],
251577
- subscribes: [...ctx2.subscriberIds, ...fail2.subscribes]
251858
+ consumes: [...chain.consumes, ...ctx.consumes, ...fail2.consumes],
251859
+ collapses: [chain.insertId, ...ctx.logicNodeIds, ...fail2.collapses],
251860
+ subscribes: [...ctx.subscriberIds, ...fail2.subscribes]
251578
251861
  };
251579
251862
  }
251580
251863
  return {
251581
251864
  action: { kind: "collection-add", collectionName: chain.collectionName, entries: entries2 },
251582
- consumes: [...chain.consumes, ...ctx2.consumes],
251583
- collapses: [chain.insertId, ...ctx2.logicNodeIds],
251584
- subscribes: ctx2.subscriberIds
251865
+ consumes: [...chain.consumes, ...ctx.consumes],
251866
+ collapses: [chain.insertId, ...ctx.logicNodeIds],
251867
+ subscribes: ctx.subscriberIds
251585
251868
  };
251586
251869
  }
251587
251870
  if (node.type === "CollectionClear") return compileCollectionClear(node);
@@ -251601,6 +251884,30 @@ ${body}
251601
251884
  if (node.type === "Condition") return compileCondition2(node);
251602
251885
  if (node.type === SET_OBJECT_PROPERTIES_TYPE) return compileSetObjectProperties(node);
251603
251886
  if (node.type === SET_COMPONENT_OBJECT_TYPE || node.type === SET_PARENT_COMPONENT_OBJECT_TYPE) return compileComponentObjectSet(node);
251887
+ return withSetDone(node, compileSetVariableWrite(node));
251888
+ };
251889
+ const withSetDone = (node, write) => {
251890
+ if ("defer" in write || write.action.kind !== "store-set") return write;
251891
+ const then2 = [];
251892
+ const consumes = [...write.consumes];
251893
+ const collapses = [...write.collapses ?? []];
251894
+ const subscribes = [...write.subscribes ?? []];
251895
+ for (const port5 of SET_VARIABLE_CHAIN_OUTPUTS) {
251896
+ if (!component.connections.some((c) => c.fromId === node.id && c.fromProperty === port5)) continue;
251897
+ const chain = doneChainOf(node, port5);
251898
+ if ("defer" in chain) {
251899
+ setDoneRefusals.set(`${node.id}:${port5}`, chain.defer);
251900
+ continue;
251901
+ }
251902
+ then2.push(...chain.then);
251903
+ consumes.push(...chain.consumes);
251904
+ collapses.push(...chain.collapses);
251905
+ subscribes.push(...chain.subscribes);
251906
+ }
251907
+ if (then2.length === 0) return { ...write, consumes, collapses, subscribes };
251908
+ return { action: { ...write.action, then: then2 }, consumes, collapses, subscribes };
251909
+ };
251910
+ const compileSetVariableWrite = (node) => {
251604
251911
  const variableName = variableNameOf(node);
251605
251912
  if (variableName === void 0) return { defer: "variable name is not a literal" };
251606
251913
  const setWith = literalParam(node, "setWith");
@@ -251882,12 +252189,28 @@ ${body}
251882
252189
  return { action: { kind: "branch", cond, whenTrue, whenFalse }, consumes, collapses, subscribes };
251883
252190
  };
251884
252191
  const compiledSinks = /* @__PURE__ */ new Map();
252192
+ const setDoneRefusals = /* @__PURE__ */ new Map();
251885
252193
  const reactiveConditionDefers = /* @__PURE__ */ new Map();
251886
252194
  const recordEffectDefers = /* @__PURE__ */ new Map();
251887
252195
  const compiling = /* @__PURE__ */ new Set();
251888
252196
  const compiledOf = (node, port5) => {
251889
252197
  const valueWires = (node.type === "Set Variable" || node.type === GLOBAL_STORE_SET) && valueWiresInto(node).length > 1;
251890
- const plainKey = `${node.id}:${port5}${valueWires ? `@value:${lastValueWireOf(node)?.key ?? "none"}` : ""}`;
252198
+ const basePlain = `${node.id}:${port5}${valueWires ? `@value:${lastValueWireOf(node)?.key ?? "none"}` : ""}`;
252199
+ const rowScope = rowChainScopes[rowChainScopes.length - 1] ?? null;
252200
+ const scopedKey = `${basePlain}@row:${rowScope ?? "none"}`;
252201
+ const scopes = relayTouchedKeys.get(basePlain);
252202
+ const key2 = scopes !== void 0 ? scopedKey : basePlain;
252203
+ relayTouches.push(/* @__PURE__ */ new Set());
252204
+ const result = compiledOfKeyed(node, port5, key2);
252205
+ const touched = relayTouches.pop();
252206
+ if (touched.size > 0) {
252207
+ relayTouchedKeys.set(basePlain, /* @__PURE__ */ new Set([...scopes ?? [], rowScope]));
252208
+ if (key2 === basePlain) compiledSinks.set(scopedKey, result);
252209
+ }
252210
+ if (touched.size > 0 || relayTouchedKeys.has(basePlain)) relayTouches[relayTouches.length - 1]?.add(rowScope ?? "none");
252211
+ return result;
252212
+ };
252213
+ const compiledOfKeyed = (node, port5, plainKey) => {
251891
252214
  const scoped = [...verbChainScope].filter(([, s]) => s === "failure").map(([id]) => id);
251892
252215
  const scopeKey = scoped.length > 0 ? `${plainKey}@failure:${[...scoped].sort().join(",")}` : void 0;
251893
252216
  const readsScoped = (result2, form) => {
@@ -251962,6 +252285,9 @@ ${body}
251962
252285
  return exprValidIn(expr.source, context, invokedScope) && whereExprs(expr.where).every((e) => exprValidIn(e, context, invokedScope));
251963
252286
  case "input-text":
251964
252287
  return context.kind === "dom" && context.nodeId === expr.inputId;
252288
+ // EXP-011 §100. Only in that repeater's row callbacks, which close over the firing row's item.
252289
+ case "row-relay":
252290
+ return context.kind === "dom" && context.nodeId === expr.repeaterId && (context.port?.startsWith(ITEM_OUTPUT_SIGNAL) ?? false);
251965
252291
  case "control-event":
251966
252292
  return context.kind === "dom" && context.nodeId === expr.controlId;
251967
252293
  case "payload":
@@ -252077,7 +252403,9 @@ ${body}
252077
252403
  */
252078
252404
  case "collection-remove":
252079
252405
  return context.kind === "dom" && context.nodeId === action.repeaterId && context.port !== void 0 && context.port.startsWith(ITEM_OUTPUT_SIGNAL) && actionsValidIn(action.then, context, invokedScope);
252406
+ // P18-S127-SETDONE. The value, then the Done chain.
252080
252407
  case "store-set":
252408
+ return exprValidIn(action.expr, context, invokedScope) && actionsValidIn(action.then ?? [], context, invokedScope);
252081
252409
  case "globalstore-set":
252082
252410
  return exprValidIn(action.expr, context, invokedScope);
252083
252411
  // EXP-011 §47. Every value in the patch, then the chain.
@@ -252210,6 +252538,9 @@ ${body}
252210
252538
  // EXP-011 §63. A call on the handle; the Done listener is validated where it compiles (the render context).
252211
252539
  case "drag-snap":
252212
252540
  return true;
252541
+ // P18-S128-FIELDSET. A call on the field's element, reading nothing.
252542
+ case "field-focus":
252543
+ return true;
252213
252544
  // EXP-011 §52. A call on the handle; the node's listeners are validated where they compile.
252214
252545
  case "script-signal":
252215
252546
  return true;
@@ -252337,10 +252668,14 @@ ${body}
252337
252668
  const wires = component.connections.filter((c) => c.toId === node.id && c.toProperty === spec.statePort);
252338
252669
  if (wires.length === 0) continue;
252339
252670
  if (spec.coerce === "textinput" && literalParam(node, "runOnChange-startValue") === false) {
252671
+ const viaSet = wiredPorts.has(`${node.id}:set`);
252340
252672
  for (const w of wires) {
252341
252673
  consumed.add(w.key);
252342
252674
  notes.push(
252343
- wireNote(w, `Value is unticked under Run On Value Change \u2014 arrivals wait for a Set pulse, which is not translated in this slice`)
252675
+ wireNote(
252676
+ w,
252677
+ viaSet ? "Value is unticked under Run On Value Change \u2014 it reaches the field only when its Set is sent (P18-S128-FIELDSET)" : "Value is unticked under Run On Value Change \u2014 arrivals wait for a Set pulse, and nothing sends this field one"
252678
+ )
252344
252679
  );
252345
252680
  }
252346
252681
  continue;
@@ -252410,6 +252745,37 @@ ${body}
252410
252745
  }
252411
252746
  }
252412
252747
  }
252748
+ const rowRelays = /* @__PURE__ */ new Map();
252749
+ const relayedSignalsOf = (forEach) => [
252750
+ ...new Set(
252751
+ component.connections.filter((c) => c.fromId === forEach.id && c.fromProperty.startsWith(ITEM_OUTPUT_SIGNAL)).map((c) => c.fromProperty.slice(ITEM_OUTPUT_SIGNAL.length))
252752
+ )
252753
+ ].join(", ") || "relayed signal";
252754
+ const rowRelayOf = (forEach, fromProperty) => {
252755
+ if (!fromProperty.startsWith(ITEM_OUTPUT_VALUE)) {
252756
+ return { defer: `a repeater's "${fromProperty}" is not a row's relayed value \u2014 those register as "${ITEM_OUTPUT_VALUE}<name>"` };
252757
+ }
252758
+ const output = fromProperty.slice(ITEM_OUTPUT_VALUE.length);
252759
+ const key2 = `${forEach.id}\0${output}`;
252760
+ const known = rowRelays.get(key2);
252761
+ if (known !== void 0) return { stateName: known.name };
252762
+ const relayed = relayedInputOf(forEach, output);
252763
+ if ("defer" in relayed) return relayed;
252764
+ const { input, declared } = relayed;
252765
+ const tsType = declared === "unknown" || declared === "any" ? declared : `${declared} | undefined`;
252766
+ const stateVar = allocStateVar(
252767
+ `relayed ${output}`,
252768
+ "relayed",
252769
+ tsType,
252770
+ null,
252771
+ forEach.id,
252772
+ "relay",
252773
+ `What the row whose "${relayedSignalsOf(forEach)}" fired last holds on its "${output}" \u2014 undefined until one fires (foreach.tsx itemOutputSignalTriggered, EXP-011 \xA799).`
252774
+ );
252775
+ rowRelays.set(key2, stateVar);
252776
+ plan.rowRelays.push({ repeaterId: forEach.id, output, input, stateName: stateVar.name });
252777
+ return { stateName: stateVar.name };
252778
+ };
252413
252779
  {
252414
252780
  const valuePropByPort = new Map(outputInterface.valueProps.map((v) => [v.port, v]));
252415
252781
  const wiresByPort = /* @__PURE__ */ new Map();
@@ -252434,15 +252800,17 @@ ${body}
252434
252800
  }
252435
252801
  const wire = wires[0];
252436
252802
  const from = nodeById.get(wire.fromId);
252803
+ let relayExpr;
252437
252804
  if (from?.type === "For Each") {
252438
- fail2(
252439
- wire.key,
252440
- `a repeater relays its rows' outputs into "${port5}" \u2014 which row fired is not statically expressible in this slice`
252441
- );
252442
- continue;
252805
+ const relay = rowRelayOf(from, wire.fromProperty);
252806
+ if ("defer" in relay) {
252807
+ fail2(wire.key, relay.defer);
252808
+ continue;
252809
+ }
252810
+ relayExpr = { kind: "state-get", name: relay.stateName };
252443
252811
  }
252444
252812
  const ctx = newCtx();
252445
- const expr = from === void 0 ? null : resolveExpr(from, wire.fromProperty, ctx);
252813
+ const expr = relayExpr ?? (from === void 0 ? null : resolveExpr(from, wire.fromProperty, ctx));
252446
252814
  if (expr === null) {
252447
252815
  fail2(
252448
252816
  wire.key,
@@ -252477,6 +252845,11 @@ ${body}
252477
252845
  }
252478
252846
  }
252479
252847
  }
252848
+ const siblingValueInputOf = (toNode, port5) => {
252849
+ if (plan.roleOf[toNode.id] !== "instance") return false;
252850
+ const target = ir.components.find((tc) => `/${tc.path}` === toNode.type);
252851
+ return target !== void 0 && target.nodes.some((n) => n.type === "Component Inputs" && n.declaredPorts.some((p) => p.plug === "output" && p.name === port5 && p.kind !== "signal"));
252852
+ };
252480
252853
  for (const c of component.connections) {
252481
252854
  if (consumed.has(c.key)) continue;
252482
252855
  const fromNode = nodeById.get(c.fromId);
@@ -252496,7 +252869,7 @@ ${body}
252496
252869
  const contentRole = (CONTENT_PARAMS[toNode.type] ?? {})[c.toProperty];
252497
252870
  const truthinessSink = c.toProperty === "visible" || c.toProperty === "mounted";
252498
252871
  const bindable = truthinessSink || contentRole === "children" || contentRole === "attr-not:disabled" || contentRole !== void 0 && contentRole.startsWith("attr:") || // EXP-011 §49. A wired style parameter the emitter prints inline.
252499
- styleSinkOf(toNode, c.toProperty) !== void 0;
252872
+ styleSinkOf(toNode, c.toProperty) !== void 0 || siblingValueInputOf(toNode, c.toProperty);
252500
252873
  if (!bindable) {
252501
252874
  consumed.add(c.key);
252502
252875
  notes.push(
@@ -252504,7 +252877,7 @@ ${body}
252504
252877
  );
252505
252878
  continue;
252506
252879
  }
252507
- if (c.toProperty === "mounted" && toNode.id === plan.rootId) {
252880
+ if (routerMounted(toNode, c.toProperty)) {
252508
252881
  consumed.add(c.key);
252509
252882
  notes.push(wireNote(c, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
252510
252883
  continue;
@@ -252516,7 +252889,8 @@ ${body}
252516
252889
  targetLegacy: fromNode.type,
252517
252890
  port: c.fromProperty,
252518
252891
  toNodeId: toNode.id,
252519
- toProperty: c.toProperty
252892
+ toProperty: c.toProperty,
252893
+ ...siblingValueInputOf(toNode, c.toProperty) ? { intoInstance: true } : {}
252520
252894
  });
252521
252895
  }
252522
252896
  const customLiftedVarByKey = /* @__PURE__ */ new Map();
@@ -252549,7 +252923,7 @@ ${body}
252549
252923
  notes.push(wireNote(c, `custom node output "${c.fromProperty}" ${bindable}`));
252550
252924
  continue;
252551
252925
  }
252552
- if (c.toProperty === "mounted" && toNode.id === plan.rootId) {
252926
+ if (routerMounted(toNode, c.toProperty)) {
252553
252927
  consumed.add(c.key);
252554
252928
  notes.push(wireNote(c, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
252555
252929
  continue;
@@ -252785,13 +253159,22 @@ ${body}
252785
253159
  if (action.expr !== void 0) {
252786
253160
  const e = snapExpr(action.expr, snap2);
252787
253161
  if ("defer" in e) return e;
252788
- snap2.set(action.name, e);
253162
+ snap2.set(action.name, action.field?.port === "set" ? "op" : e);
252789
253163
  return { ...action, expr: e };
252790
253164
  }
252791
253165
  snap2.set(action.name, "op");
252792
253166
  return action;
252793
253167
  }
252794
- case "store-set":
253168
+ // P18-S127-SETDONE. The value is read where the handler is; the Done chain follows, one arm always taken, so it
253169
+ // carries the map onward (an Object's Set's treatment).
253170
+ case "store-set": {
253171
+ const e = snapExpr(action.expr, snap2);
253172
+ if ("defer" in e) return e;
253173
+ if (action.then === void 0) return { ...action, expr: e };
253174
+ const then2 = snapActionList(action.then, snap2);
253175
+ if (!Array.isArray(then2)) return then2;
253176
+ return { ...action, expr: e, then: then2 };
253177
+ }
252795
253178
  case "globalstore-set": {
252796
253179
  const e = snapExpr(action.expr, snap2);
252797
253180
  if ("defer" in e) return e;
@@ -253409,6 +253792,7 @@ ${body}
253409
253792
  }
253410
253793
  }
253411
253794
  const receiverActions = /* @__PURE__ */ new Map();
253795
+ const inputPulseActions = /* @__PURE__ */ new Map();
253412
253796
  const unreachedOwnerOf = (connection) => excluded.owners.get(connection.fromId) ?? (connection.fromProperty === "didMount" ? excluded.mounts.get(connection.fromId) : void 0);
253413
253797
  const refuseUnreached = (connection, toNode, compiled2, reason2, outputsSink) => {
253414
253798
  notes.push(wireNote(connection, reason2));
@@ -253419,6 +253803,11 @@ ${body}
253419
253803
  if (dispositions[id] === void 0) dispositions[id] = { kind: "deferred", to: "EXP-003", reason: reason2 };
253420
253804
  }
253421
253805
  };
253806
+ const instanceBeforeAttach = /* @__PURE__ */ new Map();
253807
+ const isRunSuccessWire = (connection) => connection.fromProperty === "success" && nodeById.get(connection.fromId)?.type === JS_FUNCTION && wiredPorts.has(`${connection.fromId}:run`);
253808
+ for (const node of component.nodes) {
253809
+ if (plan.roleOf[node.id] === "instance" || plan.roleOf[node.id] === "input") instanceBeforeAttach.set(node.id, dispositions[node.id]);
253810
+ }
253422
253811
  for (const connection of component.connections) {
253423
253812
  if (consumed.has(connection.key)) continue;
253424
253813
  const toNode = nodeById.get(connection.toId);
@@ -253438,6 +253827,7 @@ ${body}
253438
253827
  if (fromNode !== void 0 && jsNodeKindOf(fromNode.type) !== null && connection.fromProperty === "done") {
253439
253828
  continue;
253440
253829
  }
253830
+ if (isRunSuccessWire(connection)) continue;
253441
253831
  consumed.add(connection.key);
253442
253832
  if (fromNode?.type === "For Each" && connection.fromProperty.startsWith(ITEM_OUTPUT_SIGNAL)) {
253443
253833
  const relayed = connection.fromProperty.slice(ITEM_OUTPUT_SIGNAL.length);
@@ -253462,7 +253852,13 @@ ${body}
253462
253852
  }
253463
253853
  compiled2 = sink;
253464
253854
  } else {
253465
- compiled2 = compiledOf(toNode, connection.toProperty);
253855
+ const rowScope = fromNode?.type === "For Each" && connection.fromProperty.startsWith(ITEM_OUTPUT_SIGNAL) ? fromNode.id : void 0;
253856
+ if (rowScope !== void 0) rowChainScopes.push(rowScope);
253857
+ try {
253858
+ compiled2 = compiledOf(toNode, connection.toProperty);
253859
+ } finally {
253860
+ if (rowScope !== void 0) rowChainScopes.pop();
253861
+ }
253466
253862
  }
253467
253863
  if ("defer" in compiled2) {
253468
253864
  if (outputsSink) {
@@ -253555,6 +253951,40 @@ ${body}
253555
253951
  for (const id of compiled2.subscribes ?? []) boundSubscribers.add(id);
253556
253952
  continue;
253557
253953
  }
253954
+ if (fromNode?.type === "Component Inputs" && signalInputPortsOf(component).has(connection.fromProperty)) {
253955
+ const refuse = (reason2) => {
253956
+ if (outputsSink) {
253957
+ if (!failedOutputsNodes.has(toNode.id)) failedOutputsNodes.set(toNode.id, reason2);
253958
+ } else {
253959
+ dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: reason2 };
253960
+ }
253961
+ notes.push(wireNote(connection, reason2));
253962
+ };
253963
+ if (!plan.file) {
253964
+ refuse("the component emits no file to host the effect a signal input runs");
253965
+ continue;
253966
+ }
253967
+ if (!actionsValidIn([compiled2.action], { kind: "render" })) {
253968
+ refuse("the action reads values that only exist inside a handler");
253969
+ continue;
253970
+ }
253971
+ const snappedIn = snapAction(compiled2.action, chainSnapshotFor(`input:${connection.fromProperty}`));
253972
+ const lateInWrite = "defer" in snappedIn ? void 0 : lateQueryFetchWrite([...inputPulseActions.get(connection.fromProperty) ?? [], snappedIn]);
253973
+ if (lateInWrite !== void 0) {
253974
+ refuse(lateInWrite);
253975
+ continue;
253976
+ }
253977
+ if ("defer" in snappedIn) {
253978
+ refuse(snappedIn.defer);
253979
+ continue;
253980
+ }
253981
+ inputPulseActions.set(connection.fromProperty, [...inputPulseActions.get(connection.fromProperty) ?? [], snappedIn]);
253982
+ if (!outputsSink) dispositions[toNode.id] = { kind: "collapsed", into: fromNode.id };
253983
+ for (const id of compiled2.collapses ?? []) dispositions[id] = { kind: "collapsed", into: fromNode.id };
253984
+ for (const key2 of compiled2.consumes) consumed.add(key2);
253985
+ for (const id of compiled2.subscribes ?? []) boundSubscribers.add(id);
253986
+ continue;
253987
+ }
253558
253988
  if (fromNode?.type === "Event Receiver" && connection.fromProperty === "eventReceived") {
253559
253989
  const eligible = receiverEligible(fromNode);
253560
253990
  if ("defer" in eligible) {
@@ -253605,6 +254035,79 @@ ${body}
253605
254035
  }
253606
254036
  notes.push(wireNote(connection, pulseRefusal ?? `the trigger is not a rendered element event or a receiver`));
253607
254037
  }
254038
+ const inSenderRowScope = (senderId, port5, fn) => {
254039
+ const scopes = [...relayTouchedKeys].find(([key2]) => key2 === `${senderId}:${port5}` || key2.startsWith(`${senderId}:${port5}@`))?.[1];
254040
+ const scope = [...scopes ?? []].find((s) => s !== null) ?? null;
254041
+ if (scope !== null) rowChainScopes.push(scope);
254042
+ try {
254043
+ return fn();
254044
+ } finally {
254045
+ if (scope !== null) rowChainScopes.pop();
254046
+ }
254047
+ };
254048
+ for (const connection of component.connections) {
254049
+ if (consumed.has(connection.key)) continue;
254050
+ const fromNode = nodeById.get(connection.fromId);
254051
+ if (fromNode?.type !== "Set Variable" || !SET_VARIABLE_CHAIN_OUTPUTS.includes(connection.fromProperty)) continue;
254052
+ const toNode = nodeById.get(connection.toId);
254053
+ if (!toNode) continue;
254054
+ const outputsSink = toNode.type === "Component Outputs";
254055
+ if (!outputsSink && !isTriggerInto(toNode, connection.toProperty)) continue;
254056
+ consumed.add(connection.key);
254057
+ const sink = outputsSink ? outputSinkOf(connection.toProperty, fromNode, connection.fromProperty) : inSenderRowScope(fromNode.id, "do", () => compiledOf(toNode, connection.toProperty));
254058
+ if ("drop" in sink) {
254059
+ notes.push(wireNote(connection, sink.drop));
254060
+ continue;
254061
+ }
254062
+ if ("defer" in sink) {
254063
+ if (outputsSink) {
254064
+ if (!failedOutputsNodes.has(toNode.id)) failedOutputsNodes.set(toNode.id, sink.defer);
254065
+ } else if (dispositions[toNode.id]?.kind !== "collapsed" && dispositions[toNode.id]?.kind !== "static") {
254066
+ dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: sink.defer };
254067
+ }
254068
+ notes.push(wireNote(connection, sink.defer));
254069
+ continue;
254070
+ }
254071
+ const trigger = `trigger ${connection.fromId}.${connection.fromProperty}`;
254072
+ const chainRefusal = setDoneRefusals.get(`${connection.fromId}:${connection.fromProperty}`);
254073
+ const own2 = dispositions[fromNode.id];
254074
+ const compiled2 = [...compiledSinks].find(([key2]) => key2 === `${fromNode.id}:do` || key2.startsWith(`${fromNode.id}:do@`))?.[1];
254075
+ const reason2 = chainRefusal !== void 0 ? `${trigger}: the Set Variable's ${connection.fromProperty === "done" ? "Done" : "Completed"} chain is not exported \u2014 ${chainRefusal}` : own2?.kind === "deferred" ? `${trigger}: the Set Variable that sends it is not exported \u2014 ${own2.reason}` : compiled2 !== void 0 && "defer" in compiled2 ? `${trigger}: the Set Variable that sends it is not exported \u2014 ${compiled2.defer}` : `${trigger}: the Set Variable that sends it is never run by an exported trigger`;
254076
+ if (outputsSink) {
254077
+ if (!failedOutputsNodes.has(toNode.id)) failedOutputsNodes.set(toNode.id, reason2);
254078
+ } else if (dispositions[toNode.id] === void 0) {
254079
+ dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: reason2 };
254080
+ }
254081
+ notes.push(wireNote(connection, reason2));
254082
+ }
254083
+ for (const connection of component.connections) {
254084
+ if (consumed.has(connection.key) || !isRunSuccessWire(connection)) continue;
254085
+ const fromNode = nodeById.get(connection.fromId);
254086
+ const toNode = nodeById.get(connection.toId);
254087
+ if (!toNode) continue;
254088
+ const outputsSink = toNode.type === "Component Outputs";
254089
+ if (!outputsSink && !isTriggerInto(toNode, connection.toProperty)) continue;
254090
+ consumed.add(connection.key);
254091
+ const sink = outputsSink ? outputSinkOf(connection.toProperty, fromNode, connection.fromProperty) : inSenderRowScope(fromNode.id, "run", () => compiledOf(toNode, connection.toProperty));
254092
+ if ("drop" in sink) {
254093
+ notes.push(wireNote(connection, sink.drop));
254094
+ continue;
254095
+ }
254096
+ const run6 = compiledSinks.get(`${fromNode.id}:run`);
254097
+ const trigger = `trigger ${connection.fromId}.success`;
254098
+ const reason2 = "defer" in sink ? sink.defer : run6 !== void 0 && "defer" in run6 ? `${trigger}: the Function that sends it is not exported \u2014 ${run6.defer}` : `${trigger}: the Function that sends it is never run by an exported trigger`;
254099
+ if (outputsSink) {
254100
+ if (!failedOutputsNodes.has(toNode.id)) failedOutputsNodes.set(toNode.id, reason2);
254101
+ } else if (dispositions[toNode.id] === void 0 || "defer" in sink && dispositions[toNode.id]?.kind !== "collapsed" && dispositions[toNode.id]?.kind !== "static") {
254102
+ dispositions[toNode.id] = { kind: "deferred", to: "EXP-003", reason: reason2 };
254103
+ }
254104
+ notes.push(wireNote(connection, reason2));
254105
+ }
254106
+ for (const [id, before] of instanceBeforeAttach) {
254107
+ if (dispositions[id]?.kind !== "deferred" || before?.kind === "deferred") continue;
254108
+ if (before === void 0) delete dispositions[id];
254109
+ else dispositions[id] = before;
254110
+ }
253608
254111
  for (const node of component.nodes) {
253609
254112
  if (node.type !== "Component Outputs" || dispositions[node.id] !== void 0) continue;
253610
254113
  const failure3 = failedOutputsNodes.get(node.id);
@@ -253639,6 +254142,7 @@ ${body}
253639
254142
  plan.receivers.push({ nodeId: node.id, channelName: channelNameOf(node), actions });
253640
254143
  dispositions[node.id] = { kind: "collapsed", into: `src/${plan.file.dir}/${plan.file.fileBase}.tsx` };
253641
254144
  }
254145
+ for (const [port5, actions] of inputPulseActions) plan.inputPulses.push({ port: port5, actions });
253642
254146
  {
253643
254147
  const attachedSlotKeys = /* @__PURE__ */ new Set();
253644
254148
  const attachedMutations = attachedRecordVerbs;
@@ -253711,6 +254215,8 @@ ${body}
253711
254215
  scanActions(action.failThen);
253712
254216
  } else if (action.kind === "log") {
253713
254217
  scanActions(action.then);
254218
+ } else if (action.kind === "store-set") {
254219
+ scanActions(action.then ?? []);
253714
254220
  } else if (action.kind === "delay") {
253715
254221
  scanActions(action.then);
253716
254222
  scanActions(action.unchangedThen);
@@ -253742,6 +254248,7 @@ ${body}
253742
254248
  for (const byPort of Object.values(plan.handlers)) for (const actions of Object.values(byPort)) scanActions(actions);
253743
254249
  for (const actions of Object.values(plan.changeHandlers)) scanActions(actions);
253744
254250
  for (const receiver of plan.receivers) scanActions(receiver.actions);
254251
+ for (const pulse of plan.inputPulses) scanActions(pulse.actions);
253745
254252
  for (const run6 of plan.runTasks) for (const chain of Object.values(run6.listeners)) if (chain !== void 0) scanActions(chain);
253746
254253
  if (plan.task !== void 0) scanActions(plan.task.actions);
253747
254254
  for (const item of plan.repeaterItems) scanActions(item.actions);
@@ -254097,7 +254604,7 @@ ${body}
254097
254604
  notes.push(wireNote(connection, VARIABLE_FROZEN));
254098
254605
  continue;
254099
254606
  }
254100
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) {
254607
+ if (routerMounted(toNode, connection.toProperty)) {
254101
254608
  notes.push(wireNote(connection, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
254102
254609
  continue;
254103
254610
  }
@@ -254117,7 +254624,7 @@ ${body}
254117
254624
  notes.push(wireNote(connection, `${read2.defer}`));
254118
254625
  continue;
254119
254626
  }
254120
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) {
254627
+ if (routerMounted(toNode, connection.toProperty)) {
254121
254628
  notes.push(wireNote(connection, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
254122
254629
  continue;
254123
254630
  }
@@ -254188,7 +254695,7 @@ ${body}
254188
254695
  );
254189
254696
  continue;
254190
254697
  }
254191
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) {
254698
+ if (routerMounted(toNode, connection.toProperty)) {
254192
254699
  notes.push(wireNote(connection, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
254193
254700
  continue;
254194
254701
  }
@@ -254214,7 +254721,7 @@ ${body}
254214
254721
  const bindable = truthinessSink || contentRole === "children" || contentRole === "attr-not:disabled" || contentRole !== void 0 && contentRole.startsWith("attr:") || // EXP-011 §49. A wired style parameter the emitter prints inline.
254215
254722
  styleSinkOf(toNode, connection.toProperty) !== void 0;
254216
254723
  if (!bindable) continue;
254217
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) continue;
254724
+ if (routerMounted(toNode, connection.toProperty)) continue;
254218
254725
  const ctx = newCtx();
254219
254726
  const expr = resolveExpr(fromNode, connection.fromProperty, ctx);
254220
254727
  if (expr === null) continue;
@@ -254251,7 +254758,7 @@ ${body}
254251
254758
  const bindable = truthinessSink || contentRole === "children" || contentRole === "attr-not:disabled" || contentRole !== void 0 && contentRole.startsWith("attr:") || // EXP-011 §49. A wired style parameter the emitter prints inline.
254252
254759
  styleSinkOf(toNode, connection.toProperty) !== void 0;
254253
254760
  if (!bindable) continue;
254254
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) continue;
254761
+ if (routerMounted(toNode, connection.toProperty)) continue;
254255
254762
  const deadRow = excluded.rows.get(fromNode.id);
254256
254763
  if (deadRow !== void 0) {
254257
254764
  consumed.add(connection.key);
@@ -254325,7 +254832,7 @@ ${body}
254325
254832
  );
254326
254833
  continue;
254327
254834
  }
254328
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) {
254835
+ if (routerMounted(toNode, connection.toProperty)) {
254329
254836
  consumed.add(connection.key);
254330
254837
  notes.push(wireNote(connection, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
254331
254838
  continue;
@@ -254364,7 +254871,7 @@ ${body}
254364
254871
  const bindable = truthinessSink || contentRole === "children" || contentRole === "attr-not:disabled" || contentRole !== void 0 && contentRole.startsWith("attr:") || // EXP-011 §49. A wired style parameter the emitter prints inline.
254365
254872
  styleSinkOf(toNode, connection.toProperty) !== void 0;
254366
254873
  if (!bindable) continue;
254367
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) continue;
254874
+ if (routerMounted(toNode, connection.toProperty)) continue;
254368
254875
  const objectStore = objectStoreNodes.get(fromNode.id);
254369
254876
  if (objectStore !== void 0) {
254370
254877
  consumed.add(connection.key);
@@ -254399,7 +254906,7 @@ ${body}
254399
254906
  const bindable = truthinessSink || contentRole === "children" || contentRole === "attr-not:disabled" || contentRole !== void 0 && contentRole.startsWith("attr:") || // EXP-011 §49. A wired style parameter the emitter prints inline.
254400
254907
  styleSinkOf(toNode, connection.toProperty) !== void 0;
254401
254908
  if (!bindable) continue;
254402
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) continue;
254909
+ if (routerMounted(toNode, connection.toProperty)) continue;
254403
254910
  const ctx = newCtx();
254404
254911
  const expr = resolveExpr(fromNode, connection.fromProperty, ctx);
254405
254912
  if (expr === null) {
@@ -254453,7 +254960,7 @@ ${body}
254453
254960
  continue;
254454
254961
  }
254455
254962
  if (fromNode?.type === "Component Inputs" && toNode && rendered2.has(toNode.id)) {
254456
- if (connection.toProperty === "mounted" && toNode.id === plan.rootId) {
254963
+ if (routerMounted(toNode, connection.toProperty)) {
254457
254964
  consumed.add(connection.key);
254458
254965
  notes.push(wireNote(connection, `a mounted wire into the component root is a router concern \u2014 not translated in this slice`));
254459
254966
  continue;
@@ -254893,6 +255400,9 @@ ${body}
254893
255400
  // EXP-011 §63. Nothing to read; the options and listeners are walked off the plan below.
254894
255401
  case "drag-snap":
254895
255402
  break;
255403
+ // P18-S128-FIELDSET. Nothing to read.
255404
+ case "field-focus":
255405
+ break;
254896
255406
  // EXP-011 §52. The same: the inputs and listeners are walked off the plan below.
254897
255407
  case "script-signal":
254898
255408
  break;
@@ -254933,6 +255443,10 @@ ${body}
254933
255443
  case "state-set":
254934
255444
  if (action.expr !== void 0) walkExpr(action.expr);
254935
255445
  break;
255446
+ // P18-S127-SETDONE. A Set Variable's Done chain (its own value is not walked here, as before).
255447
+ case "store-set":
255448
+ walkActions(action.then ?? []);
255449
+ break;
254936
255450
  default:
254937
255451
  break;
254938
255452
  }
@@ -254944,6 +255458,7 @@ ${body}
254944
255458
  for (const byPort of Object.values(plan.handlers)) for (const actions of Object.values(byPort)) walkActions(actions);
254945
255459
  for (const actions of Object.values(plan.changeHandlers)) walkActions(actions);
254946
255460
  for (const receiver of plan.receivers) walkActions(receiver.actions);
255461
+ for (const pulse of plan.inputPulses) walkActions(pulse.actions);
254947
255462
  for (const effect of plan.branchEffects) walkActions([effect.action]);
254948
255463
  for (const effect of plan.valueChangedEffects) {
254949
255464
  walkExpr(effect.watch);
@@ -255007,6 +255522,23 @@ ${body}
255007
255522
  for (const id of wiring.collapses) dispositions[id] = { kind: "collapsed", into };
255008
255523
  for (const id of wiring.subscribes) boundSubscribers.add(id);
255009
255524
  }
255525
+ for (const relay of [...plan.rowRelays]) {
255526
+ const repeater = plan.repeaters[relay.repeaterId];
255527
+ const fed = repeater !== void 0 && (plan.queries.some((q) => q.nodeId === repeater.itemsQueryId) || repeater.itemsCollectionName != null || repeater.itemsExpr !== void 0 || plan.staticData.some((sd) => sd.nodeId === repeater.itemsStaticId));
255528
+ if (fed) continue;
255529
+ const reason2 = `the repeater relaying "${relay.output}" draws no rows in the export (its items are not fed by a query, a named array, or a statically-known list), so no row ever relays it`;
255530
+ plan.rowRelays.splice(plan.rowRelays.indexOf(relay), 1);
255531
+ plan.stateVars = plan.stateVars.filter((v) => v.name !== relay.stateName);
255532
+ plan.pushEffects = plan.pushEffects.filter((e) => !(e.expr.kind === "state-get" && e.expr.name === relay.stateName));
255533
+ for (const wire of component.connections) {
255534
+ if (wire.fromId !== relay.repeaterId || wire.fromProperty !== `${ITEM_OUTPUT_VALUE}${relay.output}`) continue;
255535
+ const outputs2 = nodeById.get(wire.toId);
255536
+ if (outputs2?.type !== "Component Outputs") continue;
255537
+ plan.liftedOutputProps = plan.liftedOutputProps.filter((l) => l.port !== wire.toProperty);
255538
+ notes.push(wireNote(wire, reason2));
255539
+ if (dispositions[outputs2.id]?.kind !== "deferred") dispositions[outputs2.id] = { kind: "deferred", to: "EXP-003", reason: reason2 };
255540
+ }
255541
+ }
255010
255542
  for (const connection of component.connections) {
255011
255543
  if (consumed.has(connection.key)) continue;
255012
255544
  const kitEndpoint = undeclaredKitPortOf(connection, nodeById, kits);
@@ -255046,17 +255578,19 @@ ${body}
255046
255578
  const label2 = node.authoredLabel ? `"${node.authoredLabel}"` : "The text input";
255047
255579
  const refuse = (why) => notes.push(`node ${node.id} (${node.type}): its Value at mount is not announced \u2014 ${why}; typing still runs its chain (P18-S116-FIELDMOUNT)`);
255048
255580
  let away;
255581
+ const gates = [];
255049
255582
  for (let id = node.id; id !== void 0; id = parentOf.get(id)) {
255050
255583
  const bound = plan.bindings[id]?.["mounted"];
255051
255584
  if (bound !== void 0) {
255052
255585
  const literal2 = bound.kind === "computed" && (bound.expr.kind === "literal" || bound.expr.kind === "undefined");
255053
- if (!literal2) away = away ?? "later";
255586
+ if (!literal2 && id === plan.collapsedGroupId) away = "uncarried";
255587
+ else if (!literal2) gates.push(id);
255054
255588
  else if (bound.expr.kind === "undefined" || !bound.expr.value) away = "never";
255055
255589
  } else if (literalParam(nodeById.get(id), "mounted") === false) away = "never";
255056
255590
  }
255057
255591
  if (away === "never") continue;
255058
- if (away === "later") {
255059
- refuse("it or a group it sits in mounts from a wire, so it announces when that wire first says true, not as the component mounts");
255592
+ if (away === "uncarried") {
255593
+ refuse("it sits in the page's own Group, whose Mounted is wired and not carried (the page is drawn), so when it appears is not known here");
255060
255594
  continue;
255061
255595
  }
255062
255596
  const type = literalParam(node, "type");
@@ -255080,7 +255614,8 @@ ${body}
255080
255614
  inputId: node.id,
255081
255615
  start: raw === void 0 ? "" : String(raw),
255082
255616
  ...sync !== void 0 ? { source: sync.source } : {},
255083
- comment: `${label2} announces its Value as it mounts, and its chain runs with it (TextInput.tsx componentDidMount \u2192 setText(startValue)).`
255617
+ ...gates.length > 0 ? { gates } : {},
255618
+ comment: gates.length > 0 ? `${label2} announces its Value the first time it mounts, and its chain runs with it (TextInput.tsx componentDidMount \u2192 setText(startValue)); it mounts when a value says.` : `${label2} announces its Value as it mounts, and its chain runs with it (TextInput.tsx componentDidMount \u2192 setText(startValue)).`
255084
255619
  });
255085
255620
  }
255086
255621
  }
@@ -255340,6 +255875,8 @@ ${body}
255340
255875
  // EXP-011 §63. Nothing nested; the listeners are filled off the plan below.
255341
255876
  case "drag-snap":
255342
255877
  break;
255878
+ case "field-focus":
255879
+ break;
255343
255880
  case "script-signal":
255344
255881
  break;
255345
255882
  // EXP-011 §53. Nothing nested on the action; the listeners are filled off the plan below.
@@ -255467,6 +256004,10 @@ ${body}
255467
256004
  fillMaterialize(action.then);
255468
256005
  fillMaterialize(action.failThen);
255469
256006
  break;
256007
+ // P18-S127-SETDONE. A Set Variable's Done chain.
256008
+ case "store-set":
256009
+ fillMaterialize(action.then ?? []);
256010
+ break;
255470
256011
  default:
255471
256012
  break;
255472
256013
  }
@@ -255475,6 +256016,7 @@ ${body}
255475
256016
  for (const byPort of Object.values(plan.handlers)) for (const actions of Object.values(byPort)) fillMaterialize(actions);
255476
256017
  for (const actions of Object.values(plan.changeHandlers)) fillMaterialize(actions);
255477
256018
  for (const receiver of plan.receivers) fillMaterialize(receiver.actions);
256019
+ for (const pulse of plan.inputPulses) fillMaterialize(pulse.actions);
255478
256020
  for (const effect of plan.branchEffects) fillMaterialize([effect.action]);
255479
256021
  for (const effect of plan.valueChangedEffects) fillMaterialize(effect.actions);
255480
256022
  for (const ticked3 of plan.tickedRuns) fillMaterialize([ticked3.action]);
@@ -255574,11 +256116,45 @@ ${body}
255574
256116
  if (node === void 0 || !component.connections.some((c) => c.toId === id && isTriggerInto(node, c.toProperty))) continue;
255575
256117
  dispositions[id] = { kind: "static" };
255576
256118
  }
256119
+ if (instancePulseVars.size > 0) {
256120
+ const pulsed = /* @__PURE__ */ new Set();
256121
+ const seen = /* @__PURE__ */ new Set();
256122
+ const visit = (value) => {
256123
+ if (value === null || typeof value !== "object" || seen.has(value)) return;
256124
+ seen.add(value);
256125
+ if (Array.isArray(value)) return value.forEach(visit);
256126
+ const v = value;
256127
+ if (v.kind === "state-set" && v.op === "inc" && typeof v.name === "string") pulsed.add(v.name);
256128
+ for (const child of Object.values(value)) visit(child);
256129
+ };
256130
+ for (const [key2, value] of Object.entries(plan)) if (key2 !== "stateVars" && key2 !== "bindings") visit(value);
256131
+ for (const [key2, stateVar] of instancePulseVars) {
256132
+ if (pulsed.has(stateVar.name)) continue;
256133
+ const [nodeId, port5] = key2.split("\0");
256134
+ plan.stateVars = plan.stateVars.filter((v) => v !== stateVar);
256135
+ plan.instancePulses = plan.instancePulses.filter((p) => p.stateName !== stateVar.name);
256136
+ if (plan.bindings[nodeId] !== void 0) delete plan.bindings[nodeId][port5];
256137
+ }
256138
+ }
255577
256139
  sweepRefusedScripts();
255578
256140
  sweepUnreportedDeferrals();
255579
256141
  collectRefusals();
255580
256142
  return plan;
255581
256143
  }
256144
+ var signalInputPortsCache = /* @__PURE__ */ new WeakMap();
256145
+ function signalInputPortsOf(component) {
256146
+ if (component === void 0) return /* @__PURE__ */ new Set();
256147
+ let ports = signalInputPortsCache.get(component);
256148
+ if (ports === void 0) {
256149
+ ports = /* @__PURE__ */ new Set();
256150
+ for (const node of component.nodes) {
256151
+ if (node.type !== "Component Inputs") continue;
256152
+ for (const port5 of node.declaredPorts) if (port5.plug === "output" && port5.kind === "signal") ports.add(port5.name);
256153
+ }
256154
+ signalInputPortsCache.set(component, ports);
256155
+ }
256156
+ return ports;
256157
+ }
255582
256158
  function componentOutputInterface(component) {
255583
256159
  const takenProps = /* @__PURE__ */ new Set();
255584
256160
  for (const node of component.nodes) {
@@ -255979,7 +256555,7 @@ function indexPageStacks(ir, catalog2, kits) {
255979
256555
  return index;
255980
256556
  }
255981
256557
  function tsTypeOf(portType2, kind) {
255982
- if (kind === "signal") return "() => void";
256558
+ if (kind === "signal") return INPUT_PULSE_TS_TYPE;
255983
256559
  switch (portType2) {
255984
256560
  case "boolean":
255985
256561
  return "boolean";
@@ -267978,6 +268554,23 @@ var GENERATED_TS3 = "// @nodegx:generated (visual \u2014 provenance markers comp
267978
268554
  var GENERATED_CSS2 = "/* @nodegx:generated (visual) */\n";
267979
268555
  var PRINT_WIDTH = 100;
267980
268556
  var SIMPLE_REF = /^[A-Za-z_$][A-Za-z0-9_$.]*(\(\))?$/;
268557
+ function negatedCode(cond) {
268558
+ if (cond.startsWith("!!")) {
268559
+ const rest = cond.slice(2);
268560
+ if (SIMPLE_REF.test(rest)) return `!${rest}`;
268561
+ if (rest.startsWith("(") && closingParenOf(rest) === rest.length - 1) return `!${rest}`;
268562
+ }
268563
+ if (SIMPLE_REF.test(cond) || cond.startsWith("(") && closingParenOf(cond) === cond.length - 1) return `!${cond}`;
268564
+ return `!(${cond})`;
268565
+ }
268566
+ function closingParenOf(code) {
268567
+ let depth = 0;
268568
+ for (let i = 0; i < code.length; i++) {
268569
+ if (code[i] === "(") depth++;
268570
+ else if (code[i] === ")" && --depth === 0) return i;
268571
+ }
268572
+ return -1;
268573
+ }
267981
268574
  var FILE_OUT_OPTIONAL_FIELDS2 = /* @__PURE__ */ new Set(["upload.contentType", "upload.size", "sign.expiresAt", "sign.ttlSeconds"]);
267982
268575
  var SESSION_LOCAL = "session";
267983
268576
  var PAGE_PARAMS_LOCAL = "pageParams";
@@ -268223,25 +268816,27 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
268223
268816
  const mountRefusals = /* @__PURE__ */ new Map();
268224
268817
  {
268225
268818
  const mountGateOf = (id) => {
268819
+ const gates = [];
268226
268820
  for (let at = id; at !== void 0; at = nodeById.get(at)?.parent) {
268227
268821
  const bound = plan.bindings[at]?.["mounted"];
268228
268822
  const literal2 = nodeById.get(at)?.parameters.find((p) => p.name === "mounted")?.value;
268229
268823
  const never2 = bound !== void 0 && bound.kind === "computed" && (bound.expr.kind === "undefined" || bound.expr.kind === "literal" && !bound.expr.value) || bound === void 0 && literal2?.kind === "literal" && literal2.value === false;
268230
268824
  const whose = at === id ? "it is" : `an element above it (${at}) is`;
268231
- if (never2) return `${whose} never mounted, so its Did Mount never fires`;
268232
- if (bound !== void 0 && !(bound.kind === "computed" && bound.expr.kind === "literal")) {
268233
- return `${whose} mounted by a value, so its Did Mount fires when that value says, and a mount effect runs when the component mounts`;
268825
+ if (never2) return { never: `${whose} never mounted, so its Did Mount never fires` };
268826
+ if (bound !== void 0 && !(bound.kind === "computed" && bound.expr.kind === "literal") && at === plan.collapsedGroupId) {
268827
+ return { never: `${UNCARRIED_PAGE_GROUP} ${at}, is mounted by a value the export does not carry (it draws the page), so when it mounts is not known here` };
268234
268828
  }
268829
+ if (bound !== void 0 && !(bound.kind === "computed" && bound.expr.kind === "literal")) gates.push(at);
268235
268830
  if (at === plan.rootId) break;
268236
268831
  }
268237
- return null;
268832
+ return { gates };
268238
268833
  };
268239
268834
  const consider = (id) => {
268240
268835
  const actions = plan.handlers[id]?.["didMount"];
268241
268836
  if (actions === void 0) return;
268242
- const why = mountGateOf(id);
268243
- if (why === null) mountChains.push({ nodeId: id, actions });
268244
- else mountRefusals.set(id, why);
268837
+ const gate = mountGateOf(id);
268838
+ if ("gates" in gate) mountChains.push({ nodeId: id, actions, gates: gate.gates });
268839
+ else mountRefusals.set(id, gate.never);
268245
268840
  };
268246
268841
  const visit = (id) => {
268247
268842
  for (const child of plan.childrenOf[id] ?? []) visit(child);
@@ -268333,6 +268928,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
268333
268928
  if (action.kind === "store-set") {
268334
268929
  usedVariableNames.add(action.variableName);
268335
268930
  collectExprUse(action.expr);
268931
+ (action.then ?? []).forEach(collectActionUse);
268336
268932
  }
268337
268933
  if (action.kind === "globalstore-set") {
268338
268934
  usedStoreNames.add(action.storeName);
@@ -268492,6 +269088,8 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
268492
269088
  ...Object.values(plan.handlers).flatMap((byPort) => Object.values(byPort).flat()),
268493
269089
  ...Object.values(plan.changeHandlers).flat(),
268494
269090
  ...plan.receivers.flatMap((r) => r.actions),
269091
+ // P18-S126-INSIGNAL. A signal input's chain earns imports and references exactly as a handler's does.
269092
+ ...plan.inputPulses.flatMap((p) => p.actions),
268495
269093
  // A reactive Condition's branch earns imports, `navigate` and state references exactly as a
268496
269094
  // handler's does — it just runs from an effect instead of an event (LOGIC-TARGET §10).
268497
269095
  ...plan.branchEffects.map((e) => e.action),
@@ -268557,7 +269155,10 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
268557
269155
  // two outcome arms and its minted Failure arm, an Insert's minted Failure arm, a Remove's Done.
268558
269156
  a8.kind === "collection-clear" ? [a8, ...deepActions(a8.then), ...deepActions(a8.unchangedThen), ...deepActions(a8.minted?.failThen ?? [])] : a8.kind === "collection-add" ? [a8, ...deepActions(a8.minted?.failThen ?? [])] : a8.kind === "log" || a8.kind === "popup-show" || a8.kind === "popup-close" || a8.kind === "jsfun-run" || // EXP-011 §86. The pulse loop and each signal's arm.
268559
269157
  a8.kind === "jsfun-pulses" || a8.kind === "jsfun-pulse" || a8.kind === "date-now-read" || a8.kind === "collection-remove" || a8.kind === "array-new" || // EXP-011 §61. A Reset's one chain.
268560
- a8.kind === "stack-reset" ? [a8, ...deepActions(a8.then)] : [a8]
269158
+ a8.kind === "stack-reset" ? [a8, ...deepActions(a8.then)] : (
269159
+ // P18-S127-SETDONE. A Set Variable's Done chain.
269160
+ a8.kind === "store-set" && a8.then !== void 0 ? [a8, ...deepActions(a8.then)] : [a8]
269161
+ )
268561
269162
  )
268562
269163
  )
268563
269164
  )
@@ -268719,6 +269320,7 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
268719
269320
  if (stateVar) referencedStateNames.add(stateVar.name);
268720
269321
  }
268721
269322
  }
269323
+ for (const pulse of plan.instancePulses) if (pulse.delivered !== void 0) referencedStateNames.add(pulse.delivered.name);
268722
269324
  const referencedStateVars = plan.stateVars.filter((v) => referencedStateNames.has(v.name));
268723
269325
  const liveRecordRows = plan.recordReads.flatMap((read2) => {
268724
269326
  const row = read2.live && !read2.binds ? referencedStateVars.find((v) => v.originNodeId === read2.nodeId && v.origin === "record-row") : void 0;
@@ -268928,6 +269530,9 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
268928
269530
  case "payload":
268929
269531
  return true;
268930
269532
  // payload keys are optional-typed
269533
+ case "row-relay":
269534
+ return true;
269535
+ // EXP-011 §100: the row's field, which a row's item may lack. Must agree with plan.ts maybeUndefinedExpr.
268931
269536
  case "undefined":
268932
269537
  return true;
268933
269538
  // a Component Object property's boot value (COMPONENT-OBJECT-TARGET §3)
@@ -269120,12 +269725,15 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
269120
269725
  return `${local}.${node === "hash" ? "digest" : "value"}`;
269121
269726
  };
269122
269727
  let announcedCode;
269728
+ let rowRelayCode;
269123
269729
  const exprCode = (expr, mode) => {
269124
269730
  switch (expr.kind) {
269125
269731
  case "prop":
269126
269732
  return propName(expr.name);
269127
269733
  case "input-text":
269128
269734
  return announcedCode?.inputId === expr.inputId ? announcedCode.code : "event.target.value";
269735
+ case "row-relay":
269736
+ return rowRelayCode?.repeaterId === expr.repeaterId ? rowRelayCode.read(expr.input) : "undefined";
269129
269737
  /**
269130
269738
  * A url parameter (EXP-011 Tier 2.5) — the query first, the path segment behind it.
269131
269739
  *
@@ -269596,18 +270204,23 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
269596
270204
  // on read, so the runtime's no-object Failure is unreachable — and `store.set()` is
269597
270205
  // synchronous, so its Done chain is following statements too.
269598
270206
  (a8.kind === "popup-show" || a8.kind === "collection-remove" || a8.kind === "log" || a8.kind === "object-set" || a8.kind === "delay" && a8.verb === "restart") && a8.then.length > 0 ? [{ ...a8, then: [] }, ...expandActions(a8.then)] : (
269599
- // EXP-011 §60. The self variant cannot miss and `set` is synchronous: Done then Completed are following
269600
- // statements, in the order reportOutcome sends them. The parent form prints its own two arms.
269601
- a8.kind === "component-object-set" && !a8.parent && (a8.then.length > 0 || a8.completedThen.length > 0) ? [{ ...a8, then: [], completedThen: [] }, ...expandActions(a8.then), ...expandActions(a8.completedThen)] : (
269602
- // A jsfun-run is only its done-chain (EXP-003 §4 A2h): output reads inline the call
269603
- // at their sinks, so the run itself needs no statement — unless it materializes its
269604
- // output record (CONTROLLED-STATE §4f), which is one setter statement before the chain.
269605
- a8.kind === "jsfun-run" ? a8.materialize !== void 0 || runIsPulsed(a8) ? [{ ...a8, then: [] }, ...expandActions(a8.then)] : expandActions(a8.then) : (
269606
- // P18-S116-QUERYFETCH. A Fetch into a query the export does not keep (nothing it renders reads the rows) asks
269607
- // for nothing anyone sees; the query's own row in the report says why it is not kept.
269608
- a8.kind === "query-fetch" && !plan.queries.some((q) => q.nodeId === a8.nodeId) ? [] : (
269609
- // EXP-011 §87. A literal Condition's one side is following statements.
269610
- a8.kind === "branch" && a8.flat === true ? expandActions(a8.whenTrue) : [a8]
270207
+ // P18-S127-SETDONE. A Set Variable with a literal Name cannot fail and its write is synchronous: Done, then
270208
+ // Completed, are following statements (`withSetDone` put them in `then` in that order).
270209
+ a8.kind === "store-set" && a8.then !== void 0 ? [{ ...a8, then: void 0 }, ...expandActions(a8.then)] : (
270210
+ // EXP-011 §60. The self variant cannot miss and `set` is synchronous: Done then Completed are following
270211
+ // statements, in the order reportOutcome sends them. The parent form prints its own two arms.
270212
+ a8.kind === "component-object-set" && !a8.parent && (a8.then.length > 0 || a8.completedThen.length > 0) ? [{ ...a8, then: [], completedThen: [] }, ...expandActions(a8.then), ...expandActions(a8.completedThen)] : (
270213
+ // A jsfun-run is only its done-chain (EXP-003 §4 A2h): output reads inline the call
270214
+ // at their sinks, so the run itself needs no statement — unless it materializes its
270215
+ // output record (CONTROLLED-STATE §4f), which is one setter statement before the chain.
270216
+ // P18-S129-FNSUCCESS: or a Run of a Function a ticked input also runs, which records what it read first.
270217
+ a8.kind === "jsfun-run" ? a8.materialize !== void 0 || runIsPulsed(a8) || a8.ticked !== true && tickedRunIds.has(a8.nodeId) ? [{ ...a8, then: [] }, ...expandActions(a8.then)] : expandActions(a8.then) : (
270218
+ // P18-S116-QUERYFETCH. A Fetch into a query the export does not keep (nothing it renders reads the rows) asks
270219
+ // for nothing anyone sees; the query's own row in the report says why it is not kept.
270220
+ a8.kind === "query-fetch" && !plan.queries.some((q) => q.nodeId === a8.nodeId) ? [] : (
270221
+ // EXP-011 §87. A literal Condition's one side is following statements.
270222
+ a8.kind === "branch" && a8.flat === true ? expandActions(a8.whenTrue) : [a8]
270223
+ )
269611
270224
  )
269612
270225
  )
269613
270226
  )
@@ -269644,7 +270257,9 @@ function emitComponent(plan, project2, ir, catalog2, kitBindings = /* @__PURE__
269644
270257
  };
269645
270258
  const inAction = (a8) => {
269646
270259
  switch (a8.kind) {
270260
+ // P18-S127-SETDONE. The value, or anything in the Done chain.
269647
270261
  case "store-set":
270262
+ return reads(a8.expr) || (a8.then ?? []).some(inAction);
269648
270263
  case "globalstore-set":
269649
270264
  return reads(a8.expr);
269650
270265
  // EXP-011 §47. Any of the patch's values, or anything in the chain after it.
@@ -270101,6 +270716,7 @@ ${pad(indent)}${stateSetterOf(action.materialize)}({ ...${names2.run}.outputs })
270101
270716
  if (action.materialize !== void 0) {
270102
270717
  return `${record2}${stateSetterOf(action.materialize)}(${def.fnName}(${jsArgsObject(def, "handler")}))`;
270103
270718
  }
270719
+ if (record2 !== "" && action.then.length === 0) return `tickedArrival(${tickedRecordArgs(action.nodeId)})`;
270104
270720
  return expandActions(action.then).map(actionCode).join("; ");
270105
270721
  }
270106
270722
  /**
@@ -270129,6 +270745,7 @@ ${pad(indent)}${stateSetterOf(action.materialize)}({ ...${names2.run}.outputs })
270129
270745
  // The functional updates are §3.3's closure-staleness-immune forms.
270130
270746
  case "state-set": {
270131
270747
  const setter = stateSetterOf(action.name);
270748
+ if (action.field !== void 0 && fieldWriteIsRun(action)) return fieldWriteCode(action, action.field, indent);
270132
270749
  if (action.op === "toggle") return `${setter}((v) => !v)`;
270133
270750
  if (action.op === "inc") return `${setter}((v) => v + 1)`;
270134
270751
  if (action.op === "dec") return `${setter}((v) => v - 1)`;
@@ -270538,6 +271155,9 @@ ${at}`) : "new Date()";
270538
271155
  // EXP-011 §63. One call on the handle; Done is the node's listener, passed once to the hook.
270539
271156
  case "drag-snap":
270540
271157
  return `${action.local}.snapTo(${tsLiteral(action.axis)})`;
271158
+ // P18-S128-FIELDSET. A call on the field's element; nothing when it is not on the page.
271159
+ case "field-focus":
271160
+ return `${fieldRefs.get(action.inputId)}.current?.${action.focus ? "focus" : "blur"}()`;
270541
271161
  /**
270542
271162
  * EXP-011 §61. A push, printed as the runtime's own call (`navigate.ts` `navigate()`): the target, the
270543
271163
  * parameters by the target's own prop identifiers, the back callback (the results row written FIRST, then
@@ -270806,7 +271426,64 @@ ${pad(indent)}}`;
270806
271426
  a8.kind === "id-new" && (idNewIsBlock(a8) || a8.materialize !== void 0 || a8.then.length > 0 || chainReadsIdLocal(a8.then, a8.nodeId)) || // EXP-011 §59. Always the block form — the Failure arm always raises.
270807
271427
  a8.kind === "crypto-call" || // EXP-011 §60. The parent write is the block form (the miss arm raises); the own write is one expression.
270808
271428
  a8.kind === "component-object-set" && a8.parent || // EXP-011 §61. A push is a multi-line call; a pop is always the block form; a Reset with a chain is a run of statements.
270809
- a8.kind === "stack-push" || a8.kind === "stack-pop" || a8.kind === "stack-reset" && a8.then.length > 0;
271429
+ a8.kind === "stack-push" || a8.kind === "stack-pop" || a8.kind === "stack-reset" && a8.then.length > 0 || // P18-S128-FIELDSET. A field's Set is an `if`; a Clear with a chain to run after it is a run of statements.
271430
+ a8.kind === "state-set" && fieldWriteIsRun(a8);
271431
+ }
271432
+ const fieldChainOf = (inputId) => [
271433
+ ...plan.changeHandlers[inputId] ?? [],
271434
+ ...(plan.handlers[inputId] ?? {})["onChange"] ?? []
271435
+ ];
271436
+ const printingFieldChains = /* @__PURE__ */ new Set();
271437
+ function fieldWriteIsRun(a8) {
271438
+ if (a8.kind !== "state-set" || a8.field === void 0) return false;
271439
+ if (a8.field.port === "set") return true;
271440
+ return fieldClearedLocals.has(a8.field.inputId) || !printingFieldChains.has(a8.field.inputId) && fieldChainOf(a8.field.inputId).length > 0;
271441
+ }
271442
+ function fieldWriteCode(action, field2, indent) {
271443
+ const setter = stateSetterOf(action.name);
271444
+ const at = pad(indent);
271445
+ const cleared = fieldClearedLocals.get(field2.inputId);
271446
+ const chainLines = (code2, depth) => {
271447
+ if (printingFieldChains.has(field2.inputId)) return [];
271448
+ const chain = fieldChainOf(field2.inputId);
271449
+ if (chain.length === 0) return [];
271450
+ const outer = announcedCode;
271451
+ announcedCode = { inputId: field2.inputId, code: code2 };
271452
+ printingFieldChains.add(field2.inputId);
271453
+ const lines = effectBody(chain, depth);
271454
+ printingFieldChains.delete(field2.inputId);
271455
+ announcedCode = outer;
271456
+ return lines;
271457
+ };
271458
+ if (field2.port === "clear") {
271459
+ const lines = [`${at}${setter}('');`, ...cleared !== void 0 ? [`${at}${cleared}.current = true;`] : [], ...chainLines("''", indent)];
271460
+ return lines.join("\n").slice(at.length);
271461
+ }
271462
+ const ref = fieldRefs.get(field2.inputId);
271463
+ const inner = pad(indent + 2);
271464
+ const start3 = tsLiteral(field2.start ?? "");
271465
+ const body2 = [];
271466
+ let code;
271467
+ if (action.expr === void 0 || action.expr.kind === "literal") {
271468
+ const literal2 = tsLiteral(action.expr?.kind === "literal" ? String(action.expr.value) : field2.start ?? "");
271469
+ code = cleared !== void 0 ? `(${cleared}.current ? '' : ${literal2})` : literal2;
271470
+ } else {
271471
+ const blockLocal = (base) => {
271472
+ let candidate = base;
271473
+ for (let n = 2; reserved.has(candidate); n++) candidate = `${base}${n}`;
271474
+ return candidate;
271475
+ };
271476
+ const value = blockLocal(`${action.name}Value`);
271477
+ code = blockLocal(`${action.name}Text`);
271478
+ const asText2 = `String(${value} ?? '')`;
271479
+ const coerced = field2.start === void 0 || field2.start === "" ? asText2 : `${value} === undefined ? ${start3} : ${asText2}`;
271480
+ body2.push(
271481
+ `${inner}const ${value} = ${exprCode(action.expr, "handler")};`,
271482
+ `${inner}const ${code} = ${cleared !== void 0 ? `${cleared}.current ? '' : ${coerced}` : coerced};`
271483
+ );
271484
+ }
271485
+ body2.push(`${inner}${setter}(${code});`, ...chainLines(code, indent + 2));
271486
+ return [`if (${ref}.current !== document.activeElement) {`, ...body2, `${at}}`].join("\n");
270810
271487
  }
270811
271488
  function actionTakesNoTerminator(a8) {
270812
271489
  if ("detached" in a8 && a8.detached === true) return false;
@@ -270822,7 +271499,8 @@ ${pad(indent)}}`;
270822
271499
  a8.kind === "id-new" && idNewIsBlock(a8) || // EXP-011 §59. The block form ends in `}` too.
270823
271500
  a8.kind === "crypto-call" || // EXP-011 §60. The parent write's `if` ends in `}` and takes no terminator.
270824
271501
  a8.kind === "component-object-set" && a8.parent || // EXP-011 §61. The pop's block form ends in `}`; the push ends in `)` and takes its `;`, as does a Reset.
270825
- a8.kind === "stack-pop"
271502
+ a8.kind === "stack-pop" || // P18-S128-FIELDSET. Each line of a field write ends its own statement.
271503
+ a8.kind === "state-set" && fieldWriteIsRun(a8)
270826
271504
  );
270827
271505
  }
270828
271506
  function actionsAwait(actions) {
@@ -270834,7 +271512,10 @@ ${pad(indent)}}`;
270834
271512
  }
270835
271513
  function blockBody(expanded, indent) {
270836
271514
  return expanded.map(
270837
- (a8) => actionTakesNoTerminator(a8) ? `${pad(indent)}${actionCode(a8, indent)}` : `${pad(indent)}${actionCode(a8, "detached" in a8 && a8.detached === true || a8.kind === "date-now-read" || a8.kind === "id-new" || a8.kind === "crypto-call" || a8.kind === "branch" || a8.kind === "array-new" || a8.kind === "component-object-set" || a8.kind === "stack-push" || a8.kind === "stack-reset" || a8.kind === "jsfun-run" ? indent : 0)};`
271515
+ (a8) => (
271516
+ // P18-S128-FIELDSET. A field write's lines take the column (`actionTakesNoTerminator` holds for it).
271517
+ actionTakesNoTerminator(a8) ? `${pad(indent)}${actionCode(a8, indent)}` : `${pad(indent)}${actionCode(a8, "detached" in a8 && a8.detached === true || a8.kind === "date-now-read" || a8.kind === "id-new" || a8.kind === "crypto-call" || a8.kind === "branch" || a8.kind === "array-new" || a8.kind === "component-object-set" || a8.kind === "stack-push" || a8.kind === "stack-reset" || a8.kind === "jsfun-run" ? indent : 0)};`
271518
+ )
270838
271519
  );
270839
271520
  }
270840
271521
  function effectBody(actions, indent) {
@@ -270854,10 +271535,37 @@ ${pad(indent)}}`;
270854
271535
  }
270855
271536
  return statements.length === 1 ? `${head} => ${statements[0]}` : `${head} => { ${statements.join("; ")}; }`;
270856
271537
  };
271538
+ const prependStatements = (arrow, statements, indent) => {
271539
+ if (statements.length === 0) return arrow;
271540
+ const head = arrow.slice(0, arrow.indexOf("=> ") + 3);
271541
+ const body2 = arrow.slice(head.length);
271542
+ if (body2.startsWith("{\n")) return `${head}{
271543
+ ${statements.map((st) => `${pad(indent + 2)}${st};`).join("\n")}
271544
+ ${body2.slice(2)}`;
271545
+ if (body2.startsWith("{ ")) return `${head}{ ${statements.join("; ")}; ${body2.slice(2)}`;
271546
+ return `${head}{ ${statements.join("; ")}; ${body2}; }`;
271547
+ };
270857
271548
  const delayRefs = /* @__PURE__ */ new Map();
270858
271549
  for (const a8 of deepActions(allActions)) {
270859
271550
  if (a8.kind === "delay") delayRefs.set(a8.nodeId, a8.ref);
270860
271551
  }
271552
+ const fieldRefs = /* @__PURE__ */ new Map();
271553
+ const fieldSetSources = /* @__PURE__ */ new Map();
271554
+ const fieldCleared = /* @__PURE__ */ new Set();
271555
+ for (const a8 of deepActions(allActions)) {
271556
+ const inputId = a8.kind === "field-focus" ? a8.inputId : a8.kind === "state-set" && a8.field?.port === "set" ? a8.field.inputId : void 0;
271557
+ if (inputId !== void 0 && !fieldRefs.has(inputId)) {
271558
+ const named = lowerFirst(pascalCase2(nodeById.get(inputId)?.authoredLabel ?? "")) || "field";
271559
+ fieldRefs.set(inputId, dedupeLocal(/^[A-Za-z_$]/.test(named) ? `${named}Field` : "field"));
271560
+ }
271561
+ if (a8.kind === "state-set" && a8.field?.port === "set" && a8.expr !== void 0) fieldSetSources.set(a8.field.inputId, a8.expr);
271562
+ if (a8.kind === "state-set" && a8.field?.port === "clear") fieldCleared.add(a8.field.inputId);
271563
+ }
271564
+ const fieldClearedLocals = /* @__PURE__ */ new Map();
271565
+ for (const inputId of fieldCleared) {
271566
+ if (!fieldSetSources.has(inputId)) continue;
271567
+ fieldClearedLocals.set(inputId, dedupeLocal(`${fieldRefs.get(inputId).replace(/Field$/, "")}Cleared`));
271568
+ }
270861
271569
  const relRoot = "..";
270862
271570
  const externalImports = [];
270863
271571
  const coreHooks = [];
@@ -270880,7 +271588,9 @@ ${pad(indent)}}`;
270880
271588
  if (referencedStateVars.length > 0 && !reactImports.includes("useState")) reactImports.push("useState");
270881
271589
  if ((plan.syncEffects.length > 0 || plan.pushEffects.length > 0 || plan.branchEffects.length > 0 || // EXP-011 §39. A Value Changed is an effect; a Delay owes the unmount cleanup effect.
270882
271590
  plan.valueChangedEffects.length > 0 || // P18-S119-TICKEDRUN. A ticked input's run is an effect.
270883
- plan.tickedRuns.length > 0 || // EXP-011 §43, P18-S113-IDBINDREAD. A Record with Fetch unwired is a bind keyed on its Id.
271591
+ plan.tickedRuns.length > 0 || // P18-S126-INSIGNAL. A signal input's chain is an effect on its count.
271592
+ plan.inputPulses.length > 0 || // P18-S127-SIBLINGVALUE. A delivered count follows the count sent from an effect.
271593
+ plan.instancePulses.some((p) => p.delivered !== void 0) || // EXP-011 §43, P18-S113-IDBINDREAD. A Record with Fetch unwired is a bind keyed on its Id.
270884
271594
  recordBinds.length > 0 || // P18-S111-RECORDLIVE. A live Record's row subscribes to the client's copy of its record.
270885
271595
  liveRecordRows.length > 0 || // EXP-011 §67. A Variable's authored Value is a mount effect.
270886
271596
  plan.variableSeeds.length > 0 || // EXP-011 §71. An Object's own authored property values are a mount effect.
@@ -270891,7 +271601,8 @@ ${pad(indent)}}`;
270891
271601
  reactImports.push("useEffect");
270892
271602
  }
270893
271603
  if (radioNameLocals.size > 0) reactImports.push("useId");
270894
- if (delayRefs.size > 0 || plan.valueChangedEffects.length > 0 || usedKeptLocals.size > 0 || pulsedRunIds.size > 0 || plan.tickedRuns.length > 0) reactImports.push("useRef");
271604
+ if (delayRefs.size > 0 || plan.valueChangedEffects.length > 0 || usedKeptLocals.size > 0 || pulsedRunIds.size > 0 || plan.tickedRuns.length > 0 || plan.instancePulses.length > 0 || fieldRefs.size > 0) reactImports.push("useRef");
271605
+ if ([...fieldClearedLocals.keys()].some((id) => fieldSetSources.get(id)?.kind !== "literal") && !reactImports.includes("useEffect")) reactImports.push("useEffect");
270895
271606
  if (plan.task !== void 0 || plan.repeaterItems.length > 0 || plan.fieldMounts.length > 0 || mountChains.length > 0) {
270896
271607
  if (!reactImports.includes("useEffect")) reactImports.push("useEffect");
270897
271608
  if (!reactImports.includes("useRef")) reactImports.push("useRef");
@@ -271414,6 +272125,16 @@ ${pad(indent)}}`;
271414
272125
  defer(node.id, 'the "didMount" signal', mountRefusals.get(node.id));
271415
272126
  continue;
271416
272127
  }
272128
+ if (port5 === "onEnter" && plan.roleOf[node.id] === "input") {
272129
+ const expanded = expandActions(actions);
272130
+ attrs.push(
272131
+ `onKeyDown={${actionsAwait(expanded) ? "async " : ""}(event) => {
272132
+ ${pad(attrIndent + 2)}if (event.key !== 'Enter') return;
272133
+ ${blockBody(expanded, attrIndent + 2).join("\n")}
272134
+ ${pad(attrIndent)}}}`
272135
+ );
272136
+ continue;
272137
+ }
271417
272138
  const eventAttr = roleEvents[port5] ?? EVENT_ATTRS[port5];
271418
272139
  if (!eventAttr) {
271419
272140
  notes.push(`${plan.path}: signal ${node.id}.${port5} has no DOM event equivalent \u2014 dropped, reported`);
@@ -271459,6 +272180,19 @@ ${pad(indent)}}`;
271459
272180
  if (boolTypedSource(source)) return base;
271460
272181
  return SIMPLE_REF.test(base) ? `!!${base}` : `!!(${base})`;
271461
272182
  };
272183
+ const mountedCondOf = (id, source) => {
272184
+ const cond = truthinessCode(source);
272185
+ if (cond === null) return null;
272186
+ const node = nodeById.get(id);
272187
+ const authored = node?.parameters.find((p) => p.name === "mounted")?.value;
272188
+ if (authored?.kind === "literal" && authored.value === false) return cond;
272189
+ const isExpressionAnswer = source.kind === "computed" && source.expr.kind === "jsfun-out" && source.expr.viaState === void 0 && nodeById.get(source.expr.nodeId)?.type === "Expression";
272190
+ const undefinedAtFirst = source.kind === "prop" || source.kind === "store" || source.kind === "store-key" || source.kind === "computed" && !isExpressionAnswer && maybeUndefined(source.expr);
272191
+ if (!undefinedAtFirst) return cond;
272192
+ const base = rawBindingExpr(source);
272193
+ if (base === null || !SIMPLE_REF.test(base) || base.endsWith("()")) return cond;
272194
+ return boolTypedSource(source) ? `(${base} === undefined || ${base})` : `(${base} === undefined || !!${base})`;
272195
+ };
271462
272196
  const negatedVisibleCode = (source) => {
271463
272197
  const base = bindingExpr(source, "truthy");
271464
272198
  if (base === null) return null;
@@ -271565,6 +272299,7 @@ ${pad(indent)}}`;
271565
272299
  return typeof def === "string" || typeof def === "number" || typeof def === "boolean" ? def : void 0;
271566
272300
  };
271567
272301
  const fontIconSetsNoted = /* @__PURE__ */ new Set();
272302
+ let rootMountedCond = null;
271568
272303
  const render2 = (id, indent, radioCtx) => {
271569
272304
  const node = nodeById.get(id);
271570
272305
  const mountedBound = plan.bindings[id]?.["mounted"];
@@ -271579,8 +272314,12 @@ ${pad(indent)}}`;
271579
272314
  }
271580
272315
  const staticallyTrue = mountedBound.kind === "computed" && mountedBound.expr.kind === "literal";
271581
272316
  if (!staticallyTrue) {
271582
- const cond = truthinessCode(mountedBound);
272317
+ const cond = mountedCondOf(id, mountedBound);
271583
272318
  if (cond !== null) {
272319
+ if (id === plan.rootId) {
272320
+ rootMountedCond = cond;
272321
+ return renderCore(id, indent, radioCtx);
272322
+ }
271584
272323
  const inner = renderCore(id, indent + 2, radioCtx);
271585
272324
  return [`${pad(indent)}{${cond} && (`, ...inner, `${pad(indent)})}`];
271586
272325
  }
@@ -271651,6 +272390,7 @@ ${pad(indent)}}`;
271651
272390
  if (controlVar !== void 0) {
271652
272391
  attrs.push(`${role === "checkbox" ? "checked" : "value"}={${controlVar.name}}`);
271653
272392
  }
272393
+ if (role === "input" && fieldRefs.has(id)) attrs.push(`ref={${fieldRefs.get(id)}}`);
271654
272394
  if (role === "input" || isControl) attrs.push(...changeAttrs(node, indent + 2));
271655
272395
  attrs.push(...handlerAttrs(node, indent + 2));
271656
272396
  if (role === "stack") return renderStack(node, attrs, indent);
@@ -271796,10 +272536,14 @@ ${pad(indent)}}`;
271796
272536
  }
271797
272537
  return out;
271798
272538
  };
271799
- const rowSignalAttrs = (attrIndent) => {
272539
+ const rowSignalAttrs = (attrIndent, itemRef, entries2) => {
271800
272540
  const handlers = plan.handlers[node.id];
271801
272541
  if (handlers === void 0) return [];
271802
272542
  const propByPort = new Map((templatePlan?.outputProps ?? []).map((o) => [o.port, o.prop]));
272543
+ const relayWrites = plan.rowRelays.filter((r) => r.repeaterId === node.id).map((r) => {
272544
+ const entry = entries2.find((e) => e.input === r.input);
272545
+ return `${stateSetterOf(r.stateName)}(${entry === void 0 ? "undefined" : memberExpr(itemRef, entry.field)})`;
272546
+ });
271803
272547
  const out = [];
271804
272548
  for (const [port5, actions] of Object.entries(handlers)) {
271805
272549
  if (!port5.startsWith(ITEM_OUTPUT_SIGNAL)) {
@@ -271822,7 +272566,16 @@ ${pad(indent)}}`;
271822
272566
  defer(node.id, `the relayed row signal "${templatePort}"`, `has no callback prop on ${templateLegacy}`);
271823
272567
  continue;
271824
272568
  }
271825
- out.push(`${prop}={${handlerArrow(actions, "()", attrIndent)}}`);
272569
+ const outer = rowRelayCode;
272570
+ rowRelayCode = { repeaterId: node.id, read: (input) => {
272571
+ const entry = entries2.find((e) => e.input === input);
272572
+ return entry === void 0 ? "undefined" : memberExpr(itemRef, entry.field);
272573
+ } };
272574
+ try {
272575
+ out.push(`${prop}={${prependStatements(handlerArrow(actions, "()", attrIndent), relayWrites, attrIndent)}}`);
272576
+ } finally {
272577
+ rowRelayCode = outer;
272578
+ }
271826
272579
  }
271827
272580
  return out;
271828
272581
  };
@@ -271851,7 +272604,7 @@ ${pad(indent)}}`;
271851
272604
  );
271852
272605
  }
271853
272606
  const keyAttr = staticData.keyField ? `key={${itemLocal}.${staticData.keyField}}` : `key={${indexLocal}}`;
271854
- const attrs2 = [keyAttr, ...rowAttrs(keptStatic, itemLocal), ...rowSignalAttrs(indent + 2)];
272607
+ const attrs2 = [keyAttr, ...rowAttrs(keptStatic, itemLocal), ...rowSignalAttrs(indent + 2, itemLocal, keptStatic)];
271855
272608
  const lines2 = element(target.symbol, attrs2, null, indent + 2, false);
271856
272609
  const params = staticData.keyField ? itemLocal : `${itemLocal}, ${indexLocal}`;
271857
272610
  return [`${pad(indent)}{${staticData.constName}.map((${params}) => (`, ...lines2, `${pad(indent)}))}`];
@@ -271870,7 +272623,7 @@ ${pad(indent)}}`;
271870
272623
  );
271871
272624
  }
271872
272625
  }
271873
- const attrs2 = [`key={${indexLocal}}`, ...rowAttrs(keptExpr, itemLocal), ...rowSignalAttrs(indent + 2)];
272626
+ const attrs2 = [`key={${indexLocal}}`, ...rowAttrs(keptExpr, itemLocal), ...rowSignalAttrs(indent + 2, itemLocal, keptExpr)];
271874
272627
  const lines2 = element(target.symbol, attrs2, null, indent + 2, false);
271875
272628
  const srcCode = exprCode(itemsExpr, "render");
271876
272629
  const source = itemsExpr.kind === "minted-array-get" || itemsExpr.kind === "record-filter" ? srcCode : SIMPLE_REF.test(srcCode) ? `(${srcCode} ?? [])` : `((${srcCode}) ?? [])`;
@@ -271890,13 +272643,13 @@ ${pad(indent)}}`;
271890
272643
  );
271891
272644
  }
271892
272645
  if (collection) {
271893
- const attrs2 = [`key={${indexLocal}}`, ...rowAttrs(kept, itemLocal), ...rowSignalAttrs(indent + 2)];
272646
+ const attrs2 = [`key={${indexLocal}}`, ...rowAttrs(kept, itemLocal), ...rowSignalAttrs(indent + 2, itemLocal, kept)];
271894
272647
  const lines2 = element(target.symbol, attrs2, null, indent + 2, false);
271895
272648
  const local = collectionLocals.get(collection.name);
271896
272649
  return [`${pad(indent)}{${local}.map((${itemLocal}, ${indexLocal}) => (`, ...lines2, `${pad(indent)}))}`];
271897
272650
  }
271898
272651
  const item = query2.itemName;
271899
- const attrs = [`key={${item}.id}`, ...rowAttrs(kept, item), ...rowSignalAttrs(indent + 2)];
272652
+ const attrs = [`key={${item}.id}`, ...rowAttrs(kept, item), ...rowSignalAttrs(indent + 2, item, kept)];
271900
272653
  const lines = element(target.symbol, attrs, null, indent + 2, false);
271901
272654
  return [`${pad(indent)}{${query2.stateName}.map((${item}) => (`, ...lines, `${pad(indent)}))}`];
271902
272655
  };
@@ -272106,6 +272859,14 @@ ${pad(indent)}}`;
272106
272859
  for (const [toProperty, source] of Object.entries(plan.bindings[node.id] ?? {})) {
272107
272860
  claimBinding(node.id, toProperty);
272108
272861
  if (toProperty === "mounted") continue;
272862
+ const pulse = plan.instancePulses.find((p) => p.nodeId === node.id && p.port === toProperty);
272863
+ if (pulse !== void 0) {
272864
+ const pulseAttr = targetPropName(node.type, toProperty);
272865
+ if (pulseAttr !== null) {
272866
+ attrs.push(`${pulseAttr}={{ sent: ${pulse.delivered?.name ?? pulse.stateName}, taken: ${pulse.takenLocal} }}`);
272867
+ continue;
272868
+ }
272869
+ }
272109
272870
  if (toProperty === "visible") {
272110
272871
  notes.push(
272111
272872
  `${plan.path}: wire into instance ${node.id}.visible dropped \u2014 an instance has no element class to toggle in this slice`
@@ -272550,7 +273311,28 @@ ${pad(indent)}}`;
272550
273311
  body.push(` const ${mintedLocals.get(nodeId)} = useCollection${rowType === "any" ? "" : `<${rowType}>`}(${mintStateName(nodeId)} ?? ${NO_ARRAY2});`);
272551
273312
  }
272552
273313
  for (const [, ref] of delayRefs) body.push(` const ${ref} = useRef<DelayHandle | null>(null);`);
273314
+ for (const [, ref] of fieldRefs) body.push(` const ${ref} = useRef<HTMLInputElement>(null);`);
273315
+ for (const [inputId, local] of fieldClearedLocals) {
273316
+ const source = fieldSetSources.get(inputId);
273317
+ body.push(` const ${local} = useRef(false);`);
273318
+ if (source.kind === "literal") continue;
273319
+ const read2 = exprCode(source, "handler");
273320
+ const store2 = /^([A-Za-z_$][A-Za-z0-9_$]*)\.get\(\)$/.exec(read2);
273321
+ body.push(
273322
+ ...store2 !== null ? [` useEffect(() => ${store2[1]}.subscribe(() => { ${local}.current = false; }), []);`] : [" useEffect(() => {", ` ${local}.current = false;`, ` }, [${exprCode(source, "render")}]);`]
273323
+ );
273324
+ }
272553
273325
  for (const effect of plan.valueChangedEffects) body.push(` const ${effect.lastLocal} = useRef<unknown>(undefined);`);
273326
+ for (const pulse of plan.instancePulses) body.push(` const ${pulse.takenLocal} = useRef(0);`);
273327
+ for (const pulse of plan.instancePulses) {
273328
+ if (pulse.delivered === void 0) continue;
273329
+ body.push(
273330
+ ` // "${pulse.port}" is handed on one commit after it is sent: the values lifted with it arrive first, as the running app delivers them.`,
273331
+ " useEffect(() => {",
273332
+ ` ${pulse.delivered.setterName}(${pulse.stateName});`,
273333
+ ` }, [${pulse.stateName}]);`
273334
+ );
273335
+ }
272554
273336
  for (const id of pulsedRunIds) {
272555
273337
  const def = jsFunByNode[id];
272556
273338
  body.push(` const ${pulseNamesOf(def).node} = useRef(functionNode<NonNullable<Parameters<typeof ${def.fnName}>[1]>>([${def.signals.map(tsLiteral).join(", ")}])).current;`);
@@ -272825,16 +273607,19 @@ ${pad(indent)}}`;
272825
273607
  announcedCode = { inputId: mount2.inputId, code };
272826
273608
  const chainLines = effectBody(chain, 4);
272827
273609
  announcedCode = void 0;
273610
+ const gate = (mount2.gates ?? []).map((id) => mountedCondOf(id, plan.bindings[id]["mounted"])).filter((c) => c !== null);
273611
+ const when = gate.length === 0 ? null : gate.length === 1 ? gate[0] : gate.map((c) => SIMPLE_REF.test(c) ? c : `(${c})`).join(" && ");
272828
273612
  body.push(
272829
273613
  ` // ${mount2.comment}`,
272830
273614
  ` const ${once} = useRef(false);`,
272831
273615
  " useEffect(() => {",
272832
273616
  ` if (${once}.current) return;`,
273617
+ ...when !== null ? [` if (${negatedCode(when)}) return;`] : [],
272833
273618
  ` ${once}.current = true;`,
272834
273619
  ...lines,
272835
273620
  ...chainLines,
272836
- " // eslint-disable-next-line react-hooks/exhaustive-deps -- once, as the field mounts",
272837
- " }, []);",
273621
+ when !== null ? " // eslint-disable-next-line react-hooks/exhaustive-deps -- once, the first time the field mounts" : " // eslint-disable-next-line react-hooks/exhaustive-deps -- once, as the field mounts",
273622
+ ` }, [${gate.join(", ")}]);`,
272838
273623
  ""
272839
273624
  );
272840
273625
  }
@@ -272917,6 +273702,18 @@ ${pad(indent)}}`;
272917
273702
  ""
272918
273703
  );
272919
273704
  }
273705
+ for (const pulse of plan.inputPulses) {
273706
+ const pair = exprCode({ kind: "prop", name: pulse.port }, "render");
273707
+ body.push(
273708
+ ` // "${commentSafe(pulse.port)}", a signal the parent sends in: its chain runs once for each pulse sent, never twice.`,
273709
+ " useEffect(() => {",
273710
+ ` if (${pair} === undefined || ${pair}.taken.current === ${pair}.sent) return;`,
273711
+ ` ${pair}.taken.current = ${pair}.sent;`,
273712
+ ...effectBody(pulse.actions, 4),
273713
+ ` }, [${pair}?.sent]);`,
273714
+ ""
273715
+ );
273716
+ }
272920
273717
  for (const ticked3 of plan.tickedRuns) {
272921
273718
  const deps = [...new Set(ticked3.watch.flatMap(effectDeps))].join(", ");
272922
273719
  body.push(
@@ -272931,6 +273728,26 @@ ${pad(indent)}}`;
272931
273728
  for (const mount2 of mountChains) {
272932
273729
  const node = nodeById.get(mount2.nodeId);
272933
273730
  const mounted = dedupeLocal("mounted");
273731
+ const gate = mount2.gates.map((id) => mountedCondOf(id, plan.bindings[id]["mounted"])).filter((c) => c !== null);
273732
+ if (gate.length > 0) {
273733
+ const when = gate.length === 1 ? gate[0] : gate.map((c) => SIMPLE_REF.test(c) ? c : `(${c})`).join(" && ");
273734
+ body.push(
273735
+ ` // ${node.authoredLabel ?? node.type} \u2014 Did Mount: each time it mounts, as the running app sends it: when ${commentSafe(when)}.`,
273736
+ ` const ${mounted} = useRef(false);`,
273737
+ " useEffect(() => {",
273738
+ ` if (${negatedCode(when)}) {`,
273739
+ ` ${mounted}.current = false;`,
273740
+ " return;",
273741
+ " }",
273742
+ ` if (${mounted}.current) return;`,
273743
+ ` ${mounted}.current = true;`,
273744
+ ...effectBody(mount2.actions, 4),
273745
+ " // eslint-disable-next-line react-hooks/exhaustive-deps -- once per mount, like the element's own Did Mount",
273746
+ ` }, [${gate.join(", ")}]);`,
273747
+ ""
273748
+ );
273749
+ continue;
273750
+ }
272934
273751
  body.push(
272935
273752
  ` // ${node.authoredLabel ?? node.type} \u2014 Did Mount: once, when it mounts, as the running app sends it.`,
272936
273753
  ` const ${mounted} = useRef(false);`,
@@ -272947,7 +273764,9 @@ ${pad(indent)}}`;
272947
273764
  switch (a8.kind) {
272948
273765
  case "emit":
272949
273766
  return a8.payload.map((p) => p.expr);
273767
+ // P18-S127-SETDONE. The value, then the Done chain.
272950
273768
  case "store-set":
273769
+ return [a8.expr, ...(a8.then ?? []).flatMap(actionExprsOf)];
272951
273770
  case "globalstore-set":
272952
273771
  return [a8.expr];
272953
273772
  // EXP-011 §47. The patch's values, then the chain.
@@ -273061,6 +273880,9 @@ ${pad(indent)}}`;
273061
273880
  // EXP-011 §63. The same.
273062
273881
  case "drag-snap":
273063
273882
  return [];
273883
+ // P18-S128-FIELDSET. The same.
273884
+ case "field-focus":
273885
+ return [];
273064
273886
  // EXP-011 Tier 2.5. The link, the wired Open In New Tab, and both chains — `usesPayload`
273065
273887
  // walks this list, so a link built from a received event's payload is found here or the
273066
273888
  // emitted callback takes no argument and its body reads one.
@@ -273122,7 +273944,18 @@ ${pad(indent)}}`;
273122
273944
  ] : jsxLines;
273123
273945
  if (jsxLines.length === 1 && jsxLines[0] === " null") body.push(...preReturnComment, " return null;", "}");
273124
273946
  else if (removedRoot !== null) body.push(...preReturnComment, ` // ${removedRoot[1]}`, " return null;", "}");
273125
- else body.push(...preReturnComment, " return (", ...providedJsx, " );", "}");
273947
+ else if (rootMountedCond !== null) {
273948
+ const guard = negatedCode(rootMountedCond);
273949
+ body.push(
273950
+ ...preReturnComment,
273951
+ ` // Its root is mounted when ${commentSafe(rootMountedCond)}; while it is not, the running app draws nothing for this instance.`,
273952
+ ` if (${guard}) return null;`,
273953
+ " return (",
273954
+ ...providedJsx,
273955
+ " );",
273956
+ "}"
273957
+ );
273958
+ } else body.push(...preReturnComment, " return (", ...providedJsx, " );", "}");
273126
273959
  if (usesRowField) {
273127
273960
  body.push(
273128
273961
  "",
@@ -278721,6 +279554,10 @@ var ProjectStore = class {
278721
279554
  readStyles() {
278722
279555
  return this.viaFormat(() => this.format.readStyles());
278723
279556
  }
279557
+ /** P112-OWN020-OFFSYSTEM — element styles (nameless variants) are written here; see `tools/elementStyles.ts`. */
279558
+ writeStyles(styles) {
279559
+ this.viaFormat(() => this.format.writeStyles(styles));
279560
+ }
278724
279561
  get componentsDir() {
278725
279562
  return path11.join(this.projectDir, "components");
278726
279563
  }
@@ -279719,6 +280556,83 @@ function honestySection(h) {
279719
280556
  ].filter(Boolean).join("\n");
279720
280557
  }
279721
280558
 
280559
+ // src/tools/unknownArguments.ts
280560
+ function declaredKeys(schema) {
280561
+ const def = schema?._def;
280562
+ if (!def || def.typeName !== "ZodObject") return void 0;
280563
+ if (def.unknownKeys !== "strip" && def.unknownKeys !== "strict") return void 0;
280564
+ if (def.catchall && def.catchall._def?.typeName !== "ZodNever") return void 0;
280565
+ return Object.keys(schema.shape);
280566
+ }
280567
+ var fold = (key2) => key2.toLowerCase().replace(/[_\-\s]/g, "");
280568
+ function distance2(a8, b) {
280569
+ const row = Array.from({ length: b.length + 1 }, (_, i) => i);
280570
+ for (let i = 1; i <= a8.length; i++) {
280571
+ let prev = row[0];
280572
+ row[0] = i;
280573
+ for (let j = 1; j <= b.length; j++) {
280574
+ const cur = row[j];
280575
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a8[i - 1] === b[j - 1] ? 0 : 1));
280576
+ prev = cur;
280577
+ }
280578
+ }
280579
+ return row[b.length];
280580
+ }
280581
+ var ARGUMENT_SYNONYMS = {
280582
+ description: ["summary", "intent", "request"],
280583
+ desc: ["summary", "intent"],
280584
+ title: ["name"],
280585
+ folder: ["directory", "path"],
280586
+ dir: ["directory", "path"]
280587
+ };
280588
+ function closestArgument(key2, declared) {
280589
+ const same = declared.find((d) => fold(d) === fold(key2));
280590
+ if (same) return same;
280591
+ const synonym = (ARGUMENT_SYNONYMS[fold(key2)] ?? []).find((s) => declared.includes(s));
280592
+ if (synonym) return synonym;
280593
+ let best;
280594
+ let bestDistance = 3;
280595
+ for (const d of declared) {
280596
+ const n = distance2(fold(key2), fold(d));
280597
+ if (n < bestDistance) {
280598
+ best = d;
280599
+ bestDistance = n;
280600
+ }
280601
+ }
280602
+ return best;
280603
+ }
280604
+ function unknownArgumentKeys(schema, args) {
280605
+ const declared = declaredKeys(schema);
280606
+ if (!declared || !args || typeof args !== "object" || Array.isArray(args)) return [];
280607
+ return Object.keys(args).filter((key2) => !declared.includes(key2));
280608
+ }
280609
+ function unknownArgumentsRefusal(tool, schema, unknown3) {
280610
+ const declared = declaredKeys(schema) ?? [];
280611
+ const named = unknown3.map((key2) => {
280612
+ const meant = closestArgument(key2, declared);
280613
+ return meant ? `"${key2}" (did you mean "${meant}"?)` : `"${key2}"`;
280614
+ });
280615
+ return {
280616
+ error: {
280617
+ code: "invalid-argument",
280618
+ message: `${tool} has no argument ${named.join(", ")}. Nothing was done: an argument a tool does not have would be dropped without a word. Its arguments are: ${declared.join(", ")}.`,
280619
+ details: {
280620
+ unknownArguments: [...unknown3],
280621
+ didYouMean: Object.fromEntries(unknown3.map((key2) => [key2, closestArgument(key2, declared) ?? null]))
280622
+ }
280623
+ }
280624
+ };
280625
+ }
280626
+ function refuseUnknownArguments(server) {
280627
+ const s = server;
280628
+ const original = s.validateToolInput.bind(server);
280629
+ s.validateToolInput = async (tool, args, toolName) => {
280630
+ const unknown3 = unknownArgumentKeys(tool.inputSchema, args);
280631
+ if (unknown3.length > 0) throw new Error(JSON.stringify(unknownArgumentsRefusal(toolName, tool.inputSchema, unknown3), null, 2));
280632
+ return original(tool, args, toolName);
280633
+ };
280634
+ }
280635
+
279722
280636
  // src/tools/util.ts
279723
280637
  function jsonResult(payload) {
279724
280638
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
@@ -280069,6 +280983,11 @@ function registerCallTool(server, disclosure) {
280069
280983
  }
280070
280984
  let parsed2 = void 0;
280071
280985
  if (schema) {
280986
+ const unknown3 = unknownArgumentKeys(schema, args.arguments ?? {});
280987
+ if (unknown3.length > 0) {
280988
+ const { error: error2 } = unknownArgumentsRefusal(args.name, schema, unknown3);
280989
+ throw new ToolError(error2.code, error2.message, error2.details);
280990
+ }
280072
280991
  const result2 = await safeParseAsync3(schema, args.arguments ?? {});
280073
280992
  if (!result2.success) {
280074
280993
  throw new ToolError(
@@ -289504,6 +290423,248 @@ function renderImagery(report3) {
289504
290423
  return lines.join("\n");
289505
290424
  }
289506
290425
 
290426
+ // src/tools/elementStyles.ts
290427
+ var STYLES_SCHEMA = "https://opennoodl.dev/schemas/styles-v2.json";
290428
+ var ELEMENT_STATES = ["hover", "pressed", "focused", "disabled"];
290429
+ var LABEL = {
290430
+ labelfontSize: "var(--text-sm)",
290431
+ labelfontWeight: "var(--font-normal)",
290432
+ labelcolor: "var(--foreground)",
290433
+ labelSpacing: "var(--space-2)"
290434
+ };
290435
+ var FIELD_BORDER = {
290436
+ borderStyle: "solid",
290437
+ borderWidth: "var(--border-1)",
290438
+ borderColor: "var(--border-control)"
290439
+ };
290440
+ var ELEMENT_STYLE_STARTERS = {
290441
+ "net.noodl.controls.button": {
290442
+ parameters: {
290443
+ backgroundColor: "var(--primary)",
290444
+ color: "var(--primary-foreground)",
290445
+ fontFamily: "var(--font-sans)",
290446
+ fontSize: "var(--text-sm)",
290447
+ fontWeight: "var(--font-medium)",
290448
+ borderRadius: "var(--radius-md)",
290449
+ paddingTop: "var(--space-2)",
290450
+ paddingBottom: "var(--space-2)",
290451
+ paddingLeft: "var(--space-4)",
290452
+ paddingRight: "var(--space-4)"
290453
+ },
290454
+ stateParameters: { hover: { backgroundColor: "var(--primary-hover)" }, disabled: { opacity: 0.5 } }
290455
+ },
290456
+ "net.noodl.controls.checkbox": {
290457
+ parameters: {
290458
+ width: "var(--space-4)",
290459
+ height: "var(--space-4)",
290460
+ backgroundColor: "var(--background)",
290461
+ ...FIELD_BORDER,
290462
+ borderRadius: "var(--radius-sm)",
290463
+ ...LABEL
290464
+ },
290465
+ stateParameters: { hover: { borderColor: "var(--primary)" }, disabled: { opacity: 0.5 } }
290466
+ },
290467
+ "net.noodl.controls.radiobutton": {
290468
+ parameters: {
290469
+ width: "var(--space-4)",
290470
+ height: "var(--space-4)",
290471
+ backgroundColor: "var(--background)",
290472
+ ...FIELD_BORDER,
290473
+ borderRadius: "var(--radius-full)",
290474
+ fillColor: "var(--primary)",
290475
+ fillSpacing: "var(--space-0-5)",
290476
+ ...LABEL
290477
+ },
290478
+ stateParameters: { hover: { borderColor: "var(--primary)" }, disabled: { opacity: 0.5 } }
290479
+ },
290480
+ "net.noodl.controls.textinput": {
290481
+ parameters: {
290482
+ backgroundColor: "var(--background)",
290483
+ color: "var(--foreground)",
290484
+ fontFamily: "var(--font-sans)",
290485
+ fontSize: "var(--text-base)",
290486
+ ...FIELD_BORDER,
290487
+ borderRadius: "var(--radius-md)",
290488
+ paddingTop: "var(--space-2)",
290489
+ paddingBottom: "var(--space-2)",
290490
+ paddingLeft: "var(--space-3)",
290491
+ paddingRight: "var(--space-3)",
290492
+ ...LABEL
290493
+ },
290494
+ stateParameters: { hover: { borderColor: "var(--border-strong)" }, disabled: { opacity: 0.5 } }
290495
+ },
290496
+ "net.noodl.controls.options": {
290497
+ parameters: {
290498
+ backgroundColor: "var(--background)",
290499
+ color: "var(--foreground)",
290500
+ fontFamily: "var(--font-sans)",
290501
+ fontSize: "var(--text-base)",
290502
+ ...FIELD_BORDER,
290503
+ borderRadius: "var(--radius-md)",
290504
+ paddingTop: "var(--space-2)",
290505
+ paddingBottom: "var(--space-2)",
290506
+ paddingLeft: "var(--space-3)",
290507
+ paddingRight: "var(--space-3)",
290508
+ ...LABEL
290509
+ },
290510
+ stateParameters: { hover: { borderColor: "var(--border-strong)" }, disabled: { opacity: 0.5 } }
290511
+ },
290512
+ "net.noodl.controls.range": {
290513
+ parameters: {
290514
+ thumbColor: "var(--primary)",
290515
+ thumbWidth: "var(--space-4)",
290516
+ thumbHeight: "var(--space-4)",
290517
+ thumbBorderRadius: "var(--radius-full)",
290518
+ trackColor: "var(--muted)",
290519
+ trackActiveColor: "var(--primary)",
290520
+ trackHeight: "var(--space-1)",
290521
+ trackBorderRadius: "var(--radius-full)"
290522
+ }
290523
+ }
290524
+ };
290525
+ var isElementStyle = (v) => !!v && typeof v.typename === "string" && (v.name === void 0 || v.name === null || v.name === "");
290526
+ function readElementStyles(store2) {
290527
+ const out = {};
290528
+ for (const v of store2.readStyles()?.variants ?? []) {
290529
+ if (!isElementStyle(v)) continue;
290530
+ const states = v.stateParameters ?? v.stateParamaters;
290531
+ out[v.typename] = {
290532
+ parameters: v.parameters ?? {},
290533
+ ...states && Object.keys(states).length > 0 ? { stateParameters: states } : {}
290534
+ };
290535
+ }
290536
+ return out;
290537
+ }
290538
+ function stylesOrNew(store2) {
290539
+ return store2.readStyles() ?? { $schema: STYLES_SCHEMA };
290540
+ }
290541
+ function upsert2(styles, typename, style) {
290542
+ const variants = (styles.variants ?? []).filter((v) => !(isElementStyle(v) && v.typename === typename));
290543
+ if (style) {
290544
+ variants.push({
290545
+ typename,
290546
+ parameters: style.parameters,
290547
+ ...style.stateParameters && Object.keys(style.stateParameters).length > 0 ? { stateParameters: style.stateParameters } : {}
290548
+ });
290549
+ }
290550
+ styles.variants = variants;
290551
+ }
290552
+ function resolveElementType(name) {
290553
+ const direct = getNodeTypeDetail(name);
290554
+ if (!("error" in direct) && !direct.deprecated) return direct.typeName;
290555
+ const wanted = name.trim().toLowerCase();
290556
+ const rows = listNodeTypes({ includeHidden: true }).filter(
290557
+ (r) => r.displayName.toLowerCase() === wanted || r.typeName.toLowerCase() === wanted
290558
+ );
290559
+ const live = rows.find((r) => !r.deprecated);
290560
+ if (live) return live.typeName;
290561
+ if (!("error" in direct)) return direct.replacedBy ?? direct.typeName;
290562
+ throw new ToolError("not-found", direct.error, direct.suggestion ? { suggestion: direct.suggestion } : void 0);
290563
+ }
290564
+ function assertPorts(typename, names2, where) {
290565
+ const detail = getNodeTypeDetail(typename);
290566
+ if ("error" in detail) throw new ToolError("not-found", detail.error);
290567
+ const ports = new Set(detail.inputs.map((p) => p.name));
290568
+ const unknown3 = names2.filter((n) => !ports.has(n));
290569
+ if (unknown3.length === 0) return;
290570
+ const lower = new Map([...ports].map((p) => [p.toLowerCase(), p]));
290571
+ const suggestions = Object.fromEntries(
290572
+ unknown3.map((u) => [u, lower.get(u.toLowerCase()) ?? [...ports].find((p) => p.toLowerCase().includes(u.toLowerCase()))])
290573
+ );
290574
+ throw new ToolError(
290575
+ "invalid-argument",
290576
+ `${detail.displayName} has no ${unknown3.map((u) => `"${u}"`).join(", ")} ${where}. get_node_type lists its ports; an element style sets the same parameters a node does.`,
290577
+ { unknownParameters: unknown3, suggestions }
290578
+ );
290579
+ }
290580
+ function nodesOverriding(store2, typename, names2) {
290581
+ let nodes = 0;
290582
+ const overriding = [];
290583
+ for (const row of store2.listComponents()) {
290584
+ const key2 = row.path;
290585
+ let stored;
290586
+ try {
290587
+ stored = store2.readComponent(key2);
290588
+ } catch {
290589
+ continue;
290590
+ }
290591
+ for (const n of stored.files.nodes.nodes ?? []) {
290592
+ if (n.type !== typename || n.variant) continue;
290593
+ nodes += 1;
290594
+ const own2 = names2.filter((p) => n.parameters && Object.prototype.hasOwnProperty.call(n.parameters, p));
290595
+ if (own2.length > 0) overriding.push({ component: key2, nodeId: n.id, parameters: own2 });
290596
+ }
290597
+ }
290598
+ return { nodes, overriding };
290599
+ }
290600
+ function registerElementStyleTools(server, binding) {
290601
+ server.registerTool(
290602
+ "set_element_style",
290603
+ {
290604
+ title: "Set an element style",
290605
+ description: `Set the look of every node of one type at once \u2014 every Radio Button, Checkbox, Text Input, Dropdown, Slider, Button or Text \u2014 so whichever control you place matches the page. starter: true writes the on-system look for a control type; parameters are the node's own (get_node_type lists them), as "var(--token)" values. A node that sets a parameter itself keeps its own value. Read them with get_style_vocabulary (elementStyles). Set merges into the current style; remove: true clears it.`,
290606
+ inputSchema: {
290607
+ type: external_exports.string().describe('Node type or its display name, e.g. "Radio Button" or "net.noodl.controls.radiobutton"'),
290608
+ starter: external_exports.boolean().optional().describe("true = start from the on-system look for this control type (parameters still apply on top)"),
290609
+ parameters: external_exports.record(external_exports.unknown()).optional().describe('e.g. { "width": "var(--space-4)", "labelFontSize": "var(--text-sm)" }'),
290610
+ states: external_exports.record(external_exports.record(external_exports.unknown())).optional().describe(`Per visual state (${ELEMENT_STATES.join(", ")}), the parameters that change`),
290611
+ unset: external_exports.array(external_exports.string()).optional().describe("Parameter names to drop from the style"),
290612
+ remove: external_exports.boolean().optional().describe("true = delete this element style")
290613
+ }
290614
+ },
290615
+ guarded(
290616
+ (args) => {
290617
+ const store2 = binding.require();
290618
+ const typename = resolveElementType(args.type);
290619
+ const styles = stylesOrNew(store2);
290620
+ const current2 = readElementStyles(store2)[typename];
290621
+ if (args.remove) {
290622
+ upsert2(styles, typename, null);
290623
+ store2.writeStyles(styles);
290624
+ return jsonResult({ ok: true, type: typename, removed: !!current2 });
290625
+ }
290626
+ if (!args.starter && !args.parameters && !args.states && !args.unset) {
290627
+ throw new ToolError("invalid-argument", "Pass starter: true, parameters, states, unset or remove: true.");
290628
+ }
290629
+ const starter = args.starter ? ELEMENT_STYLE_STARTERS[typename] : void 0;
290630
+ if (args.starter && !starter) {
290631
+ throw new ToolError(
290632
+ "invalid-argument",
290633
+ `There is no starter look for ${typename}. Starters exist for: ${Object.keys(ELEMENT_STYLE_STARTERS).join(", ")}. Pass parameters instead.`
290634
+ );
290635
+ }
290636
+ const parameters = { ...current2?.parameters ?? {}, ...starter?.parameters ?? {}, ...args.parameters ?? {} };
290637
+ for (const u of args.unset ?? []) delete parameters[u];
290638
+ const stateParameters = { ...current2?.stateParameters ?? {} };
290639
+ for (const [state, params] of Object.entries(starter?.stateParameters ?? {})) {
290640
+ stateParameters[state] = { ...stateParameters[state] ?? {}, ...params };
290641
+ }
290642
+ for (const [state, params] of Object.entries(args.states ?? {})) {
290643
+ if (!ELEMENT_STATES.includes(state)) {
290644
+ throw new ToolError("invalid-argument", `Unknown visual state "${state}". Use one of: ${ELEMENT_STATES.join(", ")}.`);
290645
+ }
290646
+ stateParameters[state] = { ...stateParameters[state] ?? {}, ...params };
290647
+ }
290648
+ assertPorts(typename, Object.keys(parameters), "parameter");
290649
+ for (const [state, params] of Object.entries(stateParameters)) assertPorts(typename, Object.keys(params), `parameter (state "${state}")`);
290650
+ upsert2(styles, typename, { parameters, stateParameters });
290651
+ store2.writeStyles(styles);
290652
+ const reach2 = nodesOverriding(store2, typename, Object.keys(parameters));
290653
+ return jsonResult({
290654
+ ok: true,
290655
+ type: typename,
290656
+ style: { parameters, ...Object.keys(stateParameters).length ? { states: stateParameters } : {} },
290657
+ nodesOfType: reach2.nodes,
290658
+ ...reach2.overriding.length ? {
290659
+ overriding: reach2.overriding.slice(0, 20),
290660
+ note: `${reach2.overriding.length} of ${reach2.nodes} set some of these parameters themselves and keep their own value. Remove them from those nodes (update_node unset_parameters) to let the element style through.`
290661
+ } : {}
290662
+ });
290663
+ }
290664
+ )
290665
+ );
290666
+ }
290667
+
289507
290668
  // src/presetFontFiles.ts
289508
290669
  var fs28 = __toESM(require("fs"));
289509
290670
  var path28 = __toESM(require("path"));
@@ -289644,10 +290805,13 @@ function registerStyleReadTools(server, binding) {
289644
290805
  const vocab = buildStyleVocabulary(store2.designTokenMetaSource(), readProjectLooks(store2));
289645
290806
  const icons = readIconSets(store2.projectDir);
289646
290807
  const imagery = readImagery(store2.projectDir);
290808
+ const elementStyles = readElementStyles(store2);
289647
290809
  if (args.detail === "prompt") {
289648
290810
  return jsonResult({
289649
290811
  vocabulary: `${renderStyleVocabulary(vocab)}
289650
290812
 
290813
+ ${renderElementStyles(elementStyles)}
290814
+
289651
290815
  ${renderIconSets(icons)}
289652
290816
 
289653
290817
  ${renderImagery(imagery)}
@@ -289655,7 +290819,7 @@ ${renderImagery(imagery)}
289655
290819
  LAYOUT: ${LAYOUT_RULES_SHORT}`
289656
290820
  });
289657
290821
  }
289658
- return jsonResult({ ...vocab, icons, imagery, layout: LAYOUT_RULES });
290822
+ return jsonResult({ ...vocab, elementStyles, elementStylesNote: ELEMENT_STYLES_NOTE, icons, imagery, layout: LAYOUT_RULES });
289659
290823
  })
289660
290824
  );
289661
290825
  }
@@ -289675,6 +290839,20 @@ function readProjectLooks(store2) {
289675
290839
  };
289676
290840
  });
289677
290841
  }
290842
+ var ELEMENT_STYLES_NOTE = `Element styles dress every node of a type that does not set the parameter itself, so leave a control's look to its element style and set only what is particular to one node. Change a type's look for the whole app with set_element_style (find_tools({ group: "theme" })).`;
290843
+ function renderElementStyles(styles) {
290844
+ const types = Object.keys(styles);
290845
+ if (types.length === 0) {
290846
+ return "ELEMENT STYLES: none \u2014 every control you place draws the runtime's own look until its type has one: call set_element_style for each control type you use.";
290847
+ }
290848
+ const lines = types.map((t) => {
290849
+ const params = Object.entries(styles[t].parameters).map(([k, v]) => `${k} ${String(v)}`).join(", ");
290850
+ return ` ${t.replace("net.noodl.controls.", "")}: ${params}`;
290851
+ });
290852
+ return `ELEMENT STYLES (every node of the type wears these unless it sets the parameter itself):
290853
+ ${lines.join("\n")}
290854
+ ${ELEMENT_STYLES_NOTE}`;
290855
+ }
289678
290856
  function registerStyleWriteTools(server, binding) {
289679
290857
  server.registerTool(
289680
290858
  "set_project_tokens",
@@ -293295,87 +294473,10 @@ function registerLessonTools(server) {
293295
294473
  );
293296
294474
  }
293297
294475
 
293298
- // src/tools/unknownArguments.ts
293299
- function declaredKeys(schema) {
293300
- const def = schema?._def;
293301
- if (!def || def.typeName !== "ZodObject") return void 0;
293302
- if (def.unknownKeys !== "strip" && def.unknownKeys !== "strict") return void 0;
293303
- if (def.catchall && def.catchall._def?.typeName !== "ZodNever") return void 0;
293304
- return Object.keys(schema.shape);
293305
- }
293306
- var fold = (key2) => key2.toLowerCase().replace(/[_\-\s]/g, "");
293307
- function distance2(a8, b) {
293308
- const row = Array.from({ length: b.length + 1 }, (_, i) => i);
293309
- for (let i = 1; i <= a8.length; i++) {
293310
- let prev = row[0];
293311
- row[0] = i;
293312
- for (let j = 1; j <= b.length; j++) {
293313
- const cur = row[j];
293314
- row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a8[i - 1] === b[j - 1] ? 0 : 1));
293315
- prev = cur;
293316
- }
293317
- }
293318
- return row[b.length];
293319
- }
293320
- var ARGUMENT_SYNONYMS = {
293321
- description: ["summary", "intent", "request"],
293322
- desc: ["summary", "intent"],
293323
- title: ["name"],
293324
- folder: ["directory", "path"],
293325
- dir: ["directory", "path"]
293326
- };
293327
- function closestArgument(key2, declared) {
293328
- const same = declared.find((d) => fold(d) === fold(key2));
293329
- if (same) return same;
293330
- const synonym = (ARGUMENT_SYNONYMS[fold(key2)] ?? []).find((s) => declared.includes(s));
293331
- if (synonym) return synonym;
293332
- let best;
293333
- let bestDistance = 3;
293334
- for (const d of declared) {
293335
- const n = distance2(fold(key2), fold(d));
293336
- if (n < bestDistance) {
293337
- best = d;
293338
- bestDistance = n;
293339
- }
293340
- }
293341
- return best;
293342
- }
293343
- function unknownArgumentKeys(schema, args) {
293344
- const declared = declaredKeys(schema);
293345
- if (!declared || !args || typeof args !== "object" || Array.isArray(args)) return [];
293346
- return Object.keys(args).filter((key2) => !declared.includes(key2));
293347
- }
293348
- function unknownArgumentsRefusal(tool, schema, unknown3) {
293349
- const declared = declaredKeys(schema) ?? [];
293350
- const named = unknown3.map((key2) => {
293351
- const meant = closestArgument(key2, declared);
293352
- return meant ? `"${key2}" (did you mean "${meant}"?)` : `"${key2}"`;
293353
- });
293354
- return {
293355
- error: {
293356
- code: "invalid-argument",
293357
- message: `${tool} has no argument ${named.join(", ")}. Nothing was done: an argument a tool does not have would be dropped without a word. Its arguments are: ${declared.join(", ")}.`,
293358
- details: {
293359
- unknownArguments: [...unknown3],
293360
- didYouMean: Object.fromEntries(unknown3.map((key2) => [key2, closestArgument(key2, declared) ?? null]))
293361
- }
293362
- }
293363
- };
293364
- }
293365
- function refuseUnknownArguments(server) {
293366
- const s = server;
293367
- const original = s.validateToolInput.bind(server);
293368
- s.validateToolInput = async (tool, args, toolName) => {
293369
- const unknown3 = unknownArgumentKeys(tool.inputSchema, args);
293370
- if (unknown3.length > 0) throw new Error(JSON.stringify(unknownArgumentsRefusal(toolName, tool.inputSchema, unknown3), null, 2));
293371
- return original(tool, args, toolName);
293372
- };
293373
- }
293374
-
293375
294476
  // src/server.ts
293376
294477
  var PKG_VERSION3 = (
293377
294478
  // eslint-disable-next-line @typescript-eslint/no-var-requires
293378
- true ? "0.4.2" : null.version
294479
+ true ? "0.4.3" : null.version
293379
294480
  );
293380
294481
  function createServer2(options) {
293381
294482
  setClientProfile(options.profile);
@@ -293426,6 +294527,7 @@ function createServer2(options) {
293426
294527
  registerAuthorTools(rec, binding, planRegistry, exampleBudget, renderLedger, disclosure);
293427
294528
  registerPlanTools(rec, binding, planRegistry, exampleBudget, renderLedger, disclosure);
293428
294529
  registerStyleWriteTools(rec, binding);
294530
+ registerElementStyleTools(rec, binding);
293429
294531
  registerDocsWriteTools(rec, binding);
293430
294532
  registerScriptWriteTools(rec, binding);
293431
294533
  registerBackendWriteTools(rec, binding);