@graview/core 0.1.53 → 0.1.55
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/alias-findings.d.ts +40 -0
- package/dist/alias-findings.d.ts.map +1 -0
- package/dist/alias-findings.js +95 -0
- package/dist/alias-findings.js.map +1 -0
- package/dist/app.d.ts +13 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js.map +1 -1
- package/dist/archived-seen.d.ts +51 -15
- package/dist/archived-seen.d.ts.map +1 -1
- package/dist/archived-seen.js +123 -28
- package/dist/archived-seen.js.map +1 -1
- package/dist/capabilities.js +1 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/catalog.d.ts +32 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +17 -1
- package/dist/catalog.js.map +1 -1
- package/dist/check.d.ts +2 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +2 -0
- package/dist/check.js.map +1 -1
- package/dist/cli/check/aliases.d.ts +11 -0
- package/dist/cli/check/aliases.d.ts.map +1 -0
- package/dist/cli/check/aliases.js +17 -0
- package/dist/cli/check/aliases.js.map +1 -0
- package/dist/cli/check/context.d.ts +2 -0
- package/dist/cli/check/context.d.ts.map +1 -1
- package/dist/cli/check/context.js.map +1 -1
- package/dist/cli/check/index.d.ts +2 -0
- package/dist/cli/check/index.d.ts.map +1 -1
- package/dist/cli/check/index.js +2 -0
- package/dist/cli/check/index.js.map +1 -1
- package/dist/cli/check/jobs.d.ts +12 -0
- package/dist/cli/check/jobs.d.ts.map +1 -0
- package/dist/cli/check/jobs.js +25 -0
- package/dist/cli/check/jobs.js.map +1 -0
- package/dist/cli/check.d.ts +13 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +25 -2
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/describe.d.ts.map +1 -1
- package/dist/cli/describe.js +41 -0
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/document-schema.d.ts.map +1 -1
- package/dist/cli/document-schema.js +7 -0
- package/dist/cli/document-schema.js.map +1 -1
- package/dist/compose.d.ts +99 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +245 -0
- package/dist/compose.js.map +1 -0
- package/dist/document/brand-check.d.ts +3 -0
- package/dist/document/brand-check.d.ts.map +1 -1
- package/dist/document/brand-check.js +21 -2
- package/dist/document/brand-check.js.map +1 -1
- package/dist/document/brand.d.ts.map +1 -1
- package/dist/document/brand.js +10 -2
- package/dist/document/brand.js.map +1 -1
- package/dist/document/compile-checked.d.ts.map +1 -1
- package/dist/document/compile-checked.js +13 -7
- package/dist/document/compile-checked.js.map +1 -1
- package/dist/document/compiled.d.ts.map +1 -1
- package/dist/document/compiled.js +6 -0
- package/dist/document/compiled.js.map +1 -1
- package/dist/document/design-findings.d.ts +32 -0
- package/dist/document/design-findings.d.ts.map +1 -0
- package/dist/document/design-findings.js +593 -0
- package/dist/document/design-findings.js.map +1 -0
- package/dist/document/diff.js +1 -1
- package/dist/document/diff.js.map +1 -1
- package/dist/document/edit.d.ts +1 -1
- package/dist/document/edit.d.ts.map +1 -1
- package/dist/document/edit.js +255 -4
- package/dist/document/edit.js.map +1 -1
- package/dist/document/findings.d.ts +8 -2
- package/dist/document/findings.d.ts.map +1 -1
- package/dist/document/findings.js +4 -3
- package/dist/document/findings.js.map +1 -1
- package/dist/document/graview-template.d.ts +3 -3
- package/dist/document/purpose-spec.d.ts +37 -0
- package/dist/document/purpose-spec.d.ts.map +1 -0
- package/dist/document/purpose-spec.js +41 -0
- package/dist/document/purpose-spec.js.map +1 -0
- package/dist/document/schema.d.ts +32 -11
- package/dist/document/schema.d.ts.map +1 -1
- package/dist/document/schema.js +30 -0
- package/dist/document/schema.js.map +1 -1
- package/dist/document/to-document.d.ts.map +1 -1
- package/dist/document/to-document.js +5 -1
- package/dist/document/to-document.js.map +1 -1
- package/dist/document.schema.json +142 -2
- package/dist/friction-record.d.ts +52 -0
- package/dist/friction-record.d.ts.map +1 -0
- package/dist/friction-record.js +36 -0
- package/dist/friction-record.js.map +1 -0
- package/dist/friction.d.ts +209 -0
- package/dist/friction.d.ts.map +1 -0
- package/dist/friction.js +302 -0
- package/dist/friction.js.map +1 -0
- package/dist/graph/graph.d.ts.map +1 -1
- package/dist/graph/graph.js +20 -0
- package/dist/graph/graph.js.map +1 -1
- package/dist/history.d.ts +32 -0
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/job-findings.d.ts +39 -0
- package/dist/job-findings.d.ts.map +1 -0
- package/dist/job-findings.js +171 -0
- package/dist/job-findings.js.map +1 -0
- package/dist/job-shape.d.ts +57 -0
- package/dist/job-shape.d.ts.map +1 -0
- package/dist/job-shape.js +5 -0
- package/dist/job-shape.js.map +1 -0
- package/dist/jobs.d.ts +62 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +112 -0
- package/dist/jobs.js.map +1 -0
- package/dist/learn.d.ts +151 -0
- package/dist/learn.d.ts.map +1 -0
- package/dist/learn.js +520 -0
- package/dist/learn.js.map +1 -0
- package/dist/past.d.ts +37 -2
- package/dist/past.d.ts.map +1 -1
- package/dist/past.js +73 -13
- package/dist/past.js.map +1 -1
- package/dist/schema/types.d.ts +9 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +2 -0
- package/dist/search.js.map +1 -1
- package/dist/seat.d.ts +3 -2
- package/dist/seat.d.ts.map +1 -1
- package/dist/seat.js +3 -2
- package/dist/seat.js.map +1 -1
- package/dist/subscriptions.d.ts +5 -0
- package/dist/subscriptions.d.ts.map +1 -1
- package/dist/subscriptions.js +46 -1
- package/dist/subscriptions.js.map +1 -1
- package/dist/theme/accent.d.ts +8 -6
- package/dist/theme/accent.d.ts.map +1 -1
- package/dist/theme/accent.js +29 -23
- package/dist/theme/accent.js.map +1 -1
- package/dist/theme/ground-check.d.ts +27 -0
- package/dist/theme/ground-check.d.ts.map +1 -0
- package/dist/theme/ground-check.js +81 -0
- package/dist/theme/ground-check.js.map +1 -0
- package/dist/theme/ground.d.ts +42 -0
- package/dist/theme/ground.d.ts.map +1 -0
- package/dist/theme/ground.js +101 -0
- package/dist/theme/ground.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -2
package/dist/document/edit.js
CHANGED
|
@@ -14,9 +14,13 @@ const refusedFormat = (format, type) => {
|
|
|
14
14
|
return `${sentence}; ${fix}`;
|
|
15
15
|
};
|
|
16
16
|
import { farEnd } from "./far-end.js";
|
|
17
|
+
import { jobFindings, jobWorldOfDocument } from "../job-findings.js";
|
|
18
|
+
import { JobSpec } from "./purpose-spec.js";
|
|
19
|
+
import { aliasWord, documentAliasFindings } from "../alias-findings.js";
|
|
20
|
+
import { withArticle } from "../schema/define-node.js";
|
|
17
21
|
import { linkedRelationFindings } from "./linked-relation-findings.js";
|
|
18
22
|
import { linkedRelationOfSpec, reachesAnotherApp } from "../schema/linked.js";
|
|
19
|
-
import { ActSpec, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
|
|
23
|
+
import { ActSpec, ALIAS, MOST_ALIASES, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
|
|
20
24
|
import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES, unknownLensOption, viewReference } from "../places.js";
|
|
21
25
|
import { LENSES_THAT_LIST, listsRecords } from "../lenses-that-list.js";
|
|
22
26
|
import { placeSlug, VIEW_REFERENCE } from "../views/types.js";
|
|
@@ -24,7 +28,7 @@ import { documentListFields, kindListFindings, kindListWords, listGroup, listSor
|
|
|
24
28
|
import { computedOf, validateComputed } from "./computed.js";
|
|
25
29
|
import { parseTemplate, templateBraces, TemplateError } from "./template-parse.js";
|
|
26
30
|
import { validateViews, VIEW_SLOTS } from "./views.js";
|
|
27
|
-
import { brandFindings, emojiIconSentence, subtitleSentence } from "./brand-check.js";
|
|
31
|
+
import { brandFindings, emojiIconSentence, groundWords, subtitleSentence } from "./brand-check.js";
|
|
28
32
|
import { darkLogoSentence, logoParts } from "./logo.js";
|
|
29
33
|
import { emojiOf } from "../theme/marks.js";
|
|
30
34
|
import { canonicalize } from "./canonical.js";
|
|
@@ -33,6 +37,7 @@ import { isRecord as isObject, respellDocument } from "./respell.js";
|
|
|
33
37
|
import { refName } from "./compiled.js";
|
|
34
38
|
import { localeSentence, timeZoneSentence } from "./diff.js";
|
|
35
39
|
import { accentProblem } from "../theme/accent.js";
|
|
40
|
+
import { groundProblem } from "../theme/ground-check.js";
|
|
36
41
|
import { pageFieldFindings, pageNames, renamedOnPage, withoutOnPage } from "./page-fields.js";
|
|
37
42
|
import { capitalize, listed as listOf } from "../capital.js";
|
|
38
43
|
import { quoted } from "./quote.js";
|
|
@@ -81,6 +86,12 @@ export const EDIT_OPS = [
|
|
|
81
86
|
"remove-grant",
|
|
82
87
|
"add-sight",
|
|
83
88
|
"remove-sight",
|
|
89
|
+
"set-purpose",
|
|
90
|
+
"add-job",
|
|
91
|
+
"edit-job",
|
|
92
|
+
"remove-job",
|
|
93
|
+
"add-alias",
|
|
94
|
+
"set-aliases",
|
|
84
95
|
];
|
|
85
96
|
/** Words the rule language keeps: a field or relation renamed to one could not be named in a rule. */
|
|
86
97
|
const KEYWORDS = new Set(["and", "or", "not", "in", "where", "true", "false", "null"]);
|
|
@@ -101,6 +112,7 @@ function buildShapes() {
|
|
|
101
112
|
const edgeName = z.string().regex(EDGE_NAME, 'relation names are one camelCase word, like "tendedBy"');
|
|
102
113
|
const actName = z.string().regex(NAME, 'act names are lower-case words joined by hyphens, like "mark-paid"');
|
|
103
114
|
const lensTitle = z.string().min(1).max(80);
|
|
115
|
+
const jobName = z.string().regex(NAME, 'job names are lower-case words joined by hyphens, like "unbooked-soon"');
|
|
104
116
|
return {
|
|
105
117
|
"add-kind": z.object({ op: z.literal("add-kind"), kind: kindName, act: z.boolean().optional() }).passthrough(),
|
|
106
118
|
"rename-kind": z.object({ op: z.literal("rename-kind"), kind: kindName, to: kindName, noun: z.string().min(1).max(40).optional(), plural: z.string().min(1).max(40).optional() }).strict(),
|
|
@@ -126,6 +138,8 @@ function buildShapes() {
|
|
|
126
138
|
.object({
|
|
127
139
|
op: z.literal("set-brand"),
|
|
128
140
|
accent: z.union([z.string(), z.null()]).optional(),
|
|
141
|
+
// The page background (Roadmap f-brand-ground): one color, or one for each scheme; null for Graview's.
|
|
142
|
+
ground: z.union([z.string(), z.object({ light: z.string().optional(), dark: z.string().optional() }).strict(), z.null()]).optional(),
|
|
129
143
|
name: z.union([z.string().min(1).max(60), z.null()]).optional(),
|
|
130
144
|
currency: z.union([z.string(), z.null()]).optional(),
|
|
131
145
|
locale: z.union([z.string(), z.null()]).optional(),
|
|
@@ -226,6 +240,28 @@ function buildShapes() {
|
|
|
226
240
|
"remove-grant": z.object({ op: z.literal("remove-grant") }).passthrough(),
|
|
227
241
|
"add-sight": z.object({ op: z.literal("add-sight") }).passthrough(),
|
|
228
242
|
"remove-sight": z.object({ op: z.literal("remove-sight") }).passthrough(),
|
|
243
|
+
// Who the app is for, in words (Roadmap f-app-jobs); null takes the sentence away.
|
|
244
|
+
"set-purpose": z.object({ op: z.literal("set-purpose"), for: z.union([z.string().min(1).max(300), z.null()]) }).strict(),
|
|
245
|
+
// A job as the document holds one (`purpose.jobs[]`), placed at `at` or last.
|
|
246
|
+
"add-job": z.object({ op: z.literal("add-job"), job: z.record(z.string(), z.unknown()), at: z.number().int().min(0).optional() }).strict(),
|
|
247
|
+
// A job by its name, each key given set, null clearing an optional one.
|
|
248
|
+
"edit-job": z
|
|
249
|
+
.object({
|
|
250
|
+
op: z.literal("edit-job"),
|
|
251
|
+
name: jobName,
|
|
252
|
+
ask: z.string().min(1).max(200).optional(),
|
|
253
|
+
kind: kindName.optional(),
|
|
254
|
+
where: z.union([z.string().min(1).max(2000), z.null()]).optional(),
|
|
255
|
+
sort: z.union([listKey, z.object({ by: listKey, direction: listDirection }).strict(), z.null()]).optional(),
|
|
256
|
+
act: z.union([actName, z.null()]).optional(),
|
|
257
|
+
roles: z.union([z.array(z.string().regex(NAME)).min(1).max(40), z.null()]).optional(),
|
|
258
|
+
})
|
|
259
|
+
.strict(),
|
|
260
|
+
"remove-job": z.object({ op: z.literal("remove-job"), name: jobName }).strict(),
|
|
261
|
+
// Another word people use for a kind, or for one of its fields (Roadmap f-learned-suggestions).
|
|
262
|
+
"add-alias": z.object({ op: z.literal("add-alias"), kind: kindName, field: fieldName.optional(), alias: z.string().min(1).max(40) }).strict(),
|
|
263
|
+
// All of a kind's or a field's other words at once; null or [] takes them away.
|
|
264
|
+
"set-aliases": z.object({ op: z.literal("set-aliases"), kind: kindName, field: fieldName.optional(), aliases: z.union([z.array(z.string().min(1).max(40)).max(MOST_ALIASES), z.null()]) }).strict(),
|
|
229
265
|
};
|
|
230
266
|
}
|
|
231
267
|
let shapes;
|
|
@@ -538,7 +574,8 @@ class Editor {
|
|
|
538
574
|
* set, null clears it, and what it does not name stays as it was. Each
|
|
539
575
|
* is judged as `graview check` judges it — a mark that could act or load,
|
|
540
576
|
* a face off the list — and an accent that does not read as given is
|
|
541
|
-
* refused with the pair, the ratio and a shade that would (FR-126)
|
|
577
|
+
* refused with the pair, the ratio and a shade that would (FR-126), as
|
|
578
|
+
* is a ground that ink, muted ink or the accent would not read on.
|
|
542
579
|
*/
|
|
543
580
|
setBrand(i, e) {
|
|
544
581
|
const brand = clone(this.doc.brand ?? {});
|
|
@@ -566,6 +603,24 @@ class Editor {
|
|
|
566
603
|
brand["accent"] = e.accent;
|
|
567
604
|
this.said.push(`The app's accent color becomes ${e.accent}.`);
|
|
568
605
|
}
|
|
606
|
+
if (e.ground === null) {
|
|
607
|
+
if (brand["ground"] !== undefined)
|
|
608
|
+
this.said.push("The app's page background goes back to Graview's.");
|
|
609
|
+
delete brand["ground"];
|
|
610
|
+
}
|
|
611
|
+
else if (e.ground !== undefined) {
|
|
612
|
+
const accent = brand["accent"];
|
|
613
|
+
const refused = groundProblem(e.ground, accent);
|
|
614
|
+
const fixed = (ground, also = "") => `{"op": "set-brand", "ground": ${JSON.stringify(ground).replace(/","/g, '", "').replace(/":"/g, '": "')}${also}}`;
|
|
615
|
+
if (refused)
|
|
616
|
+
return this.fail(i, "ground", refused.sentence, refused.suggestion ? fixed(typeof e.ground === "string" ? refused.suggestion : { ...e.ground, [refused.scheme]: refused.suggestion }) : "pick a lighter color for the light scheme, a darker one for the dark");
|
|
617
|
+
// The accent the app keeps is judged on the new ground as it was on the old (FR-126).
|
|
618
|
+
const unread = accent ? accentProblem(accent, e.ground) : null;
|
|
619
|
+
if (unread)
|
|
620
|
+
return this.fail(i, "ground", unread.sentence, unread.suggestion ? fixed(e.ground, `, "accent": "${unread.suggestion}"`) : "pick another ground, or another accent");
|
|
621
|
+
brand["ground"] = e.ground;
|
|
622
|
+
this.said.push(groundWords(e.ground));
|
|
623
|
+
}
|
|
569
624
|
if (e.currency === null) {
|
|
570
625
|
delete brand["currency"];
|
|
571
626
|
this.said.push("Money is said with no currency.");
|
|
@@ -924,7 +979,144 @@ class Editor {
|
|
|
924
979
|
case "add-sight":
|
|
925
980
|
case "remove-sight":
|
|
926
981
|
return this.policyEdit(i, e);
|
|
982
|
+
case "set-purpose":
|
|
983
|
+
return this.setPurpose(e);
|
|
984
|
+
case "add-job":
|
|
985
|
+
return this.addJob(i, e);
|
|
986
|
+
case "edit-job":
|
|
987
|
+
return this.editJob(i, e);
|
|
988
|
+
case "remove-job":
|
|
989
|
+
return this.removeJob(i, e.name);
|
|
990
|
+
case "add-alias":
|
|
991
|
+
return this.setAliases(i, e.kind, e.field, [...this.aliasesOf(e.kind, e.field), e.alias], e.alias);
|
|
992
|
+
case "set-aliases":
|
|
993
|
+
return this.setAliases(i, e.kind, e.field, e.aliases ?? []);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
// ── other words for a kind or a field (Roadmap f-learned-suggestions) ──
|
|
997
|
+
aliasesOf(kind, field) {
|
|
998
|
+
const spec = this.doc.kinds[kind];
|
|
999
|
+
return (field ? spec?.fields?.[field]?.aliases : spec?.aliases) ?? [];
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* A kind's or a field's aliases, as words people search with: lower case,
|
|
1003
|
+
* each once. Refused, in the checker's words, when one is not a word or is
|
|
1004
|
+
* what something else in the app is already called. `added` is the one
|
|
1005
|
+
* word `add-alias` gave, said alone.
|
|
1006
|
+
*/
|
|
1007
|
+
setAliases(i, kind, field, given, added) {
|
|
1008
|
+
const spec = this.kind(i, kind);
|
|
1009
|
+
if (!spec)
|
|
1010
|
+
return;
|
|
1011
|
+
const target = field ? this.field(i, kind, field) : spec;
|
|
1012
|
+
if (!target)
|
|
1013
|
+
return;
|
|
1014
|
+
const said = [...new Set(given.map(aliasWord))];
|
|
1015
|
+
const at = added === undefined ? "aliases" : "alias";
|
|
1016
|
+
for (const [n, word] of said.entries())
|
|
1017
|
+
if (!ALIAS.test(word))
|
|
1018
|
+
return this.fail(i, added === undefined ? `aliases.${n}` : at, `"${word}" is not a word people search with: one to three words of letters and digits`, 'like "supplier" or "wedding venue"');
|
|
1019
|
+
if (said.length > MOST_ALIASES)
|
|
1020
|
+
return this.fail(i, at, `at most ${MOST_ALIASES} other words for one ${field ? "field" : "kind"}`);
|
|
1021
|
+
const before = target.aliases;
|
|
1022
|
+
if (said.length === 0)
|
|
1023
|
+
delete target.aliases;
|
|
1024
|
+
else
|
|
1025
|
+
target.aliases = said;
|
|
1026
|
+
const wrong = documentAliasFindings(this.doc).filter((finding) => finding.path.startsWith(`kinds.${kind}${field ? `.fields.${field}` : ""}.aliases`));
|
|
1027
|
+
if (wrong.length > 0) {
|
|
1028
|
+
if (before === undefined)
|
|
1029
|
+
delete target.aliases;
|
|
1030
|
+
else
|
|
1031
|
+
target.aliases = before;
|
|
1032
|
+
return this.fail(i, at, wrong[0].message, wrong[0].fix);
|
|
927
1033
|
}
|
|
1034
|
+
const whose = field ? `${kind}'s ${field}` : withArticle(spec.noun ?? words(kind));
|
|
1035
|
+
if (added !== undefined)
|
|
1036
|
+
this.said.push(`Search and the seat take “${aliasWord(added)}” as another word for ${whose}.`);
|
|
1037
|
+
else
|
|
1038
|
+
this.said.push(said.length === 0 ? `${capitalize(whose)} has no other words now.` : `Other words for ${whose}: ${listOf(said.map((word) => `“${word}”`))}.`);
|
|
1039
|
+
}
|
|
1040
|
+
// ── who the app is for, and what they come to do (Roadmap f-app-jobs) ──
|
|
1041
|
+
setPurpose(e) {
|
|
1042
|
+
const purpose = { ...(this.doc.purpose ?? {}) };
|
|
1043
|
+
if (e.for === null)
|
|
1044
|
+
delete purpose.for;
|
|
1045
|
+
else
|
|
1046
|
+
purpose.for = e.for;
|
|
1047
|
+
this.keepPurpose(purpose);
|
|
1048
|
+
this.said.push(e.for === null ? "The app no longer says whom it is for." : `The app says it is for ${quotedAtTheEnd(e.for)}`);
|
|
1049
|
+
}
|
|
1050
|
+
/** The purpose as it now stands, or none when it says nothing. */
|
|
1051
|
+
keepPurpose(purpose) {
|
|
1052
|
+
if (Array.isArray(purpose.jobs) && purpose.jobs.length === 0)
|
|
1053
|
+
delete purpose.jobs;
|
|
1054
|
+
if (Object.keys(purpose).length === 0)
|
|
1055
|
+
delete this.doc.purpose;
|
|
1056
|
+
else
|
|
1057
|
+
this.doc.purpose = purpose;
|
|
1058
|
+
}
|
|
1059
|
+
/** The job a name names, or -1 having said why. */
|
|
1060
|
+
jobIndex(i, name) {
|
|
1061
|
+
const jobs = this.doc.purpose?.jobs ?? [];
|
|
1062
|
+
const index = jobs.findIndex((job) => job.name === name);
|
|
1063
|
+
if (index < 0)
|
|
1064
|
+
this.fail(i, "name", `there is no job "${name}"${jobs.length > 0 ? `; the jobs are ${jobs.map((job) => `"${job.name}"`).join(", ")}` : ""}`);
|
|
1065
|
+
return index;
|
|
1066
|
+
}
|
|
1067
|
+
/** A job placed among the others, judged as `graview check` judges it (`job-*`): kept only when nothing is wrong with it. */
|
|
1068
|
+
placeJob(i, job, at, replacing) {
|
|
1069
|
+
const jobs = [...(this.doc.purpose?.jobs ?? [])];
|
|
1070
|
+
if (replacing)
|
|
1071
|
+
jobs[at] = job;
|
|
1072
|
+
else
|
|
1073
|
+
jobs.splice(at, 0, job);
|
|
1074
|
+
const wrong = jobFindings({ ...this.doc.purpose, jobs }, jobWorldOfDocument(this.doc)).filter((finding) => finding.index === at || (finding.code === "jobs-too-many" && !replacing) || (finding.code === "job-name-taken" && jobs[finding.index ?? -1]?.name === job.name));
|
|
1075
|
+
for (const finding of wrong)
|
|
1076
|
+
this.failAs(finding.code, i, finding.code === "jobs-too-many" ? "" : `job.${finding.key ?? ""}`, finding.message, finding.fix);
|
|
1077
|
+
if (wrong.length > 0)
|
|
1078
|
+
return false;
|
|
1079
|
+
this.keepPurpose({ ...(this.doc.purpose ?? {}), jobs });
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
addJob(i, e) {
|
|
1083
|
+
const parsed = JobSpec.safeParse(e.job);
|
|
1084
|
+
if (!parsed.success)
|
|
1085
|
+
return this.refuse(i, parsed.error.issues.map((issue) => ({ ...issue, path: ["job", ...issue.path] })), "a job");
|
|
1086
|
+
const job = parsed.data;
|
|
1087
|
+
const jobs = this.doc.purpose?.jobs ?? [];
|
|
1088
|
+
if (!this.placeJob(i, job, Math.min(e.at ?? jobs.length, jobs.length), false))
|
|
1089
|
+
return;
|
|
1090
|
+
this.said.push(`A new job, ${quoted(job.ask)}: ${jobWords(this.doc, job)}.`);
|
|
1091
|
+
}
|
|
1092
|
+
editJob(i, e) {
|
|
1093
|
+
const index = this.jobIndex(i, e.name);
|
|
1094
|
+
if (index < 0)
|
|
1095
|
+
return;
|
|
1096
|
+
const job = { ...this.doc.purpose.jobs[index] };
|
|
1097
|
+
const changed = ["ask", "kind", "where", "sort", "act", "roles"].filter((key) => e[key] !== undefined);
|
|
1098
|
+
if (changed.length === 0)
|
|
1099
|
+
return this.fail(i, "", "an edit-job names at least one of ask, kind, where, sort, act or roles to change", `{"op": "edit-job", "name": "${e.name}", "ask": "…"}`);
|
|
1100
|
+
for (const key of changed) {
|
|
1101
|
+
if (e[key] === null) {
|
|
1102
|
+
if (key === "ask" || key === "kind")
|
|
1103
|
+
return this.fail(i, key, `a job always has its ${key}`);
|
|
1104
|
+
delete job[key];
|
|
1105
|
+
}
|
|
1106
|
+
else
|
|
1107
|
+
job[key] = e[key];
|
|
1108
|
+
}
|
|
1109
|
+
if (!this.placeJob(i, job, index, true))
|
|
1110
|
+
return;
|
|
1111
|
+
this.said.push(`The job "${job.name}", ${quoted(job.ask)}, is now ${jobWords(this.doc, job)}.`);
|
|
1112
|
+
}
|
|
1113
|
+
removeJob(i, name) {
|
|
1114
|
+
const index = this.jobIndex(i, name);
|
|
1115
|
+
if (index < 0)
|
|
1116
|
+
return;
|
|
1117
|
+
const [job] = this.doc.purpose.jobs.splice(index, 1);
|
|
1118
|
+
this.keepPurpose({ ...this.doc.purpose });
|
|
1119
|
+
this.said.push(`The job "${job.name}" (${quotedAtTheEnd(job.ask)}) is removed.`);
|
|
928
1120
|
}
|
|
929
1121
|
/** How a record's page orders and groups its facts (FR-148): judged as the compile judges `kinds.<kind>.page`. */
|
|
930
1122
|
setPageFields(i, e) {
|
|
@@ -2068,7 +2260,7 @@ class Editor {
|
|
|
2068
2260
|
gone.push(...this.dropComputed(other, name));
|
|
2069
2261
|
}
|
|
2070
2262
|
}
|
|
2071
|
-
gone.push(...this.pruneViews(swept), ...this.pruneLenses(swept));
|
|
2263
|
+
gone.push(...this.pruneViews(swept), ...this.pruneLenses(swept), ...this.pruneJobs(swept));
|
|
2072
2264
|
// The arrangement names kinds (FR-80): a kind that is gone is no longer ordered or hidden, nor where the app opens.
|
|
2073
2265
|
if (this.doc.pages) {
|
|
2074
2266
|
for (const part of ["order", "hide", "primary"]) {
|
|
@@ -2534,6 +2726,13 @@ class Editor {
|
|
|
2534
2726
|
else
|
|
2535
2727
|
delete rule.repairs;
|
|
2536
2728
|
}
|
|
2729
|
+
// A job asks the same question without it: only what people do next is let go.
|
|
2730
|
+
for (const job of (this.doc.purpose?.jobs ?? [])) {
|
|
2731
|
+
if (job.act !== act)
|
|
2732
|
+
continue;
|
|
2733
|
+
delete job.act;
|
|
2734
|
+
gone.push(`what the job "${job.name}" does next`);
|
|
2735
|
+
}
|
|
2537
2736
|
return gone;
|
|
2538
2737
|
}
|
|
2539
2738
|
addRule(i, e) {
|
|
@@ -2563,6 +2762,9 @@ class Editor {
|
|
|
2563
2762
|
for (const r of rule.repairs ?? [])
|
|
2564
2763
|
if (r.act === from)
|
|
2565
2764
|
r.act = to;
|
|
2765
|
+
for (const job of (this.doc.purpose?.jobs ?? []))
|
|
2766
|
+
if (job.act === from)
|
|
2767
|
+
job.act = to;
|
|
2566
2768
|
}
|
|
2567
2769
|
// ── every place a name is said ──
|
|
2568
2770
|
/**
|
|
@@ -2814,8 +3016,46 @@ class Editor {
|
|
|
2814
3016
|
if (JSON.stringify(doc.pages) !== before)
|
|
2815
3017
|
touched.push("the pages");
|
|
2816
3018
|
}
|
|
3019
|
+
// Jobs (Roadmap f-app-jobs): what each is answered by, what its where reads, what it sorts by and the act it names.
|
|
3020
|
+
for (const job of (doc.purpose?.jobs ?? [])) {
|
|
3021
|
+
const before = JSON.stringify(job);
|
|
3022
|
+
if (job.where !== undefined)
|
|
3023
|
+
job.where = exprAt(job.where, new Set([job.kind]));
|
|
3024
|
+
const by = typeof job.sort === "string" ? job.sort : job.sort?.by;
|
|
3025
|
+
if (r.t === "field" && job.kind === r.kind && by === r.from)
|
|
3026
|
+
job.sort = typeof job.sort === "string" ? r.to : { ...job.sort, by: r.to };
|
|
3027
|
+
if (r.t === "kind" && job.kind === r.from)
|
|
3028
|
+
job.kind = r.to;
|
|
3029
|
+
if (job.act !== undefined && r.t === "kind")
|
|
3030
|
+
job.act = renamedAct(job.act, r.from, r.to);
|
|
3031
|
+
if (job.act !== undefined && (r.t === "kind" || r.t === "edge"))
|
|
3032
|
+
job.act = renamedRelationAct(job.act, doc.kinds, r);
|
|
3033
|
+
if (JSON.stringify(job) !== before)
|
|
3034
|
+
touched.push(`the job "${job.name}"`);
|
|
3035
|
+
}
|
|
2817
3036
|
return touched;
|
|
2818
3037
|
}
|
|
3038
|
+
/** Jobs that can no longer be answered as asked go — a where that reads what is going, a kind that is gone — and an order by it is let go. */
|
|
3039
|
+
pruneJobs(r) {
|
|
3040
|
+
const purpose = this.doc.purpose;
|
|
3041
|
+
if (!purpose?.jobs)
|
|
3042
|
+
return [];
|
|
3043
|
+
const gone = [];
|
|
3044
|
+
purpose.jobs = purpose.jobs.filter((job) => {
|
|
3045
|
+
if ((r.t === "kind" && job.kind === r.from) || exprMentions(this.doc, job.where, new Set([job.kind]), r)) {
|
|
3046
|
+
gone.push(`the job "${job.name}"`);
|
|
3047
|
+
return false;
|
|
3048
|
+
}
|
|
3049
|
+
const by = typeof job.sort === "string" ? job.sort : job.sort?.by;
|
|
3050
|
+
if (r.t === "field" && job.kind === r.kind && by === r.from) {
|
|
3051
|
+
delete job.sort;
|
|
3052
|
+
gone.push(`the order of the job "${job.name}"`);
|
|
3053
|
+
}
|
|
3054
|
+
return true;
|
|
3055
|
+
});
|
|
3056
|
+
this.keepPurpose({ ...purpose });
|
|
3057
|
+
return gone;
|
|
3058
|
+
}
|
|
2819
3059
|
/**
|
|
2820
3060
|
* Remove what mentions a field or relation that is going: acts that write or walk
|
|
2821
3061
|
* it, rules that judge it, view blocks that show it; templates that show it fall
|
|
@@ -2912,6 +3152,7 @@ class Editor {
|
|
|
2912
3152
|
delete doc.rules;
|
|
2913
3153
|
gone.push(...this.pruneViews(r));
|
|
2914
3154
|
gone.push(...this.pruneLenses(r));
|
|
3155
|
+
gone.push(...this.pruneJobs(r));
|
|
2915
3156
|
return gone;
|
|
2916
3157
|
}
|
|
2917
3158
|
/** Computed fields being dropped, so a cycle among them ends. */
|
|
@@ -3116,6 +3357,16 @@ class Editor {
|
|
|
3116
3357
|
return said;
|
|
3117
3358
|
}
|
|
3118
3359
|
}
|
|
3360
|
+
/** How a job is answered, in words: "answered by the vendors where status != 'booked', sorted by due, then book". */
|
|
3361
|
+
function jobWords(doc, job) {
|
|
3362
|
+
const sort = typeof job.sort === "string" ? { by: job.sort, direction: "asc" } : job.sort;
|
|
3363
|
+
return [
|
|
3364
|
+
`answered by the ${pluralWords(doc, job.kind).toLowerCase()}${job.where ? ` where ${job.where}` : ""}`,
|
|
3365
|
+
...(sort ? [`sorted by ${sort.by}${sort.direction === "desc" ? ", highest first" : ""}`] : []),
|
|
3366
|
+
...(job.act ? [`then "${job.act}"`] : []),
|
|
3367
|
+
...(job.roles ? [`for ${listOf(job.roles)}`] : []),
|
|
3368
|
+
].join(", ");
|
|
3369
|
+
}
|
|
3119
3370
|
/** What the people and the policy make together, as `graview check` judges it (`checkPeople`), said over the document. */
|
|
3120
3371
|
function peopleProblems(doc, at) {
|
|
3121
3372
|
const out = [];
|