@graview/core 0.1.62 → 0.1.64
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/app.d.ts +12 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js.map +1 -1
- package/dist/arranging.js +2 -2
- package/dist/arranging.js.map +1 -1
- package/dist/cli/check/index.d.ts +1 -0
- package/dist/cli/check/index.d.ts.map +1 -1
- package/dist/cli/check/index.js +1 -0
- package/dist/cli/check/index.js.map +1 -1
- package/dist/cli/check/workflows.d.ts +11 -0
- package/dist/cli/check/workflows.d.ts.map +1 -0
- package/dist/cli/check/workflows.js +20 -0
- package/dist/cli/check/workflows.js.map +1 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +2 -1
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/describe.d.ts.map +1 -1
- package/dist/cli/describe.js +31 -1
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/document-schema.d.ts.map +1 -1
- package/dist/cli/document-schema.js +9 -0
- package/dist/cli/document-schema.js.map +1 -1
- package/dist/compose.d.ts +1 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/document/amount.d.ts +1 -1
- package/dist/document/amount.d.ts.map +1 -1
- package/dist/document/amount.js +2 -2
- package/dist/document/amount.js.map +1 -1
- package/dist/document/blocks.d.ts.map +1 -1
- package/dist/document/blocks.js +17 -1
- package/dist/document/blocks.js.map +1 -1
- package/dist/document/brand-check.d.ts.map +1 -1
- package/dist/document/brand-check.js +8 -2
- package/dist/document/brand-check.js.map +1 -1
- package/dist/document/compile-checked.d.ts.map +1 -1
- package/dist/document/compile-checked.js +4 -2
- package/dist/document/compile-checked.js.map +1 -1
- package/dist/document/compile.d.ts.map +1 -1
- package/dist/document/compile.js +48 -9
- package/dist/document/compile.js.map +1 -1
- package/dist/document/compiled.d.ts +14 -0
- package/dist/document/compiled.d.ts.map +1 -1
- package/dist/document/compiled.js +39 -4
- package/dist/document/compiled.js.map +1 -1
- package/dist/document/connection-findings.js +93 -21
- package/dist/document/connection-findings.js.map +1 -1
- package/dist/document/connection-spec.d.ts +54 -1
- package/dist/document/connection-spec.d.ts.map +1 -1
- package/dist/document/connection-spec.js +76 -8
- package/dist/document/connection-spec.js.map +1 -1
- package/dist/document/derives-findings.d.ts +4 -0
- package/dist/document/derives-findings.d.ts.map +1 -0
- package/dist/document/derives-findings.js +68 -0
- package/dist/document/derives-findings.js.map +1 -0
- package/dist/document/describe-forms.js +2 -2
- package/dist/document/describe-forms.js.map +1 -1
- package/dist/document/diff.d.ts +6 -0
- package/dist/document/diff.d.ts.map +1 -1
- package/dist/document/diff.js +43 -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 +264 -23
- package/dist/document/edit.js.map +1 -1
- package/dist/document/expr/evaluate.d.ts +7 -0
- package/dist/document/expr/evaluate.d.ts.map +1 -1
- package/dist/document/expr/evaluate.js +29 -2
- package/dist/document/expr/evaluate.js.map +1 -1
- package/dist/document/format-words.d.ts +1 -0
- package/dist/document/format-words.d.ts.map +1 -1
- package/dist/document/format-words.js +2 -2
- package/dist/document/format-words.js.map +1 -1
- package/dist/document/kept-in-step-with.d.ts +20 -0
- package/dist/document/kept-in-step-with.d.ts.map +1 -0
- package/dist/document/kept-in-step-with.js +38 -0
- package/dist/document/kept-in-step-with.js.map +1 -0
- package/dist/document/kept-in-step.d.ts.map +1 -1
- package/dist/document/kept-in-step.js +3 -1
- package/dist/document/kept-in-step.js.map +1 -1
- package/dist/document/money.d.ts +10 -2
- package/dist/document/money.d.ts.map +1 -1
- package/dist/document/money.js +14 -5
- package/dist/document/money.js.map +1 -1
- package/dist/document/policy-words.d.ts +2 -0
- package/dist/document/policy-words.d.ts.map +1 -1
- package/dist/document/policy-words.js +11 -1
- package/dist/document/policy-words.js.map +1 -1
- package/dist/document/schema.d.ts +46 -1
- package/dist/document/schema.d.ts.map +1 -1
- package/dist/document/schema.js +48 -8
- package/dist/document/schema.js.map +1 -1
- package/dist/document/template-parse.d.ts.map +1 -1
- package/dist/document/template-parse.js +7 -1
- package/dist/document/template-parse.js.map +1 -1
- package/dist/document/template.d.ts +2 -0
- package/dist/document/template.d.ts.map +1 -1
- package/dist/document/template.js +10 -1
- package/dist/document/template.js.map +1 -1
- package/dist/document/to-document.js +2 -2
- package/dist/document/to-document.js.map +1 -1
- package/dist/document/workflow-spec.d.ts +72 -0
- package/dist/document/workflow-spec.d.ts.map +1 -0
- package/dist/document/workflow-spec.js +45 -0
- package/dist/document/workflow-spec.js.map +1 -0
- package/dist/document.schema.json +883 -4
- package/dist/head.d.ts +81 -0
- package/dist/head.d.ts.map +1 -0
- package/dist/head.js +123 -0
- package/dist/head.js.map +1 -0
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +2 -0
- package/dist/history.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/installation.d.ts +1 -1
- package/dist/job-findings.d.ts +5 -0
- package/dist/job-findings.d.ts.map +1 -1
- package/dist/job-findings.js +9 -3
- package/dist/job-findings.js.map +1 -1
- package/dist/keep-linked.d.ts +45 -0
- package/dist/keep-linked.d.ts.map +1 -0
- package/dist/keep-linked.js +188 -0
- package/dist/keep-linked.js.map +1 -0
- package/dist/mutations/derive-edits.js +2 -2
- package/dist/mutations/propose-changes.d.ts +24 -0
- package/dist/mutations/propose-changes.d.ts.map +1 -0
- package/dist/mutations/propose-changes.js +30 -0
- package/dist/mutations/propose-changes.js.map +1 -0
- package/dist/mutations/vote-suggestion.d.ts +30 -0
- package/dist/mutations/vote-suggestion.d.ts.map +1 -1
- package/dist/mutations/vote-suggestion.js +23 -0
- package/dist/mutations/vote-suggestion.js.map +1 -1
- package/dist/mutations/words.js +3 -3
- package/dist/ops/digest.d.ts +3 -1
- package/dist/ops/digest.d.ts.map +1 -1
- package/dist/ops/digest.js +7 -2
- package/dist/ops/digest.js.map +1 -1
- package/dist/ops/lineage.d.ts +19 -2
- package/dist/ops/lineage.d.ts.map +1 -1
- package/dist/ops/lineage.js +8 -4
- package/dist/ops/lineage.js.map +1 -1
- package/dist/ops/log.d.ts.map +1 -1
- package/dist/ops/log.js +13 -2
- package/dist/ops/log.js.map +1 -1
- package/dist/ops/suggestions.d.ts +47 -5
- package/dist/ops/suggestions.d.ts.map +1 -1
- package/dist/ops/suggestions.js +88 -13
- package/dist/ops/suggestions.js.map +1 -1
- package/dist/ops/tidying.d.ts.map +1 -1
- package/dist/ops/tidying.js +6 -1
- package/dist/ops/tidying.js.map +1 -1
- package/dist/ops/types.d.ts +28 -0
- package/dist/ops/types.d.ts.map +1 -1
- package/dist/ops/undo.d.ts.map +1 -1
- package/dist/ops/undo.js +17 -0
- package/dist/ops/undo.js.map +1 -1
- package/dist/permissions/policy.d.ts +7 -0
- package/dist/permissions/policy.d.ts.map +1 -1
- package/dist/permissions/policy.js +12 -0
- package/dist/permissions/policy.js.map +1 -1
- package/dist/permissions/types.d.ts +27 -0
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js.map +1 -1
- package/dist/places.d.ts +10 -0
- package/dist/places.d.ts.map +1 -1
- package/dist/places.js +35 -1
- package/dist/places.js.map +1 -1
- package/dist/scene.d.ts +2 -0
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +2 -0
- package/dist/scene.js.map +1 -1
- package/dist/schema/amount-mark.d.ts +2 -0
- package/dist/schema/amount-mark.d.ts.map +1 -1
- package/dist/schema/amount-mark.js.map +1 -1
- package/dist/schema/amount-words.js +2 -2
- package/dist/schema/amount-words.js.map +1 -1
- package/dist/schema/choice-words.js +2 -2
- package/dist/schema/define-node.d.ts +8 -2
- package/dist/schema/define-node.d.ts.map +1 -1
- package/dist/schema/define-node.js +2 -2
- package/dist/schema/define-node.js.map +1 -1
- package/dist/schema/humanize.d.ts +17 -0
- package/dist/schema/humanize.d.ts.map +1 -1
- package/dist/schema/humanize.js +28 -1
- package/dist/schema/humanize.js.map +1 -1
- package/dist/schema/reading.d.ts +8 -0
- package/dist/schema/reading.d.ts.map +1 -1
- package/dist/schema/reading.js +16 -2
- package/dist/schema/reading.js.map +1 -1
- package/dist/schema/types.d.ts +8 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/value-words.js +2 -2
- package/dist/schema/value-words.js.map +1 -1
- package/dist/starting-document.d.ts +16 -3
- package/dist/starting-document.d.ts.map +1 -1
- package/dist/starting-document.js +32 -5
- package/dist/starting-document.js.map +1 -1
- package/dist/store.d.ts +47 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +176 -13
- package/dist/store.js.map +1 -1
- package/dist/sync/connection.d.ts +24 -5
- package/dist/sync/connection.d.ts.map +1 -1
- package/dist/sync/connection.js +168 -31
- package/dist/sync/connection.js.map +1 -1
- package/dist/sync/engine.d.ts +97 -0
- package/dist/sync/engine.d.ts.map +1 -1
- package/dist/sync/engine.js +159 -1
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/google-calendar.d.ts.map +1 -1
- package/dist/sync/google-calendar.js +18 -1
- package/dist/sync/google-calendar.js.map +1 -1
- package/dist/sync/guest-words.d.ts +9 -0
- package/dist/sync/guest-words.d.ts.map +1 -0
- package/dist/sync/guest-words.js +41 -0
- package/dist/sync/guest-words.js.map +1 -0
- package/dist/sync/index.d.ts +8 -5
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +2 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/notion-stub.d.ts +4 -1
- package/dist/sync/notion-stub.d.ts.map +1 -1
- package/dist/sync/notion-stub.js +40 -0
- package/dist/sync/notion-stub.js.map +1 -1
- package/dist/sync/notion.d.ts +44 -2
- package/dist/sync/notion.d.ts.map +1 -1
- package/dist/sync/notion.js +289 -143
- package/dist/sync/notion.js.map +1 -1
- package/dist/sync/status.d.ts +43 -0
- package/dist/sync/status.d.ts.map +1 -0
- package/dist/sync/status.js +46 -0
- package/dist/sync/status.js.map +1 -0
- package/dist/sync/types.d.ts +60 -0
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js.map +1 -1
- package/dist/theme/marks.d.ts +12 -0
- package/dist/theme/marks.d.ts.map +1 -1
- package/dist/theme/marks.js +5 -0
- package/dist/theme/marks.js.map +1 -1
- package/dist/tidying.d.ts +53 -4
- package/dist/tidying.d.ts.map +1 -1
- package/dist/tidying.js +110 -8
- package/dist/tidying.js.map +1 -1
- package/dist/validate-graph.d.ts +2 -1
- package/dist/validate-graph.d.ts.map +1 -1
- package/dist/validate-graph.js +32 -1
- package/dist/validate-graph.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/words.d.ts +4 -1
- package/dist/words.d.ts.map +1 -1
- package/dist/words.js +4 -1
- package/dist/words.js.map +1 -1
- package/dist/workflow-shape.d.ts +223 -0
- package/dist/workflow-shape.d.ts.map +1 -0
- package/dist/workflow-shape.js +31 -0
- package/dist/workflow-shape.js.map +1 -0
- package/dist/workflows/findings.d.ts +13 -0
- package/dist/workflows/findings.d.ts.map +1 -0
- package/dist/workflows/findings.js +157 -0
- package/dist/workflows/findings.js.map +1 -0
- package/dist/workflows/generate-words.d.ts +12 -0
- package/dist/workflows/generate-words.d.ts.map +1 -0
- package/dist/workflows/generate-words.js +25 -0
- package/dist/workflows/generate-words.js.map +1 -0
- package/dist/workflows/generate.d.ts +59 -0
- package/dist/workflows/generate.d.ts.map +1 -0
- package/dist/workflows/generate.js +165 -0
- package/dist/workflows/generate.js.map +1 -0
- package/dist/workflows/judging.d.ts +17 -0
- package/dist/workflows/judging.d.ts.map +1 -0
- package/dist/workflows/judging.js +11 -0
- package/dist/workflows/judging.js.map +1 -0
- package/dist/workflows/next.d.ts +18 -0
- package/dist/workflows/next.d.ts.map +1 -0
- package/dist/workflows/next.js +75 -0
- package/dist/workflows/next.js.map +1 -0
- package/dist/workflows/on-the-map.d.ts +79 -0
- package/dist/workflows/on-the-map.d.ts.map +1 -0
- package/dist/workflows/on-the-map.js +96 -0
- package/dist/workflows/on-the-map.js.map +1 -0
- package/dist/workflows/policies.d.ts +39 -0
- package/dist/workflows/policies.d.ts.map +1 -0
- package/dist/workflows/policies.js +95 -0
- package/dist/workflows/policies.js.map +1 -0
- package/dist/workflows/resolve.d.ts +55 -0
- package/dist/workflows/resolve.d.ts.map +1 -0
- package/dist/workflows/resolve.js +57 -0
- package/dist/workflows/resolve.js.map +1 -0
- package/dist/workflows/schedule.d.ts +28 -0
- package/dist/workflows/schedule.d.ts.map +1 -0
- package/dist/workflows/schedule.js +160 -0
- package/dist/workflows/schedule.js.map +1 -0
- package/dist/workflows/step-act.d.ts +6 -0
- package/dist/workflows/step-act.d.ts.map +1 -0
- package/dist/workflows/step-act.js +79 -0
- package/dist/workflows/step-act.js.map +1 -0
- package/dist/workflows/step-agent.d.ts +4 -0
- package/dist/workflows/step-agent.d.ts.map +1 -0
- package/dist/workflows/step-agent.js +55 -0
- package/dist/workflows/step-agent.js.map +1 -0
- package/dist/workflows/step-archive.d.ts +8 -0
- package/dist/workflows/step-archive.d.ts.map +1 -0
- package/dist/workflows/step-archive.js +74 -0
- package/dist/workflows/step-archive.js.map +1 -0
- package/dist/workflows/step-classify.d.ts +25 -0
- package/dist/workflows/step-classify.d.ts.map +1 -0
- package/dist/workflows/step-classify.js +160 -0
- package/dist/workflows/step-classify.js.map +1 -0
- package/dist/workflows/step-generate.d.ts +7 -0
- package/dist/workflows/step-generate.d.ts.map +1 -0
- package/dist/workflows/step-generate.js +185 -0
- package/dist/workflows/step-generate.js.map +1 -0
- package/dist/workflows/steps.d.ts +75 -0
- package/dist/workflows/steps.d.ts.map +1 -0
- package/dist/workflows/steps.js +40 -0
- package/dist/workflows/steps.js.map +1 -0
- package/dist/workflows/triggered.d.ts +35 -0
- package/dist/workflows/triggered.d.ts.map +1 -0
- package/dist/workflows/triggered.js +69 -0
- package/dist/workflows/triggered.js.map +1 -0
- package/dist/workflows/values.d.ts +28 -0
- package/dist/workflows/values.d.ts.map +1 -0
- package/dist/workflows/values.js +75 -0
- package/dist/workflows/values.js.map +1 -0
- package/dist/workflows/words.d.ts +10 -0
- package/dist/workflows/words.d.ts.map +1 -0
- package/dist/workflows/words.js +30 -0
- package/dist/workflows/words.js.map +1 -0
- package/dist/workflows/world.d.ts +23 -0
- package/dist/workflows/world.d.ts.map +1 -0
- package/dist/workflows/world.js +50 -0
- package/dist/workflows/world.js.map +1 -0
- package/dist/workflows.d.ts +35 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +20 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +12 -2
package/dist/document/edit.js
CHANGED
|
@@ -16,13 +16,14 @@ const refusedFormat = (format, type) => {
|
|
|
16
16
|
import { farEnd } from "./far-end.js";
|
|
17
17
|
import { jobFindings, jobWorldOfDocument } from "../job-findings.js";
|
|
18
18
|
import { JobSpec } from "./purpose-spec.js";
|
|
19
|
+
import { WORKFLOW_JUDGING } from "../workflows/judging.js";
|
|
19
20
|
import { aliasWord, documentAliasFindings } from "../alias-findings.js";
|
|
20
21
|
import { humanizeField, withArticle } from "../schema/define-node.js";
|
|
21
|
-
import { ConnectionSpec } from "./connection-spec.js";
|
|
22
|
+
import { ConnectionSpec, connectionKinds } from "./connection-spec.js";
|
|
22
23
|
import { linkedRelationFindings } from "./linked-relation-findings.js";
|
|
23
24
|
import { linkedRelationOfSpec, reachesAnotherApp } from "../schema/linked.js";
|
|
24
|
-
import { ActSpec, ALIAS, MOST_ALIASES, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
|
|
25
|
-
import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES, unknownLensOption, viewReference } from "../places.js";
|
|
25
|
+
import { ActSpec, ALIAS, MOST_ALIASES, MOST_COMPUTED, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
|
|
26
|
+
import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES, TIMELINE_AXIS_ROLES, timelineBindingsWords, unknownLensOption, viewReference } from "../places.js";
|
|
26
27
|
import { LENSES_THAT_LIST, listsRecords } from "../lenses-that-list.js";
|
|
27
28
|
import { placeSlug, VIEW_REFERENCE } from "../views/types.js";
|
|
28
29
|
import { documentListFields, kindListFindings, kindListWords, listGroup, listSort, sortTypeOfDocumentField } from "../kind-list.js";
|
|
@@ -36,13 +37,15 @@ import { canonicalize } from "./canonical.js";
|
|
|
36
37
|
import { quotedAtTheEnd } from "./quote.js";
|
|
37
38
|
import { isRecord as isObject, respellDocument } from "./respell.js";
|
|
38
39
|
import { refName } from "./compiled.js";
|
|
39
|
-
import { localeSentence, timeZoneSentence } from "./diff.js";
|
|
40
|
+
import { derivesSentence, localeSentence, timeZoneSentence } from "./diff.js";
|
|
40
41
|
import { accentProblem } from "../theme/accent.js";
|
|
41
42
|
import { darkPageFor, groundProblem } from "../theme/ground-check.js";
|
|
42
43
|
import { pageFieldFindings, pageNames, renamedOnPage, withoutOnPage } from "./page-fields.js";
|
|
44
|
+
/** Whether a field or a computed field is said as money: its format, or a unit that is a currency. */
|
|
45
|
+
const isMoney = (f) => f["format"] === "money" || (f["format"] === undefined && typeof f["unit"] === "string" && /^[A-Z]{3}$/.test(f["unit"]));
|
|
43
46
|
import { capitalize, listed as listOf } from "../capital.js";
|
|
44
47
|
import { quoted } from "./quote.js";
|
|
45
|
-
import { FIRST_GRANT, FIRST_SIGHT, grantBody, grantGone, grantSentence, NO_GRANT_LEFT, NO_SIGHT_LEFT, peopleKinds, peopleSentence, archivesSentence, tidyingSentence, nounWords, pluralWords as pluralOf, sightGone, sightSentence, sightWords, whoWords } from "./policy-words.js";
|
|
48
|
+
import { FIRST_GRANT, FIRST_SIGHT, grantBody, grantGone, grantSentence, NO_GRANT_LEFT, NO_SIGHT_LEFT, peopleKinds, peopleSentence, appTidyingSentence, archivesSentence, tidyingSentence, nounWords, pluralWords as pluralOf, sightGone, sightSentence, sightWords, whoWords } from "./policy-words.js";
|
|
46
49
|
/** The ops, for tool schemas and the guide. */
|
|
47
50
|
export const EDIT_OPS = [
|
|
48
51
|
"add-kind",
|
|
@@ -84,6 +87,8 @@ export const EDIT_OPS = [
|
|
|
84
87
|
"set-people",
|
|
85
88
|
"set-archives",
|
|
86
89
|
"set-tidying",
|
|
90
|
+
"set-app-tidying",
|
|
91
|
+
"set-derives",
|
|
87
92
|
"add-grant",
|
|
88
93
|
"remove-grant",
|
|
89
94
|
"add-sight",
|
|
@@ -95,6 +100,11 @@ export const EDIT_OPS = [
|
|
|
95
100
|
"add-alias",
|
|
96
101
|
"set-aliases",
|
|
97
102
|
"set-connection",
|
|
103
|
+
"add-workflow",
|
|
104
|
+
"set-workflow",
|
|
105
|
+
"remove-workflow",
|
|
106
|
+
"pause-workflow",
|
|
107
|
+
"resume-workflow",
|
|
98
108
|
];
|
|
99
109
|
/** Words the rule language keeps: a field or relation renamed to one could not be named in a rule. */
|
|
100
110
|
const KEYWORDS = new Set(["and", "or", "not", "in", "where", "true", "false", "null"]);
|
|
@@ -126,7 +136,7 @@ function buildShapes() {
|
|
|
126
136
|
"rename-field": z.object({ op: z.literal("rename-field"), kind: kindName, field: fieldName, to: fieldName }).strict(),
|
|
127
137
|
"retype-field": z.object({ op: z.literal("retype-field"), kind: kindName, field: fieldName, type: z.enum(FIELD_TYPES), options: z.array(z.string().min(1).max(80)).min(1).max(100).optional(), multiple: z.boolean().optional(), of: z.enum(["string", "number", "date", "image", "video", "file"]).optional(), format: z.enum(["money", "percent", "duration", "markdown"]).optional() }).strict(),
|
|
128
138
|
"set-options": z.object({ op: z.literal("set-options"), kind: kindName, field: fieldName, add: z.array(z.string().min(1).max(80)).optional(), remove: z.array(z.string().min(1).max(80)).optional(), labels: z.record(z.string().min(1).max(80), z.union([z.string().min(1).max(80), z.null()])).optional() }).strict(),
|
|
129
|
-
"set-format": z.object({ op: z.literal("set-format"), kind: kindName, field: fieldName, format: z.union([z.enum(["money", "percent", "duration", "markdown"]), z.null()]).optional(), unit: z.union([z.string().min(1).max(12), z.null()]).optional() }).strict(),
|
|
139
|
+
"set-format": z.object({ op: z.literal("set-format"), kind: kindName, field: fieldName, format: z.union([z.enum(["money", "percent", "duration", "markdown"]), z.null()]).optional(), unit: z.union([z.string().min(1).max(12), z.null()]).optional(), places: z.union([z.literal(0), z.null()]).optional() }).strict(),
|
|
130
140
|
"set-range": z.object({ op: z.literal("set-range"), kind: kindName, field: fieldName, min: z.union([z.number(), z.null()]).optional(), max: z.union([z.number(), z.null()]).optional(), step: z.union([z.number().positive(), z.null()]).optional() }).strict(),
|
|
131
141
|
"set-required": z.object({ op: z.literal("set-required"), kind: kindName, field: fieldName, required: z.boolean() }).strict(),
|
|
132
142
|
"set-default": z.object({ op: z.literal("set-default"), kind: kindName, field: fieldName, default: z.unknown() }).strict(),
|
|
@@ -168,7 +178,7 @@ function buildShapes() {
|
|
|
168
178
|
// Whether the home draws the description as the line under the name (Roadmap f-home-subtitle-separate): false keeps it off, true or null draws it again.
|
|
169
179
|
subtitle: z.union([z.boolean(), z.null()]).optional(),
|
|
170
180
|
// The opening screen (Roadmap f-splash-in-document), whole; null for Graview's own.
|
|
171
|
-
splash: z.union([z.object({ style: z.string(), image: z.string().min(1).optional(), title: z.string().min(1).max(80).optional(), kicker: z.string().min(1).max(80).optional(), shader: z.string().min(1).max(40).optional() }).strict(), z.null()]).optional(),
|
|
181
|
+
splash: z.union([z.object({ style: z.string(), image: z.string().min(1).optional(), title: z.string().min(1).max(80).optional(), kicker: z.string().min(1).max(80).optional(), shader: z.string().min(1).max(40).optional(), scheme: z.enum(["light", "dark", "device"]).optional() }).strict(), z.null()]).optional(),
|
|
172
182
|
// What every worker view inherits (Roadmap f-app-wide-view-theme): a stylesheet drawn before each view's own (its length the sanitizer's to refuse, in `brandFindings`), and a fade between records; null clears either, or all of it.
|
|
173
183
|
views: z
|
|
174
184
|
.union([
|
|
@@ -254,6 +264,10 @@ function buildShapes() {
|
|
|
254
264
|
"set-archives": z.object({ op: z.literal("set-archives"), kind: kindName, archives: z.boolean() }).strict(),
|
|
255
265
|
// What the app's own actor may do on its own to a kind, or to one relation of it (`@graview/core/tidying`).
|
|
256
266
|
"set-tidying": z.object({ op: z.literal("set-tidying"), kind: kindName, relation: z.optional(z.string()), tidying: z.enum(["never", "no-links", "allowed"]) }).strict(),
|
|
267
|
+
// What the app says of suggestions as a whole (`tidying` at the top of the document): the votes at which the app's own actor makes one; null takes it away.
|
|
268
|
+
"set-app-tidying": z.object({ op: z.literal("set-app-tidying"), autoAcceptAt: z.union([z.number().int().min(1).max(10_000), z.null()]) }).strict(),
|
|
269
|
+
// What a kind follows (Roadmap f-keep-linked): the kind, the relation joining them and the fields worked out from it; null, it follows nothing.
|
|
270
|
+
"set-derives": z.object({ op: z.literal("set-derives"), kind: kindName, derives: z.union([z.object({ from: kindName, via: z.string(), set: z.record(z.string(), z.unknown()) }).strict(), z.null()]) }).strict(),
|
|
257
271
|
"add-grant": z.object({ op: z.literal("add-grant") }).passthrough(),
|
|
258
272
|
"remove-grant": z.object({ op: z.literal("remove-grant") }).passthrough(),
|
|
259
273
|
"add-sight": z.object({ op: z.literal("add-sight") }).passthrough(),
|
|
@@ -282,6 +296,12 @@ function buildShapes() {
|
|
|
282
296
|
"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(),
|
|
283
297
|
// A connection declared whole, by its name: set where there was none, or replaced (FR-229); null takes it away.
|
|
284
298
|
"set-connection": z.object({ op: z.literal("set-connection"), name: kindName, connection: z.union([z.record(z.string(), z.unknown()), z.null()]) }).strict(),
|
|
299
|
+
// A workflow by its name (Roadmap f-jobs-declared): added where there was none, replaced whole, removed, paused or resumed.
|
|
300
|
+
"add-workflow": z.object({ op: z.literal("add-workflow"), name: kindName, workflow: z.record(z.string(), z.unknown()) }).strict(),
|
|
301
|
+
"set-workflow": z.object({ op: z.literal("set-workflow"), name: kindName, workflow: z.record(z.string(), z.unknown()) }).strict(),
|
|
302
|
+
"remove-workflow": z.object({ op: z.literal("remove-workflow"), name: kindName }).strict(),
|
|
303
|
+
"pause-workflow": z.object({ op: z.literal("pause-workflow"), name: kindName }).strict(),
|
|
304
|
+
"resume-workflow": z.object({ op: z.literal("resume-workflow"), name: kindName }).strict(),
|
|
285
305
|
};
|
|
286
306
|
}
|
|
287
307
|
let shapes;
|
|
@@ -1045,8 +1065,12 @@ class Editor {
|
|
|
1045
1065
|
const fields = e.fields;
|
|
1046
1066
|
const seen = new Set();
|
|
1047
1067
|
for (const [n, field] of fields.entries()) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1068
|
+
// A computed field may be glanced at (FR-83): worked out for each card, as its page works it out — unless it is kept off what a record says of itself.
|
|
1069
|
+
const worked = spec.computed?.[field];
|
|
1070
|
+
if (!spec.fields[field] && worked === undefined)
|
|
1071
|
+
return this.fail(i, `fields.${n}`, `a glance at ${e.kind} is to say "${field}", and ${e.kind} has no field or computed field called that`, `use one of: ${[...Object.keys(spec.fields), ...Object.keys(spec.computed ?? {})].join(", ")}`);
|
|
1072
|
+
if (!spec.fields[field] && isObject(worked) && worked["hidden"] === true)
|
|
1073
|
+
return this.fail(i, `fields.${n}`, `${e.kind}'s "${field}" is worked out and kept off its card, page and CSV, so a glance cannot say it`, `show it first: set-computed with "hidden": false`);
|
|
1050
1074
|
if (seen.has(field))
|
|
1051
1075
|
return this.fail(i, `fields.${n}`, `"${field}" is in the glance twice; name each field once`);
|
|
1052
1076
|
seen.add(field);
|
|
@@ -1085,6 +1109,10 @@ class Editor {
|
|
|
1085
1109
|
return this.setArchives(i, e);
|
|
1086
1110
|
case "set-tidying":
|
|
1087
1111
|
return this.setTidying(i, e);
|
|
1112
|
+
case "set-app-tidying":
|
|
1113
|
+
return this.setAppTidying(e);
|
|
1114
|
+
case "set-derives":
|
|
1115
|
+
return this.setDerives(i, e);
|
|
1088
1116
|
case "add-grant":
|
|
1089
1117
|
case "remove-grant":
|
|
1090
1118
|
case "add-sight":
|
|
@@ -1104,6 +1132,15 @@ class Editor {
|
|
|
1104
1132
|
return this.setAliases(i, e.kind, e.field, e.aliases ?? []);
|
|
1105
1133
|
case "set-connection":
|
|
1106
1134
|
return this.setConnection(i, e.name, e.connection);
|
|
1135
|
+
case "add-workflow":
|
|
1136
|
+
return this.placeWorkflow(i, e.name, e.workflow, "add");
|
|
1137
|
+
case "set-workflow":
|
|
1138
|
+
return this.placeWorkflow(i, e.name, e.workflow, "set");
|
|
1139
|
+
case "remove-workflow":
|
|
1140
|
+
return this.removeWorkflow(i, e.name);
|
|
1141
|
+
case "pause-workflow":
|
|
1142
|
+
case "resume-workflow":
|
|
1143
|
+
return this.setWorkflowStatus(i, e.name, e.op === "pause-workflow" ? "paused" : "active");
|
|
1107
1144
|
}
|
|
1108
1145
|
}
|
|
1109
1146
|
// ── a connection, declared whole (FR-229) ──
|
|
@@ -1131,7 +1168,9 @@ class Editor {
|
|
|
1131
1168
|
const was = this.doc.connections?.[name] !== undefined;
|
|
1132
1169
|
(this.doc.connections ??= {})[name] = parsed.data;
|
|
1133
1170
|
const system = parsed.data.system === "notion" ? "a Notion database" : "a Google calendar";
|
|
1134
|
-
|
|
1171
|
+
const kinds = connectionKinds(parsed.data).map((kind) => pluralOf(this.doc.kinds, kind));
|
|
1172
|
+
const said = kinds.length <= 1 ? kinds.join("") : `${kinds.slice(0, -1).join(", ")} and ${kinds.at(-1)}`;
|
|
1173
|
+
this.said.push(`The connection ${name} ${was ? "now keeps" : "keeps"} ${said} in step with ${system}${parsed.data.direction === "read" ? ", which owns them" : ", both ways"}.`);
|
|
1135
1174
|
}
|
|
1136
1175
|
// ── other words for a kind or a field (Roadmap f-learned-suggestions) ──
|
|
1137
1176
|
aliasesOf(kind, field) {
|
|
@@ -1258,6 +1297,58 @@ class Editor {
|
|
|
1258
1297
|
this.keepPurpose({ ...this.doc.purpose });
|
|
1259
1298
|
this.said.push(`The job "${job.name}" (${quotedAtTheEnd(job.ask)}) is removed.`);
|
|
1260
1299
|
}
|
|
1300
|
+
// ── what the app does on its own, and when (Roadmap f-jobs-declared) ──
|
|
1301
|
+
/** The workflow a name names, or undefined having said why. */
|
|
1302
|
+
workflowNamed(i, name) {
|
|
1303
|
+
const workflows = this.doc.workflows ?? {};
|
|
1304
|
+
if (Object.hasOwn(workflows, name))
|
|
1305
|
+
return workflows[name];
|
|
1306
|
+
const names = Object.keys(workflows);
|
|
1307
|
+
this.fail(i, "name", `there is no workflow "${name}"${names.length > 0 ? `; the workflows are ${names.map((one) => `"${one}"`).join(", ")}` : ""}`);
|
|
1308
|
+
return undefined;
|
|
1309
|
+
}
|
|
1310
|
+
/** A workflow added or replaced whole, judged as `graview check` judges it (`workflow-*`): kept only when nothing is wrong with it. */
|
|
1311
|
+
placeWorkflow(i, name, workflow, how) {
|
|
1312
|
+
const workflows = { ...(this.doc.workflows ?? {}) };
|
|
1313
|
+
if (how === "add" && Object.hasOwn(workflows, name))
|
|
1314
|
+
return this.fail(i, "name", `there is already a workflow "${name}"`, `Change it with {"op": "set-workflow", "name": "${name}", …}, or name this one otherwise.`);
|
|
1315
|
+
if (how === "set" && !Object.hasOwn(workflows, name) && !this.workflowNamed(i, name))
|
|
1316
|
+
return;
|
|
1317
|
+
const shaped = typeof workflow.title === "string" && Array.isArray(workflow.when) && workflow.when.length > 0 && Array.isArray(workflow.steps) && workflow.steps.length > 0;
|
|
1318
|
+
if (!shaped)
|
|
1319
|
+
return this.fail(i, "workflow", "a workflow is { title, when: [...], steps: [...] }, each list with at least one", `{"op": "${how}-workflow", "name": "${name}", "workflow": {"title": "…", "when": [{"every": "day 03:00"}], "steps": [{"act": "…"}]}}`);
|
|
1320
|
+
// Judged as `graview check` judges it, where the checker is loaded; else kept as shaped, and the check says the rest.
|
|
1321
|
+
const judged = WORKFLOW_JUDGING.judge?.(name, workflow, { ...this.doc, workflows });
|
|
1322
|
+
for (const finding of judged?.errors ?? [])
|
|
1323
|
+
this.failAs(finding.code, i, `workflow${finding.path ? `.${finding.path}` : ""}`, finding.message, finding.fix);
|
|
1324
|
+
if (judged && judged.errors.length > 0)
|
|
1325
|
+
return;
|
|
1326
|
+
workflows[name] = workflow;
|
|
1327
|
+
this.doc.workflows = workflows;
|
|
1328
|
+
const steps = workflow.steps.length;
|
|
1329
|
+
this.said.push(`${how === "add" ? "A new workflow" : "The workflow"} “${workflow.title}”${how === "add" ? "" : " is now"}: ${judged?.said ?? `${steps === 1 ? "one step" : `${steps} steps`}${workflow.status === "paused" ? ", paused" : ""}`}.`);
|
|
1330
|
+
for (const warning of judged?.warnings ?? [])
|
|
1331
|
+
this.said.push(warning);
|
|
1332
|
+
}
|
|
1333
|
+
removeWorkflow(i, name) {
|
|
1334
|
+
const workflow = this.workflowNamed(i, name);
|
|
1335
|
+
if (!workflow)
|
|
1336
|
+
return;
|
|
1337
|
+
const { [name]: _gone, ...rest } = this.doc.workflows;
|
|
1338
|
+
if (Object.keys(rest).length === 0)
|
|
1339
|
+
delete this.doc.workflows;
|
|
1340
|
+
else
|
|
1341
|
+
this.doc.workflows = rest;
|
|
1342
|
+
this.said.push(`The workflow “${workflow.title ?? name}” is removed.`);
|
|
1343
|
+
}
|
|
1344
|
+
setWorkflowStatus(i, name, status) {
|
|
1345
|
+
const workflow = this.workflowNamed(i, name);
|
|
1346
|
+
if (!workflow)
|
|
1347
|
+
return;
|
|
1348
|
+
const { status: _was, ...rest } = workflow;
|
|
1349
|
+
this.doc.workflows = { ...this.doc.workflows, [name]: status === "paused" ? { ...rest, status } : rest };
|
|
1350
|
+
this.said.push(status === "paused" ? `The workflow “${workflow.title ?? name}” is paused: it runs on no clock and no change until it is resumed.` : `The workflow “${workflow.title ?? name}” runs again.`);
|
|
1351
|
+
}
|
|
1261
1352
|
/** How a record's page orders and groups its facts (FR-148): judged as the compile judges `kinds.<kind>.page`. */
|
|
1262
1353
|
setPageFields(i, e) {
|
|
1263
1354
|
const spec = this.kind(i, e.kind);
|
|
@@ -1401,6 +1492,8 @@ class Editor {
|
|
|
1401
1492
|
}
|
|
1402
1493
|
if (name === "columns" && !(this.columnsHold(i, e.bindings) && this.boardOptionsHold(i, e.bindings, e.options)))
|
|
1403
1494
|
return;
|
|
1495
|
+
if (name === "timeline" && !this.timelineHolds(i, e.bindings, e.options))
|
|
1496
|
+
return;
|
|
1404
1497
|
if (was)
|
|
1405
1498
|
lenses[at] = lens;
|
|
1406
1499
|
else
|
|
@@ -1473,6 +1566,44 @@ class Editor {
|
|
|
1473
1566
|
}
|
|
1474
1567
|
return true;
|
|
1475
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* A TIMELINE THAT WOULD DRAW, held before it is added: a timeline lays out
|
|
1571
|
+
* spans by number across columns, so each kind it binds is a kind, its
|
|
1572
|
+
* `start` and `end` are number fields, and its columns are named in the
|
|
1573
|
+
* options or read from a choice field bound to `column`. A timeline over a
|
|
1574
|
+
* project's start and end dates was accepted, said nothing and drew
|
|
1575
|
+
* nothing — never a place; it is refused here in the words the checker
|
|
1576
|
+
* uses (`lens-cannot-draw`), pointing spans of dates at the calendar.
|
|
1577
|
+
*/
|
|
1578
|
+
timelineHolds(i, bindings, options) {
|
|
1579
|
+
const refuse = (path, message, kind, start, end) => (this.failAs("lens-cannot-draw", i, path, `a timeline here would not draw: ${message}`, timelineBindingsWords(kind, typeof start === "string" ? start : "start", typeof end === "string" ? end : "end")), false);
|
|
1580
|
+
if (!isObject(bindings) || Object.keys(bindings).length === 0)
|
|
1581
|
+
return refuse("bindings", "it binds no kind", "<kind>");
|
|
1582
|
+
let columns = Array.isArray(options?.["columns"]) && options["columns"].length > 0;
|
|
1583
|
+
for (const [kind, roles] of Object.entries(bindings)) {
|
|
1584
|
+
if (!isObject(this.doc.kinds[kind]))
|
|
1585
|
+
return (this.fail(i, `bindings.${kind}`, `"${kind}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`), false);
|
|
1586
|
+
if (!isObject(roles))
|
|
1587
|
+
return refuse(`bindings.${kind}`, `it does not say which fields of ${kind} it lays out`, kind);
|
|
1588
|
+
const fields = this.doc.kinds[kind].fields;
|
|
1589
|
+
for (const role of TIMELINE_AXIS_ROLES) {
|
|
1590
|
+
const field = roles[role];
|
|
1591
|
+
if (typeof field !== "string")
|
|
1592
|
+
return refuse(`bindings.${kind}.${role}`, `it does not say which field of ${kind} is its ${role}`, kind, roles["start"], roles["end"]);
|
|
1593
|
+
if (!(field in fields))
|
|
1594
|
+
return (this.fail(i, `bindings.${kind}.${role}`, `${kind} has no field "${field}"; it has ${Object.keys(fields).join(", ")}`), false);
|
|
1595
|
+
const type = fields[field]["type"];
|
|
1596
|
+
if (type !== "number" && type !== "integer" && type !== "duration")
|
|
1597
|
+
return refuse(`bindings.${kind}.${role}`, `${kind}'s ${field} is ${withArticle(String(type ?? "text"))} field, and a timeline's axis is a number`, kind, roles["start"], roles["end"]);
|
|
1598
|
+
}
|
|
1599
|
+
const column = typeof roles["column"] === "string" ? roles["column"] : typeof roles["columns"] === "string" ? roles["columns"] : undefined;
|
|
1600
|
+
if (column !== undefined && fields[column]?.["type"] === "enum")
|
|
1601
|
+
columns = true;
|
|
1602
|
+
}
|
|
1603
|
+
if (!columns)
|
|
1604
|
+
return refuse("options.columns", "it has no columns — no option names them and no `column` role is bound to a choice field", Object.keys(bindings)[0]);
|
|
1605
|
+
return true;
|
|
1606
|
+
}
|
|
1476
1607
|
/**
|
|
1477
1608
|
* WHAT A BOARD SHOWS OF ITSELF (Roadmap f-lens-search-filter): fields of
|
|
1478
1609
|
* its kind for Find and the chips, columns of its field to open shut or
|
|
@@ -2035,8 +2166,8 @@ class Editor {
|
|
|
2035
2166
|
return this.fail(i, "name", `${e.kind} already has a ${spec.fields[name] ? "field" : "relation"} called "${name}"`);
|
|
2036
2167
|
if (KEYWORDS.has(name))
|
|
2037
2168
|
return this.fail(i, "name", `"${name}" is a word the rule language keeps; pick another name`);
|
|
2038
|
-
if (Object.keys(spec.computed ?? {}).length >=
|
|
2039
|
-
return this.fail(i, "name",
|
|
2169
|
+
if (Object.keys(spec.computed ?? {}).length >= MOST_COMPUTED)
|
|
2170
|
+
return this.fail(i, "name", `a kind works out at most ${MOST_COMPUTED} computed fields`);
|
|
2040
2171
|
}
|
|
2041
2172
|
const was = existing === undefined ? {} : typeof existing === "string" ? { expr: existing } : { ...existing };
|
|
2042
2173
|
const next = { ...was };
|
|
@@ -2128,6 +2259,86 @@ class Editor {
|
|
|
2128
2259
|
* never tidied is given no leave-alone-<kind> or allow-tidying-<kind>,
|
|
2129
2260
|
* which a grant may not then name.
|
|
2130
2261
|
*/
|
|
2262
|
+
/**
|
|
2263
|
+
* WHAT A KIND FOLLOWS (Roadmap f-keep-linked): each record of it linked
|
|
2264
|
+
* through `via` to a record of `from` takes the fields `set` works out
|
|
2265
|
+
* from it, kept in step by the store in the same change; null, it
|
|
2266
|
+
* follows nothing and its records keep the values they hold.
|
|
2267
|
+
*/
|
|
2268
|
+
setDerives(i, e) {
|
|
2269
|
+
const spec = this.kind(i, e.kind);
|
|
2270
|
+
if (!spec)
|
|
2271
|
+
return;
|
|
2272
|
+
if (e.derives === null) {
|
|
2273
|
+
const was = spec.derives?.from;
|
|
2274
|
+
delete spec.derives;
|
|
2275
|
+
this.said.push(was ? derivesSentence(e.kind, undefined) : `${e.kind} follows nothing, as before.`);
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
const { from, via, set } = e.derives;
|
|
2279
|
+
if (!this.doc.kinds[from])
|
|
2280
|
+
return this.fail(i, "derives.from", `"${from}" is not a kind this app has`);
|
|
2281
|
+
const joins = (spec.edges?.[via] && (spec.edges[via].to === "*" || spec.edges[via].to.includes(from))) || (this.doc.kinds[from].edges?.[via] && (this.doc.kinds[from].edges[via].to === "*" || this.doc.kinds[from].edges[via].to.includes(e.kind)));
|
|
2282
|
+
if (!joins)
|
|
2283
|
+
return this.fail(i, "derives.via", `"${via}" does not join ${words(e.kind)} to ${words(from)}`, `name a relation of ${e.kind} to ${from}, or of ${from} to ${e.kind} (add one with add-relation in the same change)`);
|
|
2284
|
+
spec.derives = { from, via, set };
|
|
2285
|
+
this.said.push(derivesSentence(e.kind, spec.derives));
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* WHAT A KIND FOLLOWS, held to a rename or a removal (Roadmap
|
|
2289
|
+
* f-keep-linked): the names it says follow a rename; a value reading
|
|
2290
|
+
* what is removed goes, and a `derives` left setting nothing, or
|
|
2291
|
+
* following a kind or relation that is gone, goes with it.
|
|
2292
|
+
*/
|
|
2293
|
+
followDerives(r, removing) {
|
|
2294
|
+
const touched = [];
|
|
2295
|
+
for (const [kind, spec] of Object.entries(this.doc.kinds)) {
|
|
2296
|
+
const d = spec.derives;
|
|
2297
|
+
if (!d)
|
|
2298
|
+
continue;
|
|
2299
|
+
const before = JSON.stringify(d);
|
|
2300
|
+
const ctx = new Set([d.from]);
|
|
2301
|
+
if ((r.t === "kind" && d.from === r.from) || (r.t === "edge" && d.via === r.from)) {
|
|
2302
|
+
if (removing) {
|
|
2303
|
+
delete spec.derives;
|
|
2304
|
+
touched.push(`what ${kind} follows`);
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
const set = {};
|
|
2309
|
+
for (const [field, value] of Object.entries(d.set ?? {})) {
|
|
2310
|
+
let key = field;
|
|
2311
|
+
if (r.t === "field" && r.kind === kind && field === r.from) {
|
|
2312
|
+
if (removing)
|
|
2313
|
+
continue;
|
|
2314
|
+
key = r.to;
|
|
2315
|
+
}
|
|
2316
|
+
if (isObject(value) && typeof value.expr === "string") {
|
|
2317
|
+
const source = value.expr;
|
|
2318
|
+
if (removing) {
|
|
2319
|
+
if (!exprMentions(this.doc, source, ctx, r))
|
|
2320
|
+
set[key] = value;
|
|
2321
|
+
}
|
|
2322
|
+
else
|
|
2323
|
+
set[key] = { expr: rewriteExpr(this.doc, source, ctx, r) };
|
|
2324
|
+
}
|
|
2325
|
+
else
|
|
2326
|
+
set[key] = value;
|
|
2327
|
+
}
|
|
2328
|
+
if (!removing && r.t === "kind" && d.from === r.from)
|
|
2329
|
+
d.from = r.to;
|
|
2330
|
+
if (!removing && r.t === "edge" && d.via === r.from)
|
|
2331
|
+
d.via = r.to;
|
|
2332
|
+
d.set = set;
|
|
2333
|
+
if (Object.keys(set).length === 0) {
|
|
2334
|
+
delete spec.derives;
|
|
2335
|
+
touched.push(`what ${kind} follows`);
|
|
2336
|
+
}
|
|
2337
|
+
else if (JSON.stringify(d) !== before)
|
|
2338
|
+
touched.push(`what ${kind} follows`);
|
|
2339
|
+
}
|
|
2340
|
+
return touched;
|
|
2341
|
+
}
|
|
2131
2342
|
setTidying(i, e) {
|
|
2132
2343
|
const spec = this.kind(i, e.kind);
|
|
2133
2344
|
if (!spec)
|
|
@@ -2166,6 +2377,26 @@ class Editor {
|
|
|
2166
2377
|
this.toolsMoved.add(`leave-alone-${e.kind} and allow-tidying-${e.kind} ${tidying === "never" ? "are gone" : "are offered"}`);
|
|
2167
2378
|
this.said.push(tidyingSentence(this.doc.kinds, e.kind, undefined, tidying, already));
|
|
2168
2379
|
}
|
|
2380
|
+
/**
|
|
2381
|
+
* WHAT THE APP SAYS OF SUGGESTIONS AS A WHOLE (Roadmap f-suggestion-votes):
|
|
2382
|
+
* `tidying.autoAcceptAt`, the votes for a suggestion at which the app's
|
|
2383
|
+
* own actor makes it; null takes it away, and a suggestion waits for
|
|
2384
|
+
* somebody who may decide. A `tidying` left saying nothing goes.
|
|
2385
|
+
*/
|
|
2386
|
+
setAppTidying(e) {
|
|
2387
|
+
const autoAcceptAt = e.autoAcceptAt;
|
|
2388
|
+
const was = this.doc.tidying?.autoAcceptAt;
|
|
2389
|
+
const tidying = { ...(this.doc.tidying ?? {}) };
|
|
2390
|
+
if (autoAcceptAt === null)
|
|
2391
|
+
delete tidying.autoAcceptAt;
|
|
2392
|
+
else
|
|
2393
|
+
tidying.autoAcceptAt = autoAcceptAt;
|
|
2394
|
+
if (Object.keys(tidying).length === 0)
|
|
2395
|
+
delete this.doc.tidying;
|
|
2396
|
+
else
|
|
2397
|
+
this.doc.tidying = tidying;
|
|
2398
|
+
this.said.push(appTidyingSentence(autoAcceptAt ?? undefined, was === (autoAcceptAt ?? undefined)));
|
|
2399
|
+
}
|
|
2169
2400
|
/** A grant or a sight as the document holds one, or undefined having said why not. */
|
|
2170
2401
|
policyPart(i, e, sight) {
|
|
2171
2402
|
const { op: _op, ...rest } = e;
|
|
@@ -2198,13 +2429,15 @@ class Editor {
|
|
|
2198
2429
|
return this.failAs("sight-field", i, `${key}.${n}`, `"${field}" is not a field of ${kinds.join(" or ")}`, `one of ${[...new Set(kinds.flatMap((kind) => Object.keys(this.doc.kinds[kind]?.fields ?? {})))].join(", ")}`);
|
|
2199
2430
|
}
|
|
2200
2431
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2432
|
+
for (const key of sight ? [] : ["mutations", "propose"]) {
|
|
2433
|
+
if (part[key] === undefined || part[key] === "*")
|
|
2434
|
+
continue;
|
|
2435
|
+
for (const [n, act] of part[key].entries()) {
|
|
2203
2436
|
const derived = derivedActOf(act);
|
|
2204
2437
|
if (this.doc.acts?.[act] || (derived && this.doc.kinds[derived.kind] && (this.doc.kinds[derived.kind].archives !== false || givenWhenNeverArchived(derived.act)) && (this.doc.kinds[derived.kind].tidying !== "never" || givenWhenNeverTidied(derived.act))) || givesRelationAct(act, this.doc.kinds))
|
|
2205
2438
|
continue;
|
|
2206
2439
|
const relationActs = relationActsOf(documentRelations(this.doc.kinds)).filter((name) => givesRelationAct(name, this.doc.kinds));
|
|
2207
|
-
return this.failAs("grant-act", i,
|
|
2440
|
+
return this.failAs("grant-act", i, `${key}.${n}`, `"${act}" is not an act this app has`, `one of ${[...Object.keys(this.doc.acts ?? {}), ...Object.keys(this.doc.kinds).flatMap(derivedActsOf), ...relationActs].join(", ")}`);
|
|
2208
2441
|
}
|
|
2209
2442
|
}
|
|
2210
2443
|
const said = [];
|
|
@@ -2245,10 +2478,10 @@ class Editor {
|
|
|
2245
2478
|
removePolicyPart(i, part, sight) {
|
|
2246
2479
|
const list = (sight ? this.doc.policy?.sees : this.doc.policy?.grants) ?? [];
|
|
2247
2480
|
const sameSet = (a, b) => a === b || (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((x) => b.includes(x)));
|
|
2248
|
-
const keys = sight ? ["own", "via"] : ["kinds", "self", "ownEnd", "describe"];
|
|
2481
|
+
const keys = sight ? ["own", "via"] : ["kinds", "self", "ownEnd", "propose", "describe"];
|
|
2249
2482
|
const matches = list.filter((one) => sameSet(one.roles, part.roles) &&
|
|
2250
2483
|
(sight ? sameSet(one.kinds, part.kinds) : sameSet(one.mutations, part.mutations)) &&
|
|
2251
|
-
keys.every((key) => part[key] === undefined || (typeof part[key] === "boolean" ? (one[key] ?? false) === part[key] : key === "kinds" ? sameSet(one[key] ?? "*", part[key]) : one[key] === part[key])));
|
|
2484
|
+
keys.every((key) => part[key] === undefined || (typeof part[key] === "boolean" ? (one[key] ?? false) === part[key] : key === "kinds" ? sameSet(one[key] ?? "*", part[key]) : key === "propose" ? sameSet(one[key], part[key]) : one[key] === part[key])));
|
|
2252
2485
|
const what = sight ? "sight" : "grant";
|
|
2253
2486
|
const say = (one) => (sight ? `${whoWords(one.roles)} ${sightWords(this.doc, one)}` : grantBody(this.doc, one));
|
|
2254
2487
|
if (matches.length === 0) {
|
|
@@ -2477,6 +2710,7 @@ class Editor {
|
|
|
2477
2710
|
* kind, or drew by it, goes, and `pages.first` with it when it named it.
|
|
2478
2711
|
*/
|
|
2479
2712
|
const swept = { t: "kind", from: kind, to: "\u0000" };
|
|
2713
|
+
gone.push(...this.followDerives(swept, true));
|
|
2480
2714
|
for (const [other, spec] of Object.entries(this.doc.kinds)) {
|
|
2481
2715
|
for (const [name, c] of Object.entries(spec.computed ?? {})) {
|
|
2482
2716
|
if (exprMentions(this.doc, typeof c === "string" ? c : c.expr, new Set([other]), swept))
|
|
@@ -2676,8 +2910,8 @@ class Editor {
|
|
|
2676
2910
|
const f = this.field(i, e.kind, e.field);
|
|
2677
2911
|
if (!f)
|
|
2678
2912
|
return;
|
|
2679
|
-
if (e.format === undefined && e.unit === undefined)
|
|
2680
|
-
return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration" for a number, "markdown" for a text field, or null for neither), a "unit" ("EUR", "kg", or null), or
|
|
2913
|
+
if (e.format === undefined && e.unit === undefined && e.places === undefined)
|
|
2914
|
+
return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration" for a number, "markdown" for a text field, or null for neither), a "unit" ("EUR", "kg", or null), "places" (0 for money in whole units, or null), or some of them');
|
|
2681
2915
|
if (f.type === "text") {
|
|
2682
2916
|
if (e.unit !== undefined && e.unit !== null)
|
|
2683
2917
|
return this.fail(i, "unit", refusedFormat("a unit", f.type));
|
|
@@ -2693,12 +2927,14 @@ class Editor {
|
|
|
2693
2927
|
if (!formatFits(typeof e.format === "string" ? e.format : "money", f.type))
|
|
2694
2928
|
return this.fail(i, "field", refusedFormat(typeof e.format === "string" ? e.format : "a unit", f.type));
|
|
2695
2929
|
const before = formatWords(f, this.doc.brand);
|
|
2696
|
-
for (const key of ["format", "unit"]) {
|
|
2930
|
+
for (const key of ["format", "unit", "places"]) {
|
|
2697
2931
|
if (e[key] === null)
|
|
2698
2932
|
delete f[key];
|
|
2699
2933
|
else if (e[key] !== undefined)
|
|
2700
2934
|
f[key] = e[key];
|
|
2701
2935
|
}
|
|
2936
|
+
if (f.places !== undefined && !isMoney(f))
|
|
2937
|
+
return this.fail(i, "places", `"places" is said of money, and ${e.kind}'s ${e.field} is ${formatWords(f, this.doc.brand) ?? "a plain number"}`, 'say "format": "money" with it, or leave "places" out');
|
|
2702
2938
|
const now = formatWords(f, this.doc.brand);
|
|
2703
2939
|
this.said.push(`${e.kind}'s ${e.field} is said as ${now ?? "a plain number"}${before ? `, where it was ${before.replace(/ — .*/, "")}` : ""}; its values are kept.`);
|
|
2704
2940
|
}
|
|
@@ -2710,19 +2946,21 @@ class Editor {
|
|
|
2710
2946
|
* its expression is left.
|
|
2711
2947
|
*/
|
|
2712
2948
|
setComputedFormat(i, e, worked) {
|
|
2713
|
-
if (e.format === undefined && e.unit === undefined)
|
|
2714
|
-
return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration", or null for none), a "unit" ("EUR", "kg", or null), or
|
|
2949
|
+
if (e.format === undefined && e.unit === undefined && e.places === undefined)
|
|
2950
|
+
return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration", or null for none), a "unit" ("EUR", "kg", or null), "places" (0 for money in whole units, or null), or some of them');
|
|
2715
2951
|
if (e.format === "markdown")
|
|
2716
2952
|
return this.fail(i, "format", `${e.field} is worked out, not written, so it cannot be Markdown; a computed field is said as "money", "percent" or "duration"`);
|
|
2717
2953
|
const spec = this.doc.kinds[e.kind];
|
|
2718
2954
|
const next = typeof worked === "string" ? { expr: worked } : { ...worked };
|
|
2719
2955
|
const before = formatWords(next, this.doc.brand);
|
|
2720
|
-
for (const key of ["format", "unit"]) {
|
|
2956
|
+
for (const key of ["format", "unit", "places"]) {
|
|
2721
2957
|
if (e[key] === null)
|
|
2722
2958
|
delete next[key];
|
|
2723
2959
|
else if (e[key] !== undefined)
|
|
2724
2960
|
next[key] = e[key];
|
|
2725
2961
|
}
|
|
2962
|
+
if (next.places !== undefined && !isMoney(next))
|
|
2963
|
+
return this.fail(i, "places", `"places" is said of money, and ${e.kind}'s ${e.field} is ${formatWords(next, this.doc.brand) ?? "a plain number"}`, 'say "format": "money" with it, or leave "places" out');
|
|
2726
2964
|
spec.computed[e.field] = Object.keys(next).length === 1 ? next.expr : next;
|
|
2727
2965
|
const now = formatWords(next, this.doc.brand);
|
|
2728
2966
|
this.said.push(`${e.kind}'s ${e.field} is worked out as before and said as ${now ?? "a plain number"}${before ? `, where it was ${before.replace(/ — .*/, "")}` : ""}.`);
|
|
@@ -3065,6 +3303,8 @@ class Editor {
|
|
|
3065
3303
|
const prose = r.t === "field" ? proseSwap(r.from, r.to) : r.t === "kind" ? proseSwap(r.from, r.to) : undefined;
|
|
3066
3304
|
const exprAt = (source, ctx) => (source === undefined ? undefined : rewriteExpr(doc, source, ctx, r));
|
|
3067
3305
|
const tmplAt = (source, ctx, words) => (source === undefined ? undefined : rewriteTemplate(doc, source, ctx, r, words));
|
|
3306
|
+
// What a kind follows (Roadmap f-keep-linked): its kind, relation and values follow the rename.
|
|
3307
|
+
touched.push(...this.followDerives(r, false));
|
|
3068
3308
|
// Kinds: templates, the lifecycle, where relations lead.
|
|
3069
3309
|
for (const [kind, spec] of Object.entries(doc.kinds)) {
|
|
3070
3310
|
const here = new Set([kind]);
|
|
@@ -3373,6 +3613,7 @@ class Editor {
|
|
|
3373
3613
|
// A kind's list ordered or grouped by it lets that go (a stored field removed, a computed one no longer worked out).
|
|
3374
3614
|
if (r.t === "field")
|
|
3375
3615
|
gone.push(...this.listsFollow(r.kind, r.from, undefined));
|
|
3616
|
+
gone.push(...this.followDerives(r, true));
|
|
3376
3617
|
for (const [kind, spec] of Object.entries(doc.kinds)) {
|
|
3377
3618
|
for (const key of ["label", "describe"]) {
|
|
3378
3619
|
if (templateMentions(doc, spec[key], new Set([kind]), r)) {
|