@lotics/cli 0.251.0 → 0.252.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -52,7 +52,7 @@ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__
52
52
  var define_LOTICS_KIT_VERSIONS_default;
53
53
  var init_define_LOTICS_KIT_VERSIONS = __esm({
54
54
  "<define:__LOTICS_KIT_VERSIONS__>"() {
55
- define_LOTICS_KIT_VERSIONS_default = { runtime: "0.37.0" };
55
+ define_LOTICS_KIT_VERSIONS_default = { runtime: "0.37.1" };
56
56
  }
57
57
  });
58
58
 
@@ -2547,20 +2547,20 @@ var require_compiler = __commonJS({
2547
2547
  }
2548
2548
  Compiler.prototype = {
2549
2549
  compiler: Compiler,
2550
- equals: function equals(other) {
2550
+ equals: function equals(other2) {
2551
2551
  var len = this.opcodes.length;
2552
- if (other.opcodes.length !== len) {
2552
+ if (other2.opcodes.length !== len) {
2553
2553
  return false;
2554
2554
  }
2555
2555
  for (var i = 0; i < len; i++) {
2556
- var opcode = this.opcodes[i], otherOpcode = other.opcodes[i];
2556
+ var opcode = this.opcodes[i], otherOpcode = other2.opcodes[i];
2557
2557
  if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
2558
2558
  return false;
2559
2559
  }
2560
2560
  }
2561
2561
  len = this.children.length;
2562
2562
  for (var i = 0; i < len; i++) {
2563
- if (!this.children[i].equals(other.children[i])) {
2563
+ if (!this.children[i].equals(other2.children[i])) {
2564
2564
  return false;
2565
2565
  }
2566
2566
  }
@@ -44847,8 +44847,8 @@ var ZodObject = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
44847
44847
  safeExtend(incoming) {
44848
44848
  return util_exports.safeExtend(this, incoming);
44849
44849
  },
44850
- merge(other) {
44851
- return util_exports.merge(this, other);
44850
+ merge(other2) {
44851
+ return util_exports.merge(this, other2);
44852
44852
  },
44853
44853
  pick(mask) {
44854
44854
  return util_exports.pick(this, mask);
@@ -51975,6 +51975,13 @@ function generatePrefixedId(prefix) {
51975
51975
  }
51976
51976
  var RECORD_ID_PATTERN = new RegExp(`^rec_[${ID_ALPHABET}]{${ID_LENGTH}}$`);
51977
51977
  var FILE_ID_PATTERN = new RegExp(`^fil_[${ID_ALPHABET}]{${ID_LENGTH}}$`);
51978
+ var TABLE_ID_PATTERN = new RegExp(
51979
+ `^tbl_[${ID_ALPHABET}]+$|^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`,
51980
+ "i"
51981
+ );
51982
+ function isTableId(value) {
51983
+ return TABLE_ID_PATTERN.test(value);
51984
+ }
51978
51985
  function generateWorkspaceId() {
51979
51986
  return generatePrefixedId("wsp");
51980
51987
  }
@@ -52000,6 +52007,10 @@ var FIELD_KEY_PREFIX = "fld_";
52000
52007
  function generateFieldKey() {
52001
52008
  return `${FIELD_KEY_PREFIX}${nanoid4()}`;
52002
52009
  }
52010
+ var STORED_KEY_PATTERN = /^[A-Za-z0-9_]+$/;
52011
+ function isStoredKey(value) {
52012
+ return STORED_KEY_PATTERN.test(value);
52013
+ }
52003
52014
  function generateOptionKey() {
52004
52015
  return `opt_${nanoid4()}`;
52005
52016
  }
@@ -52726,9 +52737,9 @@ function unpairableAdoptedLinks(entities, live) {
52726
52737
  for (const field2 of entity.fields) {
52727
52738
  if (field2.type !== "select_record_link") continue;
52728
52739
  if (field2.paired_field_alias === void 0 && field2.sync_both_ways !== true) continue;
52729
- const held = live.link(entity.alias, field2.alias);
52730
- if (held === void 0 || held.paired) continue;
52731
- if (held.target_table !== live.table(field2.target_entity)) continue;
52740
+ const held2 = live.link(entity.alias, field2.alias);
52741
+ if (held2 === void 0 || held2.paired) continue;
52742
+ if (held2.target_table !== live.table(field2.target_entity)) continue;
52732
52743
  found.push({ entity: entity.alias, field: field2.alias });
52733
52744
  }
52734
52745
  }
@@ -52829,15 +52840,15 @@ function selectWordRefusals(entityAlias, expression, fieldByEntity, path39) {
52829
52840
  return parseFieldRefTokens(expression).flatMap((token2) => {
52830
52841
  if (contractOptionRef(token2) !== void 0) return [];
52831
52842
  const field2 = fieldByEntity.get(entityAlias)?.get(token2);
52832
- const held = field2 === void 0 ? void 0 : formulaSelectOptions(entityAlias, field2, fieldByEntity);
52833
- const spelled = held?.options.find((option) => words.has(option.label) || words.has(option.alias));
52834
- if (held === void 0 || spelled === void 0) return [];
52843
+ const held2 = field2 === void 0 ? void 0 : formulaSelectOptions(entityAlias, field2, fieldByEntity);
52844
+ const spelled = held2?.options.find((option) => words.has(option.label) || words.has(option.alias));
52845
+ if (held2 === void 0 || spelled === void 0) return [];
52835
52846
  const word = words.has(spelled.label) ? spelled.label : spelled.alias;
52836
52847
  return [
52837
52848
  {
52838
52849
  severity: "error",
52839
52850
  path: path39,
52840
- message: held.own ? `compares {${token2}} to "${word}", and a select reaches a formula as the KEYS of its options, never their words \u2014 name the option {${token2}:${spelled.alias}}: includes({${token2}}, {${token2}:${spelled.alias}})` : `compares {${token2}} to "${word}", and a looked-up select reaches a formula as the KEYS of its options, never their words \u2014 test it on "${held.entity}", where the select lives ({field:option} names one there), and look that result up`
52851
+ message: held2.own ? `compares {${token2}} to "${word}", and a select reaches a formula as the KEYS of its options, never their words \u2014 name the option {${token2}:${spelled.alias}}: includes({${token2}}, {${token2}:${spelled.alias}})` : `compares {${token2}} to "${word}", and a looked-up select reaches a formula as the KEYS of its options, never their words \u2014 test it on "${held2.entity}", where the select lives ({field:option} names one there), and look that result up`
52841
52852
  }
52842
52853
  ];
52843
52854
  });
@@ -54151,17 +54162,17 @@ function contactHref(text) {
54151
54162
  function navigatesSections(drawn) {
54152
54163
  return drawn >= 3;
54153
54164
  }
54154
- function offeredTogether(one, other) {
54155
- const theirs = other.when;
54165
+ function offeredTogether(one, other2) {
54166
+ const theirs = other2.when;
54156
54167
  if (one.when === void 0 || theirs === void 0) return false;
54157
- const shared = one.when.flatMap((held) => {
54158
- const match2 = theirs.find((candidate) => candidate.field === held.field);
54159
- return match2 === void 0 ? [] : [held.options.some((option) => match2.options.includes(option))];
54168
+ const shared = one.when.flatMap((held2) => {
54169
+ const match2 = theirs.find((candidate) => candidate.field === held2.field);
54170
+ return match2 === void 0 ? [] : [held2.options.some((option) => match2.options.includes(option))];
54160
54171
  });
54161
54172
  return shared.length > 0 && shared.every(Boolean);
54162
54173
  }
54163
54174
  function isOutcome(act, acts) {
54164
- return act.to !== void 0 && acts.some((other) => other.alias !== act.alias && other.to !== void 0 && other.of === act.of && offeredTogether(act, other));
54175
+ return act.to !== void 0 && acts.some((other2) => other2.alias !== act.alias && other2.to !== void 0 && other2.of === act.of && offeredTogether(act, other2));
54165
54176
  }
54166
54177
  function isCorrection(act, acts) {
54167
54178
  return act.danger === true && !isOutcome(act, acts);
@@ -54267,11 +54278,11 @@ function actHome(model, app, act) {
54267
54278
  const order = field2?.type === "select" ? (field2.options ?? []).map((option) => option.alias) : [];
54268
54279
  if (typeof moved !== "string" || !order.includes(moved)) return void 0;
54269
54280
  const sections = app.record?.sections ?? [];
54270
- const held = act.when?.[status];
54271
- const own = held === void 0 ? -1 : sections.findIndex((section) => held.every((option) => section.at?.includes(option) === true));
54281
+ const held2 = act.when?.[status];
54282
+ const own = held2 === void 0 ? -1 : sections.findIndex((section) => held2.every((option) => section.at?.includes(option) === true));
54272
54283
  const into = sections.findIndex((section) => section.at?.includes(moved) === true);
54273
54284
  const earlier = (from, to) => order.indexOf(from) < order.indexOf(to);
54274
- const forward = held !== void 0 && held.some((option) => earlier(option, moved));
54285
+ const forward = held2 !== void 0 && held2.some((option) => earlier(option, moved));
54275
54286
  const passed = (stage) => stage.includes(order[0] ?? "") || actsOver(model, app.entity).some((one) => {
54276
54287
  const to = one.set?.[status];
54277
54288
  return typeof to === "string" && stage.includes(to) && (one.when?.[status] ?? []).some((from) => earlier(from, to));
@@ -54378,8 +54389,8 @@ function withEntityLocks(model, app) {
54378
54389
  const merged = /* @__PURE__ */ new Map();
54379
54390
  for (const check2 of [...app.checks ?? [], ...gathered]) {
54380
54391
  const key = `${check2.of ?? ""}|${check2.field}`;
54381
- const held = merged.get(key);
54382
- merged.set(key, held === void 0 ? check2 : { ...held, ...unionBlocks(held.blocks, check2.blocks) });
54392
+ const held2 = merged.get(key);
54393
+ merged.set(key, held2 === void 0 ? check2 : { ...held2, ...unionBlocks(held2.blocks, check2.blocks) });
54383
54394
  }
54384
54395
  return [...merged.values()];
54385
54396
  }
@@ -54436,8 +54447,8 @@ function historyShape(model, parent) {
54436
54447
  }
54437
54448
  const parentStatus = entityOf(model, parent)?.fields.find((field2) => field2.alias === status.field);
54438
54449
  if (statusField.type === "select" && parentStatus?.type === "select") {
54439
- const held = new Set(statusField.options.map((option) => option.alias));
54440
- const missing = parentStatus.options.filter((option) => !held.has(option.alias)).map((option) => option.alias);
54450
+ const held2 = new Set(statusField.options.map((option) => option.alias));
54451
+ const missing = parentStatus.options.filter((option) => !held2.has(option.alias)).map((option) => option.alias);
54441
54452
  if (missing.length > 0) {
54442
54453
  return { refused: `names history "${history.alias}", whose "${statusField.alias}" lacks the options ${missing.join(", ")} \u2014 a history writes the option the status moved to` };
54443
54454
  }
@@ -54606,11 +54617,11 @@ function checkReading(model, reading, at2) {
54606
54617
  const over = own(reading.over, `${at2}.over`);
54607
54618
  if (over !== void 0 && over.type !== "date") findings.push(error51(`${at2}.over`, `"${reading.over}" is a ${over.type} field \u2014 a reading places its rows in time by a stored date`));
54608
54619
  }
54609
- for (const [name, held] of Object.entries(reading.where ?? {})) {
54620
+ for (const [name, held2] of Object.entries(reading.where ?? {})) {
54610
54621
  const path39 = `${at2}.where.${name}`;
54611
54622
  const field2 = own(name, path39);
54612
54623
  if (field2 === void 0) continue;
54613
- if (typeof held === "boolean") {
54624
+ if (typeof held2 === "boolean") {
54614
54625
  if (field2.type !== "boolean") findings.push(error51(path39, `"${name}" is a ${field2.type} field \u2014 true or false keeps rows by a yes/no`));
54615
54626
  continue;
54616
54627
  }
@@ -54619,7 +54630,7 @@ function checkReading(model, reading, at2) {
54619
54630
  continue;
54620
54631
  }
54621
54632
  const declared = new Set(field2.options.map((option) => option.alias));
54622
- for (const [index, option] of held.entries()) {
54633
+ for (const [index, option] of held2.entries()) {
54623
54634
  if (!declared.has(option)) findings.push(error51(`${path39}.${index}`, `names "${option}", which is not an option of "${name}"`));
54624
54635
  }
54625
54636
  }
@@ -54909,9 +54920,9 @@ function checkInputDefault(model, entity, ask, at2) {
54909
54920
  })();
54910
54921
  return holds ? [] : [error51(at2, `"${name}" is a ${type} field and "${ask.input}" asks a ${ask.type} \u2014 an input starts from a field holding what it states`)];
54911
54922
  }
54912
- function offeredTogether2(one, other) {
54923
+ function offeredTogether2(one, other2) {
54913
54924
  return Object.entries(one.when ?? {}).every(([field2, options]) => {
54914
- const theirs = other.when?.[field2];
54925
+ const theirs = other2.when?.[field2];
54915
54926
  return theirs === void 0 || options.some((option) => theirs.includes(option));
54916
54927
  });
54917
54928
  }
@@ -54988,7 +54999,7 @@ function templateFields(content) {
54988
54999
  // ../shared/src/schemas/app_model_resolve_readings.ts
54989
55000
  init_define_LOTICS_KIT_VERSIONS();
54990
55001
  function whereOf(stated2) {
54991
- return Object.entries(stated2 ?? {}).map(([field2, held]) => typeof held === "boolean" ? { field: field2, value: held } : { field: field2, options: held });
55002
+ return Object.entries(stated2 ?? {}).map(([field2, held2]) => typeof held2 === "boolean" ? { field: field2, value: held2 } : { field: field2, options: held2 });
54992
55003
  }
54993
55004
  function resolveReading(model, reading, at2) {
54994
55005
  if ("list" in reading) {
@@ -55442,8 +55453,8 @@ function resolveBlock(model, parent, block2, at2) {
55442
55453
  const where = whenOf(block2.where);
55443
55454
  const stating = Array.isArray(stated2) ? stated2 : stated2 === false || !peopleWrite(child) ? [] : defaultCreate(model, child, [link.link]);
55444
55455
  if (stated2 === void 0 && stating.length > 0) defaulted.push(`${at2}.create`);
55445
- const held = where.map((one) => one.field);
55446
- const create2 = stating.length === 0 ? [] : block2.expect === void 0 ? unique([...held, ...stating]) : unique([block2.expect, ...held, ...stating, ...expectedColumns(model, child, block2.columns ?? [])]);
55456
+ const held2 = where.map((one) => one.field);
55457
+ const create2 = stating.length === 0 ? [] : block2.expect === void 0 ? unique([...held2, ...stating]) : unique([block2.expect, ...held2, ...stating, ...expectedColumns(model, child, block2.columns ?? [])]);
55447
55458
  return {
55448
55459
  block: {
55449
55460
  kind: "rows",
@@ -55495,19 +55506,19 @@ function blockHeld(model, entity, blocks) {
55495
55506
  (block2) => block2.kind === "rows" || block2.kind === "timeline" ? entity.fields.flatMap((field2) => field2.type === "select_record_link" && field2.target_entity === block2.entity && field2.paired_field_alias === block2.via ? [field2.alias] : []) : []
55496
55507
  )
55497
55508
  );
55498
- const held = /* @__PURE__ */ new Set([...own, ...links]);
55509
+ const held2 = /* @__PURE__ */ new Set([...own, ...links]);
55499
55510
  for (let grew = true; grew; ) {
55500
55511
  grew = false;
55501
55512
  for (const field2 of entity.fields) {
55502
- if (held.has(field2.alias)) continue;
55513
+ if (held2.has(field2.alias)) continue;
55503
55514
  const reads = field2.type === "rollup" || field2.type === "lookup" ? [field2.source_field_alias] : field2.type === "formula" ? contractFormulaFieldRefs(field2.formula.expression) : [];
55504
- if (reads.some((alias2) => held.has(alias2))) {
55505
- held.add(field2.alias);
55515
+ if (reads.some((alias2) => held2.has(alias2))) {
55516
+ held2.add(field2.alias);
55506
55517
  grew = true;
55507
55518
  }
55508
55519
  }
55509
55520
  }
55510
- return held;
55521
+ return held2;
55511
55522
  }
55512
55523
  function printed(model, template) {
55513
55524
  const content = model.templates?.find((one) => one.alias === template)?.content;
@@ -55515,13 +55526,13 @@ function printed(model, template) {
55515
55526
  }
55516
55527
  function actsReadBlocks(model, entity, section, acts) {
55517
55528
  if (section.blocks.length === 0) return false;
55518
- const held = blockHeld(model, entity, section.blocks);
55529
+ const held2 = blockHeld(model, entity, section.blocks);
55519
55530
  return section.acts.some((alias2) => {
55520
55531
  const act = acts.find((one) => one.alias === alias2);
55521
55532
  if (act === void 0) return false;
55522
55533
  const asked = act.asks.flatMap((ask) => typeof ask === "string" ? [ask] : []);
55523
55534
  const reads = [...act.requires, ...act.recommends, ...asked, ...printed(model, act.template)];
55524
- return reads.some((field2) => held.has(field2)) || !reads.some((field2) => section.fields.includes(field2));
55535
+ return reads.some((field2) => held2.has(field2)) || !reads.some((field2) => section.fields.includes(field2));
55525
55536
  });
55526
55537
  }
55527
55538
  function resolveAct(model, app, act, checks) {
@@ -55539,7 +55550,7 @@ function resolveAct(model, app, act, checks) {
55539
55550
  );
55540
55551
  const feeds = (name) => Object.entries(set2).filter(([, value]) => value.kind === "input" && value.name === name).map(([field2]) => field2);
55541
55552
  const sharedBy = (field2) => (app.acts ?? []).some(
55542
- (other) => other !== act && ((other.asks ?? []).includes(field2) || Object.entries(other.set ?? {}).some(([written2, value]) => written2 === field2 && typeof value === "string" && value.startsWith(SET_INPUT)))
55553
+ (other2) => other2 !== act && ((other2.asks ?? []).includes(field2) || Object.entries(other2.set ?? {}).some(([written2, value]) => written2 === field2 && typeof value === "string" && value.startsWith(SET_INPUT)))
55543
55554
  );
55544
55555
  const asField = new Map(
55545
55556
  act.workflow !== void 0 || act.template !== void 0 ? [] : inputs.flatMap((input) => {
@@ -55937,8 +55948,8 @@ function checkApp(model, app, at2) {
55937
55948
  }
55938
55949
  function hiddenWhenOffered(shown, offered) {
55939
55950
  return Object.entries(shown ?? {}).some(([field2, options]) => {
55940
- const held = offered?.[field2];
55941
- return held !== void 0 && !held.some((option) => options.includes(option));
55951
+ const held2 = offered?.[field2];
55952
+ return held2 !== void 0 && !held2.some((option) => options.includes(option));
55942
55953
  });
55943
55954
  }
55944
55955
  function misplacedMove(act, title, entry) {
@@ -56020,11 +56031,11 @@ function checkStagedAct(display, section, act, path39) {
56020
56031
  }
56021
56032
  const field2 = status.field;
56022
56033
  if (field2 === void 0) return [];
56023
- const held = act.when?.[field2];
56024
- if (held === void 0) {
56034
+ const held2 = act.when?.[field2];
56035
+ if (held2 === void 0) {
56025
56036
  return [error51(path39, `"${act.alias}" is offered whatever "${field2}" holds, and "${section.title}" is the work only while it is ${at2.join(" or ")} \u2014 narrow its \`when\` to that, or ${elsewhere}`)];
56026
56037
  }
56027
- const outside = held.filter((option) => !at2.includes(option));
56038
+ const outside = held2.filter((option) => !at2.includes(option));
56028
56039
  return outside.length === 0 ? [] : [error51(path39, `"${act.alias}" is offered while "${field2}" is ${outside.join(" or ")}, when "${section.title}" is not the work \u2014 ${elsewhere}`)];
56029
56040
  }
56030
56041
  function blockIdentity(block2) {
@@ -56048,8 +56059,8 @@ function checkWhere(child, block2, at2) {
56048
56059
  if (field2.required !== true) {
56049
56060
  return [error51(path39, `"${name}" is optional on "${child.alias}" \u2014 a row holding none would stand in no block while every unnarrowed total counts it; make it required`)];
56050
56061
  }
56051
- const held = new Set((field2.options ?? []).map((option) => option.alias));
56052
- const stray = options.filter((option) => !held.has(option));
56062
+ const held2 = new Set((field2.options ?? []).map((option) => option.alias));
56063
+ const stray = options.filter((option) => !held2.has(option));
56053
56064
  return [
56054
56065
  ...stray.length === 0 ? [] : [error51(path39, `names ${stray.map((one) => `"${one}"`).join(", ")}, which "${name}" has no option of`)],
56055
56066
  ...options.length === 1 && (block2.columns ?? []).includes(name) ? [error51(path39, `holds every row at "${options[0] ?? ""}", so its column says one thing on every row \u2014 drop the column`)] : []
@@ -56866,9 +56877,9 @@ var HISTORY_WORDS = {
56866
56877
  }
56867
56878
  };
56868
56879
  function derivedLabelClash(entity, stage, table, backLink, derivesBackLink, tables) {
56869
- const other = tables.get(table.label);
56870
- if (other !== void 0) {
56871
- return `derives a table labelled "${table.label}" from the status "${stage.label}", and "${other}" is already labelled so \u2014 rename the status field, which the table is named after`;
56880
+ const other2 = tables.get(table.label);
56881
+ if (other2 !== void 0) {
56882
+ return `derives a table labelled "${table.label}" from the status "${stage.label}", and "${other2}" is already labelled so \u2014 rename the status field, which the table is named after`;
56872
56883
  }
56873
56884
  if (derivesBackLink && entity.fields.some((field2) => field2.label === backLink.label)) {
56874
56885
  return `derives a "${backLink.label}" link on "${entity.alias}", which already has a field labelled so \u2014 rename that field, or the status "${stage.label}" the link is named after`;
@@ -57321,7 +57332,7 @@ function resultSideEffects(result) {
57321
57332
 
57322
57333
  // src/version.ts
57323
57334
  init_define_LOTICS_KIT_VERSIONS();
57324
- var VERSION = "0.251.0";
57335
+ var VERSION = "0.252.0";
57325
57336
 
57326
57337
  // src/timezone.ts
57327
57338
  init_define_LOTICS_KIT_VERSIONS();
@@ -59180,7 +59191,8 @@ function describeApp(model, app) {
59180
59191
  });
59181
59192
  lines.push(...block("checks", checks));
59182
59193
  const menu = app.acts.filter((act) => act.on === "record" && act.of === void 0 && !app.record.sections.some((section) => [...section.entry, ...section.acts].includes(act.alias)));
59183
- lines.push(` header \u22EF ${[...menu.map((act) => act.label), "Delete"].join(", ")}`);
59194
+ const removes = appWritePlan(model, app).get(app.entity)?.remove === true;
59195
+ lines.push(` header \u22EF ${[...menu.map((act) => act.label), ...removes ? ["Delete"] : []].join(", ") || "\u2014"}`);
59184
59196
  if (stages !== void 0) {
59185
59197
  lines.push(
59186
59198
  ...block(
@@ -59819,14 +59831,14 @@ function uniqueDifferences(entity, here, fields) {
59819
59831
  return found?.how === "id" || found?.how === "label" ? found.live.alias : void 0;
59820
59832
  };
59821
59833
  const named = (among, tuple2) => tuple2.map((alias2) => among.fields.find((field2) => field2.alias === alias2)?.label ?? alias2).join(" + ");
59822
- const held = new Set((here.unique ?? []).map(signature));
59834
+ const held2 = new Set((here.unique ?? []).map(signature));
59823
59835
  const stated2 = /* @__PURE__ */ new Set();
59824
59836
  const lines = [];
59825
59837
  for (const tuple2 of entity.unique ?? []) {
59826
59838
  const live = tuple2.flatMap((alias2) => liveAlias(alias2) ?? []);
59827
59839
  const whole = live.length === tuple2.length ? signature(live) : void 0;
59828
59840
  if (whole !== void 0) stated2.add(whole);
59829
- if (whole !== void 0 && held.has(whole)) continue;
59841
+ if (whole !== void 0 && held2.has(whole)) continue;
59830
59842
  lines.push({ side: "create", line: ` ${entity.label}: workspace lacks the unique set ${named(entity, tuple2)}` });
59831
59843
  }
59832
59844
  for (const tuple2 of here.unique ?? []) {
@@ -60109,10 +60121,9 @@ function aggregateRows(rows, aggregate) {
60109
60121
 
60110
60122
  // ../shared/src/schemas/app_spec.ts
60111
60123
  var APP_SPEC_VERSION = 3;
60112
- var liveId = (prefix, what) => zod_default.string().regex(new RegExp(`^${prefix}_.+`), `${what} must be a live \`${prefix}_\` id, not a model alias`);
60113
- var specTableIdSchema = liveId("tbl", "a table");
60114
- var specFieldIdSchema = liveId("fld", "a field");
60115
- var specOptionIdSchema = liveId("opt", "an option");
60124
+ var specTableIdSchema = zod_default.string().refine(isTableId, "a table must be a live table id (`tbl_\u2026`, or an older table's UUID), not a model alias");
60125
+ var specFieldIdSchema = zod_default.string().refine(isStoredKey, "a field must be a live field key, not a label");
60126
+ var specOptionIdSchema = zod_default.string().refine((id) => id.startsWith("opt_") && isStoredKey(id), "an option must be a live `opt_` key, not a model alias");
60116
60127
  var specColumnAliasSchema = zod_default.string().min(1);
60117
60128
  var specReadSchema = zod_default.object({
60118
60129
  query: zod_default.string().min(1),
@@ -63945,16 +63956,16 @@ function kitCheckouts(given) {
63945
63956
  const name = fs13.existsSync(own) ? manifestSchema.parse(JSON.parse(fs13.readFileSync(own, "utf-8"))).name : void 0;
63946
63957
  if (name !== void 0 && CHECKOUT_PACKAGES.includes(name)) return [given];
63947
63958
  const root = path14.join(given, "packages");
63948
- const held = fs13.existsSync(root) ? fs13.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path14.join(root, entry.name)).filter((dir) => {
63959
+ const held2 = fs13.existsSync(root) ? fs13.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path14.join(root, entry.name)).filter((dir) => {
63949
63960
  const manifest = path14.join(dir, "package.json");
63950
63961
  if (!fs13.existsSync(manifest)) return false;
63951
63962
  const named = manifestSchema.parse(JSON.parse(fs13.readFileSync(manifest, "utf-8"))).name;
63952
63963
  return named !== void 0 && CHECKOUT_PACKAGES.includes(named);
63953
63964
  }) : [];
63954
- if (held.length === 0) {
63965
+ if (held2.length === 0) {
63955
63966
  fail(`${given} is neither ${CHECKOUT_PACKAGES.join(" nor ")} nor a checkout holding them under packages/ \u2014 name one of those.`);
63956
63967
  }
63957
- return held;
63968
+ return held2;
63958
63969
  }
63959
63970
  function npmOutput(args, cwd) {
63960
63971
  return new Promise((resolve4, reject) => {
@@ -63979,19 +63990,19 @@ function proveKitInstall(projectDir, packed) {
63979
63990
  }
63980
63991
  async function packDependencies(kits, kitDir) {
63981
63992
  const packed = [];
63982
- const held = new Set(kits.map((kit) => kit.name));
63993
+ const held2 = new Set(kits.map((kit) => kit.name));
63983
63994
  const queue = [...kits];
63984
63995
  for (let kit = queue.shift(); kit !== void 0; kit = queue.shift()) {
63985
63996
  const manifest = manifestSchema.parse(JSON.parse(fs13.readFileSync(path14.join(kit.source, "package.json"), "utf-8")));
63986
63997
  const wanted2 = Object.entries({ ...manifest.dependencies, ...manifest.peerDependencies }).filter(([name]) => name.startsWith("@lotics/"));
63987
63998
  for (const [name, range2] of wanted2) {
63988
- if (held.has(name)) continue;
63999
+ if (held2.has(name)) continue;
63989
64000
  const floor2 = range2.replace(/^[\^~]/, "");
63990
64001
  const latest = await fetchLatestNpmVersion(name);
63991
64002
  if (latest !== null && compareReleases(latest, floor2) >= 0) continue;
63992
64003
  const sibling = siblingPackage(kit.source, name);
63993
64004
  if (sibling === void 0) continue;
63994
- held.add(name);
64005
+ held2.add(name);
63995
64006
  const one = await packCheckout(sibling, kitDir, null);
63996
64007
  packed.push(one);
63997
64008
  queue.push(one);
@@ -64012,9 +64023,9 @@ function caretRange(range2) {
64012
64023
  const floor2 = [Number(match2[1]), Number(match2[2]), Number(match2[3])];
64013
64024
  return { line: releaseLine(range2.slice(1)), floor: floor2 };
64014
64025
  }
64015
- function accepts(held, wanted2) {
64016
- if (held === wanted2) return true;
64017
- const have = caretRange(held);
64026
+ function accepts(held2, wanted2) {
64027
+ if (held2 === wanted2) return true;
64028
+ const have = caretRange(held2);
64018
64029
  const want = caretRange(wanted2);
64019
64030
  if (have === null || want === null || have.line !== want.line) return false;
64020
64031
  const differs = have.floor.findIndex((part, at2) => part !== want.floor[at2]);
@@ -74004,13 +74015,13 @@ function entitiesToBind(model, set2) {
74004
74015
  const link = rule.default_from?.split(".")[0];
74005
74016
  if (link !== void 0) add(linkTarget(entity, model.entities, link)?.alias);
74006
74017
  }
74007
- for (const [other, rules] of Object.entries(model.write_rules ?? {})) {
74008
- const from = model.entities.find((one) => one.alias === other);
74018
+ for (const [other2, rules] of Object.entries(model.write_rules ?? {})) {
74019
+ const from = model.entities.find((one) => one.alias === other2);
74009
74020
  if (from === void 0) continue;
74010
74021
  for (const [alias2, rule] of Object.entries(rules.fields ?? {})) {
74011
74022
  const bounds = [rule.min, rule.max].flatMap((bound) => typeof bound === "string" && bound.includes(".") ? [bound.split(".")[0]] : []);
74012
74023
  const links = [...bounds, ...rule.same === void 0 ? [] : [alias2]];
74013
- if (links.some((link) => linkTarget(from, model.entities, link)?.alias === entity.alias)) add(other);
74024
+ if (links.some((link) => linkTarget(from, model.entities, link)?.alias === entity.alias)) add(other2);
74014
74025
  }
74015
74026
  }
74016
74027
  }
@@ -74040,7 +74051,7 @@ function bindModel(model, wanted2, live) {
74040
74051
  const columns = /* @__PURE__ */ new Map();
74041
74052
  for (const [alias2, { entity, table }] of found) {
74042
74053
  const index = liveIndex(table.fields, { id: (field2) => field2.id, label: (field2) => field2.name });
74043
- const held = /* @__PURE__ */ new Map();
74054
+ const held2 = /* @__PURE__ */ new Map();
74044
74055
  for (const field2 of entity.fields) {
74045
74056
  const key = fieldKeyName(alias2, field2.alias);
74046
74057
  const column = bindAlias(live.binding?.entities[alias2]?.fields[field2.alias]?.id, field2.label, index);
@@ -74060,33 +74071,33 @@ function bindModel(model, wanted2, live) {
74060
74071
  problems.set(key, `field "${field2.label}" holds ${multi ? "several values" : "one value"} in this workspace and ${multi ? "one" : "several"} in the model`);
74061
74072
  continue;
74062
74073
  }
74063
- held.set(field2.alias, { field: field2, live: column.live });
74074
+ held2.set(field2.alias, { field: field2, live: column.live });
74064
74075
  }
74065
- columns.set(alias2, held);
74076
+ columns.set(alias2, held2);
74066
74077
  }
74067
74078
  const optionsOf2 = (entityAlias, fieldAlias2, hops = 0) => {
74068
- const held = columns.get(entityAlias)?.get(fieldAlias2);
74079
+ const held2 = columns.get(entityAlias)?.get(fieldAlias2);
74069
74080
  const entity = found.get(entityAlias)?.entity;
74070
- if (held === void 0 || entity === void 0 || hops > 8) return void 0;
74071
- if (held.field.type === "lookup") {
74072
- const target = linkTarget(entity, model.entities, held.field.source_field_alias);
74073
- return target === void 0 ? void 0 : optionsOf2(target.alias, held.field.lookup_field_alias, hops + 1);
74081
+ if (held2 === void 0 || entity === void 0 || hops > 8) return void 0;
74082
+ if (held2.field.type === "lookup") {
74083
+ const target = linkTarget(entity, model.entities, held2.field.source_field_alias);
74084
+ return target === void 0 ? void 0 : optionsOf2(target.alias, held2.field.lookup_field_alias, hops + 1);
74074
74085
  }
74075
- if (held.field.type !== "select") return void 0;
74076
- const index = liveIndex(held.live.options ?? [], { id: (option) => option.id, label: (option) => option.label });
74086
+ if (held2.field.type !== "select") return void 0;
74087
+ const index = liveIndex(held2.live.options ?? [], { id: (option) => option.id, label: (option) => option.label });
74077
74088
  const recorded = live.binding?.entities[entityAlias]?.fields[fieldAlias2]?.options;
74078
74089
  const options = /* @__PURE__ */ new Map();
74079
- for (const option of held.field.options) {
74090
+ for (const option of held2.field.options) {
74080
74091
  const bound = bindAlias(recorded?.[option.alias]?.id, option.label, index);
74081
74092
  if (bound.how === "id" || bound.how === "label") options.set(option.alias, bound.live.id);
74082
- else problems.set(`${fieldKeyName(entityAlias, fieldAlias2)}:${option.alias}`, `option "${option.label}" of "${held.field.label}" is not in this workspace`);
74093
+ else problems.set(`${fieldKeyName(entityAlias, fieldAlias2)}:${option.alias}`, `option "${option.label}" of "${held2.field.label}" is not in this workspace`);
74083
74094
  }
74084
74095
  return options;
74085
74096
  };
74086
74097
  const boundFields = /* @__PURE__ */ new Map();
74087
- for (const [alias2, held] of columns) {
74098
+ for (const [alias2, held2] of columns) {
74088
74099
  const fields = /* @__PURE__ */ new Map();
74089
- for (const [fieldAlias2, { field: field2, live: column }] of held) {
74100
+ for (const [fieldAlias2, { field: field2, live: column }] of held2) {
74090
74101
  const options = optionsOf2(alias2, fieldAlias2);
74091
74102
  fields.set(fieldAlias2, { alias: fieldAlias2, id: column.id, field: field2, ...options === void 0 ? {} : { options } });
74092
74103
  }
@@ -74258,6 +74269,21 @@ function heldEmpty(field2, value) {
74258
74269
  function callerValue(field2) {
74259
74270
  return field2.field.type === "select_member" && field2.field.multi === true ? `isNull(${CALLER}) ? null : [${CALLER}]` : CALLER;
74260
74271
  }
74272
+ function sends(s, field2) {
74273
+ if (!s.carries.has(field2.alias)) return void 0;
74274
+ return s.row === void 0 ? `!isNull(${s.from}.${field2.alias})` : `includes(keys(${s.from}), ${str(field2.alias)})`;
74275
+ }
74276
+ function leftBy(s, field2) {
74277
+ const held2 = s.row === void 0 ? "null" : `${s.row}.data[${str(field2.id)}]`;
74278
+ if (!s.carries.has(field2.alias)) return held2;
74279
+ return s.row === void 0 ? `${s.from}.${field2.alias}` : `(${sends(s, field2)} ? ${s.from}.${field2.alias} : ${held2})`;
74280
+ }
74281
+ function landsOn(s, field2) {
74282
+ return s.inputs.has(field2.alias) ? field2.alias : field2.id;
74283
+ }
74284
+ function other(rows, record2) {
74285
+ return record2 === void 0 ? `first(${rows})` : `first(filter(${rows}, (one) => one.id != ${record2}))`;
74286
+ }
74261
74287
  function scopeCell(clause, local) {
74262
74288
  const row = (clause.through ?? []).reduce((at2, hop) => `linked(${at2}[${str(hop.id)}])[0]`, `${local}.data`);
74263
74289
  return clause.through === void 0 ? `${row}[${str(clause.field.id)}]` : `${row}?.[${str(clause.field.id)}]`;
@@ -74273,6 +74299,13 @@ function scopePasses(rule, local) {
74273
74299
  function scopeReadsRow(rule) {
74274
74300
  return rule !== void 0 && rule.clauses.some((clause) => clause.kind !== "member_of");
74275
74301
  }
74302
+ function held(condition, at2) {
74303
+ const values2 = Array.isArray(condition.value) ? condition.value : [condition.value];
74304
+ if (condition.field.field.type === "select" && condition.field.field.multi === true) {
74305
+ return `(${values2.map((value) => `includes(${at2}, ${JSON.stringify(value)})`).join(" || ")})`;
74306
+ }
74307
+ return `includes(${JSON.stringify(values2)}, ${at2})`;
74308
+ }
74276
74309
  function whereCheck(local, condition) {
74277
74310
  const at2 = `${local}.data[${str(condition.field.id)}]`;
74278
74311
  const value = JSON.stringify(condition.value);
@@ -74290,9 +74323,9 @@ function whereCheck(local, condition) {
74290
74323
  case "less_than_or_equal_to":
74291
74324
  return `${at2} <= ${value}`;
74292
74325
  case "has_any_of":
74293
- return `includes(${value}, ${at2})`;
74326
+ return held(condition, at2);
74294
74327
  case "has_none_of":
74295
- return `!includes(${value}, ${at2})`;
74328
+ return `!${held(condition, at2)}`;
74296
74329
  default:
74297
74330
  return "false";
74298
74331
  }
@@ -74320,7 +74353,8 @@ function checksOn(checks, cellOf) {
74320
74353
  return validateBlock(
74321
74354
  checks.map((check2) => {
74322
74355
  const cell = cellOf(str(check2.field.id));
74323
- return check2.text ? checkLine(`!isEmpty(trim(coalesce(${cell}, "")))`, void 0, cell) : checkLine(`${cell} == true`, void 0, str(check2.label));
74356
+ const text = `trim(coalesce(${cell}, ""))`;
74357
+ return check2.text ? checkLine(`!isEmpty(${text})`, void 0, text) : checkLine(`${cell} == true`, void 0, str(check2.label));
74324
74358
  })
74325
74359
  );
74326
74360
  }
@@ -74336,60 +74370,73 @@ function locksGuard(locks, local, words) {
74336
74370
  ];
74337
74371
  });
74338
74372
  }
74339
- function valueChecks(fields, words, sent, from = "i") {
74373
+ function valueChecks(fields, words, s) {
74340
74374
  return fields.flatMap((one) => {
74341
74375
  const name = one.field.alias;
74342
- const given = sent === void 0 ? `!isNull(${from}.${name})` : `${sent(name)} && !isNull(${from}.${name})`;
74376
+ const sent = sends(s, one.field);
74377
+ if (sent === void 0) return [];
74378
+ const value = `${s.from}.${name}`;
74379
+ const given = s.row === void 0 ? sent : `${sent} && !isNull(${value})`;
74380
+ const at2 = landsOn(s, one.field);
74343
74381
  return [
74344
- ...one.min?.kind !== "value" ? [] : [checkLine(`${given} && ${from}.${name} < ${one.min.value}`, name, str(words.refusals.below(one.label, String(one.min.value))))],
74345
- ...one.max?.kind !== "value" ? [] : [checkLine(`${given} && ${from}.${name} > ${one.max.value}`, name, str(words.refusals.above(one.label, String(one.max.value))))]
74382
+ ...one.min?.kind !== "value" ? [] : [checkLine(`${given} && ${value} < ${one.min.value}`, at2, str(words.refusals.below(one.label, String(one.min.value))))],
74383
+ ...one.max?.kind !== "value" ? [] : [checkLine(`${given} && ${value} > ${one.max.value}`, at2, str(words.refusals.above(one.label, String(one.max.value))))]
74346
74384
  ];
74347
74385
  });
74348
74386
  }
74349
74387
  function boundsByField(fields) {
74350
74388
  return fields.some((one) => [one.min, one.max].some((bound) => bound !== void 0 && bound.kind !== "value"));
74351
74389
  }
74352
- function fieldBoundChecks(fields, words, row, from = "i") {
74353
- const sent = (name) => row === void 0 ? `!isNull(${from}.${name})` : `includes(keys(${from}), ${str(name)})`;
74354
- const now = (field2) => row === void 0 ? `${from}.${field2.alias}` : `(${sent(field2.alias)} ? ${from}.${field2.alias} : ${row}.data[${str(field2.id)}])`;
74355
- const fetched = /* @__PURE__ */ new Set();
74390
+ function fieldBoundChecks(fields, words, s) {
74356
74391
  const lines = [];
74392
+ const bound = /* @__PURE__ */ new Set();
74393
+ const local = (name, value) => {
74394
+ if (!bound.has(name)) {
74395
+ bound.add(name);
74396
+ lines.push(`const ${name} = ${value};`);
74397
+ }
74398
+ return name;
74399
+ };
74400
+ const leftOf = (field2) => local(`${camel(field2.alias)}Left`, leftBy(s, field2));
74357
74401
  const checks = [];
74358
74402
  for (const one of fields) {
74359
- for (const [side, bound] of [["min", one.min], ["max", one.max]]) {
74360
- if (bound === void 0 || bound.kind === "value") continue;
74361
- const value = now(one.field);
74403
+ for (const [side, limitBy] of [["min", one.min], ["max", one.max]]) {
74404
+ if (limitBy === void 0 || limitBy.kind === "value") continue;
74405
+ if (limitBy.kind === "field" && s.row === void 0 && !s.carries.has(limitBy.field.alias)) continue;
74406
+ const moved = [sends(s, one.field), sends(s, limitBy.kind === "field" ? limitBy.field : limitBy.link)].filter((sent) => sent !== void 0);
74407
+ if (moved.length === 0) continue;
74362
74408
  let limit;
74363
- let moved;
74364
- if (bound.kind === "field") {
74365
- if (row === void 0 && !fields.some((other) => other.field.id === bound.field.id)) continue;
74366
- limit = now(bound.field);
74367
- moved = `(${sent(one.field.alias)} || ${sent(bound.field.alias)})`;
74409
+ if (limitBy.kind === "field") {
74410
+ limit = leftOf(limitBy.field);
74368
74411
  } else {
74369
- const local = `${camel(bound.link.alias)}Bound`;
74370
- if (!fetched.has(local)) {
74371
- fetched.add(local);
74412
+ const linked = `${camel(limitBy.link.alias)}Bound`;
74413
+ if (!bound.has(linked)) {
74414
+ bound.add(linked);
74415
+ const sent = sends(s, limitBy.link);
74372
74416
  lines.push(
74373
- `let ${local} = ${row === void 0 ? "null" : `linked(${row}.data[${str(bound.link.id)}])[0]`};`,
74374
- `if (${sent(bound.link.alias)} && !isNull(${from}.${bound.link.alias})) {`,
74375
- ` const ${local}Row = await get_record({ table_id: ${str(bound.table)}, record_id: ${from}.${bound.link.alias} });`,
74376
- ` ${local} = ${local}Row.data;`,
74377
- `}`
74417
+ `let ${linked} = ${s.row === void 0 ? "null" : `linked(${s.row}.data[${str(limitBy.link.id)}])[0]`};`,
74418
+ ...sent === void 0 ? [] : [
74419
+ `if (${sent} && !isNull(${s.from}.${limitBy.link.alias})) {`,
74420
+ ` const ${linked}Row = await get_record({ table_id: ${str(limitBy.table)}, record_id: ${s.from}.${limitBy.link.alias} });`,
74421
+ ` ${linked} = ${linked}Row.data;`,
74422
+ `}`
74423
+ ]
74378
74424
  );
74379
74425
  }
74380
- limit = `(isNull(${local}) ? null : ${local}[${str(bound.field.id)}])`;
74381
- moved = `(${sent(one.field.alias)} || ${sent(bound.link.alias)})`;
74426
+ limit = local(`${camel(`${limitBy.link.alias}_${limitBy.field.alias}`)}Limit`, `isNull(${linked}) ? null : ${linked}[${str(limitBy.field.id)}]`);
74382
74427
  }
74428
+ const value = leftOf(one.field);
74383
74429
  const dated = one.field.field.type === "date";
74384
74430
  const fails = side === "min" ? dated ? `isBefore(${value}, ${limit})` : `${value} < ${limit}` : dated ? `isAfter(${value}, ${limit})` : `${value} > ${limit}`;
74385
- const said = side === "min" ? words.refusals.below(one.label, bound.label) : words.refusals.above(one.label, bound.label);
74386
- checks.push(checkLine(`${moved} && !isNull(${value}) && !isNull(${limit}) && ${fails}`, one.field.alias, str(said)));
74431
+ const said = side === "min" ? words.refusals.below(one.label, limitBy.label) : words.refusals.above(one.label, limitBy.label);
74432
+ checks.push(checkLine(`${moved.length === 1 ? moved[0] : `(${moved.join(" || ")})`} && !isNull(${value}) && !isNull(${limit}) && ${fails}`, landsOn(s, one.field), str(said)));
74387
74433
  }
74388
74434
  }
74389
74435
  return [...lines, ...validateBlock(checks)];
74390
74436
  }
74391
- function heldGuards(held, record2, words, from = "i") {
74392
- return held.flatMap((one, at2) => {
74437
+ function heldGuards(held2, record2, words, s) {
74438
+ const from = s.from;
74439
+ return held2.flatMap((one, at2) => {
74393
74440
  const local = `outside${at2 + 1}`;
74394
74441
  const dated = one.child.bounded.field.type === "date";
74395
74442
  const operator = one.side === "min" ? dated ? "before" : "less_than" : dated ? "after" : "greater_than";
@@ -74404,18 +74451,19 @@ function heldGuards(held, record2, words, from = "i") {
74404
74451
  ` });`,
74405
74452
  ` ${local} = first(${local}Rows.records);`,
74406
74453
  `}`,
74407
- ...validateBlock([checkLine(`!isNull(${local})`, one.field.alias, str(words.refusals.strands(one.child.label, one.field.field.label)))])
74454
+ ...validateBlock([checkLine(`!isNull(${local})`, landsOn(s, one.field), str(words.refusals.strands(one.child.label, one.field.field.label)))])
74408
74455
  ];
74409
74456
  });
74410
74457
  }
74411
- function overlapGuard(overlap, table, words, at2, asked, from = "i") {
74412
- if (at2 === void 0 && ![overlap.from, overlap.to, ...overlap.by].every((field2) => asked?.has(field2.alias) === true)) return [];
74413
- const sent = (field2) => at2 === void 0 ? `!isNull(${from}.${field2.alias})` : `includes(keys(${from}), ${str(field2.alias)})`;
74414
- const now = (field2) => at2 === void 0 ? `${from}.${field2.alias}` : `(${sent(field2)} ? ${from}.${field2.alias} : ${at2.row}.data[${str(field2.id)}])`;
74458
+ function overlapGuard(overlap, table, words, s, record2) {
74459
+ if (s.row === void 0 && ![overlap.from, overlap.to, ...overlap.by].every((field2) => s.carries.has(field2.alias))) return [];
74460
+ const now = (field2) => leftBy(s, field2);
74415
74461
  const shared = (field2) => {
74416
74462
  const type = field2.field.type;
74417
74463
  if (type === "select_record_link") {
74418
- const ids = at2 === void 0 ? `[${from}.${field2.alias}]` : `(${sent(field2)} ? [${from}.${field2.alias}] : ${at2.row}.data[${str(field2.id)}])`;
74464
+ const sent2 = sends(s, field2);
74465
+ const own = `[${s.from}.${field2.alias}]`;
74466
+ const ids = sent2 === void 0 ? `${s.row}.data[${str(field2.id)}]` : s.row === void 0 ? own : `(${sent2} ? ${own} : ${s.row}.data[${str(field2.id)}])`;
74419
74467
  return `{ node_type: "condition", field_key: ${str(field2.id)}, operator: "has_any_of", value: ${ids} }`;
74420
74468
  }
74421
74469
  if (type === "select_member" || type === "select") {
@@ -74428,52 +74476,61 @@ function overlapGuard(overlap, table, words, at2, asked, from = "i") {
74428
74476
  ...overlap.by.map(shared),
74429
74477
  `{ node_type: "condition", field_key: ${str(overlap.from.id)}, operator: "on_or_before", value: ${day(overlap.to)} }`,
74430
74478
  `{ node_type: "condition", field_key: ${str(overlap.to.id)}, operator: "on_or_after", value: ${day(overlap.from)} }`,
74431
- ...overlap.holds.map((hold) => `{ node_type: "condition", field_key: ${str(hold.field.id)}, operator: "has_any_of", value: [${hold.options.map(str).join(", ")}] }`),
74432
- ...at2 === void 0 ? [] : [`{ node_type: "condition", type: "record_id", operator: "is_none_of", value: [${at2.record}] }`]
74479
+ ...overlap.holds.map((hold) => `{ node_type: "condition", field_key: ${str(hold.field.id)}, operator: "has_any_of", value: [${hold.options.map(str).join(", ")}] }`)
74433
74480
  ];
74434
- const counted = overlap.holds.filter((hold) => at2 !== void 0 || asked?.has(hold.field.alias) === true);
74481
+ const counted = overlap.holds.filter((hold) => s.row !== void 0 || s.carries.has(hold.field.alias));
74435
74482
  const holds = [`!isNull(${now(overlap.from)})`, `!isNull(${now(overlap.to)})`, ...counted.map((hold) => `includes([${hold.options.map(str).join(", ")}], ${now(hold.field)})`)];
74436
- const moved = at2 === void 0 ? [] : [`(${[overlap.from, overlap.to, ...overlap.by, ...overlap.holds.map((hold) => hold.field)].map(sent).join(" || ")})`];
74483
+ const sent = [overlap.from, overlap.to, ...overlap.by, ...overlap.holds.map((hold) => hold.field)].flatMap((field2) => sends(s, field2) ?? []);
74484
+ if (s.row !== void 0 && sent.length === 0) return [];
74485
+ const moved = s.row === void 0 ? [] : [`(${sent.join(" || ")})`];
74437
74486
  const when = [...moved, ...holds].join(" && ");
74438
74487
  return [
74439
74488
  `let covered = null;`,
74440
74489
  `if (${when}) {`,
74441
74490
  ` const covering = await query_records({`,
74442
74491
  ` table_id: ${str(table.id)},`,
74492
+ // The row itself, and one other if any holds the span.
74493
+ ` limit: ${record2 === void 0 ? 1 : 2},`,
74443
74494
  ` filters: { node_type: "group", logic: "and", children: [${conditions.join(", ")}] },`,
74444
74495
  ` });`,
74445
- ` covered = first(covering.records);`,
74496
+ ` covered = ${other("covering.records", record2)};`,
74446
74497
  `}`,
74447
- ...validateBlock([checkLine(`!isNull(covered)`, overlap.from.alias, str(words.refusals.overlaps(overlap.label)))])
74498
+ ...validateBlock([checkLine(`!isNull(covered)`, landsOn(s, overlap.from), str(words.refusals.overlaps(overlap.label)))])
74448
74499
  ];
74449
74500
  }
74450
- function expectGuard(expect, table, words, at2, asked) {
74501
+ function expectGuard(expect, table, words, s, record2) {
74451
74502
  const { via, key } = expect;
74452
- if (at2 === void 0 && !(asked?.has(via.alias) === true && asked.has(key.alias))) return [];
74453
- const sent = (field2) => at2 === void 0 ? `!isNull(i.${field2.alias})` : `includes(keys(i), ${str(field2.alias)})`;
74454
- const held = (field2) => field2.field.type === "select_record_link" ? at2 === void 0 ? `[i.${field2.alias}]` : `(${sent(field2)} ? [i.${field2.alias}] : ${at2.row}.data[${str(field2.id)}])` : at2 === void 0 ? `[i.${field2.alias}]` : `[(${sent(field2)} ? i.${field2.alias} : ${at2.row}.data[${str(field2.id)}])]`;
74455
- const holds = (field2) => at2 === void 0 ? `!isNull(i.${field2.alias})` : field2.field.type === "select_record_link" ? `!isEmpty(${held(field2)})` : `!isNull(first(${held(field2)}))`;
74503
+ if (s.row === void 0 && !(s.carries.has(via.alias) && s.carries.has(key.alias))) return [];
74504
+ const held2 = (field2) => {
74505
+ const sent2 = sends(s, field2);
74506
+ const own = `[${s.from}.${field2.alias}]`;
74507
+ const stored = field2.field.type === "select_record_link" ? `${s.row}.data[${str(field2.id)}]` : `[${s.row}.data[${str(field2.id)}]]`;
74508
+ return sent2 === void 0 ? stored : s.row === void 0 ? own : `(${sent2} ? ${own} : ${stored})`;
74509
+ };
74510
+ const holds = (field2) => s.row === void 0 ? `!isNull(${s.from}.${field2.alias})` : field2.field.type === "select_record_link" ? `!isEmpty(${held2(field2)})` : `!isNull(first(${held2(field2)}))`;
74456
74511
  const local = `${camel(key.alias)}Held`;
74457
74512
  const among = expect.among.map(
74458
- (field2) => `(isNull(first(${held(field2)})) ? { node_type: "condition", field_key: ${str(field2.id)}, operator: "is_empty" } : { node_type: "condition", field_key: ${str(field2.id)}, operator: "has_any_of", value: ${held(field2)} })`
74513
+ (field2) => `(isNull(first(${held2(field2)})) ? { node_type: "condition", field_key: ${str(field2.id)}, operator: "is_empty" } : { node_type: "condition", field_key: ${str(field2.id)}, operator: "has_any_of", value: ${held2(field2)} })`
74459
74514
  );
74460
74515
  const conditions = [
74461
- `{ node_type: "condition", field_key: ${str(via.id)}, operator: "has_any_of", value: ${held(via)} }`,
74462
- `{ node_type: "condition", field_key: ${str(key.id)}, operator: "has_any_of", value: ${held(key)} }`,
74463
- ...among,
74464
- ...at2 === void 0 ? [] : [`{ node_type: "condition", type: "record_id", operator: "is_none_of", value: [${at2.record}] }`]
74516
+ `{ node_type: "condition", field_key: ${str(via.id)}, operator: "has_any_of", value: ${held2(via)} }`,
74517
+ `{ node_type: "condition", field_key: ${str(key.id)}, operator: "has_any_of", value: ${held2(key)} }`,
74518
+ ...among
74465
74519
  ];
74466
- const when = [...at2 === void 0 ? [] : [`(${[via, key, ...expect.among].map(sent).join(" || ")})`], holds(via), holds(key)].join(" && ");
74520
+ const sent = [via, key, ...expect.among].flatMap((field2) => sends(s, field2) ?? []);
74521
+ if (s.row !== void 0 && sent.length === 0) return [];
74522
+ const when = [...s.row === void 0 ? [] : [`(${sent.join(" || ")})`], holds(via), holds(key)].join(" && ");
74467
74523
  return [
74468
74524
  `let ${local} = null;`,
74469
74525
  `if (${when}) {`,
74470
74526
  ` const holding = await query_records({`,
74471
74527
  ` table_id: ${str(table.id)},`,
74528
+ ` limit: ${record2 === void 0 ? 1 : 2},`,
74472
74529
  ` filters: { node_type: "group", logic: "and", children: [${conditions.join(", ")}] },`,
74473
74530
  ` });`,
74474
- ` ${local} = first(holding.records);`,
74531
+ ` ${local} = ${other("holding.records", record2)};`,
74475
74532
  `}`,
74476
- ...validateBlock([checkLine(`!isNull(${local})`, key.alias, str(words.refusals.expected(expect.label, expect.record)))])
74533
+ ...validateBlock([checkLine(`!isNull(${local})`, landsOn(s, key), str(words.refusals.expected(expect.label, expect.record)))])
74477
74534
  ];
74478
74535
  }
74479
74536
  function admitsReadsRow(link) {
@@ -74491,7 +74548,8 @@ function linkChecks(fields, words, own) {
74491
74548
  return editedByDelta(one.field.field) ? [`for (const picked of i.${name} ?? []) {`, ...indent([...fetch2("picked"), ...refusal]), `}`] : [`if (!isNull(i.${name})) {`, ...indent([...fetch2(`i.${name}`), ...refusal]), `}`];
74492
74549
  });
74493
74550
  }
74494
- function keptGuards(kept, written, words, from) {
74551
+ function keptGuards(kept, written, words, s) {
74552
+ const from = s.from;
74495
74553
  const sent = (name) => `includes(keys(${from}), ${str(name)})`;
74496
74554
  const writes = new Set(written.map((one) => one.field.alias));
74497
74555
  const own = (field2) => writes.has(field2.alias) ? `(${sent(field2.alias)} ? ${from}.${field2.alias} : first(row.data[${str(field2.id)}]))` : `first(row.data[${str(field2.id)}])`;
@@ -74503,7 +74561,7 @@ function keptGuards(kept, written, words, from) {
74503
74561
  const passes = link.same.map((pair) => `includes(coalesce(${local}.data[${str(pair.theirs.id)}], []), ${own(pair.own)})`).join(" && ");
74504
74562
  const check2 = (id) => [
74505
74563
  `const ${local} = await get_record({ table_id: ${str(link.target.id)}, record_id: ${id} });`,
74506
- ...validateBlock([checkLine(`!(${passes})`, moved[0].own.alias, str(words.refusals.mismatched(link.target.label, moved[0].own.field.label)))])
74564
+ ...validateBlock([checkLine(`!(${passes})`, landsOn(s, moved[0].own), str(words.refusals.mismatched(link.target.label, moved[0].own.field.label)))])
74507
74565
  ];
74508
74566
  const when = moved.map((pair) => sent(pair.own.alias)).join(" || ");
74509
74567
  if (editedByDelta(one.field.field)) {
@@ -74511,17 +74569,18 @@ function keptGuards(kept, written, words, from) {
74511
74569
  const keeps2 = writes.has(one.field.alias) ? [`if (!includes(coalesce(i.${removed}, []), kept)) {`, ...indent(check2("kept")), `}`] : check2("kept");
74512
74570
  return [`if (${when}) {`, ` for (const kept of coalesce(row.data[${str(one.field.id)}], [])) {`, ...indent(keeps2, " "), ` }`, `}`];
74513
74571
  }
74514
- const held = `${camel(one.field.alias)}Kept`;
74572
+ const held2 = `${camel(one.field.alias)}Kept`;
74515
74573
  const keeps = writes.has(one.field.alias) ? `(${when}) && !${sent(one.field.alias)}` : when;
74516
- return [`if (${keeps}) {`, ` const ${held} = first(row.data[${str(one.field.id)}]);`, ` if (!isNull(${held})) {`, ...indent(check2(held), " "), ` }`, `}`];
74574
+ return [`if (${keeps}) {`, ` const ${held2} = first(row.data[${str(one.field.id)}]);`, ` if (!isNull(${held2})) {`, ...indent(check2(held2), " "), ` }`, `}`];
74517
74575
  });
74518
74576
  }
74519
- function strayGuards(strays, record2, words, from) {
74577
+ function strayGuards(strays, record2, words, s) {
74578
+ const from = s.from;
74520
74579
  return strays.flatMap((one, at2) => {
74521
74580
  const local = `stray${at2 + 1}`;
74522
74581
  const sent = `includes(keys(${from}), ${str(one.field.alias)})`;
74523
74582
  const pointing = `{ node_type: "condition", field_key: ${str(one.child.link.id)}, operator: "has_any_of", value: [${record2}] }`;
74524
- const other = `{ node_type: "condition", field_key: ${str(one.child.compared.id)}, operator: "has_none_of", value: [${from}.${one.field.alias}] }`;
74583
+ const other2 = `{ node_type: "condition", field_key: ${str(one.child.compared.id)}, operator: "has_none_of", value: [${from}.${one.field.alias}] }`;
74525
74584
  const read2 = (when, children) => [
74526
74585
  `if (${when}) {`,
74527
74586
  ` const ${local}Rows = await query_records({`,
@@ -74534,10 +74593,10 @@ function strayGuards(strays, record2, words, from) {
74534
74593
  ];
74535
74594
  return [
74536
74595
  `let ${local} = null;`,
74537
- ...read2(`${sent} && !isNull(${from}.${one.field.alias})`, [pointing, other]),
74596
+ ...read2(`${sent} && !isNull(${from}.${one.field.alias})`, [pointing, other2]),
74538
74597
  // A required field is never emptied, so only an optional one asks for any row at all.
74539
74598
  ...one.field.field.required === true ? [] : read2(`${sent} && isNull(${from}.${one.field.alias})`, [pointing]),
74540
- ...validateBlock([checkLine(`!isNull(${local})`, one.field.alias, str(words.refusals.mismatched(one.child.label, one.field.field.label)))])
74599
+ ...validateBlock([checkLine(`!isNull(${local})`, landsOn(s, one.field), str(words.refusals.mismatched(one.child.label, one.field.field.label)))])
74541
74600
  ];
74542
74601
  });
74543
74602
  }
@@ -74560,7 +74619,7 @@ function appendHistory(history, record2, moved, copies = []) {
74560
74619
  `});`
74561
74620
  ];
74562
74621
  }
74563
- function milestoneOrder(stages, touches, left, words) {
74622
+ function milestoneOrder(stages, touches, left, words, landing) {
74564
74623
  const applies = (stage) => stage.when.map((gate) => `includes([${gate.options.map(str).join(", ")}], ${left(gate.field)})`);
74565
74624
  const dated = (stage) => left(stage.field) === "now()" ? "true" : `!(${heldEmpty(stage.field, left(stage.field))})`;
74566
74625
  return validateBlock(
@@ -74571,10 +74630,11 @@ function milestoneOrder(stages, touches, left, words) {
74571
74630
  const written = touched.includes("true") ? [] : [touched.length === 1 ? touched[0] ?? "" : `(${touched.join(" || ")})`];
74572
74631
  const standing = [...written, dated(stage), ...applies(before)];
74573
74632
  return [
74574
- checkLine([...standing, heldEmpty(before.field, left(before.field))].filter((part) => part !== "true").join(" && "), stage.field.alias, str(words.refusals.milestoneOrder(stage.label, before.label))),
74633
+ checkLine([...standing, heldEmpty(before.field, left(before.field))].filter((part) => part !== "true").join(" && "), landing(stage.field), str(words.refusals.milestoneOrder(stage.label, before.label))),
74575
74634
  checkLine(
74576
- [...standing, dated(before), `differenceInCalendarDays(${left(stage.field)}, ${left(before.field)}) < 0`].filter((part) => part !== "true").join(" && "),
74577
- stage.field.alias,
74635
+ // No difference where either day is missing, which the tests before it already rule out.
74636
+ [...standing, dated(before), `coalesce(differenceInCalendarDays(${left(stage.field)}, ${left(before.field)}), 0) < 0`].filter((part) => part !== "true").join(" && "),
74637
+ landing(stage.field),
74578
74638
  str(words.refusals.milestoneBefore(stage.label, before.label))
74579
74639
  )
74580
74640
  ];
@@ -74582,12 +74642,17 @@ function milestoneOrder(stages, touches, left, words) {
74582
74642
  )
74583
74643
  );
74584
74644
  }
74585
- function milestoneClears(stages, table, record2, sent) {
74586
- return stages.slice(0, -1).flatMap((stage, at2) => [
74587
- `if (${sent(stage.field.alias)} && isNull(i.${stage.field.alias})) {`,
74588
- ` await update_records({ table_id: ${str(table)}, record_ids: [${record2}], set: { ${stages.slice(at2 + 1).map((later) => `${str(later.field.id)}: null`).join(", ")} } });`,
74589
- `}`
74590
- ]);
74645
+ function milestoneClears(stages, table, record2, s) {
74646
+ return stages.slice(0, -1).flatMap((stage, at2) => {
74647
+ const sent = sends(s, stage.field);
74648
+ if (sent === void 0) return [];
74649
+ const later = stages.slice(at2 + 1).map((one) => `${str(one.field.id)}: null`);
74650
+ return [
74651
+ `if (${sent} && isNull(${s.from}.${stage.field.alias})) {`,
74652
+ ` await update_records({ table_id: ${str(table)}, record_ids: [${record2}], set: { ${later.join(", ")} } });`,
74653
+ `}`
74654
+ ];
74655
+ });
74591
74656
  }
74592
74657
  function picksGuard(picks, words) {
74593
74658
  return picks.flatMap((pick3) => {
@@ -74618,21 +74683,45 @@ function makeDocument(template, row) {
74618
74683
  `});`
74619
74684
  ];
74620
74685
  }
74621
- function done(message2, data) {
74622
- return `return({ status: "success", message: ${str(message2)}${data === void 0 ? "" : `, data: ${data}`} });`;
74686
+ function done(message2, handed = {}) {
74687
+ const keys2 = Object.keys(handed);
74688
+ const data = keys2.length === 0 ? "" : `, data: { ${keys2.map((key) => `${key}: ${handed[key].value}`).join(", ")} }`;
74689
+ return `return({ status: "success", message: ${str(message2)}${data} });`;
74690
+ }
74691
+ function handedOutputs(handed) {
74692
+ return Object.fromEntries(Object.entries(handed).map(([key, one]) => [key, one.output]));
74623
74693
  }
74694
+ function handedRow(value, table) {
74695
+ return { value, output: { type: "record_link", table_id: table } };
74696
+ }
74697
+ var HANDED_PAPER = {
74698
+ file_id: { value: "paper.file_id", output: { type: "text" } },
74699
+ url: { value: "paper.url", output: { type: "text" } },
74700
+ filename: { value: "paper.filename", output: { type: "text" } }
74701
+ };
74624
74702
 
74625
74703
  // src/app_body_act.ts
74626
74704
  var MOVED_TO = "statusMovedTo";
74705
+ function actSending(plan) {
74706
+ return {
74707
+ from: "leaves",
74708
+ carries: /* @__PURE__ */ new Set([...plan.set.map((one) => one.field.alias), ...plan.asks.filter((one) => !editedByDelta(one.field.field)).map((one) => one.field.alias)]),
74709
+ inputs: /* @__PURE__ */ new Set([
74710
+ ...plan.asks.flatMap((one) => editedByDelta(one.field.field) ? [deltaOf(one.field).added, deltaOf(one.field).removed] : [one.field.alias]),
74711
+ ...plan.inputs.map((input) => input.name)
74712
+ ]),
74713
+ row: "row"
74714
+ };
74715
+ }
74627
74716
  function actGates(plan) {
74628
74717
  const { words } = plan;
74629
74718
  const asked = new Set(plan.asks.map((one) => one.field.alias));
74630
74719
  const lacks = (need) => {
74631
- const held = `row.data[${str(need.field.id)}]`;
74632
- if (!asked.has(need.field.alias)) return heldEmpty(need.field, held);
74633
- if (!editedByDelta(need.field.field)) return heldEmpty(need.field, `(includes(keys(i), ${str(need.field.alias)}) ? i.${need.field.alias} : ${held})`);
74720
+ const held2 = `row.data[${str(need.field.id)}]`;
74721
+ if (!asked.has(need.field.alias)) return heldEmpty(need.field, held2);
74722
+ if (!editedByDelta(need.field.field)) return heldEmpty(need.field, `(includes(keys(i), ${str(need.field.alias)}) ? i.${need.field.alias} : ${held2})`);
74634
74723
  const { added, removed } = deltaOf(need.field);
74635
- return `isEmpty(i.${added}) && size(i.${removed} ?? []) >= size(${held} ?? [])`;
74724
+ return `isEmpty(i.${added}) && size(i.${removed} ?? []) >= size(${held2} ?? [])`;
74636
74725
  };
74637
74726
  const set2 = new Map(plan.set.map((one) => [one.field.id, one.value]));
74638
74727
  return [
@@ -74655,7 +74744,8 @@ function actGates(plan) {
74655
74744
  // What the act sets it always writes; what it asks, where the ask is answered.
74656
74745
  (field2) => set2.has(field2.id) ? "true" : asked.has(field2.alias) ? `includes(keys(i), ${str(field2.alias)})` : void 0,
74657
74746
  (field2) => set2.get(field2.id) ?? (asked.has(field2.alias) ? `(includes(keys(i), ${str(field2.alias)}) ? i.${field2.alias} : row.data[${str(field2.id)}])` : `row.data[${str(field2.id)}]`),
74658
- words
74747
+ words,
74748
+ (field2) => landsOn(actSending(plan), field2)
74659
74749
  )
74660
74750
  ];
74661
74751
  }
@@ -74674,7 +74764,7 @@ function actWrite(plan, record2) {
74674
74764
  const name = deltaOf(one.field).removed;
74675
74765
  return [
74676
74766
  `if (!isEmpty(i.${name})) {`,
74677
- ` await update_records({ table_id: ${str(plan.table.id)}, record_ids: [${record2}], remove_from: { ${str(one.field.id)}: i.${name} } });`,
74767
+ ` await update_records({ table_id: ${str(plan.table.id)}, record_ids: [${record2}], remove_from: { ${str(one.field.id)}: i.${name} ?? [] } });`,
74678
74768
  `}`
74679
74769
  ];
74680
74770
  });
@@ -74717,22 +74807,27 @@ function actLeaves(plan) {
74717
74807
  }
74718
74808
  function actRules(plan, record2) {
74719
74809
  const { rules, words } = plan;
74810
+ const sending = actSending(plan);
74720
74811
  return [
74721
- ...fieldBoundChecks(rules.bounded, words, "row", "leaves"),
74722
- ...heldGuards(rules.held, record2, words, "leaves"),
74723
- ...keptGuards(rules.kept, rules.written, words, "leaves"),
74724
- ...strayGuards(rules.strays, record2, words, "leaves"),
74725
- ...rules.overlap === void 0 ? [] : overlapGuard(rules.overlap, plan.table, words, { row: "row", record: record2 }, void 0, "leaves")
74812
+ ...fieldBoundChecks(rules.bounded, words, sending),
74813
+ ...heldGuards(rules.held, record2, words, sending),
74814
+ ...keptGuards(rules.kept, rules.written, words, sending),
74815
+ ...strayGuards(rules.strays, record2, words, sending),
74816
+ ...rules.overlap === void 0 ? [] : overlapGuard(rules.overlap, plan.table, words, sending, record2)
74726
74817
  ];
74727
74818
  }
74819
+ function inputEmpty(type, value) {
74820
+ if (type === "text" || type === "long_text") return `isEmpty(trim(coalesce(${value}, "")))`;
74821
+ return type === "number" ? `isNull(${value})` : `isEmpty(${value})`;
74822
+ }
74728
74823
  function actInputChecks(plan) {
74729
74824
  const { words } = plan;
74730
74825
  return validateBlock([
74731
- ...plan.inputs.flatMap((input) => input.required ? [checkLine(`isEmpty(trim(coalesce(i.${input.name}, "")))`, input.name, str(words.refusals.required(input.label)))] : []),
74826
+ ...plan.inputs.flatMap((input) => input.required ? [checkLine(inputEmpty(input.type, `i.${input.name}`), input.name, str(words.refusals.required(input.label)))] : []),
74732
74827
  ...plan.inputs.flatMap(
74733
74828
  (input) => input.options === void 0 ? [] : [checkLine(`!isNull(i.${input.name}) && !includes([${input.options.map(str).join(", ")}], i.${input.name})`, input.name, str(words.refusals.choice(input.label)))]
74734
74829
  ),
74735
- ...actRuled(plan) ? valueChecks(plan.rules.written, words, void 0, "leaves") : []
74830
+ ...actRuled(plan) ? valueChecks(plan.rules.written, words, actSending(plan)) : []
74736
74831
  ]);
74737
74832
  }
74738
74833
  function actDocument(plan, record2) {
@@ -74789,9 +74884,9 @@ function actRegion(plan, authored = false) {
74789
74884
  ];
74790
74885
  }
74791
74886
  function actSource(plan) {
74792
- const handed = plan.template === void 0 ? void 0 : plan.on === "record" ? `{ record_id: i.${plan.param}, file_id: paper.file_id, url: paper.url, filename: paper.filename }` : `{ file_id: paper.file_id, url: paper.url, filename: paper.filename }`;
74793
- return `${[...actRegion(plan), done(plan.words.done.acted(plan.label), handed)].join("\n")}
74794
- `;
74887
+ const handed = plan.template === void 0 ? {} : plan.on === "record" ? { record_id: handedRow(`i.${plan.param}`, plan.table.id), ...HANDED_PAPER } : HANDED_PAPER;
74888
+ return { source: `${[...actRegion(plan), done(plan.words.done.acted(plan.label), handed)].join("\n")}
74889
+ `, outputs: handedOutputs(handed) };
74795
74890
  }
74796
74891
  function exportSource(plan) {
74797
74892
  const lines = [
@@ -74807,10 +74902,10 @@ function exportSource(plan) {
74807
74902
  ]),
74808
74903
  `}`,
74809
74904
  ...makeDocument(plan.template, "row"),
74810
- done(plan.words.done.made(plan.template.label), `{ file_id: paper.file_id, url: paper.url, filename: paper.filename }`)
74905
+ done(plan.words.done.made(plan.template.label), HANDED_PAPER)
74811
74906
  ];
74812
- return `${lines.join("\n")}
74813
- `;
74907
+ return { source: `${lines.join("\n")}
74908
+ `, outputs: handedOutputs(HANDED_PAPER) };
74814
74909
  }
74815
74910
  var GUARDS_START = "// <lotics:guards> Generated from the model by `lotics app regenerate` \u2014 edits between these lines are replaced.";
74816
74911
  var GUARDS_END = "// </lotics:guards> Your code follows.";
@@ -74845,13 +74940,19 @@ function createValue(field2) {
74845
74940
  }
74846
74941
  function createSource(plan) {
74847
74942
  const { words } = plan;
74943
+ const handedFields = [...plan.fields.map((one) => one.field), ...plan.parent === void 0 ? [] : [plan.parent.field]];
74944
+ const sending = {
74945
+ from: "i",
74946
+ carries: new Set(handedFields.filter((field2) => !editedByDelta(field2.field)).map((field2) => field2.alias)),
74947
+ inputs: new Set(handedFields.map((field2) => editedByDelta(field2.field) ? deltaOf(field2).added : field2.alias))
74948
+ };
74848
74949
  const lines = [`const i = trigger.app_workflow.inputs;`];
74849
74950
  lines.push(
74850
74951
  ...validateBlock([
74851
74952
  ...plan.fields.flatMap(
74852
74953
  (one) => one.required ? [checkLine(heldEmpty(one.field, createdInput(one.field)), one.field.alias, str(words.refusals.required(one.label)))] : []
74853
74954
  ),
74854
- ...valueChecks(plan.fields, words)
74955
+ ...valueChecks(plan.fields, words, sending)
74855
74956
  ])
74856
74957
  );
74857
74958
  if (plan.parent !== void 0 && (plan.parent.table.scope !== void 0 || plan.parent.checks.length > 0)) {
@@ -74870,14 +74971,14 @@ function createSource(plan) {
74870
74971
  plan.milestones,
74871
74972
  (field2) => dates(field2) ? "true" : void 0,
74872
74973
  (field2) => asked.has(field2.id) ? createdInput(field2) : stamped.get(field2.id) === "now" ? "now()" : createdDefault(field2),
74873
- words
74974
+ words,
74975
+ (field2) => landsOn(sending, field2)
74874
74976
  )
74875
74977
  );
74876
74978
  }
74877
- lines.push(...fieldBoundChecks(plan.fields, words, void 0));
74878
- if (plan.overlap !== void 0) lines.push(...overlapGuard(plan.overlap, plan.table, words, void 0, new Set(plan.fields.map((one) => one.field.alias))));
74879
- const handed = /* @__PURE__ */ new Set([...plan.fields.map((one) => one.field.alias), ...plan.parent === void 0 ? [] : [plan.parent.field.alias]]);
74880
- for (const expect of plan.expects) lines.push(...expectGuard(expect, plan.table, words, void 0, handed));
74979
+ lines.push(...fieldBoundChecks(plan.fields, words, sending));
74980
+ if (plan.overlap !== void 0) lines.push(...overlapGuard(plan.overlap, plan.table, words, sending));
74981
+ for (const expect of plan.expects) lines.push(...expectGuard(expect, plan.table, words, sending));
74881
74982
  for (const field2 of plan.unique) {
74882
74983
  const local = `${camel(field2.alias)}Taken`;
74883
74984
  lines.push(
@@ -74915,7 +75016,7 @@ function createSource(plan) {
74915
75016
  ` },`,
74916
75017
  ` ],`,
74917
75018
  `});`,
74918
- `opened = made.record_ids[0];`,
75019
+ `const opened = made.record_ids[0];`,
74919
75020
  ...plan.history === void 0 ? [] : [
74920
75021
  `const openedRow = await get_record({ table_id: ${str(plan.table.id)}, record_id: opened });`,
74921
75022
  `if (!isNull(openedRow.data[${str(plan.history.source.id)}])) {`,
@@ -74923,10 +75024,13 @@ function createSource(plan) {
74923
75024
  `}`
74924
75025
  ]
74925
75026
  ];
75027
+ const handing = (row, reused) => ({ record_id: handedRow(row, plan.table.id), reused: { value: String(reused), output: { type: "boolean" } } });
75028
+ const outputs = handedOutputs(handing("opened", false));
75029
+ const returned = done(words.done.opened(plan.table.label), handing("opened", false));
74926
75030
  if (plan.naturalKey.length === 0) {
74927
- lines.push(`let opened = null;`, ...opening, done(words.done.opened(plan.table.label), `{ record_id: opened, reused: false }`));
74928
- return `${lines.join("\n")}
74929
- `;
75031
+ lines.push(...opening, returned);
75032
+ return { source: `${lines.join("\n")}
75033
+ `, outputs };
74930
75034
  }
74931
75035
  const conditions = plan.naturalKey.map(
74932
75036
  (field2) => field2.field.type === "select_record_link" ? `{ node_type: "condition", field_key: ${str(field2.id)}, operator: "has_any_of", value: [i.${field2.alias}] }` : `{ node_type: "condition", field_key: ${str(field2.id)}, operator: "equals", value: ${field2.field.type === "text" ? `trim(coalesce(i.${field2.alias}, ""))` : `i.${field2.alias}`} }`
@@ -74939,21 +75043,14 @@ function createSource(plan) {
74939
75043
  `});`,
74940
75044
  `const found = first(known.records);`,
74941
75045
  ...plan.table.scope === void 0 ? [] : validateBlock([checkLine(`!isNull(found) && !(${scopePasses(plan.table.scope, "found")})`, first2?.alias, str(words.refusals.taken(first2?.field.label ?? "")))]),
74942
- `let opened = null;`,
74943
- `if (isNull(found)) {`,
74944
- ...indent(opening),
74945
- `}`,
74946
75046
  `if (!isNull(found)) {`,
74947
- ` opened = found.id;`,
75047
+ ` ${done(words.done.reused(plan.table.label), handing("found.id", true))}`,
74948
75048
  `}`,
74949
- `return({`,
74950
- ` status: "success",`,
74951
- ` message: isNull(found) ? ${str(words.done.opened(plan.table.label))} : ${str(words.done.reused(plan.table.label))},`,
74952
- ` data: { record_id: opened, reused: !isNull(found) },`,
74953
- `});`
75049
+ ...opening,
75050
+ returned
74954
75051
  );
74955
- return `${lines.join("\n")}
74956
- `;
75052
+ return { source: `${lines.join("\n")}
75053
+ `, outputs };
74957
75054
  }
74958
75055
 
74959
75056
  // src/app_body_update.ts
@@ -74961,15 +75058,22 @@ init_define_LOTICS_KIT_VERSIONS();
74961
75058
  function updateSource(plan) {
74962
75059
  const { words } = plan;
74963
75060
  const record2 = `i.${plan.param}`;
75061
+ const handed = { record_id: handedRow(record2, plan.table.id) };
74964
75062
  const sent = (name) => `includes(keys(i), ${str(name)})`;
75063
+ const sending = {
75064
+ from: "i",
75065
+ carries: new Set(plan.fields.filter((one) => !editedByDelta(one.field.field)).map((one) => one.field.alias)),
75066
+ inputs: new Set(plan.fields.flatMap((one) => editedByDelta(one.field.field) ? [deltaOf(one.field).added, deltaOf(one.field).removed] : [one.field.alias])),
75067
+ row: "row"
75068
+ };
74965
75069
  const locked = plan.locks.some((lock) => lock.checks.length > 0);
74966
75070
  const readsRow = scopeReadsRow(plan.table.scope) || plan.checks.length > 0 || locked || plan.history !== void 0 || boundsByField(plan.bounded) || plan.overlap !== void 0 || plan.expects.length > 0 || plan.milestones.length > 0 || plan.kept.length > 0 || sameReadsRow(plan.fields);
74967
75071
  const saved = new Set(plan.fields.map((one) => one.field.id));
74968
75072
  const left = (field2) => {
74969
- const held = `(${sent(field2.alias)} ? i.${field2.alias} : row.data[${str(field2.id)}])`;
75073
+ const held2 = leftBy(sending, field2);
74970
75074
  const at2 = plan.milestones.findIndex((stage) => stage.field.id === field2.id);
74971
75075
  const clears = plan.milestones.slice(0, Math.max(at2, 0)).filter((stage) => saved.has(stage.field.id)).map((stage) => `(${sent(stage.field.alias)} && isNull(i.${stage.field.alias}))`);
74972
- return clears.length === 0 ? held : `(${clears.join(" || ")} ? null : ${held})`;
75076
+ return clears.length === 0 ? held2 : `(${clears.join(" || ")} ? null : ${held2})`;
74973
75077
  };
74974
75078
  const write = (op, name) => [
74975
75079
  `if (${sent(name)}) {`,
@@ -74994,32 +75098,35 @@ function updateSource(plan) {
74994
75098
  ...plan.fields.flatMap(
74995
75099
  (one) => !one.required || editedByDelta(one.field.field) ? [] : [checkLine(`${sent(one.field.alias)} && ${heldEmpty(one.field, `i.${one.field.alias}`)}`, one.field.alias, str(words.refusals.required(one.label)))]
74996
75100
  ),
74997
- ...valueChecks(plan.fields, words, sent)
75101
+ ...valueChecks(plan.fields, words, sending)
74998
75102
  ]),
74999
75103
  ...readsRow ? [`const row = await get_record({ table_id: ${str(plan.table.id)}, record_id: ${record2} });`] : [],
75000
75104
  ...scopeGuard(plan.table, "row", words),
75001
75105
  ...checksGuard(plan.checks, "row"),
75002
75106
  ...locksGuard(plan.locks, "row", words),
75003
- ...fieldBoundChecks(plan.bounded, words, "row"),
75004
- ...heldGuards(plan.held, record2, words),
75005
- ...plan.overlap === void 0 ? [] : overlapGuard(plan.overlap, plan.table, words, { row: "row", record: record2 }),
75006
- ...plan.expects.flatMap((expect) => expectGuard(expect, plan.table, words, { row: "row", record: record2 })),
75107
+ ...fieldBoundChecks(plan.bounded, words, sending),
75108
+ ...heldGuards(plan.held, record2, words, sending),
75109
+ ...plan.overlap === void 0 ? [] : overlapGuard(plan.overlap, plan.table, words, sending, record2),
75110
+ ...plan.expects.flatMap((expect) => expectGuard(expect, plan.table, words, sending, record2)),
75007
75111
  // A `same` link compares with what the row names once the save lands: the value sent, else the one held.
75008
- ...linkChecks(plan.fields, words, (field2) => `(${sent(field2.alias)} ? i.${field2.alias} : first(row.data[${str(field2.id)}]))`),
75009
- ...keptGuards(plan.kept, plan.fields, words, "i"),
75010
- ...strayGuards(plan.strays, record2, words, "i"),
75011
- ...milestoneOrder(plan.milestones, (field2) => saved.has(field2.id) ? sent(field2.alias) : void 0, left, words),
75112
+ ...linkChecks(plan.fields, words, (field2) => {
75113
+ const moved = sends(sending, field2);
75114
+ return moved === void 0 ? `first(row.data[${str(field2.id)}])` : `(${moved} ? i.${field2.alias} : first(row.data[${str(field2.id)}]))`;
75115
+ }),
75116
+ ...keptGuards(plan.kept, plan.fields, words, sending),
75117
+ ...strayGuards(plan.strays, record2, words, sending),
75118
+ ...milestoneOrder(plan.milestones, (field2) => saved.has(field2.id) ? sent(field2.alias) : void 0, left, words, (field2) => landsOn(sending, field2)),
75012
75119
  ...writes,
75013
- ...milestoneClears(plan.milestones, plan.table.id, record2, sent),
75120
+ ...milestoneClears(plan.milestones, plan.table.id, record2, sending),
75014
75121
  ...history === void 0 ? [] : [
75015
75122
  `if (${sent(history.source.alias)} && i.${history.source.alias} != row.data[${str(history.source.id)}] && !isNull(i.${history.source.alias})) {`,
75016
75123
  ...indent(appendHistory(history, record2, `i.${history.source.alias}`)),
75017
75124
  `}`
75018
75125
  ],
75019
- done(words.done.updated(plan.table.label), `{ record_id: ${record2} }`)
75126
+ done(words.done.updated(plan.table.label), handed)
75020
75127
  ];
75021
- return `${lines.join("\n")}
75022
- `;
75128
+ return { source: `${lines.join("\n")}
75129
+ `, outputs: handedOutputs(handed) };
75023
75130
  }
75024
75131
  function removeSource(plan) {
75025
75132
  const record2 = `i.${plan.param}`;
@@ -75033,8 +75140,8 @@ function removeSource(plan) {
75033
75140
  `await delete_records({ table_id: ${str(plan.table.id)}, record_ids: [${record2}] });`,
75034
75141
  done(plan.words.done.removed(plan.table.label))
75035
75142
  ];
75036
- return `${lines.join("\n")}
75037
- `;
75143
+ return { source: `${lines.join("\n")}
75144
+ `, outputs: {} };
75038
75145
  }
75039
75146
 
75040
75147
  // src/app_compile_context.ts
@@ -75660,10 +75767,10 @@ function historyCopies(ctx, act, entity, history) {
75660
75767
  const shape = historyShape(ctx.model, entity);
75661
75768
  if (shape === void 0 || "refused" in shape) return [];
75662
75769
  const taken = /* @__PURE__ */ new Set([shape.link, shape.status.field, shape.at.field, ...shape.by === void 0 ? [] : [shape.by]]);
75663
- const held = ctx.bound.entities.get(shape.entity)?.fields;
75770
+ const held2 = ctx.bound.entities.get(shape.entity)?.fields;
75664
75771
  return act.asks.flatMap((ask) => {
75665
75772
  const name = typeof ask === "string" ? ask : ask.input;
75666
- const into = held?.get(name);
75773
+ const into = held2?.get(name);
75667
75774
  if (into === void 0 || taken.has(name) || editedByDelta(into.field)) return [];
75668
75775
  if (typeof ask !== "string") return [{ into, value: inputInto(into, `i.${name}`) }];
75669
75776
  const from = fieldOf2(ctx, entity, name);
@@ -75730,7 +75837,7 @@ function actPlan(ctx, act) {
75730
75837
  requires: act.requires.map((name) => ({ field: fieldOf2(ctx, entity, name), label: label2(name) })),
75731
75838
  asks,
75732
75839
  inputs: act.asks.flatMap(
75733
- (ask) => typeof ask === "string" ? [] : [{ name: ask.input, label: ask.label, required: ask.required === true, ...ask.options === void 0 ? {} : { options: ask.options.map((option) => option.alias) } }]
75840
+ (ask) => typeof ask === "string" ? [] : [{ name: ask.input, label: ask.label, type: ask.type, required: ask.required === true, ...ask.options === void 0 ? {} : { options: ask.options.map((option) => option.alias) } }]
75734
75841
  ),
75735
75842
  set: Object.entries(act.set).map(([name, value]) => {
75736
75843
  const bf = fieldOf2(ctx, entity, name);
@@ -75784,7 +75891,8 @@ function createInputs(ctx, alias2, writes) {
75784
75891
  const inputs = {};
75785
75892
  if (writes.parent !== void 0) {
75786
75893
  const via = fieldOf2(ctx, alias2, writes.parent);
75787
- inputs[via.alias] = fieldInput(ctx, via, true);
75894
+ if (via.field.type !== "select_record_link") fail(`"${alias2}.${via.alias}" files a row under its record, so it must be a link`);
75895
+ inputs[via.alias] = { type: "record_link", table_id: entityOf3(ctx, via.field.target_entity).table, required: true };
75788
75896
  }
75789
75897
  for (const name of writes.create ?? []) {
75790
75898
  const bf = fieldOf2(ctx, alias2, name);
@@ -76266,9 +76374,9 @@ function compileApp(model, app, bound, tables) {
76266
76374
  const written = /* @__PURE__ */ new Map();
76267
76375
  const deletes = /* @__PURE__ */ new Set();
76268
76376
  const wrote = (table, fields) => {
76269
- const held = written.get(table) ?? /* @__PURE__ */ new Set();
76270
- for (const field2 of fields) held.add(field2.id);
76271
- written.set(table, held);
76377
+ const held2 = written.get(table) ?? /* @__PURE__ */ new Set();
76378
+ for (const field2 of fields) held2.add(field2.id);
76379
+ written.set(table, held2);
76272
76380
  };
76273
76381
  const entities = {};
76274
76382
  for (const alias2 of drawn.keys()) {
@@ -76282,8 +76390,9 @@ function compileApp(model, app, bound, tables) {
76282
76390
  if (writes?.create !== void 0) {
76283
76391
  const workflow = createAlias(alias2);
76284
76392
  const create = createPlan(ctx, alias2, writes);
76285
- bodies[workflow] = createSource(create);
76286
- workflows[workflow] = { inputs: createInputs(ctx, alias2, writes), description: described(words.workflows.create(bent.entity.label), bent.entity) };
76393
+ const body = createSource(create);
76394
+ bodies[workflow] = body.source;
76395
+ workflows[workflow] = { inputs: createInputs(ctx, alias2, writes), outputs: body.outputs, description: described(words.workflows.create(bent.entity.label), bent.entity) };
76287
76396
  wrote(bent.table, [
76288
76397
  ...create.fields.map((one) => one.field),
76289
76398
  ...create.parent === void 0 ? [] : [create.parent.field],
@@ -76301,7 +76410,7 @@ function compileApp(model, app, bound, tables) {
76301
76410
  const overlap = bodyOverlap(ctx, alias2);
76302
76411
  const stages = bodyMilestones(ctx, alias2);
76303
76412
  const milestones = touchesMilestones(stages, new Set(fields.map((one) => one.field.alias))) ? stages : [];
76304
- bodies[workflow] = updateSource({
76413
+ const body = updateSource({
76305
76414
  table,
76306
76415
  param: RECORD_INPUT,
76307
76416
  fields,
@@ -76317,15 +76426,21 @@ function compileApp(model, app, bound, tables) {
76317
76426
  milestones,
76318
76427
  words
76319
76428
  });
76320
- workflows[workflow] = { inputs: updateInputs(ctx, alias2, fields), description: described(words.workflows.update(bent.entity.label), bent.entity) };
76429
+ bodies[workflow] = body.source;
76430
+ workflows[workflow] = { inputs: updateInputs(ctx, alias2, fields), outputs: body.outputs, description: described(words.workflows.update(bent.entity.label), bent.entity) };
76321
76431
  wrote(bent.table, [...fields.map((one) => one.field), ...milestones.map((stage) => stage.field)]);
76322
76432
  if (moves !== void 0) wrote(moves.table, historyFields(moves));
76323
76433
  spec2.update = { workflow };
76324
76434
  }
76325
76435
  if (writes?.remove === true) {
76326
76436
  const workflow = removeAlias(alias2);
76327
- bodies[workflow] = removeSource({ table, param: RECORD_INPUT, checks: bodyChecks(ctx, "delete", alias2), locks: parentLocks(ctx, alias2), words });
76328
- workflows[workflow] = { inputs: { [RECORD_INPUT]: { type: "record_link", table_id: bent.table, required: true } }, description: described(words.workflows.remove(bent.entity.label), bent.entity) };
76437
+ const body = removeSource({ table, param: RECORD_INPUT, checks: bodyChecks(ctx, "delete", alias2), locks: parentLocks(ctx, alias2), words });
76438
+ bodies[workflow] = body.source;
76439
+ workflows[workflow] = {
76440
+ inputs: { [RECORD_INPUT]: { type: "record_link", table_id: bent.table, required: true } },
76441
+ outputs: body.outputs,
76442
+ description: described(words.workflows.remove(bent.entity.label), bent.entity)
76443
+ };
76329
76444
  deletes.add(bent.table);
76330
76445
  spec2.remove = { workflow };
76331
76446
  }
@@ -76334,11 +76449,13 @@ function compileApp(model, app, bound, tables) {
76334
76449
  const acts = app.acts.map((act) => {
76335
76450
  const body = actPlan(ctx, act);
76336
76451
  const workflow = act.workflow ?? actAlias(act.alias);
76337
- if (act.workflow === void 0) bodies[workflow] = actSource(body);
76338
- else authored[workflow] = body;
76452
+ const generated = act.workflow === void 0 ? actSource(body) : void 0;
76453
+ if (generated === void 0) authored[workflow] = body;
76454
+ else bodies[workflow] = generated.source;
76339
76455
  const on = entityOf3(ctx, actEntity(ctx.app, act)).entity;
76340
76456
  workflows[workflow] = {
76341
76457
  inputs: actInputs(ctx, act),
76458
+ ...generated === void 0 ? {} : { outputs: generated.outputs },
76342
76459
  description: described(act.on === "record" ? words.workflows.act(act.label, on.label) : words.workflows.actOnRows(act.label, on.label), on)
76343
76460
  };
76344
76461
  wrote(body.table.id, [...body.set.map((one) => one.field), ...body.asks.map((one) => one.field), ...body.template?.into === void 0 ? [] : [body.template.into]]);
@@ -76350,10 +76467,12 @@ function compileApp(model, app, bound, tables) {
76350
76467
  if (exported !== void 0 && exported !== true) {
76351
76468
  const workflow = exportAlias(app.entity);
76352
76469
  const body = exportPlan(ctx, exported.template);
76353
- bodies[workflow] = exportSource(body);
76470
+ const generated = exportSource(body);
76471
+ bodies[workflow] = generated.source;
76354
76472
  const own = entityOf3(ctx, app.entity).entity;
76355
76473
  workflows[workflow] = {
76356
76474
  inputs: { [RECORDS_INPUT]: { type: "record_link", table_id: body.table.id, multi: true, max: RECORDS_INPUT_MAX, required: true } },
76475
+ outputs: generated.outputs,
76357
76476
  description: described(words.workflows.export(body.template.label, own.label), own)
76358
76477
  };
76359
76478
  exportSpec = { workflow };
@@ -76512,8 +76631,8 @@ function writesDeclaration(queries, tables, written) {
76512
76631
  const aliased = codegenAliases(queries, tables);
76513
76632
  const out = {};
76514
76633
  for (const [table, fields] of written) {
76515
- const held = aliased.find((one) => one.table.id === table);
76516
- out[held?.alias ?? table] = [...fields].map((id) => held?.fields.find((one) => one.field.id === id)?.alias ?? id).sort();
76634
+ const held2 = aliased.find((one) => one.table.id === table);
76635
+ out[held2?.alias ?? table] = [...fields].map((id) => held2?.fields.find((one) => one.field.id === id)?.alias ?? id).sort();
76517
76636
  }
76518
76637
  return out;
76519
76638
  }
@@ -76593,15 +76712,16 @@ var projectionSchema = zod_default.object({
76593
76712
  function projectionOf(declaration) {
76594
76713
  const read2 = projectionSchema.safeParse(declaration?.ast);
76595
76714
  if (!read2.success) return void 0;
76715
+ const columns = read2.data.columns.flatMap((column) => {
76716
+ if (typeof column === "string") return [{ output: column, key: column }];
76717
+ const key = typeof column.source === "string" ? column.source : void 0;
76718
+ const output = column.output ?? key;
76719
+ return output === void 0 ? [] : [{ output, ...key === void 0 ? {} : { key } }];
76720
+ });
76596
76721
  return {
76597
76722
  table: read2.data.from.table_id,
76598
- outputs: new Set(
76599
- read2.data.columns.flatMap((column) => {
76600
- if (typeof column === "string") return [column];
76601
- if (column.output !== void 0) return [column.output];
76602
- return typeof column.source === "string" ? [column.source] : [];
76603
- })
76604
- )
76723
+ outputs: new Set(columns.map((column) => column.output)),
76724
+ keys: new Map(columns.flatMap((column) => column.key === void 0 ? [] : [[column.output, column.key]]))
76605
76725
  };
76606
76726
  }
76607
76727
  var paramsOf = (manifest, alias2) => new Set(Object.keys(manifest.queries?.[alias2]?.params ?? {}));
@@ -76635,6 +76755,8 @@ function checkRead(at2, read2, entity, manifest) {
76635
76755
  if (projection2.table !== entity.table) findings.push({ at: at2, message: `reads ${entity.label} (${entity.table}) through "${read2.query}", which reads ${projection2.table}` });
76636
76756
  for (const field2 of Object.values(entity.fields)) {
76637
76757
  if (!projection2.outputs.has(field2.alias)) findings.push({ at: at2, message: `draws "${field2.alias}" (${field2.label}), which "${read2.query}" does not project` });
76758
+ const key = projection2.keys.get(field2.alias);
76759
+ if (key !== void 0 && key !== field2.id) findings.push({ at: at2, message: `reads "${field2.alias}" as field \`${field2.id}\`, and "${read2.query}" projects it from \`${key}\`` });
76638
76760
  for (const [linked, column] of Object.entries(field2.face ?? {})) {
76639
76761
  if (!projection2.outputs.has(column)) findings.push({ at: at2, message: `draws the "${linked}" of the row "${field2.alias}" names under "${column}", which "${read2.query}" does not project` });
76640
76762
  }
@@ -76861,8 +76983,8 @@ export const components: RuntimeComponents = {};
76861
76983
  function buildAppFiles(app, compiled, existing) {
76862
76984
  const generated = Object.entries(compiled.bodies).map(([alias2, body]) => ({ path: workflowPath(alias2), content: body }));
76863
76985
  const authored = Object.entries(compiled.authored).map(([alias2, plan]) => {
76864
- const held = existing(workflowPath(alias2));
76865
- return { path: workflowPath(alias2), content: injectGuards(held === null ? null : stripWorkflowHeader(held), plan) };
76986
+ const held2 = existing(workflowPath(alias2));
76987
+ return { path: workflowPath(alias2), content: injectGuards(held2 === null ? null : stripWorkflowHeader(held2), plan) };
76866
76988
  });
76867
76989
  return [
76868
76990
  { path: APP_SPEC_PATH, content: `${JSON.stringify(compiled.spec, null, 2)}
@@ -79578,8 +79700,8 @@ function planFilterTraversal(filter2, aggregatedTableId, aggregatedFields, linke
79578
79700
  }
79579
79701
  const fieldMaps = /* @__PURE__ */ new Map();
79580
79702
  const fieldMapOf = (tableId2, fields) => {
79581
- const held = fieldMaps.get(tableId2);
79582
- if (held) return held;
79703
+ const held2 = fieldMaps.get(tableId2);
79704
+ if (held2) return held2;
79583
79705
  const byKey = {};
79584
79706
  for (const f of fields) byKey[f.key] = f;
79585
79707
  fieldMaps.set(tableId2, byKey);
@@ -80052,9 +80174,9 @@ function idsOf(cell) {
80052
80174
  });
80053
80175
  }
80054
80176
  function heldBy(records, tableId2) {
80055
- const held = records.get(tableId2);
80056
- if (held === void 0) throw new Error(`this model's tables do not include ${tableId2}`);
80057
- return held;
80177
+ const held2 = records.get(tableId2);
80178
+ if (held2 === void 0) throw new Error(`this model's tables do not include ${tableId2}`);
80179
+ return held2;
80058
80180
  }
80059
80181
  function fieldsOf(fieldsByTable, tableId2) {
80060
80182
  const fields = fieldsByTable.get(tableId2);
@@ -80289,9 +80411,9 @@ function pairBack(field2, value, from, byRef, entityByAlias, tableByAlias, field
80289
80411
  if (typeof ref !== "string") continue;
80290
80412
  const linked = byRef.get(ref);
80291
80413
  if (linked === void 0) continue;
80292
- const held = readLinkCell(linked.cells.get(partnerKey));
80293
- if (held.some((one) => one.id === from.id)) continue;
80294
- linked.cells.set(partnerKey, [...held, { id: from.id, display: "" }]);
80414
+ const held2 = readLinkCell(linked.cells.get(partnerKey));
80415
+ if (held2.some((one) => one.id === from.id)) continue;
80416
+ linked.cells.set(partnerKey, [...held2, { id: from.id, display: "" }]);
80295
80417
  }
80296
80418
  }
80297
80419
  function readLinkCell(cell) {
@@ -80360,20 +80482,20 @@ function buildDisplays(entities, display, records, tableByAlias, fieldsByTable)
80360
80482
  heldBy(records, table.id).forEach((record2, r) => cellOf.set(record2.id, { entity, record: record2, r, key: at2 === -1 ? void 0 : fields[at2]?.key, link }));
80361
80483
  }
80362
80484
  const nameOf2 = (id, seen) => {
80363
- const held = cellOf.get(id);
80364
- if (held === void 0 || seen.has(id)) return void 0;
80365
- const stated2 = held.key === void 0 ? void 0 : held.record.cells.get(held.key);
80366
- if (held.link) {
80485
+ const held2 = cellOf.get(id);
80486
+ if (held2 === void 0 || seen.has(id)) return void 0;
80487
+ const stated2 = held2.key === void 0 ? void 0 : held2.record.cells.get(held2.key);
80488
+ if (held2.link) {
80367
80489
  const through = readLinkCell(stated2)[0];
80368
80490
  const name = through === void 0 ? void 0 : nameOf2(through.id, /* @__PURE__ */ new Set([...seen, id]));
80369
80491
  if (name !== void 0) return name;
80370
80492
  } else if (typeof stated2 === "string" && stated2 !== "") return stated2;
80371
- return `${held.entity.singular ?? held.entity.label} ${held.r + 1}`;
80493
+ return `${held2.entity.singular ?? held2.entity.label} ${held2.r + 1}`;
80372
80494
  };
80373
80495
  return new Map([...cellOf.keys()].map((id) => [id, nameOf2(id, /* @__PURE__ */ new Set()) ?? id]));
80374
80496
  }
80375
80497
  function fillLinkDisplays(records, displays, fieldsByTable) {
80376
- for (const [tableId2, held] of records) {
80498
+ for (const [tableId2, held2] of records) {
80377
80499
  const linking = new Set(
80378
80500
  (fieldsByTable.get(tableId2) ?? []).filter((field2) => field2.type === "select_record_link" || field2.type === "lookup" && field2.lookup_field_type === "select_record_link").map((field2) => field2.key)
80379
80501
  );
@@ -80382,14 +80504,14 @@ function fillLinkDisplays(records, displays, fieldsByTable) {
80382
80504
  const links = readLinkCell(cell);
80383
80505
  return links.length === 0 ? cell : links.map((link) => ({ id: link.id, display: displays.get(link.id) ?? link.id }));
80384
80506
  };
80385
- for (const record2 of held) {
80507
+ for (const record2 of held2) {
80386
80508
  for (const [key, cell] of record2.cells) {
80387
80509
  if (linking.has(key)) record2.cells.set(key, named(cell));
80388
80510
  }
80389
80511
  }
80390
80512
  }
80391
80513
  }
80392
- function narrowings(filter2, held) {
80514
+ function narrowings(filter2, held2) {
80393
80515
  const found = {};
80394
80516
  const visit2 = (node) => {
80395
80517
  if (node === null || typeof node !== "object") return;
@@ -80405,12 +80527,12 @@ function narrowings(filter2, held) {
80405
80527
  const operator = Reflect.get(node, "operator");
80406
80528
  const days = (side) => {
80407
80529
  if (typeof fieldKey !== "string") return void 0;
80408
- return { side, day: Object.fromEntries(held.flatMap((record2) => {
80530
+ return { side, day: Object.fromEntries(held2.flatMap((record2) => {
80409
80531
  const day = dayOf2(record2.cells.get(fieldKey));
80410
80532
  return day === null ? [] : [[record2.id, day]];
80411
80533
  })) };
80412
80534
  };
80413
- const by = type === "record_id" ? { own: operator === "is_none_of" ? "not" : "is" } : operator === "on_or_after" ? days("from") : operator === "on_or_before" ? days("to") : typeof fieldKey === "string" ? { holds: Object.fromEntries(held.map((record2) => [record2.id, idsOf(record2.cells.get(fieldKey))])) } : void 0;
80535
+ const by = type === "record_id" ? { own: operator === "is_none_of" ? "not" : "is" } : operator === "on_or_after" ? days("from") : operator === "on_or_before" ? days("to") : typeof fieldKey === "string" ? { holds: Object.fromEntries(held2.map((record2) => [record2.id, idsOf(record2.cells.get(fieldKey))])) } : void 0;
80414
80536
  if (by === void 0) return;
80415
80537
  for (const token2 of tokens) found[token2] = [...found[token2] ?? [], by];
80416
80538
  };
@@ -80448,12 +80570,12 @@ function census(entities, tableByAlias, records, fieldsByTable) {
80448
80570
  for (const entity of entities) {
80449
80571
  const table = tableByAlias.get(entity.alias);
80450
80572
  if (table === void 0) continue;
80451
- const held = heldBy(records, table.id);
80452
- if (held.length === 0) continue;
80573
+ const held2 = heldBy(records, table.id);
80574
+ if (held2.length === 0) continue;
80453
80575
  const fields = fieldsOf(fieldsByTable, table.id);
80454
80576
  entity.fields.forEach((field2, f) => {
80455
80577
  if (!COMPUTED.has(field2.type)) return;
80456
- const cells = held.map((record2) => record2.cells.get(fields[f].key));
80578
+ const cells = held2.map((record2) => record2.cells.get(fields[f].key));
80457
80579
  const named = `${entity.alias}.${field2.alias} (${field2.type})`;
80458
80580
  if (cells.some(holdsFormulaError)) errored.push(named);
80459
80581
  else if (cells.every(isEmptyFieldValue)) unfilled.push(named);
@@ -80522,10 +80644,10 @@ function computeDerived(order, tableByAlias, records, fieldsByTable) {
80522
80644
  contexts.set(table.id, context);
80523
80645
  }
80524
80646
  const linkedRecords = new Map(
80525
- [...records].map(([id, held2]) => [id, held2.map(asTableRecord)])
80647
+ [...records].map(([id, held3]) => [id, held3.map(asTableRecord)])
80526
80648
  );
80527
- const held = heldBy(records, table.id);
80528
- const computed = held.map(
80649
+ const held2 = heldBy(records, table.id);
80650
+ const computed = held2.map(
80529
80651
  (record2) => computeFormulaFieldsWithContext(
80530
80652
  Object.fromEntries(record2.cells),
80531
80653
  fields,
@@ -80537,7 +80659,7 @@ function computeDerived(order, tableByAlias, records, fieldsByTable) {
80537
80659
  linkExpanded
80538
80660
  )[key]
80539
80661
  );
80540
- held.forEach((record2, r) => {
80662
+ held2.forEach((record2, r) => {
80541
80663
  const value = computed[r];
80542
80664
  if (value === void 0) record2.cells.delete(key);
80543
80665
  else record2.cells.set(key, value);
@@ -82796,8 +82918,8 @@ function reusesInstall(project2) {
82796
82918
  const stamp = `${project2.dir}.json`;
82797
82919
  if (!fs23.existsSync(path24.join(project2.dir, "node_modules", "vite", "bin", "vite.js"))) return false;
82798
82920
  if (!fs23.existsSync(stamp)) return false;
82799
- const held = stampSchema.safeParse(parseJson2(fs23.readFileSync(stamp, "utf8")));
82800
- return held.success && JSON.stringify(sortedStamp(held.data)) === JSON.stringify(sortedStamp(project2.stamp));
82921
+ const held2 = stampSchema.safeParse(parseJson2(fs23.readFileSync(stamp, "utf8")));
82922
+ return held2.success && JSON.stringify(sortedStamp(held2.data)) === JSON.stringify(sortedStamp(project2.stamp));
82801
82923
  }
82802
82924
  function sortedStamp(stamp) {
82803
82925
  return { key: stamp.key, tarballs: sorted(stamp.tarballs) };
@@ -83116,8 +83238,8 @@ async function appPreview(args) {
83116
83238
  rmSync2(staging, { recursive: true, force: true });
83117
83239
  }
83118
83240
  }
83119
- async function previewIn(args, held) {
83120
- const { plan, project: project2, widths, timed, phases, specs, kits } = held;
83241
+ async function previewIn(args, held2) {
83242
+ const { plan, project: project2, widths, timed, phases, specs, kits } = held2;
83121
83243
  const projectDir = project2.dir;
83122
83244
  const bundle = probeBundle();
83123
83245
  if (args.shots !== void 0) mkdirSync2(args.shots, { recursive: true });
@@ -83144,7 +83266,7 @@ async function previewIn(args, held) {
83144
83266
  screens: args.screens,
83145
83267
  shots: args.shots,
83146
83268
  sleep: sleep5,
83147
- ...held.record === void 0 ? {} : { record: held.record }
83269
+ ...held2.record === void 0 ? {} : { record: held2.record }
83148
83270
  });
83149
83271
  } finally {
83150
83272
  await dev.stop();
@@ -85062,8 +85184,8 @@ function planGeneration(projectDir, generated, retiredWorkflows) {
85062
85184
  }
85063
85185
  for (const alias2 of retiredWorkflows) {
85064
85186
  const relative3 = `${WORKFLOW_DIR}${alias2}${WORKFLOW_SUFFIX}`;
85065
- const held = read(projectDir, relative3);
85066
- if (held === null || held.includes(GUARDS_START)) continue;
85187
+ const held2 = read(projectDir, relative3);
85188
+ if (held2 === null || held2.includes(GUARDS_START)) continue;
85067
85189
  deletes.push(relative3);
85068
85190
  outcomes.push({ path: relative3, kind: "deleted" });
85069
85191
  }
@@ -85141,12 +85263,12 @@ function plannedWorkflowBodies(projectDir, plan, declared) {
85141
85263
  const alias2 = workflowAlias(write.path);
85142
85264
  if (alias2 !== null) sources.set(alias2, write.content);
85143
85265
  }
85144
- const held = [...sources].map(([alias2, source]) => ({
85266
+ const held2 = [...sources].map(([alias2, source]) => ({
85145
85267
  alias: alias2,
85146
85268
  source: stripWorkflowHeader(source)
85147
85269
  }));
85148
85270
  const missing = declared.filter((alias2) => !sources.has(alias2)).map((alias2) => ({ alias: alias2, source: null }));
85149
- return [...held, ...missing].sort((a, b) => a.alias < b.alias ? -1 : a.alias > b.alias ? 1 : 0);
85271
+ return [...held2, ...missing].sort((a, b) => a.alias < b.alias ? -1 : a.alias > b.alias ? 1 : 0);
85150
85272
  }
85151
85273
  function brandingDrift(app, live) {
85152
85274
  const color = app.theme?.color ?? null;
@@ -85246,13 +85368,14 @@ async function foldRegeneration(client, args = {}) {
85246
85368
  // business reissuing it, and dropping it makes the next push read as a
85247
85369
  // new bind.
85248
85370
  ...previous.workflow_id === void 0 ? {} : { workflow_id: previous.workflow_id },
85249
- // The `outputs` the server DERIVED from a body this generation does not
85250
- // rewrite. The generator declares none, so `workflow set` writes the
85251
- // derivation back into the manifest — and a run that dropped it left the
85252
- // declaration disagreeing with the live binding over a schema nobody
85253
- // changed, which re-reported and re-pushed the same alias for ever. A
85254
- // body this run DOES rewrite loses it: the derivation belonged to the
85255
- // body it was derived from, and the next push re-derives it.
85371
+ // A generated body's `outputs` are the generator's, declared as its
85372
+ // return hands them. An AUTHORED act's are the ones the server DERIVED
85373
+ // from its body, which `workflow set` writes back into the manifest — and
85374
+ // a run that dropped them left the declaration disagreeing with the live
85375
+ // binding over a schema nobody changed, which re-reported and re-pushed
85376
+ // the same alias for ever. A body this run DOES rewrite loses them: the
85377
+ // derivation belonged to the body it was derived from, and the next push
85378
+ // re-derives it.
85256
85379
  ...next.outputs === void 0 && previous.outputs !== void 0 && !rewrittenBodies.has(alias2) ? { outputs: previous.outputs } : {}
85257
85380
  };
85258
85381
  }