@datarails-ai/dr-appspace-cli 0.10.21 → 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 +1366 -782
- 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) {
|
|
@@ -18750,7 +19324,7 @@ function shelf(path3, kit, charts, contract, problems) {
|
|
|
18750
19324
|
};
|
|
18751
19325
|
}
|
|
18752
19326
|
function appbar(page, elements) {
|
|
18753
|
-
const header =
|
|
19327
|
+
const header = isPlainObject8(page.header) ? page.header : { title: page.title ?? "", summary: null };
|
|
18754
19328
|
const summary = typeof header.summary === "string" && header.summary !== "" ? `${text2(header.summary)} ` : "";
|
|
18755
19329
|
return [
|
|
18756
19330
|
`<header class="${classesOf2(elements, "region", "header", ["dr-appbar"])}" data-dr-kit="header">`,
|
|
@@ -18810,7 +19384,7 @@ function controlRow(elements) {
|
|
|
18810
19384
|
if (controls.length === 0) return [];
|
|
18811
19385
|
const out = [`<div class="${classesOf2(elements, "region", "controls", ["dr-controlrow"])}" data-dr-kit="controls">`];
|
|
18812
19386
|
for (const control of controls) {
|
|
18813
|
-
const props =
|
|
19387
|
+
const props = isPlainObject8(control.props) ? control.props : {};
|
|
18814
19388
|
const id = control.id;
|
|
18815
19389
|
out.push(' <span class="drw-field--inline">');
|
|
18816
19390
|
if (id && typeof props.label === "string") {
|
|
@@ -18856,13 +19430,13 @@ function order(element) {
|
|
|
18856
19430
|
return Number.isInteger(element.order) ? element.order : Number.MAX_SAFE_INTEGER;
|
|
18857
19431
|
}
|
|
18858
19432
|
function cardBlock(card) {
|
|
18859
|
-
const props =
|
|
19433
|
+
const props = isPlainObject8(card.props) ? card.props : {};
|
|
18860
19434
|
const kind = kindOf2(card);
|
|
18861
19435
|
const classes = (card.classes ?? []).length > 0 ? card.classes.join(" ") : kind === "kpi" ? "dr-card dr-card--kpi" : "dr-card";
|
|
18862
|
-
const slots =
|
|
19436
|
+
const slots = isPlainObject8(card.slots) && Object.keys(card.slots).length > 0 ? card.slots : { actions: true, body: "widget-body" };
|
|
18863
19437
|
const bodyClass = typeof slots.body === "string" ? slots.body : "widget-body";
|
|
18864
19438
|
const attrs = [`class="${attr2(classes)}"`];
|
|
18865
|
-
const grid =
|
|
19439
|
+
const grid = isPlainObject8(card.grid) ? card.grid : {};
|
|
18866
19440
|
if (grid.span !== null && grid.span !== void 0) attrs.push(`data-dr-span="${attr2(grid.span)}"`);
|
|
18867
19441
|
if (grid.rows !== null && grid.rows !== void 0) attrs.push(`data-dr-rows="${attr2(grid.rows)}"`);
|
|
18868
19442
|
attrs.push(`data-dr-widget="${attr2(card.id ?? "")}"`);
|
|
@@ -18923,7 +19497,7 @@ function jsLiteral(value, depth) {
|
|
|
18923
19497
|
if (typeof value === "string") return jsString(value);
|
|
18924
19498
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
18925
19499
|
if (Array.isArray(value)) return `[${value.map((item) => jsLiteral(item, depth + 1)).join(", ")}]`;
|
|
18926
|
-
if (
|
|
19500
|
+
if (isPlainObject8(value)) {
|
|
18927
19501
|
const parts = Object.keys(value).sort(natural3).map((key) => `${/^[A-Za-z_$][\w$]*$/.test(key) ? key : jsString(key)}: ${jsLiteral(value[key], depth + 1)}`);
|
|
18928
19502
|
return parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
|
|
18929
19503
|
}
|
|
@@ -18935,7 +19509,7 @@ function pyString2(value) {
|
|
|
18935
19509
|
function byProblem2(a, b) {
|
|
18936
19510
|
return natural3(a.code, b.code) || natural3(a.where ?? "", b.where ?? "") || natural3(a.message, b.message);
|
|
18937
19511
|
}
|
|
18938
|
-
function
|
|
19512
|
+
function isPlainObject8(value) {
|
|
18939
19513
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18940
19514
|
}
|
|
18941
19515
|
|
|
@@ -18944,7 +19518,7 @@ var POSITIONAL_KEYS = ["line", "order", "depth", "path"];
|
|
|
18944
19518
|
var POSITIONAL_GRID_KEYS = ["uid", "position", "of"];
|
|
18945
19519
|
function stripPositional(contract, { keepTree = true, order: order2 } = {}) {
|
|
18946
19520
|
const out = clone5(contract);
|
|
18947
|
-
const elements =
|
|
19521
|
+
const elements = isPlainObject9(out.elements) ? out.elements : {};
|
|
18948
19522
|
const pagesByPath = new Map((Array.isArray(out.pages) ? out.pages : []).map((page) => [page.path, page]));
|
|
18949
19523
|
const byPage = /* @__PURE__ */ new Map();
|
|
18950
19524
|
for (const uid of Object.keys(elements).sort(natural3)) {
|
|
@@ -18967,7 +19541,7 @@ function stripPositional(contract, { keepTree = true, order: order2 } = {}) {
|
|
|
18967
19541
|
element.uid = next;
|
|
18968
19542
|
if (element.parent !== void 0) element.parent = element.parent === null ? null : rename2(element.parent);
|
|
18969
19543
|
if (Array.isArray(element.children)) element.children = element.children.map(rename2).sort(natural3);
|
|
18970
|
-
if (
|
|
19544
|
+
if (isPlainObject9(element.grid)) for (const key of POSITIONAL_GRID_KEYS) delete element.grid[key];
|
|
18971
19545
|
rekeyed[next] = element;
|
|
18972
19546
|
}
|
|
18973
19547
|
out.elements = sortKeys2(rekeyed);
|
|
@@ -19051,7 +19625,7 @@ function rootsAmong(elements, uids) {
|
|
|
19051
19625
|
function renameDeep(value, rename2) {
|
|
19052
19626
|
if (typeof value === "string") return rename2(value);
|
|
19053
19627
|
if (Array.isArray(value)) return value.map((item) => renameDeep(item, rename2));
|
|
19054
|
-
if (
|
|
19628
|
+
if (isPlainObject9(value)) {
|
|
19055
19629
|
const out = {};
|
|
19056
19630
|
for (const [key, held] of Object.entries(value)) out[key] = renameDeep(held, rename2);
|
|
19057
19631
|
return out;
|
|
@@ -19065,14 +19639,14 @@ function sortKeys2(object) {
|
|
|
19065
19639
|
}
|
|
19066
19640
|
function clone5(value) {
|
|
19067
19641
|
if (Array.isArray(value)) return value.map(clone5);
|
|
19068
|
-
if (
|
|
19642
|
+
if (isPlainObject9(value)) {
|
|
19069
19643
|
const out = {};
|
|
19070
19644
|
for (const [key, held] of Object.entries(value)) out[key] = clone5(held);
|
|
19071
19645
|
return out;
|
|
19072
19646
|
}
|
|
19073
19647
|
return value;
|
|
19074
19648
|
}
|
|
19075
|
-
function
|
|
19649
|
+
function isPlainObject9(value) {
|
|
19076
19650
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19077
19651
|
}
|
|
19078
19652
|
|
|
@@ -20226,7 +20800,7 @@ var CHART_FORMATS4 = Object.freeze(["currency", "number", "percent"]);
|
|
|
20226
20800
|
var PLATFORM_FORMAT2 = Object.freeze({ "type": "string", "maxLength": 64, "pattern": "^(General|[^\\u0000-\\u001f]*[#0][^\\u0000-\\u001f]*)$" });
|
|
20227
20801
|
var NEGATIVE_MODES3 = Object.freeze(["minus", "red_minus", "absolute", "red_absolute"]);
|
|
20228
20802
|
var FORMAT_PATTERNS3 = Object.freeze({ "currency": "$#,###", "number": "#,###", "percent": '#,###.#"%"' });
|
|
20229
|
-
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" });
|
|
20230
20804
|
var ROW_ACTION_WHEN3 = Object.freeze({ "dr-components": "1.2.0" });
|
|
20231
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" } });
|
|
20232
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" });
|
|
@@ -20980,7 +21554,7 @@ function readSpec2(handlers) {
|
|
|
20980
21554
|
const where2 = handler.file || `handlers.json#${handler.name}`;
|
|
20981
21555
|
const literals = new Literals2(handler.source, "py");
|
|
20982
21556
|
const value = literals.constant("SPEC");
|
|
20983
|
-
if (!
|
|
21557
|
+
if (!isPlainObject10(value)) {
|
|
20984
21558
|
unresolved.push({ where: where2, key: "SPEC", text: literals.constantText("SPEC") ?? cut2("SPEC") });
|
|
20985
21559
|
continue;
|
|
20986
21560
|
}
|
|
@@ -20996,15 +21570,15 @@ function readSpec2(handlers) {
|
|
|
20996
21570
|
var DEFAULTS_KEY2 = "_defaults";
|
|
20997
21571
|
function effective2(table) {
|
|
20998
21572
|
const defaults = table[DEFAULTS_KEY2];
|
|
20999
|
-
if (!
|
|
21573
|
+
if (!isPlainObject10(defaults)) return table;
|
|
21000
21574
|
const out = /* @__PURE__ */ Object.create(null);
|
|
21001
21575
|
for (const [key, row] of Object.entries(table)) {
|
|
21002
21576
|
if (key === DEFAULTS_KEY2) continue;
|
|
21003
|
-
out[key] = key.startsWith("_") || !
|
|
21577
|
+
out[key] = key.startsWith("_") || !isPlainObject10(row) ? row : { ...defaults, ...row };
|
|
21004
21578
|
}
|
|
21005
21579
|
return out;
|
|
21006
21580
|
}
|
|
21007
|
-
function
|
|
21581
|
+
function isPlainObject10(value) {
|
|
21008
21582
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
21009
21583
|
}
|
|
21010
21584
|
|
|
@@ -21096,7 +21670,7 @@ function readTransforms2(source) {
|
|
|
21096
21670
|
if (!literals.has(TRANSFORMS_TABLE2)) return { found: false, texts: null, span: null, text: null };
|
|
21097
21671
|
const value = literals.constant(TRANSFORMS_TABLE2);
|
|
21098
21672
|
const span = literals.constantSpan(TRANSFORMS_TABLE2);
|
|
21099
|
-
const readable =
|
|
21673
|
+
const readable = isPlainObject10(value) && Object.entries(value).every(([path3, text3]) => TRANSFORM_PATH5.test(path3) && typeof text3 === "string");
|
|
21100
21674
|
if (!readable) return { found: true, texts: null, span, text: literals.constantText(TRANSFORMS_TABLE2) };
|
|
21101
21675
|
const texts = {};
|
|
21102
21676
|
for (const path3 of Object.keys(value).sort(natural4)) texts[path3] = value[path3];
|
|
@@ -21132,6 +21706,10 @@ var THEME_ATTR3 = "data-dr-theme";
|
|
|
21132
21706
|
var DENSITY_ATTR2 = "data-dr-density";
|
|
21133
21707
|
var THEME_FILE6 = "ui/theme.css";
|
|
21134
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";
|
|
21135
21713
|
var EMPHASIS_ATTR2 = "data-dr-emphasis";
|
|
21136
21714
|
var MUTATING_VERBS2 = /* @__PURE__ */ new Set(["insert", "update", "delete"]);
|
|
21137
21715
|
var TABLE_ONLY_VERBS2 = /* @__PURE__ */ new Set([...MUTATING_VERBS2, "get"]);
|
|
@@ -21174,8 +21752,10 @@ function extract2(app, { describe: describe7 } = {}) {
|
|
|
21174
21752
|
const elements = {};
|
|
21175
21753
|
const pages = [];
|
|
21176
21754
|
const pinsByPath = /* @__PURE__ */ new Map();
|
|
21755
|
+
let shell = null;
|
|
21177
21756
|
for (const page of app.pages) {
|
|
21178
21757
|
const built = readPage2(app, page, spec, elements, problems);
|
|
21758
|
+
if (shell === null && built.shell !== null && built.shell !== void 0) shell = built.shell;
|
|
21179
21759
|
if (built.page.owned !== true && !page.text.includes(COMPONENT_ATTR2)) {
|
|
21180
21760
|
readBundle2(app, page.path, built.page.scripts, elements, problems);
|
|
21181
21761
|
}
|
|
@@ -21238,7 +21818,7 @@ function extract2(app, { describe: describe7 } = {}) {
|
|
|
21238
21818
|
v: CONTRACT_V2,
|
|
21239
21819
|
generated_by: GENERATED_BY2,
|
|
21240
21820
|
source: describe7 ? "static+describe" : "static",
|
|
21241
|
-
app: appSection2(app, manifest),
|
|
21821
|
+
app: { ...appSection2(app, manifest), ...shell === null ? {} : { shell } },
|
|
21242
21822
|
pins: [...pinsByPath.values()].map((pin) => ({ ...pin, pages: [...new Set(pin.pages)].sort() })).sort((a, b) => a.order - b.order),
|
|
21243
21823
|
pages,
|
|
21244
21824
|
elements,
|
|
@@ -21256,7 +21836,7 @@ function appSection2(app, manifest) {
|
|
|
21256
21836
|
for (const handler of app.handlers) {
|
|
21257
21837
|
handlers[handler.name] = { http: handler.http || null, entrypoint: handler.file || null };
|
|
21258
21838
|
}
|
|
21259
|
-
const nav =
|
|
21839
|
+
const nav = isPlainObject10(manifest.nav) ? manifest.nav : {};
|
|
21260
21840
|
return {
|
|
21261
21841
|
id: manifest.id ?? null,
|
|
21262
21842
|
name: manifest.name ?? null,
|
|
@@ -21272,7 +21852,7 @@ function appSection2(app, manifest) {
|
|
|
21272
21852
|
api_version: manifest.apiVersion ?? manifest.api_version ?? null,
|
|
21273
21853
|
routes: routesOf3(manifest),
|
|
21274
21854
|
handlers,
|
|
21275
|
-
capabilities:
|
|
21855
|
+
capabilities: isPlainObject10(manifest.capabilities) ? manifest.capabilities : {},
|
|
21276
21856
|
data_model: app.dataModel ? normaliseModel2(app.dataModel) : null,
|
|
21277
21857
|
theme: themeSection2(app)
|
|
21278
21858
|
};
|
|
@@ -21420,14 +22000,21 @@ function readPage2(app, page, spec, elements, problems) {
|
|
|
21420
22000
|
...bands.map((entry, index) => ({ at: entry.node.index, section: banded[index] })),
|
|
21421
22001
|
...wrappers.map((node, index) => ({ at: node.index, section: wrapped[index] }))
|
|
21422
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
|
+
}
|
|
21423
22007
|
const mount = readMount2(scripts, problems);
|
|
21424
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;
|
|
21425
22010
|
return {
|
|
22011
|
+
shell: shellOf2(main2),
|
|
21426
22012
|
page: {
|
|
21427
22013
|
path: page.path,
|
|
21428
22014
|
route: routeOf3(app.manifest, page.path),
|
|
21429
22015
|
title: titleOf2(tree, page.text),
|
|
21430
22016
|
header: headerOf2(tree, page.text),
|
|
22017
|
+
...navOf2(tree, main2),
|
|
21431
22018
|
sections,
|
|
21432
22019
|
scripts: scripts.map((script) => script.path).filter((path3) => path3 !== page.path),
|
|
21433
22020
|
theme: body?.attrs[THEME_ATTR3] ?? null,
|
|
@@ -21521,13 +22108,13 @@ function partCalls2(element) {
|
|
|
21521
22108
|
const typed = element.props?.props ?? {};
|
|
21522
22109
|
const out = [];
|
|
21523
22110
|
for (const prop of Object.keys(PARTS2[element.type].events ?? {})) {
|
|
21524
|
-
if (prop !== "on_row_action" &&
|
|
22111
|
+
if (prop !== "on_row_action" && named6(typed[prop])) out.push(typed[prop]);
|
|
21525
22112
|
}
|
|
21526
22113
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
21527
|
-
if (action && typeof action === "object" &&
|
|
22114
|
+
if (action && typeof action === "object" && named6(action.call)) out.push(action.call);
|
|
21528
22115
|
}
|
|
21529
22116
|
for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
|
|
21530
|
-
if (column && typeof column === "object" && column.cell === "toggle" &&
|
|
22117
|
+
if (column && typeof column === "object" && column.cell === "toggle" && named6(column.call)) out.push(column.call);
|
|
21531
22118
|
}
|
|
21532
22119
|
return out;
|
|
21533
22120
|
}
|
|
@@ -21687,20 +22274,43 @@ function headerOf2(tree, text3) {
|
|
|
21687
22274
|
}
|
|
21688
22275
|
const region = tree.nodes.find((node) => node.attrs[KIT_ATTR2] === "header");
|
|
21689
22276
|
if (!region) return null;
|
|
21690
|
-
const heading = firstTag2(tree, region, "h1");
|
|
22277
|
+
const heading = firstTag2(tree, region, "h1", true);
|
|
21691
22278
|
if (!heading) return null;
|
|
21692
|
-
const summary = firstTag2(tree, region, "p");
|
|
21693
|
-
|
|
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;
|
|
21694
22286
|
}
|
|
21695
|
-
function firstTag2(tree, node, tag) {
|
|
22287
|
+
function firstTag2(tree, node, tag, outer = false) {
|
|
21696
22288
|
for (const child of node.children) {
|
|
21697
22289
|
const candidate = tree.nodes[child];
|
|
21698
22290
|
if (candidate.tag === tag) return candidate;
|
|
21699
|
-
|
|
22291
|
+
if (outer && (COMPONENT_ATTR2 in candidate.attrs || FIELD_ATTR2 in candidate.attrs)) continue;
|
|
22292
|
+
const found = firstTag2(tree, candidate, tag, outer);
|
|
21700
22293
|
if (found) return found;
|
|
21701
22294
|
}
|
|
21702
22295
|
return null;
|
|
21703
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
|
+
}
|
|
21704
22314
|
function sectionOf2(node, text3, index) {
|
|
21705
22315
|
const title = textOf2(text3, node);
|
|
21706
22316
|
return { id: node.attrs.id ?? slug3(title) ?? `section_${index + 1}`, title };
|
|
@@ -21711,13 +22321,13 @@ function wrappedSectionOf2(tree, node, text3, index) {
|
|
|
21711
22321
|
const out = { id: id === void 0 || id === "" ? `section_${index + 1}` : id, title: heading ? textOf2(text3, heading) : "" };
|
|
21712
22322
|
const subtitle = node.children.map((child) => tree.nodes[child]).find((child) => child.tag === "p");
|
|
21713
22323
|
if (subtitle) out.subtitle = textOf2(text3, subtitle);
|
|
21714
|
-
for (const key of
|
|
22324
|
+
for (const key of BAND_LOOK4) {
|
|
21715
22325
|
const value = node.attrs[`data-dr-${key}`];
|
|
21716
22326
|
if (value !== void 0 && value !== "") out[key] = value;
|
|
21717
22327
|
}
|
|
21718
22328
|
return out;
|
|
21719
22329
|
}
|
|
21720
|
-
var
|
|
22330
|
+
var BAND_LOOK4 = ["layout", "surface", "aside"];
|
|
21721
22331
|
function slug3(text3) {
|
|
21722
22332
|
const clean = String(text3 ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
21723
22333
|
return clean === "" ? null : clean;
|
|
@@ -21754,7 +22364,7 @@ function readMount2(scripts, problems) {
|
|
|
21754
22364
|
const brace = script.text.indexOf("{", call);
|
|
21755
22365
|
if (brace === -1) continue;
|
|
21756
22366
|
const value = new Literals2(script.text, "js").valueAtChar(brace);
|
|
21757
|
-
if (!
|
|
22367
|
+
if (!isPlainObject10(value)) {
|
|
21758
22368
|
problems.push({
|
|
21759
22369
|
code: "mount_unreadable",
|
|
21760
22370
|
where: script.path,
|
|
@@ -21766,7 +22376,7 @@ function readMount2(scripts, problems) {
|
|
|
21766
22376
|
return {
|
|
21767
22377
|
handler: typeof value.handler === "string" ? value.handler : null,
|
|
21768
22378
|
prefs: typeof value.prefs === "boolean" ? value.prefs : null,
|
|
21769
|
-
defaults:
|
|
22379
|
+
defaults: isPlainObject10(value.defaults) ? value.defaults : null
|
|
21770
22380
|
};
|
|
21771
22381
|
}
|
|
21772
22382
|
return null;
|
|
@@ -21819,7 +22429,7 @@ function bind2(elements, spec, backend, model) {
|
|
|
21819
22429
|
}
|
|
21820
22430
|
}
|
|
21821
22431
|
for (const [key, row] of Object.entries(spec.table)) {
|
|
21822
|
-
if (!isWidgetKey2(key) || !
|
|
22432
|
+
if (!isWidgetKey2(key) || !isPlainObject10(row) || !isPlainObject10(row.params)) continue;
|
|
21823
22433
|
for (const param of Object.keys(row.params)) {
|
|
21824
22434
|
const entry = controls[param] ?? (controls[param] = { components: [], widgets: [], bound: false });
|
|
21825
22435
|
if (!entry.widgets.includes(key)) entry.widgets.push(key);
|
|
@@ -21861,11 +22471,11 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
|
|
|
21861
22471
|
if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
|
|
21862
22472
|
const props = element.props ?? {};
|
|
21863
22473
|
const entity = typeof props.entity === "string" && props.entity !== "" ? props.entity : null;
|
|
21864
|
-
if (
|
|
21865
|
-
if (
|
|
22474
|
+
if (named6(props.action)) actionRow(props.action).components.push(uid);
|
|
22475
|
+
if (named6(props.source)) actionRow(props.source).reads.push(uid);
|
|
21866
22476
|
if (isPart3(element)) for (const name of partCalls2(element)) actionRow(name).components.push(uid);
|
|
21867
22477
|
if (entity !== null && element.type !== "field") entityRow(entity).components.push(uid);
|
|
21868
|
-
if (!
|
|
22478
|
+
if (!named6(props.field)) continue;
|
|
21869
22479
|
const column = entity === null ? null : (tables[entity]?.columns ?? {})[props.field] ?? null;
|
|
21870
22480
|
const injected = entity !== null && (has(tables, entity) && INJECTED_COLUMNS3.includes(props.field) || platform(entity) && PLATFORM_TABLES3[entity].includes(props.field));
|
|
21871
22481
|
const key = `${entity ?? ""}.${props.field}`;
|
|
@@ -21892,7 +22502,7 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
|
|
|
21892
22502
|
unreached_mutations: Object.keys(facts).filter((name) => (facts[name].verbs ?? []).some((verb) => MUTATING_VERBS2.has(verb)) && !reached.has(name)).sort(natural5)
|
|
21893
22503
|
};
|
|
21894
22504
|
}
|
|
21895
|
-
function
|
|
22505
|
+
function named6(value) {
|
|
21896
22506
|
return typeof value === "string" && value !== "";
|
|
21897
22507
|
}
|
|
21898
22508
|
function isWidgetKey2(key) {
|
|
@@ -21930,7 +22540,7 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
|
|
|
21930
22540
|
for (const uid of Object.keys(elements).sort(natural5)) {
|
|
21931
22541
|
const element = elements[uid];
|
|
21932
22542
|
if (element.type !== "form") continue;
|
|
21933
|
-
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"]));
|
|
21934
22544
|
if (missing.length === 0) continue;
|
|
21935
22545
|
problems.push({
|
|
21936
22546
|
code: "unbound_form",
|
|
@@ -21952,8 +22562,8 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
|
|
|
21952
22562
|
for (const uid of Object.keys(elements).sort(natural5)) {
|
|
21953
22563
|
const element = elements[uid];
|
|
21954
22564
|
const filter = element.props?.filter;
|
|
21955
|
-
if (!isComponentType2(element.type) || !
|
|
21956
|
-
const entity =
|
|
22565
|
+
if (!isComponentType2(element.type) || !isPlainObject10(filter)) continue;
|
|
22566
|
+
const entity = named6(element.props.entity) ? element.props.entity : null;
|
|
21957
22567
|
if (entity === null || !Object.prototype.hasOwnProperty.call(tables, entity)) continue;
|
|
21958
22568
|
const columns = tables[entity].columns ?? {};
|
|
21959
22569
|
for (const column of Object.keys(filter).sort()) {
|
|
@@ -22001,7 +22611,7 @@ function dataSection2(app, manifest, spec, elements, describe7, problems) {
|
|
|
22001
22611
|
...Object.keys(sources).length > 0 ? { sources } : {}
|
|
22002
22612
|
};
|
|
22003
22613
|
if (describe7) {
|
|
22004
|
-
const widgets2 =
|
|
22614
|
+
const widgets2 = isPlainObject10(describe7.widgets) ? describe7.widgets : {};
|
|
22005
22615
|
const missing = Object.keys(spec.table).filter(isWidgetKey2).filter((key) => !(key in widgets2));
|
|
22006
22616
|
if (spec.found && spec.unresolved.length === 0 && missing.length > 0) {
|
|
22007
22617
|
problems.push({
|
|
@@ -22050,12 +22660,12 @@ function readSources2(handlers) {
|
|
|
22050
22660
|
if (!SOURCES_TABLE2.test(source) || HANDLER_LITERAL2.test(source)) continue;
|
|
22051
22661
|
const literals = new Literals2(source, "py");
|
|
22052
22662
|
const table = literals.constant("SOURCES");
|
|
22053
|
-
if (!
|
|
22663
|
+
if (!isPlainObject10(table)) continue;
|
|
22054
22664
|
for (const [name, row] of Object.entries(table)) {
|
|
22055
|
-
if (
|
|
22665
|
+
if (isPlainObject10(row) && sources[name] === void 0) sources[name] = row;
|
|
22056
22666
|
}
|
|
22057
22667
|
const carried = literals.constant("TRANSFORMS");
|
|
22058
|
-
if (!
|
|
22668
|
+
if (!isPlainObject10(carried)) continue;
|
|
22059
22669
|
for (const [name, text3] of Object.entries(carried)) {
|
|
22060
22670
|
const path3 = table[name]?.transform;
|
|
22061
22671
|
if (typeof path3 === "string" && typeof text3 === "string" && !texts.has(path3)) texts.set(path3, text3);
|
|
@@ -22090,7 +22700,7 @@ function specTransforms2(handlers, spec, problems) {
|
|
|
22090
22700
|
}
|
|
22091
22701
|
const unreadable = problems.some((problem5) => problem5.code === "transform_unreadable");
|
|
22092
22702
|
for (const key of Object.keys(spec.table).filter(isWidgetKey2).sort(natural5)) {
|
|
22093
|
-
const path3 =
|
|
22703
|
+
const path3 = isPlainObject10(spec.table[key]) ? spec.table[key].transform : void 0;
|
|
22094
22704
|
if (typeof path3 !== "string" || unreadable || carried !== null && carried.has(path3)) continue;
|
|
22095
22705
|
problems.push({
|
|
22096
22706
|
code: "transform_not_carried",
|
|
@@ -22114,7 +22724,7 @@ function readApp2(handlers, elements, manifest = null) {
|
|
|
22114
22724
|
let row = null;
|
|
22115
22725
|
if (HANDLER_LITERAL2.test(source)) {
|
|
22116
22726
|
const value = new Literals2(source, "py").constant("HANDLER");
|
|
22117
|
-
if (
|
|
22727
|
+
if (isPlainObject10(value)) row = value;
|
|
22118
22728
|
}
|
|
22119
22729
|
declared[handler.name] = { role: "custom", ...row ?? {} };
|
|
22120
22730
|
if (declared[handler.name].requiresRole === void 0) {
|
|
@@ -22142,9 +22752,9 @@ function screensOf2(elements, order2 = null) {
|
|
|
22142
22752
|
screens.push({
|
|
22143
22753
|
page: element.page,
|
|
22144
22754
|
component: element.type,
|
|
22145
|
-
id:
|
|
22146
|
-
entity:
|
|
22147
|
-
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
|
|
22148
22758
|
});
|
|
22149
22759
|
}
|
|
22150
22760
|
return screens;
|
|
@@ -22156,7 +22766,7 @@ function entityAbove2(elements, element) {
|
|
|
22156
22766
|
seen.add(at);
|
|
22157
22767
|
const above = elements[at];
|
|
22158
22768
|
if (above === void 0) return null;
|
|
22159
|
-
if (
|
|
22769
|
+
if (named6(above.props?.entity)) return above.props.entity;
|
|
22160
22770
|
at = above.parent ?? null;
|
|
22161
22771
|
}
|
|
22162
22772
|
return null;
|
|
@@ -22185,7 +22795,7 @@ function backendSection2(app, manifest, pages, elements) {
|
|
|
22185
22795
|
if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
|
|
22186
22796
|
for (const key of ["action", "source"]) {
|
|
22187
22797
|
const name = element.props?.[key];
|
|
22188
|
-
if (!
|
|
22798
|
+
if (!named6(name)) continue;
|
|
22189
22799
|
record2(name, "attribute");
|
|
22190
22800
|
attributed.add(name);
|
|
22191
22801
|
}
|
|
@@ -22214,14 +22824,14 @@ function backendSection2(app, manifest, pages, elements) {
|
|
|
22214
22824
|
data_model: model,
|
|
22215
22825
|
handlers,
|
|
22216
22826
|
roles,
|
|
22217
|
-
capabilities:
|
|
22827
|
+
capabilities: isPlainObject10(manifest.capabilities) ? manifest.capabilities : {},
|
|
22218
22828
|
ui: {
|
|
22219
22829
|
entry,
|
|
22220
22830
|
handlers_called: [...called].sort(),
|
|
22221
22831
|
handlers_called_via: [...via.values()].sort((a, b) => natural5(a.name, b.name) || natural5(a.via, b.via)),
|
|
22222
22832
|
scripts_loaded: [...new Set(pages.flatMap((page) => page.scripts))].sort()
|
|
22223
22833
|
},
|
|
22224
|
-
connect_tables:
|
|
22834
|
+
connect_tables: isPlainObject10(manifest.connectTables) ? manifest.connectTables : isPlainObject10(manifest.connect_tables) ? manifest.connect_tables : {},
|
|
22225
22835
|
invariants: invariants2(model, roles, handlers)
|
|
22226
22836
|
};
|
|
22227
22837
|
}
|
|
@@ -22231,7 +22841,7 @@ function callsOnTable2(source, verb) {
|
|
|
22231
22841
|
return holders.some((holder) => new RegExp(`(?:^|[^\\w.])(?:${holder})\\s*\\.\\s*${verb}\\s*\\(`, "m").test(source));
|
|
22232
22842
|
}
|
|
22233
22843
|
function declaredHandler2(manifest, name) {
|
|
22234
|
-
return
|
|
22844
|
+
return isPlainObject10(manifest?.handlers) ? manifest.handlers[name] ?? null : null;
|
|
22235
22845
|
}
|
|
22236
22846
|
function signatureOk2(source) {
|
|
22237
22847
|
const code = source.split("\n").map((line2) => /^\s*#/.test(line2) ? "" : line2).join("\n");
|
|
@@ -22258,7 +22868,7 @@ function rolesOf3(manifest) {
|
|
|
22258
22868
|
// Declaration order IS the ladder: `authz.py` reads the manifest's list
|
|
22259
22869
|
// top down, so the first role is the most junior.
|
|
22260
22870
|
seniority: index,
|
|
22261
|
-
tables:
|
|
22871
|
+
tables: isPlainObject10(role?.tables) ? role.tables : {},
|
|
22262
22872
|
// Stated only when the manifest states it, like the platform's own
|
|
22263
22873
|
// derivation (`app_contract_store._roles_section`): the norm is a role
|
|
22264
22874
|
// that sees only its own screens, and `[]` on every role would say
|
|
@@ -22270,16 +22880,16 @@ function rolesOf3(manifest) {
|
|
|
22270
22880
|
}
|
|
22271
22881
|
function pageRolesOf2(manifest, path3) {
|
|
22272
22882
|
const pages = Array.isArray(manifest?.pages) ? manifest.pages : [];
|
|
22273
|
-
const entry = pages.find((one) =>
|
|
22883
|
+
const entry = pages.find((one) => isPlainObject10(one) && one.path === path3);
|
|
22274
22884
|
const roles = Array.isArray(entry?.roles) ? entry.roles.filter((role) => typeof role === "string" && role !== "") : [];
|
|
22275
22885
|
return roles.length > 0 ? { roles } : {};
|
|
22276
22886
|
}
|
|
22277
22887
|
function normaliseModel2(document) {
|
|
22278
22888
|
const tables = {};
|
|
22279
|
-
const raw =
|
|
22889
|
+
const raw = isPlainObject10(document?.tables) ? document.tables : {};
|
|
22280
22890
|
for (const [name, table] of Object.entries(raw)) {
|
|
22281
22891
|
const columns = {};
|
|
22282
|
-
for (const [column, spec] of Object.entries(
|
|
22892
|
+
for (const [column, spec] of Object.entries(isPlainObject10(table?.columns) ? table.columns : {})) {
|
|
22283
22893
|
const pk = Boolean(spec?.pk);
|
|
22284
22894
|
columns[column] = {
|
|
22285
22895
|
type: spec?.type ?? null,
|
|
@@ -22297,7 +22907,7 @@ function normaliseModel2(document) {
|
|
|
22297
22907
|
// `computed` (a child total the platform selects with the row) and
|
|
22298
22908
|
// `matches` (the column it should agree with), by `display`'s rule:
|
|
22299
22909
|
// only where stated, so a model without them normalises to its bytes.
|
|
22300
|
-
...
|
|
22910
|
+
...isPlainObject10(spec?.computed) ? { computed: spec.computed } : {},
|
|
22301
22911
|
...typeof spec?.matches === "string" && spec.matches !== "" ? { matches: spec.matches } : {}
|
|
22302
22912
|
};
|
|
22303
22913
|
}
|
|
@@ -22315,7 +22925,7 @@ function normaliseModel2(document) {
|
|
|
22315
22925
|
// row reads as before it is first saved. By `audit`'s rule: only where
|
|
22316
22926
|
// stated, so a model with neither keeps normalising to its bytes.
|
|
22317
22927
|
...table?.singleton === true ? { singleton: true } : {},
|
|
22318
|
-
...table?.singleton === true &&
|
|
22928
|
+
...table?.singleton === true && isPlainObject10(table?.defaults) ? { defaults: table.defaults } : {},
|
|
22319
22929
|
columns,
|
|
22320
22930
|
indexes: Array.isArray(table?.indexes) ? table.indexes.map((index) => Array.isArray(index) ? index.map(String) : [String(index)]) : [],
|
|
22321
22931
|
injected: [...INJECTED_COLUMNS3]
|
|
@@ -22648,10 +23258,10 @@ function isObject5(value) {
|
|
|
22648
23258
|
var DECLARES_KEYS2 = Object.freeze(["reads", "calls", "produces", "shows"]);
|
|
22649
23259
|
var SHOWS_KEYS2 = Object.freeze(["field", "format", "scale"]);
|
|
22650
23260
|
var SHOWS_FORMATS2 = Object.freeze(["currency", "number", "percent"]);
|
|
22651
|
-
var
|
|
22652
|
-
var
|
|
22653
|
-
var listOfNames2 = (value) => Array.isArray(value) && value.every(
|
|
22654
|
-
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;
|
|
22655
23265
|
function pyCode2(source) {
|
|
22656
23266
|
let out = "";
|
|
22657
23267
|
let i = 0;
|
|
@@ -22720,7 +23330,7 @@ function handlerPayloadReads2(row, source) {
|
|
|
22720
23330
|
return { generated: false, required: pyPayloadReads2(source).required };
|
|
22721
23331
|
}
|
|
22722
23332
|
if (role === "save" || role === "submit") {
|
|
22723
|
-
return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(
|
|
23333
|
+
return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(named7) };
|
|
22724
23334
|
}
|
|
22725
23335
|
return { generated: true, required: [] };
|
|
22726
23336
|
}
|
|
@@ -22890,32 +23500,32 @@ function childIndex2(elements) {
|
|
|
22890
23500
|
};
|
|
22891
23501
|
for (const [uid, element] of Object.entries(elements)) {
|
|
22892
23502
|
for (const child of Array.isArray(element?.children) ? element.children : []) if (child in elements) add(uid, child);
|
|
22893
|
-
if (
|
|
23503
|
+
if (named7(element?.parent) && element.parent in elements) add(element.parent, uid);
|
|
22894
23504
|
}
|
|
22895
23505
|
return index;
|
|
22896
23506
|
}
|
|
22897
23507
|
var ROW_HOLDERS2 = /* @__PURE__ */ new Set(["list", "queue", "detail"]);
|
|
22898
23508
|
var INPUT_PARTS2 = /* @__PURE__ */ new Set(["number_input", "select", "date_picker", "toggle", "slider", "checkbox", "radio_group", "file_upload"]);
|
|
22899
23509
|
function fieldOf2(element) {
|
|
22900
|
-
if (element?.type === "field" &&
|
|
22901
|
-
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;
|
|
22902
23512
|
return null;
|
|
22903
23513
|
}
|
|
22904
23514
|
function mentions2(text3, name) {
|
|
22905
23515
|
return new RegExp(`(^|[^A-Za-z0-9_])${reEscape2(name)}([^A-Za-z0-9_]|$)`).test(text3);
|
|
22906
23516
|
}
|
|
22907
23517
|
function payloadProducers2(contract, { moduleSource = () => void 0, pageSource = () => void 0 } = {}) {
|
|
22908
|
-
const elements =
|
|
22909
|
-
const handlers =
|
|
23518
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23519
|
+
const handlers = isPlainObject11(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
22910
23520
|
const children = childIndex2(elements);
|
|
22911
23521
|
const out = /* @__PURE__ */ new Map();
|
|
22912
23522
|
const add = (handler, producer) => {
|
|
22913
|
-
if (!
|
|
23523
|
+
if (!named7(handler)) return;
|
|
22914
23524
|
if (!out.has(handler)) out.set(handler, []);
|
|
22915
23525
|
out.get(handler).push(producer);
|
|
22916
23526
|
};
|
|
22917
23527
|
const insideRow = (uid) => {
|
|
22918
|
-
for (let at = elements[uid]?.parent;
|
|
23528
|
+
for (let at = elements[uid]?.parent; named7(at) && at in elements; at = elements[at]?.parent) {
|
|
22919
23529
|
if (ROW_HOLDERS2.has(elements[at]?.type)) return true;
|
|
22920
23530
|
}
|
|
22921
23531
|
return false;
|
|
@@ -22936,16 +23546,16 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
22936
23546
|
};
|
|
22937
23547
|
for (const uid of Object.keys(elements).sort()) {
|
|
22938
23548
|
const element = elements[uid];
|
|
22939
|
-
const props =
|
|
23549
|
+
const props = isPlainObject11(element?.props) ? element.props : {};
|
|
22940
23550
|
const page = element?.page ?? null;
|
|
22941
|
-
const typed =
|
|
22942
|
-
if (
|
|
23551
|
+
const typed = isPlainObject11(props.props) ? props.props : {};
|
|
23552
|
+
if (named7(props.source)) add(props.source, { page, uid, what: "reader", keys: null });
|
|
22943
23553
|
switch (element?.type) {
|
|
22944
23554
|
case "form": {
|
|
22945
23555
|
const keys2 = fieldsUnder(uid);
|
|
22946
23556
|
if (insideRow(uid) && !keys2.includes("id")) keys2.push("id");
|
|
22947
23557
|
const by = handlers[props.action]?.by;
|
|
22948
|
-
if (
|
|
23558
|
+
if (named7(by) && !keys2.includes(by)) keys2.push(by);
|
|
22949
23559
|
add(props.action, { page, uid, what: "form", keys: keys2 });
|
|
22950
23560
|
break;
|
|
22951
23561
|
}
|
|
@@ -22960,41 +23570,41 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
22960
23570
|
// stepper's current step `{step}`; a file_upload's on_upload its field.
|
|
22961
23571
|
case "button":
|
|
22962
23572
|
add(typed.on_click, { page, uid, what: "button", keys: typed.reason === "required" || typed.reason === "optional" ? ["reason"] : [] });
|
|
22963
|
-
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: [] });
|
|
22964
23574
|
break;
|
|
22965
23575
|
case "empty_state":
|
|
22966
23576
|
add(typed.on_action, { page, uid, what: "empty_state", keys: [] });
|
|
22967
23577
|
break;
|
|
22968
23578
|
case "stepper":
|
|
22969
|
-
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"] });
|
|
22970
23580
|
break;
|
|
22971
23581
|
case "file_upload":
|
|
22972
|
-
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] : [] });
|
|
22973
23583
|
break;
|
|
22974
23584
|
case "data_grid":
|
|
22975
23585
|
add(typed.on_row, { page, uid, what: "data_grid", keys: ["row"] });
|
|
22976
23586
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
22977
|
-
if (
|
|
23587
|
+
if (isPlainObject11(action) && named7(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["id", "row"] });
|
|
22978
23588
|
}
|
|
22979
23589
|
for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
|
|
22980
|
-
if (
|
|
23590
|
+
if (isPlainObject11(column) && column.cell === "toggle" && named7(column.call)) add(column.call, { page, uid, what: "data_grid", keys: ["id", "row", "field", "value"] });
|
|
22981
23591
|
}
|
|
22982
23592
|
break;
|
|
22983
23593
|
case "action": {
|
|
22984
23594
|
const keys2 = [];
|
|
22985
|
-
if (insideRow(uid)) keys2.push(
|
|
22986
|
-
else if (
|
|
23595
|
+
if (insideRow(uid)) keys2.push(named7(props.param) ? props.param : "id");
|
|
23596
|
+
else if (named7(props.param)) keys2.push(props.param);
|
|
22987
23597
|
if (handlers[props.action]?.role === "decide") keys2.push("decision", "note");
|
|
22988
23598
|
add(props.action, { page, uid, what: "action", keys: keys2 });
|
|
22989
23599
|
break;
|
|
22990
23600
|
}
|
|
22991
23601
|
case "custom": {
|
|
22992
|
-
const declares =
|
|
22993
|
-
const produces =
|
|
23602
|
+
const declares = isPlainObject11(props.declares) ? props.declares : {};
|
|
23603
|
+
const produces = isPlainObject11(declares.produces) ? declares.produces : {};
|
|
22994
23604
|
for (const [handler, keys2] of Object.entries(produces)) {
|
|
22995
23605
|
add(handler, { page, uid, what: "custom", keys: listOfNames2(keys2) ? [...keys2] : null });
|
|
22996
23606
|
}
|
|
22997
|
-
const source =
|
|
23607
|
+
const source = named7(props.module) ? moduleSource(props.module) : void 0;
|
|
22998
23608
|
if (typeof source !== "string") {
|
|
22999
23609
|
add("*", { page, uid, what: "custom", keys: null });
|
|
23000
23610
|
break;
|
|
@@ -23020,10 +23630,10 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
23020
23630
|
}
|
|
23021
23631
|
const modules = /* @__PURE__ */ new Set();
|
|
23022
23632
|
for (const element of Object.values(elements)) {
|
|
23023
|
-
if (element?.type === "custom" &&
|
|
23633
|
+
if (element?.type === "custom" && named7(element.props?.module)) modules.add(`${element.page}|${element.props.module}`);
|
|
23024
23634
|
}
|
|
23025
23635
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23026
|
-
if (!
|
|
23636
|
+
if (!named7(page?.path)) continue;
|
|
23027
23637
|
const text3 = pageSource(page.path);
|
|
23028
23638
|
const unknown = { page: page.path, uid: null, what: "script", keys: null };
|
|
23029
23639
|
if (typeof text3 !== "string") {
|
|
@@ -23068,7 +23678,7 @@ function either4(items) {
|
|
|
23068
23678
|
return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
|
|
23069
23679
|
}
|
|
23070
23680
|
function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
|
|
23071
|
-
const handlers =
|
|
23681
|
+
const handlers = isPlainObject11(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
23072
23682
|
const producers = payloadProducers2(contract, { moduleSource, pageSource });
|
|
23073
23683
|
const anywhere = producers.get("*") ?? [];
|
|
23074
23684
|
const out = [];
|
|
@@ -23079,7 +23689,7 @@ function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, module
|
|
|
23079
23689
|
if (own.length === 0 || anywhere.length > 0 || own.some((one) => one.keys === null)) continue;
|
|
23080
23690
|
const produced = [];
|
|
23081
23691
|
for (const one of own) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
|
|
23082
|
-
const pages = [...new Set(own.map((one) => one.page).filter(
|
|
23692
|
+
const pages = [...new Set(own.map((one) => one.page).filter(named7))];
|
|
23083
23693
|
for (const key of reads.required) {
|
|
23084
23694
|
if (produced.includes(key)) continue;
|
|
23085
23695
|
const there = produced.length === 0 ? "the controls there produce no payload keys" : `the controls there produce ${produced.join(", ")}`;
|
|
@@ -23091,12 +23701,12 @@ function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, module
|
|
|
23091
23701
|
return out;
|
|
23092
23702
|
}
|
|
23093
23703
|
function undeclaredCustomCalls2(contract, { moduleSource = () => void 0 } = {}) {
|
|
23094
|
-
const elements =
|
|
23704
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23095
23705
|
const out = [];
|
|
23096
23706
|
for (const uid of Object.keys(elements).sort()) {
|
|
23097
23707
|
const element = elements[uid];
|
|
23098
|
-
const props =
|
|
23099
|
-
if (element?.type !== "custom" || !
|
|
23708
|
+
const props = isPlainObject11(element?.props) ? element.props : {};
|
|
23709
|
+
if (element?.type !== "custom" || !isPlainObject11(props.declares) || !named7(props.module)) continue;
|
|
23100
23710
|
const source = moduleSource(props.module);
|
|
23101
23711
|
if (typeof source !== "string") continue;
|
|
23102
23712
|
const calls = listOfNames2(props.declares.calls) ? props.declares.calls : [];
|
|
@@ -23124,32 +23734,32 @@ function undeclaredCallMessage2(id, module, handler, calls, how) {
|
|
|
23124
23734
|
return { problem: problem5, fix, message: `${problem5} ${fix}` };
|
|
23125
23735
|
}
|
|
23126
23736
|
function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
|
|
23127
|
-
const elements =
|
|
23128
|
-
const handlers =
|
|
23737
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23738
|
+
const handlers = isPlainObject11(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
23129
23739
|
const parents = /* @__PURE__ */ new Map();
|
|
23130
23740
|
for (const [parent, kids] of childIndex2(elements)) for (const kid of kids) if (!parents.has(kid)) parents.set(kid, parent);
|
|
23131
23741
|
const out = [];
|
|
23132
23742
|
for (const uid of Object.keys(elements).sort()) {
|
|
23133
23743
|
const element = elements[uid];
|
|
23134
|
-
const typed =
|
|
23135
|
-
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;
|
|
23136
23746
|
const field = typed.field;
|
|
23137
23747
|
const receivers = [];
|
|
23138
|
-
if (
|
|
23139
|
-
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)) {
|
|
23140
23750
|
const holder = elements[at];
|
|
23141
|
-
if (holder?.type === "dialog" &&
|
|
23751
|
+
if (holder?.type === "dialog" && named7(holder.props?.props?.on_confirm)) {
|
|
23142
23752
|
receivers.push([holder.props.props.on_confirm, `the dialog "${idOf3(holder, at)}" it sits in`]);
|
|
23143
23753
|
break;
|
|
23144
23754
|
}
|
|
23145
|
-
if (holder?.type === "form" &&
|
|
23755
|
+
if (holder?.type === "form" && named7(holder.props?.action)) {
|
|
23146
23756
|
receivers.push([holder.props.action, `the form "${idOf3(holder, at)}" it sits in`]);
|
|
23147
23757
|
break;
|
|
23148
23758
|
}
|
|
23149
23759
|
}
|
|
23150
23760
|
for (const [handler, via] of receivers) {
|
|
23151
23761
|
const row = handlers[handler];
|
|
23152
|
-
if (!
|
|
23762
|
+
if (!isPlainObject11(row)) continue;
|
|
23153
23763
|
const role = row.role ?? "custom";
|
|
23154
23764
|
let read;
|
|
23155
23765
|
if (role === "custom") {
|
|
@@ -23172,16 +23782,16 @@ function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
|
|
|
23172
23782
|
return out;
|
|
23173
23783
|
}
|
|
23174
23784
|
function stepsWithoutAction2(contract) {
|
|
23175
|
-
const elements =
|
|
23785
|
+
const elements = isPlainObject11(contract?.elements) ? contract.elements : {};
|
|
23176
23786
|
const out = [];
|
|
23177
23787
|
for (const uid of Object.keys(elements).sort()) {
|
|
23178
23788
|
const element = elements[uid];
|
|
23179
23789
|
const steps = element?.type === "stepper" && Array.isArray(element.props?.props?.steps) ? element.props.props.steps : [];
|
|
23180
23790
|
steps.forEach((step, index) => {
|
|
23181
|
-
if (index === steps.length - 1 || !
|
|
23791
|
+
if (index === steps.length - 1 || !isPlainObject11(step) || named7(step.action)) return;
|
|
23182
23792
|
const id = idOf3(element, uid);
|
|
23183
|
-
const name =
|
|
23184
|
-
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";
|
|
23185
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.`;
|
|
23186
23796
|
const fix = `Give it the handler that moves it to "${next}" (action, action_label), or make it the last step.`;
|
|
23187
23797
|
out.push({ uid, id, page: element.page ?? null, step: name, index, problem: problem5, fix, message: `${problem5} ${fix}` });
|
|
@@ -23445,6 +24055,10 @@ function checkComponents(app, contract, contractError) {
|
|
|
23445
24055
|
}
|
|
23446
24056
|
};
|
|
23447
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
|
+
}
|
|
23448
24062
|
for (const uid of uids) {
|
|
23449
24063
|
const element = elements[uid];
|
|
23450
24064
|
if (element.type !== "custom" || typeof element.props?.module !== "string") continue;
|
|
@@ -23586,45 +24200,45 @@ function isReferenced4(name, pageText) {
|
|
|
23586
24200
|
}
|
|
23587
24201
|
|
|
23588
24202
|
// ../flows/appspace-builder/kit/contract/levers.mjs
|
|
23589
|
-
var
|
|
23590
|
-
var
|
|
24203
|
+
var isPlainObject12 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
24204
|
+
var named8 = (value) => typeof value === "string" && value !== "";
|
|
23591
24205
|
function both2(items) {
|
|
23592
24206
|
if (items.length <= 1) return items.join("");
|
|
23593
24207
|
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
23594
24208
|
}
|
|
23595
24209
|
function paramPages(contract) {
|
|
23596
24210
|
return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
|
|
23597
|
-
(page) => page?.owned !== true &&
|
|
24211
|
+
(page) => page?.owned !== true && named8(page?.path) && Array.isArray(page.params) && page.params.length > 0
|
|
23598
24212
|
);
|
|
23599
24213
|
}
|
|
23600
24214
|
function namesOf(page) {
|
|
23601
24215
|
const out = [];
|
|
23602
|
-
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);
|
|
23603
24217
|
return out;
|
|
23604
24218
|
}
|
|
23605
24219
|
function elementsOn(contract, path3) {
|
|
23606
|
-
const elements =
|
|
24220
|
+
const elements = isPlainObject12(contract?.elements) ? contract.elements : {};
|
|
23607
24221
|
return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
|
|
23608
24222
|
}
|
|
23609
24223
|
function paramModeOf(element) {
|
|
23610
|
-
const props =
|
|
23611
|
-
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;
|
|
23612
24226
|
if (element?.type === "field" || element?.type === "action") return null;
|
|
23613
|
-
if (
|
|
23614
|
-
const typed =
|
|
23615
|
-
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;
|
|
23616
24230
|
}
|
|
23617
24231
|
function paramsWrittenBy(element) {
|
|
23618
24232
|
const out = [];
|
|
23619
24233
|
const one = paramModeOf(element);
|
|
23620
24234
|
if (one !== null) out.push(one);
|
|
23621
24235
|
if (element?.type === "custom") return out;
|
|
23622
|
-
const typed =
|
|
23623
|
-
if (
|
|
24236
|
+
const typed = isPlainObject12(element?.props?.props) ? element.props.props : {};
|
|
24237
|
+
if (named8(typed.param_to)) out.push(typed.param_to);
|
|
23624
24238
|
for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
|
|
23625
|
-
if (!
|
|
23626
|
-
if (
|
|
23627
|
-
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);
|
|
23628
24242
|
}
|
|
23629
24243
|
return out;
|
|
23630
24244
|
}
|
|
@@ -23636,10 +24250,10 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23636
24250
|
for (const [, element] of elementsOn(contract, page.path)) {
|
|
23637
24251
|
for (const name of paramsWrittenBy(element)) written.add(name);
|
|
23638
24252
|
if (element?.type !== "custom") continue;
|
|
23639
|
-
const props =
|
|
23640
|
-
const produces =
|
|
23641
|
-
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (
|
|
23642
|
-
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;
|
|
23643
24257
|
const source = moduleSource(props.module);
|
|
23644
24258
|
if (typeof source !== "string") {
|
|
23645
24259
|
unknown = true;
|
|
@@ -23650,6 +24264,13 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23650
24264
|
else written.add(call.name);
|
|
23651
24265
|
}
|
|
23652
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
|
+
}
|
|
23653
24274
|
out.set(page.path, { written, unknown });
|
|
23654
24275
|
}
|
|
23655
24276
|
return out;
|
|
@@ -23663,7 +24284,7 @@ function unboundControls(contract, files = {}) {
|
|
|
23663
24284
|
for (const name of namesOf(page)) {
|
|
23664
24285
|
if (written.has(name)) continue;
|
|
23665
24286
|
const param = page.params.find((one) => one?.name === name) ?? {};
|
|
23666
|
-
const kind =
|
|
24287
|
+
const kind = named8(param.kind) ? param.kind : null;
|
|
23667
24288
|
const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
|
|
23668
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}.`;
|
|
23669
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.`;
|
|
@@ -23678,16 +24299,16 @@ function honoursOf(sources, name) {
|
|
|
23678
24299
|
const queue = [name];
|
|
23679
24300
|
while (queue.length > 0) {
|
|
23680
24301
|
const one = queue.shift();
|
|
23681
|
-
if (seen.has(one) || !
|
|
24302
|
+
if (seen.has(one) || !isPlainObject12(sources[one])) continue;
|
|
23682
24303
|
seen.add(one);
|
|
23683
|
-
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);
|
|
23684
24305
|
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
|
|
23685
24306
|
}
|
|
23686
24307
|
return out;
|
|
23687
24308
|
}
|
|
23688
24309
|
function unreadParams(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
23689
|
-
const sources =
|
|
23690
|
-
const handlers =
|
|
24310
|
+
const sources = isPlainObject12(contract?.data?.sources) ? contract.data.sources : {};
|
|
24311
|
+
const handlers = isPlainObject12(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
23691
24312
|
const out = [];
|
|
23692
24313
|
for (const page of paramPages(contract)) {
|
|
23693
24314
|
const read = /* @__PURE__ */ new Set();
|
|
@@ -23696,15 +24317,15 @@ function unreadParams(contract, { moduleSource = () => void 0, handlerSource = (
|
|
|
23696
24317
|
const custom = [];
|
|
23697
24318
|
let unknown = false;
|
|
23698
24319
|
for (const [, element] of elementsOn(contract, page.path)) {
|
|
23699
|
-
const props =
|
|
23700
|
-
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);
|
|
23701
24322
|
for (const one of reads) {
|
|
23702
24323
|
if (Object.prototype.hasOwnProperty.call(sources, one)) {
|
|
23703
24324
|
if (!named_.includes(one)) named_.push(one);
|
|
23704
24325
|
for (const honored of honoursOf(sources, one)) read.add(honored);
|
|
23705
24326
|
} else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
|
|
23706
24327
|
}
|
|
23707
|
-
if (element?.type === "custom" &&
|
|
24328
|
+
if (element?.type === "custom" && named8(props.module)) {
|
|
23708
24329
|
const text3 = moduleSource(props.module);
|
|
23709
24330
|
if (typeof text3 === "string") modules.push(text3);
|
|
23710
24331
|
else unknown = true;
|
|
@@ -23746,28 +24367,28 @@ function checkLevers(app, contract, contractError) {
|
|
|
23746
24367
|
}
|
|
23747
24368
|
|
|
23748
24369
|
// ../flows/appspace-builder/kit/contract/look.mjs
|
|
23749
|
-
var
|
|
23750
|
-
var
|
|
23751
|
-
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) {
|
|
23752
24373
|
const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
|
|
23753
24374
|
return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
|
|
23754
24375
|
}
|
|
23755
|
-
var
|
|
23756
|
-
custom: { noun: "card", fallback: (p) => [
|
|
23757
|
-
chart: { noun: "card", fallback: (p) => [
|
|
23758
|
-
widget: { noun: "card", fallback: (p) => [
|
|
23759
|
-
stat: { noun: "tile", fallback: (p) => [
|
|
23760
|
-
list: { noun: "card", fallback: (p) => [
|
|
23761
|
-
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"] },
|
|
23762
24383
|
form: { noun: "card", fallback: () => ["New", "the runtime\u2019s default"] },
|
|
23763
24384
|
dialog: { noun: "dialog" }
|
|
23764
24385
|
};
|
|
23765
|
-
var
|
|
23766
|
-
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] }])
|
|
23767
24388
|
);
|
|
23768
|
-
var
|
|
23769
|
-
function
|
|
23770
|
-
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 : {};
|
|
23771
24392
|
if (Array.isArray(page?.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid] !== void 0);
|
|
23772
24393
|
const contained = /* @__PURE__ */ new Set();
|
|
23773
24394
|
for (const element of Object.values(elements)) for (const child of element?.children ?? []) contained.add(child);
|
|
@@ -23776,8 +24397,8 @@ function rootsOf2(contract, page) {
|
|
|
23776
24397
|
return element?.page === page?.path && (element.parent === null || element.parent === void 0) && !contained.has(uid);
|
|
23777
24398
|
});
|
|
23778
24399
|
}
|
|
23779
|
-
function
|
|
23780
|
-
const elements =
|
|
24400
|
+
function untitledCards2(contract) {
|
|
24401
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
23781
24402
|
const out = [];
|
|
23782
24403
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23783
24404
|
if (page?.owned === true) continue;
|
|
@@ -23788,7 +24409,7 @@ function untitledCards(contract) {
|
|
|
23788
24409
|
const element = elements[uid];
|
|
23789
24410
|
if (element === void 0 || seen.has(uid)) continue;
|
|
23790
24411
|
seen.add(uid);
|
|
23791
|
-
if (
|
|
24412
|
+
if (LAYOUT2.has(element.type ?? "")) {
|
|
23792
24413
|
const children = element.children?.length ? element.children : Object.keys(elements).filter((other) => elements[other]?.parent === uid);
|
|
23793
24414
|
walk4(children);
|
|
23794
24415
|
} else {
|
|
@@ -23796,19 +24417,19 @@ function untitledCards(contract) {
|
|
|
23796
24417
|
}
|
|
23797
24418
|
}
|
|
23798
24419
|
};
|
|
23799
|
-
walk4(
|
|
24420
|
+
walk4(rootsOf3(contract, page));
|
|
23800
24421
|
for (const uid of drawn2) {
|
|
23801
24422
|
const element = elements[uid];
|
|
23802
24423
|
const type = element.type ?? "";
|
|
23803
|
-
const titled =
|
|
24424
|
+
const titled = TITLED2[type];
|
|
23804
24425
|
if (titled === void 0) continue;
|
|
23805
|
-
const props =
|
|
23806
|
-
const typed =
|
|
23807
|
-
const card =
|
|
23808
|
-
if (
|
|
23809
|
-
if (type === "dialog" ?
|
|
23810
|
-
const id =
|
|
23811
|
-
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);
|
|
23812
24433
|
const what = id !== null ? `${type} "${id}"` : `the ${type} ${uid}`;
|
|
23813
24434
|
const shown2 = title === "" ? "has no title at all" : `is titled "${title}" after ${from}`;
|
|
23814
24435
|
out.push({ page: page.path, uid, type, id, noun: titled.noun, title, from, message: `${what} has no label, so its ${titled.noun} ${shown2}` });
|
|
@@ -23816,49 +24437,49 @@ function untitledCards(contract) {
|
|
|
23816
24437
|
}
|
|
23817
24438
|
return out;
|
|
23818
24439
|
}
|
|
23819
|
-
var
|
|
23820
|
-
function
|
|
23821
|
-
const elements =
|
|
24440
|
+
var BAND_LOOK5 = ["subtitle", "layout", "surface", "aside"];
|
|
24441
|
+
function lookPages2(contract) {
|
|
24442
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
23822
24443
|
const out = /* @__PURE__ */ new Set();
|
|
23823
24444
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23824
|
-
if (page?.owned === true || !
|
|
23825
|
-
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) =>
|
|
23826
|
-
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)));
|
|
23827
24448
|
if (bands || cards) out.add(page.path);
|
|
23828
24449
|
}
|
|
23829
24450
|
return out;
|
|
23830
24451
|
}
|
|
23831
|
-
var
|
|
23832
|
-
var
|
|
23833
|
-
function
|
|
23834
|
-
const elements =
|
|
23835
|
-
const roots =
|
|
23836
|
-
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) }));
|
|
23837
24458
|
}
|
|
23838
|
-
function
|
|
23839
|
-
const id =
|
|
24459
|
+
function nameOf4(uid, element) {
|
|
24460
|
+
const id = named9(element?.props?.id) ? element.props.id : named9(element?.id) ? element.id : null;
|
|
23840
24461
|
return id === null ? `the ${element?.type ?? "element"} ${uid}` : `${element.type} "${id}"`;
|
|
23841
24462
|
}
|
|
23842
|
-
function
|
|
23843
|
-
const props =
|
|
23844
|
-
if (
|
|
23845
|
-
if (element?.type !== "custom" && element?.type !== "action" &&
|
|
23846
|
-
if (
|
|
23847
|
-
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)) {
|
|
23848
24469
|
const source = moduleSource(props.module);
|
|
23849
24470
|
return typeof source === "string" && scanUiSetParams2(source).length > 0;
|
|
23850
24471
|
}
|
|
23851
24472
|
return false;
|
|
23852
24473
|
}
|
|
23853
|
-
function
|
|
23854
|
-
const elements =
|
|
23855
|
-
const looked =
|
|
24474
|
+
function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
|
|
24475
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
24476
|
+
const looked = lookPages2(contract);
|
|
23856
24477
|
const out = [];
|
|
23857
24478
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
23858
24479
|
if (!looked.has(page?.path)) continue;
|
|
23859
|
-
for (const { section, roots } of
|
|
24480
|
+
for (const { section, roots } of bandsOf3(contract, page)) {
|
|
23860
24481
|
const band = `band "${section.id}" on ${page.path}`;
|
|
23861
|
-
const layout =
|
|
24482
|
+
const layout = named9(section.layout) ? section.layout : void 0;
|
|
23862
24483
|
if (layout === "kpi_strip") {
|
|
23863
24484
|
for (const uid of roots) {
|
|
23864
24485
|
const element = elements[uid];
|
|
@@ -23868,30 +24489,30 @@ function layoutProblems(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23868
24489
|
page: page.path,
|
|
23869
24490
|
section: section.id,
|
|
23870
24491
|
uid,
|
|
23871
|
-
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)`
|
|
23872
24493
|
});
|
|
23873
24494
|
}
|
|
23874
24495
|
}
|
|
23875
24496
|
if (layout === "sidebar" && roots.length > 0) {
|
|
23876
24497
|
const uid = section.aside === "start" ? roots[0] : roots[roots.length - 1];
|
|
23877
|
-
if (!
|
|
24498
|
+
if (!isControl2(elements[uid], moduleSource)) {
|
|
23878
24499
|
out.push({
|
|
23879
24500
|
rule: "L2",
|
|
23880
24501
|
page: page.path,
|
|
23881
24502
|
section: section.id,
|
|
23882
24503
|
uid,
|
|
23883
|
-
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`
|
|
23884
24505
|
});
|
|
23885
24506
|
}
|
|
23886
24507
|
}
|
|
23887
|
-
if (
|
|
24508
|
+
if (SPAN_LAYOUTS2.has(layout)) {
|
|
23888
24509
|
let row = [];
|
|
23889
24510
|
let used = 0;
|
|
23890
24511
|
for (const uid of roots) {
|
|
23891
24512
|
const raw = elements[uid]?.grid?.span;
|
|
23892
24513
|
const span = raw === null || raw === void 0 ? 12 : Number(raw);
|
|
23893
24514
|
if (!Number.isInteger(span) || span < 1 || span > 12) {
|
|
23894
|
-
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` });
|
|
23895
24516
|
continue;
|
|
23896
24517
|
}
|
|
23897
24518
|
if (used + span > 12) {
|
|
@@ -23902,7 +24523,7 @@ function layoutProblems(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23902
24523
|
page: page.path,
|
|
23903
24524
|
section: section.id,
|
|
23904
24525
|
uid,
|
|
23905
|
-
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`
|
|
23906
24527
|
});
|
|
23907
24528
|
}
|
|
23908
24529
|
row = [];
|
|
@@ -23920,6 +24541,137 @@ function layoutProblems(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
23920
24541
|
}
|
|
23921
24542
|
return out;
|
|
23922
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
|
+
}
|
|
23923
24675
|
|
|
23924
24676
|
// ../flows/appspace-builder/kit/contract/platformPalette.mjs
|
|
23925
24677
|
var PLATFORM_CHART2 = Object.freeze([
|
|
@@ -24141,7 +24893,7 @@ function rgbText2(parts) {
|
|
|
24141
24893
|
|
|
24142
24894
|
// ../flows/appspace-builder/kit/checks/rules/look.mjs
|
|
24143
24895
|
var THEME_FILE8 = "ui/theme.css";
|
|
24144
|
-
var RULES3 = ["L1", "L2", "L3", "L4"];
|
|
24896
|
+
var RULES3 = ["L1", "L2", "L3", "L4", "L5", "L6", "L7"];
|
|
24145
24897
|
function checkLook(app, contract, contractError) {
|
|
24146
24898
|
const view = {
|
|
24147
24899
|
...app,
|
|
@@ -24153,14 +24905,23 @@ function checkLook(app, contract, contractError) {
|
|
|
24153
24905
|
return { violations, skipped: RULES3.map((rule) => ({ rule, reason })) };
|
|
24154
24906
|
}
|
|
24155
24907
|
const moduleSource = (module) => app.ui.find((file) => file.path === `ui/${module}`)?.text;
|
|
24156
|
-
for (const found of
|
|
24908
|
+
for (const found of layoutProblems2(contract, { moduleSource })) {
|
|
24157
24909
|
violations.push({ rule: found.rule, where: found.uid ?? found.page, message: found.message });
|
|
24158
24910
|
}
|
|
24159
|
-
const looked =
|
|
24160
|
-
for (const found of
|
|
24911
|
+
const looked = lookPages2(contract);
|
|
24912
|
+
for (const found of untitledCards2(contract)) {
|
|
24161
24913
|
if (!looked.has(found.page)) continue;
|
|
24162
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}` });
|
|
24163
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
|
+
}
|
|
24164
24925
|
return { violations, skipped: [] };
|
|
24165
24926
|
}
|
|
24166
24927
|
|
|
@@ -24321,7 +25082,7 @@ function rolesOf5(manifest) {
|
|
|
24321
25082
|
// ../flows/appspace-builder/kit/checks/rules/ux.mjs
|
|
24322
25083
|
var RULES4 = ["A3.23", "A3.24", "A3.25", "A3.26", "A3.27"];
|
|
24323
25084
|
var STATE_COLUMNS = ["status", "state"];
|
|
24324
|
-
var
|
|
25085
|
+
var isPlainObject14 = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
24325
25086
|
var said = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
24326
25087
|
var OPENERS4 = /* @__PURE__ */ new Set(["dialog", "list", "queue", "detail"]);
|
|
24327
25088
|
var ROWS = /* @__PURE__ */ new Set(["list", "queue", "data_grid"]);
|
|
@@ -24330,9 +25091,9 @@ function checkUx(app, contract, contractError) {
|
|
|
24330
25091
|
const reason = `the contract could not be extracted (${contractError ?? "no reason given"}), so the forms, lists and dialogs are not knowable here`;
|
|
24331
25092
|
return { violations: [], skipped: RULES4.map((rule) => ({ rule, reason })) };
|
|
24332
25093
|
}
|
|
24333
|
-
const elements =
|
|
24334
|
-
const handlers =
|
|
24335
|
-
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 : {};
|
|
24336
25097
|
const uids = Object.keys(elements).sort(byPageThenRank);
|
|
24337
25098
|
const kidsOf = (uid) => {
|
|
24338
25099
|
const el = elements[uid];
|
|
@@ -24370,15 +25131,15 @@ function checkUx(app, contract, contractError) {
|
|
|
24370
25131
|
if (!ROWS.has(el?.type)) continue;
|
|
24371
25132
|
if (ancestors(uid).some((up) => up.type === "detail")) continue;
|
|
24372
25133
|
const table = tableOf2(el);
|
|
24373
|
-
if (!table || table.startsWith("_") || !
|
|
25134
|
+
if (!table || table.startsWith("_") || !isPlainObject14(tables[table]?.columns) || tables[table].singleton === true) continue;
|
|
24374
25135
|
const columns = tables[table].columns;
|
|
24375
|
-
const pk = Object.keys(columns).find((c) =>
|
|
25136
|
+
const pk = Object.keys(columns).find((c) => isPlainObject14(columns[c]) && columns[c].pk === true) ?? "id";
|
|
24376
25137
|
const fields = Object.keys(columns).filter((c) => c !== pk && !c.startsWith("_"));
|
|
24377
25138
|
let shown2;
|
|
24378
25139
|
let opens3;
|
|
24379
25140
|
if (el.type === "data_grid") {
|
|
24380
|
-
const typed =
|
|
24381
|
-
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));
|
|
24382
25143
|
opens3 = Boolean(said(typed.on_row)) || Array.isArray(typed.row_actions) && typed.row_actions.length > 0;
|
|
24383
25144
|
} else {
|
|
24384
25145
|
const kids = kidsOf(uid).map((k) => elements[k]);
|
|
@@ -24398,7 +25159,7 @@ function checkUx(app, contract, contractError) {
|
|
|
24398
25159
|
for (const uid of uids) {
|
|
24399
25160
|
const el = elements[uid];
|
|
24400
25161
|
if (el?.type !== "dialog") continue;
|
|
24401
|
-
const typed =
|
|
25162
|
+
const typed = isPlainObject14(el.props?.props) ? el.props.props : {};
|
|
24402
25163
|
if (typed.values_from === "row") continue;
|
|
24403
25164
|
if (said(typed.trigger) || said(el.props?.label) || said(typed.title)) continue;
|
|
24404
25165
|
violations.push({
|
|
@@ -24411,8 +25172,8 @@ function checkUx(app, contract, contractError) {
|
|
|
24411
25172
|
const el = elements[uid];
|
|
24412
25173
|
if (!ROWS.has(el?.type) || el.type === "data_grid") continue;
|
|
24413
25174
|
const table = tableOf2(el);
|
|
24414
|
-
const columns =
|
|
24415
|
-
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));
|
|
24416
25177
|
const seen = /* @__PURE__ */ new Map();
|
|
24417
25178
|
for (const kid of kidsOf(uid)) {
|
|
24418
25179
|
const action = elements[kid];
|
|
@@ -24453,8 +25214,8 @@ function split(uid) {
|
|
|
24453
25214
|
}
|
|
24454
25215
|
|
|
24455
25216
|
// ../flows/appspace-builder/kit/contract/workflow.mjs
|
|
24456
|
-
var
|
|
24457
|
-
var
|
|
25217
|
+
var isPlainObject15 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25218
|
+
var named10 = (value) => typeof value === "string" && value !== "";
|
|
24458
25219
|
function either5(items) {
|
|
24459
25220
|
if (items.length <= 1) return items.join("");
|
|
24460
25221
|
return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
|
|
@@ -24470,16 +25231,16 @@ function roleModel2(contract) {
|
|
|
24470
25231
|
const sees = /* @__PURE__ */ new Map();
|
|
24471
25232
|
const home = /* @__PURE__ */ new Map();
|
|
24472
25233
|
for (const row of rows2) {
|
|
24473
|
-
if (!
|
|
25234
|
+
if (!isPlainObject15(row) || !named10(row.name) || names.includes(row.name)) continue;
|
|
24474
25235
|
names.push(row.name);
|
|
24475
|
-
sees.set(row.name, new Set((Array.isArray(row.sees) ? row.sees : []).filter(
|
|
24476
|
-
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);
|
|
24477
25238
|
}
|
|
24478
25239
|
return { names, sees, home };
|
|
24479
25240
|
}
|
|
24480
25241
|
function gateRoles2(gate) {
|
|
24481
|
-
if (Array.isArray(gate)) return gate.filter(
|
|
24482
|
-
return
|
|
25242
|
+
if (Array.isArray(gate)) return gate.filter(named10);
|
|
25243
|
+
return named10(gate) ? gate.split(/[\s,]+/).filter(named10) : [];
|
|
24483
25244
|
}
|
|
24484
25245
|
function seesGate2(model, role, gate) {
|
|
24485
25246
|
const wanted = gateRoles2(gate);
|
|
@@ -24497,7 +25258,7 @@ var HREF2 = /\bhref\s*=\s*["']([^"']*)["']/i;
|
|
|
24497
25258
|
var ROLE_ATTR4 = /\bdata-dr-role\s*=\s*["']([^"']*)["']/i;
|
|
24498
25259
|
var PAGE_LITERAL2 = /["'`](?:\.\/)?([A-Za-z0-9_-]+\.html)(?:[?#][^"'`]*)?["'`]/g;
|
|
24499
25260
|
function targetOf2(href) {
|
|
24500
|
-
if (!
|
|
25261
|
+
if (!named10(href) || /^(?:[a-z]+:)?\/\//i.test(href) || href.startsWith("/") || href.startsWith("#")) return null;
|
|
24501
25262
|
const file = href.replace(/^\.\//, "").replace(/[?#].*$/, "");
|
|
24502
25263
|
if (!/^[A-Za-z0-9_-]+\.html$/.test(file)) return null;
|
|
24503
25264
|
return `ui/${file}`;
|
|
@@ -24535,7 +25296,7 @@ function moduleLinks2(source) {
|
|
|
24535
25296
|
return out;
|
|
24536
25297
|
}
|
|
24537
25298
|
function pagesOf2(contract) {
|
|
24538
|
-
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) =>
|
|
25299
|
+
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named10(page?.path));
|
|
24539
25300
|
}
|
|
24540
25301
|
function entryPage2(contract) {
|
|
24541
25302
|
const pages = pagesOf2(contract);
|
|
@@ -24554,10 +25315,10 @@ function reachByRole2(contract, { pageSource = () => void 0, moduleSource = () =
|
|
|
24554
25315
|
const model = roleModel2(contract);
|
|
24555
25316
|
const pages = pagesOf2(contract);
|
|
24556
25317
|
const byPath = new Map(pages.map((page) => [page.path, page]));
|
|
24557
|
-
const elements =
|
|
25318
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
24558
25319
|
const modulesOn = /* @__PURE__ */ new Map();
|
|
24559
25320
|
for (const element of Object.values(elements)) {
|
|
24560
|
-
if (element?.type !== "custom" || !
|
|
25321
|
+
if (element?.type !== "custom" || !named10(element.page) || !named10(element.props?.module)) continue;
|
|
24561
25322
|
if (!modulesOn.has(element.page)) modulesOn.set(element.page, /* @__PURE__ */ new Set());
|
|
24562
25323
|
modulesOn.get(element.page).add(element.props.module);
|
|
24563
25324
|
}
|
|
@@ -24682,23 +25443,23 @@ function moduleAsks2(source) {
|
|
|
24682
25443
|
return ASKS2.some((pattern) => pattern.test(String(source ?? "")));
|
|
24683
25444
|
}
|
|
24684
25445
|
function decideAsks2(row) {
|
|
24685
|
-
if (row?.role !== "decide" || !
|
|
25446
|
+
if (row?.role !== "decide" || !isPlainObject15(row.decision)) return false;
|
|
24686
25447
|
const names = Object.keys(row.decision);
|
|
24687
25448
|
if (names.length !== 1) return false;
|
|
24688
25449
|
const spec = row.decision[names[0]];
|
|
24689
|
-
return
|
|
25450
|
+
return isPlainObject15(spec) && (spec.note === "required" || spec.note === "optional");
|
|
24690
25451
|
}
|
|
24691
25452
|
function insideDialog2(elements, uid) {
|
|
24692
25453
|
const seen = /* @__PURE__ */ new Set();
|
|
24693
|
-
for (let at = elements[uid]?.parent;
|
|
25454
|
+
for (let at = elements[uid]?.parent; named10(at) && !seen.has(at); at = elements[at]?.parent) {
|
|
24694
25455
|
seen.add(at);
|
|
24695
25456
|
if (elements[at]?.type === "dialog") return true;
|
|
24696
25457
|
}
|
|
24697
25458
|
return false;
|
|
24698
25459
|
}
|
|
24699
25460
|
function stateCallers2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
24700
|
-
const elements =
|
|
24701
|
-
const handlers =
|
|
25461
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
25462
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24702
25463
|
const changing = new Set(Object.keys(handlers).filter((name) => changesState2(name, handlers[name], handlerSource(name))));
|
|
24703
25464
|
const out = /* @__PURE__ */ new Map();
|
|
24704
25465
|
const add = (handler, caller) => {
|
|
@@ -24708,10 +25469,10 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
24708
25469
|
};
|
|
24709
25470
|
for (const uid of Object.keys(elements).sort()) {
|
|
24710
25471
|
const element = elements[uid];
|
|
24711
|
-
const props =
|
|
24712
|
-
const typed =
|
|
25472
|
+
const props = isPlainObject15(element?.props) ? element.props : {};
|
|
25473
|
+
const typed = isPlainObject15(props.props) ? props.props : {};
|
|
24713
25474
|
const page = element?.page ?? null;
|
|
24714
|
-
const id =
|
|
25475
|
+
const id = named10(props.id) ? props.id : uid;
|
|
24715
25476
|
switch (element?.type) {
|
|
24716
25477
|
case "form":
|
|
24717
25478
|
add(props.action, { page, uid, id, what: "form", asks: true });
|
|
@@ -24722,27 +25483,27 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
24722
25483
|
case "data_grid":
|
|
24723
25484
|
add(typed.on_row, { page, uid, id, what: "grid row click", asks: false });
|
|
24724
25485
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
24725
|
-
if (!
|
|
24726
|
-
const asks = action.confirm === true ||
|
|
25486
|
+
if (!isPlainObject15(action) || !named10(action.call)) continue;
|
|
25487
|
+
const asks = action.confirm === true || named10(action.confirm);
|
|
24727
25488
|
add(action.call, { page, uid, id, what: `row action "${action.label ?? action.call}"`, asks });
|
|
24728
25489
|
}
|
|
24729
25490
|
break;
|
|
24730
25491
|
case "action":
|
|
24731
25492
|
case "detail": {
|
|
24732
|
-
if (!
|
|
24733
|
-
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);
|
|
24734
25495
|
add(props.action, { page, uid, id, what: element.type === "action" ? "button" : "detail button", asks });
|
|
24735
25496
|
break;
|
|
24736
25497
|
}
|
|
24737
25498
|
case "custom": {
|
|
24738
|
-
const declares =
|
|
25499
|
+
const declares = isPlainObject15(props.declares) ? props.declares : {};
|
|
24739
25500
|
const called = /* @__PURE__ */ new Set([
|
|
24740
|
-
...Array.isArray(declares.calls) ? declares.calls.filter(
|
|
24741
|
-
...Object.keys(
|
|
25501
|
+
...Array.isArray(declares.calls) ? declares.calls.filter(named10) : [],
|
|
25502
|
+
...Object.keys(isPlainObject15(declares.produces) ? declares.produces : {})
|
|
24742
25503
|
]);
|
|
24743
|
-
const source =
|
|
25504
|
+
const source = named10(props.module) ? moduleSource(props.module) : void 0;
|
|
24744
25505
|
if (typeof source === "string") {
|
|
24745
|
-
for (const call of scanUiCalls2(source)) if (
|
|
25506
|
+
for (const call of scanUiCalls2(source)) if (named10(call.handler)) called.add(call.handler);
|
|
24746
25507
|
}
|
|
24747
25508
|
const asks = typeof source === "string" ? moduleAsks2(source) : null;
|
|
24748
25509
|
for (const handler of called) add(handler, { page, uid, id, what: `custom ${props.module ?? ""}`.trim(), asks, module: props.module });
|
|
@@ -24755,7 +25516,7 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
24755
25516
|
return out;
|
|
24756
25517
|
}
|
|
24757
25518
|
function unconfirmedStateChanges2(contract, files = {}) {
|
|
24758
|
-
const handlers =
|
|
25519
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24759
25520
|
const out = [];
|
|
24760
25521
|
for (const [handler, callers] of [...stateCallers2(contract, files).entries()].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
|
|
24761
25522
|
const bare = callers.filter((one) => one.asks === false);
|
|
@@ -24827,7 +25588,7 @@ function requiresReason2(lines, key, vars) {
|
|
|
24827
25588
|
return false;
|
|
24828
25589
|
}
|
|
24829
25590
|
function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
|
|
24830
|
-
const handlers =
|
|
25591
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24831
25592
|
const producers = payloadProducers2(contract, { moduleSource, pageSource });
|
|
24832
25593
|
const out = [];
|
|
24833
25594
|
for (const name of Object.keys(handlers).sort()) {
|
|
@@ -24843,7 +25604,7 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
|
|
|
24843
25604
|
for (const key of one.keys) {
|
|
24844
25605
|
if (!REASON_KEYS2.includes(key)) continue;
|
|
24845
25606
|
if (!sent.has(key)) sent.set(key, /* @__PURE__ */ new Set());
|
|
24846
|
-
if (
|
|
25607
|
+
if (named10(one.page)) sent.get(key).add(one.page);
|
|
24847
25608
|
}
|
|
24848
25609
|
}
|
|
24849
25610
|
for (const [key, pages] of [...sent.entries()].sort()) {
|
|
@@ -24866,7 +25627,7 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
|
|
|
24866
25627
|
return out;
|
|
24867
25628
|
}
|
|
24868
25629
|
function unliftableRefusals2(contract, { handlerSource = () => void 0 } = {}) {
|
|
24869
|
-
const handlers =
|
|
25630
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24870
25631
|
const out = [];
|
|
24871
25632
|
for (const name of Object.keys(handlers).sort()) {
|
|
24872
25633
|
if ((handlers[name]?.role ?? "custom") !== "custom") continue;
|
|
@@ -24924,13 +25685,13 @@ function rolesLandingNowhere2(contract) {
|
|
|
24924
25685
|
function sourceRoles2(contract, name) {
|
|
24925
25686
|
const source = contract?.data?.sources?.[name];
|
|
24926
25687
|
const stated2 = gateRoles2(source?.requiresRole);
|
|
24927
|
-
const elements =
|
|
25688
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
24928
25689
|
const pages = new Map(pagesOf2(contract).map((page) => [page.path, page]));
|
|
24929
25690
|
const union = [];
|
|
24930
25691
|
let readers = 0;
|
|
24931
25692
|
let open2 = false;
|
|
24932
25693
|
for (const element of Object.values(elements)) {
|
|
24933
|
-
const props =
|
|
25694
|
+
const props = isPlainObject15(element?.props) ? element.props : {};
|
|
24934
25695
|
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []];
|
|
24935
25696
|
if (!reads.includes(name)) continue;
|
|
24936
25697
|
readers += 1;
|
|
@@ -24965,14 +25726,14 @@ function openToEveryRole2(contract) {
|
|
|
24965
25726
|
{ one: "page", many: "pages", verb: "open" },
|
|
24966
25727
|
"State pages[].roles on each: the roles it is for (all of them, if that is what you mean)."
|
|
24967
25728
|
);
|
|
24968
|
-
const handlers =
|
|
25729
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
24969
25730
|
say(
|
|
24970
25731
|
"handler",
|
|
24971
25732
|
Object.keys(handlers).sort().filter((name) => gateRoles2(handlers[name]?.requiresRole).length === 0).map((name) => `"${name}"`),
|
|
24972
25733
|
{ one: "handler", many: "handlers", verb: "call" },
|
|
24973
25734
|
"State requiresRole on each: the roles that may call it (all of them, if that is what you mean)."
|
|
24974
25735
|
);
|
|
24975
|
-
const sources =
|
|
25736
|
+
const sources = isPlainObject15(contract?.data?.sources) ? contract.data.sources : {};
|
|
24976
25737
|
say(
|
|
24977
25738
|
"source",
|
|
24978
25739
|
Object.keys(sources).sort().filter((name) => {
|
|
@@ -25019,7 +25780,7 @@ function kitFromDisk2() {
|
|
|
25019
25780
|
}
|
|
25020
25781
|
return kitOnDisk2;
|
|
25021
25782
|
}
|
|
25022
|
-
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"]);
|
|
25023
25784
|
function verify2({ app: appRef }, { extractContract: extractContract2 = extract2, kit } = {}) {
|
|
25024
25785
|
const KIT = kit ?? kitFromDisk2();
|
|
25025
25786
|
const app = loadApp2(resolve12(appRef.dir));
|
|
@@ -27092,18 +27853,18 @@ async function resolveFile(path3, base, { read = readFile11, confine = false, re
|
|
|
27092
27853
|
const filename = basename2(full);
|
|
27093
27854
|
return { bytes, filename, mimeType: mimeTypeOf(filename) };
|
|
27094
27855
|
}
|
|
27095
|
-
function
|
|
27856
|
+
function isPlainObject16(value) {
|
|
27096
27857
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27097
27858
|
}
|
|
27098
27859
|
function fileColumnsOf(model) {
|
|
27099
27860
|
const out = /* @__PURE__ */ new Set();
|
|
27100
|
-
const tables =
|
|
27101
|
-
if (!
|
|
27861
|
+
const tables = isPlainObject16(model) ? model["tables"] : void 0;
|
|
27862
|
+
if (!isPlainObject16(tables)) return out;
|
|
27102
27863
|
for (const table of Object.values(tables)) {
|
|
27103
|
-
const columns =
|
|
27104
|
-
if (!
|
|
27864
|
+
const columns = isPlainObject16(table) ? table["columns"] : void 0;
|
|
27865
|
+
if (!isPlainObject16(columns)) continue;
|
|
27105
27866
|
for (const [name, column] of Object.entries(columns)) {
|
|
27106
|
-
const type = typeof column === "string" ? column :
|
|
27867
|
+
const type = typeof column === "string" ? column : isPlainObject16(column) ? column["type"] : void 0;
|
|
27107
27868
|
if (type === "file") out.add(name);
|
|
27108
27869
|
}
|
|
27109
27870
|
}
|
|
@@ -27167,7 +27928,7 @@ function typeOf(value) {
|
|
|
27167
27928
|
if (Array.isArray(value)) return "array";
|
|
27168
27929
|
return typeof value;
|
|
27169
27930
|
}
|
|
27170
|
-
function
|
|
27931
|
+
function isPlainObject17(value) {
|
|
27171
27932
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27172
27933
|
}
|
|
27173
27934
|
function describe6(value) {
|
|
@@ -27194,7 +27955,7 @@ function validateShape(shape, path3 = "$") {
|
|
|
27194
27955
|
}
|
|
27195
27956
|
return shape.length === 0 ? [] : validateShape(shape[0], `${path3}[]`);
|
|
27196
27957
|
}
|
|
27197
|
-
if (
|
|
27958
|
+
if (isPlainObject17(shape)) {
|
|
27198
27959
|
return Object.entries(shape).flatMap(
|
|
27199
27960
|
([key, sub]) => validateShape(sub, `${path3}.${key}`)
|
|
27200
27961
|
);
|
|
@@ -27223,8 +27984,8 @@ function matchShape(value, shape, path3 = "$") {
|
|
|
27223
27984
|
if (shape.length === 0) return [];
|
|
27224
27985
|
return value.flatMap((item, i) => matchShape(item, shape[0], `${path3}[${String(i)}]`));
|
|
27225
27986
|
}
|
|
27226
|
-
if (
|
|
27227
|
-
if (!
|
|
27987
|
+
if (isPlainObject17(shape)) {
|
|
27988
|
+
if (!isPlainObject17(value)) {
|
|
27228
27989
|
return [`${path3}: expected object, got ${typeOf(value)} (${describe6(value)}).`];
|
|
27229
27990
|
}
|
|
27230
27991
|
return Object.entries(shape).flatMap(
|
|
@@ -27234,8 +27995,8 @@ function matchShape(value, shape, path3 = "$") {
|
|
|
27234
27995
|
return [`${path3}: ${describe6(shape)} is not a shape.`];
|
|
27235
27996
|
}
|
|
27236
27997
|
function matchExpect(value, expected, path3 = "$") {
|
|
27237
|
-
if (
|
|
27238
|
-
if (!
|
|
27998
|
+
if (isPlainObject17(expected)) {
|
|
27999
|
+
if (!isPlainObject17(value)) {
|
|
27239
28000
|
return [`${path3}: expected an object, got ${typeOf(value)} (${describe6(value)}).`];
|
|
27240
28001
|
}
|
|
27241
28002
|
return Object.entries(expected).flatMap(
|
|
@@ -27266,7 +28027,7 @@ function isUiEntry(entry) {
|
|
|
27266
28027
|
}
|
|
27267
28028
|
function parseError(raw, where2) {
|
|
27268
28029
|
if (raw === true) return {};
|
|
27269
|
-
if (!
|
|
28030
|
+
if (!isPlainObject17(raw)) {
|
|
27270
28031
|
throw new FixtureError(
|
|
27271
28032
|
`${where2}: "error" is true, or an object with "type" and/or "contains" \u2014 not ${describe6(raw)}.`
|
|
27272
28033
|
);
|
|
@@ -27284,12 +28045,12 @@ function parseError(raw, where2) {
|
|
|
27284
28045
|
}
|
|
27285
28046
|
var FILE_FIELD = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
27286
28047
|
function parseFiles(raw, payload, where2) {
|
|
27287
|
-
if (!
|
|
28048
|
+
if (!isPlainObject17(raw)) {
|
|
27288
28049
|
throw new FixtureError(
|
|
27289
28050
|
`${where2}: "files" maps a payload key to "sample" or a path in the app folder \u2014 not ${describe6(raw)}.`
|
|
27290
28051
|
);
|
|
27291
28052
|
}
|
|
27292
|
-
if (!
|
|
28053
|
+
if (!isPlainObject17(payload)) {
|
|
27293
28054
|
throw new FixtureError(
|
|
27294
28055
|
`${where2}: a case with "files" puts {"upload": \u2026} under a payload key, so its "payload" is an object.`
|
|
27295
28056
|
);
|
|
@@ -27315,7 +28076,7 @@ function parseFiles(raw, payload, where2) {
|
|
|
27315
28076
|
}
|
|
27316
28077
|
function parseCase(handler, raw, index) {
|
|
27317
28078
|
const where2 = `handlers.${handler}[${String(index)}]`;
|
|
27318
|
-
if (!
|
|
28079
|
+
if (!isPlainObject17(raw)) {
|
|
27319
28080
|
throw new FixtureError(`${where2}: a case is an object, not ${describe6(raw)}.`);
|
|
27320
28081
|
}
|
|
27321
28082
|
const name = raw["name"];
|
|
@@ -27348,11 +28109,11 @@ function parseCase(handler, raw, index) {
|
|
|
27348
28109
|
return kase;
|
|
27349
28110
|
}
|
|
27350
28111
|
function parseFixtures(raw, source) {
|
|
27351
|
-
if (!
|
|
28112
|
+
if (!isPlainObject17(raw)) {
|
|
27352
28113
|
throw new FixtureError(`${source}: the fixtures file is a JSON object, not ${describe6(raw)}.`);
|
|
27353
28114
|
}
|
|
27354
28115
|
const handlersRaw = raw["handlers"] ?? {};
|
|
27355
|
-
if (!
|
|
28116
|
+
if (!isPlainObject17(handlersRaw)) {
|
|
27356
28117
|
throw new FixtureError(`${source}: "handlers" is an object keyed by handler name.`);
|
|
27357
28118
|
}
|
|
27358
28119
|
const cases = [];
|
|
@@ -27372,7 +28133,7 @@ function parseFixtures(raw, source) {
|
|
|
27372
28133
|
});
|
|
27373
28134
|
}
|
|
27374
28135
|
const uiRaw = raw["ui"] ?? {};
|
|
27375
|
-
if (!
|
|
28136
|
+
if (!isPlainObject17(uiRaw)) {
|
|
27376
28137
|
throw new FixtureError(`${source}: "ui" is an object.`);
|
|
27377
28138
|
}
|
|
27378
28139
|
const entry = uiRaw["entry"] ?? "index.html";
|
|
@@ -29446,45 +30207,45 @@ __export(levers_exports, {
|
|
|
29446
30207
|
unboundControls: () => unboundControls2,
|
|
29447
30208
|
unreadParams: () => unreadParams2
|
|
29448
30209
|
});
|
|
29449
|
-
var
|
|
29450
|
-
var
|
|
30210
|
+
var isPlainObject18 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30211
|
+
var named11 = (value) => typeof value === "string" && value !== "";
|
|
29451
30212
|
function both4(items) {
|
|
29452
30213
|
if (items.length <= 1) return items.join("");
|
|
29453
30214
|
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
29454
30215
|
}
|
|
29455
30216
|
function paramPages2(contract) {
|
|
29456
30217
|
return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
|
|
29457
|
-
(page) => page?.owned !== true &&
|
|
30218
|
+
(page) => page?.owned !== true && named11(page?.path) && Array.isArray(page.params) && page.params.length > 0
|
|
29458
30219
|
);
|
|
29459
30220
|
}
|
|
29460
30221
|
function namesOf2(page) {
|
|
29461
30222
|
const out = [];
|
|
29462
|
-
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);
|
|
29463
30224
|
return out;
|
|
29464
30225
|
}
|
|
29465
30226
|
function elementsOn2(contract, path3) {
|
|
29466
|
-
const elements =
|
|
30227
|
+
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29467
30228
|
return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
|
|
29468
30229
|
}
|
|
29469
30230
|
function paramModeOf2(element) {
|
|
29470
|
-
const props =
|
|
29471
|
-
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;
|
|
29472
30233
|
if (element?.type === "field" || element?.type === "action") return null;
|
|
29473
|
-
if (
|
|
29474
|
-
const typed =
|
|
29475
|
-
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;
|
|
29476
30237
|
}
|
|
29477
30238
|
function paramsWrittenBy2(element) {
|
|
29478
30239
|
const out = [];
|
|
29479
30240
|
const one = paramModeOf2(element);
|
|
29480
30241
|
if (one !== null) out.push(one);
|
|
29481
30242
|
if (element?.type === "custom") return out;
|
|
29482
|
-
const typed =
|
|
29483
|
-
if (
|
|
30243
|
+
const typed = isPlainObject18(element?.props?.props) ? element.props.props : {};
|
|
30244
|
+
if (named11(typed.param_to)) out.push(typed.param_to);
|
|
29484
30245
|
for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
|
|
29485
|
-
if (!
|
|
29486
|
-
if (
|
|
29487
|
-
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);
|
|
29488
30249
|
}
|
|
29489
30250
|
return out;
|
|
29490
30251
|
}
|
|
@@ -29496,10 +30257,10 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
29496
30257
|
for (const [, element] of elementsOn2(contract, page.path)) {
|
|
29497
30258
|
for (const name of paramsWrittenBy2(element)) written.add(name);
|
|
29498
30259
|
if (element?.type !== "custom") continue;
|
|
29499
|
-
const props =
|
|
29500
|
-
const produces =
|
|
29501
|
-
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (
|
|
29502
|
-
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;
|
|
29503
30264
|
const source = moduleSource(props.module);
|
|
29504
30265
|
if (typeof source !== "string") {
|
|
29505
30266
|
unknown = true;
|
|
@@ -29510,6 +30271,13 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
29510
30271
|
else written.add(call.name);
|
|
29511
30272
|
}
|
|
29512
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
|
+
}
|
|
29513
30281
|
out.set(page.path, { written, unknown });
|
|
29514
30282
|
}
|
|
29515
30283
|
return out;
|
|
@@ -29523,7 +30291,7 @@ function unboundControls2(contract, files = {}) {
|
|
|
29523
30291
|
for (const name of namesOf2(page)) {
|
|
29524
30292
|
if (written.has(name)) continue;
|
|
29525
30293
|
const param = page.params.find((one) => one?.name === name) ?? {};
|
|
29526
|
-
const kind =
|
|
30294
|
+
const kind = named11(param.kind) ? param.kind : null;
|
|
29527
30295
|
const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
|
|
29528
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}.`;
|
|
29529
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.`;
|
|
@@ -29538,16 +30306,16 @@ function honoursOf2(sources, name) {
|
|
|
29538
30306
|
const queue = [name];
|
|
29539
30307
|
while (queue.length > 0) {
|
|
29540
30308
|
const one = queue.shift();
|
|
29541
|
-
if (seen.has(one) || !
|
|
30309
|
+
if (seen.has(one) || !isPlainObject18(sources[one])) continue;
|
|
29542
30310
|
seen.add(one);
|
|
29543
|
-
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);
|
|
29544
30312
|
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
|
|
29545
30313
|
}
|
|
29546
30314
|
return out;
|
|
29547
30315
|
}
|
|
29548
30316
|
function unreadParams2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
29549
|
-
const sources =
|
|
29550
|
-
const handlers =
|
|
30317
|
+
const sources = isPlainObject18(contract?.data?.sources) ? contract.data.sources : {};
|
|
30318
|
+
const handlers = isPlainObject18(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
29551
30319
|
const out = [];
|
|
29552
30320
|
for (const page of paramPages2(contract)) {
|
|
29553
30321
|
const read = /* @__PURE__ */ new Set();
|
|
@@ -29556,15 +30324,15 @@ function unreadParams2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
29556
30324
|
const custom = [];
|
|
29557
30325
|
let unknown = false;
|
|
29558
30326
|
for (const [, element] of elementsOn2(contract, page.path)) {
|
|
29559
|
-
const props =
|
|
29560
|
-
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);
|
|
29561
30329
|
for (const one of reads) {
|
|
29562
30330
|
if (Object.prototype.hasOwnProperty.call(sources, one)) {
|
|
29563
30331
|
if (!named_.includes(one)) named_.push(one);
|
|
29564
30332
|
for (const honored of honoursOf2(sources, one)) read.add(honored);
|
|
29565
30333
|
} else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
|
|
29566
30334
|
}
|
|
29567
|
-
if (element?.type === "custom" &&
|
|
30335
|
+
if (element?.type === "custom" && named11(props.module)) {
|
|
29568
30336
|
const text3 = moduleSource(props.module);
|
|
29569
30337
|
if (typeof text3 === "string") modules.push(text3);
|
|
29570
30338
|
else unknown = true;
|
|
@@ -29589,190 +30357,6 @@ function unreadParams2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
29589
30357
|
return out;
|
|
29590
30358
|
}
|
|
29591
30359
|
|
|
29592
|
-
// ../flows/insights-builder/kit/contract/look.mjs
|
|
29593
|
-
var look_exports = {};
|
|
29594
|
-
__export(look_exports, {
|
|
29595
|
-
humanise: () => humanise3,
|
|
29596
|
-
layoutProblems: () => layoutProblems2,
|
|
29597
|
-
lookPages: () => lookPages2,
|
|
29598
|
-
rootsOf: () => rootsOf3,
|
|
29599
|
-
untitledCards: () => untitledCards2
|
|
29600
|
-
});
|
|
29601
|
-
var isPlainObject18 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29602
|
-
var named11 = (value) => typeof value === "string" && value !== "";
|
|
29603
|
-
function humanise3(name) {
|
|
29604
|
-
const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
|
|
29605
|
-
return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
|
|
29606
|
-
}
|
|
29607
|
-
var WORDING2 = {
|
|
29608
|
-
custom: { noun: "card", fallback: (p) => [humanise3(String(p.module ?? "").replace(/^.*\/|\.js$/g, "")), "its module"] },
|
|
29609
|
-
chart: { noun: "card", fallback: (p) => [humanise3(String(p.y ?? "")), "its y column"] },
|
|
29610
|
-
widget: { noun: "card", fallback: (p) => [humanise3(named11(p.source) ? p.source : "widget"), "its source"] },
|
|
29611
|
-
stat: { noun: "tile", fallback: (p) => [humanise3(String(p.measure ?? "")), "its measure"] },
|
|
29612
|
-
list: { noun: "card", fallback: (p) => [humanise3(named11(p.entity) ? p.entity : "list"), "its entity"] },
|
|
29613
|
-
queue: { noun: "card", fallback: (p) => [humanise3(named11(p.entity) ? p.entity : "queue"), "its entity"] },
|
|
29614
|
-
form: { noun: "card", fallback: () => ["New", "the runtime\u2019s default"] },
|
|
29615
|
-
dialog: { noun: "dialog" }
|
|
29616
|
-
};
|
|
29617
|
-
var TITLED2 = Object.fromEntries(
|
|
29618
|
-
TITLED_TYPES.map((type) => [type, { noun: CONTROL_PARTS.includes(type) ? "input" : "card", fallback: () => [humanise3(type), "its type"], ...WORDING2[type] }])
|
|
29619
|
-
);
|
|
29620
|
-
var LAYOUT2 = /* @__PURE__ */ new Set(["region", "tabs", "tab_panel"]);
|
|
29621
|
-
function rootsOf3(contract, page) {
|
|
29622
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29623
|
-
if (Array.isArray(page?.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid] !== void 0);
|
|
29624
|
-
const contained = /* @__PURE__ */ new Set();
|
|
29625
|
-
for (const element of Object.values(elements)) for (const child of element?.children ?? []) contained.add(child);
|
|
29626
|
-
return Object.keys(elements).filter((uid) => {
|
|
29627
|
-
const element = elements[uid];
|
|
29628
|
-
return element?.page === page?.path && (element.parent === null || element.parent === void 0) && !contained.has(uid);
|
|
29629
|
-
});
|
|
29630
|
-
}
|
|
29631
|
-
function untitledCards2(contract) {
|
|
29632
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29633
|
-
const out = [];
|
|
29634
|
-
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
29635
|
-
if (page?.owned === true) continue;
|
|
29636
|
-
const seen = /* @__PURE__ */ new Set();
|
|
29637
|
-
const drawn2 = [];
|
|
29638
|
-
const walk4 = (uids) => {
|
|
29639
|
-
for (const uid of uids) {
|
|
29640
|
-
const element = elements[uid];
|
|
29641
|
-
if (element === void 0 || seen.has(uid)) continue;
|
|
29642
|
-
seen.add(uid);
|
|
29643
|
-
if (LAYOUT2.has(element.type ?? "")) {
|
|
29644
|
-
const children = element.children?.length ? element.children : Object.keys(elements).filter((other) => elements[other]?.parent === uid);
|
|
29645
|
-
walk4(children);
|
|
29646
|
-
} else {
|
|
29647
|
-
drawn2.push(uid);
|
|
29648
|
-
}
|
|
29649
|
-
}
|
|
29650
|
-
};
|
|
29651
|
-
walk4(rootsOf3(contract, page));
|
|
29652
|
-
for (const uid of drawn2) {
|
|
29653
|
-
const element = elements[uid];
|
|
29654
|
-
const type = element.type ?? "";
|
|
29655
|
-
const titled = TITLED2[type];
|
|
29656
|
-
if (titled === void 0) continue;
|
|
29657
|
-
const props = isPlainObject18(element.props) ? element.props : {};
|
|
29658
|
-
const typed = isPlainObject18(props.props) ? props.props : {};
|
|
29659
|
-
const card = isPlainObject18(element.card) ? element.card : isPlainObject18(props.card) ? props.card : {};
|
|
29660
|
-
if (named11(props.label) || named11(card.title)) continue;
|
|
29661
|
-
if (type === "dialog" ? named11(typed.title) : named11(typed.label)) continue;
|
|
29662
|
-
const id = named11(element.id) ? element.id : named11(props.id) ? props.id : null;
|
|
29663
|
-
const [title, from] = named11(props.field) ? [humanise3(props.field), "its field"] : id !== null ? [humanise3(id), "its id"] : titled.fallback(props);
|
|
29664
|
-
const what = id !== null ? `${type} "${id}"` : `the ${type} ${uid}`;
|
|
29665
|
-
const shown2 = title === "" ? "has no title at all" : `is titled "${title}" after ${from}`;
|
|
29666
|
-
out.push({ page: page.path, uid, type, id, noun: titled.noun, title, from, message: `${what} has no label, so its ${titled.noun} ${shown2}` });
|
|
29667
|
-
}
|
|
29668
|
-
}
|
|
29669
|
-
return out;
|
|
29670
|
-
}
|
|
29671
|
-
var BAND_LOOK5 = ["subtitle", "layout", "surface", "aside"];
|
|
29672
|
-
function lookPages2(contract) {
|
|
29673
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29674
|
-
const out = /* @__PURE__ */ new Set();
|
|
29675
|
-
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
29676
|
-
if (page?.owned === true || !named11(page?.path)) continue;
|
|
29677
|
-
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK5.some((key) => named11(section?.[key])));
|
|
29678
|
-
const cards = Object.values(elements).some((element) => element?.page === page.path && (isPlainObject18(element.card) || named11(element.emphasis)));
|
|
29679
|
-
if (bands || cards) out.add(page.path);
|
|
29680
|
-
}
|
|
29681
|
-
return out;
|
|
29682
|
-
}
|
|
29683
|
-
var SPAN_LAYOUTS2 = /* @__PURE__ */ new Set([void 0, "grid"]);
|
|
29684
|
-
var CONTROL_TYPES2 = /* @__PURE__ */ new Set(["filter", "control", ...CONTROL_PARTS]);
|
|
29685
|
-
function bandsOf3(contract, page) {
|
|
29686
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29687
|
-
const roots = rootsOf3(contract, page);
|
|
29688
|
-
return (Array.isArray(page.sections) ? page.sections : []).filter((section) => named11(section?.id)).map((section) => ({ section, roots: roots.filter((uid) => elements[uid]?.section === section.id) }));
|
|
29689
|
-
}
|
|
29690
|
-
function nameOf4(uid, element) {
|
|
29691
|
-
const id = named11(element?.props?.id) ? element.props.id : named11(element?.id) ? element.id : null;
|
|
29692
|
-
return id === null ? `the ${element?.type ?? "element"} ${uid}` : `${element.type} "${id}"`;
|
|
29693
|
-
}
|
|
29694
|
-
function isControl2(element, moduleSource) {
|
|
29695
|
-
const props = isPlainObject18(element?.props) ? element.props : {};
|
|
29696
|
-
if (CONTROL_TYPES2.has(element?.type)) return true;
|
|
29697
|
-
if (element?.type !== "custom" && element?.type !== "action" && named11(props.param)) return true;
|
|
29698
|
-
if (isPlainObject18(props.props) && named11(props.props.param)) return true;
|
|
29699
|
-
if (element?.type === "custom" && named11(props.module)) {
|
|
29700
|
-
const source = moduleSource(props.module);
|
|
29701
|
-
return typeof source === "string" && scanUiSetParams(source).length > 0;
|
|
29702
|
-
}
|
|
29703
|
-
return false;
|
|
29704
|
-
}
|
|
29705
|
-
function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
|
|
29706
|
-
const elements = isPlainObject18(contract?.elements) ? contract.elements : {};
|
|
29707
|
-
const looked = lookPages2(contract);
|
|
29708
|
-
const out = [];
|
|
29709
|
-
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
29710
|
-
if (!looked.has(page?.path)) continue;
|
|
29711
|
-
for (const { section, roots } of bandsOf3(contract, page)) {
|
|
29712
|
-
const band = `band "${section.id}" on ${page.path}`;
|
|
29713
|
-
const layout = named11(section.layout) ? section.layout : void 0;
|
|
29714
|
-
if (layout === "kpi_strip") {
|
|
29715
|
-
for (const uid of roots) {
|
|
29716
|
-
const element = elements[uid];
|
|
29717
|
-
if (element.type === "stat" || element.type === "widget" && element.props?.kind === "kpi") continue;
|
|
29718
|
-
out.push({
|
|
29719
|
-
rule: "L1",
|
|
29720
|
-
page: page.path,
|
|
29721
|
-
section: section.id,
|
|
29722
|
-
uid,
|
|
29723
|
-
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)`
|
|
29724
|
-
});
|
|
29725
|
-
}
|
|
29726
|
-
}
|
|
29727
|
-
if (layout === "sidebar" && roots.length > 0) {
|
|
29728
|
-
const uid = section.aside === "start" ? roots[0] : roots[roots.length - 1];
|
|
29729
|
-
if (!isControl2(elements[uid], moduleSource)) {
|
|
29730
|
-
out.push({
|
|
29731
|
-
rule: "L2",
|
|
29732
|
-
page: page.path,
|
|
29733
|
-
section: section.id,
|
|
29734
|
-
uid,
|
|
29735
|
-
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`
|
|
29736
|
-
});
|
|
29737
|
-
}
|
|
29738
|
-
}
|
|
29739
|
-
if (SPAN_LAYOUTS2.has(layout)) {
|
|
29740
|
-
let row = [];
|
|
29741
|
-
let used = 0;
|
|
29742
|
-
for (const uid of roots) {
|
|
29743
|
-
const raw = elements[uid]?.grid?.span;
|
|
29744
|
-
const span = raw === null || raw === void 0 ? 12 : Number(raw);
|
|
29745
|
-
if (!Number.isInteger(span) || span < 1 || span > 12) {
|
|
29746
|
-
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` });
|
|
29747
|
-
continue;
|
|
29748
|
-
}
|
|
29749
|
-
if (used + span > 12) {
|
|
29750
|
-
if (used < 12) {
|
|
29751
|
-
const spans = [...row.map((one) => one.span), span];
|
|
29752
|
-
out.push({
|
|
29753
|
-
rule: "L4",
|
|
29754
|
-
page: page.path,
|
|
29755
|
-
section: section.id,
|
|
29756
|
-
uid,
|
|
29757
|
-
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`
|
|
29758
|
-
});
|
|
29759
|
-
}
|
|
29760
|
-
row = [];
|
|
29761
|
-
used = 0;
|
|
29762
|
-
}
|
|
29763
|
-
row.push({ uid, span });
|
|
29764
|
-
used += span;
|
|
29765
|
-
if (used === 12) {
|
|
29766
|
-
row = [];
|
|
29767
|
-
used = 0;
|
|
29768
|
-
}
|
|
29769
|
-
}
|
|
29770
|
-
}
|
|
29771
|
-
}
|
|
29772
|
-
}
|
|
29773
|
-
return out;
|
|
29774
|
-
}
|
|
29775
|
-
|
|
29776
30360
|
// src/commands/appspace/_findings.ts
|
|
29777
30361
|
var DECLARES = declares_exports;
|
|
29778
30362
|
var LEVERS = levers_exports;
|