@datarails-ai/dr-appspace-cli 0.12.22 → 0.12.23

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.
Files changed (2) hide show
  1. package/dist/cli.js +876 -475
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
13
13
  // package.json
14
14
  var package_default = {
15
15
  name: "@datarails-ai/dr-appspace-cli",
16
- version: "0.12.22",
16
+ version: "0.12.23",
17
17
  description: "Datarails App Space CLI \u2014 build, try, push and publish App Space apps",
18
18
  type: "module",
19
19
  engines: {
@@ -57,7 +57,7 @@ var package_default = {
57
57
  };
58
58
 
59
59
  // src/buildInfo.ts
60
- var BUILD_TIME = "2026-10-24T20:24:41.520Z";
60
+ var BUILD_TIME = "2026-10-24T20:55:46.894Z";
61
61
  function formatBuildTime(iso) {
62
62
  if (iso === "") return "";
63
63
  const d = new Date(iso);
@@ -4393,9 +4393,9 @@ function transformRowProblems(data) {
4393
4393
  const widgets = isPlainObject(data?.widgets) ? data.widgets : {};
4394
4394
  const defaults = isPlainObject(data?.defaults) ? data.defaults : {};
4395
4395
  for (const key of Object.keys(widgets).sort(natural2)) {
4396
- const own = widgets[key];
4397
- if (!isPlainObject(own) || own.transform === void 0 || own.transform === null) continue;
4398
- const row = { ...defaults, ...own };
4396
+ const own2 = widgets[key];
4397
+ if (!isPlainObject(own2) || own2.transform === void 0 || own2.transform === null) continue;
4398
+ const row = { ...defaults, ...own2 };
4399
4399
  const where2 = `/data/widgets/${key}`;
4400
4400
  if (typeof row.transform !== "string" || !TRANSFORM_PATH.test(row.transform)) {
4401
4401
  problems.push({
@@ -4433,22 +4433,22 @@ function rowShape(row, data) {
4433
4433
  if (declared[key] !== void 0 && declared[key] !== null) reasons.push(`its data names a ${key} \u2014 drop "data.${key}": the transform is handed every month and works out its own window`);
4434
4434
  }
4435
4435
  }
4436
- const named12 = typeof declared === "string" ? declared.split(".")[0] : isPlainObject(declared) ? declared.dataset : void 0;
4437
- if (typeof named12 !== "string" || named12 === "") {
4436
+ const named13 = typeof declared === "string" ? declared.split(".")[0] : isPlainObject(declared) ? declared.dataset : void 0;
4437
+ if (typeof named13 !== "string" || named13 === "") {
4438
4438
  reasons.push('it names no dataset \u2014 set "data" to the dataset whose measures it reads');
4439
4439
  return reasons;
4440
4440
  }
4441
4441
  const datasets = isPlainObject(data?.datasets) ? data.datasets : {};
4442
- const dataset = datasets[named12];
4442
+ const dataset = datasets[named13];
4443
4443
  if (!isPlainObject(dataset)) {
4444
- reasons.push(`it reads dataset ${JSON.stringify(named12)}, which data.datasets does not declare`);
4444
+ reasons.push(`it reads dataset ${JSON.stringify(named13)}, which data.datasets does not declare`);
4445
4445
  return reasons;
4446
4446
  }
4447
4447
  const reads = isPlainObject(data?.reads) ? data.reads : {};
4448
4448
  const read = typeof dataset.read === "string" ? reads[dataset.read] : void 0;
4449
4449
  if (isPlainObject(read) && Object.hasOwn(read, "period") && read.period !== "all") {
4450
4450
  reasons.push(
4451
- `dataset ${JSON.stringify(named12)} reads ${JSON.stringify(dataset.read)} with the period pushed down (period ${JSON.stringify(read.period)}) \u2014 set that read's period to "all", so the transform is handed the whole history`
4451
+ `dataset ${JSON.stringify(named13)} reads ${JSON.stringify(dataset.read)} with the period pushed down (period ${JSON.stringify(read.period)}) \u2014 set that read's period to "all", so the transform is handed the whole history`
4452
4452
  );
4453
4453
  }
4454
4454
  return reasons;
@@ -5108,9 +5108,9 @@ function assemble(input) {
5108
5108
  }
5109
5109
  return { contract, problems, transforms, did, widgetIds, fresh };
5110
5110
  }
5111
- function readSkeleton(named12, problems) {
5112
- const at = (...segments) => where(named12.name, named12.nested === true ? ["skeleton", ...segments] : segments);
5113
- const doc = named12.doc;
5111
+ function readSkeleton(named13, problems) {
5112
+ const at = (...segments) => where(named13.name, named13.nested === true ? ["skeleton", ...segments] : segments);
5113
+ const doc = named13.doc;
5114
5114
  const start = problems.length;
5115
5115
  if (!isObj(doc)) {
5116
5116
  problems.push(problem("assemble-skeleton", at(), "the skeleton is not a JSON object.", "Write the skeleton block the front of the flow handed over, as it was written."));
@@ -5293,15 +5293,15 @@ function namespacedIds(pages) {
5293
5293
  }
5294
5294
  return out;
5295
5295
  }
5296
- function readBase(named12, problems) {
5297
- const doc = named12.doc;
5296
+ function readBase(named13, problems) {
5297
+ const doc = named13.doc;
5298
5298
  const app = isObj(doc) ? doc["app"] : void 0;
5299
5299
  if (!isObj(doc) || !isObj(app) || !Array.isArray(doc["pages"]) || !isObj(doc["elements"])) {
5300
- problems.push(problem("assemble-base", where(named12.name, []), "the contract.json here has no app, pages or elements to build on.", "Remove it to start from the skeleton, or restore it with app-space get <app-id> --dir."));
5300
+ problems.push(problem("assemble-base", where(named13.name, []), "the contract.json here has no app, pages or elements to build on.", "Remove it to start from the skeleton, or restore it with app-space get <app-id> --dir."));
5301
5301
  return null;
5302
5302
  }
5303
5303
  if (app["kind"] !== "dashboard") {
5304
- problems.push(problem("assemble-base", where(named12.name, ["app", "kind"]), "the contract.json here is not a dashboard.", "A SPEC draft builds a dashboard; compile an app contract without --draft."));
5304
+ problems.push(problem("assemble-base", where(named13.name, ["app", "kind"]), "the contract.json here is not a dashboard.", "A SPEC draft builds a dashboard; compile an app contract without --draft."));
5305
5305
  return null;
5306
5306
  }
5307
5307
  const copy = clone2(doc);
@@ -5332,10 +5332,10 @@ function resolveAppId(input, base, problems) {
5332
5332
  }
5333
5333
  return id;
5334
5334
  }
5335
- function readDraft(named12, problems) {
5336
- const doc = named12.doc;
5337
- const draft = { name: named12.name, cards: [] };
5338
- const at = (...segments) => where(named12.name, segments);
5335
+ function readDraft(named13, problems) {
5336
+ const doc = named13.doc;
5337
+ const draft = { name: named13.name, cards: [] };
5338
+ const at = (...segments) => where(named13.name, segments);
5339
5339
  if (!isObj(doc)) {
5340
5340
  problems.push(problem("assemble-draft", at(), "the draft is not a JSON object.", 'Write the SPEC draft as {"widgets": {"<card id>": {"props": \u2026, "row": \u2026}}, \u2026}.'));
5341
5341
  return draft;
@@ -7199,10 +7199,10 @@ function bind(elements, spec, backend, model) {
7199
7199
  widgets[key] = { component: null, components: [], spec: key, bound: false };
7200
7200
  }
7201
7201
  const runtimeCards = uids.filter((uid) => !isDashboardCard(elements[uid]) && cardSpecOf(elements[uid]) !== null);
7202
- const own = (table, key) => Object.hasOwn(table, key) ? table[key] : void 0;
7202
+ const own2 = (table, key) => Object.hasOwn(table, key) ? table[key] : void 0;
7203
7203
  for (const uid of [...cards, ...runtimeCards].sort(natural3)) {
7204
7204
  const id = isDashboardCard(elements[uid]) ? elements[uid].id : cardSpecOf(elements[uid]);
7205
- const entry = own(widgets, id) ?? (widgets[id] = { component: null, components: [], spec: null, bound: false });
7205
+ const entry = own2(widgets, id) ?? (widgets[id] = { component: null, components: [], spec: null, bound: false });
7206
7206
  entry.components.push(uid);
7207
7207
  if (entry.component === null) entry.component = uid;
7208
7208
  entry.bound = entry.spec !== null;
@@ -7213,19 +7213,19 @@ function bind(elements, spec, backend, model) {
7213
7213
  if (element.type !== "control") continue;
7214
7214
  const name = element.binding;
7215
7215
  if (!name) continue;
7216
- const entry = own(controls, name) ?? (controls[name] = { components: [], widgets: [], bound: false });
7216
+ const entry = own2(controls, name) ?? (controls[name] = { components: [], widgets: [], bound: false });
7217
7217
  entry.components.push(uid);
7218
7218
  }
7219
7219
  for (const uid of cards) {
7220
7220
  for (const param of elements[uid].props.params ?? []) {
7221
- const entry = own(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
7221
+ const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
7222
7222
  if (!entry.widgets.includes(elements[uid].id)) entry.widgets.push(elements[uid].id);
7223
7223
  }
7224
7224
  }
7225
7225
  for (const [key, row] of Object.entries(spec.table)) {
7226
7226
  if (!isWidgetKey(key) || !isPlainObject(row) || !isPlainObject(row.params)) continue;
7227
7227
  for (const param of Object.keys(row.params)) {
7228
- const entry = own(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
7228
+ const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
7229
7229
  if (!entry.widgets.includes(key)) entry.widgets.push(key);
7230
7230
  }
7231
7231
  }
@@ -9343,11 +9343,11 @@ function unproducedPayloadKeys(contract, { handlerSource = () => void 0, moduleS
9343
9343
  for (const name of Object.keys(handlers).sort()) {
9344
9344
  const reads = handlerPayloadReads(handlers[name], handlerSource(name));
9345
9345
  if (reads === null || reads.required.length === 0) continue;
9346
- const own = producers.get(name) ?? [];
9347
- if (own.length === 0 || anywhere.length > 0 || own.some((one) => one.keys === null)) continue;
9346
+ const own2 = producers.get(name) ?? [];
9347
+ if (own2.length === 0 || anywhere.length > 0 || own2.some((one) => one.keys === null)) continue;
9348
9348
  const produced = [];
9349
- for (const one of own) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
9350
- const pages = [...new Set(own.map((one) => one.page).filter(named2))];
9349
+ for (const one of own2) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
9350
+ const pages = [...new Set(own2.map((one) => one.page).filter(named2))];
9351
9351
  for (const key of reads.required) {
9352
9352
  if (produced.includes(key)) continue;
9353
9353
  const there = produced.length === 0 ? "the controls there produce no payload keys" : `the controls there produce ${produced.join(", ")}`;
@@ -9655,7 +9655,7 @@ function shapeMisses(found, at) {
9655
9655
  return misses;
9656
9656
  }
9657
9657
  function shapes(branches, root) {
9658
- const named12 = branches.map((branch) => {
9658
+ const named13 = branches.map((branch) => {
9659
9659
  let node = branch;
9660
9660
  for (let hops = 0; isObject(node) && typeof node.$ref === "string" && hops < 8; hops += 1) {
9661
9661
  const target = resolve4(node.$ref, root);
@@ -9671,8 +9671,8 @@ function shapes(branches, root) {
9671
9671
  if ("type" in node) return (Array.isArray(node.type) ? node.type : [node.type]).map(String).join(" or ");
9672
9672
  return "a shape";
9673
9673
  });
9674
- const shown2 = named12.slice(0, 6);
9675
- return `${shown2.join(" | ")}${named12.length > shown2.length ? ` | ${named12.length - shown2.length} more` : ""}`;
9674
+ const shown2 = named13.slice(0, 6);
9675
+ return `${shown2.join(" | ")}${named13.length > shown2.length ? ` | ${named13.length - shown2.length} more` : ""}`;
9676
9676
  }
9677
9677
  function checkObject(instance, node, at, root, problems, ctx) {
9678
9678
  const properties = isObject(node.properties) ? node.properties : {};
@@ -10154,9 +10154,9 @@ function unreadReasons(contract, { handlerSource = () => void 0, moduleSource, p
10154
10154
  if (typeof source !== "string") continue;
10155
10155
  const reads = pyPayloadReads(source);
10156
10156
  const read = /* @__PURE__ */ new Set([...reads.required, ...reads.optional]);
10157
- const own = (producers.get(name) ?? []).filter((one) => Array.isArray(one.keys));
10157
+ const own2 = (producers.get(name) ?? []).filter((one) => Array.isArray(one.keys));
10158
10158
  const sent = /* @__PURE__ */ new Map();
10159
- for (const one of own) {
10159
+ for (const one of own2) {
10160
10160
  for (const key of one.keys) {
10161
10161
  if (!REASON_KEYS.includes(key)) continue;
10162
10162
  if (!sent.has(key)) sent.set(key, /* @__PURE__ */ new Set());
@@ -11776,7 +11776,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11776
11776
  const outPages = [];
11777
11777
  for (const page of pages) {
11778
11778
  const path3 = page.path;
11779
- const own = Object.keys(typed).filter((uid) => typed[uid]?.page === path3).sort(natural3);
11779
+ const own2 = Object.keys(typed).filter((uid) => typed[uid]?.page === path3).sort(natural3);
11780
11780
  const rank = (uid) => {
11781
11781
  const at = Array.isArray(page.tree) ? page.tree.indexOf(uid) : -1;
11782
11782
  return at === -1 ? Number.MAX_SAFE_INTEGER : at;
@@ -11787,7 +11787,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11787
11787
  elements[uid] = null;
11788
11788
  return uid;
11789
11789
  };
11790
- const regions = (role) => own.filter((uid) => typed[uid].type === "region" && (typed[uid].role ?? "header") === role);
11790
+ const regions = (role) => own2.filter((uid) => typed[uid].type === "region" && (typed[uid].role ?? "header") === role);
11791
11791
  const region = (uid, role, tag, children, parent = null) => ({
11792
11792
  uid,
11793
11793
  page: path3,
@@ -11809,8 +11809,8 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11809
11809
  chrome.forEach((uid, index) => {
11810
11810
  elements[uid] = region(uid, HEADER_CHROME[index], "section", [], header);
11811
11811
  });
11812
- let nav = own.find((uid) => typed[uid].type === "nav");
11813
- const declaredTabs = own.filter((uid) => typed[uid].type === "tab").sort((a, b) => orderOf(typed[a]) - orderOf(typed[b]) || natural3(a, b));
11812
+ let nav = own2.find((uid) => typed[uid].type === "nav");
11813
+ const declaredTabs = own2.filter((uid) => typed[uid].type === "tab").sort((a, b) => orderOf(typed[a]) - orderOf(typed[b]) || natural3(a, b));
11814
11814
  let tabs = [];
11815
11815
  if (declaredTabs.length > 0) {
11816
11816
  tabs = declaredTabs.map((uid) => {
@@ -11868,7 +11868,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11868
11868
  elements[filters] = region(filters, "filters", "section", []);
11869
11869
  const params = (Array.isArray(page.params) ? page.params : []).filter(isPlainObject8).map(canonical2);
11870
11870
  const parts = [];
11871
- for (const uid of own.filter((uid2) => typed[uid2].type === "control")) {
11871
+ for (const uid of own2.filter((uid2) => typed[uid2].type === "control")) {
11872
11872
  const made = controlToPart(typed[uid], uid, path3, refuse);
11873
11873
  if (made === null) continue;
11874
11874
  const param = canonical2(made.param);
@@ -11886,10 +11886,10 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11886
11886
  parts.push(uid);
11887
11887
  }
11888
11888
  const sections = sectionsOf(page);
11889
- const cards = own.filter((uid) => typed[uid].type === "widget");
11890
- const dropped = new Set(own.filter((uid) => DROPPED.has(typed[uid].type) || typed[uid].type === "region" && DROPPED_REGIONS.has(typed[uid].role ?? "header")));
11889
+ const cards = own2.filter((uid) => typed[uid].type === "widget");
11890
+ const dropped = new Set(own2.filter((uid) => DROPPED.has(typed[uid].type) || typed[uid].type === "region" && DROPPED_REGIONS.has(typed[uid].role ?? "header")));
11891
11891
  const handled = /* @__PURE__ */ new Set([header, ...chrome, filters, ...nav === void 0 ? [] : [nav, ...elements[nav].children], ...parts, ...cards]);
11892
- const kept = own.filter((uid) => !handled.has(uid) && !dropped.has(uid) && !["region", "nav", "tab", "control", "widget"].includes(typed[uid].type));
11892
+ const kept = own2.filter((uid) => !handled.has(uid) && !dropped.has(uid) && !["region", "nav", "tab", "control", "widget"].includes(typed[uid].type));
11893
11893
  for (const uid of kept) {
11894
11894
  if (conflicts.has(uid)) refuse(conflicts.get(uid).code, conflicts.get(uid).pointer, conflicts.get(uid).message);
11895
11895
  elements[uid] = keptOf(typed[uid], uid, path3, typed, declared, dropped, kept, cards, sections);
@@ -11900,7 +11900,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11900
11900
  if (conflicts.has(uid) && (parent !== null || kept.includes(typed[uid].parent))) refuse(conflicts.get(uid).code, conflicts.get(uid).pointer, conflicts.get(uid).message);
11901
11901
  elements[uid] = cardOf(typed[uid], uid, path3, handlerName, sections, refuse, parent);
11902
11902
  }
11903
- const otherRegions = own.filter((uid) => typed[uid].type === "region" && !handled.has(uid) && !dropped.has(uid));
11903
+ const otherRegions = own2.filter((uid) => typed[uid].type === "region" && !handled.has(uid) && !dropped.has(uid));
11904
11904
  for (const uid of otherRegions) elements[uid] = region(uid, typed[uid].role ?? "header", "section", []);
11905
11905
  const indexOfSection = (uid) => sections.findIndex((entry) => entry.id === elements[uid]?.section);
11906
11906
  const ownRoots = [...parts, ...kept.filter((uid) => elements[uid].parent === null), ...otherRegions].sort((a, b) => rank(a) - rank(b) || natural3(a, b));
@@ -15105,8 +15105,8 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
15105
15105
  }
15106
15106
  }
15107
15107
  const typedProps = isPlainObject9(props.props) ? props.props : {};
15108
- const own = PROPS_SCHEMAS[type]?.properties?.props?.properties ?? {};
15109
- if ("field" in own && "param" in own) {
15108
+ const own2 = PROPS_SCHEMAS[type]?.properties?.props?.properties ?? {};
15109
+ if ("field" in own2 && "param" in own2) {
15110
15110
  const field = stated(typedProps.field);
15111
15111
  const param = stated(typedProps.param);
15112
15112
  if (field && param) {
@@ -15255,12 +15255,12 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
15255
15255
  writers.push({ handler: action.call, key, pointer: `${at}/props/props/key`, via: `row action "${action.label ?? index}"` });
15256
15256
  } else if (stated(action.dialog)) {
15257
15257
  const dialog = dialogsById.get(`${element.page}|${action.dialog}`);
15258
- const own2 = isPlainObject9(dialog?.props?.props) ? dialog.props.props : null;
15259
- if (!own2 || !stated(own2.on_confirm)) return;
15260
- const ownKey = stated(own2.key);
15258
+ const own3 = isPlainObject9(dialog?.props?.props) ? dialog.props.props : null;
15259
+ if (!own3 || !stated(own3.on_confirm)) return;
15260
+ const ownKey = stated(own3.key);
15261
15261
  writers.push({
15262
- handler: own2.on_confirm,
15263
- key: ownKey ? own2.key : key,
15262
+ handler: own3.on_confirm,
15263
+ key: ownKey ? own3.key : key,
15264
15264
  pointer: ownKey ? `/elements/${escapePointer3(dialog.uid)}/props/props/key` : `${at}/props/props/key`,
15265
15265
  via: `row action "${action.label ?? index}" (dialog "${action.dialog}"${ownKey ? ", keyed by its own props.key" : ""})`
15266
15266
  });
@@ -17146,8 +17146,8 @@ function bandLook2(section) {
17146
17146
  return BAND_LOOK3.filter((key) => named5(section[key])).map((key) => ` data-dr-${key}="${attr2(section[key])}"`).join("");
17147
17147
  }
17148
17148
  function documentTitle(page, appName) {
17149
- const own = shortTitle(page, appName);
17150
- return appName === "" || own === appName ? own : `${own} \xB7 ${appName}`;
17149
+ const own2 = shortTitle(page, appName);
17150
+ return appName === "" || own2 === appName ? own2 : `${own2} \xB7 ${appName}`;
17151
17151
  }
17152
17152
  function shortTitle(page, appName) {
17153
17153
  const raw = named5(page.title) ? page.title : named5(page.header?.title) ? page.header.title : page.path;
@@ -18236,8 +18236,8 @@ function checksMetrics(catalog) {
18236
18236
  function checksTables(catalog) {
18237
18237
  return catalog.source !== "none" && catalog.tables.length > 0;
18238
18238
  }
18239
- function findTable(catalog, named12) {
18240
- const want = named12.toLowerCase();
18239
+ function findTable(catalog, named13) {
18240
+ const want = named13.toLowerCase();
18241
18241
  return catalog.tables.find(
18242
18242
  (table) => table.id.toLowerCase() === want || (table.alias ?? "").toLowerCase() === want || table.name.toLowerCase() === want
18243
18243
  );
@@ -18459,7 +18459,7 @@ function shapeMisses2(found, at) {
18459
18459
  return misses;
18460
18460
  }
18461
18461
  function shapes2(branches, root) {
18462
- const named12 = branches.map((branch) => {
18462
+ const named13 = branches.map((branch) => {
18463
18463
  let node = branch;
18464
18464
  for (let hops = 0; isObject3(node) && typeof node["$ref"] === "string" && hops < 8; hops += 1) {
18465
18465
  const target = resolveRef(node["$ref"], root, []);
@@ -18481,8 +18481,8 @@ function shapes2(branches, root) {
18481
18481
  }
18482
18482
  return "a shape";
18483
18483
  });
18484
- const shown2 = named12.slice(0, 6);
18485
- const more = named12.length - shown2.length;
18484
+ const shown2 = named13.slice(0, 6);
18485
+ const more = named13.length - shown2.length;
18486
18486
  return `${shown2.join(" | ")}${more > 0 ? ` | ${String(more)} more` : ""}`;
18487
18487
  }
18488
18488
  function describe3(node, ctx) {
@@ -19931,12 +19931,12 @@ function checkSources(doc, catalog, problems, skipped) {
19931
19931
  const spec = obj(row);
19932
19932
  const source = obj(spec["source"]);
19933
19933
  if (canMetrics && source["metric_id"] !== void 0) {
19934
- const named12 = String(source["metric_id"]);
19935
- if (!metricIds.has(named12)) {
19934
+ const named13 = String(source["metric_id"]);
19935
+ if (!metricIds.has(named13)) {
19936
19936
  problems.push({
19937
19937
  code: "unknown-metric",
19938
19938
  pointer: pointer("data", "widgets", id, "source", "metric_id"),
19939
- message: `${named12} is not in the tenant inventory.`,
19939
+ message: `${named13} is not in the tenant inventory.`,
19940
19940
  fix: "Use an id from the brief, or drop this widget and its card."
19941
19941
  });
19942
19942
  }
@@ -20469,8 +20469,8 @@ function menuless(row) {
20469
20469
  if (!addressesTable(source)) return true;
20470
20470
  const measures = list2(source["measures"]).length > 0 ? list2(source["measures"]) : source["measure"] ? [source["measure"]] : [];
20471
20471
  const fields = measures.map((one) => obj(one)["field"]);
20472
- const named12 = typeof row["value_field"] === "string" && fields.includes(row["value_field"]);
20473
- return !(named12 || fields.length === 1);
20472
+ const named13 = typeof row["value_field"] === "string" && fields.includes(row["value_field"]);
20473
+ return !(named13 || fields.length === 1);
20474
20474
  }
20475
20475
  function addressesTable(source) {
20476
20476
  return source["address"] === "id" || source["address"] === "alias" || Boolean(source["table_alias"]) || source["table_id"] !== void 0 && source["table_id"] !== null;
@@ -20893,14 +20893,14 @@ function checkAppModel(doc, problems) {
20893
20893
  }
20894
20894
  }
20895
20895
  for (const key of ["action", "source"]) {
20896
- const named12 = props[key];
20897
- if (typeof named12 !== "string" || named12 === "") continue;
20898
- if (Object.prototype.hasOwnProperty.call(handlers, named12)) continue;
20899
- if (key === "source" && Object.prototype.hasOwnProperty.call(sources, named12)) continue;
20896
+ const named13 = props[key];
20897
+ if (typeof named13 !== "string" || named13 === "") continue;
20898
+ if (Object.prototype.hasOwnProperty.call(handlers, named13)) continue;
20899
+ if (key === "source" && Object.prototype.hasOwnProperty.call(sources, named13)) continue;
20900
20900
  problems.push({
20901
20901
  code: "unknown-handler",
20902
20902
  pointer: pointer("elements", uid, "props", key),
20903
- message: key === "source" ? `${JSON.stringify(named12)} is neither a handler in data.app.handlers nor a source in data.sources.` : `${JSON.stringify(named12)} is not a handler in data.app.handlers.`,
20903
+ message: key === "source" ? `${JSON.stringify(named13)} is neither a handler in data.app.handlers nor a source in data.sources.` : `${JSON.stringify(named13)} is not a handler in data.app.handlers.`,
20904
20904
  fix: key === "source" ? "Declare the handler with its role, declare the source under data.sources, or point at one that is declared." : "Declare the handler with its role, or point at one that is declared."
20905
20905
  });
20906
20906
  }
@@ -20919,14 +20919,14 @@ function checkAppModel(doc, problems) {
20919
20919
  if (element["type"] !== "field") continue;
20920
20920
  const props = obj(element["props"]);
20921
20921
  const field = props["field"];
20922
- const own = props["entity"];
20923
- const history = typeof own === "string" && own !== "" && own !== "_events" ? historyListAround2(doc, uid) : null;
20922
+ const own2 = props["entity"];
20923
+ const history = typeof own2 === "string" && own2 !== "" && own2 !== "_events" ? historyListAround2(doc, uid) : null;
20924
20924
  if (history !== null) {
20925
20925
  const shows = typeof field === "string" && field !== "" ? ` shows ${JSON.stringify(field)} and` : "";
20926
20926
  problems.push({
20927
20927
  code: "app-model",
20928
20928
  pointer: pointer("elements", uid, "props", "entity"),
20929
- message: `field ${JSON.stringify(uid)}${shows} is a column of the history list ${JSON.stringify(history)} (over _events, the platform's record of who did what), but states entity ${JSON.stringify(own)}.`,
20929
+ message: `field ${JSON.stringify(uid)}${shows} is a column of the history list ${JSON.stringify(history)} (over _events, the platform's record of who did what), but states entity ${JSON.stringify(own2)}.`,
20930
20930
  fix: `Drop its entity so it inherits _events, or set it to "_events". ${historyColumnsInWords2()}.`
20931
20931
  });
20932
20932
  continue;
@@ -20975,8 +20975,8 @@ function checkUnwrittenProps(doc, problems) {
20975
20975
  const parent = parentOf2(uid);
20976
20976
  const parentType = parent === null ? null : obj(elements[parent])["type"];
20977
20977
  if (typeof parentType === "string" && LEAF_TYPES.has(parentType)) continue;
20978
- const own = props["entity"];
20979
- if ((type === "field" || type === "action") && typeof own === "string" && own !== "" && own !== "_events") {
20978
+ const own2 = props["entity"];
20979
+ if ((type === "field" || type === "action") && typeof own2 === "string" && own2 !== "" && own2 !== "_events") {
20980
20980
  if (!(type === "field" && historyListAround2(doc, uid) !== null)) {
20981
20981
  let supplied = null;
20982
20982
  let holder = null;
@@ -20995,15 +20995,15 @@ function checkUnwrittenProps(doc, problems) {
20995
20995
  problems.push({
20996
20996
  code: "app-model",
20997
20997
  pointer: pointer("elements", uid, "props", "entity"),
20998
- message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(own)}, and no form, list, queue, detail or filter around it names one: the page carries an entity only on those, so this one never reaches the package (a part such as a dialog carries none).`,
20998
+ message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(own2)}, and no form, list, queue, detail or filter around it names one: the page carries an entity only on those, so this one never reaches the package (a part such as a dialog carries none).`,
20999
20999
  fix: `Drop props.entity from ${JSON.stringify(uid)}: the handler it posts to already names its table.`
21000
21000
  });
21001
- } else if (supplied !== own) {
21001
+ } else if (supplied !== own2) {
21002
21002
  problems.push({
21003
21003
  code: "app-model",
21004
21004
  pointer: pointer("elements", uid, "props", "entity"),
21005
- message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(own)}, but sits in ${JSON.stringify(holder)} over ${JSON.stringify(supplied)}: the page carries only the entity of the component around it.`,
21006
- fix: `Drop props.entity so it inherits ${JSON.stringify(supplied)}, or move it into a component over ${JSON.stringify(own)}.`
21005
+ message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(own2)}, but sits in ${JSON.stringify(holder)} over ${JSON.stringify(supplied)}: the page carries only the entity of the component around it.`,
21006
+ fix: `Drop props.entity so it inherits ${JSON.stringify(supplied)}, or move it into a component over ${JSON.stringify(own2)}.`
21007
21007
  });
21008
21008
  }
21009
21009
  }
@@ -21563,11 +21563,204 @@ function endOfQuote(source, start, quote) {
21563
21563
  return source.length;
21564
21564
  }
21565
21565
 
21566
+ // ../flows/insights-builder/kit/checks/rules/runtime.mjs
21567
+ var RUNTIME_RENDERER3 = "runtime";
21568
+ var CARD_COMPONENT = "widget";
21569
+ var SPEC_CARD = "data-dr-spec-card";
21570
+ var COMPONENT_ATTR2 = "data-dr-component";
21571
+ var CONTRACT_ID = "dr-contract";
21572
+ var JSON_TYPE2 = "application/json";
21573
+ var MOUNT_CALL = /\bdrApp\s*\.\s*mount\s*\(/;
21574
+ var RUNTIME_LIBRARIES = [
21575
+ {
21576
+ module: "dr-app-runtime",
21577
+ file: "runtime.js",
21578
+ pattern: /^vendor\/dr-app-runtime@\d+\.\d+\.\d+\/runtime\.js$/,
21579
+ why: "it mounts the skeleton, batches the cards into one request to the handler and hosts the filter bar"
21580
+ },
21581
+ {
21582
+ module: "dr-app-widgets",
21583
+ file: "widgets.js",
21584
+ pattern: /^vendor\/dr-app-widgets@\d+\.\d+\.\d+\/widgets\.js$/,
21585
+ why: "it draws every card kind, formats each figure through dr-fmt and hands each card to dr-insights"
21586
+ },
21587
+ {
21588
+ module: "dr-app-dashboard",
21589
+ file: "dashboard.js",
21590
+ pattern: /^vendor\/dr-app-dashboard@\d+\.\d+\.\d+\/dashboard\.js$/,
21591
+ why: "it draws the filter bar, its presets and the saved filters the cards are read under"
21592
+ }
21593
+ ];
21594
+ var MIN_DASHBOARD_RUNTIME = "1.13.0";
21595
+ var RUNTIME_VERSION = /^vendor\/dr-app-runtime@(\d+\.\d+\.\d+)\/runtime\.js$/;
21596
+ function runtimeBelowMinimum(page) {
21597
+ const { elements } = parseHtml(page.text);
21598
+ return elements.filter((el) => el.tag === "script" && el.attrs.src !== void 0).map((el) => RUNTIME_VERSION.exec(resolveFromPlatformRoot(page.path, el.attrs.src) ?? "")?.[1]).filter((version) => version !== void 0 && compareVersions(version, MIN_DASHBOARD_RUNTIME) < 0);
21599
+ }
21600
+ function compareVersions(a, b) {
21601
+ const left = a.split(".").map(Number);
21602
+ const right = b.split(".").map(Number);
21603
+ for (let i = 0; i < 3; i++) if (left[i] !== right[i]) return left[i] - right[i];
21604
+ return 0;
21605
+ }
21606
+ function contractBlock(elements) {
21607
+ const first = elements.find((el) => el.tag === "script" && el.attrs.id === CONTRACT_ID);
21608
+ if (first === void 0 || first.attrs.src !== void 0) return null;
21609
+ return (first.attrs.type ?? "").trim().toLowerCase() === JSON_TYPE2 ? first : null;
21610
+ }
21611
+ function isEmbeddedContract(el, elements) {
21612
+ return el === contractBlock(elements);
21613
+ }
21614
+ function runtimeContractOf(app, page) {
21615
+ if (app.manifest?.kind !== "dashboard") return null;
21616
+ const { elements, scripts } = parseHtml(page.text ?? "");
21617
+ const block = contractBlock(elements);
21618
+ if (block === null) return null;
21619
+ const body = scripts[elements.filter((el) => el.tag === "script" && !("src" in el.attrs)).indexOf(block)]?.text ?? "";
21620
+ let embedded;
21621
+ try {
21622
+ embedded = JSON.parse(body.replace(/<\\\//g, "</"));
21623
+ } catch {
21624
+ return null;
21625
+ }
21626
+ return isPlainObject11(embedded) && isPlainObject11(embedded.app) && embedded.app.renderer === RUNTIME_RENDERER3 ? embedded : null;
21627
+ }
21628
+ function contractCards(embedded, path3) {
21629
+ return elementsOn(embedded, path3).filter((element) => element.type === CARD_COMPONENT).map((element) => {
21630
+ const props = isPlainObject11(element.props) ? element.props : {};
21631
+ const id = named6(props.id) ? props.id.trim() : "";
21632
+ const card = named6(props.card) ? props.card.trim() : null;
21633
+ return { key: id || card || "", id, card, source: named6(props.source) ? props.source.trim() : "" };
21634
+ });
21635
+ }
21636
+ function contractParams(embedded, path3) {
21637
+ return elementsOn(embedded, path3).flatMap((element) => {
21638
+ const param = isPlainObject11(element.props) && isPlainObject11(element.props.props) ? element.props.props.param : void 0;
21639
+ return named6(param) ? [{ name: param.trim(), id: named6(element.props.id) ? element.props.id.trim() : "" }] : [];
21640
+ });
21641
+ }
21642
+ function contractPage(embedded, path3) {
21643
+ const page = Array.isArray(embedded?.pages) ? embedded.pages.find((one) => isPlainObject11(one) && one.path === path3) : void 0;
21644
+ const params = new Set((Array.isArray(page?.params) ? page.params : []).map((one) => isPlainObject11(one) && named6(one.name) ? one.name.trim() : null).filter(Boolean));
21645
+ const handler = isPlainObject11(page?.mount) && named6(page.mount.handler) ? page.mount.handler.trim() : "";
21646
+ return { params, handler };
21647
+ }
21648
+ function elementsOn(embedded, path3) {
21649
+ const elements = isPlainObject11(embedded?.elements) ? Object.values(embedded.elements) : [];
21650
+ return elements.filter((element) => isPlainObject11(element) && element.page === path3);
21651
+ }
21652
+ function missingLibraries(page) {
21653
+ const { elements } = parseHtml(page.text);
21654
+ const loaded = elements.filter((el) => el.tag === "script" && el.attrs.src !== void 0).map((el) => resolveFromPlatformRoot(page.path, el.attrs.src)).filter((target) => target !== null);
21655
+ return RUNTIME_LIBRARIES.filter((library) => !loaded.some((target) => library.pattern.test(target)));
21656
+ }
21657
+ function libraryOrderProblems(page) {
21658
+ const { elements, scripts } = parseHtml(page.text);
21659
+ const indexOf = (module) => elements.findIndex((el) => el.tag === "script" && el.attrs.src !== void 0 && RUNTIME_LIBRARIES.find((one) => one.module === module).pattern.test(resolveFromPlatformRoot(page.path, el.attrs.src) ?? ""));
21660
+ const inline = elements.filter((el) => el.tag === "script" && !("src" in el.attrs));
21661
+ const block = contractBlock(elements);
21662
+ const mountScript = inline.find((el, index) => el !== block && MOUNT_CALL.test(executableText(scripts[index]?.text ?? "")));
21663
+ const mount = mountScript === void 0 ? -1 : elements.indexOf(mountScript);
21664
+ const runtime = indexOf("dr-app-runtime");
21665
+ const problems = [];
21666
+ for (const module of ["dr-app-runtime", "dr-app-widgets", "dr-app-dashboard"]) {
21667
+ const at = indexOf(module);
21668
+ const how = at === -1 ? null : deferralOf(elements[at]);
21669
+ if (how !== null) problems.push(`${module} is loaded with ${how}, so it does not run where it is written; load the runtime, dr-app-widgets and dr-app-dashboard as plain scripts, in that order, before the page's drApp.mount(...)`);
21670
+ }
21671
+ for (const module of ["dr-app-widgets", "dr-app-dashboard"]) {
21672
+ const at = indexOf(module);
21673
+ if (at !== -1 && runtime !== -1 && at < runtime) {
21674
+ problems.push(`${module} is loaded before dr-app-runtime; it registers with drApp when it loads, finds no drApp there yet, and registers nothing, so the cards stay blank`);
21675
+ }
21676
+ }
21677
+ const bar = indexOf("dr-app-dashboard");
21678
+ if (mount !== -1 && runtime !== -1 && runtime > mount) {
21679
+ problems.push(`the page's drApp.mount(...) runs before dr-app-runtime is loaded; drApp is undefined when it runs, and nothing is mounted`);
21680
+ }
21681
+ if (mount !== -1 && bar !== -1 && bar > mount) {
21682
+ problems.push(`dr-app-dashboard is loaded after the page's drApp.mount(...); the bar must be registered before the mount, or the cards load without waiting for the filters it settles`);
21683
+ }
21684
+ return problems;
21685
+ }
21686
+ function deferralOf(el) {
21687
+ if ("async" in el.attrs) return "async";
21688
+ if ("defer" in el.attrs) return "defer";
21689
+ return (el.attrs.type ?? "").trim().toLowerCase() === "module" ? 'type="module"' : null;
21690
+ }
21691
+ function isRuntimeCard2(el) {
21692
+ return el.attrs[COMPONENT_ATTR2] === CARD_COMPONENT;
21693
+ }
21694
+ function specSource(data, card) {
21695
+ const refs = [];
21696
+ const parts = [];
21697
+ const seen = /* @__PURE__ */ new Set();
21698
+ const cite = (source) => {
21699
+ if (!isPlainObject11(source)) return;
21700
+ if (source.metric_id !== void 0 && source.metric_id !== null) {
21701
+ const id2 = String(source.metric_id);
21702
+ if (refs.some((ref2) => ref2.kind === "metric" && ref2.id === id2)) return;
21703
+ refs.push({ kind: "metric", id: id2 });
21704
+ parts.push(`Metric: ${source.metric_name ?? "metric"} (id ${id2})`);
21705
+ return;
21706
+ }
21707
+ const id = source.table_id === void 0 || source.table_id === null ? void 0 : String(source.table_id);
21708
+ const alias = typeof source.table_alias === "string" && source.table_alias !== "" ? source.table_alias : void 0;
21709
+ const by = source.address === "id" || source.address === "alias" ? source.address : alias !== void 0 ? "alias" : id !== void 0 ? "id" : null;
21710
+ const ref = by === "id" && id !== void 0 ? { kind: "table", id } : by === "alias" && alias !== void 0 ? { kind: "table", alias } : null;
21711
+ if (ref === null) return;
21712
+ if (refs.some((one) => one.kind === "table" && one.id === ref.id && one.alias === ref.alias)) return;
21713
+ refs.push(ref);
21714
+ const label = [id === void 0 ? null : `id ${id}`, alias === void 0 ? null : `alias ${alias}`].filter(Boolean).join(", ");
21715
+ parts.push(`Table: ${source.table_name ?? "table"} (${label})`);
21716
+ };
21717
+ const readsOf = (name) => {
21718
+ const datasets = isPlainObject11(data?.datasets) ? data.datasets : {};
21719
+ const reads = isPlainObject11(data?.reads) ? data.reads : {};
21720
+ const dataset = own(datasets, name);
21721
+ if (!isPlainObject11(dataset)) {
21722
+ cite(own(reads, name));
21723
+ return;
21724
+ }
21725
+ for (const read of [dataset.read, ...Array.isArray(dataset.steps) ? dataset.steps.map(lookupOf) : []].flat()) {
21726
+ if (typeof read === "string") cite(own(reads, read));
21727
+ }
21728
+ };
21729
+ const visit = (key) => {
21730
+ if (typeof key !== "string" || seen.has(key)) return;
21731
+ seen.add(key);
21732
+ const row = own(isPlainObject11(data?.widgets) ? data.widgets : {}, key);
21733
+ if (!isPlainObject11(row)) return;
21734
+ cite(row.source);
21735
+ if (typeof row.data === "string") readsOf(row.data);
21736
+ for (const next of [...listOf(row.depends), ...listOf(row.about)]) visit(next);
21737
+ };
21738
+ visit(card);
21739
+ return { refs, sentence: parts.join("; ") };
21740
+ }
21741
+ function lookupOf(step) {
21742
+ if (!isPlainObject11(step)) return [];
21743
+ return Object.values(step).flatMap((value) => isPlainObject11(value) && typeof value.lookup === "string" ? [value.lookup] : []);
21744
+ }
21745
+ function listOf(value) {
21746
+ return Array.isArray(value) ? value : [];
21747
+ }
21748
+ function named6(value) {
21749
+ return typeof value === "string" && value.trim() !== "";
21750
+ }
21751
+ function own(table, key) {
21752
+ return isPlainObject11(table) && Object.hasOwn(table, key) ? table[key] : void 0;
21753
+ }
21754
+ function isPlainObject11(value) {
21755
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21756
+ }
21757
+
21566
21758
  // ../flows/insights-builder/kit/checks/rules/formatting.mjs
21567
21759
  var ACCOUNT_TYPE = /\b(revenues?|expenses?|cogs|liabilit(?:y|ies)|assets?|equity)\b/i;
21568
21760
  var FMT_MODULE = /^vendor\/dr-fmt@\d+\.\d+\.\d+\/fmt\.js$/;
21569
21761
  var FMT_USE = /\bdrFmt\s*\.\s*[A-Za-z_$]/;
21570
21762
  var WIDGET_LIBRARY = /^vendor\/dr-widgets@\d+\.\d+\.\d+\/widgets\.js$/;
21763
+ var RUNTIME_WIDGET_LIBRARY = /^vendor\/dr-app-widgets@\d+\.\d+\.\d+\/widgets\.js$/;
21571
21764
  function checkFormatting(frontend, kit) {
21572
21765
  const violations = [];
21573
21766
  const skipped = [];
@@ -21591,19 +21784,20 @@ function checkPage(page, module, violations, skipped) {
21591
21784
  if (financial.length === 0) {
21592
21785
  skipped.push({ rule: "R4.1", reason: `${page.path}: no widget names a P&L or balance-sheet category; dr-fmt was not required` });
21593
21786
  } else {
21594
- const named12 = categoryText(financial[0]).match(ACCOUNT_TYPE)[0];
21787
+ const named13 = categoryText(financial[0]).match(ACCOUNT_TYPE)[0];
21595
21788
  if (fmtIndex === -1) {
21596
21789
  violations.push({
21597
21790
  rule: "R4.1",
21598
21791
  where: where2(financial[0].line),
21599
- message: `widget "${financial[0].id}" shows ${named12}, and ${page.path} loads no ${shelf2}; its signs and scale are then whatever the ledger happened to store`
21792
+ message: `widget "${financial[0].id}" shows ${named13}, and ${page.path} loads no ${shelf2}; its signs and scale are then whatever the ledger happened to store`
21600
21793
  });
21601
21794
  }
21602
- if (!FMT_USE.test(executableText(page.scriptText)) && !loadsModule(page.path, page.text, WIDGET_LIBRARY)) {
21795
+ const library = page.runtime === true ? "dr-app-widgets" : "dr-widgets";
21796
+ if (!FMT_USE.test(executableText(page.scriptText)) && !loadsModule(page.path, page.text, page.runtime === true ? RUNTIME_WIDGET_LIBRARY : WIDGET_LIBRARY)) {
21603
21797
  violations.push({
21604
21798
  rule: "R4.1",
21605
21799
  where: where2(financial[0].line),
21606
- message: `no script calls drFmt.*, and the page loads no vendor/dr-widgets@<version>/widgets.js to call it; widget "${financial[0].id}" shows ${named12}, whose sign convention dr-fmt exists to normalise`
21800
+ message: `no script calls drFmt.*, and the page loads no vendor/${library}@<version>/widgets.js to call it; widget "${financial[0].id}" shows ${named13}, whose sign convention dr-fmt exists to normalise`
21607
21801
  });
21608
21802
  }
21609
21803
  }
@@ -21611,7 +21805,7 @@ function checkPage(page, module, violations, skipped) {
21611
21805
  skipped.push({ rule: "R4.2", reason: `${page.path} loads no ${module}; script order was not checked` });
21612
21806
  return;
21613
21807
  }
21614
- const deferred = deferralOf(elements[fmtIndex]);
21808
+ const deferred = deferralOf2(elements[fmtIndex]);
21615
21809
  if (deferred) {
21616
21810
  violations.push({
21617
21811
  rule: "R4.2",
@@ -21623,9 +21817,10 @@ function checkPage(page, module, violations, skipped) {
21623
21817
  for (let i = 0; i < fmtIndex; i++) {
21624
21818
  const el = elements[i];
21625
21819
  if (el.tag !== "script" || runsAfterTheParser(el)) continue;
21626
- const own = el.attrs.src === void 0 ? null : resolveInBundle(page.path, el.attrs.src);
21627
- if (el.attrs.src !== void 0 && own === null) continue;
21628
- const name = own ?? "an inline script";
21820
+ if (page.runtime === true && isEmbeddedContract(el, elements)) continue;
21821
+ const own2 = el.attrs.src === void 0 ? null : resolveInBundle(page.path, el.attrs.src);
21822
+ if (el.attrs.src !== void 0 && own2 === null) continue;
21823
+ const name = own2 ?? "an inline script";
21629
21824
  violations.push({
21630
21825
  rule: "R4.2",
21631
21826
  where: where2(el.line),
@@ -21643,7 +21838,7 @@ function runsAfterTheParser(el) {
21643
21838
  function categoryText(widget) {
21644
21839
  return `${widget.source} ${widget.title} ${widget.description}`;
21645
21840
  }
21646
- function deferralOf(el) {
21841
+ function deferralOf2(el) {
21647
21842
  if ("async" in el.attrs) return "async";
21648
21843
  if ("defer" in el.attrs) return "defer";
21649
21844
  return (el.attrs.type ?? "").trim().toLowerCase() === "module" ? 'type="module"' : null;
@@ -21664,6 +21859,7 @@ var ASSISTANT_MODULE = /^vendor\/dr-insights@(\d+\.\d+\.\d+)\/(insights\.(?:css|
21664
21859
  var UI_KITS = ["ui-kit/v3", "ui-kit/v4"];
21665
21860
  var WIDGET_ID = /^[a-z][a-z0-9_-]*$/;
21666
21861
  var REQUIRED_WIDGET_ATTRS = ["data-dr-widget-title", "data-dr-widget-description", "data-dr-widget-source"];
21862
+ var RUNTIME_WIDGET_ATTRS = ["data-dr-widget-title", "data-dr-widget-description"];
21667
21863
  var SPAN_MARK = "data-dr-span";
21668
21864
  var DRAWING = [
21669
21865
  { re: /\becharts\s*\.\s*init\s*\(/g, what: "echarts.init" },
@@ -21680,25 +21876,50 @@ function checkFrontend(app, kit, contract = null) {
21680
21876
  const widgets = [];
21681
21877
  const pages = [];
21682
21878
  const scriptTexts = [];
21879
+ const cardHandlers = /* @__PURE__ */ new Set();
21683
21880
  const handlerText = app.handlers.map((handler) => handler.source).join("\n");
21881
+ const declared = new Set(app.handlers.map((handler) => handler.name));
21882
+ const specKeys = runtimeSpecKeys(app);
21684
21883
  if (!app.index) {
21685
21884
  violations.push({ rule: "R2.1", where: "ui/index.html", message: "the bundle has no index.html" });
21686
21885
  }
21687
21886
  for (const page of app.pages) {
21688
- const result = checkPage2(app, page, kit);
21887
+ const embedded = runtimeContractOf(app, page);
21888
+ const runtime = embedded === null ? null : { embedded, data: contract?.data ?? embedded.data ?? null, specKeys, declared };
21889
+ const result = checkPage2(app, page, kit, runtime);
21689
21890
  violations.push(...result.violations, ...checkScripts(result.scripts));
21690
21891
  violations.push(...checkControls(page, result.controls, result.scriptText, handlerText));
21691
21892
  widgets.push(...result.widgets);
21692
21893
  scriptTexts.push(result.scriptText);
21693
- pages.push({ path: page.path, text: page.text, widgets: result.widgets, scriptText: result.scriptText });
21894
+ pages.push({ path: page.path, text: page.text, widgets: result.widgets, scriptText: result.scriptText, ...runtime === null ? {} : { runtime: true, claimed: result.claimed } });
21895
+ if (runtime !== null) {
21896
+ violations.push(...checkMount(page, embedded, declared));
21897
+ for (const name of runtimeHandlers(page, result.widgets, embedded)) if (declared.has(name)) cardHandlers.add(name);
21898
+ }
21694
21899
  }
21695
- violations.push(...checkAgainstSpec(app, pages, contract));
21900
+ violations.push(...checkAgainstSpec(app, pages, contract, specKeys));
21696
21901
  for (const file of app.ui) {
21697
21902
  if (DATA_FILE.test(file.path)) {
21698
21903
  violations.push({ rule: "R1.3", where: file.path, message: "a data file in the bundle; a dashboard ships no data" });
21699
21904
  }
21700
21905
  }
21701
- return { violations: distinct(violations), widgets, scriptText: scriptTexts.join("\n"), pages };
21906
+ return { violations: distinct(violations), widgets, scriptText: scriptTexts.join("\n"), pages, cardHandlers };
21907
+ }
21908
+ function runtimeHandlers(page, widgets, embedded) {
21909
+ const names = widgets.map((widget) => widget.handler).filter((name) => typeof name === "string" && name !== "");
21910
+ const { handler } = contractPage(embedded, page.path);
21911
+ if (handler !== "") names.push(handler);
21912
+ return names;
21913
+ }
21914
+ function checkMount(page, embedded, declared) {
21915
+ const { handler } = contractPage(embedded, page.path);
21916
+ if (handler === "" || declared.has(handler)) return [];
21917
+ return [{ rule: "R2.2", where: page.path, message: `${page.path}'s mount names handler "${handler}", which this package does not declare; the filter bar's catalog and saved filters would reach no handler` }];
21918
+ }
21919
+ function runtimeSpecKeys(app) {
21920
+ const spec = readSpec(app.handlers);
21921
+ if (!spec.found || spec.unresolved.length > 0) return null;
21922
+ return new Set(Object.keys(spec.table).filter((key) => !key.startsWith("_") && !key.startsWith("$")));
21702
21923
  }
21703
21924
  function checkScripts(scripts) {
21704
21925
  const violations = [];
@@ -21730,10 +21951,10 @@ function distinct(violations) {
21730
21951
  return true;
21731
21952
  });
21732
21953
  }
21733
- function checkPage2(app, page, kit) {
21954
+ function checkPage2(app, page, kit, runtime = null) {
21734
21955
  const violations = [];
21735
21956
  const widgets = [];
21736
- const scripts = collectScripts(app, page);
21957
+ const scripts = collectScripts(app, page, runtime);
21737
21958
  const scriptText = scripts.map((script) => script.text).join("\n");
21738
21959
  const { elements } = parseHtml(page.text);
21739
21960
  const where2 = (line2) => `${page.path}:${line2}`;
@@ -21778,7 +21999,7 @@ function checkPage2(app, page, kit) {
21778
21999
  }
21779
22000
  }
21780
22001
  const seen = /* @__PURE__ */ new Map();
21781
- for (const el of elements) {
22002
+ for (const el of runtime === null ? elements : []) {
21782
22003
  if (!("data-dr-widget" in el.attrs)) continue;
21783
22004
  const id = el.attrs["data-dr-widget"];
21784
22005
  if (!WIDGET_ID.test(id)) {
@@ -21805,25 +22026,178 @@ function checkPage2(app, page, kit) {
21805
22026
  line: el.line
21806
22027
  });
21807
22028
  }
21808
- if (widgets.length === 0) {
21809
- violations.push({ rule: "R2.1", where: page.path, message: `${page.path} has no [data-dr-widget] element; a dashboard page is one widget or more` });
22029
+ const cards = runtime === null ? null : checkRuntimeCards(app, page, elements, runtime, violations);
22030
+ if (cards !== null) {
22031
+ widgets.push(...cards.widgets);
22032
+ violations.push(...checkRuntimeControls(page, elements, runtime, [...cards.widgets.map((widget) => widget.id), ...cards.claimed]));
22033
+ }
22034
+ if (cards === null ? widgets.length === 0 : !cards.any) {
22035
+ violations.push({
22036
+ rule: "R2.1",
22037
+ where: page.path,
22038
+ message: runtime === null ? `${page.path} has no [data-dr-widget] element; a dashboard page is one widget or more` : `${page.path} has no [data-dr-component="widget"] card naming a ${SPEC_CARD}; a dashboard page is one widget or more`
22039
+ });
21810
22040
  }
21811
- const library = loadsModule(page.path, page.text, WIDGET_LIBRARY2);
21812
- violations.push(...checkLibraryDraws(page, library, scripts));
22041
+ const missing = runtime === null ? [] : missingLibraries(page).map((one) => one.module);
22042
+ const library = runtime === null ? loadsModule(page.path, page.text, WIDGET_LIBRARY2) : !missing.includes("dr-app-runtime") && !missing.includes("dr-app-widgets");
22043
+ violations.push(...checkLibraryDraws(page, library, scripts, runtime === null ? null : missing));
22044
+ if (runtime !== null) for (const problem5 of libraryOrderProblems(page)) violations.push({ rule: "R2.6", where: page.path, message: problem5 });
21813
22045
  if (widgets.length > 0 && !library && !/drInsights\.setData\s*\(/.test(scriptText)) {
21814
- violations.push({ rule: "R3.4", where: page.path, message: `no script loaded by ${page.path} calls window.drInsights.setData, and none loads ${climb}vendor/dr-widgets@<version>/widgets.js, which would call it; the Ask button would open with no numbers` });
22046
+ violations.push({
22047
+ rule: "R3.4",
22048
+ where: page.path,
22049
+ message: runtime === null ? `no script loaded by ${page.path} calls window.drInsights.setData, and none loads ${climb}vendor/dr-widgets@<version>/widgets.js, which would call it; the Ask button would open with no numbers` : `no script loaded by ${page.path} calls window.drInsights.setData, and it does not load both ${climb}vendor/dr-app-runtime@<version>/runtime.js and ${climb}vendor/dr-app-widgets@<version>/widgets.js, whose card bridge would call it; the Ask button would open with no numbers`
22050
+ });
21815
22051
  }
21816
- for (const widget of library ? [] : widgets) {
22052
+ for (const widget of library || runtime !== null ? [] : widgets) {
21817
22053
  const quoted3 = new RegExp(`(['"\`])${widget.id}\\1`);
21818
22054
  if (!quoted3.test(scriptText)) {
21819
22055
  violations.push({ rule: "R3.4", where: where2(widget.line), message: `widget "${widget.id}" is never named in a script, so no setData call can carry its data` });
21820
22056
  }
21821
22057
  }
21822
- return { violations, widgets, scriptText, scripts, controls };
22058
+ return { violations, widgets, scriptText, scripts, controls, claimed: cards?.claimed ?? [] };
21823
22059
  }
21824
- function checkLibraryDraws(page, library, scripts) {
22060
+ function checkRuntimeCards(app, page, elements, runtime, violations) {
22061
+ const where2 = (line2) => `${page.path}:${line2}`;
22062
+ const mounted = contractCards(runtime.embedded, page.path);
22063
+ const pool = /* @__PURE__ */ new Map();
22064
+ for (const one of mounted) pool.set(one.key, [...pool.get(one.key) ?? [], one]);
22065
+ const skeleton = elements.filter(isRuntimeCard2);
22066
+ const widgets = [];
22067
+ const claimed = [];
22068
+ const claim = (...names) => claimed.push(...names.filter((name) => typeof name === "string" && name !== ""));
22069
+ const seenId = /* @__PURE__ */ new Map();
22070
+ const seenCard = /* @__PURE__ */ new Map();
22071
+ for (const el of skeleton) {
22072
+ const id = (el.attrs["data-dr-id"] ?? "").trim();
22073
+ const own2 = (el.attrs[SPEC_CARD] ?? "").trim();
22074
+ const twins = pool.get(id || own2) ?? [];
22075
+ const same5 = twins.findIndex((one) => one.card === own2);
22076
+ const twin = twins.length === 0 ? void 0 : twins.splice(same5 === -1 ? 0 : same5, 1)[0];
22077
+ const drawn2 = twin === void 0 ? null : twin.card;
22078
+ const called = id === "" ? 'a [data-dr-component="widget"] card' : `the card with data-dr-id "${id}"`;
22079
+ if (own2 === "" && (twin === void 0 || drawn2 === null)) {
22080
+ violations.push({ rule: "R3.2", where: where2(el.line), message: `${called} names no SPEC card, in its ${SPEC_CARD} or in #dr-contract, which the runtime mounts from; nothing binds it to a row of the SPEC, so nothing on the handler answers for it` });
22081
+ claim(id);
22082
+ continue;
22083
+ }
22084
+ if (own2 === "") {
22085
+ violations.push({ rule: "R3.2", where: where2(el.line), message: `${called} names no ${SPEC_CARD}, while #dr-contract mounts it as "${drawn2}"; the skeleton is what a round trip reads back, so it names the SPEC card the runtime draws` });
22086
+ claim(id, drawn2);
22087
+ continue;
22088
+ }
22089
+ if (twin === void 0) {
22090
+ violations.push({ rule: "R2.5", where: where2(el.line), message: `the card "${own2}"${id === "" ? "" : ` (data-dr-id "${id}")`} is not in #dr-contract, which the runtime mounts from; the runtime never draws it` });
22091
+ claim(id, own2);
22092
+ continue;
22093
+ }
22094
+ if (drawn2 === null) {
22095
+ violations.push({ rule: "R3.2", where: where2(el.line), message: `the card "${own2}"${id === "" ? "" : ` (data-dr-id "${id}")`} names no SPEC card in #dr-contract, which the runtime mounts from; the runtime draws it as a plain app widget, bound to no row of the SPEC` });
22096
+ claim(id, own2);
22097
+ continue;
22098
+ }
22099
+ if (drawn2 !== own2) {
22100
+ const unknown = runtime.specKeys !== null && !runtime.specKeys.has(drawn2) ? `, and "${drawn2}" is no row of the handler's SPEC` : "";
22101
+ violations.push({ rule: "R2.5", where: where2(el.line), message: `the card${id === "" ? "" : ` with data-dr-id "${id}"`} draws "${own2}" in the skeleton and "${drawn2}" in #dr-contract, which the runtime mounts from${unknown}; the two name one SPEC card, or the page shows one card and a round trip writes back another` });
22102
+ claim(id, own2, drawn2);
22103
+ continue;
22104
+ }
22105
+ if (!WIDGET_ID.test(own2)) {
22106
+ violations.push({ rule: "R3.2", where: where2(el.line), message: `${SPEC_CARD}=${JSON.stringify(own2)} is not a short lowercase id` });
22107
+ }
22108
+ if (id !== "" && seenId.has(id)) {
22109
+ violations.push({ rule: "R3.2", where: where2(el.line), message: `data-dr-id "${id}" is already used on line ${seenId.get(id)}; dr-insights files each card's data under its id, so two cards with one id share one Ask snapshot` });
22110
+ } else if (seenCard.has(own2)) {
22111
+ violations.push({ rule: "R3.2", where: where2(el.line), message: `widget id "${own2}" is already used on line ${seenCard.get(own2)}` });
22112
+ }
22113
+ if (id !== "" && !seenId.has(id)) seenId.set(id, el.line);
22114
+ if (!seenCard.has(own2)) seenCard.set(own2, el.line);
22115
+ for (const attr3 of RUNTIME_WIDGET_ATTRS) {
22116
+ if (!(el.attrs[attr3] ?? "").trim()) {
22117
+ violations.push({ rule: "R3.2", where: where2(el.line), message: `widget "${own2}" has no ${attr3}` });
22118
+ }
22119
+ }
22120
+ if (!(el.attrs[SPAN_MARK] ?? "").trim()) {
22121
+ violations.push({ rule: "R2.7", where: where2(el.line), message: `widget "${own2}" declares no ${SPAN_MARK}; every card on the kit's 12-column grid says how many columns it takes` });
22122
+ }
22123
+ const handler = twin.source || (el.attrs["data-dr-source"] ?? "").trim();
22124
+ for (const name of new Set([handler, (el.attrs["data-dr-source"] ?? "").trim()].filter((one) => one !== ""))) {
22125
+ if (!runtime.declared.has(name)) {
22126
+ violations.push({ rule: "R2.2", where: where2(el.line), message: `widget "${own2}" is posted to handler "${name}", which this package does not declare; the runtime's request for it would reach no handler` });
22127
+ }
22128
+ }
22129
+ const source = specSource(runtime.data, own2);
22130
+ widgets.push({
22131
+ id: own2,
22132
+ page: page.path,
22133
+ source: source.sentence,
22134
+ refs: source.refs,
22135
+ title: el.attrs["data-dr-widget-title"] ?? "",
22136
+ description: el.attrs["data-dr-widget-description"] ?? "",
22137
+ line: el.line,
22138
+ handler
22139
+ });
22140
+ }
22141
+ const contractIds = /* @__PURE__ */ new Map();
22142
+ for (const one of mounted) if (one.id !== "") contractIds.set(one.id, (contractIds.get(one.id) ?? 0) + 1);
22143
+ const said2 = /* @__PURE__ */ new Set();
22144
+ for (const one of [...pool.values()].flat()) {
22145
+ claim(one.id, one.card);
22146
+ if (one.id !== "" && contractIds.get(one.id) > 1) {
22147
+ if (said2.has(one.id)) continue;
22148
+ said2.add(one.id);
22149
+ violations.push({ rule: "R3.2", where: page.path, message: `#dr-contract mounts ${contractIds.get(one.id)} cards with id "${one.id}" on ${page.path}; dr-insights files each card's data under its id, so ids are unique within a page` });
22150
+ continue;
22151
+ }
22152
+ violations.push({
22153
+ rule: "R2.5",
22154
+ where: page.path,
22155
+ message: `#dr-contract mounts a card${one.id === "" ? "" : ` with id "${one.id}"`} ${one.card === null ? "naming no SPEC card" : `drawing "${one.card}"`} on ${page.path} that the skeleton does not carry; the runtime draws it, and a round trip of this page loses it`
22156
+ });
22157
+ }
22158
+ return { widgets, claimed, any: skeleton.length > 0 || mounted.length > 0 };
22159
+ }
22160
+ function checkRuntimeControls(page, elements, runtime, cards) {
22161
+ const lines = /* @__PURE__ */ new Map();
22162
+ for (const el of elements) {
22163
+ const id = (el.attrs["data-dr-id"] ?? "").trim();
22164
+ if ("data-dr-component" in el.attrs && id !== "" && !lines.has(id)) lines.set(id, el.line);
22165
+ }
22166
+ const { params } = contractPage(runtime.embedded, page.path);
22167
+ const rows2 = runtime.data !== null && typeof runtime.data?.widgets === "object" && runtime.data.widgets !== null ? runtime.data.widgets : {};
22168
+ const reads = (card, name) => {
22169
+ const row = Object.hasOwn(rows2, card) ? rows2[card] : void 0;
22170
+ return row !== null && typeof row === "object" && row.params !== null && typeof row.params === "object" && Object.hasOwn(row.params, name);
22171
+ };
21825
22172
  const violations = [];
21826
- if (!library) {
22173
+ for (const part of contractParams(runtime.embedded, page.path)) {
22174
+ const where2 = lines.has(part.id) ? `${page.path}:${lines.get(part.id)}` : page.path;
22175
+ if (!params.has(part.name)) {
22176
+ violations.push({ rule: "R2.4", where: where2, message: `control "${part.name}" writes a param ${page.path} does not declare in #dr-contract (pages[].params); the runtime sends only the page's own params, so no card receives it` });
22177
+ } else if (!cards.some((card) => reads(card, part.name))) {
22178
+ violations.push({ rule: "R2.4", where: where2, message: `control "${part.name}" is read by no card on ${page.path}; a control re-reads the cards of its own page, so it would move a parameter nothing here shows` });
22179
+ }
22180
+ }
22181
+ return violations;
22182
+ }
22183
+ function checkLibraryDraws(page, library, scripts, missing = null) {
22184
+ const violations = [];
22185
+ if (missing !== null) {
22186
+ for (const one of RUNTIME_LIBRARIES.filter((candidate) => missing.includes(candidate.module))) {
22187
+ violations.push({
22188
+ rule: "R2.6",
22189
+ where: page.path,
22190
+ message: `a dashboard the app runtime renders draws through ${one.module}; this page loads no vendor/${one.module}@\u2026/${one.file}, and ${one.why}`
22191
+ });
22192
+ }
22193
+ for (const version of runtimeBelowMinimum(page)) {
22194
+ violations.push({
22195
+ rule: "R2.6",
22196
+ where: page.path,
22197
+ message: `this page loads dr-app-runtime ${version}, and a dashboard the app runtime renders needs ${MIN_DASHBOARD_RUNTIME} or later: card mode arrived in 1.12.0 and the filter bar's hooks in 1.13.0`
22198
+ });
22199
+ }
22200
+ } else if (!library) {
21827
22201
  violations.push({
21828
22202
  rule: "R2.6",
21829
22203
  where: page.path,
@@ -21836,11 +22210,11 @@ function checkLibraryDraws(page, library, scripts) {
21836
22210
  re.lastIndex = 0;
21837
22211
  let match;
21838
22212
  while ((match = re.exec(code)) !== null) {
21839
- const named12 = typeof what === "function" ? what(match) : what;
22213
+ const named13 = typeof what === "function" ? what(match) : what;
21840
22214
  violations.push({
21841
22215
  rule: "R2.6",
21842
22216
  where: `${script.path}:${script.line + lineOffset(code, match.index)}`,
21843
- message: `${named12} in a script this page runs; ${DRAWS_REASON}`
22217
+ message: `${named13} in a script this page runs; ${DRAWS_REASON}`
21844
22218
  });
21845
22219
  }
21846
22220
  }
@@ -21862,10 +22236,14 @@ function checkLink(app, page, el, isKit, where2) {
21862
22236
  }
21863
22237
  return [];
21864
22238
  }
21865
- function collectScripts(app, page) {
22239
+ function collectScripts(app, page, runtime = null) {
21866
22240
  const scripts = [];
21867
22241
  const { elements, scripts: inline } = parseHtml(page.text);
21868
- for (const block of inline) scripts.push({ path: page.path, line: block.line, text: block.text });
22242
+ const blocks = elements.filter((el) => el.tag === "script" && !("src" in el.attrs));
22243
+ inline.forEach((block, index) => {
22244
+ if (runtime !== null && blocks[index] !== void 0 && isEmbeddedContract(blocks[index], elements)) return;
22245
+ scripts.push({ path: page.path, line: block.line, text: block.text });
22246
+ });
21869
22247
  for (const el of elements) {
21870
22248
  if (el.tag !== "script" || !el.attrs.src) continue;
21871
22249
  const path3 = resolveInBundle(page.path, el.attrs.src);
@@ -21938,9 +22316,13 @@ function checkControls(page, controls, scriptText, handlerText) {
21938
22316
  }
21939
22317
  return violations;
21940
22318
  }
21941
- function checkAgainstSpec(app, pages, contract) {
22319
+ function checkAgainstSpec(app, pages, contract, specKeys = null) {
22320
+ const runtimePages = new Set(pages.filter((page) => page.runtime === true).map((page) => page.path));
22321
+ const runtime = runtimePages.size > 0;
22322
+ const legacyPages = pages.filter((page) => !runtimePages.has(page.path));
22323
+ let orphans;
21942
22324
  if (contract !== null) {
21943
- return contract.bindings.orphan_widgets.map((uid) => {
22325
+ orphans = contract.bindings.orphan_widgets.filter((uid) => !runtimePages.has(contract.elements[uid].page)).map((uid) => {
21944
22326
  const element = contract.elements[uid];
21945
22327
  return {
21946
22328
  rule: "R2.5",
@@ -21948,18 +22330,33 @@ function checkAgainstSpec(app, pages, contract) {
21948
22330
  message: `widget "${element.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for`
21949
22331
  };
21950
22332
  });
22333
+ } else {
22334
+ const spec = specWidgets(app.handlers);
22335
+ orphans = spec === null ? [] : legacyPages.flatMap(
22336
+ (page) => page.widgets.filter((widget) => !spec.has(widget.id)).map((widget) => ({ rule: "R2.5", where: `${page.path}:${widget.line}`, message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for` }))
22337
+ );
21951
22338
  }
21952
- const spec = specWidgets(app.handlers);
21953
- if (spec === null) return [];
21954
- return pages.flatMap(
21955
- (page) => page.widgets.filter((widget) => !spec.has(widget.id)).map((widget) => ({ rule: "R2.5", where: `${page.path}:${widget.line}`, message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for` }))
22339
+ if (!runtime || specKeys === null) return orphans;
22340
+ const runtimeOrphans = pages.filter((page) => runtimePages.has(page.path)).flatMap(
22341
+ (page) => page.widgets.filter((widget) => !specKeys.has(widget.id)).map((widget) => ({ rule: "R2.5", where: `${page.path}:${widget.line}`, message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for` }))
21956
22342
  );
22343
+ const drawn2 = new Set(pages.flatMap((page) => [...page.widgets.map((widget) => widget.id), ...page.claimed ?? []]));
22344
+ return [...orphans, ...runtimeOrphans, ...orphanSpecRows(app, [...specKeys].filter((key) => !drawn2.has(key)).sort())];
22345
+ }
22346
+ function orphanSpecRows(app, keys2) {
22347
+ const handler = app.handlers.find((one) => SPEC_TABLE.test(one.source)) ?? app.handlers[0];
22348
+ const file = handler?.file || app.handlersSource;
22349
+ return keys2.map((key) => ({
22350
+ rule: "R2.5",
22351
+ where: `${file}#SPEC.${key}`,
22352
+ message: `the SPEC declares "${key}", and no card on any page draws it (no [data-dr-component="widget"] names it, in ${SPEC_CARD} or in #dr-contract); the handler answers for a figure the dashboard never shows`
22353
+ }));
21957
22354
  }
21958
22355
 
21959
22356
  // ../flows/insights-builder/kit/checks/rules/handlers.mjs
21960
22357
  var FINANCEOS_CALL = /ctx\.financeos\.[A-Za-z_][\w.]*\s*\(/;
21961
22358
  var WIDGET_HELPER = /\bwidgets\.(?:serve|load|drill|catalog)\s*\(\s*ctx\b/;
21962
- function checkHandlers(app, frontendText) {
22359
+ function checkHandlers(app, frontendText, cardHandlers = /* @__PURE__ */ new Set()) {
21963
22360
  const violations = [];
21964
22361
  if (app.handlers.length === 0) {
21965
22362
  violations.push({ rule: "R1.2", where: app.handlersSource, message: "a dashboard has at least one handler reading FinanceOS; this app has none" });
@@ -21982,7 +22379,7 @@ function checkHandlers(app, frontendText) {
21982
22379
  });
21983
22380
  }
21984
22381
  }
21985
- if (!isReferenced2(handler.name, frontendText)) {
22382
+ if (!isReferenced2(handler.name, frontendText) && !cardHandlers.has(handler.name)) {
21986
22383
  violations.push({ rule: "R2.2", where: where2, message: `handler "${handler.name}" is not called by any widget script` });
21987
22384
  }
21988
22385
  const signature = handler.source.match(/def\s+handle\s*\(([^)]*)\)/);
@@ -22081,7 +22478,11 @@ function checkSources2(widgets, catalog) {
22081
22478
  if (!catalog && widgets.length > 0) skipped.push({ rule: "R3.3", reason: "no catalog in input; ids were not checked against the tenant" });
22082
22479
  for (const widget of widgets) {
22083
22480
  const where2 = `${widget.page ?? "ui/index.html"}:${widget.line}`;
22084
- const refs = sourceRefs(widget.source);
22481
+ const refs = widget.refs ?? sourceRefs(widget.source);
22482
+ if (widget.refs !== void 0 && refs.length === 0) {
22483
+ skipped.push({ rule: "R3.3", reason: `widget "${widget.id}" on ${where2}: its SPEC row reads no metric or table this verifier can follow; its source was not checked` });
22484
+ continue;
22485
+ }
22085
22486
  if (refs.length === 0) {
22086
22487
  violations.push({ rule: "R3.3", where: where2, message: `widget "${widget.id}" source names no metric or table id: ${JSON.stringify(widget.source)}` });
22087
22488
  continue;
@@ -22329,7 +22730,7 @@ function verify({ app: appRef, catalog, agent, describe: describe7, load }, { ex
22329
22730
  });
22330
22731
  const violations = [
22331
22732
  ...checkManifest(app),
22332
- ...checkHandlers(app, frontend.scriptText),
22733
+ ...checkHandlers(app, frontend.scriptText, frontend.cardHandlers),
22333
22734
  ...frontend.violations,
22334
22735
  ...theme.violations,
22335
22736
  ...sources.violations,
@@ -22660,7 +23061,7 @@ var TITLED_TYPES2 = Object.freeze(["widget", "form", "list", "queue", "stat", "c
22660
23061
  var CONTROL_PARTS2 = Object.freeze(["number_input", "select", "date_picker", "toggle", "control_group", "slider", "checkbox", "radio_group", "date_range", "fiscal_month"]);
22661
23062
 
22662
23063
  // ../flows/appspace-builder/kit/contract/bundleScan.mjs
22663
- var COMPONENT_ATTR2 = "data-dr-component";
23064
+ var COMPONENT_ATTR3 = "data-dr-component";
22664
23065
  var ENTITY_ATTR2 = "data-dr-entity";
22665
23066
  var ACTION_ATTR2 = "data-dr-action";
22666
23067
  var SOURCE_ATTR2 = "data-dr-source";
@@ -23393,7 +23794,7 @@ function readSpec2(handlers) {
23393
23794
  const where2 = handler.file || `handlers.json#${handler.name}`;
23394
23795
  const literals = new Literals2(handler.source, "py");
23395
23796
  const value = literals.constant("SPEC");
23396
- if (!isPlainObject11(value)) {
23797
+ if (!isPlainObject12(value)) {
23397
23798
  unresolved.push({ where: where2, key: "SPEC", text: literals.constantText("SPEC") ?? cut2("SPEC") });
23398
23799
  continue;
23399
23800
  }
@@ -23409,15 +23810,15 @@ function readSpec2(handlers) {
23409
23810
  var DEFAULTS_KEY2 = "_defaults";
23410
23811
  function effective2(table) {
23411
23812
  const defaults = table[DEFAULTS_KEY2];
23412
- if (!isPlainObject11(defaults)) return table;
23813
+ if (!isPlainObject12(defaults)) return table;
23413
23814
  const out = /* @__PURE__ */ Object.create(null);
23414
23815
  for (const [key, row] of Object.entries(table)) {
23415
23816
  if (key === DEFAULTS_KEY2) continue;
23416
- out[key] = key.startsWith("_") || !isPlainObject11(row) ? row : { ...defaults, ...row };
23817
+ out[key] = key.startsWith("_") || !isPlainObject12(row) ? row : { ...defaults, ...row };
23417
23818
  }
23418
23819
  return out;
23419
23820
  }
23420
- function isPlainObject11(value) {
23821
+ function isPlainObject12(value) {
23421
23822
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
23422
23823
  }
23423
23824
 
@@ -23509,7 +23910,7 @@ function readTransforms2(source) {
23509
23910
  if (!literals.has(TRANSFORMS_TABLE2)) return { found: false, texts: null, span: null, text: null };
23510
23911
  const value = literals.constant(TRANSFORMS_TABLE2);
23511
23912
  const span = literals.constantSpan(TRANSFORMS_TABLE2);
23512
- const readable = isPlainObject11(value) && Object.entries(value).every(([path3, text3]) => TRANSFORM_PATH5.test(path3) && typeof text3 === "string");
23913
+ const readable = isPlainObject12(value) && Object.entries(value).every(([path3, text3]) => TRANSFORM_PATH5.test(path3) && typeof text3 === "string");
23513
23914
  if (!readable) return { found: true, texts: null, span, text: literals.constantText(TRANSFORMS_TABLE2) };
23514
23915
  const texts = {};
23515
23916
  for (const path3 of Object.keys(value).sort(natural4)) texts[path3] = value[path3];
@@ -23596,7 +23997,7 @@ function extract2(app, { describe: describe7 } = {}) {
23596
23997
  for (const page of app.pages) {
23597
23998
  const built = readPage2(app, page, spec, elements, problems);
23598
23999
  if (shell === null && built.shell !== null && built.shell !== void 0) shell = built.shell;
23599
- if (built.page.owned !== true && !page.text.includes(COMPONENT_ATTR2)) {
24000
+ if (built.page.owned !== true && !page.text.includes(COMPONENT_ATTR3)) {
23600
24001
  readBundle2(app, page.path, built.page.scripts, elements, problems);
23601
24002
  }
23602
24003
  pages.push(built.page);
@@ -23678,7 +24079,7 @@ function appSection2(app, manifest) {
23678
24079
  for (const handler of app.handlers) {
23679
24080
  handlers[handler.name] = { http: handler.http || null, entrypoint: handler.file || null };
23680
24081
  }
23681
- const nav = isPlainObject11(manifest.nav) ? manifest.nav : {};
24082
+ const nav = isPlainObject12(manifest.nav) ? manifest.nav : {};
23682
24083
  return {
23683
24084
  id: manifest.id ?? null,
23684
24085
  name: manifest.name ?? null,
@@ -23694,7 +24095,7 @@ function appSection2(app, manifest) {
23694
24095
  api_version: manifest.apiVersion ?? manifest.api_version ?? null,
23695
24096
  routes: routesOf3(manifest),
23696
24097
  handlers,
23697
- capabilities: isPlainObject11(manifest.capabilities) ? manifest.capabilities : {},
24098
+ capabilities: isPlainObject12(manifest.capabilities) ? manifest.capabilities : {},
23698
24099
  data_model: app.dataModel ? normaliseModel2(app.dataModel) : null,
23699
24100
  theme: themeSection2(app)
23700
24101
  };
@@ -23795,13 +24196,13 @@ function readPage2(app, page, spec, elements, problems) {
23795
24196
  if (inherited !== null) element.props.entity = inherited;
23796
24197
  }
23797
24198
  for (const node of tree.nodes) {
23798
- const declared = node.attrs[COMPONENT_ATTR2];
24199
+ const declared = node.attrs[COMPONENT_ATTR3];
23799
24200
  if (declared === void 0 || COMPONENT_TYPE_SET2.has(declared)) continue;
23800
24201
  problems.push({
23801
24202
  code: "unknown_component_type",
23802
24203
  where: `${page.path}:${node.line}`,
23803
24204
  lossy: false,
23804
- message: `${COMPONENT_ATTR2}="${declared}" on <${node.tag}> is not a component type (${COMPONENT_TYPES5.join(", ")}); nothing holds it to a table or a handler`
24205
+ message: `${COMPONENT_ATTR3}="${declared}" on <${node.tag}> is not a component type (${COMPONENT_TYPES5.join(", ")}); nothing holds it to a table or a handler`
23805
24206
  });
23806
24207
  }
23807
24208
  if (!tree.well_formed) {
@@ -23878,11 +24279,11 @@ function paramsOf2(page) {
23878
24279
  function mountOf2(page) {
23879
24280
  const entry = embeddedPageOf2(page);
23880
24281
  const value = entry?.mount;
23881
- if (!isPlainObject11(value)) return null;
24282
+ if (!isPlainObject12(value)) return null;
23882
24283
  return {
23883
24284
  handler: typeof value.handler === "string" ? value.handler : null,
23884
24285
  prefs: typeof value.prefs === "boolean" ? value.prefs : null,
23885
- defaults: isPlainObject11(value.defaults) ? value.defaults : null
24286
+ defaults: isPlainObject12(value.defaults) ? value.defaults : null
23886
24287
  };
23887
24288
  }
23888
24289
  function embeddedContractOf2(page) {
@@ -23890,7 +24291,7 @@ function embeddedContractOf2(page) {
23890
24291
  if (found === null) return null;
23891
24292
  try {
23892
24293
  const embedded = JSON.parse((found[1] ?? "").replace(/<\\\//g, "</"));
23893
- return isPlainObject11(embedded) ? embedded : null;
24294
+ return isPlainObject12(embedded) ? embedded : null;
23894
24295
  } catch {
23895
24296
  return null;
23896
24297
  }
@@ -23903,7 +24304,7 @@ function rendererOf2(pages) {
23903
24304
  for (const page of pages ?? []) {
23904
24305
  const embedded = embeddedContractOf2(page);
23905
24306
  if (embedded === null) continue;
23906
- return embedded.app?.renderer === RUNTIME_RENDERER3 ? { renderer: RUNTIME_RENDERER3 } : {};
24307
+ return embedded.app?.renderer === RUNTIME_RENDERER4 ? { renderer: RUNTIME_RENDERER4 } : {};
23907
24308
  }
23908
24309
  return {};
23909
24310
  }
@@ -23911,7 +24312,7 @@ function cardSpecOf2(element) {
23911
24312
  const card = element?.props?.card;
23912
24313
  return element?.type === "widget" && typeof card === "string" && card !== "" ? card : null;
23913
24314
  }
23914
- var RUNTIME_RENDERER3 = "runtime";
24315
+ var RUNTIME_RENDERER4 = "runtime";
23915
24316
  function navGates2(text3, pagePath) {
23916
24317
  const out = /* @__PURE__ */ new Map();
23917
24318
  if (typeof text3 !== "string") return out;
@@ -23949,7 +24350,7 @@ function isDashboardCard2(element) {
23949
24350
  function classify2(node) {
23950
24351
  if (WIDGET_ATTR2 in node.attrs) return { type: "widget", role: node.attrs[WIDGET_ATTR2 + "-kind"] ?? null };
23951
24352
  if (CONTROL_ATTR2 in node.attrs) return { type: "control", role: node.attrs[CONTROL_ATTR2 + "-kind"] ?? null };
23952
- if (COMPONENT_TYPE_SET2.has(node.attrs[COMPONENT_ATTR2])) return { type: node.attrs[COMPONENT_ATTR2], role: null };
24353
+ if (COMPONENT_TYPE_SET2.has(node.attrs[COMPONENT_ATTR3])) return { type: node.attrs[COMPONENT_ATTR3], role: null };
23953
24354
  if (FIELD_ATTR2 in node.attrs) return { type: "field", role: null };
23954
24355
  if (KIT_ATTR2 in node.attrs) {
23955
24356
  const role = node.attrs[KIT_ATTR2];
@@ -23979,25 +24380,25 @@ function partCalls2(element) {
23979
24380
  const typed = element.props?.props ?? {};
23980
24381
  const out = [];
23981
24382
  for (const prop of Object.keys(PARTS2[element.type].events ?? {})) {
23982
- if (prop !== "on_row_action" && named6(typed[prop])) out.push(typed[prop]);
24383
+ if (prop !== "on_row_action" && named7(typed[prop])) out.push(typed[prop]);
23983
24384
  }
23984
24385
  for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
23985
- if (action && typeof action === "object" && named6(action.call)) out.push(action.call);
24386
+ if (action && typeof action === "object" && named7(action.call)) out.push(action.call);
23986
24387
  }
23987
24388
  const bulk = typed.selection && typeof typed.selection === "object" ? typed.selection.bulk_actions : void 0;
23988
24389
  for (const action of Array.isArray(bulk) ? bulk : []) {
23989
- if (action && typeof action === "object" && named6(action.call)) out.push(action.call);
24390
+ if (action && typeof action === "object" && named7(action.call)) out.push(action.call);
23990
24391
  }
23991
24392
  for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
23992
- if (column && typeof column === "object" && column.cell === "toggle" && named6(column.call)) out.push(column.call);
24393
+ if (column && typeof column === "object" && column.cell === "toggle" && named7(column.call)) out.push(column.call);
23993
24394
  }
23994
- if (element.type === "line_grid" && named6(typed.action)) out.push(typed.action);
24395
+ if (element.type === "line_grid" && named7(typed.action)) out.push(typed.action);
23995
24396
  return out;
23996
24397
  }
23997
24398
  function partReads2(element) {
23998
24399
  const typed = element.props?.props ?? {};
23999
24400
  if (element.type !== "document") return [];
24000
- return (Array.isArray(typed.sections) ? typed.sections : []).filter((section) => section && typeof section === "object" && named6(section.source)).map((section) => section.source);
24401
+ return (Array.isArray(typed.sections) ? typed.sections : []).filter((section) => section && typeof section === "object" && named7(section.source)).map((section) => section.source);
24001
24402
  }
24002
24403
  function isPart3(element) {
24003
24404
  return typeof element?.type === "string" && Object.prototype.hasOwnProperty.call(PARTS2, element.type);
@@ -24067,9 +24468,9 @@ function propsOf2(node, text3, tree) {
24067
24468
  const label = textOf2(text3, node);
24068
24469
  if (label) props.label = label;
24069
24470
  }
24070
- if (COMPONENT_ATTR2 in node.attrs || FIELD_ATTR2 in node.attrs) {
24471
+ if (COMPONENT_ATTR3 in node.attrs || FIELD_ATTR2 in node.attrs) {
24071
24472
  for (const [attr3, key] of COMPONENT_PROPS5) if (attr3 in node.attrs) props[key] = node.attrs[attr3];
24072
- if (node.attrs[COMPONENT_ATTR2] === "widget" && named6(node.attrs[SPEC_CARD_ATTR3])) props.card = node.attrs[SPEC_CARD_ATTR3];
24473
+ if (node.attrs[COMPONENT_ATTR3] === "widget" && named7(node.attrs[SPEC_CARD_ATTR3])) props.card = node.attrs[SPEC_CARD_ATTR3];
24073
24474
  if (FILTER_ATTR2 in node.attrs) props.filter = parseFilter2(node.attrs[FILTER_ATTR2]);
24074
24475
  if (ARGS_ATTR2 in node.attrs) props.args = parseArgs2(node.attrs[ARGS_ATTR2]);
24075
24476
  if (PROPS_ATTR2 in node.attrs) props.props = parseProps2(node.attrs[PROPS_ATTR2]);
@@ -24170,7 +24571,7 @@ function firstTag2(tree, node, tag, outer = false) {
24170
24571
  for (const child of node.children) {
24171
24572
  const candidate = tree.nodes[child];
24172
24573
  if (candidate.tag === tag) return candidate;
24173
- if (outer && (COMPONENT_ATTR2 in candidate.attrs || FIELD_ATTR2 in candidate.attrs)) continue;
24574
+ if (outer && (COMPONENT_ATTR3 in candidate.attrs || FIELD_ATTR2 in candidate.attrs)) continue;
24174
24575
  const found = firstTag2(tree, candidate, tag, outer);
24175
24576
  if (found) return found;
24176
24577
  }
@@ -24186,7 +24587,7 @@ function parseJsonAttr2(value) {
24186
24587
  }
24187
24588
  function shellOf2(main2) {
24188
24589
  const shell = parseJsonAttr2(main2?.attrs[SHELL_ATTR2]);
24189
- return isPlainObject11(shell) ? shell : null;
24590
+ return isPlainObject12(shell) ? shell : null;
24190
24591
  }
24191
24592
  function navOf2(tree, main2) {
24192
24593
  const nav = tree.nodes.find((node) => node.tag === "nav" && node.attrs[KIT_ATTR2] === "nav" && node.attrs[NAV_COUNTS_ATTR2] !== void 0);
@@ -24246,7 +24647,7 @@ function readMount2(scripts, problems) {
24246
24647
  const brace = script.text.indexOf("{", call);
24247
24648
  if (brace === -1) continue;
24248
24649
  const value = new Literals2(script.text, "js").valueAtChar(brace);
24249
- if (!isPlainObject11(value)) {
24650
+ if (!isPlainObject12(value)) {
24250
24651
  problems.push({
24251
24652
  code: "mount_unreadable",
24252
24653
  where: script.path,
@@ -24258,7 +24659,7 @@ function readMount2(scripts, problems) {
24258
24659
  return {
24259
24660
  handler: typeof value.handler === "string" ? value.handler : null,
24260
24661
  prefs: typeof value.prefs === "boolean" ? value.prefs : null,
24261
- defaults: isPlainObject11(value.defaults) ? value.defaults : null
24662
+ defaults: isPlainObject12(value.defaults) ? value.defaults : null
24262
24663
  };
24263
24664
  }
24264
24665
  return null;
@@ -24289,10 +24690,10 @@ function bind2(elements, spec, backend, model) {
24289
24690
  widgets[key] = { component: null, components: [], spec: key, bound: false };
24290
24691
  }
24291
24692
  const runtimeCards = uids.filter((uid) => !isDashboardCard2(elements[uid]) && cardSpecOf2(elements[uid]) !== null);
24292
- const own = (table, key) => Object.hasOwn(table, key) ? table[key] : void 0;
24693
+ const own2 = (table, key) => Object.hasOwn(table, key) ? table[key] : void 0;
24293
24694
  for (const uid of [...cards, ...runtimeCards].sort(natural5)) {
24294
24695
  const id = isDashboardCard2(elements[uid]) ? elements[uid].id : cardSpecOf2(elements[uid]);
24295
- const entry = own(widgets, id) ?? (widgets[id] = { component: null, components: [], spec: null, bound: false });
24696
+ const entry = own2(widgets, id) ?? (widgets[id] = { component: null, components: [], spec: null, bound: false });
24296
24697
  entry.components.push(uid);
24297
24698
  if (entry.component === null) entry.component = uid;
24298
24699
  entry.bound = entry.spec !== null;
@@ -24303,19 +24704,19 @@ function bind2(elements, spec, backend, model) {
24303
24704
  if (element.type !== "control") continue;
24304
24705
  const name = element.binding;
24305
24706
  if (!name) continue;
24306
- const entry = own(controls, name) ?? (controls[name] = { components: [], widgets: [], bound: false });
24707
+ const entry = own2(controls, name) ?? (controls[name] = { components: [], widgets: [], bound: false });
24307
24708
  entry.components.push(uid);
24308
24709
  }
24309
24710
  for (const uid of cards) {
24310
24711
  for (const param of elements[uid].props.params ?? []) {
24311
- const entry = own(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
24712
+ const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
24312
24713
  if (!entry.widgets.includes(elements[uid].id)) entry.widgets.push(elements[uid].id);
24313
24714
  }
24314
24715
  }
24315
24716
  for (const [key, row] of Object.entries(spec.table)) {
24316
- if (!isWidgetKey2(key) || !isPlainObject11(row) || !isPlainObject11(row.params)) continue;
24717
+ if (!isWidgetKey2(key) || !isPlainObject12(row) || !isPlainObject12(row.params)) continue;
24317
24718
  for (const param of Object.keys(row.params)) {
24318
- const entry = own(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
24719
+ const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
24319
24720
  if (!entry.widgets.includes(key)) entry.widgets.push(key);
24320
24721
  }
24321
24722
  }
@@ -24355,12 +24756,12 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
24355
24756
  if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
24356
24757
  const props = element.props ?? {};
24357
24758
  const entity = typeof props.entity === "string" && props.entity !== "" ? props.entity : null;
24358
- if (named6(props.action)) actionRow(props.action).components.push(uid);
24359
- if (named6(props.source)) actionRow(props.source).reads.push(uid);
24759
+ if (named7(props.action)) actionRow(props.action).components.push(uid);
24760
+ if (named7(props.source)) actionRow(props.source).reads.push(uid);
24360
24761
  if (isPart3(element)) for (const name of partCalls2(element)) actionRow(name).components.push(uid);
24361
24762
  if (isPart3(element)) for (const name of partReads2(element)) actionRow(name).reads.push(uid);
24362
24763
  if (entity !== null && element.type !== "field") entityRow(entity).components.push(uid);
24363
- if (!named6(props.field)) continue;
24764
+ if (!named7(props.field)) continue;
24364
24765
  const column = entity === null ? null : (tables[entity]?.columns ?? {})[props.field] ?? null;
24365
24766
  const injected = entity !== null && (has(tables, entity) && INJECTED_COLUMNS3.includes(props.field) || platform(entity) && PLATFORM_TABLES3[entity].includes(props.field));
24366
24767
  const key = `${entity ?? ""}.${props.field}`;
@@ -24387,7 +24788,7 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
24387
24788
  unreached_mutations: Object.keys(facts).filter((name) => (facts[name].verbs ?? []).some((verb) => MUTATING_VERBS2.has(verb)) && !reached.has(name)).sort(natural5)
24388
24789
  };
24389
24790
  }
24390
- function named6(value) {
24791
+ function named7(value) {
24391
24792
  return typeof value === "string" && value !== "";
24392
24793
  }
24393
24794
  function isWidgetKey2(key) {
@@ -24425,7 +24826,7 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
24425
24826
  for (const uid of Object.keys(elements).sort(natural5)) {
24426
24827
  const element = elements[uid];
24427
24828
  if (element.type !== "form") continue;
24428
- const missing = [ACTION_ATTR2, ENTITY_ATTR2].filter((attr3) => !named6(element.props[attr3 === ACTION_ATTR2 ? "action" : "entity"]));
24829
+ const missing = [ACTION_ATTR2, ENTITY_ATTR2].filter((attr3) => !named7(element.props[attr3 === ACTION_ATTR2 ? "action" : "entity"]));
24429
24830
  if (missing.length === 0) continue;
24430
24831
  problems.push({
24431
24832
  code: "unbound_form",
@@ -24447,8 +24848,8 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
24447
24848
  for (const uid of Object.keys(elements).sort(natural5)) {
24448
24849
  const element = elements[uid];
24449
24850
  const filter = element.props?.filter;
24450
- if (!isComponentType2(element.type) || !isPlainObject11(filter)) continue;
24451
- const entity = named6(element.props.entity) ? element.props.entity : null;
24851
+ if (!isComponentType2(element.type) || !isPlainObject12(filter)) continue;
24852
+ const entity = named7(element.props.entity) ? element.props.entity : null;
24452
24853
  if (entity === null || !Object.prototype.hasOwnProperty.call(tables, entity)) continue;
24453
24854
  const columns = tables[entity].columns ?? {};
24454
24855
  for (const column of Object.keys(filter).sort()) {
@@ -24464,7 +24865,7 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
24464
24865
  return problems;
24465
24866
  }
24466
24867
  function readBundle2(app, pagePath, scripts, elements, problems) {
24467
- const files = scripts.filter((path3) => path3.endsWith(".js")).map((path3) => app.ui.find((file) => file.path === path3)).filter((file) => file && file.text.includes(COMPONENT_ATTR2));
24868
+ const files = scripts.filter((path3) => path3.endsWith(".js")).map((path3) => app.ui.find((file) => file.path === path3)).filter((file) => file && file.text.includes(COMPONENT_ATTR3));
24468
24869
  let found = 0;
24469
24870
  for (const file of files) {
24470
24871
  for (const element of scanBundle2(file.text, { file: file.path, page: pagePath })) {
@@ -24496,7 +24897,7 @@ function dataSection2(app, manifest, spec, elements, describe7, problems) {
24496
24897
  ...Object.keys(sources).length > 0 ? { sources } : {}
24497
24898
  };
24498
24899
  if (describe7) {
24499
- const widgets2 = isPlainObject11(describe7.widgets) ? describe7.widgets : {};
24900
+ const widgets2 = isPlainObject12(describe7.widgets) ? describe7.widgets : {};
24500
24901
  const missing = Object.keys(spec.table).filter(isWidgetKey2).filter((key) => !(key in widgets2));
24501
24902
  if (spec.found && spec.unresolved.length === 0 && missing.length > 0) {
24502
24903
  problems.push({
@@ -24545,12 +24946,12 @@ function readSources2(handlers) {
24545
24946
  if (!SOURCES_TABLE2.test(source) || HANDLER_LITERAL2.test(source)) continue;
24546
24947
  const literals = new Literals2(source, "py");
24547
24948
  const table = literals.constant("SOURCES");
24548
- if (!isPlainObject11(table)) continue;
24949
+ if (!isPlainObject12(table)) continue;
24549
24950
  for (const [name, row] of Object.entries(table)) {
24550
- if (isPlainObject11(row) && sources[name] === void 0) sources[name] = row;
24951
+ if (isPlainObject12(row) && sources[name] === void 0) sources[name] = row;
24551
24952
  }
24552
24953
  const carried = literals.constant("TRANSFORMS");
24553
- if (!isPlainObject11(carried)) continue;
24954
+ if (!isPlainObject12(carried)) continue;
24554
24955
  for (const [name, text3] of Object.entries(carried)) {
24555
24956
  const path3 = table[name]?.transform;
24556
24957
  if (typeof path3 === "string" && typeof text3 === "string" && !texts.has(path3)) texts.set(path3, text3);
@@ -24585,7 +24986,7 @@ function specTransforms2(handlers, spec, problems) {
24585
24986
  }
24586
24987
  const unreadable = problems.some((problem5) => problem5.code === "transform_unreadable");
24587
24988
  for (const key of Object.keys(spec.table).filter(isWidgetKey2).sort(natural5)) {
24588
- const path3 = isPlainObject11(spec.table[key]) ? spec.table[key].transform : void 0;
24989
+ const path3 = isPlainObject12(spec.table[key]) ? spec.table[key].transform : void 0;
24589
24990
  if (typeof path3 !== "string" || unreadable || carried !== null && carried.has(path3)) continue;
24590
24991
  problems.push({
24591
24992
  code: "transform_not_carried",
@@ -24609,7 +25010,7 @@ function readApp2(handlers, elements, manifest = null) {
24609
25010
  let row = null;
24610
25011
  if (HANDLER_LITERAL2.test(source)) {
24611
25012
  const value = new Literals2(source, "py").constant("HANDLER");
24612
- if (isPlainObject11(value)) row = value;
25013
+ if (isPlainObject12(value)) row = value;
24613
25014
  }
24614
25015
  declared[handler.name] = { role: "custom", ...row ?? {} };
24615
25016
  if (declared[handler.name].requiresRole === void 0) {
@@ -24637,9 +25038,9 @@ function screensOf2(elements, order2 = null) {
24637
25038
  screens.push({
24638
25039
  page: element.page,
24639
25040
  component: element.type,
24640
- id: named6(props.id) ? props.id : element.id ?? null,
24641
- entity: named6(props.entity) ? props.entity : entityAbove2(elements, element),
24642
- handler: named6(props.source) ? props.source : named6(props.action) ? props.action : null
25041
+ id: named7(props.id) ? props.id : element.id ?? null,
25042
+ entity: named7(props.entity) ? props.entity : entityAbove2(elements, element),
25043
+ handler: named7(props.source) ? props.source : named7(props.action) ? props.action : null
24643
25044
  });
24644
25045
  }
24645
25046
  return screens;
@@ -24651,7 +25052,7 @@ function entityAbove2(elements, element) {
24651
25052
  seen.add(at);
24652
25053
  const above = elements[at];
24653
25054
  if (above === void 0) return null;
24654
- if (named6(above.props?.entity)) return above.props.entity;
25055
+ if (named7(above.props?.entity)) return above.props.entity;
24655
25056
  at = above.parent ?? null;
24656
25057
  }
24657
25058
  return null;
@@ -24680,7 +25081,7 @@ function backendSection2(app, manifest, pages, elements) {
24680
25081
  if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
24681
25082
  for (const key of ["action", "source"]) {
24682
25083
  const name = element.props?.[key];
24683
- if (!named6(name)) continue;
25084
+ if (!named7(name)) continue;
24684
25085
  record2(name, "attribute");
24685
25086
  attributed.add(name);
24686
25087
  }
@@ -24707,14 +25108,14 @@ function backendSection2(app, manifest, pages, elements) {
24707
25108
  data_model: model,
24708
25109
  handlers,
24709
25110
  roles,
24710
- capabilities: isPlainObject11(manifest.capabilities) ? manifest.capabilities : {},
25111
+ capabilities: isPlainObject12(manifest.capabilities) ? manifest.capabilities : {},
24711
25112
  ui: {
24712
25113
  entry,
24713
25114
  handlers_called: [...called].sort(),
24714
25115
  handlers_called_via: [...via.values()].sort((a, b) => natural5(a.name, b.name) || natural5(a.via, b.via)),
24715
25116
  scripts_loaded: [...new Set(pages.flatMap((page) => page.scripts))].sort()
24716
25117
  },
24717
- connect_tables: isPlainObject11(manifest.connectTables) ? manifest.connectTables : isPlainObject11(manifest.connect_tables) ? manifest.connect_tables : {},
25118
+ connect_tables: isPlainObject12(manifest.connectTables) ? manifest.connectTables : isPlainObject12(manifest.connect_tables) ? manifest.connect_tables : {},
24718
25119
  invariants: invariants2(model, roles, handlers)
24719
25120
  };
24720
25121
  }
@@ -24728,7 +25129,7 @@ function callsOnTable2(source, verb) {
24728
25129
  return holders.some((holder) => new RegExp(`(?:^|[^\\w.])(?:${holder})\\s*\\.\\s*${verb}\\s*\\(`, "m").test(source));
24729
25130
  }
24730
25131
  function declaredHandler2(manifest, name) {
24731
- return isPlainObject11(manifest?.handlers) ? manifest.handlers[name] ?? null : null;
25132
+ return isPlainObject12(manifest?.handlers) ? manifest.handlers[name] ?? null : null;
24732
25133
  }
24733
25134
  function signatureOk2(source) {
24734
25135
  const code = source.split("\n").map((line2) => /^\s*#/.test(line2) ? "" : line2).join("\n");
@@ -24755,7 +25156,7 @@ function rolesOf3(manifest) {
24755
25156
  // Declaration order IS the ladder: `authz.py` reads the manifest's list
24756
25157
  // top down, so the first role is the most junior.
24757
25158
  seniority: index,
24758
- tables: isPlainObject11(role?.tables) ? role.tables : {},
25159
+ tables: isPlainObject12(role?.tables) ? role.tables : {},
24759
25160
  // Stated only when the manifest states it, like the platform's own
24760
25161
  // derivation (`app_contract_store._roles_section`): the norm is a role
24761
25162
  // that sees only its own screens, and `[]` on every role would say
@@ -24767,16 +25168,16 @@ function rolesOf3(manifest) {
24767
25168
  }
24768
25169
  function pageRolesOf2(manifest, path3) {
24769
25170
  const pages = Array.isArray(manifest?.pages) ? manifest.pages : [];
24770
- const entry = pages.find((one) => isPlainObject11(one) && one.path === path3);
25171
+ const entry = pages.find((one) => isPlainObject12(one) && one.path === path3);
24771
25172
  const roles = Array.isArray(entry?.roles) ? entry.roles.filter((role) => typeof role === "string" && role !== "") : [];
24772
25173
  return roles.length > 0 ? { roles } : {};
24773
25174
  }
24774
25175
  function normaliseModel2(document) {
24775
25176
  const tables = {};
24776
- const raw = isPlainObject11(document?.tables) ? document.tables : {};
25177
+ const raw = isPlainObject12(document?.tables) ? document.tables : {};
24777
25178
  for (const [name, table] of Object.entries(raw)) {
24778
25179
  const columns = {};
24779
- for (const [column, spec] of Object.entries(isPlainObject11(table?.columns) ? table.columns : {})) {
25180
+ for (const [column, spec] of Object.entries(isPlainObject12(table?.columns) ? table.columns : {})) {
24780
25181
  const pk = Boolean(spec?.pk);
24781
25182
  columns[column] = {
24782
25183
  type: spec?.type ?? null,
@@ -24794,7 +25195,7 @@ function normaliseModel2(document) {
24794
25195
  // `computed` (a child total the platform selects with the row) and
24795
25196
  // `matches` (the column it should agree with), by `display`'s rule:
24796
25197
  // only where stated, so a model without them normalises to its bytes.
24797
- ...isPlainObject11(spec?.computed) ? { computed: spec.computed } : {},
25198
+ ...isPlainObject12(spec?.computed) ? { computed: spec.computed } : {},
24798
25199
  ...typeof spec?.matches === "string" && spec.matches !== "" ? { matches: spec.matches } : {}
24799
25200
  };
24800
25201
  }
@@ -24812,7 +25213,7 @@ function normaliseModel2(document) {
24812
25213
  // row reads as before it is first saved. By `audit`'s rule: only where
24813
25214
  // stated, so a model with neither keeps normalising to its bytes.
24814
25215
  ...table?.singleton === true ? { singleton: true } : {},
24815
- ...table?.singleton === true && isPlainObject11(table?.defaults) ? { defaults: table.defaults } : {},
25216
+ ...table?.singleton === true && isPlainObject12(table?.defaults) ? { defaults: table.defaults } : {},
24816
25217
  columns,
24817
25218
  indexes: Array.isArray(table?.indexes) ? table.indexes.map((index) => Array.isArray(index) ? index.map(String) : [String(index)]) : [],
24818
25219
  injected: [...INJECTED_COLUMNS3]
@@ -25059,7 +25460,7 @@ function shapeMisses3(found, at) {
25059
25460
  return misses;
25060
25461
  }
25061
25462
  function shapes3(branches, root) {
25062
- const named12 = branches.map((branch) => {
25463
+ const named13 = branches.map((branch) => {
25063
25464
  let node = branch;
25064
25465
  for (let hops = 0; isObject5(node) && typeof node.$ref === "string" && hops < 8; hops += 1) {
25065
25466
  const target = resolve10(node.$ref, root);
@@ -25075,8 +25476,8 @@ function shapes3(branches, root) {
25075
25476
  if ("type" in node) return (Array.isArray(node.type) ? node.type : [node.type]).map(String).join(" or ");
25076
25477
  return "a shape";
25077
25478
  });
25078
- const shown2 = named12.slice(0, 6);
25079
- return `${shown2.join(" | ")}${named12.length > shown2.length ? ` | ${named12.length - shown2.length} more` : ""}`;
25479
+ const shown2 = named13.slice(0, 6);
25480
+ return `${shown2.join(" | ")}${named13.length > shown2.length ? ` | ${named13.length - shown2.length} more` : ""}`;
25080
25481
  }
25081
25482
  function checkObject3(instance, node, at, root, problems, ctx) {
25082
25483
  const properties = isObject5(node.properties) ? node.properties : {};
@@ -25145,10 +25546,10 @@ function isObject5(value) {
25145
25546
  var DECLARES_KEYS2 = Object.freeze(["reads", "calls", "produces", "shows"]);
25146
25547
  var SHOWS_KEYS2 = Object.freeze(["field", "format", "scale"]);
25147
25548
  var SHOWS_FORMATS2 = Object.freeze(["currency", "number", "percent"]);
25148
- var isPlainObject12 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
25149
- var named7 = (value) => typeof value === "string" && value !== "";
25150
- var listOfNames2 = (value) => Array.isArray(value) && value.every(named7);
25151
- var idOf3 = (element, uid) => named7(element?.props?.id) ? element.props.id : uid;
25549
+ var isPlainObject13 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
25550
+ var named8 = (value) => typeof value === "string" && value !== "";
25551
+ var listOfNames2 = (value) => Array.isArray(value) && value.every(named8);
25552
+ var idOf3 = (element, uid) => named8(element?.props?.id) ? element.props.id : uid;
25152
25553
  function pyCode2(source) {
25153
25554
  let out = "";
25154
25555
  let i = 0;
@@ -25217,7 +25618,7 @@ function handlerPayloadReads2(row, source) {
25217
25618
  return { generated: false, required: pyPayloadReads2(source).required };
25218
25619
  }
25219
25620
  if (role === "save" || role === "submit") {
25220
- return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(named7) };
25621
+ return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(named8) };
25221
25622
  }
25222
25623
  return { generated: true, required: [] };
25223
25624
  }
@@ -25387,32 +25788,32 @@ function childIndex2(elements) {
25387
25788
  };
25388
25789
  for (const [uid, element] of Object.entries(elements)) {
25389
25790
  for (const child of Array.isArray(element?.children) ? element.children : []) if (child in elements) add(uid, child);
25390
- if (named7(element?.parent) && element.parent in elements) add(element.parent, uid);
25791
+ if (named8(element?.parent) && element.parent in elements) add(element.parent, uid);
25391
25792
  }
25392
25793
  return index;
25393
25794
  }
25394
25795
  var ROW_HOLDERS2 = /* @__PURE__ */ new Set(["list", "queue", "detail"]);
25395
25796
  var INPUT_PARTS2 = /* @__PURE__ */ new Set(["number_input", "select", "date_picker", "toggle", "slider", "checkbox", "radio_group", "file_upload"]);
25396
25797
  function fieldOf2(element) {
25397
- if (element?.type === "field" && named7(element.props?.field)) return element.props.field;
25398
- if (INPUT_PARTS2.has(element?.type) && named7(element.props?.props?.field)) return element.props.props.field;
25798
+ if (element?.type === "field" && named8(element.props?.field)) return element.props.field;
25799
+ if (INPUT_PARTS2.has(element?.type) && named8(element.props?.props?.field)) return element.props.props.field;
25399
25800
  return null;
25400
25801
  }
25401
25802
  function mentions2(text3, name) {
25402
25803
  return new RegExp(`(^|[^A-Za-z0-9_])${reEscape2(name)}([^A-Za-z0-9_]|$)`).test(text3);
25403
25804
  }
25404
25805
  function payloadProducers2(contract, { moduleSource = () => void 0, pageSource = () => void 0 } = {}) {
25405
- const elements = isPlainObject12(contract?.elements) ? contract.elements : {};
25406
- const handlers = isPlainObject12(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
25806
+ const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
25807
+ const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
25407
25808
  const children = childIndex2(elements);
25408
25809
  const out = /* @__PURE__ */ new Map();
25409
25810
  const add = (handler, producer) => {
25410
- if (!named7(handler)) return;
25811
+ if (!named8(handler)) return;
25411
25812
  if (!out.has(handler)) out.set(handler, []);
25412
25813
  out.get(handler).push(producer);
25413
25814
  };
25414
25815
  const insideRow = (uid) => {
25415
- for (let at = elements[uid]?.parent; named7(at) && at in elements; at = elements[at]?.parent) {
25816
+ for (let at = elements[uid]?.parent; named8(at) && at in elements; at = elements[at]?.parent) {
25416
25817
  if (ROW_HOLDERS2.has(elements[at]?.type)) return true;
25417
25818
  }
25418
25819
  return false;
@@ -25433,16 +25834,16 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
25433
25834
  };
25434
25835
  for (const uid of Object.keys(elements).sort()) {
25435
25836
  const element = elements[uid];
25436
- const props = isPlainObject12(element?.props) ? element.props : {};
25837
+ const props = isPlainObject13(element?.props) ? element.props : {};
25437
25838
  const page = element?.page ?? null;
25438
- const typed = isPlainObject12(props.props) ? props.props : {};
25439
- if (named7(props.source)) add(props.source, { page, uid, what: "reader", keys: null });
25839
+ const typed = isPlainObject13(props.props) ? props.props : {};
25840
+ if (named8(props.source)) add(props.source, { page, uid, what: "reader", keys: null });
25440
25841
  switch (element?.type) {
25441
25842
  case "form": {
25442
25843
  const keys2 = fieldsUnder(uid);
25443
25844
  if (insideRow(uid) && !keys2.includes("id")) keys2.push("id");
25444
25845
  const by = handlers[props.action]?.by;
25445
- if (named7(by) && !keys2.includes(by)) keys2.push(by);
25846
+ if (named8(by) && !keys2.includes(by)) keys2.push(by);
25446
25847
  add(props.action, { page, uid, what: "form", keys: keys2 });
25447
25848
  break;
25448
25849
  }
@@ -25457,44 +25858,44 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
25457
25858
  // stepper's current step `{step}`; a file_upload's on_upload its field.
25458
25859
  case "button":
25459
25860
  add(typed.on_click, { page, uid, what: "button", keys: typed.reason === "required" || typed.reason === "optional" ? ["reason"] : [] });
25460
- for (const entry of Array.isArray(typed.split) ? typed.split : []) if (isPlainObject12(entry)) add(entry.on_click, { page, uid, what: "button", keys: [] });
25861
+ for (const entry of Array.isArray(typed.split) ? typed.split : []) if (isPlainObject13(entry)) add(entry.on_click, { page, uid, what: "button", keys: [] });
25461
25862
  break;
25462
25863
  case "empty_state":
25463
25864
  add(typed.on_action, { page, uid, what: "empty_state", keys: [] });
25464
25865
  break;
25465
25866
  case "stepper":
25466
- for (const step of Array.isArray(typed.steps) ? typed.steps : []) if (isPlainObject12(step)) add(step.action, { page, uid, what: "stepper", keys: ["step"] });
25867
+ for (const step of Array.isArray(typed.steps) ? typed.steps : []) if (isPlainObject13(step)) add(step.action, { page, uid, what: "stepper", keys: ["step"] });
25467
25868
  break;
25468
25869
  case "file_upload":
25469
- add(typed.on_upload, { page, uid, what: "file_upload", keys: named7(typed.field) ? [typed.field] : [] });
25870
+ add(typed.on_upload, { page, uid, what: "file_upload", keys: named8(typed.field) ? [typed.field] : [] });
25470
25871
  break;
25471
25872
  case "data_grid":
25472
25873
  add(typed.on_row, { page, uid, what: "data_grid", keys: ["row"] });
25473
25874
  for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
25474
- if (isPlainObject12(action) && named7(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["id", "row"] });
25875
+ if (isPlainObject13(action) && named8(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["id", "row"] });
25475
25876
  }
25476
- for (const action of isPlainObject12(typed.selection) && Array.isArray(typed.selection.bulk_actions) ? typed.selection.bulk_actions : []) {
25477
- if (isPlainObject12(action) && named7(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["ids"] });
25877
+ for (const action of isPlainObject13(typed.selection) && Array.isArray(typed.selection.bulk_actions) ? typed.selection.bulk_actions : []) {
25878
+ if (isPlainObject13(action) && named8(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["ids"] });
25478
25879
  }
25479
25880
  for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
25480
- if (isPlainObject12(column) && column.cell === "toggle" && named7(column.call)) add(column.call, { page, uid, what: "data_grid", keys: ["id", "row", "field", "value"] });
25881
+ if (isPlainObject13(column) && column.cell === "toggle" && named8(column.call)) add(column.call, { page, uid, what: "data_grid", keys: ["id", "row", "field", "value"] });
25481
25882
  }
25482
25883
  break;
25483
25884
  case "action": {
25484
25885
  const keys2 = [];
25485
- if (insideRow(uid)) keys2.push(named7(props.param) ? props.param : "id");
25486
- else if (named7(props.param)) keys2.push(props.param);
25886
+ if (insideRow(uid)) keys2.push(named8(props.param) ? props.param : "id");
25887
+ else if (named8(props.param)) keys2.push(props.param);
25487
25888
  if (handlers[props.action]?.role === "decide") keys2.push("decision", "note");
25488
25889
  add(props.action, { page, uid, what: "action", keys: keys2 });
25489
25890
  break;
25490
25891
  }
25491
25892
  case "custom": {
25492
- const declares = isPlainObject12(props.declares) ? props.declares : {};
25493
- const produces = isPlainObject12(declares.produces) ? declares.produces : {};
25893
+ const declares = isPlainObject13(props.declares) ? props.declares : {};
25894
+ const produces = isPlainObject13(declares.produces) ? declares.produces : {};
25494
25895
  for (const [handler, keys2] of Object.entries(produces)) {
25495
25896
  add(handler, { page, uid, what: "custom", keys: listOfNames2(keys2) ? [...keys2] : null });
25496
25897
  }
25497
- const source = named7(props.module) ? moduleSource(props.module) : void 0;
25898
+ const source = named8(props.module) ? moduleSource(props.module) : void 0;
25498
25899
  if (typeof source !== "string") {
25499
25900
  add("*", { page, uid, what: "custom", keys: null });
25500
25901
  break;
@@ -25520,10 +25921,10 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
25520
25921
  }
25521
25922
  const modules = /* @__PURE__ */ new Set();
25522
25923
  for (const element of Object.values(elements)) {
25523
- if (element?.type === "custom" && named7(element.props?.module)) modules.add(`${element.page}|${element.props.module}`);
25924
+ if (element?.type === "custom" && named8(element.props?.module)) modules.add(`${element.page}|${element.props.module}`);
25524
25925
  }
25525
25926
  for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
25526
- if (!named7(page?.path)) continue;
25927
+ if (!named8(page?.path)) continue;
25527
25928
  const text3 = pageSource(page.path);
25528
25929
  const unknown = { page: page.path, uid: null, what: "script", keys: null };
25529
25930
  if (typeof text3 !== "string") {
@@ -25541,7 +25942,7 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
25541
25942
  }
25542
25943
  var SCRIPT_TAG2 = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
25543
25944
  var SRC_ATTR2 = /\bsrc\s*=\s*["']([^"']+)["']/i;
25544
- var JSON_TYPE2 = /\btype\s*=\s*["']application\/json["']/i;
25945
+ var JSON_TYPE3 = /\btype\s*=\s*["']application\/json["']/i;
25545
25946
  var MOUNT_ONLY2 = /^\s*drApp\.mount\(\s*\{[^}]*\}\s*\)\s*;?\s*$/;
25546
25947
  function pageCode2(html, path3, modules, pageSource) {
25547
25948
  const out = [];
@@ -25549,7 +25950,7 @@ function pageCode2(html, path3, modules, pageSource) {
25549
25950
  const attrs = match[1] ?? "";
25550
25951
  const src = SRC_ATTR2.exec(attrs)?.[1];
25551
25952
  if (src === void 0) {
25552
- if (JSON_TYPE2.test(attrs) || MOUNT_ONLY2.test(match[2] ?? "") || (match[2] ?? "").trim() === "") continue;
25953
+ if (JSON_TYPE3.test(attrs) || MOUNT_ONLY2.test(match[2] ?? "") || (match[2] ?? "").trim() === "") continue;
25553
25954
  out.push(match[2]);
25554
25955
  continue;
25555
25956
  }
@@ -25568,18 +25969,18 @@ function either4(items) {
25568
25969
  return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
25569
25970
  }
25570
25971
  function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
25571
- const handlers = isPlainObject12(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
25972
+ const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
25572
25973
  const producers = payloadProducers2(contract, { moduleSource, pageSource });
25573
25974
  const anywhere = producers.get("*") ?? [];
25574
25975
  const out = [];
25575
25976
  for (const name of Object.keys(handlers).sort()) {
25576
25977
  const reads = handlerPayloadReads2(handlers[name], handlerSource(name));
25577
25978
  if (reads === null || reads.required.length === 0) continue;
25578
- const own = producers.get(name) ?? [];
25579
- if (own.length === 0 || anywhere.length > 0 || own.some((one) => one.keys === null)) continue;
25979
+ const own2 = producers.get(name) ?? [];
25980
+ if (own2.length === 0 || anywhere.length > 0 || own2.some((one) => one.keys === null)) continue;
25580
25981
  const produced = [];
25581
- for (const one of own) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
25582
- const pages = [...new Set(own.map((one) => one.page).filter(named7))];
25982
+ for (const one of own2) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
25983
+ const pages = [...new Set(own2.map((one) => one.page).filter(named8))];
25583
25984
  for (const key of reads.required) {
25584
25985
  if (produced.includes(key)) continue;
25585
25986
  const there = produced.length === 0 ? "the controls there produce no payload keys" : `the controls there produce ${produced.join(", ")}`;
@@ -25591,12 +25992,12 @@ function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, module
25591
25992
  return out;
25592
25993
  }
25593
25994
  function undeclaredCustomCalls2(contract, { moduleSource = () => void 0 } = {}) {
25594
- const elements = isPlainObject12(contract?.elements) ? contract.elements : {};
25995
+ const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
25595
25996
  const out = [];
25596
25997
  for (const uid of Object.keys(elements).sort()) {
25597
25998
  const element = elements[uid];
25598
- const props = isPlainObject12(element?.props) ? element.props : {};
25599
- if (element?.type !== "custom" || !isPlainObject12(props.declares) || !named7(props.module)) continue;
25999
+ const props = isPlainObject13(element?.props) ? element.props : {};
26000
+ if (element?.type !== "custom" || !isPlainObject13(props.declares) || !named8(props.module)) continue;
25600
26001
  const source = moduleSource(props.module);
25601
26002
  if (typeof source !== "string") continue;
25602
26003
  const calls = listOfNames2(props.declares.calls) ? props.declares.calls : [];
@@ -25624,32 +26025,32 @@ function undeclaredCallMessage2(id, module, handler, calls, how) {
25624
26025
  return { problem: problem5, fix, message: `${problem5} ${fix}` };
25625
26026
  }
25626
26027
  function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
25627
- const elements = isPlainObject12(contract?.elements) ? contract.elements : {};
25628
- const handlers = isPlainObject12(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
26028
+ const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
26029
+ const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
25629
26030
  const parents = /* @__PURE__ */ new Map();
25630
26031
  for (const [parent, kids] of childIndex2(elements)) for (const kid of kids) if (!parents.has(kid)) parents.set(kid, parent);
25631
26032
  const out = [];
25632
26033
  for (const uid of Object.keys(elements).sort()) {
25633
26034
  const element = elements[uid];
25634
- const typed = isPlainObject12(element?.props?.props) ? element.props.props : {};
25635
- if (element?.type !== "file_upload" || !named7(typed.field)) continue;
26035
+ const typed = isPlainObject13(element?.props?.props) ? element.props.props : {};
26036
+ if (element?.type !== "file_upload" || !named8(typed.field)) continue;
25636
26037
  const field = typed.field;
25637
26038
  const receivers = [];
25638
- if (named7(typed.on_upload)) receivers.push([typed.on_upload, "its on_upload"]);
25639
- for (let at = parents.get(uid); named7(at) && at in elements; at = parents.get(at)) {
26039
+ if (named8(typed.on_upload)) receivers.push([typed.on_upload, "its on_upload"]);
26040
+ for (let at = parents.get(uid); named8(at) && at in elements; at = parents.get(at)) {
25640
26041
  const holder = elements[at];
25641
- if (holder?.type === "dialog" && named7(holder.props?.props?.on_confirm)) {
26042
+ if (holder?.type === "dialog" && named8(holder.props?.props?.on_confirm)) {
25642
26043
  receivers.push([holder.props.props.on_confirm, `the dialog "${idOf3(holder, at)}" it sits in`]);
25643
26044
  break;
25644
26045
  }
25645
- if (holder?.type === "form" && named7(holder.props?.action)) {
26046
+ if (holder?.type === "form" && named8(holder.props?.action)) {
25646
26047
  receivers.push([holder.props.action, `the form "${idOf3(holder, at)}" it sits in`]);
25647
26048
  break;
25648
26049
  }
25649
26050
  }
25650
26051
  for (const [handler, via] of receivers) {
25651
26052
  const row = handlers[handler];
25652
- if (!isPlainObject12(row)) continue;
26053
+ if (!isPlainObject13(row)) continue;
25653
26054
  const role = row.role ?? "custom";
25654
26055
  let read;
25655
26056
  if (role === "custom") {
@@ -25672,16 +26073,16 @@ function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
25672
26073
  return out;
25673
26074
  }
25674
26075
  function stepsWithoutAction2(contract) {
25675
- const elements = isPlainObject12(contract?.elements) ? contract.elements : {};
26076
+ const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
25676
26077
  const out = [];
25677
26078
  for (const uid of Object.keys(elements).sort()) {
25678
26079
  const element = elements[uid];
25679
26080
  const steps = element?.type === "stepper" && Array.isArray(element.props?.props?.steps) ? element.props.props.steps : [];
25680
26081
  steps.forEach((step, index) => {
25681
- if (index === steps.length - 1 || !isPlainObject12(step) || named7(step.action)) return;
26082
+ if (index === steps.length - 1 || !isPlainObject13(step) || named8(step.action)) return;
25682
26083
  const id = idOf3(element, uid);
25683
- const name = named7(step.id) ? step.id : String(index);
25684
- const next = named7(steps[index + 1]?.id) ? steps[index + 1].id : "the next step";
26084
+ const name = named8(step.id) ? step.id : String(index);
26085
+ const next = named8(steps[index + 1]?.id) ? steps[index + 1].id : "the next step";
25685
26086
  const problem5 = `stepper "${id}" step "${name}" names no action, so no handler moves the flow on from it \u2014 a flow that reaches it stays there.`;
25686
26087
  const fix = `Give it the handler that moves it to "${next}" (action, action_label), or make it the last step.`;
25687
26088
  out.push({ uid, id, page: element.page ?? null, step: name, index, problem: problem5, fix, message: `${problem5} ${fix}` });
@@ -26097,47 +26498,47 @@ function isReferenced4(name, pageText) {
26097
26498
  }
26098
26499
 
26099
26500
  // ../flows/appspace-builder/kit/contract/levers.mjs
26100
- var isPlainObject13 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
26101
- var named8 = (value) => typeof value === "string" && value !== "";
26501
+ var isPlainObject14 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
26502
+ var named9 = (value) => typeof value === "string" && value !== "";
26102
26503
  function both2(items) {
26103
26504
  if (items.length <= 1) return items.join("");
26104
26505
  return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
26105
26506
  }
26106
26507
  function paramPages(contract) {
26107
26508
  return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
26108
- (page) => page?.owned !== true && named8(page?.path) && Array.isArray(page.params) && page.params.length > 0
26509
+ (page) => page?.owned !== true && named9(page?.path) && Array.isArray(page.params) && page.params.length > 0
26109
26510
  );
26110
26511
  }
26111
26512
  function namesOf(page) {
26112
26513
  const out = [];
26113
- for (const param of page.params) if (isPlainObject13(param) && named8(param.name) && !out.includes(param.name)) out.push(param.name);
26514
+ for (const param of page.params) if (isPlainObject14(param) && named9(param.name) && !out.includes(param.name)) out.push(param.name);
26114
26515
  return out;
26115
26516
  }
26116
- function elementsOn(contract, path3) {
26117
- const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
26517
+ function elementsOn2(contract, path3) {
26518
+ const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26118
26519
  return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
26119
26520
  }
26120
26521
  function paramModeOf(element) {
26121
- const props = isPlainObject13(element?.props) ? element.props : {};
26122
- if (element?.type === "control") return named8(props.param) ? props.param : named8(element.binding) ? element.binding : null;
26522
+ const props = isPlainObject14(element?.props) ? element.props : {};
26523
+ if (element?.type === "control") return named9(props.param) ? props.param : named9(element.binding) ? element.binding : null;
26123
26524
  if (element?.type === "field" || element?.type === "action") return null;
26124
- if (named8(props.param) && element?.type !== "custom") return props.param;
26125
- const typed = isPlainObject13(props.props) ? props.props : {};
26126
- return named8(typed.param) ? typed.param : null;
26525
+ if (named9(props.param) && element?.type !== "custom") return props.param;
26526
+ const typed = isPlainObject14(props.props) ? props.props : {};
26527
+ return named9(typed.param) ? typed.param : null;
26127
26528
  }
26128
26529
  function paramsWrittenBy(element) {
26129
26530
  const out = [];
26130
26531
  const one = paramModeOf(element);
26131
26532
  if (one !== null) out.push(one);
26132
26533
  if (element?.type === "custom") return out;
26133
- const typed = isPlainObject13(element?.props?.props) ? element.props.props : {};
26134
- if (named8(typed.param_to)) out.push(typed.param_to);
26534
+ const typed = isPlainObject14(element?.props?.props) ? element.props.props : {};
26535
+ if (named9(typed.param_to)) out.push(typed.param_to);
26135
26536
  for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
26136
- if (!isPlainObject13(filter)) continue;
26137
- if (named8(filter.param)) out.push(filter.param);
26138
- if (named8(filter.param_to)) out.push(filter.param_to);
26139
- if (named8(filter.param_min)) out.push(filter.param_min);
26140
- if (named8(filter.param_max)) out.push(filter.param_max);
26537
+ if (!isPlainObject14(filter)) continue;
26538
+ if (named9(filter.param)) out.push(filter.param);
26539
+ if (named9(filter.param_to)) out.push(filter.param_to);
26540
+ if (named9(filter.param_min)) out.push(filter.param_min);
26541
+ if (named9(filter.param_max)) out.push(filter.param_max);
26141
26542
  }
26142
26543
  return out;
26143
26544
  }
@@ -26146,13 +26547,13 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
26146
26547
  for (const page of paramPages(contract)) {
26147
26548
  const written = /* @__PURE__ */ new Set();
26148
26549
  let unknown = false;
26149
- for (const [, element] of elementsOn(contract, page.path)) {
26550
+ for (const [, element] of elementsOn2(contract, page.path)) {
26150
26551
  for (const name of paramsWrittenBy(element)) written.add(name);
26151
26552
  if (element?.type !== "custom") continue;
26152
- const props = isPlainObject13(element.props) ? element.props : {};
26153
- const produces = isPlainObject13(props.declares?.produces) ? props.declares.produces : {};
26154
- for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named8(key)) written.add(key);
26155
- if (!named8(props.module)) continue;
26553
+ const props = isPlainObject14(element.props) ? element.props : {};
26554
+ const produces = isPlainObject14(props.declares?.produces) ? props.declares.produces : {};
26555
+ for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named9(key)) written.add(key);
26556
+ if (!named9(props.module)) continue;
26156
26557
  const source = moduleSource(props.module);
26157
26558
  if (typeof source !== "string") {
26158
26559
  unknown = true;
@@ -26165,8 +26566,8 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
26165
26566
  }
26166
26567
  if (contract?.app?.shell?.nav === "rail") {
26167
26568
  for (const entry of Array.isArray(contract.app.shell.rail?.entries) ? contract.app.shell.rail.entries : []) {
26168
- if (isPlainObject13(entry?.param)) {
26169
- for (const name of Object.keys(entry.param)) if (named8(name)) written.add(name);
26569
+ if (isPlainObject14(entry?.param)) {
26570
+ for (const name of Object.keys(entry.param)) if (named9(name)) written.add(name);
26170
26571
  }
26171
26572
  }
26172
26573
  }
@@ -26183,7 +26584,7 @@ function unboundControls(contract, files = {}) {
26183
26584
  for (const name of namesOf(page)) {
26184
26585
  if (written.has(name)) continue;
26185
26586
  const param = page.params.find((one) => one?.name === name) ?? {};
26186
- const kind = named8(param.kind) ? param.kind : null;
26587
+ const kind = named9(param.kind) ? param.kind : null;
26187
26588
  const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
26188
26589
  const problem5 = `${page.path} declares param "${name}"${kind === null ? "" : ` (${kind})`}, and no control on the page writes it \u2014 no part in param mode, no custom that calls ui.setParam("${name}", \u2026) or declares it in declares.produces \u2014 so the page only ever shows ${opens3}.`;
26189
26590
  const fix = `Give it a control \u2014 a part in param mode (props.props.param "${name}": a slider, a number_input, a select, a date_picker or a toggle), or a custom whose module calls ui.setParam("${name}", value) \u2014 or drop the param.`;
@@ -26198,16 +26599,16 @@ function honoursOf(sources, name) {
26198
26599
  const queue = [name];
26199
26600
  while (queue.length > 0) {
26200
26601
  const one = queue.shift();
26201
- if (seen.has(one) || !isPlainObject13(sources[one])) continue;
26602
+ if (seen.has(one) || !isPlainObject14(sources[one])) continue;
26202
26603
  seen.add(one);
26203
- for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named8(honored) && !out.includes(honored)) out.push(honored);
26604
+ for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named9(honored) && !out.includes(honored)) out.push(honored);
26204
26605
  if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
26205
26606
  }
26206
26607
  return out;
26207
26608
  }
26208
26609
  function unreadParams(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
26209
- const sources = isPlainObject13(contract?.data?.sources) ? contract.data.sources : {};
26210
- const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
26610
+ const sources = isPlainObject14(contract?.data?.sources) ? contract.data.sources : {};
26611
+ const handlers = isPlainObject14(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
26211
26612
  const out = [];
26212
26613
  for (const page of paramPages(contract)) {
26213
26614
  const read = /* @__PURE__ */ new Set();
@@ -26215,16 +26616,16 @@ function unreadParams(contract, { moduleSource = () => void 0, handlerSource = (
26215
26616
  const modules = [];
26216
26617
  const custom = [];
26217
26618
  let unknown = false;
26218
- for (const [, element] of elementsOn(contract, page.path)) {
26219
- const props = isPlainObject13(element?.props) ? element.props : {};
26220
- const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named8);
26619
+ for (const [, element] of elementsOn2(contract, page.path)) {
26620
+ const props = isPlainObject14(element?.props) ? element.props : {};
26621
+ const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named9);
26221
26622
  for (const one of reads) {
26222
26623
  if (Object.prototype.hasOwnProperty.call(sources, one)) {
26223
26624
  if (!named_.includes(one)) named_.push(one);
26224
26625
  for (const honored of honoursOf(sources, one)) read.add(honored);
26225
26626
  } else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
26226
26627
  }
26227
- if (element?.type === "custom" && named8(props.module)) {
26628
+ if (element?.type === "custom" && named9(props.module)) {
26228
26629
  const text3 = moduleSource(props.module);
26229
26630
  if (typeof text3 === "string") modules.push(text3);
26230
26631
  else unknown = true;
@@ -26266,8 +26667,8 @@ function checkLevers(app, contract, contractError) {
26266
26667
  }
26267
26668
 
26268
26669
  // ../flows/appspace-builder/kit/contract/look.mjs
26269
- var isPlainObject14 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
26270
- var named9 = (value) => typeof value === "string" && value !== "";
26670
+ var isPlainObject15 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
26671
+ var named10 = (value) => typeof value === "string" && value !== "";
26271
26672
  function humanise3(name) {
26272
26673
  const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
26273
26674
  return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
@@ -26275,10 +26676,10 @@ function humanise3(name) {
26275
26676
  var WORDING2 = {
26276
26677
  custom: { noun: "card", fallback: (p) => [humanise3(String(p.module ?? "").replace(/^.*\/|\.js$/g, "")), "its module"] },
26277
26678
  chart: { noun: "card", fallback: (p) => [humanise3(String(p.y ?? "")), "its y column"] },
26278
- widget: { noun: "card", fallback: (p) => [humanise3(named9(p.source) ? p.source : "widget"), "its source"] },
26679
+ widget: { noun: "card", fallback: (p) => [humanise3(named10(p.source) ? p.source : "widget"), "its source"] },
26279
26680
  stat: { noun: "tile", fallback: (p) => [humanise3(String(p.measure ?? "")), "its measure"] },
26280
- list: { noun: "card", fallback: (p) => [humanise3(named9(p.entity) ? p.entity : "list"), "its entity"] },
26281
- queue: { noun: "card", fallback: (p) => [humanise3(named9(p.entity) ? p.entity : "queue"), "its entity"] },
26681
+ list: { noun: "card", fallback: (p) => [humanise3(named10(p.entity) ? p.entity : "list"), "its entity"] },
26682
+ queue: { noun: "card", fallback: (p) => [humanise3(named10(p.entity) ? p.entity : "queue"), "its entity"] },
26282
26683
  form: { noun: "card", fallback: () => ["New", "the runtime\u2019s default"] },
26283
26684
  dialog: { noun: "dialog" }
26284
26685
  };
@@ -26287,7 +26688,7 @@ var TITLED2 = Object.fromEntries(
26287
26688
  );
26288
26689
  var LAYOUT2 = /* @__PURE__ */ new Set(["region", "tabs", "tab_panel"]);
26289
26690
  function rootsOf3(contract, page) {
26290
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26691
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26291
26692
  if (Array.isArray(page?.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid] !== void 0);
26292
26693
  const contained = /* @__PURE__ */ new Set();
26293
26694
  for (const element of Object.values(elements)) for (const child of element?.children ?? []) contained.add(child);
@@ -26297,7 +26698,7 @@ function rootsOf3(contract, page) {
26297
26698
  });
26298
26699
  }
26299
26700
  function untitledCards2(contract) {
26300
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26701
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26301
26702
  const out = [];
26302
26703
  for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
26303
26704
  if (page?.owned === true) continue;
@@ -26322,13 +26723,13 @@ function untitledCards2(contract) {
26322
26723
  const type = element.type ?? "";
26323
26724
  const titled = TITLED2[type];
26324
26725
  if (titled === void 0) continue;
26325
- const props = isPlainObject14(element.props) ? element.props : {};
26326
- const typed = isPlainObject14(props.props) ? props.props : {};
26327
- const card = isPlainObject14(element.card) ? element.card : isPlainObject14(props.card) ? props.card : {};
26328
- if (named9(props.label) || named9(card.title)) continue;
26329
- if (type === "dialog" ? named9(typed.title) : named9(typed.label)) continue;
26330
- const id = named9(element.id) ? element.id : named9(props.id) ? props.id : null;
26331
- const [title, from] = named9(props.field) ? [humanise3(props.field), "its field"] : id !== null ? [humanise3(id), "its id"] : titled.fallback(props);
26726
+ const props = isPlainObject15(element.props) ? element.props : {};
26727
+ const typed = isPlainObject15(props.props) ? props.props : {};
26728
+ const card = isPlainObject15(element.card) ? element.card : isPlainObject15(props.card) ? props.card : {};
26729
+ if (named10(props.label) || named10(card.title)) continue;
26730
+ if (type === "dialog" ? named10(typed.title) : named10(typed.label)) continue;
26731
+ const id = named10(element.id) ? element.id : named10(props.id) ? props.id : null;
26732
+ const [title, from] = named10(props.field) ? [humanise3(props.field), "its field"] : id !== null ? [humanise3(id), "its id"] : titled.fallback(props);
26332
26733
  const what = id !== null ? `${type} "${id}"` : `the ${type} ${uid}`;
26333
26734
  const shown2 = title === "" ? "has no title at all" : `is titled "${title}" after ${from}`;
26334
26735
  out.push({ page: page.path, uid, type, id, noun: titled.noun, title, from, message: `${what} has no label, so its ${titled.noun} ${shown2}` });
@@ -26338,12 +26739,12 @@ function untitledCards2(contract) {
26338
26739
  }
26339
26740
  var BAND_LOOK5 = ["subtitle", "layout", "surface", "aside"];
26340
26741
  function lookPages2(contract) {
26341
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26742
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26342
26743
  const out = /* @__PURE__ */ new Set();
26343
26744
  for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
26344
- if (page?.owned === true || !named9(page?.path)) continue;
26345
- const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK5.some((key) => named9(section?.[key])));
26346
- const cards = Object.values(elements).some((element) => element?.page === page.path && (isPlainObject14(element.card) || named9(element.emphasis)));
26745
+ if (page?.owned === true || !named10(page?.path)) continue;
26746
+ const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK5.some((key) => named10(section?.[key])));
26747
+ const cards = Object.values(elements).some((element) => element?.page === page.path && (isPlainObject15(element.card) || named10(element.emphasis)));
26347
26748
  if (bands || cards) out.add(page.path);
26348
26749
  }
26349
26750
  return out;
@@ -26351,34 +26752,34 @@ function lookPages2(contract) {
26351
26752
  var SPAN_LAYOUTS2 = /* @__PURE__ */ new Set([void 0, "grid"]);
26352
26753
  var CONTROL_TYPES2 = /* @__PURE__ */ new Set(["filter", "control", ...CONTROL_PARTS2]);
26353
26754
  function bandsOf3(contract, page) {
26354
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26755
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26355
26756
  const roots = rootsOf3(contract, page);
26356
- return (Array.isArray(page.sections) ? page.sections : []).filter((section) => named9(section?.id)).map((section) => ({ section, roots: roots.filter((uid) => elements[uid]?.section === section.id) }));
26757
+ return (Array.isArray(page.sections) ? page.sections : []).filter((section) => named10(section?.id)).map((section) => ({ section, roots: roots.filter((uid) => elements[uid]?.section === section.id) }));
26357
26758
  }
26358
26759
  function nameOf4(uid, element) {
26359
- const id = named9(element?.props?.id) ? element.props.id : named9(element?.id) ? element.id : null;
26760
+ const id = named10(element?.props?.id) ? element.props.id : named10(element?.id) ? element.id : null;
26360
26761
  return id === null ? `the ${element?.type ?? "element"} ${uid}` : `${element.type} "${id}"`;
26361
26762
  }
26362
26763
  function isControl2(element, moduleSource) {
26363
- const props = isPlainObject14(element?.props) ? element.props : {};
26764
+ const props = isPlainObject15(element?.props) ? element.props : {};
26364
26765
  if (CONTROL_TYPES2.has(element?.type)) return true;
26365
- if (element?.type !== "custom" && element?.type !== "action" && named9(props.param)) return true;
26366
- if (isPlainObject14(props.props) && named9(props.props.param)) return true;
26367
- if (element?.type === "custom" && named9(props.module)) {
26766
+ if (element?.type !== "custom" && element?.type !== "action" && named10(props.param)) return true;
26767
+ if (isPlainObject15(props.props) && named10(props.props.param)) return true;
26768
+ if (element?.type === "custom" && named10(props.module)) {
26368
26769
  const source = moduleSource(props.module);
26369
26770
  return typeof source === "string" && scanUiSetParams2(source).length > 0;
26370
26771
  }
26371
26772
  return false;
26372
26773
  }
26373
26774
  function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
26374
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26775
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26375
26776
  const looked = lookPages2(contract);
26376
26777
  const out = [];
26377
26778
  for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
26378
26779
  if (!looked.has(page?.path)) continue;
26379
26780
  for (const { section, roots } of bandsOf3(contract, page)) {
26380
26781
  const band = `band "${section.id}" on ${page.path}`;
26381
- const layout = named9(section.layout) ? section.layout : void 0;
26782
+ const layout = named10(section.layout) ? section.layout : void 0;
26382
26783
  if (layout === "kpi_strip") {
26383
26784
  for (const uid of roots) {
26384
26785
  const element = elements[uid];
@@ -26442,10 +26843,10 @@ function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
26442
26843
  }
26443
26844
  var HEADER_ACTION_TYPES2 = ["button", "dialog"];
26444
26845
  function headerActionProblems2(contract) {
26445
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26846
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26446
26847
  const out = [];
26447
26848
  for (const [pageIndex, page] of (Array.isArray(contract?.pages) ? contract.pages : []).entries()) {
26448
- if (page?.owned === true || !isPlainObject14(page?.header) || page.header.actions === void 0) continue;
26849
+ if (page?.owned === true || !isPlainObject15(page?.header) || page.header.actions === void 0) continue;
26449
26850
  const say = (index, id, uid, message) => out.push({ rule: "L5", code: "header_action_unknown", page: page.path, pageIndex, index, id, uid, message });
26450
26851
  const actions = page.header.actions;
26451
26852
  if (!Array.isArray(actions) || actions.length === 0) {
@@ -26454,7 +26855,7 @@ function headerActionProblems2(contract) {
26454
26855
  }
26455
26856
  const seen = /* @__PURE__ */ new Set();
26456
26857
  for (const [index, id] of actions.entries()) {
26457
- if (!named9(id)) {
26858
+ if (!named10(id)) {
26458
26859
  say(index, id, null, `${page.path}'s header.actions[${index}] is ${JSON.stringify(id)}; each entry is an element id (props.id) on the page`);
26459
26860
  continue;
26460
26861
  }
@@ -26465,7 +26866,7 @@ function headerActionProblems2(contract) {
26465
26866
  seen.add(id);
26466
26867
  const hits = Object.keys(elements).filter((uid) => {
26467
26868
  const element2 = elements[uid];
26468
- return element2?.page === page.path && element2.type !== "region" && (named9(element2.props?.id) ? element2.props.id : element2.id) === id;
26869
+ return element2?.page === page.path && element2.type !== "region" && (named10(element2.props?.id) ? element2.props.id : element2.id) === id;
26469
26870
  });
26470
26871
  if (hits.length === 0) {
26471
26872
  say(index, id, null, `${page.path}'s header.actions names "${id}", and no element on the page has that id, so nothing is drawn beside the title; name a button or dialog of this page by its props.id`);
@@ -26481,11 +26882,11 @@ function headerActionProblems2(contract) {
26481
26882
  }
26482
26883
  function navCountProblems2(contract) {
26483
26884
  const out = [];
26484
- const handlers = isPlainObject14(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
26885
+ const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
26485
26886
  const pages = (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true);
26486
26887
  const entries = Array.isArray(contract?.app?.shell?.rail?.entries) ? contract.app.shell.rail.entries : [];
26487
- const measured = entries.map((entry, index) => ({ entry, index })).filter(({ entry }) => named9(entry?.measure));
26488
- const counted = pages.filter((page) => isPlainObject14(page.nav) && page.nav.counts !== void 0);
26888
+ const measured = entries.map((entry, index) => ({ entry, index })).filter(({ entry }) => named10(entry?.measure));
26889
+ const counted = pages.filter((page) => isPlainObject15(page.nav) && page.nav.counts !== void 0);
26489
26890
  if (counted.length === 0) {
26490
26891
  if (measured.length > 0) {
26491
26892
  out.push({
@@ -26501,21 +26902,21 @@ function navCountProblems2(contract) {
26501
26902
  for (const page of counted) {
26502
26903
  const source = page.nav.counts?.source;
26503
26904
  const where2 = `${page.path} nav.counts.source`;
26504
- if (!named9(source)) {
26905
+ if (!named10(source)) {
26505
26906
  out.push({ rule: "L6", page: page.path, where: where2, message: `${where2} is ${JSON.stringify(source ?? null)}; it names the summary handler whose measures are the rail's counts` });
26506
26907
  continue;
26507
26908
  }
26508
26909
  const handler = handlers[source];
26509
- if (!isPlainObject14(handler) || handler.role !== "summary") {
26910
+ if (!isPlainObject15(handler) || handler.role !== "summary") {
26510
26911
  out.push({
26511
26912
  rule: "L6",
26512
26913
  page: page.path,
26513
26914
  where: where2,
26514
- message: `${where2} is "${source}", which is ${isPlainObject14(handler) ? `a ${JSON.stringify(handler.role ?? null)} handler` : "not a handler of this app"}; the rail's counts are one number per measure, which a summary handler answers`
26915
+ message: `${where2} is "${source}", which is ${isPlainObject15(handler) ? `a ${JSON.stringify(handler.role ?? null)} handler` : "not a handler of this app"}; the rail's counts are one number per measure, which a summary handler answers`
26515
26916
  });
26516
26917
  continue;
26517
26918
  }
26518
- if (!isPlainObject14(handler.measures) || measuresChecked.has(source)) continue;
26919
+ if (!isPlainObject15(handler.measures) || measuresChecked.has(source)) continue;
26519
26920
  measuresChecked.add(source);
26520
26921
  for (const { entry, index } of measured) {
26521
26922
  if (Object.prototype.hasOwnProperty.call(handler.measures, entry.measure)) continue;
@@ -26531,17 +26932,17 @@ function navCountProblems2(contract) {
26531
26932
  }
26532
26933
  function appRoles2(contract) {
26533
26934
  const raw = Array.isArray(contract?.backend?.roles) ? contract.backend.roles : [];
26534
- return raw.map((role) => typeof role === "string" ? { name: role, sees: [] } : { name: role?.name, sees: Array.isArray(role?.sees) ? role.sees.filter(named9) : [] }).filter((role) => named9(role.name));
26935
+ return raw.map((role) => typeof role === "string" ? { name: role, sees: [] } : { name: role?.name, sees: Array.isArray(role?.sees) ? role.sees.filter(named10) : [] }).filter((role) => named10(role.name));
26535
26936
  }
26536
26937
  function railReachProblems2(contract) {
26537
26938
  const out = [];
26538
26939
  const rail = contract?.app?.shell?.rail;
26539
- if (!isPlainObject14(rail)) return out;
26940
+ if (!isPlainObject15(rail)) return out;
26540
26941
  const roles = appRoles2(contract);
26541
26942
  if (roles.length === 0) return out;
26542
26943
  const declared = new Map(roles.map((role) => [role.name, role]));
26543
- const pages = new Map((Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named9(page?.path)).map((page) => [page.path, page]));
26544
- const openers = (page) => Array.isArray(page?.roles) && page.roles.filter(named9).length > 0 ? page.roles.filter(named9) : null;
26944
+ const pages = new Map((Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named10(page?.path)).map((page) => [page.path, page]));
26945
+ const openers = (page) => Array.isArray(page?.roles) && page.roles.filter(named10).length > 0 ? page.roles.filter(named10) : null;
26545
26946
  const canOpen = (name, page) => {
26546
26947
  const open2 = openers(page);
26547
26948
  if (open2 === null) return true;
@@ -26550,11 +26951,11 @@ function railReachProblems2(contract) {
26550
26951
  };
26551
26952
  for (const key of ["entries", "footer"]) {
26552
26953
  for (const [index, entry] of (Array.isArray(rail[key]) ? rail[key] : []).entries()) {
26553
- if (!isPlainObject14(entry) || !named9(entry.label) || !named9(entry.page)) continue;
26954
+ if (!isPlainObject15(entry) || !named10(entry.label) || !named10(entry.page)) continue;
26554
26955
  const page = pages.get(entry.page);
26555
26956
  if (page === void 0) continue;
26556
26957
  const where2 = `app.shell.rail.${key}[${index}]`;
26557
- const viewers = named9(entry.role) ? entry.role.split(/\s+/).filter(named9) : openers(page) ?? roles.map((role) => role.name);
26958
+ const viewers = named10(entry.role) ? entry.role.split(/\s+/).filter(named10) : openers(page) ?? roles.map((role) => role.name);
26558
26959
  const known = viewers.filter((name) => declared.has(name));
26559
26960
  if (known.length === 0) {
26560
26961
  out.push({ rule: "L7", where: where2, message: `rail ${key === "footer" ? "footer link" : "entry"} "${entry.label}" is drawn for ${viewers.map((one) => JSON.stringify(one)).join(", ")}, and the app declares none of them (${roles.map((role) => JSON.stringify(role.name)).join(", ")}), so nobody sees it` });
@@ -26581,35 +26982,35 @@ var PAGES_FIX2 = "state `app.shell` with `nav: rail`; with no `rail.entries` the
26581
26982
  var COUNTS_FIX2 = "state `app.shell` with `nav: rail` and one entry per state, each counting a measure of that summary (`pages[].nav.counts.source`); look.md rule 1";
26582
26983
  function isAppContract2(contract) {
26583
26984
  const kind = contract?.app?.kind;
26584
- return kind !== "dashboard" && (kind === "app" || isPlainObject14(contract?.data?.app));
26985
+ return kind !== "dashboard" && (kind === "app" || isPlainObject15(contract?.data?.app));
26585
26986
  }
26586
26987
  function tablesOf3(contract) {
26587
26988
  const tables = contract?.app?.data_model?.tables;
26588
- return isPlainObject14(tables) ? tables : {};
26989
+ return isPlainObject15(tables) ? tables : {};
26589
26990
  }
26590
26991
  function enumOf2(column) {
26591
26992
  return Array.isArray(column?.enum) ? column.enum.filter((one) => typeof one === "string") : [];
26592
26993
  }
26593
26994
  function stateColumn2(contract, table) {
26594
- const columns = isPlainObject14(tablesOf3(contract)[table]?.columns) ? tablesOf3(contract)[table].columns : {};
26995
+ const columns = isPlainObject15(tablesOf3(contract)[table]?.columns) ? tablesOf3(contract)[table].columns : {};
26595
26996
  return Object.keys(columns).find((name) => STATE_NAMES2.includes(name) && enumOf2(columns[name]).length >= 3) ?? null;
26596
26997
  }
26597
26998
  function realValues2(param) {
26598
26999
  return Array.isArray(param?.values) ? param.values.map(String).filter((value) => !CATCH_ALL2.has(value.toLowerCase())) : [];
26599
27000
  }
26600
27001
  function ownPages2(contract) {
26601
- return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true && named9(page?.path));
27002
+ return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true && named10(page?.path));
26602
27003
  }
26603
27004
  function folderParams2(contract) {
26604
27005
  const enums = [];
26605
27006
  for (const [table, spec] of Object.entries(tablesOf3(contract))) {
26606
- const columns = isPlainObject14(spec?.columns) ? spec.columns : {};
27007
+ const columns = isPlainObject15(spec?.columns) ? spec.columns : {};
26607
27008
  for (const name of STATE_NAMES2) if (enumOf2(columns[name]).length >= 3) enums.push({ where: `${table}.${name}`, values: new Set(enumOf2(columns[name])) });
26608
27009
  }
26609
27010
  const out = [];
26610
27011
  for (const page of ownPages2(contract)) {
26611
27012
  for (const param of Array.isArray(page.params) ? page.params : []) {
26612
- if (!PICK_KINDS2.has(param?.kind) || !named9(param?.name)) continue;
27013
+ if (!PICK_KINDS2.has(param?.kind) || !named10(param?.name)) continue;
26613
27014
  const values = realValues2(param);
26614
27015
  if (STATE_NAMES2.includes(param.name) && (values.length >= 3 || !Array.isArray(param.values))) {
26615
27016
  out.push({ page, param, values, why: `named "${param.name}"` });
@@ -26623,18 +27024,18 @@ function folderParams2(contract) {
26623
27024
  return out;
26624
27025
  }
26625
27026
  function tableOf3(contract, element) {
26626
- const props = isPlainObject14(element?.props) ? element.props : {};
26627
- if (named9(props.entity)) return props.entity;
27027
+ const props = isPlainObject15(element?.props) ? element.props : {};
27028
+ if (named10(props.entity)) return props.entity;
26628
27029
  const source = props.source;
26629
- if (!named9(source)) return null;
27030
+ if (!named10(source)) return null;
26630
27031
  const handler = contract?.data?.app?.handlers?.[source];
26631
- if (isPlainObject14(handler) && named9(handler.table)) return handler.table;
27032
+ if (isPlainObject15(handler) && named10(handler.table)) return handler.table;
26632
27033
  const read = contract?.data?.sources?.[source];
26633
- if (isPlainObject14(read?.from) && named9(read.from.table)) return read.from.table;
27034
+ if (isPlainObject15(read?.from) && named10(read.from.table)) return read.from.table;
26634
27035
  return null;
26635
27036
  }
26636
27037
  function listingOn2(contract, page, test) {
26637
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
27038
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26638
27039
  for (const [uid, element] of Object.entries(elements)) {
26639
27040
  if (element?.page !== page.path || !ROW_TYPES2.has(element.type)) continue;
26640
27041
  const table = tableOf3(contract, element);
@@ -26643,27 +27044,27 @@ function listingOn2(contract, page, test) {
26643
27044
  return null;
26644
27045
  }
26645
27046
  function stateCountColumn2(spec) {
26646
- if (!isPlainObject14(spec) || NOT_COUNTS2.some((key) => spec[key] !== void 0)) return null;
26647
- const where2 = spec.count === true ? isPlainObject14(spec.where) ? spec.where : {} : spec.count === void 0 ? spec : null;
27047
+ if (!isPlainObject15(spec) || NOT_COUNTS2.some((key) => spec[key] !== void 0)) return null;
27048
+ const where2 = spec.count === true ? isPlainObject15(spec.where) ? spec.where : {} : spec.count === void 0 ? spec : null;
26648
27049
  if (where2 === null) return null;
26649
27050
  return Object.keys(where2).map((key) => key.split("__")[0]).find((column) => STATE_NAMES2.includes(column)) ?? null;
26650
27051
  }
26651
27052
  function stateCounts2(handler) {
26652
27053
  const out = {};
26653
- for (const [measure, spec] of Object.entries(isPlainObject14(handler?.measures) ? handler.measures : {})) {
27054
+ for (const [measure, spec] of Object.entries(isPlainObject15(handler?.measures) ? handler.measures : {})) {
26654
27055
  const column = stateCountColumn2(spec);
26655
27056
  if (column !== null) out[measure] = column;
26656
27057
  }
26657
27058
  return out;
26658
27059
  }
26659
27060
  function pageReads2(contract, page, name) {
26660
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
27061
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
26661
27062
  if (page?.nav?.counts?.source === name) return true;
26662
27063
  return Object.values(elements).some((element) => element?.page === page.path && element.props?.source === name);
26663
27064
  }
26664
27065
  function shellExpectedProblems2(contract) {
26665
27066
  if (!isAppContract2(contract)) return [];
26666
- const shell = isPlainObject14(contract?.app?.shell) ? contract.app.shell : {};
27067
+ const shell = isPlainObject15(contract?.app?.shell) ? contract.app.shell : {};
26667
27068
  if (shell.nav === "rail") return [];
26668
27069
  const out = [];
26669
27070
  const say = (page, param, where2, message, fix) => out.push({ rule: "L8", code: "shell-expected", page, param, where: where2, message, fix });
@@ -26676,9 +27077,9 @@ function shellExpectedProblems2(contract) {
26676
27077
  const listed = values.length > 0 ? ` with ${values.length} values (${values.map((one) => JSON.stringify(one)).join(", ")})` : " (its values from elsewhere)";
26677
27078
  say(page.path, param.name, `${page.path} params.${param.name}`, `${page.path} declares ${param.kind} param "${param.name}" (${why})${listed} and the app ${has}, so its folders are a dropdown with no counts; they are the rail's entries`, VALUES_FIX2);
26678
27079
  }
26679
- const handlers = isPlainObject14(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27080
+ const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
26680
27081
  for (const [name, handler] of Object.entries(handlers)) {
26681
- if (handler?.role !== "summary" || !named9(handler.table)) continue;
27082
+ if (handler?.role !== "summary" || !named10(handler.table)) continue;
26682
27083
  const column = stateColumn2(contract, handler.table);
26683
27084
  const counts = Object.fromEntries(Object.entries(stateCounts2(handler)).filter(([, by]) => by === column));
26684
27085
  if (column === null || Object.keys(counts).length < 3) continue;
@@ -26700,8 +27101,8 @@ function shellExpectedProblems2(contract) {
26700
27101
  }
26701
27102
  function kpiStripWorkflowProblems2(contract) {
26702
27103
  if (!isAppContract2(contract)) return [];
26703
- const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
26704
- const handlers = isPlainObject14(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27104
+ const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
27105
+ const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
26705
27106
  const folderPages = /* @__PURE__ */ new Map();
26706
27107
  for (const { page, param } of folderParams2(contract)) if (!folderPages.has(page.path)) folderPages.set(page.path, param.name);
26707
27108
  const out = [];
@@ -26718,14 +27119,14 @@ function kpiStripWorkflowProblems2(contract) {
26718
27119
  const bySource = /* @__PURE__ */ new Map();
26719
27120
  for (const uid of stats) {
26720
27121
  const source = elements[uid]?.props?.source;
26721
- if (!named9(source) || handlers[source]?.role !== "summary") continue;
27122
+ if (!named10(source) || handlers[source]?.role !== "summary") continue;
26722
27123
  if (!bySource.has(source)) bySource.set(source, []);
26723
27124
  bySource.get(source).push(uid);
26724
27125
  }
26725
27126
  for (const [source, group] of bySource) {
26726
27127
  if (group.length < 2) continue;
26727
27128
  const measures = handlers[source].measures;
26728
- const known = isPlainObject14(measures) && group.every((uid) => Object.prototype.hasOwnProperty.call(measures, elements[uid]?.props?.measure));
27129
+ const known = isPlainObject15(measures) && group.every((uid) => Object.prototype.hasOwnProperty.call(measures, elements[uid]?.props?.measure));
26729
27130
  const counts = stateCounts2(handlers[source]);
26730
27131
  if (known ? !group.every((uid) => counts[elements[uid].props.measure] !== void 0) : group.length !== stats.length) continue;
26731
27132
  const where2 = section === null ? page.path : `band "${section.id}" on ${page.path}`;
@@ -27159,7 +27560,7 @@ function rolesOf5(manifest) {
27159
27560
  // ../flows/appspace-builder/kit/checks/rules/ux.mjs
27160
27561
  var RULES4 = ["A3.23", "A3.24", "A3.25", "A3.26", "A3.27"];
27161
27562
  var STATE_COLUMNS = ["status", "state"];
27162
- var isPlainObject15 = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
27563
+ var isPlainObject16 = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
27163
27564
  var said = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
27164
27565
  var OPENERS4 = /* @__PURE__ */ new Set(["dialog", "list", "queue", "detail"]);
27165
27566
  var ROWS = /* @__PURE__ */ new Set(["list", "queue", "data_grid"]);
@@ -27168,9 +27569,9 @@ function checkUx(app, contract, contractError) {
27168
27569
  const reason = `the contract could not be extracted (${contractError ?? "no reason given"}), so the forms, lists and dialogs are not knowable here`;
27169
27570
  return { violations: [], skipped: RULES4.map((rule) => ({ rule, reason })) };
27170
27571
  }
27171
- const elements = isPlainObject15(contract.elements) ? contract.elements : {};
27172
- const handlers = isPlainObject15(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
27173
- const tables = isPlainObject15(contract.app?.data_model?.tables) ? contract.app.data_model.tables : {};
27572
+ const elements = isPlainObject16(contract.elements) ? contract.elements : {};
27573
+ const handlers = isPlainObject16(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
27574
+ const tables = isPlainObject16(contract.app?.data_model?.tables) ? contract.app.data_model.tables : {};
27174
27575
  const uids = Object.keys(elements).sort(byPageThenRank);
27175
27576
  const kidsOf = (uid) => {
27176
27577
  const el = elements[uid];
@@ -27211,15 +27612,15 @@ function checkUx(app, contract, contractError) {
27211
27612
  if (!ROWS.has(el?.type)) continue;
27212
27613
  if (ancestors(uid).some((up) => up.type === "detail")) continue;
27213
27614
  const table = tableOf4(el);
27214
- if (!table || table.startsWith("_") || !isPlainObject15(tables[table]?.columns) || tables[table].singleton === true) continue;
27615
+ if (!table || table.startsWith("_") || !isPlainObject16(tables[table]?.columns) || tables[table].singleton === true) continue;
27215
27616
  const columns = tables[table].columns;
27216
- const pk = Object.keys(columns).find((c) => isPlainObject15(columns[c]) && columns[c].pk === true) ?? "id";
27617
+ const pk = Object.keys(columns).find((c) => isPlainObject16(columns[c]) && columns[c].pk === true) ?? "id";
27217
27618
  const fields = Object.keys(columns).filter((c) => c !== pk && !c.startsWith("_"));
27218
27619
  let shown2;
27219
27620
  let opens3;
27220
27621
  if (el.type === "data_grid") {
27221
- const typed = isPlainObject15(el.props?.props) ? el.props.props : {};
27222
- shown2 = new Set((Array.isArray(typed.columns) ? typed.columns : []).map((c) => isPlainObject15(c) ? c.field : c).filter(said));
27622
+ const typed = isPlainObject16(el.props?.props) ? el.props.props : {};
27623
+ shown2 = new Set((Array.isArray(typed.columns) ? typed.columns : []).map((c) => isPlainObject16(c) ? c.field : c).filter(said));
27223
27624
  opens3 = Boolean(said(typed.on_row)) || Array.isArray(typed.row_actions) && typed.row_actions.length > 0 || typed.row_detail === "drawer" || bindsOn.has(el.page);
27224
27625
  } else {
27225
27626
  const kids = kidsOf(uid).map((k) => elements[k]);
@@ -27229,17 +27630,17 @@ function checkUx(app, contract, contractError) {
27229
27630
  if (opens3) continue;
27230
27631
  const hidden = fields.filter((c) => !shown2.has(c));
27231
27632
  if (hidden.length === 0) continue;
27232
- const named12 = hidden.slice(0, 4).map((c) => `"${c}"`).join(", ") + (hidden.length > 4 ? ` and ${hidden.length - 4} more` : "");
27633
+ const named13 = hidden.slice(0, 4).map((c) => `"${c}"`).join(", ") + (hidden.length > 4 ? ` and ${hidden.length - 4} more` : "");
27233
27634
  violations.push({
27234
27635
  rule: "A3.24",
27235
27636
  where: uid,
27236
- message: `${el.type} "${nameOf5(uid)}" on ${el.page} shows ${shown2.size} of ${table}'s ${fields.length} columns and gives no way to open a row \u2014 no detail, no row action, no on_row (row-no-detail) \u2014 so ${named12} can never be read. ` + (el.type === "data_grid" ? "Give it an on_row or a row action that opens a dialog, or show it as a list with a detail child" : 'Give it a detail child ({"type": "detail", "props": {"entity": "' + table + '"}}): a row click opens the record in a drawer, and a detail that names no field shows every column')
27637
+ message: `${el.type} "${nameOf5(uid)}" on ${el.page} shows ${shown2.size} of ${table}'s ${fields.length} columns and gives no way to open a row \u2014 no detail, no row action, no on_row (row-no-detail) \u2014 so ${named13} can never be read. ` + (el.type === "data_grid" ? "Give it an on_row or a row action that opens a dialog, or show it as a list with a detail child" : 'Give it a detail child ({"type": "detail", "props": {"entity": "' + table + '"}}): a row click opens the record in a drawer, and a detail that names no field shows every column')
27237
27638
  });
27238
27639
  }
27239
27640
  for (const uid of uids) {
27240
27641
  const el = elements[uid];
27241
27642
  if (el?.type !== "dialog") continue;
27242
- const typed = isPlainObject15(el.props?.props) ? el.props.props : {};
27643
+ const typed = isPlainObject16(el.props?.props) ? el.props.props : {};
27243
27644
  if (typed.values_from === "row") continue;
27244
27645
  if (said(typed.trigger) || said(el.props?.label) || said(typed.title)) continue;
27245
27646
  violations.push({
@@ -27252,8 +27653,8 @@ function checkUx(app, contract, contractError) {
27252
27653
  const el = elements[uid];
27253
27654
  if (!ROWS.has(el?.type) || el.type === "data_grid") continue;
27254
27655
  const table = tableOf4(el);
27255
- const columns = isPlainObject15(tables[table]?.columns) ? tables[table].columns : {};
27256
- const state = STATE_COLUMNS.find((c) => isPlainObject15(columns[c]) && Array.isArray(columns[c].enum));
27656
+ const columns = isPlainObject16(tables[table]?.columns) ? tables[table].columns : {};
27657
+ const state = STATE_COLUMNS.find((c) => isPlainObject16(columns[c]) && Array.isArray(columns[c].enum));
27257
27658
  const seen = /* @__PURE__ */ new Map();
27258
27659
  for (const kid of kidsOf(uid)) {
27259
27660
  const action = elements[kid];
@@ -27294,8 +27695,8 @@ function split(uid) {
27294
27695
  }
27295
27696
 
27296
27697
  // ../flows/appspace-builder/kit/contract/workflow.mjs
27297
- var isPlainObject16 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
27298
- var named10 = (value) => typeof value === "string" && value !== "";
27698
+ var isPlainObject17 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
27699
+ var named11 = (value) => typeof value === "string" && value !== "";
27299
27700
  function either5(items) {
27300
27701
  if (items.length <= 1) return items.join("");
27301
27702
  return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
@@ -27311,16 +27712,16 @@ function roleModel2(contract) {
27311
27712
  const sees = /* @__PURE__ */ new Map();
27312
27713
  const home = /* @__PURE__ */ new Map();
27313
27714
  for (const row of rows2) {
27314
- if (!isPlainObject16(row) || !named10(row.name) || names.includes(row.name)) continue;
27715
+ if (!isPlainObject17(row) || !named11(row.name) || names.includes(row.name)) continue;
27315
27716
  names.push(row.name);
27316
- sees.set(row.name, new Set((Array.isArray(row.sees) ? row.sees : []).filter(named10)));
27317
- if (named10(row.home)) home.set(row.name, row.home);
27717
+ sees.set(row.name, new Set((Array.isArray(row.sees) ? row.sees : []).filter(named11)));
27718
+ if (named11(row.home)) home.set(row.name, row.home);
27318
27719
  }
27319
27720
  return { names, sees, home };
27320
27721
  }
27321
27722
  function gateRoles2(gate) {
27322
- if (Array.isArray(gate)) return gate.filter(named10);
27323
- return named10(gate) ? gate.split(/[\s,]+/).filter(named10) : [];
27723
+ if (Array.isArray(gate)) return gate.filter(named11);
27724
+ return named11(gate) ? gate.split(/[\s,]+/).filter(named11) : [];
27324
27725
  }
27325
27726
  function seesGate2(model, role, gate) {
27326
27727
  const wanted = gateRoles2(gate);
@@ -27338,7 +27739,7 @@ var HREF2 = /\bhref\s*=\s*["']([^"']*)["']/i;
27338
27739
  var ROLE_ATTR4 = /\bdata-dr-role\s*=\s*["']([^"']*)["']/i;
27339
27740
  var PAGE_LITERAL2 = /["'`](?:\.\/)?([A-Za-z0-9_-]+\.html)(?:[?#][^"'`]*)?["'`]/g;
27340
27741
  function targetOf2(href) {
27341
- if (!named10(href) || /^(?:[a-z]+:)?\/\//i.test(href) || href.startsWith("/") || href.startsWith("#")) return null;
27742
+ if (!named11(href) || /^(?:[a-z]+:)?\/\//i.test(href) || href.startsWith("/") || href.startsWith("#")) return null;
27342
27743
  const file = href.replace(/^\.\//, "").replace(/[?#].*$/, "");
27343
27744
  if (!/^[A-Za-z0-9_-]+\.html$/.test(file)) return null;
27344
27745
  return `ui/${file}`;
@@ -27376,7 +27777,7 @@ function moduleLinks2(source) {
27376
27777
  return out;
27377
27778
  }
27378
27779
  function pagesOf2(contract) {
27379
- return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named10(page?.path));
27780
+ return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named11(page?.path));
27380
27781
  }
27381
27782
  function entryPage2(contract) {
27382
27783
  const pages = pagesOf2(contract);
@@ -27395,10 +27796,10 @@ function reachByRole2(contract, { pageSource = () => void 0, moduleSource = () =
27395
27796
  const model = roleModel2(contract);
27396
27797
  const pages = pagesOf2(contract);
27397
27798
  const byPath = new Map(pages.map((page) => [page.path, page]));
27398
- const elements = isPlainObject16(contract?.elements) ? contract.elements : {};
27799
+ const elements = isPlainObject17(contract?.elements) ? contract.elements : {};
27399
27800
  const modulesOn = /* @__PURE__ */ new Map();
27400
27801
  for (const element of Object.values(elements)) {
27401
- if (element?.type !== "custom" || !named10(element.page) || !named10(element.props?.module)) continue;
27802
+ if (element?.type !== "custom" || !named11(element.page) || !named11(element.props?.module)) continue;
27402
27803
  if (!modulesOn.has(element.page)) modulesOn.set(element.page, /* @__PURE__ */ new Set());
27403
27804
  modulesOn.get(element.page).add(element.props.module);
27404
27805
  }
@@ -27523,23 +27924,23 @@ function moduleAsks2(source) {
27523
27924
  return ASKS2.some((pattern) => pattern.test(String(source ?? "")));
27524
27925
  }
27525
27926
  function decideAsks2(row) {
27526
- if (row?.role !== "decide" || !isPlainObject16(row.decision)) return false;
27927
+ if (row?.role !== "decide" || !isPlainObject17(row.decision)) return false;
27527
27928
  const names = Object.keys(row.decision);
27528
27929
  if (names.length !== 1) return false;
27529
27930
  const spec = row.decision[names[0]];
27530
- return isPlainObject16(spec) && (spec.note === "required" || spec.note === "optional");
27931
+ return isPlainObject17(spec) && (spec.note === "required" || spec.note === "optional");
27531
27932
  }
27532
27933
  function insideDialog2(elements, uid) {
27533
27934
  const seen = /* @__PURE__ */ new Set();
27534
- for (let at = elements[uid]?.parent; named10(at) && !seen.has(at); at = elements[at]?.parent) {
27935
+ for (let at = elements[uid]?.parent; named11(at) && !seen.has(at); at = elements[at]?.parent) {
27535
27936
  seen.add(at);
27536
27937
  if (elements[at]?.type === "dialog") return true;
27537
27938
  }
27538
27939
  return false;
27539
27940
  }
27540
27941
  function stateCallers2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
27541
- const elements = isPlainObject16(contract?.elements) ? contract.elements : {};
27542
- const handlers = isPlainObject16(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27942
+ const elements = isPlainObject17(contract?.elements) ? contract.elements : {};
27943
+ const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27543
27944
  const changing = new Set(Object.keys(handlers).filter((name) => changesState2(name, handlers[name], handlerSource(name))));
27544
27945
  const out = /* @__PURE__ */ new Map();
27545
27946
  const add = (handler, caller) => {
@@ -27549,10 +27950,10 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
27549
27950
  };
27550
27951
  for (const uid of Object.keys(elements).sort()) {
27551
27952
  const element = elements[uid];
27552
- const props = isPlainObject16(element?.props) ? element.props : {};
27553
- const typed = isPlainObject16(props.props) ? props.props : {};
27953
+ const props = isPlainObject17(element?.props) ? element.props : {};
27954
+ const typed = isPlainObject17(props.props) ? props.props : {};
27554
27955
  const page = element?.page ?? null;
27555
- const id = named10(props.id) ? props.id : uid;
27956
+ const id = named11(props.id) ? props.id : uid;
27556
27957
  switch (element?.type) {
27557
27958
  case "form":
27558
27959
  add(props.action, { page, uid, id, what: "form", asks: true });
@@ -27563,32 +27964,32 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
27563
27964
  case "data_grid":
27564
27965
  add(typed.on_row, { page, uid, id, what: "grid row click", asks: false });
27565
27966
  for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
27566
- if (!isPlainObject16(action) || !named10(action.call)) continue;
27567
- const asks = action.confirm === true || named10(action.confirm);
27967
+ if (!isPlainObject17(action) || !named11(action.call)) continue;
27968
+ const asks = action.confirm === true || named11(action.confirm);
27568
27969
  add(action.call, { page, uid, id, what: `row action "${action.label ?? action.call}"`, asks });
27569
27970
  }
27570
- for (const action of isPlainObject16(typed.selection) && Array.isArray(typed.selection.bulk_actions) ? typed.selection.bulk_actions : []) {
27571
- if (!isPlainObject16(action) || !named10(action.call)) continue;
27572
- const asks = action.confirm === true || named10(action.confirm);
27971
+ for (const action of isPlainObject17(typed.selection) && Array.isArray(typed.selection.bulk_actions) ? typed.selection.bulk_actions : []) {
27972
+ if (!isPlainObject17(action) || !named11(action.call)) continue;
27973
+ const asks = action.confirm === true || named11(action.confirm);
27573
27974
  add(action.call, { page, uid, id, what: `bulk action "${action.label ?? action.call}"`, asks });
27574
27975
  }
27575
27976
  break;
27576
27977
  case "action":
27577
27978
  case "detail": {
27578
- if (!named10(props.action)) break;
27579
- const asks = insideDialog2(elements, uid) || decideAsks2(handlers[props.action]) || props.confirm === true || named10(props.confirm);
27979
+ if (!named11(props.action)) break;
27980
+ const asks = insideDialog2(elements, uid) || decideAsks2(handlers[props.action]) || props.confirm === true || named11(props.confirm);
27580
27981
  add(props.action, { page, uid, id, what: element.type === "action" ? "button" : "detail button", asks });
27581
27982
  break;
27582
27983
  }
27583
27984
  case "custom": {
27584
- const declares = isPlainObject16(props.declares) ? props.declares : {};
27985
+ const declares = isPlainObject17(props.declares) ? props.declares : {};
27585
27986
  const called = /* @__PURE__ */ new Set([
27586
- ...Array.isArray(declares.calls) ? declares.calls.filter(named10) : [],
27587
- ...Object.keys(isPlainObject16(declares.produces) ? declares.produces : {})
27987
+ ...Array.isArray(declares.calls) ? declares.calls.filter(named11) : [],
27988
+ ...Object.keys(isPlainObject17(declares.produces) ? declares.produces : {})
27588
27989
  ]);
27589
- const source = named10(props.module) ? moduleSource(props.module) : void 0;
27990
+ const source = named11(props.module) ? moduleSource(props.module) : void 0;
27590
27991
  if (typeof source === "string") {
27591
- for (const call of scanUiCalls2(source)) if (named10(call.handler)) called.add(call.handler);
27992
+ for (const call of scanUiCalls2(source)) if (named11(call.handler)) called.add(call.handler);
27592
27993
  }
27593
27994
  const asks = typeof source === "string" ? moduleAsks2(source) : null;
27594
27995
  for (const handler of called) add(handler, { page, uid, id, what: `custom ${props.module ?? ""}`.trim(), asks, module: props.module });
@@ -27601,7 +28002,7 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
27601
28002
  return out;
27602
28003
  }
27603
28004
  function unconfirmedStateChanges2(contract, files = {}) {
27604
- const handlers = isPlainObject16(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
28005
+ const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27605
28006
  const out = [];
27606
28007
  for (const [handler, callers] of [...stateCallers2(contract, files).entries()].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
27607
28008
  const bare = callers.filter((one) => one.asks === false);
@@ -27673,7 +28074,7 @@ function requiresReason2(lines, key, vars) {
27673
28074
  return false;
27674
28075
  }
27675
28076
  function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
27676
- const handlers = isPlainObject16(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
28077
+ const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27677
28078
  const producers = payloadProducers2(contract, { moduleSource, pageSource });
27678
28079
  const out = [];
27679
28080
  for (const name of Object.keys(handlers).sort()) {
@@ -27683,13 +28084,13 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
27683
28084
  if (typeof source !== "string") continue;
27684
28085
  const reads = pyPayloadReads2(source);
27685
28086
  const read = /* @__PURE__ */ new Set([...reads.required, ...reads.optional]);
27686
- const own = (producers.get(name) ?? []).filter((one) => Array.isArray(one.keys));
28087
+ const own2 = (producers.get(name) ?? []).filter((one) => Array.isArray(one.keys));
27687
28088
  const sent = /* @__PURE__ */ new Map();
27688
- for (const one of own) {
28089
+ for (const one of own2) {
27689
28090
  for (const key of one.keys) {
27690
28091
  if (!REASON_KEYS2.includes(key)) continue;
27691
28092
  if (!sent.has(key)) sent.set(key, /* @__PURE__ */ new Set());
27692
- if (named10(one.page)) sent.get(key).add(one.page);
28093
+ if (named11(one.page)) sent.get(key).add(one.page);
27693
28094
  }
27694
28095
  }
27695
28096
  for (const [key, pages] of [...sent.entries()].sort()) {
@@ -27712,7 +28113,7 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
27712
28113
  return out;
27713
28114
  }
27714
28115
  function unliftableRefusals2(contract, { handlerSource = () => void 0 } = {}) {
27715
- const handlers = isPlainObject16(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
28116
+ const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27716
28117
  const out = [];
27717
28118
  for (const name of Object.keys(handlers).sort()) {
27718
28119
  if ((handlers[name]?.role ?? "custom") !== "custom") continue;
@@ -27770,13 +28171,13 @@ function rolesLandingNowhere2(contract) {
27770
28171
  function sourceRoles2(contract, name) {
27771
28172
  const source = contract?.data?.sources?.[name];
27772
28173
  const stated2 = gateRoles2(source?.requiresRole);
27773
- const elements = isPlainObject16(contract?.elements) ? contract.elements : {};
28174
+ const elements = isPlainObject17(contract?.elements) ? contract.elements : {};
27774
28175
  const pages = new Map(pagesOf2(contract).map((page) => [page.path, page]));
27775
28176
  const union = [];
27776
28177
  let readers = 0;
27777
28178
  let open2 = false;
27778
28179
  for (const element of Object.values(elements)) {
27779
- const props = isPlainObject16(element?.props) ? element.props : {};
28180
+ const props = isPlainObject17(element?.props) ? element.props : {};
27780
28181
  const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []];
27781
28182
  if (!reads.includes(name)) continue;
27782
28183
  readers += 1;
@@ -27811,14 +28212,14 @@ function openToEveryRole2(contract) {
27811
28212
  { one: "page", many: "pages", verb: "open" },
27812
28213
  "State pages[].roles on each: the roles it is for (all of them, if that is what you mean)."
27813
28214
  );
27814
- const handlers = isPlainObject16(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
28215
+ const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
27815
28216
  say(
27816
28217
  "handler",
27817
28218
  Object.keys(handlers).sort().filter((name) => gateRoles2(handlers[name]?.requiresRole).length === 0).map((name) => `"${name}"`),
27818
28219
  { one: "handler", many: "handlers", verb: "call" },
27819
28220
  "State requiresRole on each: the roles that may call it (all of them, if that is what you mean)."
27820
28221
  );
27821
- const sources = isPlainObject16(contract?.data?.sources) ? contract.data.sources : {};
28222
+ const sources = isPlainObject17(contract?.data?.sources) ? contract.data.sources : {};
27822
28223
  say(
27823
28224
  "source",
27824
28225
  Object.keys(sources).sort().filter((name) => {
@@ -28789,8 +29190,8 @@ function compareElements(want, got, diffs) {
28789
29190
  }
28790
29191
  }
28791
29192
  function describeElement(element) {
28792
- const named12 = element["id"] ?? element["role"];
28793
- if (named12 !== void 0 && named12 !== null) return describeValue(named12);
29193
+ const named13 = element["id"] ?? element["role"];
29194
+ if (named13 !== void 0 && named13 !== null) return describeValue(named13);
28794
29195
  return `on ${describeValue(element["page"])}`;
28795
29196
  }
28796
29197
  function compare(want, got, at, diffs) {
@@ -28916,10 +29317,10 @@ async function loadContract(dir, opts) {
28916
29317
  }
28917
29318
  return { contract: assembled.contract, assemble: record2, headline, overlay };
28918
29319
  }
28919
- function pageDrafts(named12) {
28920
- const doc = named12.doc;
28921
- if (!isPlain(doc) || !Array.isArray(doc["drafts"]) || Object.keys(doc).length !== 1) return [named12];
28922
- return doc["drafts"].map((one, index) => ({ name: `${named12.name}[${String(index + 1)}]`, doc: one }));
29320
+ function pageDrafts(named13) {
29321
+ const doc = named13.doc;
29322
+ if (!isPlain(doc) || !Array.isArray(doc["drafts"]) || Object.keys(doc).length !== 1) return [named13];
29323
+ return doc["drafts"].map((one, index) => ({ name: `${named13.name}[${String(index + 1)}]`, doc: one }));
28923
29324
  }
28924
29325
  function nothingWritten(report) {
28925
29326
  const transforms = report.assemble?.transforms ?? [];
@@ -30004,18 +30405,18 @@ async function resolveFile(path3, base, { read = readFile11, confine = false, re
30004
30405
  const filename = basename2(full);
30005
30406
  return { bytes, filename, mimeType: mimeTypeOf(filename) };
30006
30407
  }
30007
- function isPlainObject17(value) {
30408
+ function isPlainObject18(value) {
30008
30409
  return typeof value === "object" && value !== null && !Array.isArray(value);
30009
30410
  }
30010
30411
  function fileColumnsOf(model) {
30011
30412
  const out = /* @__PURE__ */ new Set();
30012
- const tables = isPlainObject17(model) ? model["tables"] : void 0;
30013
- if (!isPlainObject17(tables)) return out;
30413
+ const tables = isPlainObject18(model) ? model["tables"] : void 0;
30414
+ if (!isPlainObject18(tables)) return out;
30014
30415
  for (const table of Object.values(tables)) {
30015
- const columns = isPlainObject17(table) ? table["columns"] : void 0;
30016
- if (!isPlainObject17(columns)) continue;
30416
+ const columns = isPlainObject18(table) ? table["columns"] : void 0;
30417
+ if (!isPlainObject18(columns)) continue;
30017
30418
  for (const [name, column] of Object.entries(columns)) {
30018
- const type = typeof column === "string" ? column : isPlainObject17(column) ? column["type"] : void 0;
30419
+ const type = typeof column === "string" ? column : isPlainObject18(column) ? column["type"] : void 0;
30019
30420
  if (type === "file") out.add(name);
30020
30421
  }
30021
30422
  }
@@ -30079,7 +30480,7 @@ function typeOf(value) {
30079
30480
  if (Array.isArray(value)) return "array";
30080
30481
  return typeof value;
30081
30482
  }
30082
- function isPlainObject18(value) {
30483
+ function isPlainObject19(value) {
30083
30484
  return typeof value === "object" && value !== null && !Array.isArray(value);
30084
30485
  }
30085
30486
  function describe6(value) {
@@ -30106,7 +30507,7 @@ function validateShape(shape, path3 = "$") {
30106
30507
  }
30107
30508
  return shape.length === 0 ? [] : validateShape(shape[0], `${path3}[]`);
30108
30509
  }
30109
- if (isPlainObject18(shape)) {
30510
+ if (isPlainObject19(shape)) {
30110
30511
  return Object.entries(shape).flatMap(
30111
30512
  ([key, sub]) => validateShape(sub, `${path3}.${key}`)
30112
30513
  );
@@ -30135,8 +30536,8 @@ function matchShape(value, shape, path3 = "$") {
30135
30536
  if (shape.length === 0) return [];
30136
30537
  return value.flatMap((item, i) => matchShape(item, shape[0], `${path3}[${String(i)}]`));
30137
30538
  }
30138
- if (isPlainObject18(shape)) {
30139
- if (!isPlainObject18(value)) {
30539
+ if (isPlainObject19(shape)) {
30540
+ if (!isPlainObject19(value)) {
30140
30541
  return [`${path3}: expected object, got ${typeOf(value)} (${describe6(value)}).`];
30141
30542
  }
30142
30543
  return Object.entries(shape).flatMap(
@@ -30146,8 +30547,8 @@ function matchShape(value, shape, path3 = "$") {
30146
30547
  return [`${path3}: ${describe6(shape)} is not a shape.`];
30147
30548
  }
30148
30549
  function matchExpect(value, expected, path3 = "$") {
30149
- if (isPlainObject18(expected)) {
30150
- if (!isPlainObject18(value)) {
30550
+ if (isPlainObject19(expected)) {
30551
+ if (!isPlainObject19(value)) {
30151
30552
  return [`${path3}: expected an object, got ${typeOf(value)} (${describe6(value)}).`];
30152
30553
  }
30153
30554
  return Object.entries(expected).flatMap(
@@ -30178,7 +30579,7 @@ function isUiEntry(entry) {
30178
30579
  }
30179
30580
  function parseError(raw, where2) {
30180
30581
  if (raw === true) return {};
30181
- if (!isPlainObject18(raw)) {
30582
+ if (!isPlainObject19(raw)) {
30182
30583
  throw new FixtureError(
30183
30584
  `${where2}: "error" is true, or an object with "type" and/or "contains" \u2014 not ${describe6(raw)}.`
30184
30585
  );
@@ -30196,12 +30597,12 @@ function parseError(raw, where2) {
30196
30597
  }
30197
30598
  var FILE_FIELD = /^[A-Za-z_][A-Za-z0-9_]*$/;
30198
30599
  function parseFiles(raw, payload, where2) {
30199
- if (!isPlainObject18(raw)) {
30600
+ if (!isPlainObject19(raw)) {
30200
30601
  throw new FixtureError(
30201
30602
  `${where2}: "files" maps a payload key to "sample" or a path in the app folder \u2014 not ${describe6(raw)}.`
30202
30603
  );
30203
30604
  }
30204
- if (!isPlainObject18(payload)) {
30605
+ if (!isPlainObject19(payload)) {
30205
30606
  throw new FixtureError(
30206
30607
  `${where2}: a case with "files" puts {"upload": \u2026} under a payload key, so its "payload" is an object.`
30207
30608
  );
@@ -30227,7 +30628,7 @@ function parseFiles(raw, payload, where2) {
30227
30628
  }
30228
30629
  function parseCase(handler, raw, index) {
30229
30630
  const where2 = `handlers.${handler}[${String(index)}]`;
30230
- if (!isPlainObject18(raw)) {
30631
+ if (!isPlainObject19(raw)) {
30231
30632
  throw new FixtureError(`${where2}: a case is an object, not ${describe6(raw)}.`);
30232
30633
  }
30233
30634
  const name = raw["name"];
@@ -30260,11 +30661,11 @@ function parseCase(handler, raw, index) {
30260
30661
  return kase;
30261
30662
  }
30262
30663
  function parseFixtures(raw, source) {
30263
- if (!isPlainObject18(raw)) {
30664
+ if (!isPlainObject19(raw)) {
30264
30665
  throw new FixtureError(`${source}: the fixtures file is a JSON object, not ${describe6(raw)}.`);
30265
30666
  }
30266
30667
  const handlersRaw = raw["handlers"] ?? {};
30267
- if (!isPlainObject18(handlersRaw)) {
30668
+ if (!isPlainObject19(handlersRaw)) {
30268
30669
  throw new FixtureError(`${source}: "handlers" is an object keyed by handler name.`);
30269
30670
  }
30270
30671
  const cases = [];
@@ -30284,7 +30685,7 @@ function parseFixtures(raw, source) {
30284
30685
  });
30285
30686
  }
30286
30687
  const uiRaw = raw["ui"] ?? {};
30287
- if (!isPlainObject18(uiRaw)) {
30688
+ if (!isPlainObject19(uiRaw)) {
30288
30689
  throw new FixtureError(`${source}: "ui" is an object.`);
30289
30690
  }
30290
30691
  const entry = uiRaw["entry"] ?? "index.html";
@@ -30995,7 +31396,7 @@ function nullTexts(document) {
30995
31396
  out.push({ literal: match[0], near: near.length <= NULL_TEXT_CONTEXT ? near : `${near.slice(0, NULL_TEXT_CONTEXT - 1)}\u2026` });
30996
31397
  }
30997
31398
  };
30998
- const own = /* @__PURE__ */ new Map();
31399
+ const own2 = /* @__PURE__ */ new Map();
30999
31400
  const walker = doc.createTreeWalker(
31000
31401
  body,
31001
31402
  4
@@ -31004,9 +31405,9 @@ function nullTexts(document) {
31004
31405
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
31005
31406
  const parent = node.parentElement;
31006
31407
  if (parent === null) continue;
31007
- own.set(parent, (own.get(parent) ?? "") + (node.nodeValue ?? ""));
31408
+ own2.set(parent, (own2.get(parent) ?? "") + (node.nodeValue ?? ""));
31008
31409
  }
31009
- for (const [parent, text3] of own) {
31410
+ for (const [parent, text3] of own2) {
31010
31411
  NULL_LITERAL.lastIndex = 0;
31011
31412
  if (!NULL_LITERAL.test(text3)) continue;
31012
31413
  NULL_LITERAL.lastIndex = 0;
@@ -31937,9 +32338,9 @@ async function runUiSmoke(opts) {
31937
32338
  }
31938
32339
  const takers = how.readers === void 0 ? null : how.readers[name] ?? [];
31939
32340
  if (takers !== null && !refetched.some((call) => takers.some((source) => callsHandler(call.url, source)))) {
31940
- const named12 = takers.length === 0 ? "no source honours it" : `none of ${takers.join(", ")} (the sources that honour it) refetched`;
32341
+ const named13 = takers.length === 0 ? "no source honours it" : `none of ${takers.join(", ")} (the sources that honour it) refetched`;
31941
32342
  return {
31942
- inconclusive: `moving it re-read no source that takes it: ${named12} within ${String(graceMs)}ms, so no transform was asked with it moved`
32343
+ inconclusive: `moving it re-read no source that takes it: ${named13} within ${String(graceMs)}ms, so no transform was asked with it moved`
31943
32344
  };
31944
32345
  }
31945
32346
  return judged;
@@ -32145,13 +32546,13 @@ async function readRoleView(client, appId) {
32145
32546
  problem: `could not read which role you hold on this app (${err instanceof Error ? err.message : String(err)}); only its owner can view its preview as another role`
32146
32547
  };
32147
32548
  }
32148
- const own = typeof mine.role === "string" ? mine.role : null;
32549
+ const own2 = typeof mine.role === "string" ? mine.role : null;
32149
32550
  const roles = Array.isArray(mine.roles) ? mine.roles.filter((one) => typeof one === "string") : [];
32150
- const view = { own, roles, allowed: {} };
32151
- if (own !== null) view.allowed[own] = asAllowed(mine.allowed);
32551
+ const view = { own: own2, roles, allowed: {} };
32552
+ if (own2 !== null) view.allowed[own2] = asAllowed(mine.allowed);
32152
32553
  const failed = [];
32153
32554
  for (const role of roles) {
32154
- if (role === own) continue;
32555
+ if (role === own2) continue;
32155
32556
  try {
32156
32557
  view.allowed[role] = asAllowed((await readAccess(client, appId, role)).allowed);
32157
32558
  } catch {
@@ -32358,47 +32759,47 @@ __export(levers_exports, {
32358
32759
  unboundControls: () => unboundControls2,
32359
32760
  unreadParams: () => unreadParams2
32360
32761
  });
32361
- var isPlainObject19 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
32362
- var named11 = (value) => typeof value === "string" && value !== "";
32762
+ var isPlainObject20 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
32763
+ var named12 = (value) => typeof value === "string" && value !== "";
32363
32764
  function both4(items) {
32364
32765
  if (items.length <= 1) return items.join("");
32365
32766
  return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
32366
32767
  }
32367
32768
  function paramPages2(contract) {
32368
32769
  return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
32369
- (page) => page?.owned !== true && named11(page?.path) && Array.isArray(page.params) && page.params.length > 0
32770
+ (page) => page?.owned !== true && named12(page?.path) && Array.isArray(page.params) && page.params.length > 0
32370
32771
  );
32371
32772
  }
32372
32773
  function namesOf2(page) {
32373
32774
  const out = [];
32374
- for (const param of page.params) if (isPlainObject19(param) && named11(param.name) && !out.includes(param.name)) out.push(param.name);
32775
+ for (const param of page.params) if (isPlainObject20(param) && named12(param.name) && !out.includes(param.name)) out.push(param.name);
32375
32776
  return out;
32376
32777
  }
32377
- function elementsOn2(contract, path3) {
32378
- const elements = isPlainObject19(contract?.elements) ? contract.elements : {};
32778
+ function elementsOn3(contract, path3) {
32779
+ const elements = isPlainObject20(contract?.elements) ? contract.elements : {};
32379
32780
  return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
32380
32781
  }
32381
32782
  function paramModeOf2(element) {
32382
- const props = isPlainObject19(element?.props) ? element.props : {};
32383
- if (element?.type === "control") return named11(props.param) ? props.param : named11(element.binding) ? element.binding : null;
32783
+ const props = isPlainObject20(element?.props) ? element.props : {};
32784
+ if (element?.type === "control") return named12(props.param) ? props.param : named12(element.binding) ? element.binding : null;
32384
32785
  if (element?.type === "field" || element?.type === "action") return null;
32385
- if (named11(props.param) && element?.type !== "custom") return props.param;
32386
- const typed = isPlainObject19(props.props) ? props.props : {};
32387
- return named11(typed.param) ? typed.param : null;
32786
+ if (named12(props.param) && element?.type !== "custom") return props.param;
32787
+ const typed = isPlainObject20(props.props) ? props.props : {};
32788
+ return named12(typed.param) ? typed.param : null;
32388
32789
  }
32389
32790
  function paramsWrittenBy2(element) {
32390
32791
  const out = [];
32391
32792
  const one = paramModeOf2(element);
32392
32793
  if (one !== null) out.push(one);
32393
32794
  if (element?.type === "custom") return out;
32394
- const typed = isPlainObject19(element?.props?.props) ? element.props.props : {};
32395
- if (named11(typed.param_to)) out.push(typed.param_to);
32795
+ const typed = isPlainObject20(element?.props?.props) ? element.props.props : {};
32796
+ if (named12(typed.param_to)) out.push(typed.param_to);
32396
32797
  for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
32397
- if (!isPlainObject19(filter)) continue;
32398
- if (named11(filter.param)) out.push(filter.param);
32399
- if (named11(filter.param_to)) out.push(filter.param_to);
32400
- if (named11(filter.param_min)) out.push(filter.param_min);
32401
- if (named11(filter.param_max)) out.push(filter.param_max);
32798
+ if (!isPlainObject20(filter)) continue;
32799
+ if (named12(filter.param)) out.push(filter.param);
32800
+ if (named12(filter.param_to)) out.push(filter.param_to);
32801
+ if (named12(filter.param_min)) out.push(filter.param_min);
32802
+ if (named12(filter.param_max)) out.push(filter.param_max);
32402
32803
  }
32403
32804
  return out;
32404
32805
  }
@@ -32407,13 +32808,13 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
32407
32808
  for (const page of paramPages2(contract)) {
32408
32809
  const written = /* @__PURE__ */ new Set();
32409
32810
  let unknown = false;
32410
- for (const [, element] of elementsOn2(contract, page.path)) {
32811
+ for (const [, element] of elementsOn3(contract, page.path)) {
32411
32812
  for (const name of paramsWrittenBy2(element)) written.add(name);
32412
32813
  if (element?.type !== "custom") continue;
32413
- const props = isPlainObject19(element.props) ? element.props : {};
32414
- const produces = isPlainObject19(props.declares?.produces) ? props.declares.produces : {};
32415
- for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named11(key)) written.add(key);
32416
- if (!named11(props.module)) continue;
32814
+ const props = isPlainObject20(element.props) ? element.props : {};
32815
+ const produces = isPlainObject20(props.declares?.produces) ? props.declares.produces : {};
32816
+ for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named12(key)) written.add(key);
32817
+ if (!named12(props.module)) continue;
32417
32818
  const source = moduleSource(props.module);
32418
32819
  if (typeof source !== "string") {
32419
32820
  unknown = true;
@@ -32426,8 +32827,8 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
32426
32827
  }
32427
32828
  if (contract?.app?.shell?.nav === "rail") {
32428
32829
  for (const entry of Array.isArray(contract.app.shell.rail?.entries) ? contract.app.shell.rail.entries : []) {
32429
- if (isPlainObject19(entry?.param)) {
32430
- for (const name of Object.keys(entry.param)) if (named11(name)) written.add(name);
32830
+ if (isPlainObject20(entry?.param)) {
32831
+ for (const name of Object.keys(entry.param)) if (named12(name)) written.add(name);
32431
32832
  }
32432
32833
  }
32433
32834
  }
@@ -32444,7 +32845,7 @@ function unboundControls2(contract, files = {}) {
32444
32845
  for (const name of namesOf2(page)) {
32445
32846
  if (written.has(name)) continue;
32446
32847
  const param = page.params.find((one) => one?.name === name) ?? {};
32447
- const kind = named11(param.kind) ? param.kind : null;
32848
+ const kind = named12(param.kind) ? param.kind : null;
32448
32849
  const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
32449
32850
  const problem5 = `${page.path} declares param "${name}"${kind === null ? "" : ` (${kind})`}, and no control on the page writes it \u2014 no part in param mode, no custom that calls ui.setParam("${name}", \u2026) or declares it in declares.produces \u2014 so the page only ever shows ${opens3}.`;
32450
32851
  const fix = `Give it a control \u2014 a part in param mode (props.props.param "${name}": a slider, a number_input, a select, a date_picker or a toggle), or a custom whose module calls ui.setParam("${name}", value) \u2014 or drop the param.`;
@@ -32459,16 +32860,16 @@ function honoursOf2(sources, name) {
32459
32860
  const queue = [name];
32460
32861
  while (queue.length > 0) {
32461
32862
  const one = queue.shift();
32462
- if (seen.has(one) || !isPlainObject19(sources[one])) continue;
32863
+ if (seen.has(one) || !isPlainObject20(sources[one])) continue;
32463
32864
  seen.add(one);
32464
- for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named11(honored) && !out.includes(honored)) out.push(honored);
32865
+ for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named12(honored) && !out.includes(honored)) out.push(honored);
32465
32866
  if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
32466
32867
  }
32467
32868
  return out;
32468
32869
  }
32469
32870
  function unreadParams2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
32470
- const sources = isPlainObject19(contract?.data?.sources) ? contract.data.sources : {};
32471
- const handlers = isPlainObject19(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
32871
+ const sources = isPlainObject20(contract?.data?.sources) ? contract.data.sources : {};
32872
+ const handlers = isPlainObject20(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
32472
32873
  const out = [];
32473
32874
  for (const page of paramPages2(contract)) {
32474
32875
  const read = /* @__PURE__ */ new Set();
@@ -32476,16 +32877,16 @@ function unreadParams2(contract, { moduleSource = () => void 0, handlerSource =
32476
32877
  const modules = [];
32477
32878
  const custom = [];
32478
32879
  let unknown = false;
32479
- for (const [, element] of elementsOn2(contract, page.path)) {
32480
- const props = isPlainObject19(element?.props) ? element.props : {};
32481
- const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named11);
32880
+ for (const [, element] of elementsOn3(contract, page.path)) {
32881
+ const props = isPlainObject20(element?.props) ? element.props : {};
32882
+ const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named12);
32482
32883
  for (const one of reads) {
32483
32884
  if (Object.prototype.hasOwnProperty.call(sources, one)) {
32484
32885
  if (!named_.includes(one)) named_.push(one);
32485
32886
  for (const honored of honoursOf2(sources, one)) read.add(honored);
32486
32887
  } else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
32487
32888
  }
32488
- if (element?.type === "custom" && named11(props.module)) {
32889
+ if (element?.type === "custom" && named12(props.module)) {
32489
32890
  const text3 = moduleSource(props.module);
32490
32891
  if (typeof text3 === "string") modules.push(text3);
32491
32892
  else unknown = true;
@@ -33116,8 +33517,8 @@ function partFindings(runs) {
33116
33517
  if (fresh.length > 0) {
33117
33518
  const seen = unfilled.get(where2);
33118
33519
  const blank4 = [.../* @__PURE__ */ new Set([...seen?.blank ?? [], ...fresh])];
33119
- const named12 = component.props.source;
33120
- const answerer = typeof named12 === "string" && named12 !== "" ? `"${named12}"` : "its source";
33520
+ const named13 = component.props.source;
33521
+ const answerer = typeof named13 === "string" && named13 !== "" ? `"${named13}"` : "its source";
33121
33522
  const finding = {
33122
33523
  code: "callout-unfilled",
33123
33524
  where: where2,
@@ -33455,16 +33856,16 @@ function partCalls3(element) {
33455
33856
  if (entry === void 0) return [];
33456
33857
  const inner = element.props?.["props"];
33457
33858
  const typed = typeof inner === "object" && inner !== null && !Array.isArray(inner) ? inner : {};
33458
- const named12 = (value) => typeof value === "string" && value !== "";
33859
+ const named13 = (value) => typeof value === "string" && value !== "";
33459
33860
  const out = [];
33460
33861
  for (const prop of Object.keys(entry.events ?? {})) {
33461
33862
  const value = typed[prop];
33462
- if (prop !== "on_row_action" && named12(value)) out.push(value);
33863
+ if (prop !== "on_row_action" && named13(value)) out.push(value);
33463
33864
  }
33464
33865
  const actions = typed["row_actions"];
33465
33866
  for (const action of Array.isArray(actions) ? actions : []) {
33466
33867
  const call = typeof action === "object" && action !== null ? action.call : void 0;
33467
- if (named12(call)) out.push(call);
33868
+ if (named13(call)) out.push(call);
33468
33869
  }
33469
33870
  return out;
33470
33871
  }
@@ -33564,14 +33965,14 @@ function roleFindings(contract, held) {
33564
33965
  }
33565
33966
  }
33566
33967
  if (gatedElsewhere === 0 || mine > 0) return [];
33567
- const own = JSON.stringify(held.own);
33968
+ const own2 = JSON.stringify(held.own);
33568
33969
  const whose = others.join(", ");
33569
33970
  return [
33570
33971
  {
33571
33972
  code: "role-sees-nothing",
33572
33973
  where: "backend.roles",
33573
- message: `you hold ${own} \u2014 the app's creator holds the last role declared \u2014 and every one of the ${String(gatedElsewhere)} role-gated components is for ${whose}, so opening the app you see none of them (only the ${String(open2)} ungated)`,
33574
- fix: `Declare backend.roles least senior first, the default first and the role these screens are for last, so the creator holds it; if ${own} really is the most senior, gate those components to it too, or add ${whose} to its sees and to the requiresRole of their handlers.`,
33974
+ message: `you hold ${own2} \u2014 the app's creator holds the last role declared \u2014 and every one of the ${String(gatedElsewhere)} role-gated components is for ${whose}, so opening the app you see none of them (only the ${String(open2)} ungated)`,
33975
+ fix: `Declare backend.roles least senior first, the default first and the role these screens are for last, so the creator holds it; if ${own2} really is the most senior, gate those components to it too, or add ${whose} to its sees and to the requiresRole of their handlers.`,
33575
33976
  see: SEE.roles
33576
33977
  }
33577
33978
  ];
@@ -33940,13 +34341,13 @@ function componentRoles(html) {
33940
34341
  function rendersFor(gated, view, as, pageFor = []) {
33941
34342
  if (as !== void 0) return [as];
33942
34343
  if (view === void 0 || view.own === null) return [null];
33943
- const own = view.own;
33944
- const sees = view.allowed[own] ?? {};
34344
+ const own2 = view.own;
34345
+ const sees = view.allowed[own2] ?? {};
33945
34346
  const extra = gated.filter(
33946
- (role) => role !== own && sees[role] !== true && view.roles.includes(role)
34347
+ (role) => role !== own2 && sees[role] !== true && view.roles.includes(role)
33947
34348
  );
33948
34349
  const declared = pageFor.filter((role) => view.roles.includes(role));
33949
- if (declared.length > 0 && !declared.some((role) => role === own || sees[role] === true)) {
34350
+ if (declared.length > 0 && !declared.some((role) => role === own2 || sees[role] === true)) {
33950
34351
  const first = declared[0];
33951
34352
  return [first, ...extra.filter((role) => role !== first && declared.includes(role))];
33952
34353
  }
@@ -33989,9 +34390,9 @@ function wrapNote(message, width = 92) {
33989
34390
  }
33990
34391
  function heldRole(view) {
33991
34392
  if (view.own === null) return { own: null, sees: [] };
33992
- const own = view.own;
33993
- const allowed = view.allowed[own] ?? {};
33994
- return { own, sees: view.roles.filter((role) => role !== own && allowed[role] === true) };
34393
+ const own2 = view.own;
34394
+ const allowed = view.allowed[own2] ?? {};
34395
+ return { own: own2, sees: view.roles.filter((role) => role !== own2 && allowed[role] === true) };
33995
34396
  }
33996
34397
  function rolesLine(view, as) {
33997
34398
  if (as !== void 0) return `role ${as} (--as), of ${view.roles.join(", ")}
@@ -35371,8 +35772,8 @@ function registerScaffoldCommand(parent) {
35371
35772
  printJson({ id, name, dir, kind, table, record: record2, files: [CONTRACT_FILE], overwritten });
35372
35773
  return;
35373
35774
  }
35374
- const named12 = (opts.server ?? "").trim();
35375
- const server = named12 === "" ? "" : ` -s ${named12}`;
35775
+ const named13 = (opts.server ?? "").trim();
35776
+ const server = named13 === "" ? "" : ` -s ${named13}`;
35376
35777
  process.stdout.write(
35377
35778
  `${id} ${name} (${kind})
35378
35779
  ${dir}