@datarails-ai/dr-appspace-cli 0.10.20 → 0.10.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1383 -784
- 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.10.
|
|
16
|
+
version: "0.10.22",
|
|
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-
|
|
60
|
+
var BUILD_TIME = "2026-10-07T10:52:20.870Z";
|
|
61
61
|
function formatBuildTime(iso) {
|
|
62
62
|
if (iso === "") return "";
|
|
63
63
|
const d = new Date(iso);
|
|
@@ -5952,7 +5952,7 @@ var CHART_FORMATS = Object.freeze(["currency", "number", "percent"]);
|
|
|
5952
5952
|
var PLATFORM_FORMAT = Object.freeze({ "type": "string", "maxLength": 64, "pattern": "^(General|[^\\u0000-\\u001f]*[#0][^\\u0000-\\u001f]*)$" });
|
|
5953
5953
|
var NEGATIVE_MODES = Object.freeze(["minus", "red_minus", "absolute", "red_absolute"]);
|
|
5954
5954
|
var FORMAT_PATTERNS = Object.freeze({ "currency": "$#,###", "number": "#,###", "percent": '#,###.#"%"' });
|
|
5955
|
-
var PLATFORM_NUMBERS = Object.freeze({ "dr-app-runtime": "1.
|
|
5955
|
+
var PLATFORM_NUMBERS = Object.freeze({ "dr-app-runtime": "1.9.0", "dr-app-widgets": "1.2.0", "dr-components": "1.2.0", "dr-components-controls": "1.1.0" });
|
|
5956
5956
|
var ROW_ACTION_WHEN = Object.freeze({ "dr-components": "1.2.0" });
|
|
5957
5957
|
var PROPS_SINCE = Object.freeze({ "select": { "multiple": "1.3.0", "searchable": "1.3.0", "group_by": "1.3.0", "values": "1.3.0", "options[].group": "1.3.0" }, "data_grid": { "theme": "1.3.0", "hierarchy": "1.3.0", "totals": "1.3.0", "pin": "1.3.0", "csv_params": "1.3.0", "columns[].cell": "1.3.0", "columns[].badge_map": "1.3.0", "columns[].status_map": "1.3.0", "columns[].link_field": "1.3.0", "columns[].call": "1.3.0" } });
|
|
5958
5958
|
var MODULE_OF = Object.freeze({ "widget": "dr-app-widgets", "form": "dr-app-runtime", "list": "dr-app-runtime", "detail": "dr-app-runtime", "queue": "dr-app-runtime", "action": "dr-app-runtime", "filter": "dr-app-runtime", "stat": "dr-app-runtime", "chart": "dr-app-runtime", "custom": "dr-app-runtime", "field": "dr-app-runtime", "number_input": "dr-components", "tabs": "dr-components", "tab_panel": "dr-components", "select": "dr-components", "date_picker": "dr-components", "data_grid": "dr-components", "dialog": "dr-components", "variance_table": "dr-components", "toggle": "dr-components", "control_group": "dr-components", "callout": "dr-components", "scenario_columns": "dr-components", "slider": "dr-components-controls", "button": "dr-components", "status": "dr-components", "badge": "dr-components", "tag": "dr-components", "empty_state": "dr-components", "stepper": "dr-components", "alert": "dr-components", "checkbox": "dr-components", "radio_group": "dr-components", "file_upload": "dr-components", "skeleton": "dr-components", "progress": "dr-components", "accordion": "dr-components", "tooltip_info": "dr-components", "filter_bar": "dr-components", "filter_chip": "dr-components", "date_range": "dr-components", "fiscal_month": "dr-components" });
|
|
@@ -6191,6 +6191,10 @@ var THEME_ATTR = "data-dr-theme";
|
|
|
6191
6191
|
var DENSITY_ATTR = "data-dr-density";
|
|
6192
6192
|
var THEME_FILE = "ui/theme.css";
|
|
6193
6193
|
var CARD_ATTR = "data-dr-card";
|
|
6194
|
+
var SHELL_ATTR = "data-dr-shell";
|
|
6195
|
+
var HEADER_ACTIONS_ATTR = "data-dr-header-actions";
|
|
6196
|
+
var TITLE_FROM_ATTR = "data-dr-title-from";
|
|
6197
|
+
var NAV_COUNTS_ATTR = "data-dr-nav-counts";
|
|
6194
6198
|
var EMPHASIS_ATTR = "data-dr-emphasis";
|
|
6195
6199
|
var MUTATING_VERBS = /* @__PURE__ */ new Set(["insert", "update", "delete"]);
|
|
6196
6200
|
var TABLE_ONLY_VERBS = /* @__PURE__ */ new Set([...MUTATING_VERBS, "get"]);
|
|
@@ -6235,8 +6239,10 @@ function extract(app, { describe: describe7 } = {}) {
|
|
|
6235
6239
|
const elements = {};
|
|
6236
6240
|
const pages = [];
|
|
6237
6241
|
const pinsByPath = /* @__PURE__ */ new Map();
|
|
6242
|
+
let shell = null;
|
|
6238
6243
|
for (const page of app.pages) {
|
|
6239
6244
|
const built = readPage(app, page, spec, elements, problems);
|
|
6245
|
+
if (shell === null && built.shell !== null && built.shell !== void 0) shell = built.shell;
|
|
6240
6246
|
if (built.page.owned !== true && !page.text.includes(COMPONENT_ATTR)) {
|
|
6241
6247
|
readBundle(app, page.path, built.page.scripts, elements, problems);
|
|
6242
6248
|
}
|
|
@@ -6299,7 +6305,7 @@ function extract(app, { describe: describe7 } = {}) {
|
|
|
6299
6305
|
v: CONTRACT_V,
|
|
6300
6306
|
generated_by: GENERATED_BY,
|
|
6301
6307
|
source: describe7 ? "static+describe" : "static",
|
|
6302
|
-
app: appSection(app, manifest),
|
|
6308
|
+
app: { ...appSection(app, manifest), ...shell === null ? {} : { shell } },
|
|
6303
6309
|
pins: [...pinsByPath.values()].map((pin) => ({ ...pin, pages: [...new Set(pin.pages)].sort() })).sort((a, b) => a.order - b.order),
|
|
6304
6310
|
pages,
|
|
6305
6311
|
elements,
|
|
@@ -6481,14 +6487,21 @@ function readPage(app, page, spec, elements, problems) {
|
|
|
6481
6487
|
...bands.map((entry, index) => ({ at: entry.node.index, section: banded[index] })),
|
|
6482
6488
|
...wrappers.map((node, index) => ({ at: node.index, section: wrapped[index] }))
|
|
6483
6489
|
].sort((a, b) => a.at - b.at).map((one) => one.section);
|
|
6490
|
+
for (const entry of significant) {
|
|
6491
|
+
if (entry.type !== "region" || entry.node.attrs[HEADER_ACTIONS_ATTR] === void 0) continue;
|
|
6492
|
+
for (const child of childrenOf2.get(entry.node.index) ?? []) elements[uids.get(child)].section = "header";
|
|
6493
|
+
}
|
|
6484
6494
|
const mount = readMount(scripts, problems);
|
|
6485
6495
|
const body = tree.nodes.find((node) => node.tag === "body") ?? tree.nodes.find((node) => node.tag === "html");
|
|
6496
|
+
const main2 = tree.nodes.find((node) => node.tag === "main" && "data-dr-app" in node.attrs) ?? null;
|
|
6486
6497
|
return {
|
|
6498
|
+
shell: shellOf(main2),
|
|
6487
6499
|
page: {
|
|
6488
6500
|
path: page.path,
|
|
6489
6501
|
route: routeOf(app.manifest, page.path),
|
|
6490
6502
|
title: titleOf(tree, page.text),
|
|
6491
6503
|
header: headerOf(tree, page.text),
|
|
6504
|
+
...navOf(tree, main2),
|
|
6492
6505
|
sections,
|
|
6493
6506
|
scripts: scripts.map((script) => script.path).filter((path3) => path3 !== page.path),
|
|
6494
6507
|
theme: body?.attrs[THEME_ATTR] ?? null,
|
|
@@ -6751,20 +6764,43 @@ function headerOf(tree, text3) {
|
|
|
6751
6764
|
}
|
|
6752
6765
|
const region = tree.nodes.find((node) => node.attrs[KIT_ATTR] === "header");
|
|
6753
6766
|
if (!region) return null;
|
|
6754
|
-
const heading = firstTag(tree, region, "h1");
|
|
6767
|
+
const heading = firstTag(tree, region, "h1", true);
|
|
6755
6768
|
if (!heading) return null;
|
|
6756
|
-
const summary = firstTag(tree, region, "p");
|
|
6757
|
-
|
|
6769
|
+
const summary = firstTag(tree, region, "p", true);
|
|
6770
|
+
const out = { title: textOf(text3, heading), summary: summary ? textOf(text3, summary) || null : null };
|
|
6771
|
+
const actions = parseJsonAttr(region.attrs[HEADER_ACTIONS_ATTR]);
|
|
6772
|
+
if (Array.isArray(actions)) out.actions = actions;
|
|
6773
|
+
const from = region.attrs[TITLE_FROM_ATTR];
|
|
6774
|
+
if (from !== void 0 && from !== "") out.title_from = from;
|
|
6775
|
+
return out;
|
|
6758
6776
|
}
|
|
6759
|
-
function firstTag(tree, node, tag) {
|
|
6777
|
+
function firstTag(tree, node, tag, outer = false) {
|
|
6760
6778
|
for (const child of node.children) {
|
|
6761
6779
|
const candidate = tree.nodes[child];
|
|
6762
6780
|
if (candidate.tag === tag) return candidate;
|
|
6763
|
-
|
|
6781
|
+
if (outer && (COMPONENT_ATTR in candidate.attrs || FIELD_ATTR in candidate.attrs)) continue;
|
|
6782
|
+
const found = firstTag(tree, candidate, tag, outer);
|
|
6764
6783
|
if (found) return found;
|
|
6765
6784
|
}
|
|
6766
6785
|
return null;
|
|
6767
6786
|
}
|
|
6787
|
+
function parseJsonAttr(value) {
|
|
6788
|
+
if (value === void 0 || value === "") return void 0;
|
|
6789
|
+
try {
|
|
6790
|
+
return JSON.parse(value);
|
|
6791
|
+
} catch {
|
|
6792
|
+
return void 0;
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
function shellOf(main2) {
|
|
6796
|
+
const shell = parseJsonAttr(main2?.attrs[SHELL_ATTR]);
|
|
6797
|
+
return isPlainObject(shell) ? shell : null;
|
|
6798
|
+
}
|
|
6799
|
+
function navOf(tree, main2) {
|
|
6800
|
+
const nav = tree.nodes.find((node) => node.tag === "nav" && node.attrs[KIT_ATTR] === "nav" && node.attrs[NAV_COUNTS_ATTR] !== void 0);
|
|
6801
|
+
const source = nav?.attrs[NAV_COUNTS_ATTR] ?? main2?.attrs[NAV_COUNTS_ATTR];
|
|
6802
|
+
return source === void 0 || source === "" ? {} : { nav: { counts: { source } } };
|
|
6803
|
+
}
|
|
6768
6804
|
function sectionOf(node, text3, index) {
|
|
6769
6805
|
const title = textOf(text3, node);
|
|
6770
6806
|
return { id: node.attrs.id ?? slug2(title) ?? `section_${index + 1}`, title };
|
|
@@ -9766,6 +9802,48 @@ var contract_schema_default = {
|
|
|
9766
9802
|
},
|
|
9767
9803
|
capabilities: { type: "object" },
|
|
9768
9804
|
data_model: { oneOf: [{ type: "null" }, { $ref: "#/$defs/data_model" }] },
|
|
9805
|
+
shell: {
|
|
9806
|
+
type: "object",
|
|
9807
|
+
additionalProperties: false,
|
|
9808
|
+
description: `OPTIONAL, apps (dr-app-runtime@1.8.0, lib/shell.ts). The app shell: nav "rail" draws the page's nav as a 240px left rail, topbar true draws the product top bar (not when the page is framed). Absent, or nav "tabs" with no topbar, the page is 1.7.1's. Compiled as data-dr-shell (sorted JSON) on every page's <main data-dr-app>; the runtime reads it from the embedded contract.`,
|
|
9809
|
+
properties: {
|
|
9810
|
+
nav: { enum: ["tabs", "rail"], description: "tabs (absent is this): the row of page tabs; rail: the left rail" },
|
|
9811
|
+
topbar: { type: "boolean", description: "the 56px product top bar: nav_label (or name), Help/Settings when a help.html/settings.html page exists, the viewer's name" },
|
|
9812
|
+
rail: {
|
|
9813
|
+
type: "object",
|
|
9814
|
+
additionalProperties: false,
|
|
9815
|
+
properties: {
|
|
9816
|
+
title: { type: "string", description: "a small muted label above the entries" },
|
|
9817
|
+
entries: {
|
|
9818
|
+
type: "array",
|
|
9819
|
+
description: "absent: one entry per page, as the tab row draws them",
|
|
9820
|
+
items: {
|
|
9821
|
+
type: "object",
|
|
9822
|
+
required: ["label"],
|
|
9823
|
+
additionalProperties: false,
|
|
9824
|
+
properties: {
|
|
9825
|
+
label: { type: "string", minLength: 1 },
|
|
9826
|
+
page: { type: "string", description: "a page to link to (ui/requests.html)" },
|
|
9827
|
+
param: { type: "object", description: 'page params this entry sets through the param store ({"folder": "inbox"}); active while the store holds them all' },
|
|
9828
|
+
measure: { type: "string", minLength: 1, description: "a measure of pages[].nav.counts.source, drawn as a count (0 is not drawn)" },
|
|
9829
|
+
role: { type: "string", minLength: 1, description: "dr-app-runtime@1.8.1: the app roles this entry is drawn for, any of them, space-separated like data-dr-role. Absent, the runtime gates an entry with page by that page's pages[].roles, and a param entry by its target page's roles; a viewer none of them allows does not see the entry" }
|
|
9830
|
+
}
|
|
9831
|
+
}
|
|
9832
|
+
},
|
|
9833
|
+
footer: {
|
|
9834
|
+
type: "array",
|
|
9835
|
+
description: "links pinned to the bottom of the rail",
|
|
9836
|
+
items: {
|
|
9837
|
+
type: "object",
|
|
9838
|
+
required: ["label", "page"],
|
|
9839
|
+
additionalProperties: false,
|
|
9840
|
+
properties: { label: { type: "string", minLength: 1 }, page: { type: "string" }, role: { type: "string", minLength: 1, description: "dr-app-runtime@1.8.1: the roles this link is drawn for (space-separated, any of them); absent, the page's pages[].roles gate it" } }
|
|
9841
|
+
}
|
|
9842
|
+
}
|
|
9843
|
+
}
|
|
9844
|
+
}
|
|
9845
|
+
}
|
|
9846
|
+
},
|
|
9769
9847
|
theme: {
|
|
9770
9848
|
oneOf: [
|
|
9771
9849
|
{ type: "null" },
|
|
@@ -9817,7 +9895,9 @@ var contract_schema_default = {
|
|
|
9817
9895
|
description: `The words at the top of the page, as a reader sees them: a dashboard's app bar (.dr-appbar__title over .dr-appbar__summary) or an app's header region (data-dr-kit="header" around an h1 and a p). Not <title>, which is the browser tab. OPTIONAL: a contract written before this key existed omits it.`,
|
|
9818
9896
|
properties: {
|
|
9819
9897
|
title: { type: "string" },
|
|
9820
|
-
summary: { type: ["string", "null"], description: "the one line under the title, or null where the page writes none" }
|
|
9898
|
+
summary: { type: ["string", "null"], description: "the one line under the title, or null where the page writes none" },
|
|
9899
|
+
actions: { type: "array", minItems: 1, items: { type: "string", minLength: 1 }, description: 'OPTIONAL, apps (dr-app-runtime@1.8.0). The ids (props.id) of button or dialog elements on this page drawn as a right-aligned row beside the title, in this order, and not in their band. Compiled inside the header region (data-dr-header-actions), each element with section "header".' },
|
|
9900
|
+
title_from: { enum: ["rail"], description: "OPTIONAL, apps (dr-app-runtime@1.8.0). rail: the title is the active rail entry's label, falling back to title. data-dr-title-from on the header region." }
|
|
9821
9901
|
}
|
|
9822
9902
|
}
|
|
9823
9903
|
]
|
|
@@ -9871,7 +9951,7 @@ var contract_schema_default = {
|
|
|
9871
9951
|
additionalProperties: false,
|
|
9872
9952
|
properties: {
|
|
9873
9953
|
name: { type: "string" },
|
|
9874
|
-
kind: { type: "string", enum: ["period", "multi", "select", "slider", "number", "toggle", "month"] },
|
|
9954
|
+
kind: { type: "string", enum: ["period", "multi", "select", "slider", "number", "toggle", "month", "text", "date"], description: "text (any string) and date (a YYYY-MM-DD day, or '') from dr-app-runtime@1.8.0" },
|
|
9875
9955
|
label: { type: "string", description: "OPTIONAL. Absent falls back to name." },
|
|
9876
9956
|
min: { type: "number", description: "OPTIONAL. slider/number only." },
|
|
9877
9957
|
max: { type: "number", description: "OPTIONAL. slider/number only." },
|
|
@@ -9887,9 +9967,25 @@ var contract_schema_default = {
|
|
|
9887
9967
|
{ properties: { kind: { const: "slider" }, default: { type: "number" } } },
|
|
9888
9968
|
{ properties: { kind: { const: "number" }, default: { type: "number" } } },
|
|
9889
9969
|
{ properties: { kind: { const: "toggle" }, default: { type: "boolean" } } },
|
|
9890
|
-
{ properties: { kind: { const: "month" }, default: { type: "string" } } }
|
|
9970
|
+
{ properties: { kind: { const: "month" }, default: { type: "string" } } },
|
|
9971
|
+
{ properties: { kind: { const: "text" }, default: { type: "string" } } },
|
|
9972
|
+
{ properties: { kind: { const: "date" }, default: { type: "string", pattern: "^([0-9]{4}-[0-9]{2}-[0-9]{2})?$" } } }
|
|
9891
9973
|
]
|
|
9892
9974
|
}
|
|
9975
|
+
},
|
|
9976
|
+
nav: {
|
|
9977
|
+
type: "object",
|
|
9978
|
+
required: ["counts"],
|
|
9979
|
+
additionalProperties: false,
|
|
9980
|
+
description: "OPTIONAL, apps (dr-app-runtime@1.8.0). counts.source is a summary handler the runtime calls once with {} (and again on the page's refresh): its measures fill the rail entries' counts (app.shell.rail.entries[].measure). data-dr-nav-counts on the page's nav (on <main> when it has none).",
|
|
9981
|
+
properties: {
|
|
9982
|
+
counts: {
|
|
9983
|
+
type: "object",
|
|
9984
|
+
required: ["source"],
|
|
9985
|
+
additionalProperties: false,
|
|
9986
|
+
properties: { source: { type: "string", minLength: 1 } }
|
|
9987
|
+
}
|
|
9988
|
+
}
|
|
9893
9989
|
}
|
|
9894
9990
|
}
|
|
9895
9991
|
},
|
|
@@ -11139,11 +11235,11 @@ var MAP = [
|
|
|
11139
11235
|
["--card-foreground", "colour", "--dr-n-900"],
|
|
11140
11236
|
["--primary", "colour", "--dr-accent"],
|
|
11141
11237
|
["--primary-foreground", "contrast", "--dr-accent"],
|
|
11142
|
-
["--secondary", "colour", "--dr-n-
|
|
11238
|
+
["--secondary", "colour", "--dr-n-50"],
|
|
11143
11239
|
["--secondary-foreground", "colour", "--dr-n-700"],
|
|
11144
11240
|
["--muted", "colour", "--dr-n-100"],
|
|
11145
11241
|
["--muted-foreground", "colour", "--dr-n-500"],
|
|
11146
|
-
["--accent", "colour", "--dr-n-
|
|
11242
|
+
["--accent", "colour", "--dr-n-50"],
|
|
11147
11243
|
["--accent-foreground", "colour", "--dr-n-900"],
|
|
11148
11244
|
["--destructive", "colour", "--dr-negative"],
|
|
11149
11245
|
["--destructive-foreground", "contrast", "--dr-negative"],
|
|
@@ -12036,6 +12132,325 @@ function isPlatformChartColour(n, value) {
|
|
|
12036
12132
|
return colour !== "" && (PLATFORM_CHART[n - 1] === colour || APP_CHART_ORDER[n - 1] === colour);
|
|
12037
12133
|
}
|
|
12038
12134
|
|
|
12135
|
+
// ../flows/insights-builder/kit/contract/look.mjs
|
|
12136
|
+
var look_exports = {};
|
|
12137
|
+
__export(look_exports, {
|
|
12138
|
+
HEADER_ACTION_TYPES: () => HEADER_ACTION_TYPES,
|
|
12139
|
+
headerActionProblems: () => headerActionProblems,
|
|
12140
|
+
humanise: () => humanise,
|
|
12141
|
+
layoutProblems: () => layoutProblems,
|
|
12142
|
+
lookPages: () => lookPages,
|
|
12143
|
+
navCountProblems: () => navCountProblems,
|
|
12144
|
+
railReachProblems: () => railReachProblems,
|
|
12145
|
+
rootsOf: () => rootsOf,
|
|
12146
|
+
untitledCards: () => untitledCards
|
|
12147
|
+
});
|
|
12148
|
+
var isPlainObject5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12149
|
+
var named4 = (value) => typeof value === "string" && value !== "";
|
|
12150
|
+
function humanise(name) {
|
|
12151
|
+
const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
|
|
12152
|
+
return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
|
|
12153
|
+
}
|
|
12154
|
+
var WORDING = {
|
|
12155
|
+
custom: { noun: "card", fallback: (p) => [humanise(String(p.module ?? "").replace(/^.*\/|\.js$/g, "")), "its module"] },
|
|
12156
|
+
chart: { noun: "card", fallback: (p) => [humanise(String(p.y ?? "")), "its y column"] },
|
|
12157
|
+
widget: { noun: "card", fallback: (p) => [humanise(named4(p.source) ? p.source : "widget"), "its source"] },
|
|
12158
|
+
stat: { noun: "tile", fallback: (p) => [humanise(String(p.measure ?? "")), "its measure"] },
|
|
12159
|
+
list: { noun: "card", fallback: (p) => [humanise(named4(p.entity) ? p.entity : "list"), "its entity"] },
|
|
12160
|
+
queue: { noun: "card", fallback: (p) => [humanise(named4(p.entity) ? p.entity : "queue"), "its entity"] },
|
|
12161
|
+
form: { noun: "card", fallback: () => ["New", "the runtime\u2019s default"] },
|
|
12162
|
+
dialog: { noun: "dialog" }
|
|
12163
|
+
};
|
|
12164
|
+
var TITLED = Object.fromEntries(
|
|
12165
|
+
TITLED_TYPES.map((type) => [type, { noun: CONTROL_PARTS.includes(type) ? "input" : "card", fallback: () => [humanise(type), "its type"], ...WORDING[type] }])
|
|
12166
|
+
);
|
|
12167
|
+
var LAYOUT = /* @__PURE__ */ new Set(["region", "tabs", "tab_panel"]);
|
|
12168
|
+
function rootsOf(contract, page) {
|
|
12169
|
+
const elements = isPlainObject5(contract?.elements) ? contract.elements : {};
|
|
12170
|
+
if (Array.isArray(page?.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid] !== void 0);
|
|
12171
|
+
const contained = /* @__PURE__ */ new Set();
|
|
12172
|
+
for (const element of Object.values(elements)) for (const child of element?.children ?? []) contained.add(child);
|
|
12173
|
+
return Object.keys(elements).filter((uid) => {
|
|
12174
|
+
const element = elements[uid];
|
|
12175
|
+
return element?.page === page?.path && (element.parent === null || element.parent === void 0) && !contained.has(uid);
|
|
12176
|
+
});
|
|
12177
|
+
}
|
|
12178
|
+
function untitledCards(contract) {
|
|
12179
|
+
const elements = isPlainObject5(contract?.elements) ? contract.elements : {};
|
|
12180
|
+
const out = [];
|
|
12181
|
+
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
12182
|
+
if (page?.owned === true) continue;
|
|
12183
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12184
|
+
const drawn2 = [];
|
|
12185
|
+
const walk4 = (uids) => {
|
|
12186
|
+
for (const uid of uids) {
|
|
12187
|
+
const element = elements[uid];
|
|
12188
|
+
if (element === void 0 || seen.has(uid)) continue;
|
|
12189
|
+
seen.add(uid);
|
|
12190
|
+
if (LAYOUT.has(element.type ?? "")) {
|
|
12191
|
+
const children = element.children?.length ? element.children : Object.keys(elements).filter((other) => elements[other]?.parent === uid);
|
|
12192
|
+
walk4(children);
|
|
12193
|
+
} else {
|
|
12194
|
+
drawn2.push(uid);
|
|
12195
|
+
}
|
|
12196
|
+
}
|
|
12197
|
+
};
|
|
12198
|
+
walk4(rootsOf(contract, page));
|
|
12199
|
+
for (const uid of drawn2) {
|
|
12200
|
+
const element = elements[uid];
|
|
12201
|
+
const type = element.type ?? "";
|
|
12202
|
+
const titled = TITLED[type];
|
|
12203
|
+
if (titled === void 0) continue;
|
|
12204
|
+
const props = isPlainObject5(element.props) ? element.props : {};
|
|
12205
|
+
const typed = isPlainObject5(props.props) ? props.props : {};
|
|
12206
|
+
const card = isPlainObject5(element.card) ? element.card : isPlainObject5(props.card) ? props.card : {};
|
|
12207
|
+
if (named4(props.label) || named4(card.title)) continue;
|
|
12208
|
+
if (type === "dialog" ? named4(typed.title) : named4(typed.label)) continue;
|
|
12209
|
+
const id = named4(element.id) ? element.id : named4(props.id) ? props.id : null;
|
|
12210
|
+
const [title, from] = named4(props.field) ? [humanise(props.field), "its field"] : id !== null ? [humanise(id), "its id"] : titled.fallback(props);
|
|
12211
|
+
const what = id !== null ? `${type} "${id}"` : `the ${type} ${uid}`;
|
|
12212
|
+
const shown2 = title === "" ? "has no title at all" : `is titled "${title}" after ${from}`;
|
|
12213
|
+
out.push({ page: page.path, uid, type, id, noun: titled.noun, title, from, message: `${what} has no label, so its ${titled.noun} ${shown2}` });
|
|
12214
|
+
}
|
|
12215
|
+
}
|
|
12216
|
+
return out;
|
|
12217
|
+
}
|
|
12218
|
+
var BAND_LOOK2 = ["subtitle", "layout", "surface", "aside"];
|
|
12219
|
+
function lookPages(contract) {
|
|
12220
|
+
const elements = isPlainObject5(contract?.elements) ? contract.elements : {};
|
|
12221
|
+
const out = /* @__PURE__ */ new Set();
|
|
12222
|
+
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
12223
|
+
if (page?.owned === true || !named4(page?.path)) continue;
|
|
12224
|
+
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK2.some((key) => named4(section?.[key])));
|
|
12225
|
+
const cards = Object.values(elements).some((element) => element?.page === page.path && (isPlainObject5(element.card) || named4(element.emphasis)));
|
|
12226
|
+
if (bands || cards) out.add(page.path);
|
|
12227
|
+
}
|
|
12228
|
+
return out;
|
|
12229
|
+
}
|
|
12230
|
+
var SPAN_LAYOUTS = /* @__PURE__ */ new Set([void 0, "grid"]);
|
|
12231
|
+
var CONTROL_TYPES = /* @__PURE__ */ new Set(["filter", "control", ...CONTROL_PARTS]);
|
|
12232
|
+
function bandsOf(contract, page) {
|
|
12233
|
+
const elements = isPlainObject5(contract?.elements) ? contract.elements : {};
|
|
12234
|
+
const roots = rootsOf(contract, page);
|
|
12235
|
+
return (Array.isArray(page.sections) ? page.sections : []).filter((section) => named4(section?.id)).map((section) => ({ section, roots: roots.filter((uid) => elements[uid]?.section === section.id) }));
|
|
12236
|
+
}
|
|
12237
|
+
function nameOf3(uid, element) {
|
|
12238
|
+
const id = named4(element?.props?.id) ? element.props.id : named4(element?.id) ? element.id : null;
|
|
12239
|
+
return id === null ? `the ${element?.type ?? "element"} ${uid}` : `${element.type} "${id}"`;
|
|
12240
|
+
}
|
|
12241
|
+
function isControl(element, moduleSource) {
|
|
12242
|
+
const props = isPlainObject5(element?.props) ? element.props : {};
|
|
12243
|
+
if (CONTROL_TYPES.has(element?.type)) return true;
|
|
12244
|
+
if (element?.type !== "custom" && element?.type !== "action" && named4(props.param)) return true;
|
|
12245
|
+
if (isPlainObject5(props.props) && named4(props.props.param)) return true;
|
|
12246
|
+
if (element?.type === "custom" && named4(props.module)) {
|
|
12247
|
+
const source = moduleSource(props.module);
|
|
12248
|
+
return typeof source === "string" && scanUiSetParams(source).length > 0;
|
|
12249
|
+
}
|
|
12250
|
+
return false;
|
|
12251
|
+
}
|
|
12252
|
+
function layoutProblems(contract, { moduleSource = () => void 0 } = {}) {
|
|
12253
|
+
const elements = isPlainObject5(contract?.elements) ? contract.elements : {};
|
|
12254
|
+
const looked = lookPages(contract);
|
|
12255
|
+
const out = [];
|
|
12256
|
+
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
12257
|
+
if (!looked.has(page?.path)) continue;
|
|
12258
|
+
for (const { section, roots } of bandsOf(contract, page)) {
|
|
12259
|
+
const band = `band "${section.id}" on ${page.path}`;
|
|
12260
|
+
const layout = named4(section.layout) ? section.layout : void 0;
|
|
12261
|
+
if (layout === "kpi_strip") {
|
|
12262
|
+
for (const uid of roots) {
|
|
12263
|
+
const element = elements[uid];
|
|
12264
|
+
if (element.type === "stat" || element.type === "widget" && element.props?.kind === "kpi") continue;
|
|
12265
|
+
out.push({
|
|
12266
|
+
rule: "L1",
|
|
12267
|
+
page: page.path,
|
|
12268
|
+
section: section.id,
|
|
12269
|
+
uid,
|
|
12270
|
+
message: `${band} is a kpi_strip, which draws equal KPI tiles, and holds ${nameOf3(uid, element)}; move it to a band of its own, or make it a stat (or a kpi widget)`
|
|
12271
|
+
});
|
|
12272
|
+
}
|
|
12273
|
+
}
|
|
12274
|
+
if (layout === "sidebar" && roots.length > 0) {
|
|
12275
|
+
const uid = section.aside === "start" ? roots[0] : roots[roots.length - 1];
|
|
12276
|
+
if (!isControl(elements[uid], moduleSource)) {
|
|
12277
|
+
out.push({
|
|
12278
|
+
rule: "L2",
|
|
12279
|
+
page: page.path,
|
|
12280
|
+
section: section.id,
|
|
12281
|
+
uid,
|
|
12282
|
+
message: `${band} is a sidebar whose aside (its ${section.aside === "start" ? "first" : "last"} element) is ${nameOf3(uid, elements[uid])}; the narrow column holds the controls that turn the page \u2014 a filter, an input, a part in param mode, a custom that calls ui.setParam \u2014 so put one there, or pick another layout`
|
|
12283
|
+
});
|
|
12284
|
+
}
|
|
12285
|
+
}
|
|
12286
|
+
if (SPAN_LAYOUTS.has(layout)) {
|
|
12287
|
+
let row = [];
|
|
12288
|
+
let used = 0;
|
|
12289
|
+
for (const uid of roots) {
|
|
12290
|
+
const raw = elements[uid]?.grid?.span;
|
|
12291
|
+
const span = raw === null || raw === void 0 ? 12 : Number(raw);
|
|
12292
|
+
if (!Number.isInteger(span) || span < 1 || span > 12) {
|
|
12293
|
+
out.push({ rule: "L4", page: page.path, section: section.id, uid, message: `${band}: ${nameOf3(uid, elements[uid])} spans ${JSON.stringify(raw)}, and a span is a whole number of the twelve columns, 1 to 12` });
|
|
12294
|
+
continue;
|
|
12295
|
+
}
|
|
12296
|
+
if (used + span > 12) {
|
|
12297
|
+
if (used < 12) {
|
|
12298
|
+
const spans = [...row.map((one) => one.span), span];
|
|
12299
|
+
out.push({
|
|
12300
|
+
rule: "L4",
|
|
12301
|
+
page: page.path,
|
|
12302
|
+
section: section.id,
|
|
12303
|
+
uid,
|
|
12304
|
+
message: `${band}: ${row.map((one) => nameOf3(one.uid, elements[one.uid])).join(" + ")} + ${nameOf3(uid, elements[uid])} span ${spans.join(" + ")} = ${spans.reduce((a, b) => a + b, 0)} on one row of twelve, so the last wraps and leaves ${12 - used} column${12 - used === 1 ? "" : "s"} of the row empty; make the row's spans add up to 12`
|
|
12305
|
+
});
|
|
12306
|
+
}
|
|
12307
|
+
row = [];
|
|
12308
|
+
used = 0;
|
|
12309
|
+
}
|
|
12310
|
+
row.push({ uid, span });
|
|
12311
|
+
used += span;
|
|
12312
|
+
if (used === 12) {
|
|
12313
|
+
row = [];
|
|
12314
|
+
used = 0;
|
|
12315
|
+
}
|
|
12316
|
+
}
|
|
12317
|
+
}
|
|
12318
|
+
}
|
|
12319
|
+
}
|
|
12320
|
+
return out;
|
|
12321
|
+
}
|
|
12322
|
+
var HEADER_ACTION_TYPES = ["button", "dialog"];
|
|
12323
|
+
function headerActionProblems(contract) {
|
|
12324
|
+
const elements = isPlainObject5(contract?.elements) ? contract.elements : {};
|
|
12325
|
+
const out = [];
|
|
12326
|
+
for (const [pageIndex, page] of (Array.isArray(contract?.pages) ? contract.pages : []).entries()) {
|
|
12327
|
+
if (page?.owned === true || !isPlainObject5(page?.header) || page.header.actions === void 0) continue;
|
|
12328
|
+
const say = (index, id, uid, message) => out.push({ rule: "L5", code: "header_action_unknown", page: page.path, pageIndex, index, id, uid, message });
|
|
12329
|
+
const actions = page.header.actions;
|
|
12330
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
12331
|
+
say(null, actions, null, `${page.path} states header.actions as ${JSON.stringify(actions)}; it is a list of the ids of the buttons and dialogs drawn beside the title`);
|
|
12332
|
+
continue;
|
|
12333
|
+
}
|
|
12334
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12335
|
+
for (const [index, id] of actions.entries()) {
|
|
12336
|
+
if (!named4(id)) {
|
|
12337
|
+
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`);
|
|
12338
|
+
continue;
|
|
12339
|
+
}
|
|
12340
|
+
if (seen.has(id)) {
|
|
12341
|
+
say(index, id, null, `${page.path}'s header.actions lists "${id}" twice; the runtime draws it once`);
|
|
12342
|
+
continue;
|
|
12343
|
+
}
|
|
12344
|
+
seen.add(id);
|
|
12345
|
+
const hits = Object.keys(elements).filter((uid) => {
|
|
12346
|
+
const element2 = elements[uid];
|
|
12347
|
+
return element2?.page === page.path && element2.type !== "region" && (named4(element2.props?.id) ? element2.props.id : element2.id) === id;
|
|
12348
|
+
});
|
|
12349
|
+
if (hits.length === 0) {
|
|
12350
|
+
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`);
|
|
12351
|
+
continue;
|
|
12352
|
+
}
|
|
12353
|
+
const element = elements[hits[0]];
|
|
12354
|
+
if (!HEADER_ACTION_TYPES.includes(element.type)) {
|
|
12355
|
+
say(index, id, hits[0], `${page.path}'s header.actions names ${nameOf3(hits[0], element)}, and a header action is a ${HEADER_ACTION_TYPES.join(" or a ")}; make it one, or leave it in its band`);
|
|
12356
|
+
}
|
|
12357
|
+
}
|
|
12358
|
+
}
|
|
12359
|
+
return out;
|
|
12360
|
+
}
|
|
12361
|
+
function navCountProblems(contract) {
|
|
12362
|
+
const out = [];
|
|
12363
|
+
const handlers = isPlainObject5(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
12364
|
+
const pages = (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true);
|
|
12365
|
+
const entries = Array.isArray(contract?.app?.shell?.rail?.entries) ? contract.app.shell.rail.entries : [];
|
|
12366
|
+
const measured = entries.map((entry, index) => ({ entry, index })).filter(({ entry }) => named4(entry?.measure));
|
|
12367
|
+
const counted = pages.filter((page) => isPlainObject5(page.nav) && page.nav.counts !== void 0);
|
|
12368
|
+
if (counted.length === 0) {
|
|
12369
|
+
if (measured.length > 0) {
|
|
12370
|
+
out.push({
|
|
12371
|
+
rule: "L6",
|
|
12372
|
+
page: null,
|
|
12373
|
+
where: "app.shell.rail.entries",
|
|
12374
|
+
message: `the rail's entries name measures (${measured.map(({ entry }) => JSON.stringify(entry.measure)).join(", ")}) and no page states nav.counts.source, so no count is ever drawn; state the summary handler on the pages that draw the rail`
|
|
12375
|
+
});
|
|
12376
|
+
}
|
|
12377
|
+
return out;
|
|
12378
|
+
}
|
|
12379
|
+
const measuresChecked = /* @__PURE__ */ new Set();
|
|
12380
|
+
for (const page of counted) {
|
|
12381
|
+
const source = page.nav.counts?.source;
|
|
12382
|
+
const where2 = `${page.path} nav.counts.source`;
|
|
12383
|
+
if (!named4(source)) {
|
|
12384
|
+
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` });
|
|
12385
|
+
continue;
|
|
12386
|
+
}
|
|
12387
|
+
const handler = handlers[source];
|
|
12388
|
+
if (!isPlainObject5(handler) || handler.role !== "summary") {
|
|
12389
|
+
out.push({
|
|
12390
|
+
rule: "L6",
|
|
12391
|
+
page: page.path,
|
|
12392
|
+
where: where2,
|
|
12393
|
+
message: `${where2} is "${source}", which is ${isPlainObject5(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`
|
|
12394
|
+
});
|
|
12395
|
+
continue;
|
|
12396
|
+
}
|
|
12397
|
+
if (!isPlainObject5(handler.measures) || measuresChecked.has(source)) continue;
|
|
12398
|
+
measuresChecked.add(source);
|
|
12399
|
+
for (const { entry, index } of measured) {
|
|
12400
|
+
if (Object.prototype.hasOwnProperty.call(handler.measures, entry.measure)) continue;
|
|
12401
|
+
out.push({
|
|
12402
|
+
rule: "L6",
|
|
12403
|
+
page: page.path,
|
|
12404
|
+
where: `app.shell.rail.entries[${index}].measure`,
|
|
12405
|
+
message: `rail entry "${entry.label}" counts measure "${entry.measure}", which "${source}" (${page.path} nav.counts.source) does not answer (it has ${Object.keys(handler.measures).map((one) => JSON.stringify(one)).join(", ")}), so its count is never drawn`
|
|
12406
|
+
});
|
|
12407
|
+
}
|
|
12408
|
+
}
|
|
12409
|
+
return out;
|
|
12410
|
+
}
|
|
12411
|
+
function appRoles(contract) {
|
|
12412
|
+
const raw = Array.isArray(contract?.backend?.roles) ? contract.backend.roles : [];
|
|
12413
|
+
return raw.map((role) => typeof role === "string" ? { name: role, sees: [] } : { name: role?.name, sees: Array.isArray(role?.sees) ? role.sees.filter(named4) : [] }).filter((role) => named4(role.name));
|
|
12414
|
+
}
|
|
12415
|
+
function railReachProblems(contract) {
|
|
12416
|
+
const out = [];
|
|
12417
|
+
const rail = contract?.app?.shell?.rail;
|
|
12418
|
+
if (!isPlainObject5(rail)) return out;
|
|
12419
|
+
const roles = appRoles(contract);
|
|
12420
|
+
if (roles.length === 0) return out;
|
|
12421
|
+
const declared = new Map(roles.map((role) => [role.name, role]));
|
|
12422
|
+
const pages = new Map((Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named4(page?.path)).map((page) => [page.path, page]));
|
|
12423
|
+
const openers = (page) => Array.isArray(page?.roles) && page.roles.filter(named4).length > 0 ? page.roles.filter(named4) : null;
|
|
12424
|
+
const canOpen = (name, page) => {
|
|
12425
|
+
const open2 = openers(page);
|
|
12426
|
+
if (open2 === null) return true;
|
|
12427
|
+
const role = declared.get(name);
|
|
12428
|
+
return open2.includes(name) || (role?.sees ?? []).some((seen) => open2.includes(seen));
|
|
12429
|
+
};
|
|
12430
|
+
for (const key of ["entries", "footer"]) {
|
|
12431
|
+
for (const [index, entry] of (Array.isArray(rail[key]) ? rail[key] : []).entries()) {
|
|
12432
|
+
if (!isPlainObject5(entry) || !named4(entry.label) || !named4(entry.page)) continue;
|
|
12433
|
+
const page = pages.get(entry.page);
|
|
12434
|
+
if (page === void 0) continue;
|
|
12435
|
+
const where2 = `app.shell.rail.${key}[${index}]`;
|
|
12436
|
+
const viewers = named4(entry.role) ? entry.role.split(/\s+/).filter(named4) : openers(page) ?? roles.map((role) => role.name);
|
|
12437
|
+
const known = viewers.filter((name) => declared.has(name));
|
|
12438
|
+
if (known.length === 0) {
|
|
12439
|
+
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` });
|
|
12440
|
+
continue;
|
|
12441
|
+
}
|
|
12442
|
+
if (!known.some((name) => canOpen(name, page))) {
|
|
12443
|
+
out.push({
|
|
12444
|
+
rule: "L7",
|
|
12445
|
+
where: where2,
|
|
12446
|
+
message: `rail ${key === "footer" ? "footer link" : "entry"} "${entry.label}" links to ${entry.page}, which ${known.map((one) => JSON.stringify(one)).join(", ")} may not open (it is for ${openers(page).map((one) => JSON.stringify(one)).join(", ")}), so the entry is dead; give it the page's roles, or link a page they may open`
|
|
12447
|
+
});
|
|
12448
|
+
}
|
|
12449
|
+
}
|
|
12450
|
+
}
|
|
12451
|
+
return out;
|
|
12452
|
+
}
|
|
12453
|
+
|
|
12039
12454
|
// ../flows/insights-builder/kit/contract/platformBadges.mjs
|
|
12040
12455
|
var PLATFORM_BADGE_COLOURS = Object.freeze([
|
|
12041
12456
|
["--dr-color-additional-blue", "#0061ff"],
|
|
@@ -12092,7 +12507,7 @@ function badgesCss() {
|
|
|
12092
12507
|
}
|
|
12093
12508
|
|
|
12094
12509
|
// ../flows/insights-builder/kit/contract/compileApp.mjs
|
|
12095
|
-
var RUNTIME = "dr-app-runtime@1.
|
|
12510
|
+
var RUNTIME = "dr-app-runtime@1.8.1";
|
|
12096
12511
|
var UI_KIT2 = "ui-kit/v4";
|
|
12097
12512
|
var CHARTS = "echarts@5.6.0";
|
|
12098
12513
|
var FMT = "dr-fmt@2.0.1";
|
|
@@ -12122,12 +12537,12 @@ var REQUIRED_KEYS = {
|
|
|
12122
12537
|
var KEY_SHAPE = {
|
|
12123
12538
|
table: {
|
|
12124
12539
|
reads: 'HANDLER["table"]',
|
|
12125
|
-
ok: (value) =>
|
|
12540
|
+
ok: (value) => named5(value),
|
|
12126
12541
|
says: "the name of the table it is over, as a string"
|
|
12127
12542
|
},
|
|
12128
12543
|
fields: {
|
|
12129
12544
|
reads: 'HANDLER["fields"]',
|
|
12130
|
-
ok: (value) => Array.isArray(value) && value.length > 0 && value.every(
|
|
12545
|
+
ok: (value) => Array.isArray(value) && value.length > 0 && value.every(named5),
|
|
12131
12546
|
says: "a non-empty list of the columns it takes off the payload"
|
|
12132
12547
|
},
|
|
12133
12548
|
// Two spellings. The ARRAY names the values the status column may be moved
|
|
@@ -12137,27 +12552,27 @@ var KEY_SHAPE = {
|
|
|
12137
12552
|
// it was not told to.
|
|
12138
12553
|
decision: {
|
|
12139
12554
|
reads: 'HANDLER["decision"]',
|
|
12140
|
-
ok: (value) => Array.isArray(value) && value.length > 0 && value.every(
|
|
12555
|
+
ok: (value) => Array.isArray(value) && value.length > 0 && value.every(named5) || isPlainObject6(value) && Object.keys(value).length > 0 && Object.keys(value).every(named5) && Object.values(value).every((spec) => isPlainObject6(spec) && named5(spec.to)),
|
|
12141
12556
|
says: "a non-empty list of the values this decision can write, or an object of decision name to {to, note?, set?}"
|
|
12142
12557
|
},
|
|
12143
12558
|
guard: {
|
|
12144
12559
|
reads: 'HANDLER["guard"]',
|
|
12145
|
-
ok: (value) =>
|
|
12560
|
+
ok: (value) => named5(value),
|
|
12146
12561
|
says: "the one state a row may be acted on from, as a string"
|
|
12147
12562
|
},
|
|
12148
12563
|
requires: {
|
|
12149
12564
|
reads: "REQUIRES",
|
|
12150
|
-
ok: (value) =>
|
|
12565
|
+
ok: (value) => isPlainObject6(value) && Object.keys(value).length > 0 && Object.values(value).every((conditions) => isPlainObject6(conditions) && Object.keys(conditions).length > 0),
|
|
12151
12566
|
says: 'an object of ref column to {column of the row it names: the value it must hold, or {"not": the value it must not}}'
|
|
12152
12567
|
},
|
|
12153
12568
|
measures: {
|
|
12154
12569
|
reads: 'HANDLER["measures"].items()',
|
|
12155
|
-
ok: (value) =>
|
|
12570
|
+
ok: (value) => isPlainObject6(value) && Object.keys(value).length > 0,
|
|
12156
12571
|
says: "a non-empty object of measure name to what it answers: an equality filter it counts, or {count: true | sum | avg | avg_days | count_events: true, where}"
|
|
12157
12572
|
},
|
|
12158
12573
|
by: {
|
|
12159
12574
|
reads: 'HANDLER["by"]',
|
|
12160
|
-
ok: (value) =>
|
|
12575
|
+
ok: (value) => named5(value),
|
|
12161
12576
|
says: "the ref column the rows are filtered by, as a string"
|
|
12162
12577
|
},
|
|
12163
12578
|
not_own: {
|
|
@@ -12221,11 +12636,11 @@ function isNumberType(type) {
|
|
|
12221
12636
|
return typeof type === "string" && (NUMBER_TYPES.has(type) || DECIMAL_RE.test(type));
|
|
12222
12637
|
}
|
|
12223
12638
|
function statesTopLevelId(element) {
|
|
12224
|
-
return
|
|
12639
|
+
return isPlainObject6(element) && named5(element.id);
|
|
12225
12640
|
}
|
|
12226
12641
|
function propsId(element) {
|
|
12227
12642
|
const props = element?.props;
|
|
12228
|
-
return
|
|
12643
|
+
return isPlainObject6(props) && named5(props.id) ? props.id : void 0;
|
|
12229
12644
|
}
|
|
12230
12645
|
function foldStatedIds(elements) {
|
|
12231
12646
|
const folding = Object.keys(elements).filter((uid) => {
|
|
@@ -12238,31 +12653,31 @@ function foldStatedIds(elements) {
|
|
|
12238
12653
|
const out = { ...elements };
|
|
12239
12654
|
for (const uid of folding) {
|
|
12240
12655
|
const element = out[uid];
|
|
12241
|
-
out[uid] = { ...element, id: null, props: { id: element.id, ...
|
|
12656
|
+
out[uid] = { ...element, id: null, props: { id: element.id, ...isPlainObject6(element.props) ? element.props : {} } };
|
|
12242
12657
|
}
|
|
12243
12658
|
return out;
|
|
12244
12659
|
}
|
|
12245
12660
|
function roleListOf(element) {
|
|
12246
|
-
const role =
|
|
12661
|
+
const role = isPlainObject6(element?.props) ? element.props.role : void 0;
|
|
12247
12662
|
return Array.isArray(role) ? role : null;
|
|
12248
12663
|
}
|
|
12249
12664
|
function roleListCoversHandler(element, handlers) {
|
|
12250
12665
|
const listed = roleListOf(element);
|
|
12251
|
-
if (listed === null || element?.type !== "action" || !listed.every(
|
|
12666
|
+
if (listed === null || element?.type !== "action" || !listed.every(named5)) return false;
|
|
12252
12667
|
const gate = handlers?.[element.props?.action]?.requiresRole;
|
|
12253
12668
|
return Array.isArray(gate) && gate.length > 0 && gate.every((role) => listed.includes(role));
|
|
12254
12669
|
}
|
|
12255
12670
|
function foldRoleLists(elements, handlers = {}) {
|
|
12256
12671
|
const folding = Object.keys(elements).filter((uid) => {
|
|
12257
12672
|
const listed = roleListOf(elements[uid]);
|
|
12258
|
-
return listed !== null && (listed.length === 1 &&
|
|
12673
|
+
return listed !== null && (listed.length === 1 && named5(listed[0]) || roleListCoversHandler(elements[uid], handlers));
|
|
12259
12674
|
});
|
|
12260
12675
|
if (folding.length === 0) return elements;
|
|
12261
12676
|
const out = { ...elements };
|
|
12262
12677
|
for (const uid of folding) {
|
|
12263
12678
|
const element = out[uid];
|
|
12264
12679
|
const { role, ...rest } = element.props;
|
|
12265
|
-
out[uid] = { ...element, props: role.length === 1 &&
|
|
12680
|
+
out[uid] = { ...element, props: role.length === 1 && named5(role[0]) ? { ...element.props, role: role[0] } : rest };
|
|
12266
12681
|
}
|
|
12267
12682
|
return out;
|
|
12268
12683
|
}
|
|
@@ -12270,7 +12685,7 @@ function roleListProblems(elements, handlers = {}) {
|
|
|
12270
12685
|
const problems = [];
|
|
12271
12686
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
12272
12687
|
const listed = roleListOf(elements[uid]);
|
|
12273
|
-
if (listed === null || listed.length === 1 &&
|
|
12688
|
+
if (listed === null || listed.length === 1 && named5(listed[0]) || roleListCoversHandler(elements[uid], handlers)) continue;
|
|
12274
12689
|
problems.push({
|
|
12275
12690
|
code: "app_model_mismatch",
|
|
12276
12691
|
pointer: `/elements/${escapePointer3(uid)}/props/role`,
|
|
@@ -12282,7 +12697,7 @@ function roleListProblems(elements, handlers = {}) {
|
|
|
12282
12697
|
function foldHeaderRoles(elements) {
|
|
12283
12698
|
const folding = Object.keys(elements).filter((uid) => {
|
|
12284
12699
|
const element = elements[uid];
|
|
12285
|
-
return
|
|
12700
|
+
return isPlainObject6(element) && element.type === "region" && (element.role === void 0 || element.role === null);
|
|
12286
12701
|
});
|
|
12287
12702
|
if (folding.length === 0) return elements;
|
|
12288
12703
|
const out = { ...elements };
|
|
@@ -12292,7 +12707,7 @@ function foldHeaderRoles(elements) {
|
|
|
12292
12707
|
function foldNavRegions(elements) {
|
|
12293
12708
|
const folding = Object.keys(elements).filter((uid) => {
|
|
12294
12709
|
const element = elements[uid];
|
|
12295
|
-
return
|
|
12710
|
+
return isPlainObject6(element) && element.type === "region" && element.role === "nav";
|
|
12296
12711
|
});
|
|
12297
12712
|
if (folding.length === 0) return elements;
|
|
12298
12713
|
const out = { ...elements };
|
|
@@ -12303,8 +12718,8 @@ function foldDialogTriggers(elements) {
|
|
|
12303
12718
|
const words = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
12304
12719
|
const folding = Object.keys(elements).filter((uid) => {
|
|
12305
12720
|
const element = elements[uid];
|
|
12306
|
-
if (!
|
|
12307
|
-
const typed =
|
|
12721
|
+
if (!isPlainObject6(element) || element.type !== "dialog" || !isPlainObject6(element.props)) return false;
|
|
12722
|
+
const typed = isPlainObject6(element.props.props) ? element.props.props : {};
|
|
12308
12723
|
if (words(typed.trigger) || typed.values_from === "row") return false;
|
|
12309
12724
|
return Boolean(words(element.props.label) ?? words(typed.title));
|
|
12310
12725
|
});
|
|
@@ -12312,7 +12727,7 @@ function foldDialogTriggers(elements) {
|
|
|
12312
12727
|
const out = { ...elements };
|
|
12313
12728
|
for (const uid of folding) {
|
|
12314
12729
|
const element = out[uid];
|
|
12315
|
-
const typed =
|
|
12730
|
+
const typed = isPlainObject6(element.props.props) ? element.props.props : {};
|
|
12316
12731
|
const trigger = words(element.props.label) ?? words(typed.title);
|
|
12317
12732
|
out[uid] = { ...element, props: { ...element.props, props: { ...typed, trigger } } };
|
|
12318
12733
|
}
|
|
@@ -12322,10 +12737,10 @@ function headerPropsProblems(elements) {
|
|
|
12322
12737
|
const problems = [];
|
|
12323
12738
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
12324
12739
|
const element = elements[uid];
|
|
12325
|
-
if (!
|
|
12740
|
+
if (!isPlainObject6(element) || element.type !== "region") continue;
|
|
12326
12741
|
if ((element.role ?? "header") !== "header") continue;
|
|
12327
12742
|
if (element.props === void 0 || element.props === null) continue;
|
|
12328
|
-
if (
|
|
12743
|
+
if (isPlainObject6(element.props) && Object.keys(element.props).length === 0) continue;
|
|
12329
12744
|
problems.push({
|
|
12330
12745
|
code: "app_model_mismatch",
|
|
12331
12746
|
pointer: `/elements/${escapePointer3(uid)}/props`,
|
|
@@ -12350,7 +12765,7 @@ function statedIdProblems(elements) {
|
|
|
12350
12765
|
return problems;
|
|
12351
12766
|
}
|
|
12352
12767
|
function specMapProblems(widgets) {
|
|
12353
|
-
if (!
|
|
12768
|
+
if (!isPlainObject6(widgets)) return [];
|
|
12354
12769
|
return Object.keys(widgets).sort(natural3).map((id) => ({
|
|
12355
12770
|
code: "app_model_mismatch",
|
|
12356
12771
|
pointer: `/data/widgets/${escapePointer3(id)}`,
|
|
@@ -12363,14 +12778,14 @@ var SOURCE_TABLE_KEYS = ["where", "order_by", "limit"];
|
|
|
12363
12778
|
function originOf(row) {
|
|
12364
12779
|
const from = row?.from;
|
|
12365
12780
|
if (Array.isArray(from)) return "depends";
|
|
12366
|
-
if (!
|
|
12781
|
+
if (!isPlainObject6(from)) return null;
|
|
12367
12782
|
for (const spelling of ["financeos", "table", "call"]) {
|
|
12368
12783
|
if (Object.prototype.hasOwnProperty.call(from, spelling)) return spelling;
|
|
12369
12784
|
}
|
|
12370
12785
|
return null;
|
|
12371
12786
|
}
|
|
12372
12787
|
function sourcesOf(contract) {
|
|
12373
|
-
return
|
|
12788
|
+
return isPlainObject6(contract?.data?.sources) ? contract.data.sources : {};
|
|
12374
12789
|
}
|
|
12375
12790
|
var FINANCEOS_SCOPES = {
|
|
12376
12791
|
commands: {
|
|
@@ -12414,7 +12829,7 @@ function financeosNeeds(row, origin) {
|
|
|
12414
12829
|
}
|
|
12415
12830
|
return needs;
|
|
12416
12831
|
}
|
|
12417
|
-
const read =
|
|
12832
|
+
const read = isPlainObject6(row.from.financeos) ? row.from.financeos : {};
|
|
12418
12833
|
const filterable = Array.isArray(row.filterable) && row.filterable.length > 0;
|
|
12419
12834
|
if (read.metric_id !== void 0 && read.metric_id !== null) {
|
|
12420
12835
|
if (read.drill_rows) need("data_models", scopesGranting("namespaces", "data_models"));
|
|
@@ -12462,7 +12877,7 @@ function servedSources(elements, sources) {
|
|
|
12462
12877
|
const out = /* @__PURE__ */ new Set();
|
|
12463
12878
|
for (const element of Object.values(elements)) {
|
|
12464
12879
|
const name = element?.props?.source;
|
|
12465
|
-
if (
|
|
12880
|
+
if (named5(name) && Object.prototype.hasOwnProperty.call(sources, name)) out.add(name);
|
|
12466
12881
|
}
|
|
12467
12882
|
return [...out].sort(natural3);
|
|
12468
12883
|
}
|
|
@@ -12474,7 +12889,7 @@ function sourceProblems(sources, app, handlers, extra) {
|
|
|
12474
12889
|
const granted = Array.isArray(financeos) && financeos.length > 0;
|
|
12475
12890
|
for (const name of names) {
|
|
12476
12891
|
const row = sources[name];
|
|
12477
|
-
if (!
|
|
12892
|
+
if (!isPlainObject6(row)) {
|
|
12478
12893
|
problems.push({ code: "source_shape", pointer: at(name), message: `source "${name}" is ${JSON.stringify(row ?? null)}; a source is an object saying where its rows come from (from) and, when it reshapes them, which transform file does it` });
|
|
12479
12894
|
continue;
|
|
12480
12895
|
}
|
|
@@ -12503,7 +12918,7 @@ function sourceProblems(sources, app, handlers, extra) {
|
|
|
12503
12918
|
}
|
|
12504
12919
|
if (origin === "depends") {
|
|
12505
12920
|
for (const [index, dep] of row.from.entries()) {
|
|
12506
|
-
if (!
|
|
12921
|
+
if (!named5(dep) || !Object.prototype.hasOwnProperty.call(sources, dep)) {
|
|
12507
12922
|
problems.push({
|
|
12508
12923
|
code: "source_unknown",
|
|
12509
12924
|
pointer: at(name, `from/${index}`),
|
|
@@ -12560,7 +12975,7 @@ function orderProblems(name, row, at) {
|
|
|
12560
12975
|
}
|
|
12561
12976
|
if (has("items")) {
|
|
12562
12977
|
const labels = Array.isArray(row.items) ? row.items : null;
|
|
12563
|
-
if (labels === null || labels.length === 0 || !labels.every(
|
|
12978
|
+
if (labels === null || labels.length === 0 || !labels.every(named5) || new Set(labels).size !== labels.length) {
|
|
12564
12979
|
say("items", `states items as ${JSON.stringify(row.items)}; items is the breakdown's labels, each once, in the order they are drawn (["Opening ARR", "New", "Churn"])`);
|
|
12565
12980
|
} else if (row.order !== "declared") {
|
|
12566
12981
|
say("items", 'lists items, which are read only with order "declared"; add "order": "declared", or drop items');
|
|
@@ -12586,7 +13001,7 @@ function bridgeFixes(row) {
|
|
|
12586
13001
|
fixes.push('add "items": [the step labels, in order] beside "order": "declared"');
|
|
12587
13002
|
}
|
|
12588
13003
|
} else if (row.order === "source") {
|
|
12589
|
-
if (!transformed && !(origin === "table" &&
|
|
13004
|
+
if (!transformed && !(origin === "table" && named5(row.order_by))) {
|
|
12590
13005
|
order2 = "unpromised";
|
|
12591
13006
|
fixes.push(
|
|
12592
13007
|
origin === "table" ? `add "order_by" naming the column the steps sort by (${unordered} comes back in no promised order), or replace "order": "source" with ${declare}` : `replace "order": "source" with ${declare} \u2014 ${unordered} comes back in no promised order, so its steps could be drawn shuffled`
|
|
@@ -12606,15 +13021,15 @@ function carriedFields(row, model) {
|
|
|
12606
13021
|
if (row.transform !== void 0 && row.transform !== null) return null;
|
|
12607
13022
|
const origin = originOf(row);
|
|
12608
13023
|
if (origin === "financeos") {
|
|
12609
|
-
const read =
|
|
12610
|
-
const measures = Array.isArray(read.measures) ? read.measures :
|
|
13024
|
+
const read = isPlainObject6(row.from.financeos) ? row.from.financeos : {};
|
|
13025
|
+
const measures = Array.isArray(read.measures) ? read.measures : isPlainObject6(read.measure) ? [read.measure] : [];
|
|
12611
13026
|
if (measures.length === 0) return null;
|
|
12612
13027
|
const fields = /* @__PURE__ */ new Set();
|
|
12613
|
-
for (const one of measures) if (
|
|
13028
|
+
for (const one of measures) if (named5(one?.field)) fields.add(one.field);
|
|
12614
13029
|
if (measures.length === 1) fields.add("value");
|
|
12615
|
-
for (const one of Array.isArray(read.dimensions) ? read.dimensions : []) if (
|
|
12616
|
-
const period =
|
|
12617
|
-
if (
|
|
13030
|
+
for (const one of Array.isArray(read.dimensions) ? read.dimensions : []) if (named5(one)) fields.add(one);
|
|
13031
|
+
const period = isPlainObject6(read.period_field) ? read.period_field.field : read.period_field;
|
|
13032
|
+
if (named5(period)) {
|
|
12618
13033
|
fields.add(period);
|
|
12619
13034
|
fields.add("period");
|
|
12620
13035
|
}
|
|
@@ -12632,13 +13047,13 @@ function compareProblems(sources, model) {
|
|
|
12632
13047
|
const at = (name, key) => `/data/sources/${escapePointer3(name)}/${key}`;
|
|
12633
13048
|
for (const name of Object.keys(sources).sort(natural3)) {
|
|
12634
13049
|
const row = sources[name];
|
|
12635
|
-
if (!
|
|
13050
|
+
if (!isPlainObject6(row)) continue;
|
|
12636
13051
|
const say = (key, message) => problems.push({ code: "source_compare_field", pointer: at(name, key), message: `source "${name}" ${message}` });
|
|
12637
|
-
if (row.compare_label !== void 0 && !
|
|
13052
|
+
if (row.compare_label !== void 0 && !named5(row.compare_label)) {
|
|
12638
13053
|
say("compare_label", `states compare_label as ${JSON.stringify(row.compare_label)}; compare_label is the words the compare line says ("budget", "plan")`);
|
|
12639
13054
|
}
|
|
12640
13055
|
if (row.compare === void 0) continue;
|
|
12641
|
-
if (!
|
|
13056
|
+
if (!named5(row.compare)) {
|
|
12642
13057
|
say("compare", `states compare as ${JSON.stringify(row.compare)}; compare names ONE measure field of the source's rows ("budget")`);
|
|
12643
13058
|
continue;
|
|
12644
13059
|
}
|
|
@@ -12646,7 +13061,7 @@ function compareProblems(sources, model) {
|
|
|
12646
13061
|
say("compare", `states compare, which only a source shaped kpi reads (this one answers ${JSON.stringify(row.shape ?? "rows")}); give it shape kpi, or drop compare`);
|
|
12647
13062
|
continue;
|
|
12648
13063
|
}
|
|
12649
|
-
const value =
|
|
13064
|
+
const value = named5(row.value_field) ? row.value_field : "value";
|
|
12650
13065
|
if (row.compare === value) {
|
|
12651
13066
|
say("compare", `compares its figure (${value}) against itself; compare names the OTHER measure (actual against "budget")`);
|
|
12652
13067
|
continue;
|
|
@@ -12700,7 +13115,7 @@ function appIdProblems(app) {
|
|
|
12700
13115
|
}
|
|
12701
13116
|
];
|
|
12702
13117
|
}
|
|
12703
|
-
return appIdLengthProblems(id,
|
|
13118
|
+
return appIdLengthProblems(id, isPlainObject6(app.data_model.tables) ? Object.keys(app.data_model.tables) : []);
|
|
12704
13119
|
}
|
|
12705
13120
|
function appIdLengthProblems(id, tables) {
|
|
12706
13121
|
const bytes = (text3) => new TextEncoder().encode(text3).length;
|
|
@@ -12725,10 +13140,10 @@ function themeProblems(app) {
|
|
|
12725
13140
|
if (theme === void 0 || theme === null) return [];
|
|
12726
13141
|
const at = (key) => `/app/theme${key ? `/${key}` : ""}`;
|
|
12727
13142
|
const refuse = (key, message) => ({ code: "theme_shape", pointer: at(key), message });
|
|
12728
|
-
if (!
|
|
13143
|
+
if (!isPlainObject6(theme)) return [refuse("", `app.theme is ${JSON.stringify(theme)}; it is {"file": "ui/theme.css", "tokens": {"--dr-\u2026": "\u2026"}}, or null for none`)];
|
|
12729
13144
|
const out = [];
|
|
12730
13145
|
if (theme.file !== THEME_FILE2) out.push(refuse("file", `app.theme.file is ${JSON.stringify(theme.file ?? null)}; an app's theme is written to ${THEME_FILE2}, the one file its pages link after runtime.css`));
|
|
12731
|
-
if (!
|
|
13146
|
+
if (!isPlainObject6(theme.tokens)) out.push(refuse("tokens", `app.theme.tokens is ${JSON.stringify(theme.tokens ?? null)}; it is an object of --dr-* tokens to the values the app overrides them with ({} for none)`));
|
|
12732
13147
|
else {
|
|
12733
13148
|
for (const token of Object.keys(theme.tokens).sort(natural3)) {
|
|
12734
13149
|
const value = theme.tokens[token];
|
|
@@ -12762,7 +13177,7 @@ function themeValue(token, tokens, seen = /* @__PURE__ */ new Set()) {
|
|
|
12762
13177
|
return hit ? themeValue(hit[1], tokens, seen) : value;
|
|
12763
13178
|
}
|
|
12764
13179
|
function themeCss(theme) {
|
|
12765
|
-
const tokens =
|
|
13180
|
+
const tokens = isPlainObject6(theme?.tokens) ? theme.tokens : {};
|
|
12766
13181
|
const names = Object.keys(tokens).sort(natural3);
|
|
12767
13182
|
const lines = [
|
|
12768
13183
|
"/* The app's own look, generated from contract.json: edit app.theme, not this",
|
|
@@ -12792,21 +13207,29 @@ function themeCss(theme) {
|
|
|
12792
13207
|
}
|
|
12793
13208
|
function fillTheme(app, pages) {
|
|
12794
13209
|
const theme = app?.theme;
|
|
12795
|
-
if (!
|
|
13210
|
+
if (!isPlainObject6(theme) || theme.preset === void 0 && theme.density === void 0) return { app, pages };
|
|
12796
13211
|
const filled = pages.map((page) => {
|
|
12797
13212
|
if (page?.owned === true) return page;
|
|
12798
13213
|
const out = { ...page };
|
|
12799
|
-
if (theme.preset !== void 0 && !
|
|
12800
|
-
if (theme.density !== void 0 && !
|
|
13214
|
+
if (theme.preset !== void 0 && !named5(page.theme)) out.theme = theme.preset;
|
|
13215
|
+
if (theme.density !== void 0 && !named5(page.density)) out.density = theme.density;
|
|
12801
13216
|
return out;
|
|
12802
13217
|
});
|
|
12803
13218
|
const { preset, density, ...rest } = theme;
|
|
12804
13219
|
return { app: { ...app, theme: rest }, pages: filled };
|
|
12805
13220
|
}
|
|
12806
|
-
var PARAM_KINDS = ["period", "multi", "select", "slider", "number", "toggle", "month"];
|
|
13221
|
+
var PARAM_KINDS = ["period", "multi", "select", "slider", "number", "toggle", "month", "text", "date"];
|
|
12807
13222
|
var NUMERIC_PARAM_KINDS = /* @__PURE__ */ new Set(["slider", "number"]);
|
|
12808
13223
|
var STRING_PARAM_KINDS = /* @__PURE__ */ new Set(["select", "multi", "period", "month"]);
|
|
12809
13224
|
var MONTH_RE = /^\d{4}-\d{2}$/;
|
|
13225
|
+
var DAY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
13226
|
+
function isRealDay(value) {
|
|
13227
|
+
const found = DAY_RE.exec(value);
|
|
13228
|
+
if (found === null) return false;
|
|
13229
|
+
const [year, month, day] = found.slice(1).map(Number);
|
|
13230
|
+
const at = new Date(Date.UTC(year, month - 1, day));
|
|
13231
|
+
return at.getUTCFullYear() === year && at.getUTCMonth() === month - 1 && at.getUTCDate() === day;
|
|
13232
|
+
}
|
|
12810
13233
|
function isStandardParam(name) {
|
|
12811
13234
|
return name === "period" || name === "scenario" || typeof name === "string" && name.startsWith("dimensions:");
|
|
12812
13235
|
}
|
|
@@ -12832,18 +13255,18 @@ function composedOf(sources, name) {
|
|
|
12832
13255
|
const queue = [name];
|
|
12833
13256
|
while (queue.length > 0) {
|
|
12834
13257
|
const one = queue.shift();
|
|
12835
|
-
if (out.has(one) || !
|
|
13258
|
+
if (out.has(one) || !isPlainObject6(sources[one])) continue;
|
|
12836
13259
|
out.add(one);
|
|
12837
|
-
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from.filter(
|
|
13260
|
+
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from.filter(named5));
|
|
12838
13261
|
}
|
|
12839
13262
|
return out;
|
|
12840
13263
|
}
|
|
12841
13264
|
function sourceReaders(contract, sources) {
|
|
12842
13265
|
const out = /* @__PURE__ */ new Map();
|
|
12843
|
-
const elements =
|
|
13266
|
+
const elements = isPlainObject6(contract?.elements) ? contract.elements : {};
|
|
12844
13267
|
for (const element of Object.values(elements)) {
|
|
12845
|
-
const props =
|
|
12846
|
-
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(
|
|
13268
|
+
const props = isPlainObject6(element?.props) ? element.props : {};
|
|
13269
|
+
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named5);
|
|
12847
13270
|
for (const read of reads) {
|
|
12848
13271
|
for (const one of composedOf(sources, read)) {
|
|
12849
13272
|
if (!out.has(one)) out.set(one, /* @__PURE__ */ new Set());
|
|
@@ -12860,7 +13283,7 @@ function paramProblems(contract, sources) {
|
|
|
12860
13283
|
for (const { page, pageIndex, index, param } of declared) {
|
|
12861
13284
|
const at = (key) => `/pages/${pageIndex}/params/${index}${key ? `/${key}` : ""}`;
|
|
12862
13285
|
const where2 = `page ${JSON.stringify(page.path ?? null)}`;
|
|
12863
|
-
if (!
|
|
13286
|
+
if (!isPlainObject6(param) || !named5(param.name)) {
|
|
12864
13287
|
problems.push({ code: "param_kind_value", pointer: at(), message: `${where2} declares a param as ${JSON.stringify(param ?? null)}; a param is {"name": "...", "kind": "..."} with a name` });
|
|
12865
13288
|
continue;
|
|
12866
13289
|
}
|
|
@@ -12890,12 +13313,12 @@ function paramProblems(contract, sources) {
|
|
|
12890
13313
|
});
|
|
12891
13314
|
}
|
|
12892
13315
|
}
|
|
12893
|
-
const names = new Set(declared.filter((one) =>
|
|
13316
|
+
const names = new Set(declared.filter((one) => isPlainObject6(one.param) && named5(one.param.name)).map((one) => one.param.name));
|
|
12894
13317
|
const readers = sourceReaders(contract, sources);
|
|
12895
|
-
const namesOn = (paths) => new Set(declared.filter((one) => paths.has(one.page?.path) &&
|
|
13318
|
+
const namesOn = (paths) => new Set(declared.filter((one) => paths.has(one.page?.path) && isPlainObject6(one.param) && named5(one.param.name)).map((one) => one.param.name));
|
|
12896
13319
|
for (const source of Object.keys(sources).sort(natural3)) {
|
|
12897
13320
|
const row = sources[source];
|
|
12898
|
-
if (!
|
|
13321
|
+
if (!isPlainObject6(row) || row.honors === void 0) continue;
|
|
12899
13322
|
if (!Array.isArray(row.honors)) {
|
|
12900
13323
|
problems.push({ code: "source_shape", pointer: `/data/sources/${escapePointer3(source)}/honors`, message: `source "${source}" states honors as ${JSON.stringify(row.honors)}; honors is a list of the param names the source takes` });
|
|
12901
13324
|
continue;
|
|
@@ -12904,8 +13327,8 @@ function paramProblems(contract, sources) {
|
|
|
12904
13327
|
const scope = pages === null ? names : namesOn(pages);
|
|
12905
13328
|
const which = pages === null ? "no page declares" : `no page that reads it (${[...pages].sort(natural3).map((one) => JSON.stringify(one)).join(", ")}) declares`;
|
|
12906
13329
|
for (const [index, honored] of row.honors.entries()) {
|
|
12907
|
-
if (
|
|
12908
|
-
if (
|
|
13330
|
+
if (named5(honored) && scope.has(honored)) continue;
|
|
13331
|
+
if (named5(honored) && isStandardParam(honored) && scope.size === 0) continue;
|
|
12909
13332
|
problems.push({
|
|
12910
13333
|
code: "param_unknown_honor",
|
|
12911
13334
|
pointer: `/data/sources/${escapePointer3(source)}/honors/${index}`,
|
|
@@ -12950,7 +13373,7 @@ function paramValueProblems(param, said2, at) {
|
|
|
12950
13373
|
}
|
|
12951
13374
|
if (param.values !== void 0) {
|
|
12952
13375
|
if (!STRING_PARAM_KINDS.has(kind)) refuse("values", `states values, which only a select, multi, period or month is held to`);
|
|
12953
|
-
else if (!Array.isArray(param.values) || param.values.length === 0 || !param.values.every(
|
|
13376
|
+
else if (!Array.isArray(param.values) || param.values.length === 0 || !param.values.every(named5)) refuse("values", `states values as ${JSON.stringify(param.values)}; it is a non-empty list of strings (leave it out to take the values from the source's catalog)`);
|
|
12954
13377
|
else if (new Set(param.values).size !== param.values.length) refuse("values", "lists one value twice");
|
|
12955
13378
|
}
|
|
12956
13379
|
if (param.max_selected !== void 0) {
|
|
@@ -12982,6 +13405,8 @@ function paramValueProblems(param, said2, at) {
|
|
|
12982
13405
|
refuse("default", `defaults to ${JSON.stringify(value)}; a ${kind} opens on a string`);
|
|
12983
13406
|
} else if (kind === "month" && !MONTH_RE.test(value)) {
|
|
12984
13407
|
refuse("default", `defaults to ${JSON.stringify(value)}; a month is written YYYY-MM`);
|
|
13408
|
+
} else if (kind === "date" && value !== "" && !isRealDay(value)) {
|
|
13409
|
+
refuse("default", `defaults to ${JSON.stringify(value)}; a date is a real day written YYYY-MM-DD, or '' for none`);
|
|
12985
13410
|
} else if (values !== null && !values.includes(value)) {
|
|
12986
13411
|
refuse("default", `defaults to ${JSON.stringify(value)}, which is not among its values ${JSON.stringify(values)}`);
|
|
12987
13412
|
}
|
|
@@ -12991,8 +13416,8 @@ function fillFilterable(sources) {
|
|
|
12991
13416
|
let out = sources;
|
|
12992
13417
|
for (const name of Object.keys(sources)) {
|
|
12993
13418
|
const row = sources[name];
|
|
12994
|
-
if (!
|
|
12995
|
-
const dims = row.honors.filter((one) =>
|
|
13419
|
+
if (!isPlainObject6(row) || row.filterable !== void 0 || !Array.isArray(row.honors)) continue;
|
|
13420
|
+
const dims = row.honors.filter((one) => named5(one) && one.startsWith("dimensions:")).map((one) => one.slice("dimensions:".length));
|
|
12996
13421
|
if (dims.length === 0) continue;
|
|
12997
13422
|
if (out === sources) out = { ...sources };
|
|
12998
13423
|
out[name] = { ...row, filterable: [...new Set(dims)] };
|
|
@@ -13056,17 +13481,17 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13056
13481
|
const appWidgets = typeof kit?.appWidgets === "string" && kit.appWidgets !== "" ? kit.appWidgets : WIDGETS;
|
|
13057
13482
|
const appComponents = typeof kit?.appComponents === "string" && kit.appComponents !== "" ? kit.appComponents : COMPONENTS;
|
|
13058
13483
|
const appControls = typeof kit?.appControls === "string" && kit.appControls !== "" ? kit.appControls : CONTROLS;
|
|
13059
|
-
if (app.kind === "dashboard" || app.kind !== "app" && !
|
|
13484
|
+
if (app.kind === "dashboard" || app.kind !== "app" && !isPlainObject6(contract?.data?.app)) {
|
|
13060
13485
|
problems.push({
|
|
13061
13486
|
code: "kind_mismatch",
|
|
13062
13487
|
pointer: "/app/kind",
|
|
13063
|
-
message: `app.kind is ${JSON.stringify(app.kind ?? null)} and data.app ${
|
|
13488
|
+
message: `app.kind is ${JSON.stringify(app.kind ?? null)} and data.app ${isPlainObject6(contract?.data?.app) ? "is present" : "is absent"}; compileApp writes App Space apps, which say what their backend does in data.app, and a dashboard is compiled by compile.mjs`
|
|
13064
13489
|
});
|
|
13065
13490
|
return { files: {}, problems, contract };
|
|
13066
13491
|
}
|
|
13067
13492
|
problems.push(...appIdProblems(app));
|
|
13068
|
-
const typed =
|
|
13069
|
-
const gates =
|
|
13493
|
+
const typed = isPlainObject6(contract.elements) ? contract.elements : {};
|
|
13494
|
+
const gates = isPlainObject6(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
13070
13495
|
problems.push(...statedIdProblems(typed), ...roleListProblems(typed, gates));
|
|
13071
13496
|
const declared = foldDialogTriggers(foldNavRegions(foldHeaderRoles(foldRoleLists(foldStatedIds(typed), gates))));
|
|
13072
13497
|
problems.push(...headerPropsProblems(declared));
|
|
@@ -13080,7 +13505,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13080
13505
|
}
|
|
13081
13506
|
const model = modelIndex(app.data_model);
|
|
13082
13507
|
problems.push(...pkProblems(model));
|
|
13083
|
-
const handlers =
|
|
13508
|
+
const handlers = isPlainObject6(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
13084
13509
|
const names = Object.keys(handlers).sort(natural3);
|
|
13085
13510
|
const stated2 = sourcesOf(contract);
|
|
13086
13511
|
const sourceFindings = sourceProblems(stated2, app, handlers, extra);
|
|
@@ -13088,6 +13513,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13088
13513
|
problems.push(...compareProblems(stated2, model));
|
|
13089
13514
|
problems.push(...paramProblems(contract, stated2));
|
|
13090
13515
|
problems.push(...themeProblems(app));
|
|
13516
|
+
problems.push(...shellProblems(contract, typed, handlers, stated2));
|
|
13091
13517
|
const sources = fillFilterable(stated2);
|
|
13092
13518
|
problems.push(...specMapProblems(contract?.data?.widgets));
|
|
13093
13519
|
for (const name of names) {
|
|
@@ -13112,7 +13538,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13112
13538
|
const element = declared[uid];
|
|
13113
13539
|
if (element?.type !== "custom") continue;
|
|
13114
13540
|
const args = element.props?.args;
|
|
13115
|
-
if (args !== void 0 && args !== null && !
|
|
13541
|
+
if (args !== void 0 && args !== null && !isPlainObject6(args)) {
|
|
13116
13542
|
problems.push({
|
|
13117
13543
|
code: "app_model_mismatch",
|
|
13118
13544
|
pointer: `/elements/${escapePointer3(uid)}/props/args`,
|
|
@@ -13121,7 +13547,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13121
13547
|
}
|
|
13122
13548
|
const module = element.props?.module;
|
|
13123
13549
|
const pointer2 = `/elements/${escapePointer3(uid)}/props/module`;
|
|
13124
|
-
if (!
|
|
13550
|
+
if (!named5(module) || !MODULE_RE.test(module)) {
|
|
13125
13551
|
problems.push({
|
|
13126
13552
|
code: "custom_module_path",
|
|
13127
13553
|
pointer: pointer2,
|
|
@@ -13157,7 +13583,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13157
13583
|
message: `"${uid}" sits on ${declared[uid].page}, which is owned: the compiler writes nothing on that page, so it cannot write this \u2014 move the element to a compiled page, or drop owned`
|
|
13158
13584
|
});
|
|
13159
13585
|
}
|
|
13160
|
-
|
|
13586
|
+
let tree = treeOf(declared);
|
|
13161
13587
|
problems.push(...tree.problems);
|
|
13162
13588
|
if (tree.problems.length === 0) problems.push(...byProblems(declared, tree, handlers, model), ...pickerProblems(declared, tree, handlers, model), ...whenProblems(declared, tree, model));
|
|
13163
13589
|
problems.push(...computedProblems(declared, tree, handlers, model));
|
|
@@ -13176,8 +13602,10 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13176
13602
|
if (sources !== stated2) contract = { ...contract, data: { ...contract.data, sources } };
|
|
13177
13603
|
const parented = dropEmptyArgs(fillChartFormats(fillParents(declared, tree), handlers, model));
|
|
13178
13604
|
const headed = fillHeaders(contract, parented);
|
|
13179
|
-
const
|
|
13180
|
-
const
|
|
13605
|
+
const hoisted = hoistHeaderActions(headed.pages, headed.elements);
|
|
13606
|
+
const elements = hoisted.elements;
|
|
13607
|
+
if (hoisted.elements !== headed.elements) tree = treeOf(hoisted.elements);
|
|
13608
|
+
const themed = fillTheme(app, hoisted.pages);
|
|
13181
13609
|
const pages = themed.pages;
|
|
13182
13610
|
if (themed.app !== app) contract = { ...contract, app: themed.app };
|
|
13183
13611
|
const order2 = emittedOrder(contract, elements, pages, tree);
|
|
@@ -13195,7 +13623,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13195
13623
|
}
|
|
13196
13624
|
const params = paramsTable(contract);
|
|
13197
13625
|
for (const name of served) files[`handlers/${name}.py`] = sourceModule(name, sources, transforms, params);
|
|
13198
|
-
const theme =
|
|
13626
|
+
const theme = isPlainObject6(app.theme) ? app.theme : null;
|
|
13199
13627
|
if (theme !== null) files[THEME_FILE2] = themeCss(theme);
|
|
13200
13628
|
files[PALETTE_FILE] = paletteCss();
|
|
13201
13629
|
if (pages.some((page) => page.owned !== true && tokensOn(page, elements))) files[BADGES_FILE] = badgesCss();
|
|
@@ -13221,8 +13649,8 @@ function restate(contract, elements, pages, typed, order2) {
|
|
|
13221
13649
|
}
|
|
13222
13650
|
var DASHBOARD_DATA_KEYS = ["page", "reads", "datasets", "library"];
|
|
13223
13651
|
function dropEmptyDashboardData(data) {
|
|
13224
|
-
if (!
|
|
13225
|
-
const empty = DASHBOARD_DATA_KEYS.filter((key) =>
|
|
13652
|
+
if (!isPlainObject6(data)) return data;
|
|
13653
|
+
const empty = DASHBOARD_DATA_KEYS.filter((key) => isPlainObject6(data[key]) && Object.keys(data[key]).length === 0);
|
|
13226
13654
|
if (empty.length === 0) return data;
|
|
13227
13655
|
const out = { ...data };
|
|
13228
13656
|
for (const key of empty) delete out[key];
|
|
@@ -13251,7 +13679,7 @@ function pageOrder(contract, page, elements, tree) {
|
|
|
13251
13679
|
if (!nested) return;
|
|
13252
13680
|
for (const child of writtenChildren(uid, elements, tree)) visit(child.uid, child.nested);
|
|
13253
13681
|
};
|
|
13254
|
-
for (const band of
|
|
13682
|
+
for (const band of bandsOf2(page, elements, rootsOf2(contract, page, elements, tree))) {
|
|
13255
13683
|
for (const uid of band.roots) visit(uid, true);
|
|
13256
13684
|
}
|
|
13257
13685
|
return out;
|
|
@@ -13260,6 +13688,8 @@ function writtenChildren(uid, elements, tree) {
|
|
|
13260
13688
|
const kids = childrenOf(uid, elements, tree);
|
|
13261
13689
|
if (isPart2(elements[uid])) return kids.map((child) => ({ uid: child, nested: true }));
|
|
13262
13690
|
switch (elements[uid]?.type) {
|
|
13691
|
+
case "region":
|
|
13692
|
+
return (elements[uid].role ?? "header") === "header" ? kids.map((child) => ({ uid: child, nested: true })) : [];
|
|
13263
13693
|
case "form":
|
|
13264
13694
|
return kids.map((child) => ({ uid: child, nested: isPart2(elements[child]) }));
|
|
13265
13695
|
case "detail": {
|
|
@@ -13336,33 +13766,173 @@ function placementProblems(elements, pages, tree, order2) {
|
|
|
13336
13766
|
}
|
|
13337
13767
|
return problems;
|
|
13338
13768
|
}
|
|
13339
|
-
|
|
13340
|
-
|
|
13341
|
-
const
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13769
|
+
var SHELL_NAVS = ["tabs", "rail"];
|
|
13770
|
+
function shellProblems(contract, elements, handlers, sources) {
|
|
13771
|
+
const problems = [];
|
|
13772
|
+
const app = contract?.app ?? {};
|
|
13773
|
+
const pages = Array.isArray(contract?.pages) ? contract.pages : [];
|
|
13774
|
+
const paths = new Set(pages.map((page) => page?.path).filter(named5));
|
|
13775
|
+
const params = new Set(pages.flatMap((page) => (Array.isArray(page?.params) ? page.params : []).map((param) => param?.name)).filter(named5));
|
|
13776
|
+
const refuse = (pointer2, message) => problems.push({ code: "shell_shape", pointer: pointer2, message });
|
|
13777
|
+
const roleNames = new Set(
|
|
13778
|
+
(Array.isArray(contract?.backend?.roles) ? contract.backend.roles : []).map((role) => typeof role === "string" ? role : role?.name).filter(named5)
|
|
13345
13779
|
);
|
|
13346
|
-
if (
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13780
|
+
if (app.shell !== void 0 && app.shell !== null) {
|
|
13781
|
+
const shell = app.shell;
|
|
13782
|
+
if (!isPlainObject6(shell)) refuse("/app/shell", `app.shell is ${JSON.stringify(shell)}; it is {"nav": "tabs"|"rail", "topbar"?: true, "rail"?: {...}}`);
|
|
13783
|
+
else {
|
|
13784
|
+
if (shell.nav !== void 0 && !SHELL_NAVS.includes(shell.nav)) refuse("/app/shell/nav", `app.shell.nav is ${JSON.stringify(shell.nav)}; it is ${either3(SHELL_NAVS)}`);
|
|
13785
|
+
if (shell.topbar !== void 0 && typeof shell.topbar !== "boolean") refuse("/app/shell/topbar", `app.shell.topbar is ${JSON.stringify(shell.topbar)}; it is true or false`);
|
|
13786
|
+
if (shell.rail !== void 0 && !isPlainObject6(shell.rail)) refuse("/app/shell/rail", `app.shell.rail is ${JSON.stringify(shell.rail)}; it is {"title"?, "entries"?: [...], "footer"?: [...]}`);
|
|
13787
|
+
else if (isPlainObject6(shell.rail)) {
|
|
13788
|
+
for (const key of ["entries", "footer"]) {
|
|
13789
|
+
const list3 = shell.rail[key];
|
|
13790
|
+
if (list3 === void 0) continue;
|
|
13791
|
+
if (!Array.isArray(list3)) {
|
|
13792
|
+
refuse(`/app/shell/rail/${key}`, `app.shell.rail.${key} is ${JSON.stringify(list3)}; it is a list of {"label", ...}`);
|
|
13793
|
+
continue;
|
|
13794
|
+
}
|
|
13795
|
+
for (const [index, entry] of list3.entries()) {
|
|
13796
|
+
const at = `/app/shell/rail/${key}/${index}`;
|
|
13797
|
+
if (!isPlainObject6(entry) || !named5(entry.label)) {
|
|
13798
|
+
refuse(at, `app.shell.rail.${key}[${index}] is ${JSON.stringify(entry)}; every entry has a label (the runtime drops one without)`);
|
|
13799
|
+
continue;
|
|
13800
|
+
}
|
|
13801
|
+
if (entry.page !== void 0 && !(named5(entry.page) && paths.has(entry.page))) {
|
|
13802
|
+
refuse(`${at}/page`, `rail entry "${entry.label}" links to ${JSON.stringify(entry.page)}, which pages[] does not declare; name a page of this app (${[...paths].sort(natural3).map((one) => JSON.stringify(one)).join(", ")})`);
|
|
13803
|
+
}
|
|
13804
|
+
if (key === "footer" && !named5(entry.page)) refuse(`${at}/page`, `rail footer entry "${entry.label}" links nowhere; a footer entry is {"label", "page"}`);
|
|
13805
|
+
if (entry.param !== void 0) {
|
|
13806
|
+
if (!isPlainObject6(entry.param) || Object.keys(entry.param).length === 0) {
|
|
13807
|
+
refuse(`${at}/param`, `rail entry "${entry.label}" states param as ${JSON.stringify(entry.param)}; it is {"<page param>": <value>}`);
|
|
13808
|
+
} else {
|
|
13809
|
+
for (const name of Object.keys(entry.param).sort(natural3)) {
|
|
13810
|
+
if (!params.has(name)) refuse(`${at}/param/${escapePointer3(name)}`, `rail entry "${entry.label}" sets the param "${name}", which no page declares in pages[].params; the param store keeps only declared params, so the entry would do nothing`);
|
|
13811
|
+
}
|
|
13812
|
+
}
|
|
13813
|
+
}
|
|
13814
|
+
if (entry.measure !== void 0 && !named5(entry.measure)) refuse(`${at}/measure`, `rail entry "${entry.label}" states measure as ${JSON.stringify(entry.measure)}; it is the name of one measure of the page's nav.counts.source`);
|
|
13815
|
+
if (entry.role !== void 0) {
|
|
13816
|
+
const names = named5(entry.role) ? entry.role.split(/\s+/).filter(named5) : [];
|
|
13817
|
+
const unknown = names.filter((name) => !roleNames.has(name));
|
|
13818
|
+
if (names.length === 0) refuse(`${at}/role`, `rail entry "${entry.label}" states role as ${JSON.stringify(entry.role)}; it is one role name, or several space-separated`);
|
|
13819
|
+
else if (unknown.length > 0) {
|
|
13820
|
+
refuse(
|
|
13821
|
+
`${at}/role`,
|
|
13822
|
+
`rail entry "${entry.label}" is for ${unknown.map((one) => JSON.stringify(one)).join(", ")}, which ${roleNames.size === 0 ? "the app does not declare (it declares no roles)" : `backend.roles does not declare (${[...roleNames].map((one) => JSON.stringify(one)).join(", ")})`}; the runtime would draw it for nobody`
|
|
13823
|
+
);
|
|
13824
|
+
}
|
|
13825
|
+
}
|
|
13826
|
+
}
|
|
13827
|
+
}
|
|
13828
|
+
}
|
|
13829
|
+
}
|
|
13830
|
+
}
|
|
13831
|
+
for (const [index, page] of pages.entries()) {
|
|
13832
|
+
if (!isPlainObject6(page) || page.owned === true) continue;
|
|
13833
|
+
if (isPlainObject6(page.header) && page.header.title_from !== void 0 && page.header.title_from !== "rail") {
|
|
13834
|
+
refuse(`/pages/${index}/header/title_from`, `${page.path}'s header.title_from is ${JSON.stringify(page.header.title_from)}; the one this runtime reads is "rail" (the active rail entry's label)`);
|
|
13835
|
+
}
|
|
13836
|
+
if (page.nav !== void 0) {
|
|
13837
|
+
const source = page.nav?.counts?.source;
|
|
13838
|
+
if (!isPlainObject6(page.nav) || !isPlainObject6(page.nav.counts) || !named5(source)) {
|
|
13839
|
+
refuse(`/pages/${index}/nav`, `${page.path}'s nav is ${JSON.stringify(page.nav)}; it is {"counts": {"source": "<summary handler>"}}`);
|
|
13840
|
+
} else if (!Object.prototype.hasOwnProperty.call(handlers, source) && !Object.prototype.hasOwnProperty.call(sources ?? {}, source)) {
|
|
13841
|
+
refuse(`/pages/${index}/nav/counts/source`, `${page.path}'s nav.counts.source is "${source}", which is not a handler or source of this app; name the summary handler whose measures are the rail's counts`);
|
|
13842
|
+
}
|
|
13843
|
+
}
|
|
13844
|
+
}
|
|
13845
|
+
for (const found of headerActionProblems({ ...contract, elements })) {
|
|
13846
|
+
problems.push({ code: found.code, pointer: `/pages/${found.pageIndex}/header/actions${found.index === null ? "" : `/${found.index}`}`, message: found.message });
|
|
13847
|
+
}
|
|
13848
|
+
for (const [index, page] of pages.entries()) {
|
|
13849
|
+
if (!isPlainObject6(page) || page.owned === true || !Array.isArray(page.header?.actions)) continue;
|
|
13850
|
+
for (const [at, id] of page.header.actions.entries()) {
|
|
13851
|
+
const uid = Object.keys(elements).find((one) => elements[one]?.page === page.path && elements[one].type !== "region" && (named5(elements[one].props?.id) ? elements[one].props.id : elements[one].id) === id);
|
|
13852
|
+
if (uid === void 0) continue;
|
|
13853
|
+
const parent = parentUid(uid, elements);
|
|
13854
|
+
if (parent === null) continue;
|
|
13855
|
+
const holder = elements[parent];
|
|
13856
|
+
if (holder?.type === "region" && (holder.role ?? "header") === "header") continue;
|
|
13857
|
+
problems.push({
|
|
13858
|
+
code: "header_action_unknown",
|
|
13859
|
+
pointer: `/pages/${index}/header/actions/${at}`,
|
|
13860
|
+
message: `${page.path}'s header.actions names "${id}", which sits inside "${parent}" (a ${holder?.type ?? "element"}); a header action is one of the page's top-level elements, so move it out to the page`
|
|
13861
|
+
});
|
|
13862
|
+
}
|
|
13863
|
+
}
|
|
13864
|
+
return problems;
|
|
13865
|
+
}
|
|
13866
|
+
function parentUid(uid, elements) {
|
|
13867
|
+
const stated2 = elements[uid]?.parent ?? null;
|
|
13868
|
+
if (stated2 !== null) return stated2;
|
|
13869
|
+
for (const [other, element] of Object.entries(elements)) {
|
|
13870
|
+
if (Array.isArray(element?.children) && element.children.includes(uid)) return other;
|
|
13871
|
+
}
|
|
13872
|
+
return null;
|
|
13873
|
+
}
|
|
13874
|
+
function hoistHeaderActions(pages, elements) {
|
|
13875
|
+
let out = elements;
|
|
13876
|
+
let outPages = pages;
|
|
13877
|
+
for (const [pageIndex, page] of pages.entries()) {
|
|
13878
|
+
if (page?.owned === true || !Array.isArray(page?.header?.actions) || page.header.actions.length === 0) continue;
|
|
13879
|
+
const header = Object.keys(elements).sort(natural3).find((uid) => elements[uid].page === page.path && elements[uid].type === "region" && (elements[uid].role ?? "header") === "header");
|
|
13880
|
+
if (header === void 0) continue;
|
|
13881
|
+
const moved = [];
|
|
13882
|
+
for (const id of page.header.actions) {
|
|
13883
|
+
const uid = Object.keys(elements).sort(natural3).find((one) => elements[one].page === page.path && elements[one].type !== "region" && (named5(elements[one].props?.id) ? elements[one].props.id : elements[one].id) === id);
|
|
13884
|
+
if (uid !== void 0 && !moved.includes(uid)) moved.push(uid);
|
|
13885
|
+
}
|
|
13886
|
+
const region = out[header];
|
|
13887
|
+
const already = moved.every((uid) => out[uid].parent === header && out[uid].section === "header") && JSON.stringify(region.children ?? []) === JSON.stringify(moved);
|
|
13888
|
+
if (moved.length === 0 || already) continue;
|
|
13889
|
+
if (out === elements) out = { ...elements };
|
|
13890
|
+
for (const uid of moved) out[uid] = { ...out[uid], parent: header, section: "header" };
|
|
13891
|
+
out[header] = { ...region, children: moved };
|
|
13892
|
+
if (Array.isArray(page.tree) && page.tree.some((uid) => moved.includes(uid))) {
|
|
13893
|
+
if (outPages === pages) outPages = [...pages];
|
|
13894
|
+
outPages[pageIndex] = { ...page, tree: page.tree.filter((uid) => !moved.includes(uid)) };
|
|
13895
|
+
}
|
|
13896
|
+
}
|
|
13897
|
+
return { elements: out, pages: outPages };
|
|
13898
|
+
}
|
|
13899
|
+
function shellAttr(app) {
|
|
13900
|
+
return isPlainObject6(app?.shell) ? ` data-dr-shell="${attr(JSON.stringify(canonical2(app.shell)))}"` : "";
|
|
13901
|
+
}
|
|
13902
|
+
function navCountsAttr(page) {
|
|
13903
|
+
const source = page?.nav?.counts?.source;
|
|
13904
|
+
return named5(source) ? ` data-dr-nav-counts="${attr(source)}"` : "";
|
|
13905
|
+
}
|
|
13906
|
+
function hasNav(page, elements) {
|
|
13907
|
+
return Object.values(elements).some((element) => element?.page === page.path && element.type === "nav");
|
|
13908
|
+
}
|
|
13909
|
+
function fillHeaders(contract, elements) {
|
|
13910
|
+
const pages = Array.isArray(contract.pages) ? contract.pages : [];
|
|
13911
|
+
const wanted = pages.filter(
|
|
13912
|
+
(page) => page.owned !== true && isPlainObject6(page.header) && !Object.values(elements).some(
|
|
13913
|
+
(element) => element.page === page.path && element.type === "region" && (element.role ?? "header") === "header"
|
|
13914
|
+
)
|
|
13915
|
+
);
|
|
13916
|
+
if (wanted.length === 0) return { elements, pages };
|
|
13917
|
+
const filled = { ...elements };
|
|
13918
|
+
const out = pages.map((page) => {
|
|
13919
|
+
if (!wanted.includes(page)) return page;
|
|
13920
|
+
let uid = `${page.path}#0`;
|
|
13921
|
+
while (filled[uid] !== void 0) uid += "a";
|
|
13922
|
+
filled[uid] = { uid, page: page.path, type: "region", role: "header", parent: null, children: [] };
|
|
13923
|
+
return Array.isArray(page.tree) && page.tree.length > 0 ? { ...page, tree: [uid, ...page.tree] } : page;
|
|
13924
|
+
});
|
|
13925
|
+
return { elements: filled, pages: out };
|
|
13926
|
+
}
|
|
13927
|
+
function modelIndex(document) {
|
|
13928
|
+
const tables = {};
|
|
13929
|
+
const raw = isPlainObject6(document?.tables) ? document.tables : {};
|
|
13930
|
+
for (const [name, table] of Object.entries(raw)) {
|
|
13931
|
+
const columns = {};
|
|
13932
|
+
for (const [column, spec] of Object.entries(isPlainObject6(table?.columns) ? table.columns : {})) {
|
|
13933
|
+
const held = typeof spec === "string" ? { type: spec } : isPlainObject6(spec) ? spec : {};
|
|
13934
|
+
const pk = Boolean(held.pk);
|
|
13935
|
+
columns[column] = {
|
|
13366
13936
|
name: column,
|
|
13367
13937
|
type: typeof held.type === "string" ? held.type : held.enum ? "text" : "text",
|
|
13368
13938
|
declaredType: typeof held.type === "string",
|
|
@@ -13374,7 +13944,7 @@ function modelIndex(document) {
|
|
|
13374
13944
|
// On a ref column: which column of the target a reader sees instead
|
|
13375
13945
|
// of the id. Validated in `pickerProblems`, not here — a model is
|
|
13376
13946
|
// read as written and refused with a pointer, never silently fixed.
|
|
13377
|
-
display:
|
|
13947
|
+
display: named5(held.display) ? held.display : null,
|
|
13378
13948
|
// A child total the platform selects with the row (`computedProblems`
|
|
13379
13949
|
// holds it to its grammar), and the column it should agree with.
|
|
13380
13950
|
// Read as written, like `display`.
|
|
@@ -13397,7 +13967,7 @@ function modelIndex(document) {
|
|
|
13397
13967
|
// What that one row reads as before it is first saved. Read as written;
|
|
13398
13968
|
// the platform's parser is what holds each value to its column.
|
|
13399
13969
|
// Only on a singleton, as the platform's parser and the schema have it.
|
|
13400
|
-
defaults: table?.singleton === true &&
|
|
13970
|
+
defaults: table?.singleton === true && isPlainObject6(table?.defaults) ? table.defaults : null,
|
|
13401
13971
|
columns,
|
|
13402
13972
|
indexes: Array.isArray(table?.indexes) ? table.indexes.map((index) => Array.isArray(index) ? index.map(String) : [String(index)]) : []
|
|
13403
13973
|
};
|
|
@@ -13483,7 +14053,7 @@ function fillParents(elements, tree) {
|
|
|
13483
14053
|
function entityOf(uid, elements, tree) {
|
|
13484
14054
|
for (let current = uid; current !== null && current !== void 0; current = parentOf(tree, current)) {
|
|
13485
14055
|
const value = elements[current]?.props?.entity;
|
|
13486
|
-
if (
|
|
14056
|
+
if (named5(value)) return value;
|
|
13487
14057
|
if (!elements[current]) return null;
|
|
13488
14058
|
}
|
|
13489
14059
|
return null;
|
|
@@ -13514,11 +14084,11 @@ function historyColumnsInWords() {
|
|
|
13514
14084
|
return `History columns are ${lead.join(", ")}${rest.length > 0 ? ` (also ${rest.join(", ")})` : ""}`;
|
|
13515
14085
|
}
|
|
13516
14086
|
function historyFieldProblem(uid, type, props, elements, tree) {
|
|
13517
|
-
if (type !== "field" || !
|
|
14087
|
+
if (type !== "field" || !named5(props.entity) || props.entity === "_events") return null;
|
|
13518
14088
|
const list3 = historyListAround(uid, elements, tree);
|
|
13519
14089
|
if (list3 === null) return null;
|
|
13520
|
-
const listId =
|
|
13521
|
-
const shows =
|
|
14090
|
+
const listId = named5(elements[list3]?.props?.id) ? elements[list3].props.id : list3;
|
|
14091
|
+
const shows = named5(props.field) ? ` shows "${props.field}" and` : "";
|
|
13522
14092
|
return {
|
|
13523
14093
|
code: "app_model_mismatch",
|
|
13524
14094
|
pointer: `/elements/${escapePointer3(uid)}/props/entity`,
|
|
@@ -13527,9 +14097,9 @@ function historyFieldProblem(uid, type, props, elements, tree) {
|
|
|
13527
14097
|
}
|
|
13528
14098
|
function decideButtonProblem(uid, type, props, handlers) {
|
|
13529
14099
|
if (type !== "action" && type !== "detail") return null;
|
|
13530
|
-
if (!
|
|
14100
|
+
if (!named5(props.action) || !Object.prototype.hasOwnProperty.call(handlers, props.action)) return null;
|
|
13531
14101
|
const row = handlers[props.action] ?? {};
|
|
13532
|
-
if (row.role !== "decide" || !
|
|
14102
|
+
if (row.role !== "decide" || !isPlainObject6(row.decision)) return null;
|
|
13533
14103
|
const names = decisionNames(row);
|
|
13534
14104
|
if (names.length < 2) return null;
|
|
13535
14105
|
const split2 = names.map((one) => `${props.action}_${one} with {"${one}": {...}}`).join(" and ");
|
|
@@ -13552,7 +14122,7 @@ function modelProblems(elements, tree, handlers, model, roles, sources = {}) {
|
|
|
13552
14122
|
problems.push(inHistory);
|
|
13553
14123
|
continue;
|
|
13554
14124
|
}
|
|
13555
|
-
if ((type === "chart" || type === "custom" || type === "widget") &&
|
|
14125
|
+
if ((type === "chart" || type === "custom" || type === "widget") && named5(props.entity)) {
|
|
13556
14126
|
const answers = type === "widget" ? "its payload is whatever its source answers" : "its rows are whatever its source answers";
|
|
13557
14127
|
problems.push({
|
|
13558
14128
|
code: "app_model_mismatch",
|
|
@@ -13571,25 +14141,25 @@ function modelProblems(elements, tree, handlers, model, roles, sources = {}) {
|
|
|
13571
14141
|
say(uid, `${type} "${uid}" names entity "${entity}", which data_model declares no table for`);
|
|
13572
14142
|
continue;
|
|
13573
14143
|
}
|
|
13574
|
-
if (
|
|
14144
|
+
if (named5(props.field) && entity !== null && isPlatformTable(entity)) {
|
|
13575
14145
|
if (!hasColumn(model, entity, props.field)) {
|
|
13576
14146
|
say(
|
|
13577
14147
|
uid,
|
|
13578
14148
|
props.field === "changes" ? `${type} "${uid}" shows "changes", which is a jsonb cell a reader would see as [object Object] \u2014 show "summary", which is changes in words` : `${type} "${uid}" names field "${props.field}", which is no column the platform answers ${entity} with (${PLATFORM_TABLES[entity].join(", ")})`
|
|
13579
14149
|
);
|
|
13580
14150
|
}
|
|
13581
|
-
} else if (
|
|
14151
|
+
} else if (named5(props.field)) {
|
|
13582
14152
|
if (entity === null) say(uid, `${type} "${uid}" names field "${props.field}" and sits under no entity, so no column can answer for it`);
|
|
13583
14153
|
else if (!hasColumn(model, entity, props.field)) {
|
|
13584
14154
|
const history = PLATFORM_TABLES._events.includes(props.field) ? `; "${props.field}" is a column of a row's history (_events), which only a list over "_events" sourced by a handler with role "history" shows, inside the row's detail` : "";
|
|
13585
14155
|
say(uid, `${type} "${uid}" names field "${props.field}", which is no column of table "${entity}", nor one the platform injects${history}`);
|
|
13586
14156
|
}
|
|
13587
14157
|
}
|
|
13588
|
-
for (const column of Object.keys(
|
|
14158
|
+
for (const column of Object.keys(isPlainObject6(props.filter) ? props.filter : {}).sort()) {
|
|
13589
14159
|
if (entity !== null && !hasColumn(model, entity, column)) say(uid, `${type} "${uid}" filters on "${column}", which is no column of table "${entity}"`);
|
|
13590
14160
|
}
|
|
13591
14161
|
for (const key of ["source", "action"]) {
|
|
13592
|
-
if (!
|
|
14162
|
+
if (!named5(props[key])) continue;
|
|
13593
14163
|
if ((type === "chart" || type === "widget") && !stated(props[key])) continue;
|
|
13594
14164
|
if (Object.prototype.hasOwnProperty.call(handlers, props[key])) continue;
|
|
13595
14165
|
if (key === "source" && Object.prototype.hasOwnProperty.call(sources, props[key])) continue;
|
|
@@ -13658,7 +14228,7 @@ function modelProblems(elements, tree, handlers, model, roles, sources = {}) {
|
|
|
13658
14228
|
}
|
|
13659
14229
|
}
|
|
13660
14230
|
problems.push(...platformQueryProblems(name, row, model.tables[row.table]));
|
|
13661
|
-
const ambiguous =
|
|
14231
|
+
const ambiguous = named5(row.guard) && ["decide", "remove", "save"].includes(row.role) ? ambiguousStatus(name, row, model) : null;
|
|
13662
14232
|
if (ambiguous !== null) {
|
|
13663
14233
|
problems.push(ambiguous);
|
|
13664
14234
|
} else if (row.role === "decide" && statusColumn(model, row) === null) {
|
|
@@ -13667,17 +14237,17 @@ function modelProblems(elements, tree, handlers, model, roles, sources = {}) {
|
|
|
13667
14237
|
pointer: `/data/app/handlers/${escapePointer3(name)}/guard`,
|
|
13668
14238
|
message: `handler "${name}" decides ${JSON.stringify(decisionTargets(row))} from "${row.guard}", and table "${row.table}" has no column whose enum holds both; a decision needs a status column to move`
|
|
13669
14239
|
});
|
|
13670
|
-
} else if (row.role === "decide" &&
|
|
14240
|
+
} else if (row.role === "decide" && isPlainObject6(row.decision)) {
|
|
13671
14241
|
problems.push(...decisionProblems(name, row, model.tables[row.table], statusColumn(model, row)));
|
|
13672
14242
|
}
|
|
13673
|
-
if (ambiguous === null && row.role === "save" &&
|
|
14243
|
+
if (ambiguous === null && row.role === "save" && named5(row.guard) && statusColumn(model, row) !== null && Array.isArray(row.fields) && row.fields.includes(statusColumn(model, row))) {
|
|
13674
14244
|
problems.push({
|
|
13675
14245
|
code: "app_model_mismatch",
|
|
13676
14246
|
pointer: `/data/app/handlers/${escapePointer3(name)}/fields`,
|
|
13677
14247
|
message: `handler "${name}" guards its edits on "${row.guard}" and also takes "${statusColumn(model, row)}" off the payload, so an edit could move the row to any state; drop "${statusColumn(model, row)}" from fields and move the row with a decide`
|
|
13678
14248
|
});
|
|
13679
14249
|
}
|
|
13680
|
-
if (ambiguous === null && (row.role === "remove" || row.role === "save") &&
|
|
14250
|
+
if (ambiguous === null && (row.role === "remove" || row.role === "save") && named5(row.guard) && statusColumn(model, row) === null) {
|
|
13681
14251
|
problems.push({
|
|
13682
14252
|
code: "app_model_mismatch",
|
|
13683
14253
|
pointer: `/data/app/handlers/${escapePointer3(name)}/guard`,
|
|
@@ -13686,7 +14256,7 @@ function modelProblems(elements, tree, handlers, model, roles, sources = {}) {
|
|
|
13686
14256
|
}
|
|
13687
14257
|
if (!misKeyed && row.requires !== void 0) problems.push(...requiresProblems(name, row, model));
|
|
13688
14258
|
if (row.role === "summary") problems.push(...measureProblems(name, row, model, handlers));
|
|
13689
|
-
if ((row.role === "list" || row.role === "inbox") &&
|
|
14259
|
+
if ((row.role === "list" || row.role === "inbox") && isPlainObject6(row.where)) {
|
|
13690
14260
|
problems.push(...whereProblems(row.where, model.tables[row.table], (key) => `/data/app/handlers/${escapePointer3(name)}/where/${escapePointer3(key)}`, `handler "${name}"`));
|
|
13691
14261
|
}
|
|
13692
14262
|
if (!misKeyed && row.not_own === true) {
|
|
@@ -13723,20 +14293,20 @@ function platformQueryProblems(name, row, table) {
|
|
|
13723
14293
|
message: `handler "${name}" ${key === "order_by" ? "sorts" : "filters"} on platform column "${column}", and ${why}`
|
|
13724
14294
|
});
|
|
13725
14295
|
};
|
|
13726
|
-
if (
|
|
14296
|
+
if (named5(row.order_by)) {
|
|
13727
14297
|
const column = row.order_by.replace(/^-+/, "");
|
|
13728
14298
|
if (column.startsWith("_") && !allowed.has(column)) refuse("order_by", column);
|
|
13729
14299
|
}
|
|
13730
|
-
for (const key of Object.keys(
|
|
14300
|
+
for (const key of Object.keys(isPlainObject6(row.where) ? row.where : {}).sort()) {
|
|
13731
14301
|
const column = key.split("__")[0];
|
|
13732
14302
|
if (column.startsWith("_") && !allowed.has(column)) refuse("where", column);
|
|
13733
14303
|
}
|
|
13734
14304
|
return problems;
|
|
13735
14305
|
}
|
|
13736
14306
|
function linkedParent(row, model) {
|
|
13737
|
-
if (!
|
|
14307
|
+
if (!named5(row?.by) || !BY_ROLES.includes(row.role)) return null;
|
|
13738
14308
|
const column = model.tables[row.table]?.columns?.[row.by];
|
|
13739
|
-
if (!
|
|
14309
|
+
if (!named5(column?.ref)) return null;
|
|
13740
14310
|
const target = String(column.ref).split(".")[0];
|
|
13741
14311
|
return Object.prototype.hasOwnProperty.call(model.tables, target) ? target : null;
|
|
13742
14312
|
}
|
|
@@ -13758,13 +14328,13 @@ function byProblems(elements, tree, handlers, model) {
|
|
|
13758
14328
|
say(`handler "${name}" states by as ${JSON.stringify(row.by)}; state by as ${KEY_SHAPE.by.says}`);
|
|
13759
14329
|
continue;
|
|
13760
14330
|
}
|
|
13761
|
-
if (!
|
|
14331
|
+
if (!named5(row.table) || !Object.prototype.hasOwnProperty.call(model.tables, row.table)) continue;
|
|
13762
14332
|
const column = model.tables[row.table].columns[row.by];
|
|
13763
14333
|
if (column === void 0) {
|
|
13764
14334
|
say(`handler "${name}" is filtered by "${row.by}", which is no column of table "${row.table}"`);
|
|
13765
14335
|
continue;
|
|
13766
14336
|
}
|
|
13767
|
-
if (!
|
|
14337
|
+
if (!named5(column.ref)) {
|
|
13768
14338
|
say(
|
|
13769
14339
|
`handler "${name}" is filtered by "${row.by}", and "${row.table}.${row.by}" is not a ref: by is a link to the parent row, so the column has to be a ref to the parent table (e.g. "ref": "invoices.id")`
|
|
13770
14340
|
);
|
|
@@ -13791,7 +14361,7 @@ function byProblems(elements, tree, handlers, model) {
|
|
|
13791
14361
|
const element = elements[uid] ?? {};
|
|
13792
14362
|
for (const key of ["source", "action"]) {
|
|
13793
14363
|
const name = element.props?.[key];
|
|
13794
|
-
if (!
|
|
14364
|
+
if (!named5(name) || !linked.has(name)) continue;
|
|
13795
14365
|
const target = linked.get(name);
|
|
13796
14366
|
const by = handlers[name].by;
|
|
13797
14367
|
if (element.type === "field" && key === "source") {
|
|
@@ -13851,7 +14421,7 @@ function historyProblems(elements, tree, handlers, model) {
|
|
|
13851
14421
|
if (row.limit !== void 0 && !(Number.isInteger(row.limit) && row.limit > 0)) {
|
|
13852
14422
|
say("limit", `handler "${name}" states limit as ${JSON.stringify(row.limit)}; state limit as a positive whole number, or omit it for the platform's 100`);
|
|
13853
14423
|
}
|
|
13854
|
-
if (!
|
|
14424
|
+
if (!named5(row.table) || !Object.prototype.hasOwnProperty.call(model.tables, row.table)) continue;
|
|
13855
14425
|
if (model.tables[row.table].audit !== true) {
|
|
13856
14426
|
say(
|
|
13857
14427
|
"table",
|
|
@@ -13867,7 +14437,7 @@ function historyProblems(elements, tree, handlers, model) {
|
|
|
13867
14437
|
const props = element.props ?? {};
|
|
13868
14438
|
const pointer2 = (key) => `/elements/${escapePointer3(uid)}/props/${key}`;
|
|
13869
14439
|
const say = (key, message) => problems.push({ code: "app_model_mismatch", pointer: pointer2(key), message });
|
|
13870
|
-
const source =
|
|
14440
|
+
const source = named5(props.source) ? props.source : null;
|
|
13871
14441
|
const history = source !== null && handlers[source]?.role === "history";
|
|
13872
14442
|
if (element.type === "list" && isPlatformTable(props.entity) && !history) {
|
|
13873
14443
|
say("source", `list "${uid}" shows "${props.entity}", which only a history handler reads \u2014 source it from a handler with role "history"`);
|
|
@@ -13938,7 +14508,7 @@ function numberPinProblems(elements, pins) {
|
|
|
13938
14508
|
const need = typeof PLATFORM_NUMBERS2[module] === "string" ? `${module}@${PLATFORM_NUMBERS2[module]}` : `a ${module} release that draws it`;
|
|
13939
14509
|
const walk4 = (node, at) => {
|
|
13940
14510
|
if (Array.isArray(node)) return node.forEach((child, i) => walk4(child, `${at}/${i}`));
|
|
13941
|
-
if (!
|
|
14511
|
+
if (!isPlainObject6(node)) return;
|
|
13942
14512
|
for (const [key, value] of Object.entries(node)) {
|
|
13943
14513
|
const pointer2 = `${at}/${escapePointer3(key)}`;
|
|
13944
14514
|
const say = (what) => problems.push({
|
|
@@ -13966,7 +14536,7 @@ function whenPinProblems(elements, pins) {
|
|
|
13966
14536
|
if (typeof pin !== "string" || typeof first === "string" && pinAtLeast(pin, first)) continue;
|
|
13967
14537
|
const actions = element.props?.props?.row_actions;
|
|
13968
14538
|
for (const [i, action] of (Array.isArray(actions) ? actions : []).entries()) {
|
|
13969
|
-
if (!
|
|
14539
|
+
if (!isPlainObject6(action) || action.when === void 0 || action.when === null) continue;
|
|
13970
14540
|
problems.push({
|
|
13971
14541
|
code: "when_unsupported",
|
|
13972
14542
|
pointer: `/elements/${escapePointer3(uid)}/props/props/row_actions/${i}/when`,
|
|
@@ -13981,7 +14551,7 @@ function propsPinProblems(elements, pins) {
|
|
|
13981
14551
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
13982
14552
|
const element = elements[uid] ?? {};
|
|
13983
14553
|
const gates = PROPS_SINCE2?.[element.type];
|
|
13984
|
-
if (!
|
|
14554
|
+
if (!isPlainObject6(gates)) continue;
|
|
13985
14555
|
const module = MODULE_OF2[element.type];
|
|
13986
14556
|
const pin = pins[module];
|
|
13987
14557
|
if (typeof pin !== "string") continue;
|
|
@@ -13993,7 +14563,7 @@ function propsPinProblems(elements, pins) {
|
|
|
13993
14563
|
const key = many ? step.slice(0, -2) : step;
|
|
13994
14564
|
const next = [];
|
|
13995
14565
|
for (const [at, node] of reached) {
|
|
13996
|
-
if (!
|
|
14566
|
+
if (!isPlainObject6(node) || node[key] === void 0 || node[key] === null) continue;
|
|
13997
14567
|
const pointer2 = `${at}/${escapePointer3(key)}`;
|
|
13998
14568
|
if (!many) next.push([pointer2, node[key]]);
|
|
13999
14569
|
else if (Array.isArray(node[key])) node[key].forEach((item, i) => next.push([`${pointer2}/${i}`, item]));
|
|
@@ -14064,7 +14634,7 @@ function chartProblems(elements, tree, handlers, model) {
|
|
|
14064
14634
|
say("source", `reads rows, and "${props.source}" has role ${row.role ?? "none"}, ${answers}; source a list, an inbox, or a custom handler that answers {"rows": [...]}`);
|
|
14065
14635
|
continue;
|
|
14066
14636
|
}
|
|
14067
|
-
if (
|
|
14637
|
+
if (named5(row.by) && BY_ROLES.includes(row.role)) {
|
|
14068
14638
|
say(
|
|
14069
14639
|
"source",
|
|
14070
14640
|
`reads "${props.source}", which reads the children of an opened row (by "${row.by}"), and a chart sits at the top of its page with no row to name; source a handler with no by, or return every row's points from one custom handler with a series column`
|
|
@@ -14110,7 +14680,7 @@ var TYPED_PROP_KINDS = Object.freeze({
|
|
|
14110
14680
|
var ORDERED_KINDS = Object.freeze(["waterfall", "waterfall-breakdown", "waterfall-walkthrough"]);
|
|
14111
14681
|
function widgetProblems(elements, tree, handlers, sources, widgetsPin = WIDGETS) {
|
|
14112
14682
|
const problems = [];
|
|
14113
|
-
const declaredSources =
|
|
14683
|
+
const declaredSources = isPlainObject6(sources) ? sources : {};
|
|
14114
14684
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
14115
14685
|
const element = elements[uid] ?? {};
|
|
14116
14686
|
if (element.type !== "widget") continue;
|
|
@@ -14137,11 +14707,11 @@ function widgetProblems(elements, tree, handlers, sources, widgetsPin = WIDGETS)
|
|
|
14137
14707
|
mismatch(flat[0], `states ${flat.map((key) => JSON.stringify(key)).join(", ")} at the top of its props; a widget's typed props sit under props.props ({"props": {"${flat[0]}": ...}}), which the page carries as data-dr-props`);
|
|
14138
14708
|
continue;
|
|
14139
14709
|
}
|
|
14140
|
-
if (given(props.props) && !
|
|
14710
|
+
if (given(props.props) && !isPlainObject6(props.props)) {
|
|
14141
14711
|
mismatch("props", `states props ${JSON.stringify(props.props)}; a widget's typed props are an object ({"props": {"goal": 1000}}), written as data-dr-props`);
|
|
14142
14712
|
continue;
|
|
14143
14713
|
}
|
|
14144
|
-
const typed =
|
|
14714
|
+
const typed = isPlainObject6(props.props) ? props.props : {};
|
|
14145
14715
|
const finance = APP_WIDGET_KINDS2.includes(props.kind);
|
|
14146
14716
|
const smart = given(typed.variant) || given(typed.compare);
|
|
14147
14717
|
const pinned = String(widgetsPin ?? "").slice(String(widgetsPin ?? "").lastIndexOf("@") + 1);
|
|
@@ -14193,7 +14763,7 @@ function widgetProblems(elements, tree, handlers, sources, widgetsPin = WIDGETS)
|
|
|
14193
14763
|
}
|
|
14194
14764
|
const source = declaredSources[props.source];
|
|
14195
14765
|
const handled = Object.prototype.hasOwnProperty.call(handlers, props.source);
|
|
14196
|
-
if (!handled && !
|
|
14766
|
+
if (!handled && !isPlainObject6(source)) continue;
|
|
14197
14767
|
const row = handlers[props.source] ?? {};
|
|
14198
14768
|
if (handled && !WIDGET_SOURCE_ROLES.includes(row.role)) {
|
|
14199
14769
|
const answers = row.role === "summary" ? "which answers one number per measure" : row.role === "history" ? "which answers one row's events" : row.role === "list" || row.role === "inbox" ? "which answers rows" : "which answers no payload";
|
|
@@ -14203,8 +14773,8 @@ function widgetProblems(elements, tree, handlers, sources, widgetsPin = WIDGETS)
|
|
|
14203
14773
|
);
|
|
14204
14774
|
continue;
|
|
14205
14775
|
}
|
|
14206
|
-
if (!
|
|
14207
|
-
const shape =
|
|
14776
|
+
if (!isPlainObject6(source)) continue;
|
|
14777
|
+
const shape = named5(source.shape) ? source.shape : "rows";
|
|
14208
14778
|
const legacy = !finance;
|
|
14209
14779
|
const shaped = (message) => problems.push({ code: "widget_source_shape", pointer: `/elements/${escapePointer3(uid)}/props/source`, message: `widget "${uid}" ${message}`, ...legacy ? { advisory: true } : {} });
|
|
14210
14780
|
if (shape === "rows") {
|
|
@@ -14241,21 +14811,21 @@ var CONTROL_KINDS2 = {
|
|
|
14241
14811
|
};
|
|
14242
14812
|
var TOGGLE_CALL_ROLES = ["custom"];
|
|
14243
14813
|
function rowTableOf(source, handlers, sources, model) {
|
|
14244
|
-
if (!model || !
|
|
14814
|
+
if (!model || !named5(source)) return null;
|
|
14245
14815
|
let table = null;
|
|
14246
14816
|
if (Object.prototype.hasOwnProperty.call(handlers, source)) {
|
|
14247
14817
|
const row = handlers[source] ?? {};
|
|
14248
|
-
if (["list", "inbox"].includes(row.role) &&
|
|
14249
|
-
} else if (
|
|
14818
|
+
if (["list", "inbox"].includes(row.role) && named5(row.table)) table = row.table;
|
|
14819
|
+
} else if (isPlainObject6(sources) && isPlainObject6(sources[source])) {
|
|
14250
14820
|
const declared = sources[source];
|
|
14251
|
-
if (!stated(declared.transform) &&
|
|
14821
|
+
if (!stated(declared.transform) && isPlainObject6(declared.from) && named5(declared.from.table)) table = declared.from.table;
|
|
14252
14822
|
}
|
|
14253
14823
|
if (table === null) return null;
|
|
14254
14824
|
return isPlatformTable(table) || Object.prototype.hasOwnProperty.call(model.tables, table) ? table : null;
|
|
14255
14825
|
}
|
|
14256
14826
|
var PK_WRITING_ROLES = ["remove", "save", "decide"];
|
|
14257
14827
|
function pkWriterTable(row, model) {
|
|
14258
|
-
if (!model || !
|
|
14828
|
+
if (!model || !isPlainObject6(row) || !PK_WRITING_ROLES.includes(row.role) || !named5(row.table)) return null;
|
|
14259
14829
|
if (!Object.prototype.hasOwnProperty.call(model.tables, row.table)) return null;
|
|
14260
14830
|
return primaryKeyOf(model, row.table) === null ? null : row.table;
|
|
14261
14831
|
}
|
|
@@ -14265,17 +14835,17 @@ function primaryKeyOf(model, table) {
|
|
|
14265
14835
|
}
|
|
14266
14836
|
function partProblems(elements, tree, handlers, sources, pages = [], model = null, pins = {}) {
|
|
14267
14837
|
const problems = [];
|
|
14268
|
-
const declaredSources =
|
|
14838
|
+
const declaredSources = isPlainObject6(sources) ? sources : {};
|
|
14269
14839
|
const paramsOf4 = (path3) => {
|
|
14270
14840
|
const page = (Array.isArray(pages) ? pages : []).find((one) => one?.path === path3);
|
|
14271
|
-
return Array.isArray(page?.params) ? page.params.filter((one) =>
|
|
14841
|
+
return Array.isArray(page?.params) ? page.params.filter((one) => isPlainObject6(one) && named5(one.name)) : [];
|
|
14272
14842
|
};
|
|
14273
14843
|
const rowDialogs = /* @__PURE__ */ new Set();
|
|
14274
14844
|
const dialogsById = /* @__PURE__ */ new Map();
|
|
14275
14845
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
14276
14846
|
const element = elements[uid];
|
|
14277
|
-
const typed =
|
|
14278
|
-
if (element?.type === "dialog" && typed.values_from === "row" &&
|
|
14847
|
+
const typed = isPlainObject6(element?.props?.props) ? element.props.props : {};
|
|
14848
|
+
if (element?.type === "dialog" && typed.values_from === "row" && named5(element.props?.id)) {
|
|
14279
14849
|
const where2 = `${element.page}|${element.props.id}`;
|
|
14280
14850
|
rowDialogs.add(where2);
|
|
14281
14851
|
if (!dialogsById.has(where2)) dialogsById.set(where2, { ...element, uid });
|
|
@@ -14286,7 +14856,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14286
14856
|
if (!isPart2(element)) continue;
|
|
14287
14857
|
const type = element.type;
|
|
14288
14858
|
const entry = PARTS[type];
|
|
14289
|
-
const props =
|
|
14859
|
+
const props = isPlainObject6(element.props) ? element.props : {};
|
|
14290
14860
|
const at = `/elements/${escapePointer3(uid)}`;
|
|
14291
14861
|
const say = (code, pointer2, message) => problems.push({ code, pointer: pointer2, message: `${type} "${props.id ?? uid}" ${message}` });
|
|
14292
14862
|
const schemaLines = validate2(props, PROPS_SCHEMAS[type], { quote: true });
|
|
@@ -14307,7 +14877,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14307
14877
|
say("part_children", `/elements/${escapePointer3(kid)}`, `holds "${kid}" (a ${elements[kid]?.type ?? "element"}), and a ${type} holds only ${either3(entry.children)}`);
|
|
14308
14878
|
}
|
|
14309
14879
|
}
|
|
14310
|
-
const typedProps =
|
|
14880
|
+
const typedProps = isPlainObject6(props.props) ? props.props : {};
|
|
14311
14881
|
const own = PROPS_SCHEMAS[type]?.properties?.props?.properties ?? {};
|
|
14312
14882
|
if ("field" in own && "param" in own) {
|
|
14313
14883
|
const field = stated(typedProps.field);
|
|
@@ -14371,11 +14941,11 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14371
14941
|
}
|
|
14372
14942
|
}
|
|
14373
14943
|
const formAbove = () => {
|
|
14374
|
-
for (let up = parentOf(tree, uid);
|
|
14944
|
+
for (let up = parentOf(tree, uid); named5(up) && up in elements; up = parentOf(tree, up)) if (elements[up]?.type === "form") return up;
|
|
14375
14945
|
return null;
|
|
14376
14946
|
};
|
|
14377
14947
|
const dialogAbove = () => {
|
|
14378
|
-
for (let up = parentOf(tree, uid);
|
|
14948
|
+
for (let up = parentOf(tree, uid); named5(up) && up in elements; up = parentOf(tree, up)) if (elements[up]?.type === "dialog") return up;
|
|
14379
14949
|
return null;
|
|
14380
14950
|
};
|
|
14381
14951
|
if (type === "file_upload") {
|
|
@@ -14393,7 +14963,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14393
14963
|
if ((type === "checkbox" || type === "toggle") && stated(typedProps.field)) {
|
|
14394
14964
|
const form = formAbove();
|
|
14395
14965
|
const table = form !== null ? handlers[elements[form].props?.action]?.table ?? elements[form].props?.entity : null;
|
|
14396
|
-
const column =
|
|
14966
|
+
const column = named5(table) ? model?.tables?.[table]?.columns?.[typedProps.field] : null;
|
|
14397
14967
|
if (form !== null && column?.type === "bool") {
|
|
14398
14968
|
say(
|
|
14399
14969
|
"part_placement",
|
|
@@ -14414,10 +14984,10 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14414
14984
|
}
|
|
14415
14985
|
const nested = [];
|
|
14416
14986
|
if (type === "button" && Array.isArray(typedProps.split)) {
|
|
14417
|
-
typedProps.split.forEach((item, index) =>
|
|
14987
|
+
typedProps.split.forEach((item, index) => isPlainObject6(item) && stated(item.on_click) && nested.push([item.on_click, `${at}/props/props/split/${index}/on_click`, `split entry "${item.label ?? index}"`, entry.events?.on_click ?? []]));
|
|
14418
14988
|
}
|
|
14419
14989
|
if (type === "stepper" && Array.isArray(typedProps.steps)) {
|
|
14420
|
-
typedProps.steps.forEach((step, index) =>
|
|
14990
|
+
typedProps.steps.forEach((step, index) => isPlainObject6(step) && stated(step.action) && nested.push([step.action, `${at}/props/props/steps/${index}/action`, `step "${step.id ?? index}"`, entry.events?.on_step ?? []]));
|
|
14421
14991
|
}
|
|
14422
14992
|
for (const [name, pointer2, what, roles] of nested) {
|
|
14423
14993
|
if (!Object.prototype.hasOwnProperty.call(handlers, name)) {
|
|
@@ -14432,19 +15002,19 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14432
15002
|
if (type === "dialog" && !stated(typedProps.trigger) && typedProps.values_from !== "row") {
|
|
14433
15003
|
say("part_mode", `${at}/props/props`, `states neither props.trigger nor values_from: "row", so nothing could open it; give it a trigger, or values_from "row" for a grid's row action to open`);
|
|
14434
15004
|
}
|
|
14435
|
-
if (type === "data_grid" && Array.isArray(typedProps.row_actions) && typedProps.row_actions.some(
|
|
15005
|
+
if (type === "data_grid" && Array.isArray(typedProps.row_actions) && typedProps.row_actions.some(isPlainObject6)) {
|
|
14436
15006
|
const key = typedProps.key;
|
|
14437
15007
|
if (!stated(key)) {
|
|
14438
15008
|
say("part_row_key", `${at}/props/props`, "has row_actions and states no props.key; a row action posts the row's key as id, so name the column that identifies a row (id, for a table's own rows)");
|
|
14439
15009
|
} else {
|
|
14440
15010
|
const writers = [];
|
|
14441
15011
|
typedProps.row_actions.forEach((action, index) => {
|
|
14442
|
-
if (!
|
|
15012
|
+
if (!isPlainObject6(action)) return;
|
|
14443
15013
|
if (stated(action.call)) {
|
|
14444
15014
|
writers.push({ handler: action.call, key, pointer: `${at}/props/props/key`, via: `row action "${action.label ?? index}"` });
|
|
14445
15015
|
} else if (stated(action.dialog)) {
|
|
14446
15016
|
const dialog = dialogsById.get(`${element.page}|${action.dialog}`);
|
|
14447
|
-
const own2 =
|
|
15017
|
+
const own2 = isPlainObject6(dialog?.props?.props) ? dialog.props.props : null;
|
|
14448
15018
|
if (!own2 || !stated(own2.on_confirm)) return;
|
|
14449
15019
|
const ownKey = stated(own2.key);
|
|
14450
15020
|
writers.push({
|
|
@@ -14489,7 +15059,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14489
15059
|
}
|
|
14490
15060
|
}
|
|
14491
15061
|
typedProps.row_actions.forEach((action, index) => {
|
|
14492
|
-
if (!
|
|
15062
|
+
if (!isPlainObject6(action)) return;
|
|
14493
15063
|
const pointer2 = `${at}/props/props/row_actions/${index}`;
|
|
14494
15064
|
if (stated(action.dialog) && !rowDialogs.has(`${element.page}|${action.dialog}`)) {
|
|
14495
15065
|
say("part_row_action", `${pointer2}/dialog`, `row action "${action.label ?? index}" opens dialog "${action.dialog}", and ${element.page} holds no dialog of that id with values_from: "row"; add one (its inputs start from the row, it posts id with them), or name the one there is`);
|
|
@@ -14530,7 +15100,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14530
15100
|
const names = declared.map((one) => `"${one.name}"`);
|
|
14531
15101
|
const filters = type === "filter_bar" ? Array.isArray(typedProps.filters) ? typedProps.filters.map((filter, i) => [filter, `${at}/props/props/filters/${i}`]) : [] : [[typedProps, `${at}/props/props`]];
|
|
14532
15102
|
for (const [filter, base] of filters) {
|
|
14533
|
-
if (!
|
|
15103
|
+
if (!isPlainObject6(filter) || !stated(filter.param)) continue;
|
|
14534
15104
|
const who = type === "filter_bar" ? `filter "${filter.label ?? filter.param}"` : "filter";
|
|
14535
15105
|
const multiple = filter.kind === "option" && filter.multiple === true;
|
|
14536
15106
|
const kinds = filter.kind === "option" ? multiple ? ["multi"] : ["select", "period", "month"] : filter.kind === "date-range" ? ["period", "month"] : ["month", "period"];
|
|
@@ -14573,7 +15143,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14573
15143
|
const columns = Array.isArray(typedProps.columns) ? typedProps.columns : [];
|
|
14574
15144
|
const rowTable = rowTableOf(props.source, handlers, declaredSources, model);
|
|
14575
15145
|
const carries = (column) => rowTable === null || hasColumn(model, rowTable, column);
|
|
14576
|
-
const hierarchy =
|
|
15146
|
+
const hierarchy = isPlainObject6(typedProps.hierarchy) ? typedProps.hierarchy : null;
|
|
14577
15147
|
if (hierarchy) {
|
|
14578
15148
|
const hp = `${at}/props/props/hierarchy`;
|
|
14579
15149
|
if (stated(hierarchy.label) && !columns.some((column) => column?.field === hierarchy.label)) {
|
|
@@ -14590,7 +15160,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14590
15160
|
}
|
|
14591
15161
|
}
|
|
14592
15162
|
columns.forEach((column, index) => {
|
|
14593
|
-
if (!
|
|
15163
|
+
if (!isPlainObject6(column)) return;
|
|
14594
15164
|
const cp2 = `${at}/props/props/columns/${index}`;
|
|
14595
15165
|
if (stated(column.link_field)) {
|
|
14596
15166
|
if (column.cell !== "link") say("part_cell", `${cp2}/link_field`, `column "${column.field}" states link_field and draws no link; add cell: "link", or drop link_field`);
|
|
@@ -14624,14 +15194,14 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14624
15194
|
if (!entry.reads.roles.includes(role)) {
|
|
14625
15195
|
say("part_source_shape", `${at}/props/source`, `reads rows, and "${source}" has role ${role ?? "none"}, which answers no rows a ${type} draws; source a ${either3(entry.reads.roles)} handler, or a data.sources entry with shape rows`);
|
|
14626
15196
|
}
|
|
14627
|
-
} else if (
|
|
14628
|
-
const shape =
|
|
15197
|
+
} else if (isPlainObject6(declaredSources[source])) {
|
|
15198
|
+
const shape = named5(declaredSources[source].shape) ? declaredSources[source].shape : "rows";
|
|
14629
15199
|
if (shape !== "rows") {
|
|
14630
15200
|
say("part_source_shape", `${at}/props/source`, `reads rows, and source "${source}" answers ${shape} (its shape); a ${shape} body is what a widget draws \u2014 give the source shape rows`);
|
|
14631
15201
|
}
|
|
14632
15202
|
}
|
|
14633
15203
|
}
|
|
14634
|
-
const typed =
|
|
15204
|
+
const typed = isPlainObject6(props.props) ? props.props : {};
|
|
14635
15205
|
for (const [prop, roles] of Object.entries(entry.events ?? {})) {
|
|
14636
15206
|
if (prop === "on_row_action") continue;
|
|
14637
15207
|
const name = typed[prop];
|
|
@@ -14652,7 +15222,7 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
14652
15222
|
var ROWLESS_EVENTS = /* @__PURE__ */ new Set(["button.on_click", "empty_state.on_action"]);
|
|
14653
15223
|
function rowlessRefusal(name, handlers) {
|
|
14654
15224
|
const row = handlers[name] ?? {};
|
|
14655
|
-
const required = Array.isArray(row.required) ? row.required.filter(
|
|
15225
|
+
const required = Array.isArray(row.required) ? row.required.filter(named5) : [];
|
|
14656
15226
|
if (row.role === "submit" && required.length > 0) {
|
|
14657
15227
|
return `a submit handler that requires ${required.map((key) => `"${key}"`).join(", ")}, and the part posts none of them \u2014 it would refuse every click; call a custom handler, or a submit with nothing required`;
|
|
14658
15228
|
}
|
|
@@ -14662,11 +15232,11 @@ function fillChartFormats(elements, handlers, model) {
|
|
|
14662
15232
|
let out = null;
|
|
14663
15233
|
for (const uid of Object.keys(elements)) {
|
|
14664
15234
|
const element = elements[uid];
|
|
14665
|
-
if (element?.type !== "chart" ||
|
|
15235
|
+
if (element?.type !== "chart" || named5(element.props?.format)) continue;
|
|
14666
15236
|
const source = element.props?.source;
|
|
14667
|
-
const row =
|
|
14668
|
-
const table =
|
|
14669
|
-
const column =
|
|
15237
|
+
const row = named5(source) && Object.prototype.hasOwnProperty.call(handlers, source) ? handlers[source] ?? {} : {};
|
|
15238
|
+
const table = named5(row.table) && Object.prototype.hasOwnProperty.call(model.tables, row.table) ? model.tables[row.table] : null;
|
|
15239
|
+
const column = named5(element.props?.y) && table !== null && Object.prototype.hasOwnProperty.call(table.columns, element.props.y) ? table.columns[element.props.y] : null;
|
|
14670
15240
|
const format = row.role !== "custom" && isDecimalType(column?.type) ? "currency" : "number";
|
|
14671
15241
|
out ??= { ...elements };
|
|
14672
15242
|
out[uid] = { ...element, props: { ...element.props, format } };
|
|
@@ -14675,10 +15245,10 @@ function fillChartFormats(elements, handlers, model) {
|
|
|
14675
15245
|
}
|
|
14676
15246
|
function dropEmptyArgs(elements) {
|
|
14677
15247
|
let out = null;
|
|
14678
|
-
const empty = (value) => value === null ||
|
|
15248
|
+
const empty = (value) => value === null || isPlainObject6(value) && Object.keys(value).length === 0;
|
|
14679
15249
|
for (const uid of Object.keys(elements)) {
|
|
14680
15250
|
const element = elements[uid];
|
|
14681
|
-
if (element?.type !== "custom" || !
|
|
15251
|
+
if (element?.type !== "custom" || !isPlainObject6(element.props)) continue;
|
|
14682
15252
|
const drop = ["args", "declares"].filter((key) => key in element.props && empty(element.props[key]));
|
|
14683
15253
|
if (drop.length === 0) continue;
|
|
14684
15254
|
const props = { ...element.props };
|
|
@@ -14708,8 +15278,8 @@ function computedProblems(elements, tree, handlers, model) {
|
|
|
14708
15278
|
const base = `/app/data_model/tables/${escapePointer3(table)}/columns/${escapePointer3(name)}`;
|
|
14709
15279
|
const said2 = (message, key = "computed") => push(`${base}/${key}`, `"${table}.${name}" ${message}`);
|
|
14710
15280
|
if (column.matches !== null) {
|
|
14711
|
-
const other =
|
|
14712
|
-
if (!
|
|
15281
|
+
const other = named5(column.matches) ? columns[column.matches] : void 0;
|
|
15282
|
+
if (!named5(column.matches)) said2(`states matches as ${JSON.stringify(column.matches)}; matches names a column of table "${table}"`, "matches");
|
|
14713
15283
|
else if (column.computed === null) said2(`states matches, which says which column a computed value should agree with, and "${name}" computes nothing; drop matches, or make "${name}" computed`, "matches");
|
|
14714
15284
|
else if (column.matches === name) said2('matches itself; name the column its total should agree with, e.g. "subtotal"', "matches");
|
|
14715
15285
|
else if (other === void 0) said2(`matches "${column.matches}", which is no column of table "${table}"`, "matches");
|
|
@@ -14718,7 +15288,7 @@ function computedProblems(elements, tree, handlers, model) {
|
|
|
14718
15288
|
}
|
|
14719
15289
|
if (column.computed === null) continue;
|
|
14720
15290
|
const spec = column.computed;
|
|
14721
|
-
if (!
|
|
15291
|
+
if (!isPlainObject6(spec)) {
|
|
14722
15292
|
said2(`states computed as ${JSON.stringify(spec)}; computed is {"sum": "<child table>.<number column>", "by": "<the child's ref column to ${table}>"}`);
|
|
14723
15293
|
continue;
|
|
14724
15294
|
}
|
|
@@ -14735,7 +15305,7 @@ function computedProblems(elements, tree, handlers, model) {
|
|
|
14735
15305
|
if (stated2) said2(`is computed and states ${key === "nullable" ? "nullable: false" : key}; ${why} \u2014 drop ${key}`);
|
|
14736
15306
|
}
|
|
14737
15307
|
if (model.tables[table].indexes.some((index) => index.includes(name))) said2("is computed and named in an index; the platform stores no such column, so there is nothing to index \u2014 drop it from indexes");
|
|
14738
|
-
if (fns.length !== 1 || !COMPUTED_FNS.includes(fns[0]) || !
|
|
15308
|
+
if (fns.length !== 1 || !COMPUTED_FNS.includes(fns[0]) || !named5(spec.by)) {
|
|
14739
15309
|
said2(`states computed as ${JSON.stringify(spec)}; computed is {"sum": "<child table>.<number column>", "by": "<the child's ref column to ${table}>"}`);
|
|
14740
15310
|
continue;
|
|
14741
15311
|
}
|
|
@@ -14776,17 +15346,17 @@ function computedProblems(elements, tree, handlers, model) {
|
|
|
14776
15346
|
for (const key of ["fields", "required"]) {
|
|
14777
15347
|
for (const hit of (Array.isArray(row[key]) ? row[key] : []).filter((column) => computed.has(column))) refuse(at(key), hit, key === "fields" ? "takes" : "requires");
|
|
14778
15348
|
}
|
|
14779
|
-
for (const fixed of Object.keys(
|
|
15349
|
+
for (const fixed of Object.keys(isPlainObject6(row.fixed) ? row.fixed : {}).filter((column) => computed.has(column))) refuse(at("fixed"), fixed, "fixes");
|
|
14780
15350
|
}
|
|
14781
15351
|
}
|
|
14782
15352
|
if (tree.problems.length > 0) return problems;
|
|
14783
15353
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
14784
15354
|
const element = elements[uid] ?? {};
|
|
14785
|
-
if (element.type !== "field" || !
|
|
15355
|
+
if (element.type !== "field" || !named5(element.props?.field)) continue;
|
|
14786
15356
|
const entity = entityOf(uid, elements, tree);
|
|
14787
15357
|
if (entity === null || !computedIn(entity).has(element.props.field)) continue;
|
|
14788
15358
|
const holder = elements[parentOf(tree, uid)] ?? null;
|
|
14789
|
-
const edits = holder?.type === "form" || holder?.type === "detail" &&
|
|
15359
|
+
const edits = holder?.type === "form" || holder?.type === "detail" && named5(holder.props?.action);
|
|
14790
15360
|
if (edits) {
|
|
14791
15361
|
push(
|
|
14792
15362
|
`/elements/${escapePointer3(uid)}/props/field`,
|
|
@@ -14809,7 +15379,7 @@ function whenProblems(elements, tree, model) {
|
|
|
14809
15379
|
push(`"${uid}" states when, and when says which rows a row action shows on: only an action inside a list or a queue has a row \u2014 drop when`);
|
|
14810
15380
|
continue;
|
|
14811
15381
|
}
|
|
14812
|
-
if (!
|
|
15382
|
+
if (!isPlainObject6(when) || Object.keys(when).length === 0) {
|
|
14813
15383
|
push(`"${uid}" states when as ${JSON.stringify(when)}; when is an object of column to the list of values the row may hold \u2014 {"status": ["open", "in_progress"]}`);
|
|
14814
15384
|
continue;
|
|
14815
15385
|
}
|
|
@@ -14817,7 +15387,7 @@ function whenProblems(elements, tree, model) {
|
|
|
14817
15387
|
for (const column of Object.keys(when).sort()) {
|
|
14818
15388
|
const states = when[column];
|
|
14819
15389
|
const pointer2 = `${at}/${escapePointer3(column)}`;
|
|
14820
|
-
if (!Array.isArray(states) || states.length === 0 || !states.every(
|
|
15390
|
+
if (!Array.isArray(states) || states.length === 0 || !states.every(named5)) {
|
|
14821
15391
|
push(`"${uid}" states when.${column} as ${JSON.stringify(states)}; each column takes a non-empty list of the values it may hold, as strings \u2014 even one: ["open"]`, pointer2);
|
|
14822
15392
|
continue;
|
|
14823
15393
|
}
|
|
@@ -14842,7 +15412,7 @@ function pickerProblems(elements, tree, handlers, model) {
|
|
|
14842
15412
|
for (const [name, column] of Object.entries(model.tables[table].columns)) {
|
|
14843
15413
|
if (column.display === null) continue;
|
|
14844
15414
|
const pointer2 = `/app/data_model/tables/${escapePointer3(table)}/columns/${escapePointer3(name)}/display`;
|
|
14845
|
-
if (!
|
|
15415
|
+
if (!named5(column.ref)) {
|
|
14846
15416
|
push(pointer2, `display says which column of the referenced table to show, and "${table}.${name}" references nothing; drop display, or make "${name}" a ref`);
|
|
14847
15417
|
continue;
|
|
14848
15418
|
}
|
|
@@ -14856,19 +15426,19 @@ function pickerProblems(elements, tree, handlers, model) {
|
|
|
14856
15426
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
14857
15427
|
const element = elements[uid] ?? {};
|
|
14858
15428
|
const props = element.props ?? {};
|
|
14859
|
-
if (element.type !== "field" || !
|
|
15429
|
+
if (element.type !== "field" || !named5(props.source)) continue;
|
|
14860
15430
|
const entity = entityOf(uid, elements, tree);
|
|
14861
15431
|
const column = entity === null ? void 0 : model.tables[entity]?.columns?.[props.field];
|
|
14862
15432
|
const handler = handlers[props.source];
|
|
14863
15433
|
if (column === void 0 || handler === void 0) continue;
|
|
14864
15434
|
const at = (key) => `/elements/${escapePointer3(uid)}/props/${key}`;
|
|
14865
15435
|
const holder = elements[parentOf(tree, uid)] ?? null;
|
|
14866
|
-
const isInput = holder?.type === "form" || holder?.type === "detail" &&
|
|
15436
|
+
const isInput = holder?.type === "form" || holder?.type === "detail" && named5(holder.props?.action);
|
|
14867
15437
|
if (!isInput) {
|
|
14868
15438
|
push(at("source"), `field "${uid}" names source "${props.source}", and a picker is a form input: under a ${holder?.type ?? "page"} a field is ${holder?.type === "detail" ? "a shown term" : "a column"}, which draws the linked row by its display and needs no source`);
|
|
14869
15439
|
continue;
|
|
14870
15440
|
}
|
|
14871
|
-
if (!
|
|
15441
|
+
if (!named5(column.ref)) {
|
|
14872
15442
|
push(at("source"), `field "${uid}" names source "${props.source}", and a picker draws the rows of the table a ref column points at; "${entity}.${props.field}" is not a ref column`);
|
|
14873
15443
|
continue;
|
|
14874
15444
|
}
|
|
@@ -14879,17 +15449,17 @@ function pickerProblems(elements, tree, handlers, model) {
|
|
|
14879
15449
|
if (!["list", "inbox"].includes(handler.role) || handler.table !== target) {
|
|
14880
15450
|
const what = ["list", "inbox"].includes(handler.role) ? `is over "${handler.table}"` : `is a ${handler.role ?? "custom"} handler`;
|
|
14881
15451
|
push(at("source"), `"${props.source}" ${what}, and "${props.field}" references "${target}"; a picker's options come from a list or inbox over the table the ref points at`);
|
|
14882
|
-
} else if (
|
|
15452
|
+
} else if (named5(handler.by)) {
|
|
14883
15453
|
push(at("source"), `"${props.source}" is filtered by "${handler.by}", so a picker \u2014 which calls it with nothing \u2014 would always get no options; give the picker an unfiltered list over "${target}"`);
|
|
14884
15454
|
}
|
|
14885
|
-
if (
|
|
15455
|
+
if (named5(props.input_type) && props.input_type !== "select") {
|
|
14886
15456
|
push(at("input_type"), `field "${uid}" is a picker, which is drawn as a select, and states input_type "${props.input_type}"; drop input_type`);
|
|
14887
15457
|
}
|
|
14888
15458
|
const gate = Array.isArray(handler.requiresRole) ? handler.requiresRole.map(String) : [];
|
|
14889
15459
|
if (gate.length > 0) {
|
|
14890
15460
|
const marked2 = [];
|
|
14891
15461
|
for (let current = uid; current !== null && elements[current] !== void 0; current = parentOf(tree, current)) {
|
|
14892
|
-
if (
|
|
15462
|
+
if (named5(elements[current].props?.role)) marked2.push(elements[current].props.role);
|
|
14893
15463
|
}
|
|
14894
15464
|
if (!marked2.some((role) => gate.includes(role))) {
|
|
14895
15465
|
push(
|
|
@@ -14904,13 +15474,13 @@ function pickerProblems(elements, tree, handlers, model) {
|
|
|
14904
15474
|
function decisionTargets(row) {
|
|
14905
15475
|
if (row?.role !== void 0 && row.role !== "decide") return [];
|
|
14906
15476
|
if (Array.isArray(row?.decision)) return row.decision.map(String);
|
|
14907
|
-
if (
|
|
14908
|
-
return Object.values(row.decision).filter((spec) =>
|
|
15477
|
+
if (isPlainObject6(row?.decision)) {
|
|
15478
|
+
return Object.values(row.decision).filter((spec) => isPlainObject6(spec) && named5(spec.to)).map((spec) => String(spec.to));
|
|
14909
15479
|
}
|
|
14910
15480
|
return [];
|
|
14911
15481
|
}
|
|
14912
15482
|
function decisionNames(row) {
|
|
14913
|
-
return Object.keys(
|
|
15483
|
+
return Object.keys(isPlainObject6(row?.decision) ? row.decision : {}).sort(natural3);
|
|
14914
15484
|
}
|
|
14915
15485
|
function either3(names) {
|
|
14916
15486
|
return names.length <= 1 ? names.join("") : `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}`;
|
|
@@ -14929,10 +15499,10 @@ function decisionProblems(name, row, table, status) {
|
|
|
14929
15499
|
}
|
|
14930
15500
|
}
|
|
14931
15501
|
const set = spec.set === void 0 ? {} : spec.set;
|
|
14932
|
-
if (!
|
|
15502
|
+
if (!isPlainObject6(set)) {
|
|
14933
15503
|
say(at("set"), `states set as ${JSON.stringify(set)}; set is an object of column to the value the decision writes there \u2014 a literal, null, "$now", "$caller" or "$note"`);
|
|
14934
15504
|
}
|
|
14935
|
-
const sets =
|
|
15505
|
+
const sets = isPlainObject6(set) ? set : {};
|
|
14936
15506
|
if (spec.note !== void 0) {
|
|
14937
15507
|
if (spec.note !== "required" && spec.note !== "optional") {
|
|
14938
15508
|
say(at("note"), `states note as ${JSON.stringify(spec.note)}; a note is "required" or "optional", and an absent note is optional`);
|
|
@@ -15024,7 +15594,7 @@ function requiresProblems(name, row, model) {
|
|
|
15024
15594
|
say(at(column), `something of "${column}", which is no column of table "${row.table}"; requires is keyed by a ref column of the handler's own table`);
|
|
15025
15595
|
continue;
|
|
15026
15596
|
}
|
|
15027
|
-
if (!
|
|
15597
|
+
if (!named5(held.ref)) {
|
|
15028
15598
|
say(at(column), `something of "${column}", which references no table; requires reads the row a ref column names, so key it by one (e.g. "vendor_id")`);
|
|
15029
15599
|
continue;
|
|
15030
15600
|
}
|
|
@@ -15057,7 +15627,7 @@ function requiresProblems(name, row, model) {
|
|
|
15057
15627
|
say(where2, `"${field}" of the ${singular(target)}, a ${targetColumn.type} column; a condition compares text, a whole number, true/false or an id \u2014 a ${targetColumn.type} read back never equals the value the contract states`);
|
|
15058
15628
|
continue;
|
|
15059
15629
|
}
|
|
15060
|
-
const negated =
|
|
15630
|
+
const negated = isPlainObject6(wanted);
|
|
15061
15631
|
if (negated && !(Object.keys(wanted).length === 1 && "not" in wanted && scalar(wanted.not))) {
|
|
15062
15632
|
say(where2, `"${field}" to be ${JSON.stringify(wanted)}; a condition is a value the column must hold, or {"not": a value it must not}`);
|
|
15063
15633
|
continue;
|
|
@@ -15077,7 +15647,7 @@ function requiresProblems(name, row, model) {
|
|
|
15077
15647
|
function requiresOf(row, model) {
|
|
15078
15648
|
const table = model.tables[row.table];
|
|
15079
15649
|
const out = {};
|
|
15080
|
-
for (const column of Object.keys(
|
|
15650
|
+
for (const column of Object.keys(isPlainObject6(row.requires) ? row.requires : {}).sort(natural3)) {
|
|
15081
15651
|
const held = table.columns[column];
|
|
15082
15652
|
const target = String(held.ref).split(".")[0];
|
|
15083
15653
|
out[column] = [target, singular(target), held.display ?? null, row.requires[column]];
|
|
@@ -15085,11 +15655,11 @@ function requiresOf(row, model) {
|
|
|
15085
15655
|
return out;
|
|
15086
15656
|
}
|
|
15087
15657
|
function requiresDoc(row) {
|
|
15088
|
-
if (!
|
|
15658
|
+
if (!isPlainObject6(row.requires)) return "";
|
|
15089
15659
|
return "\n\n`requires` checks the row each ref column names first, read under this\ncaller's own scope: one this caller may not see, or none at all, refuses the\nsame way as one that fails its condition.";
|
|
15090
15660
|
}
|
|
15091
15661
|
function requiresConstants(row, model) {
|
|
15092
|
-
if (!
|
|
15662
|
+
if (!isPlainObject6(row.requires)) return [];
|
|
15093
15663
|
const requires = requiresOf(row, model);
|
|
15094
15664
|
return [assignment("REQUIRES", requires, Object.keys(requires))];
|
|
15095
15665
|
}
|
|
@@ -15148,7 +15718,7 @@ var EVENTS_SHAPE = {
|
|
|
15148
15718
|
};
|
|
15149
15719
|
var PLATFORM_EVENT_HANDLERS = ["seed", "dry-run"];
|
|
15150
15720
|
function isNewMeasure(measure) {
|
|
15151
|
-
return
|
|
15721
|
+
return isPlainObject6(measure) && Object.keys(measure).some((key) => MEASURE_WORDS.includes(key));
|
|
15152
15722
|
}
|
|
15153
15723
|
function isSummable(type) {
|
|
15154
15724
|
return type === "int" || type === "bigint" || typeof type === "string" && DECIMAL_RE.test(type);
|
|
@@ -15229,12 +15799,12 @@ function whereProblems(where2, table, pointer2, who, { platform = false, noColum
|
|
|
15229
15799
|
function measureProblems(name, row, model, handlers = {}) {
|
|
15230
15800
|
const problems = [];
|
|
15231
15801
|
const table = model.tables[row.table];
|
|
15232
|
-
const measures =
|
|
15802
|
+
const measures = isPlainObject6(row.measures) ? row.measures : {};
|
|
15233
15803
|
for (const measure of Object.keys(measures).sort(natural3)) {
|
|
15234
15804
|
const spec = measures[measure];
|
|
15235
15805
|
const base = `/data/app/handlers/${escapePointer3(name)}/measures/${escapePointer3(measure)}`;
|
|
15236
15806
|
const say = (message, tail = null) => problems.push({ code: "app_model_mismatch", pointer: tail === null ? base : `${base}/${escapePointer3(tail)}`, message: `measure "${measure}" of handler "${name}" ${message}` });
|
|
15237
|
-
if (!
|
|
15807
|
+
if (!isPlainObject6(spec)) {
|
|
15238
15808
|
say(`is ${JSON.stringify(spec)}; a measure is an equality filter it counts ({"status": "open"}), or {count: true | sum | avg | avg_days | count_events: true, where}`);
|
|
15239
15809
|
continue;
|
|
15240
15810
|
}
|
|
@@ -15247,7 +15817,7 @@ function measureProblems(name, row, model, handlers = {}) {
|
|
|
15247
15817
|
}
|
|
15248
15818
|
const metrics = keys2.filter((key) => MEASURE_METRICS.includes(key));
|
|
15249
15819
|
const strays = keys2.filter((key) => !MEASURE_WORDS.includes(key));
|
|
15250
|
-
const shaped = metrics.length === 1 && strays.length === 0 && (metrics[0] !== "count" || spec.count === true) && (metrics[0] !== "count_events" || spec.count_events === true) && (metrics[0] !== "avg_days" || Array.isArray(spec.avg_days)) && (!["sum", "avg"].includes(metrics[0]) ||
|
|
15820
|
+
const shaped = metrics.length === 1 && strays.length === 0 && (metrics[0] !== "count" || spec.count === true) && (metrics[0] !== "count_events" || spec.count_events === true) && (metrics[0] !== "avg_days" || Array.isArray(spec.avg_days)) && (!["sum", "avg"].includes(metrics[0]) || named5(spec[metrics[0]]));
|
|
15251
15821
|
if (!shaped && keys2.every((key) => Object.prototype.hasOwnProperty.call(table.columns, key))) {
|
|
15252
15822
|
say(
|
|
15253
15823
|
`reads as a filter on ${keys2.map((key) => `"${key}"`).join(", ")} and as a measure at once, because ${keys2.filter((key) => MEASURE_WORDS.includes(key)).map((key) => `"${key}"`).join(", ")} is also a column of table "${table.name}"; spell the filter as {"count": true, "where": ${JSON.stringify(spec)}}`
|
|
@@ -15269,14 +15839,14 @@ function measureProblems(name, row, model, handlers = {}) {
|
|
|
15269
15839
|
}
|
|
15270
15840
|
if (metric === "sum" || metric === "avg") {
|
|
15271
15841
|
const column = spec[metric];
|
|
15272
|
-
const type =
|
|
15273
|
-
if (!
|
|
15842
|
+
const type = named5(column) ? readableType(table, column) : null;
|
|
15843
|
+
if (!named5(column)) say(`states ${metric} as ${JSON.stringify(column)}; ${metric} names the column it reads`, metric);
|
|
15274
15844
|
else if (type === null) say(`reads ${metric} of "${column}", which is no column of table "${table.name}"`, metric);
|
|
15275
15845
|
else if (!isSummable(type)) say(`reads ${metric} of "${column}", which is ${type}; ${metric} takes a number column (int, bigint, decimal(p,s))`, metric);
|
|
15276
15846
|
}
|
|
15277
15847
|
if (metric === "avg_days") {
|
|
15278
15848
|
const ends = spec.avg_days;
|
|
15279
|
-
if (!Array.isArray(ends) || ends.length !== 2 || !ends.every(
|
|
15849
|
+
if (!Array.isArray(ends) || ends.length !== 2 || !ends.every(named5)) {
|
|
15280
15850
|
say(`states avg_days as ${JSON.stringify(ends)}; avg_days is [<from column>, <to column or "$now">]`, "avg_days");
|
|
15281
15851
|
} else {
|
|
15282
15852
|
for (const [index, end] of ends.entries()) {
|
|
@@ -15296,7 +15866,7 @@ function measureProblems(name, row, model, handlers = {}) {
|
|
|
15296
15866
|
if (spec.where !== void 0) {
|
|
15297
15867
|
const who = `measure "${measure}" of handler "${name}"`;
|
|
15298
15868
|
const at = (key) => `${base}/where/${escapePointer3(key)}`;
|
|
15299
|
-
if (!
|
|
15869
|
+
if (!isPlainObject6(spec.where)) say(`states where as ${JSON.stringify(spec.where)}; where is an object of "<column>" or "<column>__<op>" to a value`, "where");
|
|
15300
15870
|
else if (events) {
|
|
15301
15871
|
const found = whereProblems(spec.where, EVENTS_SHAPE, at, who, {
|
|
15302
15872
|
noColumn: (column) => `filters on "${column}", which is no column of the events; filter on at, action, handler, actor or note`
|
|
@@ -15345,11 +15915,11 @@ function measuresOf(row) {
|
|
|
15345
15915
|
out[measure] = ["count", "count", spec];
|
|
15346
15916
|
continue;
|
|
15347
15917
|
}
|
|
15348
|
-
const where2 =
|
|
15918
|
+
const where2 = isPlainObject6(spec.where) ? spec.where : {};
|
|
15349
15919
|
if (spec.count === true) out[measure] = ["count", "count", where2];
|
|
15350
15920
|
else if (spec.count_events === true) out[measure] = ["count_events", "count_events", where2];
|
|
15351
|
-
else if (
|
|
15352
|
-
else if (
|
|
15921
|
+
else if (named5(spec.sum)) out[measure] = ["sum", ["sum", spec.sum], where2];
|
|
15922
|
+
else if (named5(spec.avg)) out[measure] = ["avg", ["avg", spec.avg], where2];
|
|
15353
15923
|
else out[measure] = ["avg_days", ["avg_days", ...spec.avg_days], where2];
|
|
15354
15924
|
}
|
|
15355
15925
|
return out;
|
|
@@ -15434,11 +16004,11 @@ function manifestOf(contract, app, handlers, names, model, served = []) {
|
|
|
15434
16004
|
...typeof app.description === "string" ? { description: app.description } : {},
|
|
15435
16005
|
version: app.version ?? DEFAULT_VERSION
|
|
15436
16006
|
};
|
|
15437
|
-
if (
|
|
16007
|
+
if (named5(app.nav_label)) manifest.nav = { label: app.nav_label };
|
|
15438
16008
|
manifest.routes = routesOf2(app, contract.pages ?? []);
|
|
15439
16009
|
const roles = rolesOf2(contract.backend?.roles, model);
|
|
15440
16010
|
if (roles.length > 0) manifest.roles = roles;
|
|
15441
|
-
const gated = (Array.isArray(contract.pages) ? contract.pages : []).filter((page) =>
|
|
16011
|
+
const gated = (Array.isArray(contract.pages) ? contract.pages : []).filter((page) => named5(page?.path) && gateRoles(page.roles).length > 0).map((page) => ({ path: page.path, roles: gateRoles(page.roles) }));
|
|
15442
16012
|
if (gated.length > 0) manifest.pages = gated;
|
|
15443
16013
|
manifest.handlers = {};
|
|
15444
16014
|
for (const name of names) {
|
|
@@ -15454,7 +16024,7 @@ function manifestOf(contract, app, handlers, names, model, served = []) {
|
|
|
15454
16024
|
manifest.handlers[name] = entry;
|
|
15455
16025
|
}
|
|
15456
16026
|
manifest.handlers = Object.fromEntries(Object.keys(manifest.handlers).sort(natural3).map((name) => [name, manifest.handlers[name]]));
|
|
15457
|
-
manifest.capabilities =
|
|
16027
|
+
manifest.capabilities = isPlainObject6(app.capabilities) && Object.keys(app.capabilities).length > 0 ? app.capabilities : { data: ["rw"] };
|
|
15458
16028
|
return manifest;
|
|
15459
16029
|
}
|
|
15460
16030
|
function routesOf2(app, pages) {
|
|
@@ -15470,9 +16040,9 @@ function rolesOf2(declared, model) {
|
|
|
15470
16040
|
const out = { name: String(role?.name ?? "") };
|
|
15471
16041
|
if (role?.default) out.default = true;
|
|
15472
16042
|
const scope = role?.default ? "own" : "all";
|
|
15473
|
-
out.tables =
|
|
16043
|
+
out.tables = isPlainObject6(role?.tables) && Object.keys(role.tables).length > 0 ? role.tables : Object.fromEntries(tables.map((table) => [table, { read: model.tables[table]?.singleton ? "all" : scope, write: scope }]));
|
|
15474
16044
|
if (Array.isArray(role?.sees) && role.sees.length > 0) out.sees = role.sees.map(String);
|
|
15475
|
-
if (
|
|
16045
|
+
if (named5(role?.home)) out.home = role.home;
|
|
15476
16046
|
return out;
|
|
15477
16047
|
});
|
|
15478
16048
|
}
|
|
@@ -15540,8 +16110,8 @@ function decimalsOf(row, table) {
|
|
|
15540
16110
|
}
|
|
15541
16111
|
function groupedBy(row) {
|
|
15542
16112
|
const columns = [];
|
|
15543
|
-
for (const where2 of Object.values(
|
|
15544
|
-
for (const column of Object.keys(
|
|
16113
|
+
for (const where2 of Object.values(isPlainObject6(row.measures) ? row.measures : {})) {
|
|
16114
|
+
for (const column of Object.keys(isPlainObject6(where2) ? where2 : {})) {
|
|
15545
16115
|
if (!columns.includes(column)) columns.push(column);
|
|
15546
16116
|
}
|
|
15547
16117
|
}
|
|
@@ -15552,8 +16122,8 @@ function singular(name) {
|
|
|
15552
16122
|
}
|
|
15553
16123
|
function queryCall(row, { receiver = 'ctx.data.table(HANDLER["table"])', indent: indent2 = 4, include = false } = {}) {
|
|
15554
16124
|
const args = [];
|
|
15555
|
-
if (
|
|
15556
|
-
if (
|
|
16125
|
+
if (isPlainObject6(row.where)) args.push('where=HANDLER["where"]');
|
|
16126
|
+
if (named5(row.order_by)) args.push('order_by=HANDLER["order_by"]');
|
|
15557
16127
|
if (row.limit !== void 0 && row.limit !== null) args.push('limit=HANDLER["limit"]');
|
|
15558
16128
|
if (include) args.push("include=INCLUDE");
|
|
15559
16129
|
const pad = " ".repeat(indent2);
|
|
@@ -15633,13 +16203,13 @@ function parentLines(row, parent, { missing, invisible }) {
|
|
|
15633
16203
|
];
|
|
15634
16204
|
}
|
|
15635
16205
|
function includesOf(table) {
|
|
15636
|
-
return Object.keys(table?.columns ?? {}).filter((name) =>
|
|
16206
|
+
return Object.keys(table?.columns ?? {}).filter((name) => named5(table.columns[name].ref) && named5(table.columns[name].display));
|
|
15637
16207
|
}
|
|
15638
16208
|
function refsOf(row, table) {
|
|
15639
16209
|
const refs = {};
|
|
15640
16210
|
for (const field of Array.isArray(row.fields) ? row.fields : []) {
|
|
15641
16211
|
const ref = table.columns?.[field]?.ref;
|
|
15642
|
-
if (!
|
|
16212
|
+
if (!named5(ref)) continue;
|
|
15643
16213
|
const target = String(ref).split(".")[0];
|
|
15644
16214
|
refs[field] = [target, singular(target)];
|
|
15645
16215
|
}
|
|
@@ -15663,10 +16233,10 @@ function refLines() {
|
|
|
15663
16233
|
}
|
|
15664
16234
|
function childList(row, parent, include = []) {
|
|
15665
16235
|
const args = ["where=where"];
|
|
15666
|
-
if (
|
|
16236
|
+
if (named5(row.order_by)) args.push('order_by=HANDLER["order_by"]');
|
|
15667
16237
|
if (row.limit !== void 0 && row.limit !== null) args.push('limit=HANDLER["limit"]');
|
|
15668
16238
|
if (include.length > 0) args.push("include=INCLUDE");
|
|
15669
|
-
const where2 =
|
|
16239
|
+
const where2 = isPlainObject6(row.where) ? ' where = {**HANDLER["where"], HANDLER["by"]: parent_id}' : ' where = {HANDLER["by"]: parent_id}';
|
|
15670
16240
|
return {
|
|
15671
16241
|
docstring: `The ${row.table} of one ${singular(parent)}: the ${singular(parent)} a reader opened, named by \`by\`.
|
|
15672
16242
|
|
|
@@ -15737,7 +16307,7 @@ function decideMap(name, row, table, model) {
|
|
|
15737
16307
|
const noun = singular(row.table);
|
|
15738
16308
|
const names = decisionNames(row);
|
|
15739
16309
|
const specs = names.map((one) => row.decision[one]);
|
|
15740
|
-
const sets = specs.map((spec) =>
|
|
16310
|
+
const sets = specs.map((spec) => isPlainObject6(spec.set) ? spec.set : {});
|
|
15741
16311
|
const used = new Set(sets.flatMap((set) => Object.values(set)).filter((value) => SET_TOKENS.includes(value)));
|
|
15742
16312
|
const setsAnything = sets.some((set) => Object.keys(set).length > 0);
|
|
15743
16313
|
const requiresNote = specs.some((spec) => spec.note === "required");
|
|
@@ -15763,7 +16333,7 @@ function decideMap(name, row, table, model) {
|
|
|
15763
16333
|
` if row is None or row.get(${pyString(column)}) != HANDLER["guard"]:`,
|
|
15764
16334
|
` return {"error": ${pyString(`that ${noun} is not ${row.guard}`)}}`,
|
|
15765
16335
|
...notOwnLines(row, 'row["id"]', "decide", 4),
|
|
15766
|
-
...
|
|
16336
|
+
...isPlainObject6(row.requires) ? requiresLines(row, "{decision}") : []
|
|
15767
16337
|
);
|
|
15768
16338
|
if (readsNote) lines.push(' note = str(payload.get("note") or "").strip()');
|
|
15769
16339
|
if (requiresNote) {
|
|
@@ -15864,10 +16434,10 @@ contract, not this file.`,
|
|
|
15864
16434
|
].join("\n")
|
|
15865
16435
|
}),
|
|
15866
16436
|
save: (name, row, table, model) => {
|
|
15867
|
-
if (table?.singleton === true && !
|
|
16437
|
+
if (table?.singleton === true && !named5(row.guard) && row.not_own !== true) return singletonSave(row, table);
|
|
15868
16438
|
const enums = enumsOf(row, table);
|
|
15869
16439
|
const refs = refsOf(row, table);
|
|
15870
|
-
const guarded =
|
|
16440
|
+
const guarded = named5(row.guard);
|
|
15871
16441
|
const constants = Object.keys(enums).length > 0 ? [assignment("ENUMS", enums, Object.keys(enums))] : [];
|
|
15872
16442
|
if (Object.keys(refs).length > 0) constants.push(assignment("REFS", refs, Object.keys(refs)));
|
|
15873
16443
|
const guardDoc = guarded ? `
|
|
@@ -15901,7 +16471,7 @@ contract, not this file.`,
|
|
|
15901
16471
|
].join("\n")
|
|
15902
16472
|
};
|
|
15903
16473
|
},
|
|
15904
|
-
remove: (name, row, table, model) =>
|
|
16474
|
+
remove: (name, row, table, model) => named5(row.guard) ? guardedRemove(row, model) : {
|
|
15905
16475
|
docstring: `Delete one ${singular(row.table)}, by the id the row itself carries.
|
|
15906
16476
|
|
|
15907
16477
|
The platform's row scope is what decides whether this caller may: a delete
|
|
@@ -15950,11 +16520,11 @@ contract, not this file.`,
|
|
|
15950
16520
|
lines.push(...parentLines(row, parent, { missing: `{"error": ${pyString(`${row.by} is required`)}}`, invisible: `{"error": ${pyString(`${row.by} names no ${singular(parent)} this app can see`)}}` }));
|
|
15951
16521
|
lines.push(' row[HANDLER["by"]] = parent_id');
|
|
15952
16522
|
}
|
|
15953
|
-
if (
|
|
16523
|
+
if (isPlainObject6(row.requires)) {
|
|
15954
16524
|
constants.push(...requiresConstants(row, model));
|
|
15955
16525
|
lines.push(...requiresLines(row, "saved"));
|
|
15956
16526
|
}
|
|
15957
|
-
if (
|
|
16527
|
+
if (isPlainObject6(row.fixed) && Object.keys(row.fixed).length > 0) lines.push(' row.update(HANDLER["fixed"])');
|
|
15958
16528
|
lines.push(' return {"row": ctx.data.table(HANDLER["table"]).insert(row)}');
|
|
15959
16529
|
const linkDoc = parent === null ? "" : `
|
|
15960
16530
|
|
|
@@ -15975,7 +16545,7 @@ contract, not this file.`,
|
|
|
15975
16545
|
};
|
|
15976
16546
|
},
|
|
15977
16547
|
decide: (name, row, table, model) => {
|
|
15978
|
-
if (
|
|
16548
|
+
if (isPlainObject6(row.decision)) return decideMap(name, row, table, model);
|
|
15979
16549
|
const column = statusColumn(model, row);
|
|
15980
16550
|
const columns = table.columns ?? {};
|
|
15981
16551
|
const stamps = ["decided_by", "decided_at"].filter((one) => Object.prototype.hasOwnProperty.call(columns, one) && columns[one].computed === null);
|
|
@@ -16001,7 +16571,7 @@ contract, not this file.`,
|
|
|
16001
16571
|
// After the guard, so a row that is not there (or not in the state)
|
|
16002
16572
|
// answers what it always did, and the compiler's own fixture still does.
|
|
16003
16573
|
...notOwnLines(row, 'row["id"]', "decide", 4),
|
|
16004
|
-
...
|
|
16574
|
+
...isPlainObject6(row.requires) ? requiresLines(row, "{decision}") : [],
|
|
16005
16575
|
` patch = {${pyString(column)}: decision}`
|
|
16006
16576
|
];
|
|
16007
16577
|
if (stamps.includes("decided_by")) lines.push(' patch["decided_by"] = getattr(getattr(ctx, "user", None), "email", None)');
|
|
@@ -16073,7 +16643,7 @@ function testCase(name, row) {
|
|
|
16073
16643
|
switch (row.role) {
|
|
16074
16644
|
case "list":
|
|
16075
16645
|
case "inbox":
|
|
16076
|
-
if (
|
|
16646
|
+
if (named5(row.by)) return { name: `${name} answers with no rows when no ${row.by} is named`, payload: {}, expect: { rows: [] } };
|
|
16077
16647
|
return { name: `${name} answers with rows`, payload: {}, shape: { rows: [] } };
|
|
16078
16648
|
case "summary":
|
|
16079
16649
|
return {
|
|
@@ -16082,7 +16652,7 @@ function testCase(name, row) {
|
|
|
16082
16652
|
// An average over no rows is None, so its shape is `any`; a count
|
|
16083
16653
|
// and a sum are always numbers.
|
|
16084
16654
|
shape: Object.fromEntries(
|
|
16085
|
-
Object.entries(
|
|
16655
|
+
Object.entries(isPlainObject6(row.measures) ? row.measures : {}).map(([measure, spec]) => [
|
|
16086
16656
|
measure,
|
|
16087
16657
|
isNewMeasure(spec) && (spec.avg !== void 0 || spec.avg_days !== void 0) ? "any" : "number"
|
|
16088
16658
|
])
|
|
@@ -16091,7 +16661,7 @@ function testCase(name, row) {
|
|
|
16091
16661
|
case "submit":
|
|
16092
16662
|
case "save": {
|
|
16093
16663
|
const required = Array.isArray(row.required) ? row.required : [];
|
|
16094
|
-
if (required.length === 0 && row.role === "submit" &&
|
|
16664
|
+
if (required.length === 0 && row.role === "submit" && named5(row.by)) {
|
|
16095
16665
|
return { name: `${name} refuses a row with no ${row.by}`, payload: {}, expect: { error: `${row.by} is required` } };
|
|
16096
16666
|
}
|
|
16097
16667
|
if (required.length === 0) return { name: `${name} writes the row it is given`, payload: {}, shape: { row: {} } };
|
|
@@ -16116,21 +16686,21 @@ function testCase(name, row) {
|
|
|
16116
16686
|
}
|
|
16117
16687
|
function pageHtml(contract, page, elements, tree, model, { runtime, uiKit, charts, fmt, appWidgets, appComponents, appControls, theme = false, extra = {} }) {
|
|
16118
16688
|
const app = contract.app ?? {};
|
|
16119
|
-
const name =
|
|
16689
|
+
const name = named5(app.name) ? app.name : named5(app.id) ? app.id : "";
|
|
16120
16690
|
const title = documentTitle(page, name);
|
|
16121
16691
|
const embedded = JSON.stringify(canonical2(contract)).replace(/<\//g, "<\\/");
|
|
16122
16692
|
const bodyAttrs = [' class="dr-app-page"'];
|
|
16123
|
-
if (
|
|
16124
|
-
if (
|
|
16693
|
+
if (named5(page.theme)) bodyAttrs.push(` data-dr-theme="${attr(page.theme)}"`);
|
|
16694
|
+
if (named5(page.density)) bodyAttrs.push(` data-dr-density="${attr(page.density)}"`);
|
|
16125
16695
|
const skeleton = [];
|
|
16126
|
-
for (const band of
|
|
16696
|
+
for (const band of bandsOf2(page, elements, rootsOf2(contract, page, elements, tree))) {
|
|
16127
16697
|
if (band.section === null) {
|
|
16128
16698
|
for (const uid of band.roots) skeleton.push(...nodeLines(uid, contract, page, elements, tree, model, 0));
|
|
16129
16699
|
continue;
|
|
16130
16700
|
}
|
|
16131
16701
|
skeleton.push(`<section data-dr-section="${attr(band.section.id)}"${bandLook(band.section)}>`);
|
|
16132
|
-
if (
|
|
16133
|
-
if (
|
|
16702
|
+
if (named5(band.section.title)) skeleton.push(` <h2>${text(band.section.title)}</h2>`);
|
|
16703
|
+
if (named5(band.section.subtitle)) skeleton.push(` <p>${text(band.section.subtitle)}</p>`);
|
|
16134
16704
|
for (const uid of band.roots) skeleton.push(...nodeLines(uid, contract, page, elements, tree, model, 1));
|
|
16135
16705
|
skeleton.push("</section>");
|
|
16136
16706
|
}
|
|
@@ -16148,7 +16718,7 @@ function pageHtml(contract, page, elements, tree, model, { runtime, uiKit, chart
|
|
|
16148
16718
|
...theme ? ['<link rel="stylesheet" href="./theme.css">'] : [],
|
|
16149
16719
|
`</head><body${bodyAttrs.join("")}>`,
|
|
16150
16720
|
`<script type="application/json" id="dr-contract">${embedded}</script>`,
|
|
16151
|
-
`<main data-dr-app data-dr-page="${attr(page.path)}">`,
|
|
16721
|
+
`<main data-dr-app data-dr-page="${attr(page.path)}"${shellAttr(app)}${hasNav(page, elements) ? "" : navCountsAttr(page)}>`,
|
|
16152
16722
|
...skeleton,
|
|
16153
16723
|
"</main>",
|
|
16154
16724
|
// P1/P1b stylesheets, in the body after </main> for the reason the
|
|
@@ -16190,7 +16760,7 @@ function chartScripts(page, elements, { charts, fmt }) {
|
|
|
16190
16760
|
function platformNumbersOn(page, elements) {
|
|
16191
16761
|
const states = (node) => {
|
|
16192
16762
|
if (Array.isArray(node)) return node.some(states);
|
|
16193
|
-
if (!
|
|
16763
|
+
if (!isPlainObject6(node)) return false;
|
|
16194
16764
|
return Object.entries(node).some(
|
|
16195
16765
|
([key, value]) => key === "format" && isPlatformFormat(value) || key === "negative" && NEGATIVE_MODES2.includes(value) || states(value)
|
|
16196
16766
|
);
|
|
@@ -16229,7 +16799,7 @@ function iconsOn(page, elements, extra) {
|
|
|
16229
16799
|
for (const element of Object.values(elements)) {
|
|
16230
16800
|
if (element?.page !== page.path) continue;
|
|
16231
16801
|
if (ICON_RE.test(JSON.stringify(element.props ?? {}))) return true;
|
|
16232
|
-
if (element.type !== "custom" || !
|
|
16802
|
+
if (element.type !== "custom" || !named5(element.props?.module)) continue;
|
|
16233
16803
|
const source = extra[`ui/${element.props.module}`];
|
|
16234
16804
|
if (typeof source === "string" && ICON_RE.test(source)) return true;
|
|
16235
16805
|
}
|
|
@@ -16238,12 +16808,12 @@ function iconsOn(page, elements, extra) {
|
|
|
16238
16808
|
function moduleScripts(page, elements) {
|
|
16239
16809
|
const modules = /* @__PURE__ */ new Set();
|
|
16240
16810
|
for (const element of Object.values(elements)) {
|
|
16241
|
-
if (element?.page === page.path && element.type === "custom" &&
|
|
16811
|
+
if (element?.page === page.path && element.type === "custom" && named5(element.props?.module)) modules.add(element.props.module);
|
|
16242
16812
|
}
|
|
16243
16813
|
return [...modules].sort(natural3).map((module) => `<script src="./${attr(module)}"></script>`);
|
|
16244
16814
|
}
|
|
16245
|
-
function
|
|
16246
|
-
const sections = (Array.isArray(page.sections) ? page.sections : []).filter((entry) =>
|
|
16815
|
+
function bandsOf2(page, elements, roots) {
|
|
16816
|
+
const sections = (Array.isArray(page.sections) ? page.sections : []).filter((entry) => named5(entry?.id));
|
|
16247
16817
|
if (sections.length === 0) return [{ section: null, roots }];
|
|
16248
16818
|
const ids = new Set(sections.map((entry) => entry.id));
|
|
16249
16819
|
const bands = [{ section: null, roots: roots.filter((uid) => !ids.has(elements[uid]?.section)) }];
|
|
@@ -16252,16 +16822,16 @@ function bandsOf(page, elements, roots) {
|
|
|
16252
16822
|
}
|
|
16253
16823
|
return bands;
|
|
16254
16824
|
}
|
|
16255
|
-
var
|
|
16825
|
+
var BAND_LOOK3 = ["layout", "surface", "aside"];
|
|
16256
16826
|
function bandLook(section) {
|
|
16257
|
-
return
|
|
16827
|
+
return BAND_LOOK3.filter((key) => named5(section[key])).map((key) => ` data-dr-${key}="${attr(section[key])}"`).join("");
|
|
16258
16828
|
}
|
|
16259
16829
|
function documentTitle(page, appName) {
|
|
16260
16830
|
const own = shortTitle(page, appName);
|
|
16261
16831
|
return appName === "" || own === appName ? own : `${own} \xB7 ${appName}`;
|
|
16262
16832
|
}
|
|
16263
16833
|
function shortTitle(page, appName) {
|
|
16264
|
-
const raw =
|
|
16834
|
+
const raw = named5(page.title) ? page.title : named5(page.header?.title) ? page.header.title : page.path;
|
|
16265
16835
|
const suffix = ` \xB7 ${appName}`;
|
|
16266
16836
|
return appName !== "" && raw.endsWith(suffix) ? raw.slice(0, -suffix.length) : raw;
|
|
16267
16837
|
}
|
|
@@ -16269,7 +16839,7 @@ function routeOf2(path3) {
|
|
|
16269
16839
|
const file = String(path3).split("/").pop();
|
|
16270
16840
|
return file === "index.html" ? "/" : `/${file}`;
|
|
16271
16841
|
}
|
|
16272
|
-
function
|
|
16842
|
+
function rootsOf2(contract, page, elements, tree) {
|
|
16273
16843
|
if (Array.isArray(page.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid]);
|
|
16274
16844
|
return Object.keys(elements).filter((uid) => elements[uid].page === page.path && parentOf(tree, uid) === null).sort((a, b) => (elements[a].order ?? 0) - (elements[b].order ?? 0) || natural3(a, b));
|
|
16275
16845
|
}
|
|
@@ -16280,12 +16850,12 @@ function childrenOf(uid, elements, tree) {
|
|
|
16280
16850
|
}
|
|
16281
16851
|
function pageRole(contract, page, elements, tree) {
|
|
16282
16852
|
if (gateRoles(page.roles).length > 0) return pageGateKey(page.roles);
|
|
16283
|
-
if (
|
|
16853
|
+
if (named5(page.role)) return page.role;
|
|
16284
16854
|
const roles = /* @__PURE__ */ new Set();
|
|
16285
|
-
for (const uid of
|
|
16855
|
+
for (const uid of rootsOf2(contract, page, elements, tree)) {
|
|
16286
16856
|
const element = elements[uid];
|
|
16287
16857
|
if (!COMPONENT_TYPES2.includes(element.type)) continue;
|
|
16288
|
-
roles.add(
|
|
16858
|
+
roles.add(named5(element.props?.role) ? element.props.role : null);
|
|
16289
16859
|
}
|
|
16290
16860
|
return roles.size === 1 && !roles.has(null) ? [...roles][0] : null;
|
|
16291
16861
|
}
|
|
@@ -16298,9 +16868,13 @@ function nodeLines(uid, contract, page, elements, tree, model, depth) {
|
|
|
16298
16868
|
case "region": {
|
|
16299
16869
|
if ((element.role ?? "header") !== "header") return [`${pad}<section data-dr-kit="${attr(element.role ?? "")}"></section>`];
|
|
16300
16870
|
const header = page.header ?? {};
|
|
16301
|
-
const heading =
|
|
16302
|
-
const lede =
|
|
16303
|
-
|
|
16871
|
+
const heading = named5(header.title) ? header.title : shortTitle(page, contract.app?.name ?? "");
|
|
16872
|
+
const lede = named5(header.summary) ? `<p>${text(header.summary)}</p>` : "";
|
|
16873
|
+
const actions = Array.isArray(header.actions) && header.actions.length > 0 ? ` data-dr-header-actions="${attr(JSON.stringify(header.actions))}"` : "";
|
|
16874
|
+
const from = named5(header.title_from) ? ` data-dr-title-from="${attr(header.title_from)}"` : "";
|
|
16875
|
+
const open2 = `${pad}<header data-dr-kit="header"${gridAttrs(element)}${actions}${from}><h1>${text(heading)}</h1>${lede}`;
|
|
16876
|
+
if (kids.length === 0) return [`${open2}</header>`];
|
|
16877
|
+
return [open2, ...kids.flatMap((child) => nodeLines(child, contract, page, elements, tree, model, depth + 1)), `${pad}</header>`];
|
|
16304
16878
|
}
|
|
16305
16879
|
case "nav": {
|
|
16306
16880
|
const entries = (contract.pages ?? []).map((one) => {
|
|
@@ -16309,7 +16883,7 @@ function nodeLines(uid, contract, page, elements, tree, model, depth) {
|
|
|
16309
16883
|
const gate = role === null ? "" : ` data-dr-role="${attr(role)}"`;
|
|
16310
16884
|
return `<a href="./${attr(String(one.path).split("/").pop())}"${gate}${current}>${text(shortTitle(one, contract.app?.name ?? ""))}</a>`;
|
|
16311
16885
|
});
|
|
16312
|
-
return [`${pad}<nav data-dr-kit="nav"${gridAttrs(element)}>${entries.join("")}</nav>`];
|
|
16886
|
+
return [`${pad}<nav data-dr-kit="nav"${gridAttrs(element)}${navCountsAttr(page)}>${entries.join("")}</nav>`];
|
|
16313
16887
|
}
|
|
16314
16888
|
case "stat":
|
|
16315
16889
|
case "chart":
|
|
@@ -16381,7 +16955,7 @@ function fieldInput(uid, elements, tree, model, depth) {
|
|
|
16381
16955
|
const column = (model.tables[entity]?.columns ?? {})[props.field] ?? null;
|
|
16382
16956
|
const label = labelOf(element);
|
|
16383
16957
|
const required = props.required === true ? " required" : "";
|
|
16384
|
-
const picker =
|
|
16958
|
+
const picker = named5(column?.ref) && named5(props.source);
|
|
16385
16959
|
const kind = picker ? "select" : props.input_type ?? (column?.enum ? "select" : isNumberType(column?.type) ? "number" : "text");
|
|
16386
16960
|
if (kind === "select") {
|
|
16387
16961
|
const options = picker ? [] : Array.isArray(props.options) && props.options.length > 0 ? props.options : column?.enum ?? [];
|
|
@@ -16397,12 +16971,12 @@ function fieldInput(uid, elements, tree, model, depth) {
|
|
|
16397
16971
|
}
|
|
16398
16972
|
function labelOf(element) {
|
|
16399
16973
|
const props = element.props ?? {};
|
|
16400
|
-
if (
|
|
16401
|
-
if (
|
|
16402
|
-
if (
|
|
16974
|
+
if (named5(props.label)) return props.label;
|
|
16975
|
+
if (named5(props.field)) return humanise2(props.field);
|
|
16976
|
+
if (named5(props.id)) return humanise2(props.id);
|
|
16403
16977
|
return "";
|
|
16404
16978
|
}
|
|
16405
|
-
function
|
|
16979
|
+
function humanise2(name) {
|
|
16406
16980
|
const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
|
|
16407
16981
|
return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
|
|
16408
16982
|
}
|
|
@@ -16419,7 +16993,7 @@ function attrsOf(element) {
|
|
|
16419
16993
|
const value = props[key];
|
|
16420
16994
|
if (value === void 0 || value === null || value === "") continue;
|
|
16421
16995
|
if (key === "filter") {
|
|
16422
|
-
if (!
|
|
16996
|
+
if (!isPlainObject6(value)) continue;
|
|
16423
16997
|
const written = Object.keys(value).sort().map((column) => `${column}=${value[column]}`).join(",");
|
|
16424
16998
|
if (written !== "") out.push(["filter", written]);
|
|
16425
16999
|
continue;
|
|
@@ -16449,11 +17023,11 @@ function attrsOf(element) {
|
|
|
16449
17023
|
}
|
|
16450
17024
|
const card = writeArgs(element.card);
|
|
16451
17025
|
if (card !== "") out.push(["card", card]);
|
|
16452
|
-
if (
|
|
17026
|
+
if (named5(element.emphasis)) out.push(["emphasis", element.emphasis]);
|
|
16453
17027
|
return gridAttrs(element) + out.map(([key, value]) => ` data-dr-${key}="${attr(value)}"`).join("");
|
|
16454
17028
|
}
|
|
16455
17029
|
function gridAttrs(element) {
|
|
16456
|
-
const grid =
|
|
17030
|
+
const grid = isPlainObject6(element.grid) ? element.grid : {};
|
|
16457
17031
|
let out = "";
|
|
16458
17032
|
if (grid.span !== null && grid.span !== void 0) out += ` data-dr-span="${attr(grid.span)}"`;
|
|
16459
17033
|
if (grid.rows !== null && grid.rows !== void 0) out += ` data-dr-rows="${attr(grid.rows)}"`;
|
|
@@ -16469,13 +17043,13 @@ function attr(value) {
|
|
|
16469
17043
|
function text(value) {
|
|
16470
17044
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
16471
17045
|
}
|
|
16472
|
-
function
|
|
17046
|
+
function named5(value) {
|
|
16473
17047
|
return typeof value === "string" && value !== "";
|
|
16474
17048
|
}
|
|
16475
17049
|
function stated(value) {
|
|
16476
17050
|
return typeof value === "string" && value.trim() !== "";
|
|
16477
17051
|
}
|
|
16478
|
-
function
|
|
17052
|
+
function isPlainObject6(value) {
|
|
16479
17053
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16480
17054
|
}
|
|
16481
17055
|
function escapePointer3(key) {
|
|
@@ -18280,11 +18854,11 @@ function composeLayout(contract) {
|
|
|
18280
18854
|
const out = clone4(contract);
|
|
18281
18855
|
const applied = [];
|
|
18282
18856
|
const record2 = (what, where2, message) => applied.push({ what, where: where2, message });
|
|
18283
|
-
const app =
|
|
18857
|
+
const app = isPlainObject7(out.app) ? out.app : out.app = {};
|
|
18284
18858
|
const pages = Array.isArray(out.pages) ? out.pages : out.pages = [];
|
|
18285
|
-
const elements =
|
|
18859
|
+
const elements = isPlainObject7(out.elements) ? out.elements : out.elements = {};
|
|
18286
18860
|
const preset = presetOf(app, pages);
|
|
18287
|
-
if (
|
|
18861
|
+
if (isPlainObject7(app.theme) && "preset" in app.theme) delete app.theme.preset;
|
|
18288
18862
|
for (const page of pages) {
|
|
18289
18863
|
const path3 = page.path ?? "ui/index.html";
|
|
18290
18864
|
if (page.theme === void 0 || page.theme === null) {
|
|
@@ -18352,7 +18926,7 @@ function composeSpans(elements, record2) {
|
|
|
18352
18926
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
18353
18927
|
const element = elements[uid];
|
|
18354
18928
|
if (element?.type !== "widget") continue;
|
|
18355
|
-
const grid =
|
|
18929
|
+
const grid = isPlainObject7(element.grid) ? element.grid : null;
|
|
18356
18930
|
if (grid && grid.span !== null && grid.span !== void 0) continue;
|
|
18357
18931
|
const kind = kindOf2(element);
|
|
18358
18932
|
const span = SPAN_BY_KIND[kind] ?? DEFAULT_SPAN;
|
|
@@ -18362,8 +18936,8 @@ function composeSpans(elements, record2) {
|
|
|
18362
18936
|
}
|
|
18363
18937
|
function composeTheme(app, preset, record2) {
|
|
18364
18938
|
const table = THEME_TOKENS[preset] ?? THEME_TOKENS[DEFAULT_PRESET];
|
|
18365
|
-
const theme =
|
|
18366
|
-
const tokens =
|
|
18939
|
+
const theme = isPlainObject7(app.theme) ? app.theme : {};
|
|
18940
|
+
const tokens = isPlainObject7(theme.tokens) ? { ...theme.tokens } : {};
|
|
18367
18941
|
let filled = 0;
|
|
18368
18942
|
for (const [token, value] of Object.entries(table)) {
|
|
18369
18943
|
if (token in tokens) continue;
|
|
@@ -18380,7 +18954,7 @@ function composeTheme(app, preset, record2) {
|
|
|
18380
18954
|
}
|
|
18381
18955
|
}
|
|
18382
18956
|
function presetOf(app, pages) {
|
|
18383
|
-
const asked =
|
|
18957
|
+
const asked = isPlainObject7(app.theme) ? app.theme.preset : null;
|
|
18384
18958
|
if (typeof asked === "string" && PRESETS2.includes(asked)) return asked;
|
|
18385
18959
|
for (const page of pages) {
|
|
18386
18960
|
if (typeof page?.theme === "string" && PRESETS2.includes(page.theme)) return page.theme;
|
|
@@ -18388,7 +18962,7 @@ function presetOf(app, pages) {
|
|
|
18388
18962
|
return DEFAULT_PRESET;
|
|
18389
18963
|
}
|
|
18390
18964
|
function kindOf2(element) {
|
|
18391
|
-
const props =
|
|
18965
|
+
const props = isPlainObject7(element?.props) ? element.props : {};
|
|
18392
18966
|
if (typeof props.kind === "string" && props.kind !== "") return props.kind;
|
|
18393
18967
|
return typeof element?.role === "string" ? element.role : "";
|
|
18394
18968
|
}
|
|
@@ -18397,7 +18971,7 @@ function routeFor2(path3) {
|
|
|
18397
18971
|
return clean === "index.html" ? "/" : `/${clean}`;
|
|
18398
18972
|
}
|
|
18399
18973
|
function onlyHandler(app) {
|
|
18400
|
-
const names =
|
|
18974
|
+
const names = isPlainObject7(app.handlers) ? Object.keys(app.handlers) : [];
|
|
18401
18975
|
return names.length === 1 ? names[0] : "dashboard";
|
|
18402
18976
|
}
|
|
18403
18977
|
function sortKeys(object) {
|
|
@@ -18407,14 +18981,14 @@ function sortKeys(object) {
|
|
|
18407
18981
|
}
|
|
18408
18982
|
function clone4(value) {
|
|
18409
18983
|
if (Array.isArray(value)) return value.map(clone4);
|
|
18410
|
-
if (
|
|
18984
|
+
if (isPlainObject7(value)) {
|
|
18411
18985
|
const out = {};
|
|
18412
18986
|
for (const [key, held] of Object.entries(value)) out[key] = clone4(held);
|
|
18413
18987
|
return out;
|
|
18414
18988
|
}
|
|
18415
18989
|
return value;
|
|
18416
18990
|
}
|
|
18417
|
-
function
|
|
18991
|
+
function isPlainObject7(value) {
|
|
18418
18992
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18419
18993
|
}
|
|
18420
18994
|
|
|
@@ -18433,13 +19007,13 @@ var WIDGET_FILE = "ui/widget.js";
|
|
|
18433
19007
|
var MANIFEST_FILE = "manifest.json";
|
|
18434
19008
|
var PROP_ORDER = ["kind", "params", "title", "subtitle", "description", "source"];
|
|
18435
19009
|
function compile(contract, { kit, extra = {} } = {}) {
|
|
18436
|
-
if (!
|
|
19010
|
+
if (!isPlainObject8(kit)) throw new TypeError("compile(contract, {kit}): the kit manifest (kit.json) says which shelf modules a page loads");
|
|
18437
19011
|
const problems = [];
|
|
18438
19012
|
const refuse = (code, where2, message) => {
|
|
18439
19013
|
problems.push({ code, where: where2, lossy: false, message });
|
|
18440
19014
|
return { files: {}, problems };
|
|
18441
19015
|
};
|
|
18442
|
-
const source =
|
|
19016
|
+
const source = isPlainObject8(contract) ? contract : {};
|
|
18443
19017
|
const kind = source.app?.kind ?? null;
|
|
18444
19018
|
if (kind !== "dashboard") {
|
|
18445
19019
|
return refuse(
|
|
@@ -18465,11 +19039,11 @@ function compile(contract, { kit, extra = {} } = {}) {
|
|
|
18465
19039
|
);
|
|
18466
19040
|
}
|
|
18467
19041
|
const handlerName = handlers[0];
|
|
18468
|
-
const library =
|
|
19042
|
+
const library = isPlainObject8(composed.data?.library) ? composed.data.library : null;
|
|
18469
19043
|
const written = writeSpec(composed.data ?? {}, {
|
|
18470
19044
|
library: {
|
|
18471
19045
|
module: library?.module ?? LIBRARY?.module ?? DEFAULT_LIBRARY_MODULE,
|
|
18472
|
-
...
|
|
19046
|
+
...isPlainObject8(LIBRARY?.functions) ? { functions: LIBRARY.functions } : {}
|
|
18473
19047
|
}
|
|
18474
19048
|
});
|
|
18475
19049
|
if (written.text === null) {
|
|
@@ -18502,17 +19076,17 @@ function compile(contract, { kit, extra = {} } = {}) {
|
|
|
18502
19076
|
return { files, problems: problems.sort(byProblem2) };
|
|
18503
19077
|
}
|
|
18504
19078
|
function describeTier(data) {
|
|
18505
|
-
if (!
|
|
19079
|
+
if (!isPlainObject8(data)) return { where: "handlers/", message: "the contract carries no data section; a dashboard compiles from the SPEC its handler declares" };
|
|
18506
19080
|
if (data.source !== "static") {
|
|
18507
19081
|
return {
|
|
18508
19082
|
where: "/data/source",
|
|
18509
19083
|
message: `data.source is ${JSON.stringify(data.source ?? null)}; only a "static" section is the SPEC a handler declares, and a describe answer is what one SAYS at runtime`
|
|
18510
19084
|
};
|
|
18511
19085
|
}
|
|
18512
|
-
const widgets =
|
|
19086
|
+
const widgets = isPlainObject8(data.widgets) ? data.widgets : {};
|
|
18513
19087
|
for (const key of Object.keys(widgets).sort(natural3)) {
|
|
18514
19088
|
const row = widgets[key];
|
|
18515
|
-
if (!
|
|
19089
|
+
if (!isPlainObject8(row)) {
|
|
18516
19090
|
return { where: `/data/widgets/${key}`, message: `the row for "${key}" is ${row === null ? "null" : typeof row}; a SPEC row a compiler can write is an object` };
|
|
18517
19091
|
}
|
|
18518
19092
|
if ("_described" in row) {
|
|
@@ -18526,9 +19100,9 @@ function describeTier(data) {
|
|
|
18526
19100
|
}
|
|
18527
19101
|
function missingExpressions(widgets) {
|
|
18528
19102
|
const problems = [];
|
|
18529
|
-
for (const key of Object.keys(
|
|
19103
|
+
for (const key of Object.keys(isPlainObject8(widgets) ? widgets : {}).sort(natural3)) {
|
|
18530
19104
|
const row = widgets[key];
|
|
18531
|
-
if (!
|
|
19105
|
+
if (!isPlainObject8(row) || typeof row.expr === "string") continue;
|
|
18532
19106
|
const transformed = typeof row.transform === "string" && row.transform !== "";
|
|
18533
19107
|
if (!("compute" in row) && !transformed) continue;
|
|
18534
19108
|
problems.push({
|
|
@@ -18554,7 +19128,7 @@ function manifestFile(contract, handlerName) {
|
|
|
18554
19128
|
...app.nav_label ? { nav: { label: app.nav_label } } : {},
|
|
18555
19129
|
routes: Array.isArray(app.routes) ? app.routes : [],
|
|
18556
19130
|
handlers: { [handlerName]: { entrypoint: handlerPath(contract, handlerName), http: handlerHttp(contract, handlerName) } },
|
|
18557
|
-
capabilities:
|
|
19131
|
+
capabilities: isPlainObject8(app.capabilities) ? app.capabilities : { ...DEFAULT_CAPABILITIES }
|
|
18558
19132
|
};
|
|
18559
19133
|
return `${JSON.stringify(manifest, null, 2)}
|
|
18560
19134
|
`;
|
|
@@ -18584,8 +19158,8 @@ function themeFile(contract) {
|
|
|
18584
19158
|
}
|
|
18585
19159
|
function widgetFile(contract, pages, handlerName) {
|
|
18586
19160
|
const mount = pages.find((page) => page.mount?.handler === handlerName)?.mount ?? {};
|
|
18587
|
-
const defaults =
|
|
18588
|
-
const filters = defaults !== null &&
|
|
19161
|
+
const defaults = isPlainObject8(mount.defaults) ? mount.defaults : null;
|
|
19162
|
+
const filters = defaults !== null && isPlainObject8(defaults.filters) ? defaults.filters : null;
|
|
18589
19163
|
const rest = defaults === null ? {} : Object.fromEntries(Object.entries(defaults).filter(([key]) => key !== "filters"));
|
|
18590
19164
|
const lines = [
|
|
18591
19165
|
"/* The dashboard hands itself to the widget library.",
|
|
@@ -18703,7 +19277,7 @@ function pageFile(page, ctx) {
|
|
|
18703
19277
|
`;
|
|
18704
19278
|
}
|
|
18705
19279
|
function elementsOf(contract, path3) {
|
|
18706
|
-
const elements =
|
|
19280
|
+
const elements = isPlainObject8(contract.elements) ? contract.elements : {};
|
|
18707
19281
|
return Object.keys(elements).sort(natural3).map((uid) => ({ uid, ...elements[uid] })).filter((element) => element.page === path3);
|
|
18708
19282
|
}
|
|
18709
19283
|
function shelf(path3, kit, charts, contract, problems) {
|
|
@@ -18714,6 +19288,9 @@ function shelf(path3, kit, charts, contract, problems) {
|
|
|
18714
19288
|
{ path: `vendor/${kit.assistant}/insights.css`, rel: "stylesheet" },
|
|
18715
19289
|
{ path: `vendor/${kit.widgets}/widgets.css`, rel: "stylesheet" },
|
|
18716
19290
|
...charts ? [{ path: `vendor/${kit.charts}/echarts.min.js`, rel: "script" }] : [],
|
|
19291
|
+
// Before the assistant: the drawer renders a reply through `window.drMd`
|
|
19292
|
+
// when it is there and prints the raw markdown when it is not.
|
|
19293
|
+
...kit.markdown ? [{ path: `vendor/${kit.markdown}/md.js`, rel: "script" }] : [],
|
|
18717
19294
|
{ path: `vendor/${kit.assistant}/insights.js`, rel: "script" },
|
|
18718
19295
|
{ path: `vendor/${kit.fmt}/fmt.js`, rel: "script" },
|
|
18719
19296
|
{ path: `vendor/${kit.widgets}/widgets.js`, rel: "script" }
|
|
@@ -18747,7 +19324,7 @@ function shelf(path3, kit, charts, contract, problems) {
|
|
|
18747
19324
|
};
|
|
18748
19325
|
}
|
|
18749
19326
|
function appbar(page, elements) {
|
|
18750
|
-
const header =
|
|
19327
|
+
const header = isPlainObject8(page.header) ? page.header : { title: page.title ?? "", summary: null };
|
|
18751
19328
|
const summary = typeof header.summary === "string" && header.summary !== "" ? `${text2(header.summary)} ` : "";
|
|
18752
19329
|
return [
|
|
18753
19330
|
`<header class="${classesOf2(elements, "region", "header", ["dr-appbar"])}" data-dr-kit="header">`,
|
|
@@ -18807,7 +19384,7 @@ function controlRow(elements) {
|
|
|
18807
19384
|
if (controls.length === 0) return [];
|
|
18808
19385
|
const out = [`<div class="${classesOf2(elements, "region", "controls", ["dr-controlrow"])}" data-dr-kit="controls">`];
|
|
18809
19386
|
for (const control of controls) {
|
|
18810
|
-
const props =
|
|
19387
|
+
const props = isPlainObject8(control.props) ? control.props : {};
|
|
18811
19388
|
const id = control.id;
|
|
18812
19389
|
out.push(' <span class="drw-field--inline">');
|
|
18813
19390
|
if (id && typeof props.label === "string") {
|
|
@@ -18853,13 +19430,13 @@ function order(element) {
|
|
|
18853
19430
|
return Number.isInteger(element.order) ? element.order : Number.MAX_SAFE_INTEGER;
|
|
18854
19431
|
}
|
|
18855
19432
|
function cardBlock(card) {
|
|
18856
|
-
const props =
|
|
19433
|
+
const props = isPlainObject8(card.props) ? card.props : {};
|
|
18857
19434
|
const kind = kindOf2(card);
|
|
18858
19435
|
const classes = (card.classes ?? []).length > 0 ? card.classes.join(" ") : kind === "kpi" ? "dr-card dr-card--kpi" : "dr-card";
|
|
18859
|
-
const slots =
|
|
19436
|
+
const slots = isPlainObject8(card.slots) && Object.keys(card.slots).length > 0 ? card.slots : { actions: true, body: "widget-body" };
|
|
18860
19437
|
const bodyClass = typeof slots.body === "string" ? slots.body : "widget-body";
|
|
18861
19438
|
const attrs = [`class="${attr2(classes)}"`];
|
|
18862
|
-
const grid =
|
|
19439
|
+
const grid = isPlainObject8(card.grid) ? card.grid : {};
|
|
18863
19440
|
if (grid.span !== null && grid.span !== void 0) attrs.push(`data-dr-span="${attr2(grid.span)}"`);
|
|
18864
19441
|
if (grid.rows !== null && grid.rows !== void 0) attrs.push(`data-dr-rows="${attr2(grid.rows)}"`);
|
|
18865
19442
|
attrs.push(`data-dr-widget="${attr2(card.id ?? "")}"`);
|
|
@@ -18920,7 +19497,7 @@ function jsLiteral(value, depth) {
|
|
|
18920
19497
|
if (typeof value === "string") return jsString(value);
|
|
18921
19498
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
18922
19499
|
if (Array.isArray(value)) return `[${value.map((item) => jsLiteral(item, depth + 1)).join(", ")}]`;
|
|
18923
|
-
if (
|
|
19500
|
+
if (isPlainObject8(value)) {
|
|
18924
19501
|
const parts = Object.keys(value).sort(natural3).map((key) => `${/^[A-Za-z_$][\w$]*$/.test(key) ? key : jsString(key)}: ${jsLiteral(value[key], depth + 1)}`);
|
|
18925
19502
|
return parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
|
|
18926
19503
|
}
|
|
@@ -18932,7 +19509,7 @@ function pyString2(value) {
|
|
|
18932
19509
|
function byProblem2(a, b) {
|
|
18933
19510
|
return natural3(a.code, b.code) || natural3(a.where ?? "", b.where ?? "") || natural3(a.message, b.message);
|
|
18934
19511
|
}
|
|
18935
|
-
function
|
|
19512
|
+
function isPlainObject8(value) {
|
|
18936
19513
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18937
19514
|
}
|
|
18938
19515
|
|
|
@@ -18941,7 +19518,7 @@ var POSITIONAL_KEYS = ["line", "order", "depth", "path"];
|
|
|
18941
19518
|
var POSITIONAL_GRID_KEYS = ["uid", "position", "of"];
|
|
18942
19519
|
function stripPositional(contract, { keepTree = true, order: order2 } = {}) {
|
|
18943
19520
|
const out = clone5(contract);
|
|
18944
|
-
const elements =
|
|
19521
|
+
const elements = isPlainObject9(out.elements) ? out.elements : {};
|
|
18945
19522
|
const pagesByPath = new Map((Array.isArray(out.pages) ? out.pages : []).map((page) => [page.path, page]));
|
|
18946
19523
|
const byPage = /* @__PURE__ */ new Map();
|
|
18947
19524
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
@@ -18964,7 +19541,7 @@ function stripPositional(contract, { keepTree = true, order: order2 } = {}) {
|
|
|
18964
19541
|
element.uid = next;
|
|
18965
19542
|
if (element.parent !== void 0) element.parent = element.parent === null ? null : rename2(element.parent);
|
|
18966
19543
|
if (Array.isArray(element.children)) element.children = element.children.map(rename2).sort(natural3);
|
|
18967
|
-
if (
|
|
19544
|
+
if (isPlainObject9(element.grid)) for (const key of POSITIONAL_GRID_KEYS) delete element.grid[key];
|
|
18968
19545
|
rekeyed[next] = element;
|
|
18969
19546
|
}
|
|
18970
19547
|
out.elements = sortKeys2(rekeyed);
|
|
@@ -19048,7 +19625,7 @@ function rootsAmong(elements, uids) {
|
|
|
19048
19625
|
function renameDeep(value, rename2) {
|
|
19049
19626
|
if (typeof value === "string") return rename2(value);
|
|
19050
19627
|
if (Array.isArray(value)) return value.map((item) => renameDeep(item, rename2));
|
|
19051
|
-
if (
|
|
19628
|
+
if (isPlainObject9(value)) {
|
|
19052
19629
|
const out = {};
|
|
19053
19630
|
for (const [key, held] of Object.entries(value)) out[key] = renameDeep(held, rename2);
|
|
19054
19631
|
return out;
|
|
@@ -19062,14 +19639,14 @@ function sortKeys2(object) {
|
|
|
19062
19639
|
}
|
|
19063
19640
|
function clone5(value) {
|
|
19064
19641
|
if (Array.isArray(value)) return value.map(clone5);
|
|
19065
|
-
if (
|
|
19642
|
+
if (isPlainObject9(value)) {
|
|
19066
19643
|
const out = {};
|
|
19067
19644
|
for (const [key, held] of Object.entries(value)) out[key] = clone5(held);
|
|
19068
19645
|
return out;
|
|
19069
19646
|
}
|
|
19070
19647
|
return value;
|
|
19071
19648
|
}
|
|
19072
|
-
function
|
|
19649
|
+
function isPlainObject9(value) {
|
|
19073
19650
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19074
19651
|
}
|
|
19075
19652
|
|
|
@@ -19247,6 +19824,7 @@ var CONTROL_MARK = "data-dr-control";
|
|
|
19247
19824
|
var CONTROL_KIND = "data-dr-control-kind";
|
|
19248
19825
|
var CONTROL_BOXES = /* @__PURE__ */ new Set(["controls", "filters"]);
|
|
19249
19826
|
var WIDGET_LIBRARY2 = /^vendor\/dr-widgets@\d+\.\d+\.\d+\/widgets\.js$/;
|
|
19827
|
+
var ASSISTANT_MODULE = /^vendor\/dr-insights@(\d+\.\d+\.\d+)\/(insights\.(?:css|js))$/;
|
|
19250
19828
|
var WIDGET_ID = /^[a-z][a-z0-9_-]*$/;
|
|
19251
19829
|
var REQUIRED_WIDGET_ATTRS = ["data-dr-widget-title", "data-dr-widget-description", "data-dr-widget-source"];
|
|
19252
19830
|
var SPAN_MARK = "data-dr-span";
|
|
@@ -19326,12 +19904,22 @@ function checkPage2(app, page, kit) {
|
|
|
19326
19904
|
const css = `vendor/${kit.assistant}/insights.css`;
|
|
19327
19905
|
const js = `vendor/${kit.assistant}/insights.js`;
|
|
19328
19906
|
const loads = (tag, attr3, target) => elements.some((el) => el.tag === tag && resolveFromPlatformRoot(page.path, el.attrs[attr3]) === target);
|
|
19329
|
-
|
|
19907
|
+
const versionsLoaded = (tag, attr3, file) => elements.filter((el) => el.tag === tag).map((el) => ASSISTANT_MODULE.exec(resolveFromPlatformRoot(page.path, el.attrs[attr3]) ?? "")).filter((match) => match && match[2] === file).map((match) => match[1]);
|
|
19908
|
+
const cssVersions = versionsLoaded("link", "href", "insights.css");
|
|
19909
|
+
const jsVersions = versionsLoaded("script", "src", "insights.js");
|
|
19910
|
+
if (cssVersions.length === 0) {
|
|
19330
19911
|
violations.push({ rule: "R3.1", where: page.path, message: `${page.path} loads no stylesheet ${climb}${css}` });
|
|
19331
19912
|
}
|
|
19332
|
-
if (
|
|
19913
|
+
if (jsVersions.length === 0) {
|
|
19333
19914
|
violations.push({ rule: "R3.1", where: page.path, message: `${page.path} loads no script ${climb}${js}; without it no widget has an Ask button` });
|
|
19334
19915
|
}
|
|
19916
|
+
if (cssVersions.length > 0 && jsVersions.length > 0 && !jsVersions.some((version) => cssVersions.includes(version))) {
|
|
19917
|
+
violations.push({
|
|
19918
|
+
rule: "R3.1",
|
|
19919
|
+
where: page.path,
|
|
19920
|
+
message: `${page.path} loads the dr-insights script at ${jsVersions.join(", ")} and its stylesheet at ${cssVersions.join(", ")}; load both from one release (this kit writes ${kit.assistant})`
|
|
19921
|
+
});
|
|
19922
|
+
}
|
|
19335
19923
|
const uiKit = `${kit.uiKit}/dr.css`;
|
|
19336
19924
|
if (!loads("link", "href", uiKit)) {
|
|
19337
19925
|
violations.push({ rule: "R2.7", where: page.path, message: `${page.path} loads no stylesheet ${climb}${uiKit}; the grid, the card, the tab bar and every token come from it` });
|
|
@@ -20212,7 +20800,7 @@ var CHART_FORMATS4 = Object.freeze(["currency", "number", "percent"]);
|
|
|
20212
20800
|
var PLATFORM_FORMAT2 = Object.freeze({ "type": "string", "maxLength": 64, "pattern": "^(General|[^\\u0000-\\u001f]*[#0][^\\u0000-\\u001f]*)$" });
|
|
20213
20801
|
var NEGATIVE_MODES3 = Object.freeze(["minus", "red_minus", "absolute", "red_absolute"]);
|
|
20214
20802
|
var FORMAT_PATTERNS3 = Object.freeze({ "currency": "$#,###", "number": "#,###", "percent": '#,###.#"%"' });
|
|
20215
|
-
var PLATFORM_NUMBERS3 = Object.freeze({ "dr-app-runtime": "1.
|
|
20803
|
+
var PLATFORM_NUMBERS3 = Object.freeze({ "dr-app-runtime": "1.9.0", "dr-app-widgets": "1.2.0", "dr-components": "1.2.0", "dr-components-controls": "1.1.0" });
|
|
20216
20804
|
var ROW_ACTION_WHEN3 = Object.freeze({ "dr-components": "1.2.0" });
|
|
20217
20805
|
var PROPS_SINCE3 = Object.freeze({ "select": { "multiple": "1.3.0", "searchable": "1.3.0", "group_by": "1.3.0", "values": "1.3.0", "options[].group": "1.3.0" }, "data_grid": { "theme": "1.3.0", "hierarchy": "1.3.0", "totals": "1.3.0", "pin": "1.3.0", "csv_params": "1.3.0", "columns[].cell": "1.3.0", "columns[].badge_map": "1.3.0", "columns[].status_map": "1.3.0", "columns[].link_field": "1.3.0", "columns[].call": "1.3.0" } });
|
|
20218
20806
|
var MODULE_OF3 = Object.freeze({ "widget": "dr-app-widgets", "form": "dr-app-runtime", "list": "dr-app-runtime", "detail": "dr-app-runtime", "queue": "dr-app-runtime", "action": "dr-app-runtime", "filter": "dr-app-runtime", "stat": "dr-app-runtime", "chart": "dr-app-runtime", "custom": "dr-app-runtime", "field": "dr-app-runtime", "number_input": "dr-components", "tabs": "dr-components", "tab_panel": "dr-components", "select": "dr-components", "date_picker": "dr-components", "data_grid": "dr-components", "dialog": "dr-components", "variance_table": "dr-components", "toggle": "dr-components", "control_group": "dr-components", "callout": "dr-components", "scenario_columns": "dr-components", "slider": "dr-components-controls", "button": "dr-components", "status": "dr-components", "badge": "dr-components", "tag": "dr-components", "empty_state": "dr-components", "stepper": "dr-components", "alert": "dr-components", "checkbox": "dr-components", "radio_group": "dr-components", "file_upload": "dr-components", "skeleton": "dr-components", "progress": "dr-components", "accordion": "dr-components", "tooltip_info": "dr-components", "filter_bar": "dr-components", "filter_chip": "dr-components", "date_range": "dr-components", "fiscal_month": "dr-components" });
|
|
@@ -20966,7 +21554,7 @@ function readSpec2(handlers) {
|
|
|
20966
21554
|
const where2 = handler.file || `handlers.json#${handler.name}`;
|
|
20967
21555
|
const literals = new Literals2(handler.source, "py");
|
|
20968
21556
|
const value = literals.constant("SPEC");
|
|
20969
|
-
if (!
|
|
21557
|
+
if (!isPlainObject10(value)) {
|
|
20970
21558
|
unresolved.push({ where: where2, key: "SPEC", text: literals.constantText("SPEC") ?? cut2("SPEC") });
|
|
20971
21559
|
continue;
|
|
20972
21560
|
}
|
|
@@ -20982,15 +21570,15 @@ function readSpec2(handlers) {
|
|
|
20982
21570
|
var DEFAULTS_KEY2 = "_defaults";
|
|
20983
21571
|
function effective2(table) {
|
|
20984
21572
|
const defaults = table[DEFAULTS_KEY2];
|
|
20985
|
-
if (!
|
|
21573
|
+
if (!isPlainObject10(defaults)) return table;
|
|
20986
21574
|
const out = /* @__PURE__ */ Object.create(null);
|
|
20987
21575
|
for (const [key, row] of Object.entries(table)) {
|
|
20988
21576
|
if (key === DEFAULTS_KEY2) continue;
|
|
20989
|
-
out[key] = key.startsWith("_") || !
|
|
21577
|
+
out[key] = key.startsWith("_") || !isPlainObject10(row) ? row : { ...defaults, ...row };
|
|
20990
21578
|
}
|
|
20991
21579
|
return out;
|
|
20992
21580
|
}
|
|
20993
|
-
function
|
|
21581
|
+
function isPlainObject10(value) {
|
|
20994
21582
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
20995
21583
|
}
|
|
20996
21584
|
|
|
@@ -21082,7 +21670,7 @@ function readTransforms2(source) {
|
|
|
21082
21670
|
if (!literals.has(TRANSFORMS_TABLE2)) return { found: false, texts: null, span: null, text: null };
|
|
21083
21671
|
const value = literals.constant(TRANSFORMS_TABLE2);
|
|
21084
21672
|
const span = literals.constantSpan(TRANSFORMS_TABLE2);
|
|
21085
|
-
const readable =
|
|
21673
|
+
const readable = isPlainObject10(value) && Object.entries(value).every(([path3, text3]) => TRANSFORM_PATH5.test(path3) && typeof text3 === "string");
|
|
21086
21674
|
if (!readable) return { found: true, texts: null, span, text: literals.constantText(TRANSFORMS_TABLE2) };
|
|
21087
21675
|
const texts = {};
|
|
21088
21676
|
for (const path3 of Object.keys(value).sort(natural4)) texts[path3] = value[path3];
|
|
@@ -21118,6 +21706,10 @@ var THEME_ATTR3 = "data-dr-theme";
|
|
|
21118
21706
|
var DENSITY_ATTR2 = "data-dr-density";
|
|
21119
21707
|
var THEME_FILE6 = "ui/theme.css";
|
|
21120
21708
|
var CARD_ATTR2 = "data-dr-card";
|
|
21709
|
+
var SHELL_ATTR2 = "data-dr-shell";
|
|
21710
|
+
var HEADER_ACTIONS_ATTR2 = "data-dr-header-actions";
|
|
21711
|
+
var TITLE_FROM_ATTR2 = "data-dr-title-from";
|
|
21712
|
+
var NAV_COUNTS_ATTR2 = "data-dr-nav-counts";
|
|
21121
21713
|
var EMPHASIS_ATTR2 = "data-dr-emphasis";
|
|
21122
21714
|
var MUTATING_VERBS2 = /* @__PURE__ */ new Set(["insert", "update", "delete"]);
|
|
21123
21715
|
var TABLE_ONLY_VERBS2 = /* @__PURE__ */ new Set([...MUTATING_VERBS2, "get"]);
|
|
@@ -21160,8 +21752,10 @@ function extract2(app, { describe: describe7 } = {}) {
|
|
|
21160
21752
|
const elements = {};
|
|
21161
21753
|
const pages = [];
|
|
21162
21754
|
const pinsByPath = /* @__PURE__ */ new Map();
|
|
21755
|
+
let shell = null;
|
|
21163
21756
|
for (const page of app.pages) {
|
|
21164
21757
|
const built = readPage2(app, page, spec, elements, problems);
|
|
21758
|
+
if (shell === null && built.shell !== null && built.shell !== void 0) shell = built.shell;
|
|
21165
21759
|
if (built.page.owned !== true && !page.text.includes(COMPONENT_ATTR2)) {
|
|
21166
21760
|
readBundle2(app, page.path, built.page.scripts, elements, problems);
|
|
21167
21761
|
}
|
|
@@ -21224,7 +21818,7 @@ function extract2(app, { describe: describe7 } = {}) {
|
|
|
21224
21818
|
v: CONTRACT_V2,
|
|
21225
21819
|
generated_by: GENERATED_BY2,
|
|
21226
21820
|
source: describe7 ? "static+describe" : "static",
|
|
21227
|
-
app: appSection2(app, manifest),
|
|
21821
|
+
app: { ...appSection2(app, manifest), ...shell === null ? {} : { shell } },
|
|
21228
21822
|
pins: [...pinsByPath.values()].map((pin) => ({ ...pin, pages: [...new Set(pin.pages)].sort() })).sort((a, b) => a.order - b.order),
|
|
21229
21823
|
pages,
|
|
21230
21824
|
elements,
|
|
@@ -21242,7 +21836,7 @@ function appSection2(app, manifest) {
|
|
|
21242
21836
|
for (const handler of app.handlers) {
|
|
21243
21837
|
handlers[handler.name] = { http: handler.http || null, entrypoint: handler.file || null };
|
|
21244
21838
|
}
|
|
21245
|
-
const nav =
|
|
21839
|
+
const nav = isPlainObject10(manifest.nav) ? manifest.nav : {};
|
|
21246
21840
|
return {
|
|
21247
21841
|
id: manifest.id ?? null,
|
|
21248
21842
|
name: manifest.name ?? null,
|
|
@@ -21258,7 +21852,7 @@ function appSection2(app, manifest) {
|
|
|
21258
21852
|
api_version: manifest.apiVersion ?? manifest.api_version ?? null,
|
|
21259
21853
|
routes: routesOf3(manifest),
|
|
21260
21854
|
handlers,
|
|
21261
|
-
capabilities:
|
|
21855
|
+
capabilities: isPlainObject10(manifest.capabilities) ? manifest.capabilities : {},
|
|
21262
21856
|
data_model: app.dataModel ? normaliseModel2(app.dataModel) : null,
|
|
21263
21857
|
theme: themeSection2(app)
|
|
21264
21858
|
};
|
|
@@ -21406,14 +22000,21 @@ function readPage2(app, page, spec, elements, problems) {
|
|
|
21406
22000
|
...bands.map((entry, index) => ({ at: entry.node.index, section: banded[index] })),
|
|
21407
22001
|
...wrappers.map((node, index) => ({ at: node.index, section: wrapped[index] }))
|
|
21408
22002
|
].sort((a, b) => a.at - b.at).map((one) => one.section);
|
|
22003
|
+
for (const entry of significant) {
|
|
22004
|
+
if (entry.type !== "region" || entry.node.attrs[HEADER_ACTIONS_ATTR2] === void 0) continue;
|
|
22005
|
+
for (const child of childrenOf2.get(entry.node.index) ?? []) elements[uids.get(child)].section = "header";
|
|
22006
|
+
}
|
|
21409
22007
|
const mount = readMount2(scripts, problems);
|
|
21410
22008
|
const body = tree.nodes.find((node) => node.tag === "body") ?? tree.nodes.find((node) => node.tag === "html");
|
|
22009
|
+
const main2 = tree.nodes.find((node) => node.tag === "main" && "data-dr-app" in node.attrs) ?? null;
|
|
21411
22010
|
return {
|
|
22011
|
+
shell: shellOf2(main2),
|
|
21412
22012
|
page: {
|
|
21413
22013
|
path: page.path,
|
|
21414
22014
|
route: routeOf3(app.manifest, page.path),
|
|
21415
22015
|
title: titleOf2(tree, page.text),
|
|
21416
22016
|
header: headerOf2(tree, page.text),
|
|
22017
|
+
...navOf2(tree, main2),
|
|
21417
22018
|
sections,
|
|
21418
22019
|
scripts: scripts.map((script) => script.path).filter((path3) => path3 !== page.path),
|
|
21419
22020
|
theme: body?.attrs[THEME_ATTR3] ?? null,
|
|
@@ -21507,13 +22108,13 @@ function partCalls2(element) {
|
|
|
21507
22108
|
const typed = element.props?.props ?? {};
|
|
21508
22109
|
const out = [];
|
|
21509
22110
|
for (const prop of Object.keys(PARTS2[element.type].events ?? {})) {
|
|
21510
|
-
if (prop !== "on_row_action" &&
|
|
22111
|
+
if (prop !== "on_row_action" && named6(typed[prop])) out.push(typed[prop]);
|
|
21511
22112
|
}
|
|
21512
22113
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
21513
|
-
if (action && typeof action === "object" &&
|
|
22114
|
+
if (action && typeof action === "object" && named6(action.call)) out.push(action.call);
|
|
21514
22115
|
}
|
|
21515
22116
|
for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
|
|
21516
|
-
if (column && typeof column === "object" && column.cell === "toggle" &&
|
|
22117
|
+
if (column && typeof column === "object" && column.cell === "toggle" && named6(column.call)) out.push(column.call);
|
|
21517
22118
|
}
|
|
21518
22119
|
return out;
|
|
21519
22120
|
}
|
|
@@ -21673,20 +22274,43 @@ function headerOf2(tree, text3) {
|
|
|
21673
22274
|
}
|
|
21674
22275
|
const region = tree.nodes.find((node) => node.attrs[KIT_ATTR2] === "header");
|
|
21675
22276
|
if (!region) return null;
|
|
21676
|
-
const heading = firstTag2(tree, region, "h1");
|
|
22277
|
+
const heading = firstTag2(tree, region, "h1", true);
|
|
21677
22278
|
if (!heading) return null;
|
|
21678
|
-
const summary = firstTag2(tree, region, "p");
|
|
21679
|
-
|
|
22279
|
+
const summary = firstTag2(tree, region, "p", true);
|
|
22280
|
+
const out = { title: textOf2(text3, heading), summary: summary ? textOf2(text3, summary) || null : null };
|
|
22281
|
+
const actions = parseJsonAttr2(region.attrs[HEADER_ACTIONS_ATTR2]);
|
|
22282
|
+
if (Array.isArray(actions)) out.actions = actions;
|
|
22283
|
+
const from = region.attrs[TITLE_FROM_ATTR2];
|
|
22284
|
+
if (from !== void 0 && from !== "") out.title_from = from;
|
|
22285
|
+
return out;
|
|
21680
22286
|
}
|
|
21681
|
-
function firstTag2(tree, node, tag) {
|
|
22287
|
+
function firstTag2(tree, node, tag, outer = false) {
|
|
21682
22288
|
for (const child of node.children) {
|
|
21683
22289
|
const candidate = tree.nodes[child];
|
|
21684
22290
|
if (candidate.tag === tag) return candidate;
|
|
21685
|
-
|
|
22291
|
+
if (outer && (COMPONENT_ATTR2 in candidate.attrs || FIELD_ATTR2 in candidate.attrs)) continue;
|
|
22292
|
+
const found = firstTag2(tree, candidate, tag, outer);
|
|
21686
22293
|
if (found) return found;
|
|
21687
22294
|
}
|
|
21688
22295
|
return null;
|
|
21689
22296
|
}
|
|
22297
|
+
function parseJsonAttr2(value) {
|
|
22298
|
+
if (value === void 0 || value === "") return void 0;
|
|
22299
|
+
try {
|
|
22300
|
+
return JSON.parse(value);
|
|
22301
|
+
} catch {
|
|
22302
|
+
return void 0;
|
|
22303
|
+
}
|
|
22304
|
+
}
|
|
22305
|
+
function shellOf2(main2) {
|
|
22306
|
+
const shell = parseJsonAttr2(main2?.attrs[SHELL_ATTR2]);
|
|
22307
|
+
return isPlainObject10(shell) ? shell : null;
|
|
22308
|
+
}
|
|
22309
|
+
function navOf2(tree, main2) {
|
|
22310
|
+
const nav = tree.nodes.find((node) => node.tag === "nav" && node.attrs[KIT_ATTR2] === "nav" && node.attrs[NAV_COUNTS_ATTR2] !== void 0);
|
|
22311
|
+
const source = nav?.attrs[NAV_COUNTS_ATTR2] ?? main2?.attrs[NAV_COUNTS_ATTR2];
|
|
22312
|
+
return source === void 0 || source === "" ? {} : { nav: { counts: { source } } };
|
|
22313
|
+
}
|
|
21690
22314
|
function sectionOf2(node, text3, index) {
|
|
21691
22315
|
const title = textOf2(text3, node);
|
|
21692
22316
|
return { id: node.attrs.id ?? slug3(title) ?? `section_${index + 1}`, title };
|
|
@@ -21697,13 +22321,13 @@ function wrappedSectionOf2(tree, node, text3, index) {
|
|
|
21697
22321
|
const out = { id: id === void 0 || id === "" ? `section_${index + 1}` : id, title: heading ? textOf2(text3, heading) : "" };
|
|
21698
22322
|
const subtitle = node.children.map((child) => tree.nodes[child]).find((child) => child.tag === "p");
|
|
21699
22323
|
if (subtitle) out.subtitle = textOf2(text3, subtitle);
|
|
21700
|
-
for (const key of
|
|
22324
|
+
for (const key of BAND_LOOK4) {
|
|
21701
22325
|
const value = node.attrs[`data-dr-${key}`];
|
|
21702
22326
|
if (value !== void 0 && value !== "") out[key] = value;
|
|
21703
22327
|
}
|
|
21704
22328
|
return out;
|
|
21705
22329
|
}
|
|
21706
|
-
var
|
|
22330
|
+
var BAND_LOOK4 = ["layout", "surface", "aside"];
|
|
21707
22331
|
function slug3(text3) {
|
|
21708
22332
|
const clean = String(text3 ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
21709
22333
|
return clean === "" ? null : clean;
|
|
@@ -21740,7 +22364,7 @@ function readMount2(scripts, problems) {
|
|
|
21740
22364
|
const brace = script.text.indexOf("{", call);
|
|
21741
22365
|
if (brace === -1) continue;
|
|
21742
22366
|
const value = new Literals2(script.text, "js").valueAtChar(brace);
|
|
21743
|
-
if (!
|
|
22367
|
+
if (!isPlainObject10(value)) {
|
|
21744
22368
|
problems.push({
|
|
21745
22369
|
code: "mount_unreadable",
|
|
21746
22370
|
where: script.path,
|
|
@@ -21752,7 +22376,7 @@ function readMount2(scripts, problems) {
|
|
|
21752
22376
|
return {
|
|
21753
22377
|
handler: typeof value.handler === "string" ? value.handler : null,
|
|
21754
22378
|
prefs: typeof value.prefs === "boolean" ? value.prefs : null,
|
|
21755
|
-
defaults:
|
|
22379
|
+
defaults: isPlainObject10(value.defaults) ? value.defaults : null
|
|
21756
22380
|
};
|
|
21757
22381
|
}
|
|
21758
22382
|
return null;
|
|
@@ -21805,7 +22429,7 @@ function bind2(elements, spec, backend, model) {
|
|
|
21805
22429
|
}
|
|
21806
22430
|
}
|
|
21807
22431
|
for (const [key, row] of Object.entries(spec.table)) {
|
|
21808
|
-
if (!isWidgetKey2(key) || !
|
|
22432
|
+
if (!isWidgetKey2(key) || !isPlainObject10(row) || !isPlainObject10(row.params)) continue;
|
|
21809
22433
|
for (const param of Object.keys(row.params)) {
|
|
21810
22434
|
const entry = controls[param] ?? (controls[param] = { components: [], widgets: [], bound: false });
|
|
21811
22435
|
if (!entry.widgets.includes(key)) entry.widgets.push(key);
|
|
@@ -21847,11 +22471,11 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
|
|
|
21847
22471
|
if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
|
|
21848
22472
|
const props = element.props ?? {};
|
|
21849
22473
|
const entity = typeof props.entity === "string" && props.entity !== "" ? props.entity : null;
|
|
21850
|
-
if (
|
|
21851
|
-
if (
|
|
22474
|
+
if (named6(props.action)) actionRow(props.action).components.push(uid);
|
|
22475
|
+
if (named6(props.source)) actionRow(props.source).reads.push(uid);
|
|
21852
22476
|
if (isPart3(element)) for (const name of partCalls2(element)) actionRow(name).components.push(uid);
|
|
21853
22477
|
if (entity !== null && element.type !== "field") entityRow(entity).components.push(uid);
|
|
21854
|
-
if (!
|
|
22478
|
+
if (!named6(props.field)) continue;
|
|
21855
22479
|
const column = entity === null ? null : (tables[entity]?.columns ?? {})[props.field] ?? null;
|
|
21856
22480
|
const injected = entity !== null && (has(tables, entity) && INJECTED_COLUMNS3.includes(props.field) || platform(entity) && PLATFORM_TABLES3[entity].includes(props.field));
|
|
21857
22481
|
const key = `${entity ?? ""}.${props.field}`;
|
|
@@ -21878,7 +22502,7 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
|
|
|
21878
22502
|
unreached_mutations: Object.keys(facts).filter((name) => (facts[name].verbs ?? []).some((verb) => MUTATING_VERBS2.has(verb)) && !reached.has(name)).sort(natural5)
|
|
21879
22503
|
};
|
|
21880
22504
|
}
|
|
21881
|
-
function
|
|
22505
|
+
function named6(value) {
|
|
21882
22506
|
return typeof value === "string" && value !== "";
|
|
21883
22507
|
}
|
|
21884
22508
|
function isWidgetKey2(key) {
|
|
@@ -21916,7 +22540,7 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
|
|
|
21916
22540
|
for (const uid of Object.keys(elements).sort(natural5)) {
|
|
21917
22541
|
const element = elements[uid];
|
|
21918
22542
|
if (element.type !== "form") continue;
|
|
21919
|
-
const missing = [ACTION_ATTR2, ENTITY_ATTR2].filter((attr3) => !
|
|
22543
|
+
const missing = [ACTION_ATTR2, ENTITY_ATTR2].filter((attr3) => !named6(element.props[attr3 === ACTION_ATTR2 ? "action" : "entity"]));
|
|
21920
22544
|
if (missing.length === 0) continue;
|
|
21921
22545
|
problems.push({
|
|
21922
22546
|
code: "unbound_form",
|
|
@@ -21938,8 +22562,8 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
|
|
|
21938
22562
|
for (const uid of Object.keys(elements).sort(natural5)) {
|
|
21939
22563
|
const element = elements[uid];
|
|
21940
22564
|
const filter = element.props?.filter;
|
|
21941
|
-
if (!isComponentType2(element.type) || !
|
|
21942
|
-
const entity =
|
|
22565
|
+
if (!isComponentType2(element.type) || !isPlainObject10(filter)) continue;
|
|
22566
|
+
const entity = named6(element.props.entity) ? element.props.entity : null;
|
|
21943
22567
|
if (entity === null || !Object.prototype.hasOwnProperty.call(tables, entity)) continue;
|
|
21944
22568
|
const columns = tables[entity].columns ?? {};
|
|
21945
22569
|
for (const column of Object.keys(filter).sort()) {
|
|
@@ -21987,7 +22611,7 @@ function dataSection2(app, manifest, spec, elements, describe7, problems) {
|
|
|
21987
22611
|
...Object.keys(sources).length > 0 ? { sources } : {}
|
|
21988
22612
|
};
|
|
21989
22613
|
if (describe7) {
|
|
21990
|
-
const widgets2 =
|
|
22614
|
+
const widgets2 = isPlainObject10(describe7.widgets) ? describe7.widgets : {};
|
|
21991
22615
|
const missing = Object.keys(spec.table).filter(isWidgetKey2).filter((key) => !(key in widgets2));
|
|
21992
22616
|
if (spec.found && spec.unresolved.length === 0 && missing.length > 0) {
|
|
21993
22617
|
problems.push({
|
|
@@ -22036,12 +22660,12 @@ function readSources2(handlers) {
|
|
|
22036
22660
|
if (!SOURCES_TABLE2.test(source) || HANDLER_LITERAL2.test(source)) continue;
|
|
22037
22661
|
const literals = new Literals2(source, "py");
|
|
22038
22662
|
const table = literals.constant("SOURCES");
|
|
22039
|
-
if (!
|
|
22663
|
+
if (!isPlainObject10(table)) continue;
|
|
22040
22664
|
for (const [name, row] of Object.entries(table)) {
|
|
22041
|
-
if (
|
|
22665
|
+
if (isPlainObject10(row) && sources[name] === void 0) sources[name] = row;
|
|
22042
22666
|
}
|
|
22043
22667
|
const carried = literals.constant("TRANSFORMS");
|
|
22044
|
-
if (!
|
|
22668
|
+
if (!isPlainObject10(carried)) continue;
|
|
22045
22669
|
for (const [name, text3] of Object.entries(carried)) {
|
|
22046
22670
|
const path3 = table[name]?.transform;
|
|
22047
22671
|
if (typeof path3 === "string" && typeof text3 === "string" && !texts.has(path3)) texts.set(path3, text3);
|
|
@@ -22076,7 +22700,7 @@ function specTransforms2(handlers, spec, problems) {
|
|
|
22076
22700
|
}
|
|
22077
22701
|
const unreadable = problems.some((problem5) => problem5.code === "transform_unreadable");
|
|
22078
22702
|
for (const key of Object.keys(spec.table).filter(isWidgetKey2).sort(natural5)) {
|
|
22079
|
-
const path3 =
|
|
22703
|
+
const path3 = isPlainObject10(spec.table[key]) ? spec.table[key].transform : void 0;
|
|
22080
22704
|
if (typeof path3 !== "string" || unreadable || carried !== null && carried.has(path3)) continue;
|
|
22081
22705
|
problems.push({
|
|
22082
22706
|
code: "transform_not_carried",
|
|
@@ -22100,7 +22724,7 @@ function readApp2(handlers, elements, manifest = null) {
|
|
|
22100
22724
|
let row = null;
|
|
22101
22725
|
if (HANDLER_LITERAL2.test(source)) {
|
|
22102
22726
|
const value = new Literals2(source, "py").constant("HANDLER");
|
|
22103
|
-
if (
|
|
22727
|
+
if (isPlainObject10(value)) row = value;
|
|
22104
22728
|
}
|
|
22105
22729
|
declared[handler.name] = { role: "custom", ...row ?? {} };
|
|
22106
22730
|
if (declared[handler.name].requiresRole === void 0) {
|
|
@@ -22128,9 +22752,9 @@ function screensOf2(elements, order2 = null) {
|
|
|
22128
22752
|
screens.push({
|
|
22129
22753
|
page: element.page,
|
|
22130
22754
|
component: element.type,
|
|
22131
|
-
id:
|
|
22132
|
-
entity:
|
|
22133
|
-
handler:
|
|
22755
|
+
id: named6(props.id) ? props.id : element.id ?? null,
|
|
22756
|
+
entity: named6(props.entity) ? props.entity : entityAbove2(elements, element),
|
|
22757
|
+
handler: named6(props.source) ? props.source : named6(props.action) ? props.action : null
|
|
22134
22758
|
});
|
|
22135
22759
|
}
|
|
22136
22760
|
return screens;
|
|
@@ -22142,7 +22766,7 @@ function entityAbove2(elements, element) {
|
|
|
22142
22766
|
seen.add(at);
|
|
22143
22767
|
const above = elements[at];
|
|
22144
22768
|
if (above === void 0) return null;
|
|
22145
|
-
if (
|
|
22769
|
+
if (named6(above.props?.entity)) return above.props.entity;
|
|
22146
22770
|
at = above.parent ?? null;
|
|
22147
22771
|
}
|
|
22148
22772
|
return null;
|
|
@@ -22171,7 +22795,7 @@ function backendSection2(app, manifest, pages, elements) {
|
|
|
22171
22795
|
if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
|
|
22172
22796
|
for (const key of ["action", "source"]) {
|
|
22173
22797
|
const name = element.props?.[key];
|
|
22174
|
-
if (!
|
|
22798
|
+
if (!named6(name)) continue;
|
|
22175
22799
|
record2(name, "attribute");
|
|
22176
22800
|
attributed.add(name);
|
|
22177
22801
|
}
|
|
@@ -22200,14 +22824,14 @@ function backendSection2(app, manifest, pages, elements) {
|
|
|
22200
22824
|
data_model: model,
|
|
22201
22825
|
handlers,
|
|
22202
22826
|
roles,
|
|
22203
|
-
capabilities:
|
|
22827
|
+
capabilities: isPlainObject10(manifest.capabilities) ? manifest.capabilities : {},
|
|
22204
22828
|
ui: {
|
|
22205
22829
|
entry,
|
|
22206
22830
|
handlers_called: [...called].sort(),
|
|
22207
22831
|
handlers_called_via: [...via.values()].sort((a, b) => natural5(a.name, b.name) || natural5(a.via, b.via)),
|
|
22208
22832
|
scripts_loaded: [...new Set(pages.flatMap((page) => page.scripts))].sort()
|
|
22209
22833
|
},
|
|
22210
|
-
connect_tables:
|
|
22834
|
+
connect_tables: isPlainObject10(manifest.connectTables) ? manifest.connectTables : isPlainObject10(manifest.connect_tables) ? manifest.connect_tables : {},
|
|
22211
22835
|
invariants: invariants2(model, roles, handlers)
|
|
22212
22836
|
};
|
|
22213
22837
|
}
|
|
@@ -22217,7 +22841,7 @@ function callsOnTable2(source, verb) {
|
|
|
22217
22841
|
return holders.some((holder) => new RegExp(`(?:^|[^\\w.])(?:${holder})\\s*\\.\\s*${verb}\\s*\\(`, "m").test(source));
|
|
22218
22842
|
}
|
|
22219
22843
|
function declaredHandler2(manifest, name) {
|
|
22220
|
-
return
|
|
22844
|
+
return isPlainObject10(manifest?.handlers) ? manifest.handlers[name] ?? null : null;
|
|
22221
22845
|
}
|
|
22222
22846
|
function signatureOk2(source) {
|
|
22223
22847
|
const code = source.split("\n").map((line2) => /^\s*#/.test(line2) ? "" : line2).join("\n");
|
|
@@ -22244,7 +22868,7 @@ function rolesOf3(manifest) {
|
|
|
22244
22868
|
// Declaration order IS the ladder: `authz.py` reads the manifest's list
|
|
22245
22869
|
// top down, so the first role is the most junior.
|
|
22246
22870
|
seniority: index,
|
|
22247
|
-
tables:
|
|
22871
|
+
tables: isPlainObject10(role?.tables) ? role.tables : {},
|
|
22248
22872
|
// Stated only when the manifest states it, like the platform's own
|
|
22249
22873
|
// derivation (`app_contract_store._roles_section`): the norm is a role
|
|
22250
22874
|
// that sees only its own screens, and `[]` on every role would say
|
|
@@ -22256,16 +22880,16 @@ function rolesOf3(manifest) {
|
|
|
22256
22880
|
}
|
|
22257
22881
|
function pageRolesOf2(manifest, path3) {
|
|
22258
22882
|
const pages = Array.isArray(manifest?.pages) ? manifest.pages : [];
|
|
22259
|
-
const entry = pages.find((one) =>
|
|
22883
|
+
const entry = pages.find((one) => isPlainObject10(one) && one.path === path3);
|
|
22260
22884
|
const roles = Array.isArray(entry?.roles) ? entry.roles.filter((role) => typeof role === "string" && role !== "") : [];
|
|
22261
22885
|
return roles.length > 0 ? { roles } : {};
|
|
22262
22886
|
}
|
|
22263
22887
|
function normaliseModel2(document) {
|
|
22264
22888
|
const tables = {};
|
|
22265
|
-
const raw =
|
|
22889
|
+
const raw = isPlainObject10(document?.tables) ? document.tables : {};
|
|
22266
22890
|
for (const [name, table] of Object.entries(raw)) {
|
|
22267
22891
|
const columns = {};
|
|
22268
|
-
for (const [column, spec] of Object.entries(
|
|
22892
|
+
for (const [column, spec] of Object.entries(isPlainObject10(table?.columns) ? table.columns : {})) {
|
|
22269
22893
|
const pk = Boolean(spec?.pk);
|
|
22270
22894
|
columns[column] = {
|
|
22271
22895
|
type: spec?.type ?? null,
|
|
@@ -22283,7 +22907,7 @@ function normaliseModel2(document) {
|
|
|
22283
22907
|
// `computed` (a child total the platform selects with the row) and
|
|
22284
22908
|
// `matches` (the column it should agree with), by `display`'s rule:
|
|
22285
22909
|
// only where stated, so a model without them normalises to its bytes.
|
|
22286
|
-
...
|
|
22910
|
+
...isPlainObject10(spec?.computed) ? { computed: spec.computed } : {},
|
|
22287
22911
|
...typeof spec?.matches === "string" && spec.matches !== "" ? { matches: spec.matches } : {}
|
|
22288
22912
|
};
|
|
22289
22913
|
}
|
|
@@ -22301,7 +22925,7 @@ function normaliseModel2(document) {
|
|
|
22301
22925
|
// row reads as before it is first saved. By `audit`'s rule: only where
|
|
22302
22926
|
// stated, so a model with neither keeps normalising to its bytes.
|
|
22303
22927
|
...table?.singleton === true ? { singleton: true } : {},
|
|
22304
|
-
...table?.singleton === true &&
|
|
22928
|
+
...table?.singleton === true && isPlainObject10(table?.defaults) ? { defaults: table.defaults } : {},
|
|
22305
22929
|
columns,
|
|
22306
22930
|
indexes: Array.isArray(table?.indexes) ? table.indexes.map((index) => Array.isArray(index) ? index.map(String) : [String(index)]) : [],
|
|
22307
22931
|
injected: [...INJECTED_COLUMNS3]
|
|
@@ -22634,10 +23258,10 @@ function isObject5(value) {
|
|
|
22634
23258
|
var DECLARES_KEYS2 = Object.freeze(["reads", "calls", "produces", "shows"]);
|
|
22635
23259
|
var SHOWS_KEYS2 = Object.freeze(["field", "format", "scale"]);
|
|
22636
23260
|
var SHOWS_FORMATS2 = Object.freeze(["currency", "number", "percent"]);
|
|
22637
|
-
var
|
|
22638
|
-
var
|
|
22639
|
-
var listOfNames2 = (value) => Array.isArray(value) && value.every(
|
|
22640
|
-
var idOf3 = (element, uid) =>
|
|
23261
|
+
var isPlainObject11 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
23262
|
+
var named7 = (value) => typeof value === "string" && value !== "";
|
|
23263
|
+
var listOfNames2 = (value) => Array.isArray(value) && value.every(named7);
|
|
23264
|
+
var idOf3 = (element, uid) => named7(element?.props?.id) ? element.props.id : uid;
|
|
22641
23265
|
function pyCode2(source) {
|
|
22642
23266
|
let out = "";
|
|
22643
23267
|
let i = 0;
|
|
@@ -22706,7 +23330,7 @@ function handlerPayloadReads2(row, source) {
|
|
|
22706
23330
|
return { generated: false, required: pyPayloadReads2(source).required };
|
|
22707
23331
|
}
|
|
22708
23332
|
if (role === "save" || role === "submit") {
|
|
22709
|
-
return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(
|
|
23333
|
+
return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(named7) };
|
|
22710
23334
|
}
|
|
22711
23335
|
return { generated: true, required: [] };
|
|
22712
23336
|
}
|
|
@@ -22876,32 +23500,32 @@ function childIndex2(elements) {
|
|
|
22876
23500
|
};
|
|
22877
23501
|
for (const [uid, element] of Object.entries(elements)) {
|
|
22878
23502
|
for (const child of Array.isArray(element?.children) ? element.children : []) if (child in elements) add(uid, child);
|
|
22879
|
-
if (
|
|
23503
|
+
if (named7(element?.parent) && element.parent in elements) add(element.parent, uid);
|
|
22880
23504
|
}
|
|
22881
23505
|
return index;
|
|
22882
23506
|
}
|
|
22883
23507
|
var ROW_HOLDERS2 = /* @__PURE__ */ new Set(["list", "queue", "detail"]);
|
|
22884
23508
|
var INPUT_PARTS2 = /* @__PURE__ */ new Set(["number_input", "select", "date_picker", "toggle", "slider", "checkbox", "radio_group", "file_upload"]);
|
|
22885
23509
|
function fieldOf2(element) {
|
|
22886
|
-
if (element?.type === "field" &&
|
|
22887
|
-
if (INPUT_PARTS2.has(element?.type) &&
|
|
23510
|
+
if (element?.type === "field" && named7(element.props?.field)) return element.props.field;
|
|
23511
|
+
if (INPUT_PARTS2.has(element?.type) && named7(element.props?.props?.field)) return element.props.props.field;
|
|
22888
23512
|
return null;
|
|
22889
23513
|
}
|
|
22890
23514
|
function mentions2(text3, name) {
|
|
22891
23515
|
return new RegExp(`(^|[^A-Za-z0-9_])${reEscape2(name)}([^A-Za-z0-9_]|$)`).test(text3);
|
|
22892
23516
|
}
|
|
22893
23517
|
function payloadProducers2(contract, { moduleSource = () => void 0, pageSource = () => void 0 } = {}) {
|
|
22894
|
-
const elements =
|
|
22895
|
-
const handlers =
|
|
23518
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23519
|
+
const handlers = isPlainObject11(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
22896
23520
|
const children = childIndex2(elements);
|
|
22897
23521
|
const out = /* @__PURE__ */ new Map();
|
|
22898
23522
|
const add = (handler, producer) => {
|
|
22899
|
-
if (!
|
|
23523
|
+
if (!named7(handler)) return;
|
|
22900
23524
|
if (!out.has(handler)) out.set(handler, []);
|
|
22901
23525
|
out.get(handler).push(producer);
|
|
22902
23526
|
};
|
|
22903
23527
|
const insideRow = (uid) => {
|
|
22904
|
-
for (let at = elements[uid]?.parent;
|
|
23528
|
+
for (let at = elements[uid]?.parent; named7(at) && at in elements; at = elements[at]?.parent) {
|
|
22905
23529
|
if (ROW_HOLDERS2.has(elements[at]?.type)) return true;
|
|
22906
23530
|
}
|
|
22907
23531
|
return false;
|
|
@@ -22922,16 +23546,16 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
22922
23546
|
};
|
|
22923
23547
|
for (const uid of Object.keys(elements).sort()) {
|
|
22924
23548
|
const element = elements[uid];
|
|
22925
|
-
const props =
|
|
23549
|
+
const props = isPlainObject11(element?.props) ? element.props : {};
|
|
22926
23550
|
const page = element?.page ?? null;
|
|
22927
|
-
const typed =
|
|
22928
|
-
if (
|
|
23551
|
+
const typed = isPlainObject11(props.props) ? props.props : {};
|
|
23552
|
+
if (named7(props.source)) add(props.source, { page, uid, what: "reader", keys: null });
|
|
22929
23553
|
switch (element?.type) {
|
|
22930
23554
|
case "form": {
|
|
22931
23555
|
const keys2 = fieldsUnder(uid);
|
|
22932
23556
|
if (insideRow(uid) && !keys2.includes("id")) keys2.push("id");
|
|
22933
23557
|
const by = handlers[props.action]?.by;
|
|
22934
|
-
if (
|
|
23558
|
+
if (named7(by) && !keys2.includes(by)) keys2.push(by);
|
|
22935
23559
|
add(props.action, { page, uid, what: "form", keys: keys2 });
|
|
22936
23560
|
break;
|
|
22937
23561
|
}
|
|
@@ -22946,41 +23570,41 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
22946
23570
|
// stepper's current step `{step}`; a file_upload's on_upload its field.
|
|
22947
23571
|
case "button":
|
|
22948
23572
|
add(typed.on_click, { page, uid, what: "button", keys: typed.reason === "required" || typed.reason === "optional" ? ["reason"] : [] });
|
|
22949
|
-
for (const entry of Array.isArray(typed.split) ? typed.split : []) if (
|
|
23573
|
+
for (const entry of Array.isArray(typed.split) ? typed.split : []) if (isPlainObject11(entry)) add(entry.on_click, { page, uid, what: "button", keys: [] });
|
|
22950
23574
|
break;
|
|
22951
23575
|
case "empty_state":
|
|
22952
23576
|
add(typed.on_action, { page, uid, what: "empty_state", keys: [] });
|
|
22953
23577
|
break;
|
|
22954
23578
|
case "stepper":
|
|
22955
|
-
for (const step of Array.isArray(typed.steps) ? typed.steps : []) if (
|
|
23579
|
+
for (const step of Array.isArray(typed.steps) ? typed.steps : []) if (isPlainObject11(step)) add(step.action, { page, uid, what: "stepper", keys: ["step"] });
|
|
22956
23580
|
break;
|
|
22957
23581
|
case "file_upload":
|
|
22958
|
-
add(typed.on_upload, { page, uid, what: "file_upload", keys:
|
|
23582
|
+
add(typed.on_upload, { page, uid, what: "file_upload", keys: named7(typed.field) ? [typed.field] : [] });
|
|
22959
23583
|
break;
|
|
22960
23584
|
case "data_grid":
|
|
22961
23585
|
add(typed.on_row, { page, uid, what: "data_grid", keys: ["row"] });
|
|
22962
23586
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
22963
|
-
if (
|
|
23587
|
+
if (isPlainObject11(action) && named7(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["id", "row"] });
|
|
22964
23588
|
}
|
|
22965
23589
|
for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
|
|
22966
|
-
if (
|
|
23590
|
+
if (isPlainObject11(column) && column.cell === "toggle" && named7(column.call)) add(column.call, { page, uid, what: "data_grid", keys: ["id", "row", "field", "value"] });
|
|
22967
23591
|
}
|
|
22968
23592
|
break;
|
|
22969
23593
|
case "action": {
|
|
22970
23594
|
const keys2 = [];
|
|
22971
|
-
if (insideRow(uid)) keys2.push(
|
|
22972
|
-
else if (
|
|
23595
|
+
if (insideRow(uid)) keys2.push(named7(props.param) ? props.param : "id");
|
|
23596
|
+
else if (named7(props.param)) keys2.push(props.param);
|
|
22973
23597
|
if (handlers[props.action]?.role === "decide") keys2.push("decision", "note");
|
|
22974
23598
|
add(props.action, { page, uid, what: "action", keys: keys2 });
|
|
22975
23599
|
break;
|
|
22976
23600
|
}
|
|
22977
23601
|
case "custom": {
|
|
22978
|
-
const declares =
|
|
22979
|
-
const produces =
|
|
23602
|
+
const declares = isPlainObject11(props.declares) ? props.declares : {};
|
|
23603
|
+
const produces = isPlainObject11(declares.produces) ? declares.produces : {};
|
|
22980
23604
|
for (const [handler, keys2] of Object.entries(produces)) {
|
|
22981
23605
|
add(handler, { page, uid, what: "custom", keys: listOfNames2(keys2) ? [...keys2] : null });
|
|
22982
23606
|
}
|
|
22983
|
-
const source =
|
|
23607
|
+
const source = named7(props.module) ? moduleSource(props.module) : void 0;
|
|
22984
23608
|
if (typeof source !== "string") {
|
|
22985
23609
|
add("*", { page, uid, what: "custom", keys: null });
|
|
22986
23610
|
break;
|
|
@@ -23006,10 +23630,10 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
23006
23630
|
}
|
|
23007
23631
|
const modules = /* @__PURE__ */ new Set();
|
|
23008
23632
|
for (const element of Object.values(elements)) {
|
|
23009
|
-
if (element?.type === "custom" &&
|
|
23633
|
+
if (element?.type === "custom" && named7(element.props?.module)) modules.add(`${element.page}|${element.props.module}`);
|
|
23010
23634
|
}
|
|
23011
23635
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23012
|
-
if (!
|
|
23636
|
+
if (!named7(page?.path)) continue;
|
|
23013
23637
|
const text3 = pageSource(page.path);
|
|
23014
23638
|
const unknown = { page: page.path, uid: null, what: "script", keys: null };
|
|
23015
23639
|
if (typeof text3 !== "string") {
|
|
@@ -23054,7 +23678,7 @@ function either4(items) {
|
|
|
23054
23678
|
return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
|
|
23055
23679
|
}
|
|
23056
23680
|
function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
|
|
23057
|
-
const handlers =
|
|
23681
|
+
const handlers = isPlainObject11(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
23058
23682
|
const producers = payloadProducers2(contract, { moduleSource, pageSource });
|
|
23059
23683
|
const anywhere = producers.get("*") ?? [];
|
|
23060
23684
|
const out = [];
|
|
@@ -23065,7 +23689,7 @@ function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, module
|
|
|
23065
23689
|
if (own.length === 0 || anywhere.length > 0 || own.some((one) => one.keys === null)) continue;
|
|
23066
23690
|
const produced = [];
|
|
23067
23691
|
for (const one of own) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
|
|
23068
|
-
const pages = [...new Set(own.map((one) => one.page).filter(
|
|
23692
|
+
const pages = [...new Set(own.map((one) => one.page).filter(named7))];
|
|
23069
23693
|
for (const key of reads.required) {
|
|
23070
23694
|
if (produced.includes(key)) continue;
|
|
23071
23695
|
const there = produced.length === 0 ? "the controls there produce no payload keys" : `the controls there produce ${produced.join(", ")}`;
|
|
@@ -23077,12 +23701,12 @@ function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, module
|
|
|
23077
23701
|
return out;
|
|
23078
23702
|
}
|
|
23079
23703
|
function undeclaredCustomCalls2(contract, { moduleSource = () => void 0 } = {}) {
|
|
23080
|
-
const elements =
|
|
23704
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23081
23705
|
const out = [];
|
|
23082
23706
|
for (const uid of Object.keys(elements).sort()) {
|
|
23083
23707
|
const element = elements[uid];
|
|
23084
|
-
const props =
|
|
23085
|
-
if (element?.type !== "custom" || !
|
|
23708
|
+
const props = isPlainObject11(element?.props) ? element.props : {};
|
|
23709
|
+
if (element?.type !== "custom" || !isPlainObject11(props.declares) || !named7(props.module)) continue;
|
|
23086
23710
|
const source = moduleSource(props.module);
|
|
23087
23711
|
if (typeof source !== "string") continue;
|
|
23088
23712
|
const calls = listOfNames2(props.declares.calls) ? props.declares.calls : [];
|
|
@@ -23110,32 +23734,32 @@ function undeclaredCallMessage2(id, module, handler, calls, how) {
|
|
|
23110
23734
|
return { problem: problem5, fix, message: `${problem5} ${fix}` };
|
|
23111
23735
|
}
|
|
23112
23736
|
function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
|
|
23113
|
-
const elements =
|
|
23114
|
-
const handlers =
|
|
23737
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23738
|
+
const handlers = isPlainObject11(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
23115
23739
|
const parents = /* @__PURE__ */ new Map();
|
|
23116
23740
|
for (const [parent, kids] of childIndex2(elements)) for (const kid of kids) if (!parents.has(kid)) parents.set(kid, parent);
|
|
23117
23741
|
const out = [];
|
|
23118
23742
|
for (const uid of Object.keys(elements).sort()) {
|
|
23119
23743
|
const element = elements[uid];
|
|
23120
|
-
const typed =
|
|
23121
|
-
if (element?.type !== "file_upload" || !
|
|
23744
|
+
const typed = isPlainObject11(element?.props?.props) ? element.props.props : {};
|
|
23745
|
+
if (element?.type !== "file_upload" || !named7(typed.field)) continue;
|
|
23122
23746
|
const field = typed.field;
|
|
23123
23747
|
const receivers = [];
|
|
23124
|
-
if (
|
|
23125
|
-
for (let at = parents.get(uid);
|
|
23748
|
+
if (named7(typed.on_upload)) receivers.push([typed.on_upload, "its on_upload"]);
|
|
23749
|
+
for (let at = parents.get(uid); named7(at) && at in elements; at = parents.get(at)) {
|
|
23126
23750
|
const holder = elements[at];
|
|
23127
|
-
if (holder?.type === "dialog" &&
|
|
23751
|
+
if (holder?.type === "dialog" && named7(holder.props?.props?.on_confirm)) {
|
|
23128
23752
|
receivers.push([holder.props.props.on_confirm, `the dialog "${idOf3(holder, at)}" it sits in`]);
|
|
23129
23753
|
break;
|
|
23130
23754
|
}
|
|
23131
|
-
if (holder?.type === "form" &&
|
|
23755
|
+
if (holder?.type === "form" && named7(holder.props?.action)) {
|
|
23132
23756
|
receivers.push([holder.props.action, `the form "${idOf3(holder, at)}" it sits in`]);
|
|
23133
23757
|
break;
|
|
23134
23758
|
}
|
|
23135
23759
|
}
|
|
23136
23760
|
for (const [handler, via] of receivers) {
|
|
23137
23761
|
const row = handlers[handler];
|
|
23138
|
-
if (!
|
|
23762
|
+
if (!isPlainObject11(row)) continue;
|
|
23139
23763
|
const role = row.role ?? "custom";
|
|
23140
23764
|
let read;
|
|
23141
23765
|
if (role === "custom") {
|
|
@@ -23158,16 +23782,16 @@ function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
|
|
|
23158
23782
|
return out;
|
|
23159
23783
|
}
|
|
23160
23784
|
function stepsWithoutAction2(contract) {
|
|
23161
|
-
const elements =
|
|
23785
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23162
23786
|
const out = [];
|
|
23163
23787
|
for (const uid of Object.keys(elements).sort()) {
|
|
23164
23788
|
const element = elements[uid];
|
|
23165
23789
|
const steps = element?.type === "stepper" && Array.isArray(element.props?.props?.steps) ? element.props.props.steps : [];
|
|
23166
23790
|
steps.forEach((step, index) => {
|
|
23167
|
-
if (index === steps.length - 1 || !
|
|
23791
|
+
if (index === steps.length - 1 || !isPlainObject11(step) || named7(step.action)) return;
|
|
23168
23792
|
const id = idOf3(element, uid);
|
|
23169
|
-
const name =
|
|
23170
|
-
const next =
|
|
23793
|
+
const name = named7(step.id) ? step.id : String(index);
|
|
23794
|
+
const next = named7(steps[index + 1]?.id) ? steps[index + 1].id : "the next step";
|
|
23171
23795
|
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.`;
|
|
23172
23796
|
const fix = `Give it the handler that moves it to "${next}" (action, action_label), or make it the last step.`;
|
|
23173
23797
|
out.push({ uid, id, page: element.page ?? null, step: name, index, problem: problem5, fix, message: `${problem5} ${fix}` });
|
|
@@ -23431,6 +24055,10 @@ function checkComponents(app, contract, contractError) {
|
|
|
23431
24055
|
}
|
|
23432
24056
|
};
|
|
23433
24057
|
for (const uid of uids) if (isPart4(elements[uid])) refsIn(elements[uid].props?.props);
|
|
24058
|
+
for (const page of Array.isArray(contract.pages) ? contract.pages : []) {
|
|
24059
|
+
const source = page?.nav?.counts?.source;
|
|
24060
|
+
if (typeof source === "string" && source !== "") attributeRefs.add(source);
|
|
24061
|
+
}
|
|
23434
24062
|
for (const uid of uids) {
|
|
23435
24063
|
const element = elements[uid];
|
|
23436
24064
|
if (element.type !== "custom" || typeof element.props?.module !== "string") continue;
|
|
@@ -23572,45 +24200,45 @@ function isReferenced4(name, pageText) {
|
|
|
23572
24200
|
}
|
|
23573
24201
|
|
|
23574
24202
|
// ../flows/appspace-builder/kit/contract/levers.mjs
|
|
23575
|
-
var
|
|
23576
|
-
var
|
|
24203
|
+
var isPlainObject12 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
24204
|
+
var named8 = (value) => typeof value === "string" && value !== "";
|
|
23577
24205
|
function both2(items) {
|
|
23578
24206
|
if (items.length <= 1) return items.join("");
|
|
23579
24207
|
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
23580
24208
|
}
|
|
23581
24209
|
function paramPages(contract) {
|
|
23582
24210
|
return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
|
|
23583
|
-
(page) => page?.owned !== true &&
|
|
24211
|
+
(page) => page?.owned !== true && named8(page?.path) && Array.isArray(page.params) && page.params.length > 0
|
|
23584
24212
|
);
|
|
23585
24213
|
}
|
|
23586
24214
|
function namesOf(page) {
|
|
23587
24215
|
const out = [];
|
|
23588
|
-
for (const param of page.params) if (
|
|
24216
|
+
for (const param of page.params) if (isPlainObject12(param) && named8(param.name) && !out.includes(param.name)) out.push(param.name);
|
|
23589
24217
|
return out;
|
|
23590
24218
|
}
|
|
23591
24219
|
function elementsOn(contract, path3) {
|
|
23592
|
-
const elements =
|
|
24220
|
+
const elements = isPlainObject12(contract?.elements) ? contract.elements : {};
|
|
23593
24221
|
return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
|
|
23594
24222
|
}
|
|
23595
24223
|
function paramModeOf(element) {
|
|
23596
|
-
const props =
|
|
23597
|
-
if (element?.type === "control") return
|
|
24224
|
+
const props = isPlainObject12(element?.props) ? element.props : {};
|
|
24225
|
+
if (element?.type === "control") return named8(props.param) ? props.param : named8(element.binding) ? element.binding : null;
|
|
23598
24226
|
if (element?.type === "field" || element?.type === "action") return null;
|
|
23599
|
-
if (
|
|
23600
|
-
const typed =
|
|
23601
|
-
return
|
|
24227
|
+
if (named8(props.param) && element?.type !== "custom") return props.param;
|
|
24228
|
+
const typed = isPlainObject12(props.props) ? props.props : {};
|
|
24229
|
+
return named8(typed.param) ? typed.param : null;
|
|
23602
24230
|
}
|
|
23603
24231
|
function paramsWrittenBy(element) {
|
|
23604
24232
|
const out = [];
|
|
23605
24233
|
const one = paramModeOf(element);
|
|
23606
24234
|
if (one !== null) out.push(one);
|
|
23607
24235
|
if (element?.type === "custom") return out;
|
|
23608
|
-
const typed =
|
|
23609
|
-
if (
|
|
24236
|
+
const typed = isPlainObject12(element?.props?.props) ? element.props.props : {};
|
|
24237
|
+
if (named8(typed.param_to)) out.push(typed.param_to);
|
|
23610
24238
|
for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
|
|
23611
|
-
if (!
|
|
23612
|
-
if (
|
|
23613
|
-
if (
|
|
24239
|
+
if (!isPlainObject12(filter)) continue;
|
|
24240
|
+
if (named8(filter.param)) out.push(filter.param);
|
|
24241
|
+
if (named8(filter.param_to)) out.push(filter.param_to);
|
|
23614
24242
|
}
|
|
23615
24243
|
return out;
|
|
23616
24244
|
}
|
|
@@ -23622,10 +24250,10 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23622
24250
|
for (const [, element] of elementsOn(contract, page.path)) {
|
|
23623
24251
|
for (const name of paramsWrittenBy(element)) written.add(name);
|
|
23624
24252
|
if (element?.type !== "custom") continue;
|
|
23625
|
-
const props =
|
|
23626
|
-
const produces =
|
|
23627
|
-
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (
|
|
23628
|
-
if (!
|
|
24253
|
+
const props = isPlainObject12(element.props) ? element.props : {};
|
|
24254
|
+
const produces = isPlainObject12(props.declares?.produces) ? props.declares.produces : {};
|
|
24255
|
+
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named8(key)) written.add(key);
|
|
24256
|
+
if (!named8(props.module)) continue;
|
|
23629
24257
|
const source = moduleSource(props.module);
|
|
23630
24258
|
if (typeof source !== "string") {
|
|
23631
24259
|
unknown = true;
|
|
@@ -23636,6 +24264,13 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23636
24264
|
else written.add(call.name);
|
|
23637
24265
|
}
|
|
23638
24266
|
}
|
|
24267
|
+
if (contract?.app?.shell?.nav === "rail") {
|
|
24268
|
+
for (const entry of Array.isArray(contract.app.shell.rail?.entries) ? contract.app.shell.rail.entries : []) {
|
|
24269
|
+
if (isPlainObject12(entry?.param)) {
|
|
24270
|
+
for (const name of Object.keys(entry.param)) if (named8(name)) written.add(name);
|
|
24271
|
+
}
|
|
24272
|
+
}
|
|
24273
|
+
}
|
|
23639
24274
|
out.set(page.path, { written, unknown });
|
|
23640
24275
|
}
|
|
23641
24276
|
return out;
|
|
@@ -23649,7 +24284,7 @@ function unboundControls(contract, files = {}) {
|
|
|
23649
24284
|
for (const name of namesOf(page)) {
|
|
23650
24285
|
if (written.has(name)) continue;
|
|
23651
24286
|
const param = page.params.find((one) => one?.name === name) ?? {};
|
|
23652
|
-
const kind =
|
|
24287
|
+
const kind = named8(param.kind) ? param.kind : null;
|
|
23653
24288
|
const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
|
|
23654
24289
|
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}.`;
|
|
23655
24290
|
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.`;
|
|
@@ -23664,16 +24299,16 @@ function honoursOf(sources, name) {
|
|
|
23664
24299
|
const queue = [name];
|
|
23665
24300
|
while (queue.length > 0) {
|
|
23666
24301
|
const one = queue.shift();
|
|
23667
|
-
if (seen.has(one) || !
|
|
24302
|
+
if (seen.has(one) || !isPlainObject12(sources[one])) continue;
|
|
23668
24303
|
seen.add(one);
|
|
23669
|
-
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (
|
|
24304
|
+
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named8(honored) && !out.includes(honored)) out.push(honored);
|
|
23670
24305
|
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
|
|
23671
24306
|
}
|
|
23672
24307
|
return out;
|
|
23673
24308
|
}
|
|
23674
24309
|
function unreadParams(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
23675
|
-
const sources =
|
|
23676
|
-
const handlers =
|
|
24310
|
+
const sources = isPlainObject12(contract?.data?.sources) ? contract.data.sources : {};
|
|
24311
|
+
const handlers = isPlainObject12(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
23677
24312
|
const out = [];
|
|
23678
24313
|
for (const page of paramPages(contract)) {
|
|
23679
24314
|
const read = /* @__PURE__ */ new Set();
|
|
@@ -23682,15 +24317,15 @@ function unreadParams(contract, { moduleSource = () => void 0, handlerSource = (
|
|
|
23682
24317
|
const custom = [];
|
|
23683
24318
|
let unknown = false;
|
|
23684
24319
|
for (const [, element] of elementsOn(contract, page.path)) {
|
|
23685
|
-
const props =
|
|
23686
|
-
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(
|
|
24320
|
+
const props = isPlainObject12(element?.props) ? element.props : {};
|
|
24321
|
+
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named8);
|
|
23687
24322
|
for (const one of reads) {
|
|
23688
24323
|
if (Object.prototype.hasOwnProperty.call(sources, one)) {
|
|
23689
24324
|
if (!named_.includes(one)) named_.push(one);
|
|
23690
24325
|
for (const honored of honoursOf(sources, one)) read.add(honored);
|
|
23691
24326
|
} else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
|
|
23692
24327
|
}
|
|
23693
|
-
if (element?.type === "custom" &&
|
|
24328
|
+
if (element?.type === "custom" && named8(props.module)) {
|
|
23694
24329
|
const text3 = moduleSource(props.module);
|
|
23695
24330
|
if (typeof text3 === "string") modules.push(text3);
|
|
23696
24331
|
else unknown = true;
|
|
@@ -23732,28 +24367,28 @@ function checkLevers(app, contract, contractError) {
|
|
|
23732
24367
|
}
|
|
23733
24368
|
|
|
23734
24369
|
// ../flows/appspace-builder/kit/contract/look.mjs
|
|
23735
|
-
var
|
|
23736
|
-
var
|
|
23737
|
-
function
|
|
24370
|
+
var isPlainObject13 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
24371
|
+
var named9 = (value) => typeof value === "string" && value !== "";
|
|
24372
|
+
function humanise3(name) {
|
|
23738
24373
|
const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
|
|
23739
24374
|
return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
|
|
23740
24375
|
}
|
|
23741
|
-
var
|
|
23742
|
-
custom: { noun: "card", fallback: (p) => [
|
|
23743
|
-
chart: { noun: "card", fallback: (p) => [
|
|
23744
|
-
widget: { noun: "card", fallback: (p) => [
|
|
23745
|
-
stat: { noun: "tile", fallback: (p) => [
|
|
23746
|
-
list: { noun: "card", fallback: (p) => [
|
|
23747
|
-
queue: { noun: "card", fallback: (p) => [
|
|
24376
|
+
var WORDING2 = {
|
|
24377
|
+
custom: { noun: "card", fallback: (p) => [humanise3(String(p.module ?? "").replace(/^.*\/|\.js$/g, "")), "its module"] },
|
|
24378
|
+
chart: { noun: "card", fallback: (p) => [humanise3(String(p.y ?? "")), "its y column"] },
|
|
24379
|
+
widget: { noun: "card", fallback: (p) => [humanise3(named9(p.source) ? p.source : "widget"), "its source"] },
|
|
24380
|
+
stat: { noun: "tile", fallback: (p) => [humanise3(String(p.measure ?? "")), "its measure"] },
|
|
24381
|
+
list: { noun: "card", fallback: (p) => [humanise3(named9(p.entity) ? p.entity : "list"), "its entity"] },
|
|
24382
|
+
queue: { noun: "card", fallback: (p) => [humanise3(named9(p.entity) ? p.entity : "queue"), "its entity"] },
|
|
23748
24383
|
form: { noun: "card", fallback: () => ["New", "the runtime\u2019s default"] },
|
|
23749
24384
|
dialog: { noun: "dialog" }
|
|
23750
24385
|
};
|
|
23751
|
-
var
|
|
23752
|
-
TITLED_TYPES2.map((type) => [type, { noun: CONTROL_PARTS2.includes(type) ? "input" : "card", fallback: () => [
|
|
24386
|
+
var TITLED2 = Object.fromEntries(
|
|
24387
|
+
TITLED_TYPES2.map((type) => [type, { noun: CONTROL_PARTS2.includes(type) ? "input" : "card", fallback: () => [humanise3(type), "its type"], ...WORDING2[type] }])
|
|
23753
24388
|
);
|
|
23754
|
-
var
|
|
23755
|
-
function
|
|
23756
|
-
const elements =
|
|
24389
|
+
var LAYOUT2 = /* @__PURE__ */ new Set(["region", "tabs", "tab_panel"]);
|
|
24390
|
+
function rootsOf3(contract, page) {
|
|
24391
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
23757
24392
|
if (Array.isArray(page?.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid] !== void 0);
|
|
23758
24393
|
const contained = /* @__PURE__ */ new Set();
|
|
23759
24394
|
for (const element of Object.values(elements)) for (const child of element?.children ?? []) contained.add(child);
|
|
@@ -23762,8 +24397,8 @@ function rootsOf2(contract, page) {
|
|
|
23762
24397
|
return element?.page === page?.path && (element.parent === null || element.parent === void 0) && !contained.has(uid);
|
|
23763
24398
|
});
|
|
23764
24399
|
}
|
|
23765
|
-
function
|
|
23766
|
-
const elements =
|
|
24400
|
+
function untitledCards2(contract) {
|
|
24401
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
23767
24402
|
const out = [];
|
|
23768
24403
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23769
24404
|
if (page?.owned === true) continue;
|
|
@@ -23774,7 +24409,7 @@ function untitledCards(contract) {
|
|
|
23774
24409
|
const element = elements[uid];
|
|
23775
24410
|
if (element === void 0 || seen.has(uid)) continue;
|
|
23776
24411
|
seen.add(uid);
|
|
23777
|
-
if (
|
|
24412
|
+
if (LAYOUT2.has(element.type ?? "")) {
|
|
23778
24413
|
const children = element.children?.length ? element.children : Object.keys(elements).filter((other) => elements[other]?.parent === uid);
|
|
23779
24414
|
walk4(children);
|
|
23780
24415
|
} else {
|
|
@@ -23782,19 +24417,19 @@ function untitledCards(contract) {
|
|
|
23782
24417
|
}
|
|
23783
24418
|
}
|
|
23784
24419
|
};
|
|
23785
|
-
walk4(
|
|
24420
|
+
walk4(rootsOf3(contract, page));
|
|
23786
24421
|
for (const uid of drawn2) {
|
|
23787
24422
|
const element = elements[uid];
|
|
23788
24423
|
const type = element.type ?? "";
|
|
23789
|
-
const titled =
|
|
24424
|
+
const titled = TITLED2[type];
|
|
23790
24425
|
if (titled === void 0) continue;
|
|
23791
|
-
const props =
|
|
23792
|
-
const typed =
|
|
23793
|
-
const card =
|
|
23794
|
-
if (
|
|
23795
|
-
if (type === "dialog" ?
|
|
23796
|
-
const id =
|
|
23797
|
-
const [title, from] =
|
|
24426
|
+
const props = isPlainObject13(element.props) ? element.props : {};
|
|
24427
|
+
const typed = isPlainObject13(props.props) ? props.props : {};
|
|
24428
|
+
const card = isPlainObject13(element.card) ? element.card : isPlainObject13(props.card) ? props.card : {};
|
|
24429
|
+
if (named9(props.label) || named9(card.title)) continue;
|
|
24430
|
+
if (type === "dialog" ? named9(typed.title) : named9(typed.label)) continue;
|
|
24431
|
+
const id = named9(element.id) ? element.id : named9(props.id) ? props.id : null;
|
|
24432
|
+
const [title, from] = named9(props.field) ? [humanise3(props.field), "its field"] : id !== null ? [humanise3(id), "its id"] : titled.fallback(props);
|
|
23798
24433
|
const what = id !== null ? `${type} "${id}"` : `the ${type} ${uid}`;
|
|
23799
24434
|
const shown2 = title === "" ? "has no title at all" : `is titled "${title}" after ${from}`;
|
|
23800
24435
|
out.push({ page: page.path, uid, type, id, noun: titled.noun, title, from, message: `${what} has no label, so its ${titled.noun} ${shown2}` });
|
|
@@ -23802,49 +24437,49 @@ function untitledCards(contract) {
|
|
|
23802
24437
|
}
|
|
23803
24438
|
return out;
|
|
23804
24439
|
}
|
|
23805
|
-
var
|
|
23806
|
-
function
|
|
23807
|
-
const elements =
|
|
24440
|
+
var BAND_LOOK5 = ["subtitle", "layout", "surface", "aside"];
|
|
24441
|
+
function lookPages2(contract) {
|
|
24442
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
23808
24443
|
const out = /* @__PURE__ */ new Set();
|
|
23809
24444
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23810
|
-
if (page?.owned === true || !
|
|
23811
|
-
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) =>
|
|
23812
|
-
const cards = Object.values(elements).some((element) => element?.page === page.path && (
|
|
24445
|
+
if (page?.owned === true || !named9(page?.path)) continue;
|
|
24446
|
+
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK5.some((key) => named9(section?.[key])));
|
|
24447
|
+
const cards = Object.values(elements).some((element) => element?.page === page.path && (isPlainObject13(element.card) || named9(element.emphasis)));
|
|
23813
24448
|
if (bands || cards) out.add(page.path);
|
|
23814
24449
|
}
|
|
23815
24450
|
return out;
|
|
23816
24451
|
}
|
|
23817
|
-
var
|
|
23818
|
-
var
|
|
23819
|
-
function
|
|
23820
|
-
const elements =
|
|
23821
|
-
const roots =
|
|
23822
|
-
return (Array.isArray(page.sections) ? page.sections : []).filter((section) =>
|
|
24452
|
+
var SPAN_LAYOUTS2 = /* @__PURE__ */ new Set([void 0, "grid"]);
|
|
24453
|
+
var CONTROL_TYPES2 = /* @__PURE__ */ new Set(["filter", "control", ...CONTROL_PARTS2]);
|
|
24454
|
+
function bandsOf3(contract, page) {
|
|
24455
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
24456
|
+
const roots = rootsOf3(contract, page);
|
|
24457
|
+
return (Array.isArray(page.sections) ? page.sections : []).filter((section) => named9(section?.id)).map((section) => ({ section, roots: roots.filter((uid) => elements[uid]?.section === section.id) }));
|
|
23823
24458
|
}
|
|
23824
|
-
function
|
|
23825
|
-
const id =
|
|
24459
|
+
function nameOf4(uid, element) {
|
|
24460
|
+
const id = named9(element?.props?.id) ? element.props.id : named9(element?.id) ? element.id : null;
|
|
23826
24461
|
return id === null ? `the ${element?.type ?? "element"} ${uid}` : `${element.type} "${id}"`;
|
|
23827
24462
|
}
|
|
23828
|
-
function
|
|
23829
|
-
const props =
|
|
23830
|
-
if (
|
|
23831
|
-
if (element?.type !== "custom" && element?.type !== "action" &&
|
|
23832
|
-
if (
|
|
23833
|
-
if (element?.type === "custom" &&
|
|
24463
|
+
function isControl2(element, moduleSource) {
|
|
24464
|
+
const props = isPlainObject13(element?.props) ? element.props : {};
|
|
24465
|
+
if (CONTROL_TYPES2.has(element?.type)) return true;
|
|
24466
|
+
if (element?.type !== "custom" && element?.type !== "action" && named9(props.param)) return true;
|
|
24467
|
+
if (isPlainObject13(props.props) && named9(props.props.param)) return true;
|
|
24468
|
+
if (element?.type === "custom" && named9(props.module)) {
|
|
23834
24469
|
const source = moduleSource(props.module);
|
|
23835
24470
|
return typeof source === "string" && scanUiSetParams2(source).length > 0;
|
|
23836
24471
|
}
|
|
23837
24472
|
return false;
|
|
23838
24473
|
}
|
|
23839
|
-
function
|
|
23840
|
-
const elements =
|
|
23841
|
-
const looked =
|
|
24474
|
+
function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
|
|
24475
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
24476
|
+
const looked = lookPages2(contract);
|
|
23842
24477
|
const out = [];
|
|
23843
24478
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23844
24479
|
if (!looked.has(page?.path)) continue;
|
|
23845
|
-
for (const { section, roots } of
|
|
24480
|
+
for (const { section, roots } of bandsOf3(contract, page)) {
|
|
23846
24481
|
const band = `band "${section.id}" on ${page.path}`;
|
|
23847
|
-
const layout =
|
|
24482
|
+
const layout = named9(section.layout) ? section.layout : void 0;
|
|
23848
24483
|
if (layout === "kpi_strip") {
|
|
23849
24484
|
for (const uid of roots) {
|
|
23850
24485
|
const element = elements[uid];
|
|
@@ -23854,30 +24489,30 @@ function layoutProblems(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23854
24489
|
page: page.path,
|
|
23855
24490
|
section: section.id,
|
|
23856
24491
|
uid,
|
|
23857
|
-
message: `${band} is a kpi_strip, which draws equal KPI tiles, and holds ${
|
|
24492
|
+
message: `${band} is a kpi_strip, which draws equal KPI tiles, and holds ${nameOf4(uid, element)}; move it to a band of its own, or make it a stat (or a kpi widget)`
|
|
23858
24493
|
});
|
|
23859
24494
|
}
|
|
23860
24495
|
}
|
|
23861
24496
|
if (layout === "sidebar" && roots.length > 0) {
|
|
23862
24497
|
const uid = section.aside === "start" ? roots[0] : roots[roots.length - 1];
|
|
23863
|
-
if (!
|
|
24498
|
+
if (!isControl2(elements[uid], moduleSource)) {
|
|
23864
24499
|
out.push({
|
|
23865
24500
|
rule: "L2",
|
|
23866
24501
|
page: page.path,
|
|
23867
24502
|
section: section.id,
|
|
23868
24503
|
uid,
|
|
23869
|
-
message: `${band} is a sidebar whose aside (its ${section.aside === "start" ? "first" : "last"} element) is ${
|
|
24504
|
+
message: `${band} is a sidebar whose aside (its ${section.aside === "start" ? "first" : "last"} element) is ${nameOf4(uid, elements[uid])}; the narrow column holds the controls that turn the page \u2014 a filter, an input, a part in param mode, a custom that calls ui.setParam \u2014 so put one there, or pick another layout`
|
|
23870
24505
|
});
|
|
23871
24506
|
}
|
|
23872
24507
|
}
|
|
23873
|
-
if (
|
|
24508
|
+
if (SPAN_LAYOUTS2.has(layout)) {
|
|
23874
24509
|
let row = [];
|
|
23875
24510
|
let used = 0;
|
|
23876
24511
|
for (const uid of roots) {
|
|
23877
24512
|
const raw = elements[uid]?.grid?.span;
|
|
23878
24513
|
const span = raw === null || raw === void 0 ? 12 : Number(raw);
|
|
23879
24514
|
if (!Number.isInteger(span) || span < 1 || span > 12) {
|
|
23880
|
-
out.push({ rule: "L4", page: page.path, section: section.id, uid, message: `${band}: ${
|
|
24515
|
+
out.push({ rule: "L4", page: page.path, section: section.id, uid, message: `${band}: ${nameOf4(uid, elements[uid])} spans ${JSON.stringify(raw)}, and a span is a whole number of the twelve columns, 1 to 12` });
|
|
23881
24516
|
continue;
|
|
23882
24517
|
}
|
|
23883
24518
|
if (used + span > 12) {
|
|
@@ -23888,7 +24523,7 @@ function layoutProblems(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23888
24523
|
page: page.path,
|
|
23889
24524
|
section: section.id,
|
|
23890
24525
|
uid,
|
|
23891
|
-
message: `${band}: ${row.map((one) =>
|
|
24526
|
+
message: `${band}: ${row.map((one) => nameOf4(one.uid, elements[one.uid])).join(" + ")} + ${nameOf4(uid, elements[uid])} span ${spans.join(" + ")} = ${spans.reduce((a, b) => a + b, 0)} on one row of twelve, so the last wraps and leaves ${12 - used} column${12 - used === 1 ? "" : "s"} of the row empty; make the row's spans add up to 12`
|
|
23892
24527
|
});
|
|
23893
24528
|
}
|
|
23894
24529
|
row = [];
|
|
@@ -23906,6 +24541,137 @@ function layoutProblems(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23906
24541
|
}
|
|
23907
24542
|
return out;
|
|
23908
24543
|
}
|
|
24544
|
+
var HEADER_ACTION_TYPES2 = ["button", "dialog"];
|
|
24545
|
+
function headerActionProblems2(contract) {
|
|
24546
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
24547
|
+
const out = [];
|
|
24548
|
+
for (const [pageIndex, page] of (Array.isArray(contract?.pages) ? contract.pages : []).entries()) {
|
|
24549
|
+
if (page?.owned === true || !isPlainObject13(page?.header) || page.header.actions === void 0) continue;
|
|
24550
|
+
const say = (index, id, uid, message) => out.push({ rule: "L5", code: "header_action_unknown", page: page.path, pageIndex, index, id, uid, message });
|
|
24551
|
+
const actions = page.header.actions;
|
|
24552
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
24553
|
+
say(null, actions, null, `${page.path} states header.actions as ${JSON.stringify(actions)}; it is a list of the ids of the buttons and dialogs drawn beside the title`);
|
|
24554
|
+
continue;
|
|
24555
|
+
}
|
|
24556
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24557
|
+
for (const [index, id] of actions.entries()) {
|
|
24558
|
+
if (!named9(id)) {
|
|
24559
|
+
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`);
|
|
24560
|
+
continue;
|
|
24561
|
+
}
|
|
24562
|
+
if (seen.has(id)) {
|
|
24563
|
+
say(index, id, null, `${page.path}'s header.actions lists "${id}" twice; the runtime draws it once`);
|
|
24564
|
+
continue;
|
|
24565
|
+
}
|
|
24566
|
+
seen.add(id);
|
|
24567
|
+
const hits = Object.keys(elements).filter((uid) => {
|
|
24568
|
+
const element2 = elements[uid];
|
|
24569
|
+
return element2?.page === page.path && element2.type !== "region" && (named9(element2.props?.id) ? element2.props.id : element2.id) === id;
|
|
24570
|
+
});
|
|
24571
|
+
if (hits.length === 0) {
|
|
24572
|
+
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`);
|
|
24573
|
+
continue;
|
|
24574
|
+
}
|
|
24575
|
+
const element = elements[hits[0]];
|
|
24576
|
+
if (!HEADER_ACTION_TYPES2.includes(element.type)) {
|
|
24577
|
+
say(index, id, hits[0], `${page.path}'s header.actions names ${nameOf4(hits[0], element)}, and a header action is a ${HEADER_ACTION_TYPES2.join(" or a ")}; make it one, or leave it in its band`);
|
|
24578
|
+
}
|
|
24579
|
+
}
|
|
24580
|
+
}
|
|
24581
|
+
return out;
|
|
24582
|
+
}
|
|
24583
|
+
function navCountProblems2(contract) {
|
|
24584
|
+
const out = [];
|
|
24585
|
+
const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24586
|
+
const pages = (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true);
|
|
24587
|
+
const entries = Array.isArray(contract?.app?.shell?.rail?.entries) ? contract.app.shell.rail.entries : [];
|
|
24588
|
+
const measured = entries.map((entry, index) => ({ entry, index })).filter(({ entry }) => named9(entry?.measure));
|
|
24589
|
+
const counted = pages.filter((page) => isPlainObject13(page.nav) && page.nav.counts !== void 0);
|
|
24590
|
+
if (counted.length === 0) {
|
|
24591
|
+
if (measured.length > 0) {
|
|
24592
|
+
out.push({
|
|
24593
|
+
rule: "L6",
|
|
24594
|
+
page: null,
|
|
24595
|
+
where: "app.shell.rail.entries",
|
|
24596
|
+
message: `the rail's entries name measures (${measured.map(({ entry }) => JSON.stringify(entry.measure)).join(", ")}) and no page states nav.counts.source, so no count is ever drawn; state the summary handler on the pages that draw the rail`
|
|
24597
|
+
});
|
|
24598
|
+
}
|
|
24599
|
+
return out;
|
|
24600
|
+
}
|
|
24601
|
+
const measuresChecked = /* @__PURE__ */ new Set();
|
|
24602
|
+
for (const page of counted) {
|
|
24603
|
+
const source = page.nav.counts?.source;
|
|
24604
|
+
const where2 = `${page.path} nav.counts.source`;
|
|
24605
|
+
if (!named9(source)) {
|
|
24606
|
+
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` });
|
|
24607
|
+
continue;
|
|
24608
|
+
}
|
|
24609
|
+
const handler = handlers[source];
|
|
24610
|
+
if (!isPlainObject13(handler) || handler.role !== "summary") {
|
|
24611
|
+
out.push({
|
|
24612
|
+
rule: "L6",
|
|
24613
|
+
page: page.path,
|
|
24614
|
+
where: where2,
|
|
24615
|
+
message: `${where2} is "${source}", which is ${isPlainObject13(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`
|
|
24616
|
+
});
|
|
24617
|
+
continue;
|
|
24618
|
+
}
|
|
24619
|
+
if (!isPlainObject13(handler.measures) || measuresChecked.has(source)) continue;
|
|
24620
|
+
measuresChecked.add(source);
|
|
24621
|
+
for (const { entry, index } of measured) {
|
|
24622
|
+
if (Object.prototype.hasOwnProperty.call(handler.measures, entry.measure)) continue;
|
|
24623
|
+
out.push({
|
|
24624
|
+
rule: "L6",
|
|
24625
|
+
page: page.path,
|
|
24626
|
+
where: `app.shell.rail.entries[${index}].measure`,
|
|
24627
|
+
message: `rail entry "${entry.label}" counts measure "${entry.measure}", which "${source}" (${page.path} nav.counts.source) does not answer (it has ${Object.keys(handler.measures).map((one) => JSON.stringify(one)).join(", ")}), so its count is never drawn`
|
|
24628
|
+
});
|
|
24629
|
+
}
|
|
24630
|
+
}
|
|
24631
|
+
return out;
|
|
24632
|
+
}
|
|
24633
|
+
function appRoles2(contract) {
|
|
24634
|
+
const raw = Array.isArray(contract?.backend?.roles) ? contract.backend.roles : [];
|
|
24635
|
+
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));
|
|
24636
|
+
}
|
|
24637
|
+
function railReachProblems2(contract) {
|
|
24638
|
+
const out = [];
|
|
24639
|
+
const rail = contract?.app?.shell?.rail;
|
|
24640
|
+
if (!isPlainObject13(rail)) return out;
|
|
24641
|
+
const roles = appRoles2(contract);
|
|
24642
|
+
if (roles.length === 0) return out;
|
|
24643
|
+
const declared = new Map(roles.map((role) => [role.name, role]));
|
|
24644
|
+
const pages = new Map((Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named9(page?.path)).map((page) => [page.path, page]));
|
|
24645
|
+
const openers = (page) => Array.isArray(page?.roles) && page.roles.filter(named9).length > 0 ? page.roles.filter(named9) : null;
|
|
24646
|
+
const canOpen = (name, page) => {
|
|
24647
|
+
const open2 = openers(page);
|
|
24648
|
+
if (open2 === null) return true;
|
|
24649
|
+
const role = declared.get(name);
|
|
24650
|
+
return open2.includes(name) || (role?.sees ?? []).some((seen) => open2.includes(seen));
|
|
24651
|
+
};
|
|
24652
|
+
for (const key of ["entries", "footer"]) {
|
|
24653
|
+
for (const [index, entry] of (Array.isArray(rail[key]) ? rail[key] : []).entries()) {
|
|
24654
|
+
if (!isPlainObject13(entry) || !named9(entry.label) || !named9(entry.page)) continue;
|
|
24655
|
+
const page = pages.get(entry.page);
|
|
24656
|
+
if (page === void 0) continue;
|
|
24657
|
+
const where2 = `app.shell.rail.${key}[${index}]`;
|
|
24658
|
+
const viewers = named9(entry.role) ? entry.role.split(/\s+/).filter(named9) : openers(page) ?? roles.map((role) => role.name);
|
|
24659
|
+
const known = viewers.filter((name) => declared.has(name));
|
|
24660
|
+
if (known.length === 0) {
|
|
24661
|
+
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` });
|
|
24662
|
+
continue;
|
|
24663
|
+
}
|
|
24664
|
+
if (!known.some((name) => canOpen(name, page))) {
|
|
24665
|
+
out.push({
|
|
24666
|
+
rule: "L7",
|
|
24667
|
+
where: where2,
|
|
24668
|
+
message: `rail ${key === "footer" ? "footer link" : "entry"} "${entry.label}" links to ${entry.page}, which ${known.map((one) => JSON.stringify(one)).join(", ")} may not open (it is for ${openers(page).map((one) => JSON.stringify(one)).join(", ")}), so the entry is dead; give it the page's roles, or link a page they may open`
|
|
24669
|
+
});
|
|
24670
|
+
}
|
|
24671
|
+
}
|
|
24672
|
+
}
|
|
24673
|
+
return out;
|
|
24674
|
+
}
|
|
23909
24675
|
|
|
23910
24676
|
// ../flows/appspace-builder/kit/contract/platformPalette.mjs
|
|
23911
24677
|
var PLATFORM_CHART2 = Object.freeze([
|
|
@@ -24127,7 +24893,7 @@ function rgbText2(parts) {
|
|
|
24127
24893
|
|
|
24128
24894
|
// ../flows/appspace-builder/kit/checks/rules/look.mjs
|
|
24129
24895
|
var THEME_FILE8 = "ui/theme.css";
|
|
24130
|
-
var RULES3 = ["L1", "L2", "L3", "L4"];
|
|
24896
|
+
var RULES3 = ["L1", "L2", "L3", "L4", "L5", "L6", "L7"];
|
|
24131
24897
|
function checkLook(app, contract, contractError) {
|
|
24132
24898
|
const view = {
|
|
24133
24899
|
...app,
|
|
@@ -24139,14 +24905,23 @@ function checkLook(app, contract, contractError) {
|
|
|
24139
24905
|
return { violations, skipped: RULES3.map((rule) => ({ rule, reason })) };
|
|
24140
24906
|
}
|
|
24141
24907
|
const moduleSource = (module) => app.ui.find((file) => file.path === `ui/${module}`)?.text;
|
|
24142
|
-
for (const found of
|
|
24908
|
+
for (const found of layoutProblems2(contract, { moduleSource })) {
|
|
24143
24909
|
violations.push({ rule: found.rule, where: found.uid ?? found.page, message: found.message });
|
|
24144
24910
|
}
|
|
24145
|
-
const looked =
|
|
24146
|
-
for (const found of
|
|
24911
|
+
const looked = lookPages2(contract);
|
|
24912
|
+
for (const found of untitledCards2(contract)) {
|
|
24147
24913
|
if (!looked.has(found.page)) continue;
|
|
24148
24914
|
violations.push({ rule: "L3", where: found.uid, message: `${found.message}; give it props.label (or card.title) \u2014 what a reader should call this ${found.noun}` });
|
|
24149
24915
|
}
|
|
24916
|
+
for (const found of headerActionProblems2(contract)) {
|
|
24917
|
+
violations.push({ rule: "L5", where: found.uid ?? `${found.page} header.actions`, message: `${found.code}: ${found.message}` });
|
|
24918
|
+
}
|
|
24919
|
+
for (const found of navCountProblems2(contract)) {
|
|
24920
|
+
violations.push({ rule: "L6", where: found.where, message: found.message });
|
|
24921
|
+
}
|
|
24922
|
+
for (const found of railReachProblems2(contract)) {
|
|
24923
|
+
violations.push({ rule: "L7", where: found.where, message: found.message });
|
|
24924
|
+
}
|
|
24150
24925
|
return { violations, skipped: [] };
|
|
24151
24926
|
}
|
|
24152
24927
|
|
|
@@ -24307,7 +25082,7 @@ function rolesOf5(manifest) {
|
|
|
24307
25082
|
// ../flows/appspace-builder/kit/checks/rules/ux.mjs
|
|
24308
25083
|
var RULES4 = ["A3.23", "A3.24", "A3.25", "A3.26", "A3.27"];
|
|
24309
25084
|
var STATE_COLUMNS = ["status", "state"];
|
|
24310
|
-
var
|
|
25085
|
+
var isPlainObject14 = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
24311
25086
|
var said = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
24312
25087
|
var OPENERS4 = /* @__PURE__ */ new Set(["dialog", "list", "queue", "detail"]);
|
|
24313
25088
|
var ROWS = /* @__PURE__ */ new Set(["list", "queue", "data_grid"]);
|
|
@@ -24316,9 +25091,9 @@ function checkUx(app, contract, contractError) {
|
|
|
24316
25091
|
const reason = `the contract could not be extracted (${contractError ?? "no reason given"}), so the forms, lists and dialogs are not knowable here`;
|
|
24317
25092
|
return { violations: [], skipped: RULES4.map((rule) => ({ rule, reason })) };
|
|
24318
25093
|
}
|
|
24319
|
-
const elements =
|
|
24320
|
-
const handlers =
|
|
24321
|
-
const tables =
|
|
25094
|
+
const elements = isPlainObject14(contract.elements) ? contract.elements : {};
|
|
25095
|
+
const handlers = isPlainObject14(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
25096
|
+
const tables = isPlainObject14(contract.app?.data_model?.tables) ? contract.app.data_model.tables : {};
|
|
24322
25097
|
const uids = Object.keys(elements).sort(byPageThenRank);
|
|
24323
25098
|
const kidsOf = (uid) => {
|
|
24324
25099
|
const el = elements[uid];
|
|
@@ -24356,15 +25131,15 @@ function checkUx(app, contract, contractError) {
|
|
|
24356
25131
|
if (!ROWS.has(el?.type)) continue;
|
|
24357
25132
|
if (ancestors(uid).some((up) => up.type === "detail")) continue;
|
|
24358
25133
|
const table = tableOf2(el);
|
|
24359
|
-
if (!table || table.startsWith("_") || !
|
|
25134
|
+
if (!table || table.startsWith("_") || !isPlainObject14(tables[table]?.columns) || tables[table].singleton === true) continue;
|
|
24360
25135
|
const columns = tables[table].columns;
|
|
24361
|
-
const pk = Object.keys(columns).find((c) =>
|
|
25136
|
+
const pk = Object.keys(columns).find((c) => isPlainObject14(columns[c]) && columns[c].pk === true) ?? "id";
|
|
24362
25137
|
const fields = Object.keys(columns).filter((c) => c !== pk && !c.startsWith("_"));
|
|
24363
25138
|
let shown2;
|
|
24364
25139
|
let opens3;
|
|
24365
25140
|
if (el.type === "data_grid") {
|
|
24366
|
-
const typed =
|
|
24367
|
-
shown2 = new Set((Array.isArray(typed.columns) ? typed.columns : []).map((c) =>
|
|
25141
|
+
const typed = isPlainObject14(el.props?.props) ? el.props.props : {};
|
|
25142
|
+
shown2 = new Set((Array.isArray(typed.columns) ? typed.columns : []).map((c) => isPlainObject14(c) ? c.field : c).filter(said));
|
|
24368
25143
|
opens3 = Boolean(said(typed.on_row)) || Array.isArray(typed.row_actions) && typed.row_actions.length > 0;
|
|
24369
25144
|
} else {
|
|
24370
25145
|
const kids = kidsOf(uid).map((k) => elements[k]);
|
|
@@ -24384,7 +25159,7 @@ function checkUx(app, contract, contractError) {
|
|
|
24384
25159
|
for (const uid of uids) {
|
|
24385
25160
|
const el = elements[uid];
|
|
24386
25161
|
if (el?.type !== "dialog") continue;
|
|
24387
|
-
const typed =
|
|
25162
|
+
const typed = isPlainObject14(el.props?.props) ? el.props.props : {};
|
|
24388
25163
|
if (typed.values_from === "row") continue;
|
|
24389
25164
|
if (said(typed.trigger) || said(el.props?.label) || said(typed.title)) continue;
|
|
24390
25165
|
violations.push({
|
|
@@ -24397,8 +25172,8 @@ function checkUx(app, contract, contractError) {
|
|
|
24397
25172
|
const el = elements[uid];
|
|
24398
25173
|
if (!ROWS.has(el?.type) || el.type === "data_grid") continue;
|
|
24399
25174
|
const table = tableOf2(el);
|
|
24400
|
-
const columns =
|
|
24401
|
-
const state = STATE_COLUMNS.find((c) =>
|
|
25175
|
+
const columns = isPlainObject14(tables[table]?.columns) ? tables[table].columns : {};
|
|
25176
|
+
const state = STATE_COLUMNS.find((c) => isPlainObject14(columns[c]) && Array.isArray(columns[c].enum));
|
|
24402
25177
|
const seen = /* @__PURE__ */ new Map();
|
|
24403
25178
|
for (const kid of kidsOf(uid)) {
|
|
24404
25179
|
const action = elements[kid];
|
|
@@ -24439,8 +25214,8 @@ function split(uid) {
|
|
|
24439
25214
|
}
|
|
24440
25215
|
|
|
24441
25216
|
// ../flows/appspace-builder/kit/contract/workflow.mjs
|
|
24442
|
-
var
|
|
24443
|
-
var
|
|
25217
|
+
var isPlainObject15 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25218
|
+
var named10 = (value) => typeof value === "string" && value !== "";
|
|
24444
25219
|
function either5(items) {
|
|
24445
25220
|
if (items.length <= 1) return items.join("");
|
|
24446
25221
|
return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
|
|
@@ -24456,16 +25231,16 @@ function roleModel2(contract) {
|
|
|
24456
25231
|
const sees = /* @__PURE__ */ new Map();
|
|
24457
25232
|
const home = /* @__PURE__ */ new Map();
|
|
24458
25233
|
for (const row of rows2) {
|
|
24459
|
-
if (!
|
|
25234
|
+
if (!isPlainObject15(row) || !named10(row.name) || names.includes(row.name)) continue;
|
|
24460
25235
|
names.push(row.name);
|
|
24461
|
-
sees.set(row.name, new Set((Array.isArray(row.sees) ? row.sees : []).filter(
|
|
24462
|
-
if (
|
|
25236
|
+
sees.set(row.name, new Set((Array.isArray(row.sees) ? row.sees : []).filter(named10)));
|
|
25237
|
+
if (named10(row.home)) home.set(row.name, row.home);
|
|
24463
25238
|
}
|
|
24464
25239
|
return { names, sees, home };
|
|
24465
25240
|
}
|
|
24466
25241
|
function gateRoles2(gate) {
|
|
24467
|
-
if (Array.isArray(gate)) return gate.filter(
|
|
24468
|
-
return
|
|
25242
|
+
if (Array.isArray(gate)) return gate.filter(named10);
|
|
25243
|
+
return named10(gate) ? gate.split(/[\s,]+/).filter(named10) : [];
|
|
24469
25244
|
}
|
|
24470
25245
|
function seesGate2(model, role, gate) {
|
|
24471
25246
|
const wanted = gateRoles2(gate);
|
|
@@ -24483,7 +25258,7 @@ var HREF2 = /\bhref\s*=\s*["']([^"']*)["']/i;
|
|
|
24483
25258
|
var ROLE_ATTR4 = /\bdata-dr-role\s*=\s*["']([^"']*)["']/i;
|
|
24484
25259
|
var PAGE_LITERAL2 = /["'`](?:\.\/)?([A-Za-z0-9_-]+\.html)(?:[?#][^"'`]*)?["'`]/g;
|
|
24485
25260
|
function targetOf2(href) {
|
|
24486
|
-
if (!
|
|
25261
|
+
if (!named10(href) || /^(?:[a-z]+:)?\/\//i.test(href) || href.startsWith("/") || href.startsWith("#")) return null;
|
|
24487
25262
|
const file = href.replace(/^\.\//, "").replace(/[?#].*$/, "");
|
|
24488
25263
|
if (!/^[A-Za-z0-9_-]+\.html$/.test(file)) return null;
|
|
24489
25264
|
return `ui/${file}`;
|
|
@@ -24521,7 +25296,7 @@ function moduleLinks2(source) {
|
|
|
24521
25296
|
return out;
|
|
24522
25297
|
}
|
|
24523
25298
|
function pagesOf2(contract) {
|
|
24524
|
-
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) =>
|
|
25299
|
+
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named10(page?.path));
|
|
24525
25300
|
}
|
|
24526
25301
|
function entryPage2(contract) {
|
|
24527
25302
|
const pages = pagesOf2(contract);
|
|
@@ -24540,10 +25315,10 @@ function reachByRole2(contract, { pageSource = () => void 0, moduleSource = () =
|
|
|
24540
25315
|
const model = roleModel2(contract);
|
|
24541
25316
|
const pages = pagesOf2(contract);
|
|
24542
25317
|
const byPath = new Map(pages.map((page) => [page.path, page]));
|
|
24543
|
-
const elements =
|
|
25318
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
24544
25319
|
const modulesOn = /* @__PURE__ */ new Map();
|
|
24545
25320
|
for (const element of Object.values(elements)) {
|
|
24546
|
-
if (element?.type !== "custom" || !
|
|
25321
|
+
if (element?.type !== "custom" || !named10(element.page) || !named10(element.props?.module)) continue;
|
|
24547
25322
|
if (!modulesOn.has(element.page)) modulesOn.set(element.page, /* @__PURE__ */ new Set());
|
|
24548
25323
|
modulesOn.get(element.page).add(element.props.module);
|
|
24549
25324
|
}
|
|
@@ -24668,23 +25443,23 @@ function moduleAsks2(source) {
|
|
|
24668
25443
|
return ASKS2.some((pattern) => pattern.test(String(source ?? "")));
|
|
24669
25444
|
}
|
|
24670
25445
|
function decideAsks2(row) {
|
|
24671
|
-
if (row?.role !== "decide" || !
|
|
25446
|
+
if (row?.role !== "decide" || !isPlainObject15(row.decision)) return false;
|
|
24672
25447
|
const names = Object.keys(row.decision);
|
|
24673
25448
|
if (names.length !== 1) return false;
|
|
24674
25449
|
const spec = row.decision[names[0]];
|
|
24675
|
-
return
|
|
25450
|
+
return isPlainObject15(spec) && (spec.note === "required" || spec.note === "optional");
|
|
24676
25451
|
}
|
|
24677
25452
|
function insideDialog2(elements, uid) {
|
|
24678
25453
|
const seen = /* @__PURE__ */ new Set();
|
|
24679
|
-
for (let at = elements[uid]?.parent;
|
|
25454
|
+
for (let at = elements[uid]?.parent; named10(at) && !seen.has(at); at = elements[at]?.parent) {
|
|
24680
25455
|
seen.add(at);
|
|
24681
25456
|
if (elements[at]?.type === "dialog") return true;
|
|
24682
25457
|
}
|
|
24683
25458
|
return false;
|
|
24684
25459
|
}
|
|
24685
25460
|
function stateCallers2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
24686
|
-
const elements =
|
|
24687
|
-
const handlers =
|
|
25461
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
25462
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24688
25463
|
const changing = new Set(Object.keys(handlers).filter((name) => changesState2(name, handlers[name], handlerSource(name))));
|
|
24689
25464
|
const out = /* @__PURE__ */ new Map();
|
|
24690
25465
|
const add = (handler, caller) => {
|
|
@@ -24694,10 +25469,10 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
24694
25469
|
};
|
|
24695
25470
|
for (const uid of Object.keys(elements).sort()) {
|
|
24696
25471
|
const element = elements[uid];
|
|
24697
|
-
const props =
|
|
24698
|
-
const typed =
|
|
25472
|
+
const props = isPlainObject15(element?.props) ? element.props : {};
|
|
25473
|
+
const typed = isPlainObject15(props.props) ? props.props : {};
|
|
24699
25474
|
const page = element?.page ?? null;
|
|
24700
|
-
const id =
|
|
25475
|
+
const id = named10(props.id) ? props.id : uid;
|
|
24701
25476
|
switch (element?.type) {
|
|
24702
25477
|
case "form":
|
|
24703
25478
|
add(props.action, { page, uid, id, what: "form", asks: true });
|
|
@@ -24708,27 +25483,27 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
24708
25483
|
case "data_grid":
|
|
24709
25484
|
add(typed.on_row, { page, uid, id, what: "grid row click", asks: false });
|
|
24710
25485
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
24711
|
-
if (!
|
|
24712
|
-
const asks = action.confirm === true ||
|
|
25486
|
+
if (!isPlainObject15(action) || !named10(action.call)) continue;
|
|
25487
|
+
const asks = action.confirm === true || named10(action.confirm);
|
|
24713
25488
|
add(action.call, { page, uid, id, what: `row action "${action.label ?? action.call}"`, asks });
|
|
24714
25489
|
}
|
|
24715
25490
|
break;
|
|
24716
25491
|
case "action":
|
|
24717
25492
|
case "detail": {
|
|
24718
|
-
if (!
|
|
24719
|
-
const asks = insideDialog2(elements, uid) || decideAsks2(handlers[props.action]) || props.confirm === true ||
|
|
25493
|
+
if (!named10(props.action)) break;
|
|
25494
|
+
const asks = insideDialog2(elements, uid) || decideAsks2(handlers[props.action]) || props.confirm === true || named10(props.confirm);
|
|
24720
25495
|
add(props.action, { page, uid, id, what: element.type === "action" ? "button" : "detail button", asks });
|
|
24721
25496
|
break;
|
|
24722
25497
|
}
|
|
24723
25498
|
case "custom": {
|
|
24724
|
-
const declares =
|
|
25499
|
+
const declares = isPlainObject15(props.declares) ? props.declares : {};
|
|
24725
25500
|
const called = /* @__PURE__ */ new Set([
|
|
24726
|
-
...Array.isArray(declares.calls) ? declares.calls.filter(
|
|
24727
|
-
...Object.keys(
|
|
25501
|
+
...Array.isArray(declares.calls) ? declares.calls.filter(named10) : [],
|
|
25502
|
+
...Object.keys(isPlainObject15(declares.produces) ? declares.produces : {})
|
|
24728
25503
|
]);
|
|
24729
|
-
const source =
|
|
25504
|
+
const source = named10(props.module) ? moduleSource(props.module) : void 0;
|
|
24730
25505
|
if (typeof source === "string") {
|
|
24731
|
-
for (const call of scanUiCalls2(source)) if (
|
|
25506
|
+
for (const call of scanUiCalls2(source)) if (named10(call.handler)) called.add(call.handler);
|
|
24732
25507
|
}
|
|
24733
25508
|
const asks = typeof source === "string" ? moduleAsks2(source) : null;
|
|
24734
25509
|
for (const handler of called) add(handler, { page, uid, id, what: `custom ${props.module ?? ""}`.trim(), asks, module: props.module });
|
|
@@ -24741,7 +25516,7 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
24741
25516
|
return out;
|
|
24742
25517
|
}
|
|
24743
25518
|
function unconfirmedStateChanges2(contract, files = {}) {
|
|
24744
|
-
const handlers =
|
|
25519
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24745
25520
|
const out = [];
|
|
24746
25521
|
for (const [handler, callers] of [...stateCallers2(contract, files).entries()].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
|
|
24747
25522
|
const bare = callers.filter((one) => one.asks === false);
|
|
@@ -24813,7 +25588,7 @@ function requiresReason2(lines, key, vars) {
|
|
|
24813
25588
|
return false;
|
|
24814
25589
|
}
|
|
24815
25590
|
function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
|
|
24816
|
-
const handlers =
|
|
25591
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24817
25592
|
const producers = payloadProducers2(contract, { moduleSource, pageSource });
|
|
24818
25593
|
const out = [];
|
|
24819
25594
|
for (const name of Object.keys(handlers).sort()) {
|
|
@@ -24829,7 +25604,7 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
|
|
|
24829
25604
|
for (const key of one.keys) {
|
|
24830
25605
|
if (!REASON_KEYS2.includes(key)) continue;
|
|
24831
25606
|
if (!sent.has(key)) sent.set(key, /* @__PURE__ */ new Set());
|
|
24832
|
-
if (
|
|
25607
|
+
if (named10(one.page)) sent.get(key).add(one.page);
|
|
24833
25608
|
}
|
|
24834
25609
|
}
|
|
24835
25610
|
for (const [key, pages] of [...sent.entries()].sort()) {
|
|
@@ -24852,7 +25627,7 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
|
|
|
24852
25627
|
return out;
|
|
24853
25628
|
}
|
|
24854
25629
|
function unliftableRefusals2(contract, { handlerSource = () => void 0 } = {}) {
|
|
24855
|
-
const handlers =
|
|
25630
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24856
25631
|
const out = [];
|
|
24857
25632
|
for (const name of Object.keys(handlers).sort()) {
|
|
24858
25633
|
if ((handlers[name]?.role ?? "custom") !== "custom") continue;
|
|
@@ -24910,13 +25685,13 @@ function rolesLandingNowhere2(contract) {
|
|
|
24910
25685
|
function sourceRoles2(contract, name) {
|
|
24911
25686
|
const source = contract?.data?.sources?.[name];
|
|
24912
25687
|
const stated2 = gateRoles2(source?.requiresRole);
|
|
24913
|
-
const elements =
|
|
25688
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
24914
25689
|
const pages = new Map(pagesOf2(contract).map((page) => [page.path, page]));
|
|
24915
25690
|
const union = [];
|
|
24916
25691
|
let readers = 0;
|
|
24917
25692
|
let open2 = false;
|
|
24918
25693
|
for (const element of Object.values(elements)) {
|
|
24919
|
-
const props =
|
|
25694
|
+
const props = isPlainObject15(element?.props) ? element.props : {};
|
|
24920
25695
|
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []];
|
|
24921
25696
|
if (!reads.includes(name)) continue;
|
|
24922
25697
|
readers += 1;
|
|
@@ -24951,14 +25726,14 @@ function openToEveryRole2(contract) {
|
|
|
24951
25726
|
{ one: "page", many: "pages", verb: "open" },
|
|
24952
25727
|
"State pages[].roles on each: the roles it is for (all of them, if that is what you mean)."
|
|
24953
25728
|
);
|
|
24954
|
-
const handlers =
|
|
25729
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24955
25730
|
say(
|
|
24956
25731
|
"handler",
|
|
24957
25732
|
Object.keys(handlers).sort().filter((name) => gateRoles2(handlers[name]?.requiresRole).length === 0).map((name) => `"${name}"`),
|
|
24958
25733
|
{ one: "handler", many: "handlers", verb: "call" },
|
|
24959
25734
|
"State requiresRole on each: the roles that may call it (all of them, if that is what you mean)."
|
|
24960
25735
|
);
|
|
24961
|
-
const sources =
|
|
25736
|
+
const sources = isPlainObject15(contract?.data?.sources) ? contract.data.sources : {};
|
|
24962
25737
|
say(
|
|
24963
25738
|
"source",
|
|
24964
25739
|
Object.keys(sources).sort().filter((name) => {
|
|
@@ -25005,7 +25780,7 @@ function kitFromDisk2() {
|
|
|
25005
25780
|
}
|
|
25006
25781
|
return kitOnDisk2;
|
|
25007
25782
|
}
|
|
25008
|
-
var ADVISORY = /* @__PURE__ */ new Set(["A1.5", "A1.6", "A2.5", "A2.6", "A3.15", "A3.17", "A3.19", "A3.20", "A3.21", "A3.22", "A3.23", "A3.24", "A3.25", "A3.26", "A3.27", "A3.28", "A3.29"]);
|
|
25783
|
+
var ADVISORY = /* @__PURE__ */ new Set(["L6", "L7", "A1.5", "A1.6", "A2.5", "A2.6", "A3.15", "A3.17", "A3.19", "A3.20", "A3.21", "A3.22", "A3.23", "A3.24", "A3.25", "A3.26", "A3.27", "A3.28", "A3.29"]);
|
|
25009
25784
|
function verify2({ app: appRef }, { extractContract: extractContract2 = extract2, kit } = {}) {
|
|
25010
25785
|
const KIT = kit ?? kitFromDisk2();
|
|
25011
25786
|
const app = loadApp2(resolve12(appRef.dir));
|
|
@@ -25107,6 +25882,7 @@ var kit_default = {
|
|
|
25107
25882
|
assistant: "dr-insights@1.4.0",
|
|
25108
25883
|
charts: "echarts@5.6.0",
|
|
25109
25884
|
fmt: "dr-fmt@1.0.0",
|
|
25885
|
+
markdown: "dr-md@1.0.0",
|
|
25110
25886
|
uiKit: "ui-kit/v3",
|
|
25111
25887
|
widgets: "dr-widgets@1.1.2"
|
|
25112
25888
|
};
|
|
@@ -27077,18 +27853,18 @@ async function resolveFile(path3, base, { read = readFile11, confine = false, re
|
|
|
27077
27853
|
const filename = basename2(full);
|
|
27078
27854
|
return { bytes, filename, mimeType: mimeTypeOf(filename) };
|
|
27079
27855
|
}
|
|
27080
|
-
function
|
|
27856
|
+
function isPlainObject16(value) {
|
|
27081
27857
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27082
27858
|
}
|
|
27083
27859
|
function fileColumnsOf(model) {
|
|
27084
27860
|
const out = /* @__PURE__ */ new Set();
|
|
27085
|
-
const tables =
|
|
27086
|
-
if (!
|
|
27861
|
+
const tables = isPlainObject16(model) ? model["tables"] : void 0;
|
|
27862
|
+
if (!isPlainObject16(tables)) return out;
|
|
27087
27863
|
for (const table of Object.values(tables)) {
|
|
27088
|
-
const columns =
|
|
27089
|
-
if (!
|
|
27864
|
+
const columns = isPlainObject16(table) ? table["columns"] : void 0;
|
|
27865
|
+
if (!isPlainObject16(columns)) continue;
|
|
27090
27866
|
for (const [name, column] of Object.entries(columns)) {
|
|
27091
|
-
const type = typeof column === "string" ? column :
|
|
27867
|
+
const type = typeof column === "string" ? column : isPlainObject16(column) ? column["type"] : void 0;
|
|
27092
27868
|
if (type === "file") out.add(name);
|
|
27093
27869
|
}
|
|
27094
27870
|
}
|
|
@@ -27152,7 +27928,7 @@ function typeOf(value) {
|
|
|
27152
27928
|
if (Array.isArray(value)) return "array";
|
|
27153
27929
|
return typeof value;
|
|
27154
27930
|
}
|
|
27155
|
-
function
|
|
27931
|
+
function isPlainObject17(value) {
|
|
27156
27932
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27157
27933
|
}
|
|
27158
27934
|
function describe6(value) {
|
|
@@ -27179,7 +27955,7 @@ function validateShape(shape, path3 = "$") {
|
|
|
27179
27955
|
}
|
|
27180
27956
|
return shape.length === 0 ? [] : validateShape(shape[0], `${path3}[]`);
|
|
27181
27957
|
}
|
|
27182
|
-
if (
|
|
27958
|
+
if (isPlainObject17(shape)) {
|
|
27183
27959
|
return Object.entries(shape).flatMap(
|
|
27184
27960
|
([key, sub]) => validateShape(sub, `${path3}.${key}`)
|
|
27185
27961
|
);
|
|
@@ -27208,8 +27984,8 @@ function matchShape(value, shape, path3 = "$") {
|
|
|
27208
27984
|
if (shape.length === 0) return [];
|
|
27209
27985
|
return value.flatMap((item, i) => matchShape(item, shape[0], `${path3}[${String(i)}]`));
|
|
27210
27986
|
}
|
|
27211
|
-
if (
|
|
27212
|
-
if (!
|
|
27987
|
+
if (isPlainObject17(shape)) {
|
|
27988
|
+
if (!isPlainObject17(value)) {
|
|
27213
27989
|
return [`${path3}: expected object, got ${typeOf(value)} (${describe6(value)}).`];
|
|
27214
27990
|
}
|
|
27215
27991
|
return Object.entries(shape).flatMap(
|
|
@@ -27219,8 +27995,8 @@ function matchShape(value, shape, path3 = "$") {
|
|
|
27219
27995
|
return [`${path3}: ${describe6(shape)} is not a shape.`];
|
|
27220
27996
|
}
|
|
27221
27997
|
function matchExpect(value, expected, path3 = "$") {
|
|
27222
|
-
if (
|
|
27223
|
-
if (!
|
|
27998
|
+
if (isPlainObject17(expected)) {
|
|
27999
|
+
if (!isPlainObject17(value)) {
|
|
27224
28000
|
return [`${path3}: expected an object, got ${typeOf(value)} (${describe6(value)}).`];
|
|
27225
28001
|
}
|
|
27226
28002
|
return Object.entries(expected).flatMap(
|
|
@@ -27251,7 +28027,7 @@ function isUiEntry(entry) {
|
|
|
27251
28027
|
}
|
|
27252
28028
|
function parseError(raw, where2) {
|
|
27253
28029
|
if (raw === true) return {};
|
|
27254
|
-
if (!
|
|
28030
|
+
if (!isPlainObject17(raw)) {
|
|
27255
28031
|
throw new FixtureError(
|
|
27256
28032
|
`${where2}: "error" is true, or an object with "type" and/or "contains" \u2014 not ${describe6(raw)}.`
|
|
27257
28033
|
);
|
|
@@ -27269,12 +28045,12 @@ function parseError(raw, where2) {
|
|
|
27269
28045
|
}
|
|
27270
28046
|
var FILE_FIELD = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
27271
28047
|
function parseFiles(raw, payload, where2) {
|
|
27272
|
-
if (!
|
|
28048
|
+
if (!isPlainObject17(raw)) {
|
|
27273
28049
|
throw new FixtureError(
|
|
27274
28050
|
`${where2}: "files" maps a payload key to "sample" or a path in the app folder \u2014 not ${describe6(raw)}.`
|
|
27275
28051
|
);
|
|
27276
28052
|
}
|
|
27277
|
-
if (!
|
|
28053
|
+
if (!isPlainObject17(payload)) {
|
|
27278
28054
|
throw new FixtureError(
|
|
27279
28055
|
`${where2}: a case with "files" puts {"upload": \u2026} under a payload key, so its "payload" is an object.`
|
|
27280
28056
|
);
|
|
@@ -27300,7 +28076,7 @@ function parseFiles(raw, payload, where2) {
|
|
|
27300
28076
|
}
|
|
27301
28077
|
function parseCase(handler, raw, index) {
|
|
27302
28078
|
const where2 = `handlers.${handler}[${String(index)}]`;
|
|
27303
|
-
if (!
|
|
28079
|
+
if (!isPlainObject17(raw)) {
|
|
27304
28080
|
throw new FixtureError(`${where2}: a case is an object, not ${describe6(raw)}.`);
|
|
27305
28081
|
}
|
|
27306
28082
|
const name = raw["name"];
|
|
@@ -27333,11 +28109,11 @@ function parseCase(handler, raw, index) {
|
|
|
27333
28109
|
return kase;
|
|
27334
28110
|
}
|
|
27335
28111
|
function parseFixtures(raw, source) {
|
|
27336
|
-
if (!
|
|
28112
|
+
if (!isPlainObject17(raw)) {
|
|
27337
28113
|
throw new FixtureError(`${source}: the fixtures file is a JSON object, not ${describe6(raw)}.`);
|
|
27338
28114
|
}
|
|
27339
28115
|
const handlersRaw = raw["handlers"] ?? {};
|
|
27340
|
-
if (!
|
|
28116
|
+
if (!isPlainObject17(handlersRaw)) {
|
|
27341
28117
|
throw new FixtureError(`${source}: "handlers" is an object keyed by handler name.`);
|
|
27342
28118
|
}
|
|
27343
28119
|
const cases = [];
|
|
@@ -27357,7 +28133,7 @@ function parseFixtures(raw, source) {
|
|
|
27357
28133
|
});
|
|
27358
28134
|
}
|
|
27359
28135
|
const uiRaw = raw["ui"] ?? {};
|
|
27360
|
-
if (!
|
|
28136
|
+
if (!isPlainObject17(uiRaw)) {
|
|
27361
28137
|
throw new FixtureError(`${source}: "ui" is an object.`);
|
|
27362
28138
|
}
|
|
27363
28139
|
const entry = uiRaw["entry"] ?? "index.html";
|
|
@@ -29431,45 +30207,45 @@ __export(levers_exports, {
|
|
|
29431
30207
|
unboundControls: () => unboundControls2,
|
|
29432
30208
|
unreadParams: () => unreadParams2
|
|
29433
30209
|
});
|
|
29434
|
-
var
|
|
29435
|
-
var
|
|
30210
|
+
var isPlainObject18 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30211
|
+
var named11 = (value) => typeof value === "string" && value !== "";
|
|
29436
30212
|
function both4(items) {
|
|
29437
30213
|
if (items.length <= 1) return items.join("");
|
|
29438
30214
|
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
29439
30215
|
}
|
|
29440
30216
|
function paramPages2(contract) {
|
|
29441
30217
|
return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
|
|
29442
|
-
(page) => page?.owned !== true &&
|
|
30218
|
+
(page) => page?.owned !== true && named11(page?.path) && Array.isArray(page.params) && page.params.length > 0
|
|
29443
30219
|
);
|
|
29444
30220
|
}
|
|
29445
30221
|
function namesOf2(page) {
|
|
29446
30222
|
const out = [];
|
|
29447
|
-
for (const param of page.params) if (
|
|
30223
|
+
for (const param of page.params) if (isPlainObject18(param) && named11(param.name) && !out.includes(param.name)) out.push(param.name);
|
|
29448
30224
|
return out;
|
|
29449
30225
|
}
|
|
29450
30226
|
function elementsOn2(contract, path3) {
|
|
29451
|
-
const elements =
|
|
30227
|
+
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29452
30228
|
return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
|
|
29453
30229
|
}
|
|
29454
30230
|
function paramModeOf2(element) {
|
|
29455
|
-
const props =
|
|
29456
|
-
if (element?.type === "control") return
|
|
30231
|
+
const props = isPlainObject18(element?.props) ? element.props : {};
|
|
30232
|
+
if (element?.type === "control") return named11(props.param) ? props.param : named11(element.binding) ? element.binding : null;
|
|
29457
30233
|
if (element?.type === "field" || element?.type === "action") return null;
|
|
29458
|
-
if (
|
|
29459
|
-
const typed =
|
|
29460
|
-
return
|
|
30234
|
+
if (named11(props.param) && element?.type !== "custom") return props.param;
|
|
30235
|
+
const typed = isPlainObject18(props.props) ? props.props : {};
|
|
30236
|
+
return named11(typed.param) ? typed.param : null;
|
|
29461
30237
|
}
|
|
29462
30238
|
function paramsWrittenBy2(element) {
|
|
29463
30239
|
const out = [];
|
|
29464
30240
|
const one = paramModeOf2(element);
|
|
29465
30241
|
if (one !== null) out.push(one);
|
|
29466
30242
|
if (element?.type === "custom") return out;
|
|
29467
|
-
const typed =
|
|
29468
|
-
if (
|
|
30243
|
+
const typed = isPlainObject18(element?.props?.props) ? element.props.props : {};
|
|
30244
|
+
if (named11(typed.param_to)) out.push(typed.param_to);
|
|
29469
30245
|
for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
|
|
29470
|
-
if (!
|
|
29471
|
-
if (
|
|
29472
|
-
if (
|
|
30246
|
+
if (!isPlainObject18(filter)) continue;
|
|
30247
|
+
if (named11(filter.param)) out.push(filter.param);
|
|
30248
|
+
if (named11(filter.param_to)) out.push(filter.param_to);
|
|
29473
30249
|
}
|
|
29474
30250
|
return out;
|
|
29475
30251
|
}
|
|
@@ -29481,10 +30257,10 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
29481
30257
|
for (const [, element] of elementsOn2(contract, page.path)) {
|
|
29482
30258
|
for (const name of paramsWrittenBy2(element)) written.add(name);
|
|
29483
30259
|
if (element?.type !== "custom") continue;
|
|
29484
|
-
const props =
|
|
29485
|
-
const produces =
|
|
29486
|
-
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (
|
|
29487
|
-
if (!
|
|
30260
|
+
const props = isPlainObject18(element.props) ? element.props : {};
|
|
30261
|
+
const produces = isPlainObject18(props.declares?.produces) ? props.declares.produces : {};
|
|
30262
|
+
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named11(key)) written.add(key);
|
|
30263
|
+
if (!named11(props.module)) continue;
|
|
29488
30264
|
const source = moduleSource(props.module);
|
|
29489
30265
|
if (typeof source !== "string") {
|
|
29490
30266
|
unknown = true;
|
|
@@ -29495,6 +30271,13 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
29495
30271
|
else written.add(call.name);
|
|
29496
30272
|
}
|
|
29497
30273
|
}
|
|
30274
|
+
if (contract?.app?.shell?.nav === "rail") {
|
|
30275
|
+
for (const entry of Array.isArray(contract.app.shell.rail?.entries) ? contract.app.shell.rail.entries : []) {
|
|
30276
|
+
if (isPlainObject18(entry?.param)) {
|
|
30277
|
+
for (const name of Object.keys(entry.param)) if (named11(name)) written.add(name);
|
|
30278
|
+
}
|
|
30279
|
+
}
|
|
30280
|
+
}
|
|
29498
30281
|
out.set(page.path, { written, unknown });
|
|
29499
30282
|
}
|
|
29500
30283
|
return out;
|
|
@@ -29508,7 +30291,7 @@ function unboundControls2(contract, files = {}) {
|
|
|
29508
30291
|
for (const name of namesOf2(page)) {
|
|
29509
30292
|
if (written.has(name)) continue;
|
|
29510
30293
|
const param = page.params.find((one) => one?.name === name) ?? {};
|
|
29511
|
-
const kind =
|
|
30294
|
+
const kind = named11(param.kind) ? param.kind : null;
|
|
29512
30295
|
const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
|
|
29513
30296
|
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}.`;
|
|
29514
30297
|
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.`;
|
|
@@ -29523,16 +30306,16 @@ function honoursOf2(sources, name) {
|
|
|
29523
30306
|
const queue = [name];
|
|
29524
30307
|
while (queue.length > 0) {
|
|
29525
30308
|
const one = queue.shift();
|
|
29526
|
-
if (seen.has(one) || !
|
|
30309
|
+
if (seen.has(one) || !isPlainObject18(sources[one])) continue;
|
|
29527
30310
|
seen.add(one);
|
|
29528
|
-
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (
|
|
30311
|
+
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named11(honored) && !out.includes(honored)) out.push(honored);
|
|
29529
30312
|
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
|
|
29530
30313
|
}
|
|
29531
30314
|
return out;
|
|
29532
30315
|
}
|
|
29533
30316
|
function unreadParams2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
29534
|
-
const sources =
|
|
29535
|
-
const handlers =
|
|
30317
|
+
const sources = isPlainObject18(contract?.data?.sources) ? contract.data.sources : {};
|
|
30318
|
+
const handlers = isPlainObject18(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
29536
30319
|
const out = [];
|
|
29537
30320
|
for (const page of paramPages2(contract)) {
|
|
29538
30321
|
const read = /* @__PURE__ */ new Set();
|
|
@@ -29541,15 +30324,15 @@ function unreadParams2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
29541
30324
|
const custom = [];
|
|
29542
30325
|
let unknown = false;
|
|
29543
30326
|
for (const [, element] of elementsOn2(contract, page.path)) {
|
|
29544
|
-
const props =
|
|
29545
|
-
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(
|
|
30327
|
+
const props = isPlainObject18(element?.props) ? element.props : {};
|
|
30328
|
+
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named11);
|
|
29546
30329
|
for (const one of reads) {
|
|
29547
30330
|
if (Object.prototype.hasOwnProperty.call(sources, one)) {
|
|
29548
30331
|
if (!named_.includes(one)) named_.push(one);
|
|
29549
30332
|
for (const honored of honoursOf2(sources, one)) read.add(honored);
|
|
29550
30333
|
} else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
|
|
29551
30334
|
}
|
|
29552
|
-
if (element?.type === "custom" &&
|
|
30335
|
+
if (element?.type === "custom" && named11(props.module)) {
|
|
29553
30336
|
const text3 = moduleSource(props.module);
|
|
29554
30337
|
if (typeof text3 === "string") modules.push(text3);
|
|
29555
30338
|
else unknown = true;
|
|
@@ -29574,190 +30357,6 @@ function unreadParams2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
29574
30357
|
return out;
|
|
29575
30358
|
}
|
|
29576
30359
|
|
|
29577
|
-
// ../flows/insights-builder/kit/contract/look.mjs
|
|
29578
|
-
var look_exports = {};
|
|
29579
|
-
__export(look_exports, {
|
|
29580
|
-
humanise: () => humanise3,
|
|
29581
|
-
layoutProblems: () => layoutProblems2,
|
|
29582
|
-
lookPages: () => lookPages2,
|
|
29583
|
-
rootsOf: () => rootsOf3,
|
|
29584
|
-
untitledCards: () => untitledCards2
|
|
29585
|
-
});
|
|
29586
|
-
var isPlainObject18 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29587
|
-
var named11 = (value) => typeof value === "string" && value !== "";
|
|
29588
|
-
function humanise3(name) {
|
|
29589
|
-
const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
|
|
29590
|
-
return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
|
|
29591
|
-
}
|
|
29592
|
-
var WORDING2 = {
|
|
29593
|
-
custom: { noun: "card", fallback: (p) => [humanise3(String(p.module ?? "").replace(/^.*\/|\.js$/g, "")), "its module"] },
|
|
29594
|
-
chart: { noun: "card", fallback: (p) => [humanise3(String(p.y ?? "")), "its y column"] },
|
|
29595
|
-
widget: { noun: "card", fallback: (p) => [humanise3(named11(p.source) ? p.source : "widget"), "its source"] },
|
|
29596
|
-
stat: { noun: "tile", fallback: (p) => [humanise3(String(p.measure ?? "")), "its measure"] },
|
|
29597
|
-
list: { noun: "card", fallback: (p) => [humanise3(named11(p.entity) ? p.entity : "list"), "its entity"] },
|
|
29598
|
-
queue: { noun: "card", fallback: (p) => [humanise3(named11(p.entity) ? p.entity : "queue"), "its entity"] },
|
|
29599
|
-
form: { noun: "card", fallback: () => ["New", "the runtime\u2019s default"] },
|
|
29600
|
-
dialog: { noun: "dialog" }
|
|
29601
|
-
};
|
|
29602
|
-
var TITLED2 = Object.fromEntries(
|
|
29603
|
-
TITLED_TYPES.map((type) => [type, { noun: CONTROL_PARTS.includes(type) ? "input" : "card", fallback: () => [humanise3(type), "its type"], ...WORDING2[type] }])
|
|
29604
|
-
);
|
|
29605
|
-
var LAYOUT2 = /* @__PURE__ */ new Set(["region", "tabs", "tab_panel"]);
|
|
29606
|
-
function rootsOf3(contract, page) {
|
|
29607
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29608
|
-
if (Array.isArray(page?.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid] !== void 0);
|
|
29609
|
-
const contained = /* @__PURE__ */ new Set();
|
|
29610
|
-
for (const element of Object.values(elements)) for (const child of element?.children ?? []) contained.add(child);
|
|
29611
|
-
return Object.keys(elements).filter((uid) => {
|
|
29612
|
-
const element = elements[uid];
|
|
29613
|
-
return element?.page === page?.path && (element.parent === null || element.parent === void 0) && !contained.has(uid);
|
|
29614
|
-
});
|
|
29615
|
-
}
|
|
29616
|
-
function untitledCards2(contract) {
|
|
29617
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29618
|
-
const out = [];
|
|
29619
|
-
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
29620
|
-
if (page?.owned === true) continue;
|
|
29621
|
-
const seen = /* @__PURE__ */ new Set();
|
|
29622
|
-
const drawn2 = [];
|
|
29623
|
-
const walk4 = (uids) => {
|
|
29624
|
-
for (const uid of uids) {
|
|
29625
|
-
const element = elements[uid];
|
|
29626
|
-
if (element === void 0 || seen.has(uid)) continue;
|
|
29627
|
-
seen.add(uid);
|
|
29628
|
-
if (LAYOUT2.has(element.type ?? "")) {
|
|
29629
|
-
const children = element.children?.length ? element.children : Object.keys(elements).filter((other) => elements[other]?.parent === uid);
|
|
29630
|
-
walk4(children);
|
|
29631
|
-
} else {
|
|
29632
|
-
drawn2.push(uid);
|
|
29633
|
-
}
|
|
29634
|
-
}
|
|
29635
|
-
};
|
|
29636
|
-
walk4(rootsOf3(contract, page));
|
|
29637
|
-
for (const uid of drawn2) {
|
|
29638
|
-
const element = elements[uid];
|
|
29639
|
-
const type = element.type ?? "";
|
|
29640
|
-
const titled = TITLED2[type];
|
|
29641
|
-
if (titled === void 0) continue;
|
|
29642
|
-
const props = isPlainObject18(element.props) ? element.props : {};
|
|
29643
|
-
const typed = isPlainObject18(props.props) ? props.props : {};
|
|
29644
|
-
const card = isPlainObject18(element.card) ? element.card : isPlainObject18(props.card) ? props.card : {};
|
|
29645
|
-
if (named11(props.label) || named11(card.title)) continue;
|
|
29646
|
-
if (type === "dialog" ? named11(typed.title) : named11(typed.label)) continue;
|
|
29647
|
-
const id = named11(element.id) ? element.id : named11(props.id) ? props.id : null;
|
|
29648
|
-
const [title, from] = named11(props.field) ? [humanise3(props.field), "its field"] : id !== null ? [humanise3(id), "its id"] : titled.fallback(props);
|
|
29649
|
-
const what = id !== null ? `${type} "${id}"` : `the ${type} ${uid}`;
|
|
29650
|
-
const shown2 = title === "" ? "has no title at all" : `is titled "${title}" after ${from}`;
|
|
29651
|
-
out.push({ page: page.path, uid, type, id, noun: titled.noun, title, from, message: `${what} has no label, so its ${titled.noun} ${shown2}` });
|
|
29652
|
-
}
|
|
29653
|
-
}
|
|
29654
|
-
return out;
|
|
29655
|
-
}
|
|
29656
|
-
var BAND_LOOK5 = ["subtitle", "layout", "surface", "aside"];
|
|
29657
|
-
function lookPages2(contract) {
|
|
29658
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29659
|
-
const out = /* @__PURE__ */ new Set();
|
|
29660
|
-
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
29661
|
-
if (page?.owned === true || !named11(page?.path)) continue;
|
|
29662
|
-
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK5.some((key) => named11(section?.[key])));
|
|
29663
|
-
const cards = Object.values(elements).some((element) => element?.page === page.path && (isPlainObject18(element.card) || named11(element.emphasis)));
|
|
29664
|
-
if (bands || cards) out.add(page.path);
|
|
29665
|
-
}
|
|
29666
|
-
return out;
|
|
29667
|
-
}
|
|
29668
|
-
var SPAN_LAYOUTS2 = /* @__PURE__ */ new Set([void 0, "grid"]);
|
|
29669
|
-
var CONTROL_TYPES2 = /* @__PURE__ */ new Set(["filter", "control", ...CONTROL_PARTS]);
|
|
29670
|
-
function bandsOf3(contract, page) {
|
|
29671
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29672
|
-
const roots = rootsOf3(contract, page);
|
|
29673
|
-
return (Array.isArray(page.sections) ? page.sections : []).filter((section) => named11(section?.id)).map((section) => ({ section, roots: roots.filter((uid) => elements[uid]?.section === section.id) }));
|
|
29674
|
-
}
|
|
29675
|
-
function nameOf4(uid, element) {
|
|
29676
|
-
const id = named11(element?.props?.id) ? element.props.id : named11(element?.id) ? element.id : null;
|
|
29677
|
-
return id === null ? `the ${element?.type ?? "element"} ${uid}` : `${element.type} "${id}"`;
|
|
29678
|
-
}
|
|
29679
|
-
function isControl2(element, moduleSource) {
|
|
29680
|
-
const props = isPlainObject18(element?.props) ? element.props : {};
|
|
29681
|
-
if (CONTROL_TYPES2.has(element?.type)) return true;
|
|
29682
|
-
if (element?.type !== "custom" && element?.type !== "action" && named11(props.param)) return true;
|
|
29683
|
-
if (isPlainObject18(props.props) && named11(props.props.param)) return true;
|
|
29684
|
-
if (element?.type === "custom" && named11(props.module)) {
|
|
29685
|
-
const source = moduleSource(props.module);
|
|
29686
|
-
return typeof source === "string" && scanUiSetParams(source).length > 0;
|
|
29687
|
-
}
|
|
29688
|
-
return false;
|
|
29689
|
-
}
|
|
29690
|
-
function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
|
|
29691
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29692
|
-
const looked = lookPages2(contract);
|
|
29693
|
-
const out = [];
|
|
29694
|
-
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
29695
|
-
if (!looked.has(page?.path)) continue;
|
|
29696
|
-
for (const { section, roots } of bandsOf3(contract, page)) {
|
|
29697
|
-
const band = `band "${section.id}" on ${page.path}`;
|
|
29698
|
-
const layout = named11(section.layout) ? section.layout : void 0;
|
|
29699
|
-
if (layout === "kpi_strip") {
|
|
29700
|
-
for (const uid of roots) {
|
|
29701
|
-
const element = elements[uid];
|
|
29702
|
-
if (element.type === "stat" || element.type === "widget" && element.props?.kind === "kpi") continue;
|
|
29703
|
-
out.push({
|
|
29704
|
-
rule: "L1",
|
|
29705
|
-
page: page.path,
|
|
29706
|
-
section: section.id,
|
|
29707
|
-
uid,
|
|
29708
|
-
message: `${band} is a kpi_strip, which draws equal KPI tiles, and holds ${nameOf4(uid, element)}; move it to a band of its own, or make it a stat (or a kpi widget)`
|
|
29709
|
-
});
|
|
29710
|
-
}
|
|
29711
|
-
}
|
|
29712
|
-
if (layout === "sidebar" && roots.length > 0) {
|
|
29713
|
-
const uid = section.aside === "start" ? roots[0] : roots[roots.length - 1];
|
|
29714
|
-
if (!isControl2(elements[uid], moduleSource)) {
|
|
29715
|
-
out.push({
|
|
29716
|
-
rule: "L2",
|
|
29717
|
-
page: page.path,
|
|
29718
|
-
section: section.id,
|
|
29719
|
-
uid,
|
|
29720
|
-
message: `${band} is a sidebar whose aside (its ${section.aside === "start" ? "first" : "last"} element) is ${nameOf4(uid, elements[uid])}; the narrow column holds the controls that turn the page \u2014 a filter, an input, a part in param mode, a custom that calls ui.setParam \u2014 so put one there, or pick another layout`
|
|
29721
|
-
});
|
|
29722
|
-
}
|
|
29723
|
-
}
|
|
29724
|
-
if (SPAN_LAYOUTS2.has(layout)) {
|
|
29725
|
-
let row = [];
|
|
29726
|
-
let used = 0;
|
|
29727
|
-
for (const uid of roots) {
|
|
29728
|
-
const raw = elements[uid]?.grid?.span;
|
|
29729
|
-
const span = raw === null || raw === void 0 ? 12 : Number(raw);
|
|
29730
|
-
if (!Number.isInteger(span) || span < 1 || span > 12) {
|
|
29731
|
-
out.push({ rule: "L4", page: page.path, section: section.id, uid, message: `${band}: ${nameOf4(uid, elements[uid])} spans ${JSON.stringify(raw)}, and a span is a whole number of the twelve columns, 1 to 12` });
|
|
29732
|
-
continue;
|
|
29733
|
-
}
|
|
29734
|
-
if (used + span > 12) {
|
|
29735
|
-
if (used < 12) {
|
|
29736
|
-
const spans = [...row.map((one) => one.span), span];
|
|
29737
|
-
out.push({
|
|
29738
|
-
rule: "L4",
|
|
29739
|
-
page: page.path,
|
|
29740
|
-
section: section.id,
|
|
29741
|
-
uid,
|
|
29742
|
-
message: `${band}: ${row.map((one) => nameOf4(one.uid, elements[one.uid])).join(" + ")} + ${nameOf4(uid, elements[uid])} span ${spans.join(" + ")} = ${spans.reduce((a, b) => a + b, 0)} on one row of twelve, so the last wraps and leaves ${12 - used} column${12 - used === 1 ? "" : "s"} of the row empty; make the row's spans add up to 12`
|
|
29743
|
-
});
|
|
29744
|
-
}
|
|
29745
|
-
row = [];
|
|
29746
|
-
used = 0;
|
|
29747
|
-
}
|
|
29748
|
-
row.push({ uid, span });
|
|
29749
|
-
used += span;
|
|
29750
|
-
if (used === 12) {
|
|
29751
|
-
row = [];
|
|
29752
|
-
used = 0;
|
|
29753
|
-
}
|
|
29754
|
-
}
|
|
29755
|
-
}
|
|
29756
|
-
}
|
|
29757
|
-
}
|
|
29758
|
-
return out;
|
|
29759
|
-
}
|
|
29760
|
-
|
|
29761
30360
|
// src/commands/appspace/_findings.ts
|
|
29762
30361
|
var DECLARES = declares_exports;
|
|
29763
30362
|
var LEVERS = levers_exports;
|