@nodegx/preview 0.2.7 → 0.2.8

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.
@@ -13786,7 +13786,7 @@ var require_common2 = __commonJS({
13786
13786
  exports2.ownProp = ownProp;
13787
13787
  exports2.makeAbs = makeAbs;
13788
13788
  exports2.finish = finish3;
13789
- exports2.mark = mark;
13789
+ exports2.mark = mark2;
13790
13790
  exports2.isIgnored = isIgnored;
13791
13791
  exports2.childrenIgnored = childrenIgnored;
13792
13792
  function ownProp(obj, field2) {
@@ -13922,7 +13922,7 @@ var require_common2 = __commonJS({
13922
13922
  });
13923
13923
  self2.found = all;
13924
13924
  }
13925
- function mark(self2, p) {
13925
+ function mark2(self2, p) {
13926
13926
  var abs = makeAbs(self2, p);
13927
13927
  var c = self2.cache[abs];
13928
13928
  var m = p;
@@ -19380,10 +19380,10 @@ var require_nodescope = __commonJS({
19380
19380
  } else {
19381
19381
  const parameters = nodeModel.parameters;
19382
19382
  var inputNames = Object.keys(parameters);
19383
- const RUN_ON_CHANGE_PREFIX4 = "runOnChange-";
19383
+ const RUN_ON_CHANGE_PREFIX5 = "runOnChange-";
19384
19384
  const metadata = this.context.nodeRegister.hasNode(node.name) ? this.context.nodeRegister.getNodeMetadata(node.name) : void 0;
19385
19385
  const priorityOf = function(name2) {
19386
- if (name2.indexOf(RUN_ON_CHANGE_PREFIX4) === 0) return Number.MAX_SAFE_INTEGER;
19386
+ if (name2.indexOf(RUN_ON_CHANGE_PREFIX5) === 0) return Number.MAX_SAFE_INTEGER;
19387
19387
  const input2 = metadata ? metadata.inputs[name2] : void 0;
19388
19388
  return input2 ? input2.inputPriority : 0;
19389
19389
  };
@@ -23328,7 +23328,10 @@ var require_nodedefinition = __commonJS({
23328
23328
  node.registerOutput(name2, {
23329
23329
  getter: function() {
23330
23330
  return void 0;
23331
- }
23331
+ },
23332
+ onFirstConnectionAdded: output.onFirstConnectionAdded,
23333
+ onLastConnectionRemoved: output.onLastConnectionRemoved
23334
+ // a signal keeps its wire hooks (P121-TWO003)
23332
23335
  });
23333
23336
  } else {
23334
23337
  node.registerOutput(name2, output);
@@ -27291,6 +27294,7 @@ var require_simplejavascript = __commonJS({
27291
27294
  return names2;
27292
27295
  }
27293
27296
  function publishHeldWrites(node) {
27297
+ sendHeldChanges(node);
27294
27298
  node._internal.heldWrites.forEach((prop) => {
27295
27299
  node.registerOutputIfNeeded("out-" + prop);
27296
27300
  node.flagOutputDirty("out-" + prop);
@@ -27334,7 +27338,7 @@ var require_simplejavascript = __commonJS({
27334
27338
  if (value2 !== this._internal.outputValues[prop]) {
27335
27339
  this.registerOutputIfNeeded("out-" + prop);
27336
27340
  this._internal.outputValues[prop] = value2;
27337
- this.flagOutputDirty("out-" + prop);
27341
+ holdChange(this, prop);
27338
27342
  this._internal.heldWrites.delete(prop);
27339
27343
  } else {
27340
27344
  this._internal.heldWrites.add(prop);
@@ -27513,7 +27517,7 @@ var require_simplejavascript = __commonJS({
27513
27517
  });
27514
27518
  },
27515
27519
  runScript: async function() {
27516
- const func = this._internal.func;
27520
+ const func = sendingChangesAfterTheSyncPart(this, this._internal.func);
27517
27521
  const tokens = this._internal.pendingRunOutcomes || [];
27518
27522
  this._internal.pendingRunOutcomes = void 0;
27519
27523
  if (func === void 0) {
@@ -27955,6 +27959,32 @@ var require_simplejavascript = __commonJS({
27955
27959
  });
27956
27960
  }
27957
27961
  };
27962
+ function holdChange(node, prop) {
27963
+ const internal2 = node._internal;
27964
+ if (!internal2.heldChanges) internal2.heldChanges = /* @__PURE__ */ new Set();
27965
+ internal2.heldChanges.add(prop);
27966
+ if (internal2.heldChangesQueued) return;
27967
+ internal2.heldChangesQueued = true;
27968
+ Promise.resolve().then(() => sendHeldChanges(node));
27969
+ }
27970
+ function sendHeldChanges(node) {
27971
+ const internal2 = node._internal;
27972
+ internal2.heldChangesQueued = false;
27973
+ const held = internal2.heldChanges;
27974
+ if (!held || held.size === 0 || node._deleted) return;
27975
+ internal2.heldChanges = /* @__PURE__ */ new Set();
27976
+ held.forEach((prop) => node.flagOutputDirty("out-" + prop));
27977
+ }
27978
+ function sendingChangesAfterTheSyncPart(node, func) {
27979
+ if (func === void 0) return void 0;
27980
+ return function(...args) {
27981
+ try {
27982
+ return func.apply(this, args);
27983
+ } finally {
27984
+ sendHeldChanges(node);
27985
+ }
27986
+ };
27987
+ }
27958
27988
  module2.exports = SimpleJavascriptNodeModule;
27959
27989
  }
27960
27990
  });
@@ -69189,6 +69219,133 @@ var init_tooltips = __esm({
69189
69219
  }
69190
69220
  });
69191
69221
 
69222
+ // ../noodl-viewer-react/src/keyboard-click.ts
69223
+ function holdsPressable(el) {
69224
+ return el.querySelector(PRESSABLE_INSIDE) !== null;
69225
+ }
69226
+ function clickIsWired(node) {
69227
+ if (!node.hasOutput("onClick")) return false;
69228
+ return reachesSomething(node.getOutput("onClick"), 0);
69229
+ }
69230
+ function reachesSomething(output, depth) {
69231
+ if (!output.hasConnections()) return false;
69232
+ if (!Array.isArray(output.connections)) return true;
69233
+ return output.connections.some((c) => !goesNowhere(c, depth));
69234
+ }
69235
+ function goesNowhere(connection, depth) {
69236
+ const target = connection.node;
69237
+ if (!target || target.name !== "Component Outputs" || typeof connection.inputPortName !== "string") return false;
69238
+ const owner = target.nodeScope?.componentOwner;
69239
+ if (!owner || depth > 16 || owner._internal?.creatorCallbacks) return false;
69240
+ if (typeof owner.hasOutput !== "function" || typeof owner.getOutput !== "function") return false;
69241
+ if (!owner.hasOutput(connection.inputPortName)) return true;
69242
+ return !reachesSomething(owner.getOutput(connection.inputPortName), depth + 1);
69243
+ }
69244
+ function isNativelyInteractive(el) {
69245
+ const tag = el.tagName;
69246
+ if (tag === "BUTTON" || tag === "INPUT" || tag === "SELECT" || tag === "TEXTAREA" || tag === "SUMMARY") return true;
69247
+ if ((tag === "A" || tag === "AREA") && el.hasAttribute("href")) return true;
69248
+ if ((tag === "VIDEO" || tag === "AUDIO") && el.hasAttribute("controls")) return true;
69249
+ return el.isContentEditable === true;
69250
+ }
69251
+ function isSpace(e) {
69252
+ return e.key === " " || e.key === "Spacebar";
69253
+ }
69254
+ function isOwnKey(el, e) {
69255
+ return e.target === el && !e.defaultPrevented && !e.altKey && !e.ctrlKey && !e.metaKey && !e.isComposing;
69256
+ }
69257
+ function mark(el) {
69258
+ let spaceArmed = false;
69259
+ const onKeyDown2 = (e) => {
69260
+ if (!isOwnKey(el, e)) return;
69261
+ if (e.key === "Enter") {
69262
+ e.preventDefault();
69263
+ if (!e.repeat) el.click();
69264
+ } else if (isSpace(e)) {
69265
+ e.preventDefault();
69266
+ if (!e.repeat) spaceArmed = true;
69267
+ }
69268
+ };
69269
+ const onKeyUp = (e) => {
69270
+ if (!isSpace(e)) return;
69271
+ const armed6 = spaceArmed;
69272
+ spaceArmed = false;
69273
+ if (armed6 && e.target === el && !e.defaultPrevented) el.click();
69274
+ };
69275
+ const m = { el, added: [], onKeyDown: onKeyDown2, onKeyUp, observer: null };
69276
+ if (isNativelyInteractive(el)) return m;
69277
+ refresh(m);
69278
+ if (typeof MutationObserver !== "undefined") {
69279
+ m.observer = new MutationObserver(() => refresh(m));
69280
+ m.observer.observe(el, {
69281
+ childList: true,
69282
+ subtree: true,
69283
+ attributes: true,
69284
+ attributeFilter: ["tabindex", "contenteditable", "href", "controls", "type"]
69285
+ });
69286
+ }
69287
+ return m;
69288
+ }
69289
+ function refresh(m) {
69290
+ const { el } = m;
69291
+ const want = !holdsPressable(el);
69292
+ if (want && !m.added.length) {
69293
+ if (!el.hasAttribute("tabindex")) {
69294
+ el.setAttribute("tabindex", "0");
69295
+ m.added.push("tabindex");
69296
+ }
69297
+ if (!el.hasAttribute("role")) {
69298
+ el.setAttribute("role", "button");
69299
+ m.added.push("role");
69300
+ }
69301
+ if (m.added.length) {
69302
+ el.setAttribute(KEYBOARD_CLICK_ATTRIBUTE, "");
69303
+ m.added.push(KEYBOARD_CLICK_ATTRIBUTE);
69304
+ el.addEventListener("keydown", m.onKeyDown);
69305
+ el.addEventListener("keyup", m.onKeyUp);
69306
+ }
69307
+ } else if (!want && m.added.length) {
69308
+ giveBack(m);
69309
+ }
69310
+ }
69311
+ function giveBack(m) {
69312
+ for (const name2 of m.added) m.el.removeAttribute(name2);
69313
+ m.added = [];
69314
+ m.el.removeEventListener("keydown", m.onKeyDown);
69315
+ m.el.removeEventListener("keyup", m.onKeyUp);
69316
+ }
69317
+ function unmark(m) {
69318
+ m.observer?.disconnect();
69319
+ giveBack(m);
69320
+ }
69321
+ function syncKeyboardClick(node) {
69322
+ const el = typeof node.getDOMElement === "function" ? node.getDOMElement() : null;
69323
+ const want = el && clickIsWired(node) ? el : null;
69324
+ const was = marked.get(node);
69325
+ if (was && was.el === want) return;
69326
+ if (was) {
69327
+ unmark(was);
69328
+ marked.delete(node);
69329
+ }
69330
+ if (want) marked.set(node, mark(want));
69331
+ }
69332
+ var KEYBOARD_CLICK_ATTRIBUTE, PRESSABLE_INSIDE, marked, KEYBOARD_CLICK_HOOKS;
69333
+ var init_keyboard_click = __esm({
69334
+ "../noodl-viewer-react/src/keyboard-click.ts"() {
69335
+ KEYBOARD_CLICK_ATTRIBUTE = "data-ndl-keyboard-click";
69336
+ PRESSABLE_INSIDE = 'button, input:not([type="hidden"]), select, textarea, summary, a[href], area[href], video[controls], audio[controls], [contenteditable=""], [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
69337
+ marked = /* @__PURE__ */ new WeakMap();
69338
+ KEYBOARD_CLICK_HOOKS = {
69339
+ onFirstConnectionAdded() {
69340
+ syncKeyboardClick(this);
69341
+ },
69342
+ onLastConnectionRemoved() {
69343
+ syncKeyboardClick(this);
69344
+ }
69345
+ };
69346
+ }
69347
+ });
69348
+
69192
69349
  // ../noodl-viewer-react/src/node-shared-port-definitions.ts
69193
69350
  function humanCorner(suffix) {
69194
69351
  return suffix.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
@@ -69456,6 +69613,7 @@ var init_node_shared_port_definitions = __esm({
69456
69613
  init_fontloader();
69457
69614
  init_media_source();
69458
69615
  init_tooltips();
69616
+ init_keyboard_click();
69459
69617
  SIZE_MODES = ["explicit", "contentWidth", "contentHeight", "contentSize"];
69460
69618
  node_shared_port_definitions_default = {
69461
69619
  addInputProps: addInputProps2,
@@ -69963,7 +70121,9 @@ var init_node_shared_port_definitions = __esm({
69963
70121
  description: "Fires when this element is clicked or tapped",
69964
70122
  group: "Events",
69965
70123
  type: "signal",
69966
- propPath: "pointer"
70124
+ propPath: "pointer",
70125
+ ...KEYBOARD_CLICK_HOOKS
70126
+ // P121-TWO003-GROUPKEYBOARD: wired, the element is a button to Tab and a screen reader
69967
70127
  },
69968
70128
  pointerDown: {
69969
70129
  displayName: "Pointer Down",
@@ -73684,6 +73844,7 @@ function createNodeFromReactComponent(def2) {
73684
73844
  }
73685
73845
  if (element) syncDataAttributesOnElement(this);
73686
73846
  if (element) elementChanged(this, element);
73847
+ syncKeyboardClick(this);
73687
73848
  },
73688
73849
  /**
73689
73850
  * NDA-012 (Visual) check A3 — run an imperative action on the inner React component,
@@ -74110,6 +74271,7 @@ var init_react_component_node = __esm({
74110
74271
  init_mergedeep();
74111
74272
  init_node_shared_port_definitions();
74112
74273
  init_node_transitions();
74274
+ init_keyboard_click();
74113
74275
  ICON_SOURCE_DIAGNOSTIC = "visual/icon-source-not-an-icon";
74114
74276
  DATA_ATTRIBUTE_NAME_PATTERN = "^data-[a-z0-9][a-z0-9._-]*$";
74115
74277
  DATA_ATTRIBUTE_NAME = new RegExp(DATA_ATTRIBUTE_NAME_PATTERN);
@@ -80880,6 +81042,38 @@ function _typeName(t) {
80880
81042
  if (typeof t === "object") return t.name;
80881
81043
  else return t;
80882
81044
  }
81045
+ function isInsideRow(node, row) {
81046
+ if (node === row) return true;
81047
+ let owner = (node.parentNodeScope || node.nodeScope)?.componentOwner;
81048
+ for (let depth = 0; owner && depth < 64; depth++) {
81049
+ if (owner === row) return true;
81050
+ const next2 = (owner.parentNodeScope || owner.nodeScope)?.componentOwner;
81051
+ if (next2 === owner) return false;
81052
+ owner = next2;
81053
+ }
81054
+ return false;
81055
+ }
81056
+ function settleRow(context, row) {
81057
+ const self2 = row;
81058
+ for (let pass = 0; pass < 10; pass++) {
81059
+ const dirty = context._dirtyNodes;
81060
+ if (!Array.isArray(dirty)) return;
81061
+ const mine = [];
81062
+ for (let i2 = 0; i2 < dirty.length; i2++) {
81063
+ const node = dirty[i2];
81064
+ if (node && node._dirty && !node._deleted && mine.indexOf(node) === -1 && isInsideRow(node, self2))
81065
+ mine.push(node);
81066
+ }
81067
+ if (mine.length === 0) return;
81068
+ for (let i2 = 0; i2 < mine.length; i2++) {
81069
+ try {
81070
+ mine[i2].update();
81071
+ } catch (e) {
81072
+ console.error(e);
81073
+ }
81074
+ }
81075
+ }
81076
+ }
80883
81077
  var import_react16, import_runtime4, import_collection, import_model5, ITEMS_DIAGNOSTIC, defaultDynamicScript, ForEachDefinition, defaultMapCode, ForEachModule, foreach_default;
80884
81078
  var init_foreach = __esm({
80885
81079
  "../noodl-viewer-react/src/nodes/std-library/data/foreach.tsx"() {
@@ -81253,6 +81447,7 @@ var init_foreach = __esm({
81253
81447
  }
81254
81448
  }
81255
81449
  };
81450
+ settleRow(this.context, itemNode);
81256
81451
  const forEachActions = itemNode.nodeScope.getNodesWithType("For Each Actions");
81257
81452
  for (let j = 0; j < forEachActions.length; j++) {
81258
81453
  forEachActions[j].signalAdded();
@@ -90070,10 +90265,16 @@ function Text(props) {
90070
90265
  ...pointerProps(props),
90071
90266
  style: style2
90072
90267
  },
90073
- renderableText(props.text)
90268
+ style2.textOverflow === "ellipsis" ? (
90269
+ // P18-S112-FLEXELLIPSIS — the Text is `display: flex` (its default CSS, which Text Vertical Align's `alignItems`
90270
+ // needs), so its words were an anonymous flex item and `text-overflow` on the element drew no "…": Ellipsis
90271
+ // looked the same as Clip. The words go in a block of their own that may shrink to the box and draws the "…";
90272
+ // the element keeps its flex, so both alignments still place them.
90273
+ /* @__PURE__ */ import_react22.default.createElement("span", { style: { ...ELLIPSIS_LINE, overflow: style2.overflow } }, renderableText(props.text))
90274
+ ) : renderableText(props.text)
90074
90275
  );
90075
90276
  }
90076
- var import_react22;
90277
+ var import_react22, ELLIPSIS_LINE;
90077
90278
  var init_Text = __esm({
90078
90279
  "../noodl-viewer-react/src/components/visual/Text/Text.tsx"() {
90079
90280
  import_react22 = __toESM(require_react());
@@ -90081,6 +90282,7 @@ var init_Text = __esm({
90081
90282
  init_layout();
90082
90283
  init_pointerlisteners();
90083
90284
  init_noodl_root_ref();
90285
+ ELLIPSIS_LINE = { minWidth: 0, textOverflow: "ellipsis", whiteSpace: "nowrap" };
90084
90286
  }
90085
90287
  });
90086
90288
 
@@ -90454,7 +90656,6 @@ var init_Video = __esm({
90454
90656
  ...videoProps,
90455
90657
  playsInline: true,
90456
90658
  src,
90457
- ...pointerProps(this.props),
90458
90659
  ref: (video) => {
90459
90660
  this.video = video;
90460
90661
  this.props.innerRef(video);
@@ -90473,7 +90674,7 @@ var init_Video = __esm({
90473
90674
  this.canPlay = false;
90474
90675
  }
90475
90676
  setSourceObject(src) {
90476
- if (this.video.srcObject !== src) {
90677
+ if (this.video && this.video.srcObject !== src) {
90477
90678
  this.video.srcObject = src;
90478
90679
  this.canPlay = false;
90479
90680
  }
@@ -90579,6 +90780,7 @@ var init_Video = __esm({
90579
90780
  CachedVideo,
90580
90781
  {
90581
90782
  ...props.dom,
90783
+ ...pointerProps(this.props),
90582
90784
  className: withDefaultsClass(props.className, defaultsClass),
90583
90785
  style: style2,
90584
90786
  innerRef: (video) => {
@@ -97687,6 +97889,7 @@ var init_parentcomponentobject = __esm({
97687
97889
  */
97688
97890
  nodeScopeDidInitialize() {
97689
97891
  this.context.scheduleAfterUpdate(() => {
97892
+ if (this._deleted) return;
97690
97893
  this._internal.resolutionIsLoud = true;
97691
97894
  this.onComponentStateNodesChanged();
97692
97895
  });
@@ -102063,7 +102266,7 @@ var init_signinwith = __esm({
102063
102266
  const tokens = [this.beginOutcome()];
102064
102267
  if (state2.succeeded) {
102065
102268
  this._internal.notice = state2.notice;
102066
- this._internal.error = void 0;
102269
+ this._internal.error = "";
102067
102270
  this.flagOutputDirty("notice");
102068
102271
  this.flagOutputDirty("error");
102069
102272
  reportOutcomes(this, tokens, "done");
@@ -164973,7 +165176,7 @@ var node_catalog_default = {
164973
165176
  "always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
164974
165177
  ],
164975
165178
  visible: [
164976
- "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it"
165179
+ "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
164977
165180
  ]
164978
165181
  },
164979
165182
  warnings: {
@@ -167158,7 +167361,7 @@ var node_catalog_default = {
167158
167361
  "always: the page, or the scrolling Group holding it, scrolls until the box can be seen"
167159
167362
  ],
167160
167363
  visible: [
167161
- "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it"
167364
+ "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
167162
167365
  ]
167163
167366
  },
167164
167367
  warnings: {
@@ -169770,7 +169973,7 @@ var node_catalog_default = {
169770
169973
  "always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
169771
169974
  ],
169772
169975
  visible: [
169773
- "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it"
169976
+ "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
169774
169977
  ]
169775
169978
  },
169776
169979
  warnings: {
@@ -171952,7 +172155,7 @@ var node_catalog_default = {
171952
172155
  "always: the page, or the scrolling Group holding it, scrolls until the button can be seen"
171953
172156
  ],
171954
172157
  visible: [
171955
- "always: shown or hidden at the next paint, its space kept; hidden, it takes no click and a Focus does not reach it"
172158
+ "always: shown or hidden at once, its space kept; hidden, it takes no click and a Focus does not reach it"
171956
172159
  ]
171957
172160
  },
171958
172161
  warnings: {
@@ -174357,7 +174560,7 @@ var node_catalog_default = {
174357
174560
  "always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
174358
174561
  ],
174359
174562
  visible: [
174360
- "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it"
174563
+ "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
174361
174564
  ]
174362
174565
  },
174363
174566
  warnings: {
@@ -177227,7 +177430,7 @@ var node_catalog_default = {
177227
177430
  "always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
177228
177431
  ],
177229
177432
  visible: [
177230
- "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it"
177433
+ "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
177231
177434
  ],
177232
177435
  opacity: [
177233
177436
  "always: how see-through it is; at exactly 0 it takes no pointer, and an act goes to what is behind it"
@@ -182069,7 +182272,7 @@ var node_catalog_default = {
182069
182272
  },
182070
182273
  isSignal: false,
182071
182274
  default: false,
182072
- description: "Whether a request with no valid session token is allowed to run this function at all"
182275
+ description: "Whether a request with no valid session token is allowed to run this function at all. A run the app starts itself \u2014 a schedule, a row change, a run from Studio \u2014 is always allowed and sees nobody signed in; a webhook is a request like any other"
182073
182276
  },
182074
182277
  {
182075
182278
  name: "params",
@@ -187095,8 +187298,8 @@ function renderMarkdown(md) {
187095
187298
  continue;
187096
187299
  }
187097
187300
  if (lines.every((l) => /^\s*>\s?/.test(l))) {
187098
- const quoted = lines.map((l) => l.replace(/^\s*>\s?/, "")).join("\n");
187099
- html.push(`<blockquote>${renderMarkdown(quoted)}</blockquote>`);
187301
+ const quoted2 = lines.map((l) => l.replace(/^\s*>\s?/, "")).join("\n");
187302
+ html.push(`<blockquote>${renderMarkdown(quoted2)}</blockquote>`);
187100
187303
  continue;
187101
187304
  }
187102
187305
  if (lines.every((l) => /^\s*[-*]\s+/.test(l))) {
@@ -199194,7 +199397,10 @@ var invocations = [
199194
199397
  { headers: { "X-Parse-Session-Token": "t1", origin: "null", host: "h" } },
199195
199398
  { headers: { "x-parse-session-token": "t1" }, body: '{"a":null}', at: 100 },
199196
199399
  { headers: { host: "internal:8577", "x-forwarded-host": "public.example.com", "x-forwarded-proto": "https, http" }, body: '{"total":"7"}' },
199197
- { headers: { origin: ["https://app.example.com/", "https://b.example"], "x-forwarded-proto": "jAvascript", host: "h" }, body: "{}" }
199400
+ { headers: { origin: ["https://app.example.com/", "https://b.example"], "x-forwarded-proto": "jAvascript", host: "h" }, body: "{}" },
199401
+ { headers: {}, body: '{"total":3}', startedByApp: true },
199402
+ { headers: { "x-parse-session-token": "t1" }, body: "{}", startedByApp: true },
199403
+ { headers: { startedbyapp: "true" }, body: '{"startedByApp":true}' }
199198
199404
  ];
199199
199405
  var CLOUD = { appId: "app", masterKey: "mk", endpoint: "https://be.example" };
199200
199406
  var HOSTS3 = [{}];
@@ -199205,7 +199411,7 @@ for (const invocation of invocations) {
199205
199411
  HOSTS3.push({ invocation: invocations[0], cloudServices: { ...CLOUD, runId: "run-1" } });
199206
199412
  var CloudRequest = defineNode({
199207
199413
  type: "noodl.cloud.request",
199208
- version: 3,
199414
+ version: 4,
199209
199415
  changes: [
199210
199416
  {
199211
199417
  version: 2,
@@ -199218,6 +199424,12 @@ var CloudRequest = defineNode({
199218
199424
  says: "User Id is text (it carries the id of the person the session token resolved to). It was declared a flag, so a wire into a Number read the id as NaN and the canvas offered it to a signal input",
199219
199425
  row: "P107-S97-USERIDBOOLEAN (the Beta sweep s6)",
199220
199426
  migration: { none: "No saved wire stops connecting: measured 106 wires from User Id in 13,839 project files, 70 into script inputs (any type), 35 into text ports, 1 into a record field, none into a signal" }
199427
+ },
199428
+ {
199429
+ version: 4,
199430
+ says: "A run the app starts itself \u2014 a schedule, a row change, a run from Studio \u2014 is not refused when Allow Unauthenticated is off: it runs with nobody signed in. It was refused, so a weekly job could only run a function opened to everyone. A webhook and a direct call are refused as before",
199431
+ row: "P127-BKO006-SCHEDULEAUTH (Richard ruled 2026-10-27: a run the app starts itself counts as the app)",
199432
+ migration: { none: "Nothing a project stores changes: only a run the backend marks as started by the app gets a different answer, and it gets the one its author had to open the function to everyone to get" }
199221
199433
  }
199222
199434
  ],
199223
199435
  source: "packages/noodl-viewer-cloud/src/nodes/cloud/request.ts; packages/noodl-viewer-cloud/src/nodes/cloud/requestContract.ts; packages/noodl-viewer-cloud/src/nodes/cloud/requestOrigin.ts; packages/noodl-viewer-cloud/src/services/userservice.js",
@@ -199244,7 +199456,7 @@ var CloudRequest = defineNode({
199244
199456
  state: { contract: {}, authenticated: void 0, authUserId: void 0, origin: void 0, requestParameters: {}, waiting: void 0 },
199245
199457
  inputs: {
199246
199458
  // :154-163 — held raw, read when the request comes and again when its lookup fails
199247
- allowNoAuth: { type: "boolean", default: false, coerce: "none", displayName: "Allow Unauthenticated", group: "General", description: "Whether a request with no valid session token is allowed to run this function at all", examples: [true, false, "false", 1] },
199459
+ allowNoAuth: { type: "boolean", default: false, coerce: "none", displayName: "Allow Unauthenticated", group: "General", description: "Whether a request with no valid session token is allowed to run this function at all. A run the app starts itself \u2014 a schedule, a row change, a run from Studio \u2014 is always allowed and sees nobody signed in; a webhook is a request like any other", examples: [true, false, "false", 1] },
199248
199460
  // :164-172 — held raw
199249
199461
  params: { type: "stringlist", coerce: "none", editOnly: true, group: "Parameters", description: "Names to pull out of the request body, each becoming an output \u2014 and a Type, Required and Default row to declare what it is", examples: ["total", "total,a", "a,0", "0,1", ""] }
199250
199462
  },
@@ -199291,7 +199503,7 @@ var CloudRequest = defineNode({
199291
199503
  }
199292
199504
  const waiting = { params, headers: req.headers };
199293
199505
  if (!token) {
199294
- if (!i2.allowNoAuth) return { send: [], answers: [{ op: "refuse", reason: UNAUTHENTICATED }] };
199506
+ if (!i2.allowNoAuth && req.startedByApp !== true) return { send: [], answers: [{ op: "refuse", reason: UNAUTHENTICATED }] };
199295
199507
  return proceed(s, params, req.headers, i2, w, { set: {}, send: [] });
199296
199508
  }
199297
199509
  const cloud = w.hostCloud();
@@ -203105,6 +203317,18 @@ function growChange(version) {
203105
203317
  };
203106
203318
  }
203107
203319
 
203320
+ // ../nodegx-node-spec/src/nodes/visible-at-once.ts
203321
+ function visibleAtOnceChange(version) {
203322
+ return {
203323
+ version,
203324
+ says: "No app does anything differently. Visible shows or hides the control at once: a Focus sent in the same moment as Visible going on reaches it (Focused fires), and one sent as Visible goes off does nothing. The version before said Visible waited for the next paint, so it lost the first Focus and kept the second; the runtime never did either",
203325
+ row: "BETA-S7-DROPDOWNFOCUSDIVERGE",
203326
+ migration: {
203327
+ none: "The runtime did not change; only the spec stopped saying Visible waits for the next paint. No stored project changes"
203328
+ }
203329
+ };
203330
+ }
203331
+
203108
203332
  // ../nodegx-node-spec/src/nodes/button.ts
203109
203333
  var NONE = { send: [] };
203110
203334
  var Button3 = defineNode({
@@ -203113,8 +203337,9 @@ var Button3 = defineNode({
203113
203337
  // v3 (P109 ISL-031, P107-D30 ruled "Reset and announce"): a control leaving the page (Mounted off) takes Focused, Hover and Pressed with it — each goes false, sent if it was true, with Blurred and Hover End for the ones that were on
203114
203338
  // v4 (NSP-019 R35): a Focus that finds the node off the page shows as its warning until a Focus reaches it, and an Icon Source that is not an icon shows as one until an icon or nothing arrives (`diagnostics`)
203115
203339
  // v5 (NSP-019 R38): a wired Width in a row, or a wired Height in a column, that is a percent shows as its warning (wired-grow.ts)
203116
- version: 6,
203117
- changes: [focusAndIconSourceChange(4), growChange(5), repeatWhileSavingChange(6, "click")],
203340
+ // v7 (BETA-S7-DROPDOWNFOCUSDIVERGE, the spec correcting itself): Visible reaches the element at once, so a Focus in the same frame meets it (visible-at-once.ts)
203341
+ version: 7,
203342
+ changes: [focusAndIconSourceChange(4), growChange(5), repeatWhileSavingChange(6, "click"), visibleAtOnceChange(7)],
203118
203343
  diagnostics: { ...focusDiagnostics(controlFocusNotMounted("button")), ...iconSourceDiagnostics(), ...wiredGrowDiagnostic },
203119
203344
  source: "packages/noodl-viewer-react/src/nodes/controls/button.ts",
203120
203345
  needs: ["person"],
@@ -203300,8 +203525,9 @@ var Button3 = defineNode({
203300
203525
  // NSP-019 R38 — node.ts :446-458, react-component-node.ts :706-753 (wired-grow.ts)
203301
203526
  width: sizeReducer("width"),
203302
203527
  height: sizeReducer("height"),
203303
- // node-shared-port-definitions.ts :366-375 — the page follows at the next paint
203304
- visible: (_s, v) => ({ says: "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it", set: { visible: !!v }, send: [] })
203528
+ // node-shared-port-definitions.ts :374-388 — written onto the element at once, so a Focus in the same frame meets it
203529
+ // (visible-at-once.ts)
203530
+ visible: (_s, v) => ({ says: "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it", set: { visible: !!v, pageVisible: !!v }, send: [] })
203305
203531
  },
203306
203532
  {
203307
203533
  // placed on the page with its params set — the first paint (react-component-node.ts :1740-1743 reads `wantsToBeMounted`):
@@ -203382,8 +203608,9 @@ var Checkbox2 = defineNode({
203382
203608
  // v3 (P109 ISL-031, P107-C64 ruled "Ignore touches, keep hover"): a finger on a disabled control reports nothing — no Pressed, no Pointer Down or Up — as Enabled promises; the pointer's hover still reports
203383
203609
  // v4 (P109 ISL-031, P107-D30 ruled "Reset and announce"): a control leaving the page (Mounted off) takes Focused, Hover and Pressed with it — each goes false, sent if it was true, with Blurred and Hover End for the ones that were on
203384
203610
  // v5 (NSP-019 R35): a Focus that finds the node off the page shows as its warning until a Focus reaches it, and an Icon Source that is not an icon shows as one until an icon or nothing arrives (`diagnostics`)
203385
- version: 5,
203386
- changes: [focusAndIconSourceChange(5)],
203611
+ // v6 (BETA-S7-DROPDOWNFOCUSDIVERGE, the spec correcting itself): Visible reaches the element at once, so a Focus in the same frame meets it (visible-at-once.ts)
203612
+ version: 6,
203613
+ changes: [focusAndIconSourceChange(5), visibleAtOnceChange(6)],
203387
203614
  diagnostics: { ...focusDiagnostics(controlFocusNotMounted("checkbox")), ...iconSourceDiagnostics() },
203388
203615
  source: "packages/noodl-viewer-react/src/nodes/controls/checkbox.ts",
203389
203616
  needs: ["person"],
@@ -203567,8 +203794,9 @@ var Checkbox2 = defineNode({
203567
203794
  return { says: "it holds the keyboard: it is taken off it, and the Focused state goes false", set: { focused: false, elementFocused: false }, send: ["focusState"], emit: ["onBlur"], outcome: "done" };
203568
203795
  },
203569
203796
  scrollIntoView: () => ({ says: "always: the page, or the scrolling Group holding it, scrolls until the box can be seen", send: [] }),
203570
- // node-shared-port-definitions.ts :366-375 — the page follows at the next paint
203571
- visible: (_s, v) => ({ says: "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it", set: { visible: !!v }, send: [] })
203797
+ // node-shared-port-definitions.ts :374-388 — written onto the element at once, so a Focus in the same frame meets it
203798
+ // (visible-at-once.ts)
203799
+ visible: (_s, v) => ({ says: "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it", set: { visible: !!v, pageVisible: !!v }, send: [] })
203572
203800
  },
203573
203801
  {
203574
203802
  world: { place: (s) => painted2(s) },
@@ -203693,8 +203921,9 @@ var TextInput3 = defineNode({
203693
203921
  // v6 (P109 ISL-031, P107-D30 ruled "Reset and announce"): a control leaving the page (Mounted off) takes Focused, Hover and Pressed with it — each goes false, sent if it was true, with Blurred and Hover End for the ones that were on
203694
203922
  // v7 (NSP-019 R35): a Focus that finds the node off the page shows as its warning until a Focus reaches it, and an Icon Source that is not an icon shows as one until an icon or nothing arrives (`diagnostics`)
203695
203923
  // v8 (NSP-019 R38): a wired Width in a row, or a wired Height in a column, that is a percent shows as its warning (wired-grow.ts)
203696
- version: 9,
203697
- changes: [focusAndIconSourceChange(7), growChange(8), repeatWhileSavingChange(9, "Enter")],
203924
+ // v10 (BETA-S7-DROPDOWNFOCUSDIVERGE, the spec correcting itself): Visible reaches the element at once, so a Focus in the same frame meets it (visible-at-once.ts)
203925
+ version: 10,
203926
+ changes: [focusAndIconSourceChange(7), growChange(8), repeatWhileSavingChange(9, "Enter"), visibleAtOnceChange(10)],
203698
203927
  diagnostics: { ...focusDiagnostics(FIELD_FOCUS_NOT_MOUNTED), ...iconSourceDiagnostics(), ...wiredGrowDiagnostic },
203699
203928
  source: "packages/noodl-viewer-react/src/nodes/controls/text-input.ts",
203700
203929
  needs: ["person"],
@@ -204015,9 +204244,10 @@ var TextInput3 = defineNode({
204015
204244
  // NSP-019 R38 — node.ts :446-458, react-component-node.ts :706-753 (wired-grow.ts)
204016
204245
  width: sizeReducer("width"),
204017
204246
  height: sizeReducer("height"),
204018
- // node-shared-port-definitions.ts :366-375, :320-328 — the page follows at the next paint (TextInput.tsx :118-120 compares the
204019
- // Opacity the style holds with the number 0)
204020
- visible: (_s, v) => ({ says: "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it", set: { visible: !!v }, send: [] }),
204247
+ // node-shared-port-definitions.ts :374-388 — written onto the element at once, so a Focus in the same frame meets it
204248
+ // (visible-at-once.ts); Opacity (:320-328) follows at the next paint (TextInput.tsx :118-120 compares the Opacity the style
204249
+ // holds with the number 0)
204250
+ visible: (_s, v) => ({ says: "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it", set: { visible: !!v, pageVisible: !!v }, send: [] }),
204021
204251
  opacity: (_s, v) => ({ says: "always: how see-through it is; at exactly 0 it takes no pointer, and an act goes to what is behind it", set: { opacityZero: v === 0 }, send: [] })
204022
204252
  },
204023
204253
  {
@@ -204330,7 +204560,8 @@ var Dropdown = defineNode({
204330
204560
  // v10 (NSP-019 R38): a wired Width in a row, or a wired Height in a column, that is a percent shows as its warning (wired-grow.ts)
204331
204561
  // v11 (P121-TWO005-DROPDOWNSTALE): Items that do not offer the placed node's seed ("Option 1") clear it to '' and send Value
204332
204562
  // v12 (BETA-S7-DROPDOWNFIRSTPICK): with the value in no option, the element keeps its hidden empty option selected, so every option is a pick
204333
- version: 12,
204563
+ // v13 (BETA-S7-DROPDOWNFOCUSDIVERGE, the spec correcting itself): Visible reaches the element at once, so a Focus in the same frame meets it (visible-at-once.ts)
204564
+ version: 13,
204334
204565
  // NSP-010 — v7 is the spec correcting itself, not the runtime changing
204335
204566
  changes: [
204336
204567
  {
@@ -204366,7 +204597,8 @@ var Dropdown = defineNode({
204366
204597
  migration: {
204367
204598
  none: "A pick that did nothing now does what the person asked; nothing an app could rely on, and no stored project changes"
204368
204599
  }
204369
- }
204600
+ },
204601
+ visibleAtOnceChange(13)
204370
204602
  ],
204371
204603
  diagnostics: { ...focusDiagnostics(controlFocusNotMounted("dropdown")), ...iconSourceDiagnostics(), ...wiredGrowDiagnostic },
204372
204604
  source: "packages/noodl-viewer-react/src/nodes/controls/options.ts",
@@ -204648,8 +204880,9 @@ var Dropdown = defineNode({
204648
204880
  // NSP-019 R38 — node.ts :446-458, react-component-node.ts :706-753 (wired-grow.ts)
204649
204881
  width: sizeReducer("width"),
204650
204882
  height: sizeReducer("height"),
204651
- // node-shared-port-definitions.ts :366-375 — the page follows at the next paint
204652
- visible: (_s, v) => ({ says: "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it", set: { visible: !!v }, send: [] })
204883
+ // node-shared-port-definitions.ts :374-388 — written onto the element at once, so a Focus in the same frame meets it
204884
+ // (visible-at-once.ts)
204885
+ visible: (_s, v) => ({ says: "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it", set: { visible: !!v, pageVisible: !!v }, send: [] })
204653
204886
  },
204654
204887
  {
204655
204888
  // react-component-node.ts :785-830 `DATA_ATTRIBUTES_RULE` — one string input per name the Data Attributes list names, written
@@ -204818,7 +205051,8 @@ var Slider2 = defineNode({
204818
205051
  // v7 (NSP-019 R39, ruled "Yes, warn"): a Max below Min warns, as the Counter's Min above Max does; and a Value that is not a
204819
205052
  // number, which the runtime always reported on the error bus, is declared with it (the editor records the `slider/` keys)
204820
205053
  // v8 (NSP-019 R38): a wired Width in a row that is a percent shows as its warning (wired-grow.ts)
204821
- version: 8,
205054
+ // v9 (BETA-S7-DROPDOWNFOCUSDIVERGE, the spec correcting itself): Visible reaches the element at once, so a Focus in the same frame meets it (visible-at-once.ts)
205055
+ version: 9,
204822
205056
  changes: [
204823
205057
  focusChange(6),
204824
205058
  {
@@ -204827,7 +205061,8 @@ var Slider2 = defineNode({
204827
205061
  row: "NSP-019 R39 (P107 s104)",
204828
205062
  migration: { none: "Only the editor is told. Nothing a wire carries changes, and nothing a person does moves differently" }
204829
205063
  },
204830
- growChange(8)
205064
+ growChange(8),
205065
+ visibleAtOnceChange(9)
204831
205066
  ],
204832
205067
  diagnostics: {
204833
205068
  ...focusDiagnostics(controlFocusNotMounted("slider")),
@@ -205061,8 +205296,9 @@ var Slider2 = defineNode({
205061
205296
  scrollIntoView: () => ({ says: "always: the page, or the scrolling Group holding it, scrolls until the slider can be seen", send: [] }),
205062
205297
  // NSP-019 R38 — node.ts :446-458, react-component-node.ts :706-753 (wired-grow.ts)
205063
205298
  width: sizeReducer("width", false),
205064
- // node-shared-port-definitions.ts :366-375 — the page follows at the next paint
205065
- visible: (_s, v) => ({ says: "always: shown or hidden at the next paint, its space kept; hidden, a Focus does not reach it", set: { visible: !!v }, send: [] })
205299
+ // node-shared-port-definitions.ts :374-388 — written onto the element at once, so a Focus in the same frame meets it
205300
+ // (visible-at-once.ts)
205301
+ visible: (_s, v) => ({ says: "always: shown or hidden at once, its space kept; hidden, a Focus does not reach it", set: { visible: !!v, pageVisible: !!v }, send: [] })
205066
205302
  },
205067
205303
  {
205068
205304
  // placed on the page with its params set — the first render: Did Mount, and the mount's effect (Slider.tsx :88-90)
@@ -208042,7 +208278,7 @@ var Video2 = defineNode({
208042
208278
  autoplay: (_s, v) => ({ says: "always: drawn at the next paint, and read as a load completes \u2014 on, a video that may play (Muted, or a person has acted on the page) and that Pause or Reset has not stopped since the load began starts by itself: On Can Play, then On Play", set: { autoplay: v }, send: [] }),
208043
208279
  loop: (_s, v) => ({ says: "always: drawn at the next paint, and read as the video reaches its end \u2014 on, it goes back to the start and plays on; off, it pauses there (On Pause)", set: { loop: v }, send: [] }),
208044
208280
  // :35-47 — on the component, or waiting for it with the actions (`sourceObject`: only a `<video>` takes one, and only nothing is
208045
- // seen to change it; C96, C99 on the runtime)
208281
+ // seen to change it; C99 on the runtime)
208046
208282
  srcObject: (s, v) => {
208047
208283
  if (!s.onPage) return said("off the page: waits for the element behind the actions already waiting, in order, and is written at the paint that puts the video there (at most 16 wait, and the oldest is pushed out)", toPatch2(waitFor(s, { srcObject: v })));
208048
208284
  if (s.kind !== "video" || v !== void 0) return { says: "null (what it already has), a value that is not a stream, or anything on an embedded player: nothing changes \u2014 a live stream itself plays in place of Source, and no scenario holds one", set: {}, send: [], emit: [] };
@@ -210416,7 +210652,7 @@ function start(w, op) {
210416
210652
  if (b === MISSING) w.st.hops = HOPS_ADD_MISSING;
210417
210653
  else if (b === null) w.st.hops = 2;
210418
210654
  else w.st.building = { kind: "add", row: b, index: op.index, at: childIndexFor(w.st.rows, op.index) };
210419
- } else refresh(w);
210655
+ } else refresh2(w);
210420
210656
  }
210421
210657
  function compileMapping(code, rethrow = false) {
210422
210658
  if (!code) return void 0;
@@ -210599,7 +210835,7 @@ function report2(w, heard3) {
210599
210835
  w.pulses.push(`itemOutputSignal-${signal2}`);
210600
210836
  }
210601
210837
  }
210602
- function refresh(w) {
210838
+ function refresh2(w) {
210603
210839
  const st = w.st;
210604
210840
  st.refreshScheduled = false;
210605
210841
  const tokens = st.tokens;
@@ -211615,8 +211851,9 @@ var RadioButton3 = defineNode({
211615
211851
  // v3 (P109 ISL-031, P107-C64 ruled "Ignore touches, keep hover"): a finger on a disabled control reports nothing — no Pressed, no Pointer Down or Up — as Enabled promises; the pointer's hover still reports
211616
211852
  // v4 (P109 ISL-031, P107-D30 ruled "Reset and announce"): a control leaving the page (Mounted off) takes Focused, Hover and Pressed with it — each goes false, sent if it was true, with Blurred and Hover End for the ones that were on
211617
211853
  // v5 (NSP-019 R35): a Focus that finds the node off the page shows as its warning until a Focus reaches it, and an Icon Source that is not an icon shows as one until an icon or nothing arrives (`diagnostics`)
211618
- version: 5,
211619
- changes: [focusAndIconSourceChange(5)],
211854
+ // v6 (BETA-S7-DROPDOWNFOCUSDIVERGE, the spec correcting itself): Visible reaches the element at once, so a Focus in the same frame meets it (visible-at-once.ts)
211855
+ version: 6,
211856
+ changes: [focusAndIconSourceChange(5), visibleAtOnceChange(6)],
211620
211857
  diagnostics: { ...focusDiagnostics(controlFocusNotMounted("radio button")), ...iconSourceDiagnostics() },
211621
211858
  source: "packages/noodl-viewer-react/src/nodes/controls/radiobutton.ts",
211622
211859
  needs: ["person", "group"],
@@ -211808,8 +212045,9 @@ var RadioButton3 = defineNode({
211808
212045
  return { says: "it holds the keyboard: it is taken off it, and the Focused state goes false", set: { focused: false, elementFocused: false }, send: ["focusState"], emit: ["onBlur"], outcome: "done" };
211809
212046
  },
211810
212047
  scrollIntoView: () => ({ says: "always: the page, or the scrolling Group holding it, scrolls until the button can be seen", send: [] }),
211811
- // node-shared-port-definitions.ts :366-375 — the page follows at the next paint
211812
- visible: (_s, v) => ({ says: "always: shown or hidden at the next paint, its space kept; hidden, it takes no click and a Focus does not reach it", set: { visible: !!v }, send: [] })
212048
+ // node-shared-port-definitions.ts :374-388 — written onto the element at once, so a Focus in the same frame meets it
212049
+ // (visible-at-once.ts)
212050
+ visible: (_s, v) => ({ says: "always: shown or hidden at once, its space kept; hidden, it takes no click and a Focus does not reach it", set: { visible: !!v, pageVisible: !!v }, send: [] })
211813
212051
  },
211814
212052
  {
211815
212053
  world: {
@@ -215374,7 +215612,7 @@ function applyReturn(s, r) {
215374
215612
  if (r.inProgress) return { set: { signingIn: true }, send: ["signingIn"] };
215375
215613
  if (s.returned) return { send: [] };
215376
215614
  if (r.succeeded) {
215377
- return { set: { signingIn: false, notice: r.notice, error: void 0, returned: true }, send: ["signingIn", "notice", "error"], opens: [{ outcome: "done" }] };
215615
+ return { set: { signingIn: false, notice: r.notice, error: "", returned: true }, send: ["signingIn", "notice", "error"], opens: [{ outcome: "done" }] };
215378
215616
  }
215379
215617
  return { set: { signingIn: false, error: r.error || SIGN_IN_NOT_COMPLETED, returned: true }, send: ["signingIn", "error"], opens: [{ outcome: "failure", error: SIGN_IN_WITH_ERROR_CODE2 }] };
215380
215618
  }
@@ -215384,7 +215622,19 @@ var SignInWith = defineNode({
215384
215622
  // v1 played the launcher only; no v1 trace moves (no v1 world carried a return)
215385
215623
  // v3 (P109 ISL-030, row C50 ruled "Fire it once"): a return's final state is reported once per page load; v2 reported a
215386
215624
  // provider's error twice — at mount and at the announcement a tick later.
215387
- version: 3,
215625
+ // v4 (Beta sweep s19, row P109-S24-SIGNINWITHERRORKEPT ruled "Blank text, like magic link"): a succeeded return clears Error
215626
+ // to blank text and sends it, as Request Magic Link's Done does (D25); v3 cleared it to `undefined`, never sent.
215627
+ version: 4,
215628
+ changes: [
215629
+ {
215630
+ version: 4,
215631
+ says: "A sign-in that comes back succeeded clears Error to blank text and sends it, so a label wired to Error lets go of the last failure (before, the clear was nothing, which never reaches a wire)",
215632
+ row: "P109-S24-SIGNINWITHERRORKEPT",
215633
+ migration: {
215634
+ none: "A page load gets one sign-in back and the node is new on it, so its Error never held an earlier failure: no project can see the before"
215635
+ }
215636
+ }
215637
+ ],
215388
215638
  source: "packages/noodl-viewer-react/src/nodes/std-library/user/signinwith.ts; userservice.ts; RestAuthAdapter.ts; ParseAuthAdapter.ts",
215389
215639
  needs: ["backend", "registry", "clock"],
215390
215640
  worldPool: { backends: [...pool("signInWithProvider", null), ...RETURNS] },
@@ -231392,7 +231642,7 @@ var cloud_node_library_default = {
231392
231642
  plug: "input",
231393
231643
  group: "General",
231394
231644
  displayName: "Allow Unauthenticated",
231395
- description: "Whether a request with no valid session token is allowed to run this function at all",
231645
+ description: "Whether a request with no valid session token is allowed to run this function at all. A run the app starts itself \u2014 a schedule, a row change, a run from Studio \u2014 is always allowed and sees nobody signed in; a webhook is a request like any other",
231396
231646
  default: false
231397
231647
  },
231398
231648
  {
@@ -235538,7 +235788,11 @@ var AUTHORED_BLOCKING_WARNINGS = /* @__PURE__ */ new Set([
235538
235788
  // Bug P121-TWO003-SCRIPTCOMPILE — the runtime's own compile fails, so every Run fails. No benign
235539
235789
  // reading for a graph an agent just wrote; the write said nothing before, and the first person
235540
235790
  // to run the function got "That could not be done."
235541
- "function-script-not-compiled" /* FunctionScriptNotCompiled */
235791
+ "function-script-not-compiled" /* FunctionScriptNotCompiled */,
235792
+ // Bug P127-BKO006-RUNONCHANGEGUESS — a `runOnChange-` name a Function does not have is never read
235793
+ // (2,805 right and 5 wrong over 2,718 Functions, all five agent-written); the message names the
235794
+ // right one.
235795
+ "function-run-on-change-unknown" /* FunctionRunOnChangeUnknown */
235542
235796
  ]);
235543
235797
 
235544
235798
  // ../noodl-editor/src/editor/src/validation/diagnosticExamples.ts
@@ -235678,6 +235932,12 @@ var DIAGNOSTIC_EXAMPLES = [
235678
235932
  code: "function-guards-an-open-collection" /* FunctionGuardsAnOpenCollection */,
235679
235933
  examples: ["cloud-members-can-edit-my-records"],
235680
235934
  why: `the records only a function creates, with the collection's create rule "nobody": the function runs as the system`
235935
+ },
235936
+ // P112-OWN018-FANOUT (ruled 2026-10-27 "Teach it and warn") — the warning carries the chained shape.
235937
+ {
235938
+ code: "condition-reads-a-siblings-change" /* ConditionReadsASiblingsChange */,
235939
+ examples: ["score-then-move-on"],
235940
+ why: "the press evaluating the gate that reads, and that gate's Done evaluating the gate that moves on, so the score is decided first"
235681
235941
  }
235682
235942
  ];
235683
235943
 
@@ -237264,6 +237524,156 @@ var functionNeverReruns = {
237264
237524
  }
237265
237525
  };
237266
237526
 
237527
+ // ../noodl-editor/src/editor/src/validation/rules/functionRunOnChangeUnknown.ts
237528
+ var RUN_ON_CHANGE_PREFIX4 = "runOnChange-";
237529
+ var FUNCTION_INPUT_PREFIX = "in-";
237530
+ var functionRunOnChangeUnknown = {
237531
+ code: "function-run-on-change-unknown" /* FunctionRunOnChangeUnknown */,
237532
+ description: "A Function has a Run On Value Change setting under a name it does not have (its boxes are runOnChange-in-<input>).",
237533
+ defaultEnabled: true,
237534
+ run(ctx) {
237535
+ const out = [];
237536
+ for (const { component } of ctx.components) {
237537
+ for (const node of component.nodes) {
237538
+ if (node.type !== FUNCTION_NODE_TYPE || !node.parameters) continue;
237539
+ const guessed = Object.keys(node.parameters).filter(
237540
+ (key) => key.startsWith(RUN_ON_CHANGE_PREFIX4) && !key.startsWith(RUN_ON_CHANGE_PREFIX4 + FUNCTION_INPUT_PREFIX)
237541
+ );
237542
+ if (guessed.length === 0) continue;
237543
+ const inputs = scriptPortsForNode(node).filter((p) => p.plug === "input" && p.name.startsWith(FUNCTION_INPUT_PREFIX)).map((p) => p.name.slice(FUNCTION_INPUT_PREFIX.length));
237544
+ const name2 = node.label ? `"${node.label}"` : "This Function";
237545
+ for (const key of guessed) {
237546
+ const wanted = key.slice(RUN_ON_CHANGE_PREFIX4.length);
237547
+ const input2 = inputs.includes(wanted) ? wanted : nearest(wanted, inputs);
237548
+ const right = input2 !== void 0 ? `${RUN_ON_CHANGE_PREFIX4}${FUNCTION_INPUT_PREFIX}${input2}` : void 0;
237549
+ out.push({
237550
+ code: "function-run-on-change-unknown" /* FunctionRunOnChangeUnknown */,
237551
+ severity: "warning",
237552
+ message: `${name2} has no setting "${key}", so nothing reads it` + (input2 === wanted ? ` and a new "${wanted}" still runs it. ` : ". ") + `A Function's Run On Value Change box for an input is "runOnChange-in-<input>"` + (right ? input2 === wanted ? `: write "${right}".` : `; its script reads no "${wanted}" (the nearest input is "${input2}": "${right}").` : `, and its script reads no inputs.`),
237553
+ location: {
237554
+ component: component.name,
237555
+ nodeId: node.id,
237556
+ nodeType: node.type,
237557
+ nodeLabel: node.label,
237558
+ port: key,
237559
+ plug: "input"
237560
+ },
237561
+ ...right ? { suggestion: `${right}: ${JSON.stringify(node.parameters[key])}` } : {}
237562
+ });
237563
+ }
237564
+ }
237565
+ }
237566
+ return out;
237567
+ }
237568
+ };
237569
+
237570
+ // ../noodl-editor/src/editor/src/validation/rules/conditionReadsASiblingsChange.ts
237571
+ var CONDITION = "Condition";
237572
+ var conditionReadsASiblingsChange = {
237573
+ code: "condition-reads-a-siblings-change" /* ConditionReadsASiblingsChange */,
237574
+ description: "Two Conditions evaluated by one signal, where one's On True or On False changes what the other reads.",
237575
+ defaultEnabled: true,
237576
+ run(ctx) {
237577
+ const out = [];
237578
+ const { catalog } = ctx;
237579
+ for (const { component, nodeById } of ctx.components) {
237580
+ let exclusiveStates = function(a, b) {
237581
+ const stateOf = (gate) => {
237582
+ const c = (incoming.get(gate) ?? []).find((w) => w.toProperty === "condition");
237583
+ return c && nodeById.get(c.fromId)?.type === "States" && c.fromProperty.startsWith("at-") ? c : void 0;
237584
+ };
237585
+ const sa = stateOf(a);
237586
+ const sb = stateOf(b);
237587
+ return !!sa && !!sb && sa.fromId === sb.fromId && sa.fromProperty !== sb.fromProperty;
237588
+ }, feedingValues = function(gate) {
237589
+ const seen = /* @__PURE__ */ new Set();
237590
+ const stack = (incoming.get(gate) ?? []).filter((c) => c.toProperty === "condition").map((c) => c.fromId);
237591
+ while (stack.length > 0) {
237592
+ const id = stack.pop();
237593
+ if (id === gate || seen.has(id)) continue;
237594
+ seen.add(id);
237595
+ if (!passesThrough(id)) continue;
237596
+ const type = nodeById.get(id)?.type;
237597
+ for (const c of incoming.get(id) ?? []) {
237598
+ if (type && catalog.getPort(type, "input", c.toProperty)?.isSignal) continue;
237599
+ stack.push(c.fromId);
237600
+ }
237601
+ }
237602
+ return seen;
237603
+ }, reaches5 = function(gate, targets) {
237604
+ if (targets.size === 0) return false;
237605
+ const seen = /* @__PURE__ */ new Set([gate]);
237606
+ const stack = (outgoing.get(gate) ?? []).map((c) => c.toId);
237607
+ while (stack.length > 0) {
237608
+ const id = stack.pop();
237609
+ if (targets.has(id)) return true;
237610
+ if (seen.has(id)) continue;
237611
+ seen.add(id);
237612
+ if (!passesThrough(id)) continue;
237613
+ for (const c of outgoing.get(id) ?? []) stack.push(c.toId);
237614
+ }
237615
+ return false;
237616
+ };
237617
+ const outgoing = /* @__PURE__ */ new Map();
237618
+ const incoming = /* @__PURE__ */ new Map();
237619
+ for (const c of component.connections) {
237620
+ (outgoing.get(c.fromId) ?? outgoing.set(c.fromId, []).get(c.fromId)).push(c);
237621
+ (incoming.get(c.toId) ?? incoming.set(c.toId, []).get(c.toId)).push(c);
237622
+ }
237623
+ const isCondition = (id) => nodeById.get(id)?.type === CONDITION;
237624
+ const passesThrough = (id) => {
237625
+ const type = nodeById.get(id)?.type;
237626
+ const known = type ? catalog.getNode(type) : void 0;
237627
+ return !!known && !known.isVisual;
237628
+ };
237629
+ const gatesBySource = /* @__PURE__ */ new Map();
237630
+ for (const c of component.connections) {
237631
+ if (c.toProperty !== "eval" || !isCondition(c.toId)) continue;
237632
+ const key = `${c.fromId}\0${c.fromProperty}`;
237633
+ const gates = gatesBySource.get(key) ?? [];
237634
+ if (!gates.includes(c.toId)) gates.push(c.toId);
237635
+ gatesBySource.set(key, gates);
237636
+ }
237637
+ const reported3 = /* @__PURE__ */ new Set();
237638
+ for (const [key, gates] of gatesBySource) {
237639
+ if (gates.length < 2) continue;
237640
+ const sourceId = key.split("\0")[0];
237641
+ for (const reader of gates) {
237642
+ if (reported3.has(reader)) continue;
237643
+ const feeds = feedingValues(reader);
237644
+ for (const writer of gates) {
237645
+ if (writer === reader) continue;
237646
+ if (exclusiveStates(reader, writer)) continue;
237647
+ if (!reaches5(writer, feeds)) continue;
237648
+ reported3.add(reader);
237649
+ const r = nodeById.get(reader);
237650
+ const w = nodeById.get(writer);
237651
+ const rName = r.label ? `"${r.label}"` : "This Condition";
237652
+ const wName = w.label ? `"${w.label}"` : "another Condition";
237653
+ const sName = nodeById.get(sourceId)?.label ? `"${nodeById.get(sourceId).label}"` : "the same signal";
237654
+ out.push({
237655
+ code: "condition-reads-a-siblings-change" /* ConditionReadsASiblingsChange */,
237656
+ severity: "warning",
237657
+ message: `${rName} and ${wName} are both evaluated by ${sName}, and what ${wName} does changes what ${rName} reads. ${rName} reads its Condition after that change, whichever order the two are wired in. If it should decide on the value from before, chain them: ${rName}'s Done into ${wName}'s Evaluate, instead of both from ${sName}.`,
237658
+ location: {
237659
+ component: component.name,
237660
+ nodeId: r.id,
237661
+ nodeType: r.type,
237662
+ nodeLabel: r.label,
237663
+ port: "eval",
237664
+ plug: "input"
237665
+ },
237666
+ suggestion: `${r.id}.done \u2192 ${w.id}.eval`
237667
+ });
237668
+ break;
237669
+ }
237670
+ }
237671
+ }
237672
+ }
237673
+ return out;
237674
+ }
237675
+ };
237676
+
237267
237677
  // ../noodl-editor/src/editor/src/validation/rules/layoutFillTraps.ts
237268
237678
  var GROUP = "Group";
237269
237679
  var CONTENT_WIDTH = /* @__PURE__ */ new Set(["contentSize", "contentWidth"]);
@@ -237371,6 +237781,97 @@ var rowOfWordsFillsItsColumn = {
237371
237781
  }
237372
237782
  };
237373
237783
 
237784
+ // ../noodl-editor/src/editor/src/validation/rules/clickAreaHoldsAControl.ts
237785
+ var CONTROL_TYPES = /* @__PURE__ */ new Map([
237786
+ ["net.noodl.controls.button", "Button"],
237787
+ ["net.noodl.controls.textinput", "Text Input"],
237788
+ ["net.noodl.controls.checkbox", "Checkbox"],
237789
+ ["net.noodl.controls.radiobutton", "Radio Button"],
237790
+ ["net.noodl.controls.options", "Dropdown"],
237791
+ ["net.noodl.controls.range", "Slider"]
237792
+ ]);
237793
+ var CLICK = "onClick";
237794
+ var COMPONENT_OUTPUTS2 = "Component Outputs";
237795
+ var clickWired = (component, node) => component.connections.some((c) => c.fromId === node.id && c.fromProperty === CLICK);
237796
+ function reaches4(component, nodeById, node, port5, used) {
237797
+ return component.connections.some((c) => {
237798
+ if (c.fromId !== node.id || c.fromProperty !== port5) return false;
237799
+ if (nodeById.get(c.toId)?.type !== COMPONENT_OUTPUTS2) return true;
237800
+ return used === "all" || used.has(c.toProperty);
237801
+ });
237802
+ }
237803
+ var quoted = (node) => node.label ? ` ("${node.label}")` : "";
237804
+ var clickAreaHoldsAControl = {
237805
+ code: "click-area-holds-a-control" /* ClickAreaHoldsAControl */,
237806
+ description: "An element whose Click is wired holds a control, so the keyboard reaches the control and not the element.",
237807
+ defaultEnabled: true,
237808
+ run(ctx) {
237809
+ const out = [];
237810
+ const byPath = new Map(ctx.components.map((c) => [refToPath(c.component.name), c]));
237811
+ const memo = /* @__PURE__ */ new Map();
237812
+ const componentHolds = (ref, used) => {
237813
+ const path10 = refToPath(ref);
237814
+ const key = `${path10}|${[...used].sort().join(",")}`;
237815
+ if (memo.has(key)) return memo.get(key) ?? null;
237816
+ memo.set(key, null);
237817
+ const entry = byPath.get(path10);
237818
+ let found = null;
237819
+ if (entry) {
237820
+ for (const node of entry.component.nodes) {
237821
+ found = held(node, entry.component, entry.nodeById, used);
237822
+ if (found) break;
237823
+ }
237824
+ }
237825
+ memo.set(key, found);
237826
+ return found;
237827
+ };
237828
+ function held(node, component, nodeById, used) {
237829
+ const control = CONTROL_TYPES.get(node.type);
237830
+ if (control) return `a ${control}${quoted(node)}`;
237831
+ if (reaches4(component, nodeById, node, CLICK, used)) {
237832
+ return `${node.label ? `"${node.label}"` : "an element"}, which answers a click of its own`;
237833
+ }
237834
+ if (isComponentRef(node.type)) {
237835
+ const ports = new Set(component.connections.filter((c) => c.fromId === node.id).map((c) => c.fromProperty));
237836
+ const usedHere = new Set([...ports].filter((port5) => reaches4(component, nodeById, node, port5, used)));
237837
+ const inside = componentHolds(node.type, usedHere);
237838
+ if (inside) return `the component ${node.label ? `"${node.label}"` : `"${refToPath(node.type)}"`}, which holds ${inside}`;
237839
+ }
237840
+ return null;
237841
+ }
237842
+ for (const { component, nodeById } of ctx.components) {
237843
+ for (const area of component.nodes) {
237844
+ if (CONTROL_TYPES.has(area.type) || !clickWired(component, area)) continue;
237845
+ if (!ctx.catalog.getNode(area.type)?.isVisual) continue;
237846
+ let inner = null;
237847
+ const stack = [...area.children ?? []];
237848
+ while (stack.length && !inner) {
237849
+ const node = nodeById.get(stack.shift());
237850
+ if (!node) continue;
237851
+ inner = held(node, component, nodeById, "all");
237852
+ stack.push(...node.children ?? []);
237853
+ }
237854
+ if (!inner) continue;
237855
+ const name2 = area.label ? `"${area.label}"` : `This ${ctx.catalog.getNode(area.type)?.displayName ?? area.type}`;
237856
+ out.push({
237857
+ code: "click-area-holds-a-control" /* ClickAreaHoldsAControl */,
237858
+ severity: "info",
237859
+ message: `${name2} answers a click. It holds ${inner}. The Tab key stops on what it holds instead. A person using a keyboard or a screen reader cannot press ${area.label ? name2 : "it"} itself. If its click does something nothing inside it does, give that its own Button.`,
237860
+ location: {
237861
+ component: component.name,
237862
+ nodeId: area.id,
237863
+ nodeType: area.type,
237864
+ ...area.label ? { nodeLabel: area.label } : {},
237865
+ port: CLICK,
237866
+ plug: "output"
237867
+ }
237868
+ });
237869
+ }
237870
+ }
237871
+ return out;
237872
+ }
237873
+ };
237874
+
237374
237875
  // ../noodl-editor/src/editor/src/validation/rules/index.ts
237375
237876
  var ALL_RULES = [
237376
237877
  duplicateNodeId,
@@ -237427,6 +237928,10 @@ var ALL_RULES = [
237427
237928
  // arrangement ones.
237428
237929
  functionScriptNotCompiled,
237429
237930
  functionNeverReruns,
237931
+ functionRunOnChangeUnknown,
237932
+ // P112-OWN018-FANOUT (ruled 2026-10-27 "Teach it and warn") — beside the Function rules: what a graph's signals do in
237933
+ // what order, read from the wires. Two gates off one signal, one changing what the other reads.
237934
+ conditionReadsASiblingsChange,
237430
237935
  repeatedSiblingSubtree,
237431
237936
  // DEF-025 — beside `repeatedSiblingSubtree` because both read the visual
237432
237937
  // tree's arrangement rather than wires: that one asks whether siblings
@@ -237440,6 +237945,9 @@ var ALL_RULES = [
237440
237945
  // against the shipped corpus before they were written — this one is 0 hits
237441
237946
  // on all ten templates.
237442
237947
  dialogWithoutName,
237948
+ // P121-TWO003-NESTEDBUTTON (ruled 2026-10-27) — beside the two above: what a keyboard and a screen reader reach, read
237949
+ // from the arrangement. An info, live, so the Problems panel and an agent's write both say it.
237950
+ clickAreaHoldsAControl,
237443
237951
  // P112-OWN020-PILLWRAP / FILLGROW (ruled 2026-10-26: warn and teach) — a node with no size setting fills its parent,
237444
237952
  // read from the arrangement like the two above, and live so a person sees it while editing.
237445
237953
  contentRowSplitsItsChildren,