@graview/core 0.1.60 → 0.1.62
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/README.md +23 -1
- package/dist/app-places.d.ts.map +1 -1
- package/dist/app-places.js +2 -2
- package/dist/app-places.js.map +1 -1
- package/dist/app.d.ts +10 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js.map +1 -1
- package/dist/arrangement.d.ts +2 -0
- package/dist/arrangement.d.ts.map +1 -1
- package/dist/arrangement.js +14 -0
- package/dist/arrangement.js.map +1 -1
- package/dist/capabilities.js +1 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +2 -1
- package/dist/catalog.js.map +1 -1
- package/dist/cli/check/context.d.ts +5 -0
- package/dist/cli/check/context.d.ts.map +1 -1
- package/dist/cli/check/context.js.map +1 -1
- package/dist/cli/check/face.d.ts +14 -0
- package/dist/cli/check/face.d.ts.map +1 -0
- package/dist/cli/check/face.js +161 -0
- package/dist/cli/check/face.js.map +1 -0
- package/dist/cli/check/hidden-reads.d.ts.map +1 -1
- package/dist/cli/check/hidden-reads.js +26 -0
- package/dist/cli/check/hidden-reads.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/tidying.d.ts +14 -0
- package/dist/cli/check/tidying.d.ts.map +1 -0
- package/dist/cli/check/tidying.js +24 -0
- package/dist/cli/check/tidying.js.map +1 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +4 -4
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/describe.d.ts.map +1 -1
- package/dist/cli/describe.js +14 -1
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/docs.d.ts.map +1 -1
- package/dist/cli/docs.js +5 -3
- package/dist/cli/docs.js.map +1 -1
- package/dist/cli/document-schema.d.ts.map +1 -1
- package/dist/cli/document-schema.js +8 -2
- package/dist/cli/document-schema.js.map +1 -1
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +15 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +2 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/compose.d.ts +14 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +16 -1
- package/dist/compose.js.map +1 -1
- package/dist/css/sanitize.d.ts +125 -0
- package/dist/css/sanitize.d.ts.map +1 -0
- package/dist/css/sanitize.js +989 -0
- package/dist/css/sanitize.js.map +1 -0
- package/dist/css/tables.d.ts +55 -0
- package/dist/css/tables.d.ts.map +1 -0
- package/dist/css/tables.js +137 -0
- package/dist/css/tables.js.map +1 -0
- package/dist/css.d.ts +20 -0
- package/dist/css.d.ts.map +1 -0
- package/dist/css.js +19 -0
- package/dist/css.js.map +1 -0
- package/dist/days.d.ts +15 -0
- package/dist/days.d.ts.map +1 -1
- package/dist/days.js +21 -0
- package/dist/days.js.map +1 -1
- package/dist/describe.d.ts +1 -1
- package/dist/describe.d.ts.map +1 -1
- package/dist/document/act-findings.d.ts.map +1 -1
- package/dist/document/act-findings.js +80 -1
- package/dist/document/act-findings.js.map +1 -1
- package/dist/document/blocks.d.ts.map +1 -1
- package/dist/document/blocks.js +2 -1
- package/dist/document/blocks.js.map +1 -1
- package/dist/document/brand-check.d.ts +15 -1
- package/dist/document/brand-check.d.ts.map +1 -1
- package/dist/document/brand-check.js +80 -0
- package/dist/document/brand-check.js.map +1 -1
- package/dist/document/brand.d.ts.map +1 -1
- package/dist/document/brand.js +2 -0
- package/dist/document/brand.js.map +1 -1
- package/dist/document/compile-checked.d.ts.map +1 -1
- package/dist/document/compile-checked.js +4 -3
- package/dist/document/compile-checked.js.map +1 -1
- package/dist/document/compile.d.ts.map +1 -1
- package/dist/document/compile.js +54 -9
- package/dist/document/compile.js.map +1 -1
- package/dist/document/compiled.d.ts +18 -3
- package/dist/document/compiled.d.ts.map +1 -1
- package/dist/document/compiled.js +91 -10
- package/dist/document/compiled.js.map +1 -1
- package/dist/document/connection-findings.d.ts.map +1 -1
- package/dist/document/connection-findings.js +219 -19
- package/dist/document/connection-findings.js.map +1 -1
- package/dist/document/connection-spec.d.ts +7 -0
- package/dist/document/connection-spec.d.ts.map +1 -1
- package/dist/document/connection-spec.js +26 -2
- package/dist/document/connection-spec.js.map +1 -1
- package/dist/document/covers.d.ts +48 -0
- package/dist/document/covers.d.ts.map +1 -0
- package/dist/document/covers.js +90 -0
- package/dist/document/covers.js.map +1 -0
- package/dist/document/describe-place.d.ts +3 -0
- package/dist/document/describe-place.d.ts.map +1 -1
- package/dist/document/describe-place.js +11 -0
- package/dist/document/describe-place.js.map +1 -1
- package/dist/document/described.d.ts +28 -0
- package/dist/document/described.d.ts.map +1 -1
- package/dist/document/diff.js +10 -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 +186 -18
- package/dist/document/edit.js.map +1 -1
- package/dist/document/expr/evaluate.d.ts +10 -1
- package/dist/document/expr/evaluate.d.ts.map +1 -1
- package/dist/document/expr/evaluate.js +110 -2
- package/dist/document/expr/evaluate.js.map +1 -1
- package/dist/document/expr/index.d.ts +1 -0
- package/dist/document/expr/index.d.ts.map +1 -1
- package/dist/document/expr/index.js +2 -0
- package/dist/document/expr/index.js.map +1 -1
- package/dist/document/expr/typing.d.ts +24 -0
- package/dist/document/expr/typing.d.ts.map +1 -0
- package/dist/document/expr/typing.js +219 -0
- package/dist/document/expr/typing.js.map +1 -0
- package/dist/document/expr/when.d.ts +52 -0
- package/dist/document/expr/when.d.ts.map +1 -0
- package/dist/document/expr/when.js +215 -0
- package/dist/document/expr/when.js.map +1 -0
- package/dist/document/expr-type-findings.d.ts +5 -0
- package/dist/document/expr-type-findings.d.ts.map +1 -0
- package/dist/document/expr-type-findings.js +90 -0
- package/dist/document/expr-type-findings.js.map +1 -0
- package/dist/document/field-spec.d.ts.map +1 -1
- package/dist/document/field-spec.js +5 -0
- package/dist/document/field-spec.js.map +1 -1
- package/dist/document/findings.d.ts +33 -2
- package/dist/document/findings.d.ts.map +1 -1
- package/dist/document/findings.js +14 -17
- package/dist/document/findings.js.map +1 -1
- package/dist/document/index.d.ts +6 -3
- package/dist/document/index.d.ts.map +1 -1
- package/dist/document/index.js +5 -2
- package/dist/document/index.js.map +1 -1
- package/dist/document/linked-findings.js +1 -1
- package/dist/document/linked-findings.js.map +1 -1
- package/dist/document/migrate.d.ts.map +1 -1
- package/dist/document/migrate.js +10 -4
- package/dist/document/migrate.js.map +1 -1
- package/dist/document/rule-line.d.ts +7 -3
- package/dist/document/rule-line.d.ts.map +1 -1
- package/dist/document/rule-line.js +17 -2
- package/dist/document/rule-line.js.map +1 -1
- package/dist/document/rule-reads.d.ts.map +1 -1
- package/dist/document/rule-reads.js +3 -1
- package/dist/document/rule-reads.js.map +1 -1
- package/dist/document/rules.d.ts +30 -0
- package/dist/document/rules.d.ts.map +1 -1
- package/dist/document/rules.js +74 -0
- package/dist/document/rules.js.map +1 -1
- package/dist/document/schema.d.ts +80 -2
- package/dist/document/schema.d.ts.map +1 -1
- package/dist/document/schema.js +90 -4
- package/dist/document/schema.js.map +1 -1
- package/dist/document/to-document.d.ts.map +1 -1
- package/dist/document/to-document.js +3 -1
- package/dist/document/to-document.js.map +1 -1
- package/dist/document.schema.json +304 -5
- package/dist/formats.d.ts +21 -13
- package/dist/formats.d.ts.map +1 -1
- package/dist/formats.js +21 -13
- package/dist/formats.js.map +1 -1
- package/dist/import/coerce.d.ts.map +1 -1
- package/dist/import/coerce.js +16 -4
- package/dist/import/coerce.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/invariants/types.d.ts +7 -2
- package/dist/invariants/types.d.ts.map +1 -1
- package/dist/kind-list.d.ts +1 -0
- package/dist/kind-list.d.ts.map +1 -1
- package/dist/kind-list.js +2 -1
- package/dist/kind-list.js.map +1 -1
- package/dist/lines.d.ts.map +1 -1
- package/dist/lines.js +6 -2
- package/dist/lines.js.map +1 -1
- package/dist/mutations/form.d.ts +2 -0
- package/dist/mutations/form.d.ts.map +1 -1
- package/dist/mutations/form.js +6 -3
- package/dist/mutations/form.js.map +1 -1
- package/dist/mutations/types.d.ts +9 -0
- package/dist/mutations/types.d.ts.map +1 -1
- package/dist/mutations/vote-suggestion.d.ts +67 -0
- package/dist/mutations/vote-suggestion.d.ts.map +1 -0
- package/dist/mutations/vote-suggestion.js +36 -0
- package/dist/mutations/vote-suggestion.js.map +1 -0
- package/dist/navigate.d.ts +7 -3
- package/dist/navigate.d.ts.map +1 -1
- package/dist/navigate.js +7 -3
- package/dist/navigate.js.map +1 -1
- package/dist/ops/lineage.d.ts +11 -1
- package/dist/ops/lineage.d.ts.map +1 -1
- package/dist/ops/lineage.js +6 -2
- package/dist/ops/lineage.js.map +1 -1
- package/dist/ops/suggestions.d.ts +33 -1
- package/dist/ops/suggestions.d.ts.map +1 -1
- package/dist/ops/suggestions.js +44 -2
- package/dist/ops/suggestions.js.map +1 -1
- package/dist/ops/types.d.ts +13 -0
- package/dist/ops/types.d.ts.map +1 -1
- package/dist/ops/types.js.map +1 -1
- package/dist/place-names.d.ts +42 -0
- package/dist/place-names.d.ts.map +1 -0
- package/dist/place-names.js +43 -0
- package/dist/place-names.js.map +1 -0
- package/dist/places.d.ts +2 -0
- package/dist/places.d.ts.map +1 -1
- package/dist/places.js +2 -0
- package/dist/places.js.map +1 -1
- package/dist/scaffold/agents.d.ts +9 -1
- package/dist/scaffold/agents.d.ts.map +1 -1
- package/dist/scaffold/agents.js +33 -2
- package/dist/scaffold/agents.js.map +1 -1
- package/dist/scaffold/project.js +1 -1
- package/dist/schema/choice-words.d.ts.map +1 -1
- package/dist/schema/choice-words.js +3 -0
- package/dist/schema/choice-words.js.map +1 -1
- package/dist/starting-document.d.ts +73 -0
- package/dist/starting-document.d.ts.map +1 -0
- package/dist/starting-document.js +645 -0
- package/dist/starting-document.js.map +1 -0
- package/dist/store.d.ts +50 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +167 -27
- package/dist/store.js.map +1 -1
- package/dist/sync/connection.d.ts +20 -1
- package/dist/sync/connection.d.ts.map +1 -1
- package/dist/sync/connection.js +198 -34
- package/dist/sync/connection.js.map +1 -1
- package/dist/sync/engine.d.ts +13 -4
- package/dist/sync/engine.d.ts.map +1 -1
- package/dist/sync/engine.js +10 -6
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/google-calendar.d.ts +8 -0
- package/dist/sync/google-calendar.d.ts.map +1 -1
- package/dist/sync/google-calendar.js +16 -4
- package/dist/sync/google-calendar.js.map +1 -1
- package/dist/sync/guests.d.ts +44 -0
- package/dist/sync/guests.d.ts.map +1 -0
- package/dist/sync/guests.js +57 -0
- package/dist/sync/guests.js.map +1 -0
- package/dist/sync/notion-types.d.ts +7 -0
- package/dist/sync/notion-types.d.ts.map +1 -1
- package/dist/sync/notion-types.js +6 -1
- package/dist/sync/notion-types.js.map +1 -1
- package/dist/sync/recurrence.d.ts +41 -0
- package/dist/sync/recurrence.d.ts.map +1 -0
- package/dist/sync/recurrence.js +192 -0
- package/dist/sync/recurrence.js.map +1 -0
- package/dist/sync/types.d.ts +9 -0
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/theme/contrast.d.ts +2 -0
- package/dist/theme/contrast.d.ts.map +1 -1
- package/dist/theme/contrast.js +4 -0
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/ground.d.ts +1 -2
- package/dist/theme/ground.d.ts.map +1 -1
- package/dist/theme/ground.js +2 -5
- package/dist/theme/ground.js.map +1 -1
- package/dist/theme/marks.d.ts +14 -2
- package/dist/theme/marks.d.ts.map +1 -1
- package/dist/theme/marks.js +20 -3
- package/dist/theme/marks.js.map +1 -1
- package/dist/theme/types.d.ts +22 -0
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/theme/types.js.map +1 -1
- package/dist/tidying.d.ts +30 -4
- package/dist/tidying.d.ts.map +1 -1
- package/dist/tidying.js +110 -5
- package/dist/tidying.js.map +1 -1
- package/dist/trait-vocabulary.d.ts +17 -0
- package/dist/trait-vocabulary.d.ts.map +1 -0
- package/dist/trait-vocabulary.js +16 -0
- package/dist/trait-vocabulary.js.map +1 -0
- package/dist/traits.d.ts +79 -0
- package/dist/traits.d.ts.map +1 -0
- package/dist/traits.js +468 -0
- package/dist/traits.js.map +1 -0
- package/dist/typed.d.ts +4 -0
- package/dist/typed.d.ts.map +1 -1
- package/dist/typed.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -1
package/dist/document/edit.js
CHANGED
|
@@ -18,6 +18,7 @@ import { jobFindings, jobWorldOfDocument } from "../job-findings.js";
|
|
|
18
18
|
import { JobSpec } from "./purpose-spec.js";
|
|
19
19
|
import { aliasWord, documentAliasFindings } from "../alias-findings.js";
|
|
20
20
|
import { humanizeField, withArticle } from "../schema/define-node.js";
|
|
21
|
+
import { ConnectionSpec } from "./connection-spec.js";
|
|
21
22
|
import { linkedRelationFindings } from "./linked-relation-findings.js";
|
|
22
23
|
import { linkedRelationOfSpec, reachesAnotherApp } from "../schema/linked.js";
|
|
23
24
|
import { ActSpec, ALIAS, MOST_ALIASES, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
|
|
@@ -28,7 +29,7 @@ import { documentListFields, kindListFindings, kindListWords, listGroup, listSor
|
|
|
28
29
|
import { computedOf, validateComputed } from "./computed.js";
|
|
29
30
|
import { parseTemplate, templateBraces, TemplateError } from "./template-parse.js";
|
|
30
31
|
import { validateViews, VIEW_SLOTS } from "./views.js";
|
|
31
|
-
import { brandFindings, emojiIconSentence, groundWords, subtitleSentence } from "./brand-check.js";
|
|
32
|
+
import { brandFindings, emojiIconSentence, groundWords, splashSentence, subtitleSentence, viewsCssSentence, viewsMotionSentence } from "./brand-check.js";
|
|
32
33
|
import { darkLogoSentence, logoParts } from "./logo.js";
|
|
33
34
|
import { emojiOf } from "../theme/marks.js";
|
|
34
35
|
import { canonicalize } from "./canonical.js";
|
|
@@ -93,6 +94,7 @@ export const EDIT_OPS = [
|
|
|
93
94
|
"remove-job",
|
|
94
95
|
"add-alias",
|
|
95
96
|
"set-aliases",
|
|
97
|
+
"set-connection",
|
|
96
98
|
];
|
|
97
99
|
/** Words the rule language keeps: a field or relation renamed to one could not be named in a rule. */
|
|
98
100
|
const KEYWORDS = new Set(["and", "or", "not", "in", "where", "true", "false", "null"]);
|
|
@@ -122,7 +124,7 @@ function buildShapes() {
|
|
|
122
124
|
"remove-kind": z.object({ op: z.literal("remove-kind"), kind: kindName }).strict(),
|
|
123
125
|
"add-field": z.object({ op: z.literal("add-field"), kind: kindName, field: fieldName, fill: z.unknown().optional(), spec: z.record(z.string(), z.unknown()).optional() }).passthrough(),
|
|
124
126
|
"rename-field": z.object({ op: z.literal("rename-field"), kind: kindName, field: fieldName, to: fieldName }).strict(),
|
|
125
|
-
"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(), of: z.enum(["string", "number", "date", "image", "video", "file"]).optional(), format: z.enum(["money", "percent", "duration", "markdown"]).optional() }).strict(),
|
|
127
|
+
"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(),
|
|
126
128
|
"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(),
|
|
127
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(),
|
|
128
130
|
"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(),
|
|
@@ -165,6 +167,15 @@ function buildShapes() {
|
|
|
165
167
|
scheme: z.union([z.enum(["light", "dark", "auto"]), z.null()]).optional(),
|
|
166
168
|
// 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.
|
|
167
169
|
subtitle: z.union([z.boolean(), z.null()]).optional(),
|
|
170
|
+
// 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(),
|
|
172
|
+
// 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
|
+
views: z
|
|
174
|
+
.union([
|
|
175
|
+
z.object({ css: z.union([z.string().min(1), z.null()]).optional(), motion: z.union([z.enum(["none", "fade"]), z.null()]).optional() }).strict(),
|
|
176
|
+
z.null(),
|
|
177
|
+
])
|
|
178
|
+
.optional(),
|
|
168
179
|
})
|
|
169
180
|
.strict(),
|
|
170
181
|
"set-name": z.object({ op: z.literal("set-name"), name: z.string().min(1).max(80) }).strict(),
|
|
@@ -269,6 +280,8 @@ function buildShapes() {
|
|
|
269
280
|
"add-alias": z.object({ op: z.literal("add-alias"), kind: kindName, field: fieldName.optional(), alias: z.string().min(1).max(40) }).strict(),
|
|
270
281
|
// All of a kind's or a field's other words at once; null or [] takes them away.
|
|
271
282
|
"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
|
+
// A connection declared whole, by its name: set where there was none, or replaced (FR-229); null takes it away.
|
|
284
|
+
"set-connection": z.object({ op: z.literal("set-connection"), name: kindName, connection: z.union([z.record(z.string(), z.unknown()), z.null()]) }).strict(),
|
|
272
285
|
};
|
|
273
286
|
}
|
|
274
287
|
let shapes;
|
|
@@ -276,6 +289,13 @@ const opShapes = () => (shapes ??= buildShapes());
|
|
|
276
289
|
const NONE = new Set();
|
|
277
290
|
const clone = (v) => structuredClone(v);
|
|
278
291
|
const asArray = (v) => [v ?? []].flat();
|
|
292
|
+
/** The first of a choice's default that is none of its options — a list of them where the choice takes several (FR-233) — or undefined when every one is. */
|
|
293
|
+
function defaultOutsideOptions(f, given) {
|
|
294
|
+
const options = f.options ?? [];
|
|
295
|
+
if (!f.multiple)
|
|
296
|
+
return Array.isArray(given) || !options.includes(String(given)) ? given : undefined;
|
|
297
|
+
return (Array.isArray(given) ? given : [given]).find((one) => !options.includes(String(one)));
|
|
298
|
+
}
|
|
279
299
|
const kebab = (camel) => camel.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
280
300
|
const words = (name) => kebab(name).replace(/-/g, " ");
|
|
281
301
|
/** Another app's kinds as people say them: "one house in the app @nick/house", "houses or flats in the app @nick/house". Their plural is that app's to say; this one says the plain one. */
|
|
@@ -539,6 +559,11 @@ function setTargets(act, doc) {
|
|
|
539
559
|
const end = doc && relation ? farEnd(doc.kinds, relation, asArray(act.on)) : undefined;
|
|
540
560
|
out.push({ set: act.setsOther, kinds: end && end.kinds !== "*" ? new Set(end.kinds) : "*", where: "setsOther" });
|
|
541
561
|
}
|
|
562
|
+
// What it carries to the records linked to its subject, by relation: the kinds at each relation's far end.
|
|
563
|
+
for (const [relation, set] of Object.entries(act.setsLinked ?? {})) {
|
|
564
|
+
const end = doc ? farEnd(doc.kinds, relation, asArray(act.on)) : undefined;
|
|
565
|
+
out.push({ set, kinds: end && end.kinds !== "*" ? new Set(end.kinds) : "*", where: "setsLinked", relation });
|
|
566
|
+
}
|
|
542
567
|
(act.effects ?? []).forEach((e, index) => {
|
|
543
568
|
if (e.create)
|
|
544
569
|
out.push({ set: e.set ?? {}, kinds: new Set([e.create]), where: "create", index });
|
|
@@ -591,10 +616,18 @@ class Editor {
|
|
|
591
616
|
const named = Object.keys(e).filter((key) => key !== "op" && e[key] !== undefined);
|
|
592
617
|
if (named.length === 0)
|
|
593
618
|
return this.fail(i, "", "a set-brand edit names at least one key of the brand to set, or null to clear one", '{"op": "set-brand", "accent": "#0f6e5c"}');
|
|
594
|
-
const emptyPart = ["typography", "shape", "accents"].find((key) => isObject(e[key]) && Object.keys(e[key]).length === 0);
|
|
619
|
+
const emptyPart = ["typography", "shape", "accents", "views"].find((key) => isObject(e[key]) && Object.keys(e[key]).length === 0);
|
|
595
620
|
if (emptyPart)
|
|
596
621
|
return this.fail(i, emptyPart, `${emptyPart} names no part to set; name one, or give null to clear all of it`);
|
|
597
|
-
const judged = brandFindings({
|
|
622
|
+
const judged = brandFindings({
|
|
623
|
+
...(typeof e.timeZone === "string" ? { timeZone: e.timeZone } : {}),
|
|
624
|
+
...(isObject(e.splash) ? { splash: e.splash } : {}),
|
|
625
|
+
...(typeof e.logo === "string" || isObject(e.logo) ? { logo: e.logo } : {}),
|
|
626
|
+
...(typeof e.favicon === "string" || isObject(e.favicon) ? { favicon: e.favicon } : {}),
|
|
627
|
+
...(isObject(e.typography) ? { typography: Object.fromEntries(Object.entries(e.typography).filter(([, v]) => typeof v === "string")) } : {}),
|
|
628
|
+
// The stylesheet every view gets is judged as a page draws it (Roadmap f-app-wide-view-theme): an edit that would leave part of it undrawn is refused, saying which part.
|
|
629
|
+
...(isObject(e.views) && typeof e.views["css"] === "string" ? { views: { css: e.views["css"] } } : {}),
|
|
630
|
+
}, { at, ...(this.options.fonts ? { fonts: this.options.fonts } : {}) });
|
|
598
631
|
if (judged.length > 0)
|
|
599
632
|
return void this.findings.push(...judged);
|
|
600
633
|
if (e.accent === null) {
|
|
@@ -768,6 +801,45 @@ class Editor {
|
|
|
768
801
|
delete brand["subtitle"];
|
|
769
802
|
this.said.push(subtitleSentence(e.subtitle !== false));
|
|
770
803
|
}
|
|
804
|
+
// The opening screen is set whole or cleared: its keys are few, and half of one is nothing a page could draw.
|
|
805
|
+
if (e.splash !== undefined) {
|
|
806
|
+
if (e.splash === null)
|
|
807
|
+
delete brand["splash"];
|
|
808
|
+
else
|
|
809
|
+
brand["splash"] = e.splash;
|
|
810
|
+
if (!(e.splash === null && !isObject(this.doc.brand?.splash)))
|
|
811
|
+
this.said.push(splashSentence(e.splash));
|
|
812
|
+
}
|
|
813
|
+
/* What every view inherits (Roadmap f-app-wide-view-theme): the stylesheet and the fade, each set or cleared on its own, null clearing both. */
|
|
814
|
+
if (e.views === null) {
|
|
815
|
+
const had = brand["views"] ?? {};
|
|
816
|
+
if (had["css"] !== undefined || had["motion"] === undefined)
|
|
817
|
+
this.said.push(viewsCssSentence(false));
|
|
818
|
+
if (had["motion"] === "fade")
|
|
819
|
+
this.said.push(viewsMotionSentence(undefined));
|
|
820
|
+
delete brand["views"];
|
|
821
|
+
}
|
|
822
|
+
else if (isObject(e.views)) {
|
|
823
|
+
const into = { ...(brand["views"] ?? {}) };
|
|
824
|
+
if (e.views["css"] !== undefined) {
|
|
825
|
+
if (e.views["css"] === null)
|
|
826
|
+
delete into["css"];
|
|
827
|
+
else
|
|
828
|
+
into["css"] = e.views["css"];
|
|
829
|
+
this.said.push(viewsCssSentence(e.views["css"] !== null));
|
|
830
|
+
}
|
|
831
|
+
if (e.views["motion"] !== undefined) {
|
|
832
|
+
if (e.views["motion"] === "fade")
|
|
833
|
+
into["motion"] = "fade";
|
|
834
|
+
else
|
|
835
|
+
delete into["motion"];
|
|
836
|
+
this.said.push(viewsMotionSentence(e.views["motion"] === "fade" ? "fade" : undefined));
|
|
837
|
+
}
|
|
838
|
+
if (Object.keys(into).length === 0)
|
|
839
|
+
delete brand["views"];
|
|
840
|
+
else
|
|
841
|
+
brand["views"] = into;
|
|
842
|
+
}
|
|
771
843
|
if (Object.keys(brand).length === 0)
|
|
772
844
|
delete this.doc.brand;
|
|
773
845
|
else
|
|
@@ -860,8 +932,8 @@ class Editor {
|
|
|
860
932
|
this.said.push(`${e.kind}'s ${e.field} has no default now.`);
|
|
861
933
|
}
|
|
862
934
|
else {
|
|
863
|
-
if (f.type === "enum" &&
|
|
864
|
-
return this.fail(i, "default", `"${String(e.default)}" is not one of ${e.field}'s options (${(f.options ?? []).join(", ")})`);
|
|
935
|
+
if (f.type === "enum" && defaultOutsideOptions(f, e.default) !== undefined)
|
|
936
|
+
return this.fail(i, "default", `"${String(defaultOutsideOptions(f, e.default))}" is not one of ${e.field}'s options (${(f.options ?? []).join(", ")})`);
|
|
865
937
|
const outside = outsideRange(e.default, f, e.field);
|
|
866
938
|
if (outside)
|
|
867
939
|
return this.fail(i, "default", outside);
|
|
@@ -1030,8 +1102,37 @@ class Editor {
|
|
|
1030
1102
|
return this.setAliases(i, e.kind, e.field, [...this.aliasesOf(e.kind, e.field), e.alias], e.alias);
|
|
1031
1103
|
case "set-aliases":
|
|
1032
1104
|
return this.setAliases(i, e.kind, e.field, e.aliases ?? []);
|
|
1105
|
+
case "set-connection":
|
|
1106
|
+
return this.setConnection(i, e.name, e.connection);
|
|
1033
1107
|
}
|
|
1034
1108
|
}
|
|
1109
|
+
// ── a connection, declared whole (FR-229) ──
|
|
1110
|
+
/**
|
|
1111
|
+
* A CONNECTION SET BY NAME: its shape is judged here, as the document's
|
|
1112
|
+
* is; what it says of the kind and the fields it maps, and the acts an
|
|
1113
|
+
* inbound change lands through, the checker judges of the document it
|
|
1114
|
+
* leaves (`connection-findings.ts`). Null takes the connection away.
|
|
1115
|
+
*/
|
|
1116
|
+
setConnection(i, name, connection) {
|
|
1117
|
+
if (connection === null) {
|
|
1118
|
+
if (!this.doc.connections?.[name])
|
|
1119
|
+
return this.fail(i, "name", `there is no connection called "${name}"${this.doc.connections ? `; there ${Object.keys(this.doc.connections).length === 1 ? "is" : "are"} ${Object.keys(this.doc.connections).join(", ")}` : ""}`);
|
|
1120
|
+
delete this.doc.connections[name];
|
|
1121
|
+
if (Object.keys(this.doc.connections).length === 0)
|
|
1122
|
+
delete this.doc.connections;
|
|
1123
|
+
this.said.push(`The connection ${name} is gone: nothing keeps its records in step now.`);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
const parsed = ConnectionSpec.safeParse(connection);
|
|
1127
|
+
if (!parsed.success) {
|
|
1128
|
+
this.refuse(i, parsed.error.issues.map((issue) => ({ ...issue, path: ["connection", ...issue.path] })), "a connection");
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const was = this.doc.connections?.[name] !== undefined;
|
|
1132
|
+
(this.doc.connections ??= {})[name] = parsed.data;
|
|
1133
|
+
const system = parsed.data.system === "notion" ? "a Notion database" : "a Google calendar";
|
|
1134
|
+
this.said.push(`The connection ${name} ${was ? "now keeps" : "keeps"} ${pluralOf(this.doc.kinds, parsed.data.kind)} in step with ${system}${parsed.data.direction === "read" ? ", which owns them" : ", both ways"}.`);
|
|
1135
|
+
}
|
|
1035
1136
|
// ── other words for a kind or a field (Roadmap f-learned-suggestions) ──
|
|
1036
1137
|
aliasesOf(kind, field) {
|
|
1037
1138
|
const spec = this.doc.kinds[kind];
|
|
@@ -1361,9 +1462,10 @@ class Editor {
|
|
|
1361
1462
|
return (this.fail(i, `bindings.${kind}.column`, `the board does not say which field of ${kind} its columns are`, example), false);
|
|
1362
1463
|
if (!(field in fields))
|
|
1363
1464
|
return (this.fail(i, `bindings.${kind}.column`, `${kind} has no field "${field}"; it has ${Object.keys(fields).join(", ")}`), false);
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1465
|
+
// A choice that takes several of its options (FR-233) has no columns: a record would stand in several.
|
|
1466
|
+
if (fields[field]["type"] !== "enum" || fields[field]["multiple"] === true) {
|
|
1467
|
+
const choices = Object.keys(fields).filter((one) => fields[one]["type"] === "enum" && fields[one]["multiple"] !== true);
|
|
1468
|
+
return (this.fail(i, `bindings.${kind}.column`, `${kind}'s ${field} is ${fields[field]["multiple"] === true ? "a choice of several, so a record would stand in more than one column" : "not a choice, so it has no columns"}`, choices.length > 0 ? `bind one of ${choices.join(", ")}` : `give ${kind} a choice field first: add-field with "type": "enum" and its "options", in the order the columns go`), false);
|
|
1367
1469
|
}
|
|
1368
1470
|
const others = Object.keys(roles).filter((role) => role !== "column");
|
|
1369
1471
|
if (others.length > 0)
|
|
@@ -2237,6 +2339,12 @@ class Editor {
|
|
|
2237
2339
|
spec.plural = e.plural;
|
|
2238
2340
|
else if (spec.plural && (spec.plural === `${words(from)}s` || spec.plural === `${from}s`))
|
|
2239
2341
|
spec.plural = `${words(to)}s`;
|
|
2342
|
+
// Its hue follows it.
|
|
2343
|
+
if (isObject(this.doc.brand?.accents) && from in this.doc.brand.accents && !(to in this.doc.brand.accents)) {
|
|
2344
|
+
this.doc.brand.accents[to] = this.doc.brand.accents[from];
|
|
2345
|
+
delete this.doc.brand.accents[from];
|
|
2346
|
+
touched.push("its hue");
|
|
2347
|
+
}
|
|
2240
2348
|
// Its list's filters, order and grouping follow it.
|
|
2241
2349
|
const lists = this.doc.pages?.lists;
|
|
2242
2350
|
if (lists?.[from] !== undefined && lists[to] === undefined) {
|
|
@@ -2288,6 +2396,7 @@ class Editor {
|
|
|
2288
2396
|
gone.push(what);
|
|
2289
2397
|
}
|
|
2290
2398
|
const removedPlural = this.doc.kinds[kind].plural ?? `${words(kind)}s`;
|
|
2399
|
+
const people = this.doc.kinds[kind].people === true;
|
|
2291
2400
|
// Its own relations' connect and disconnect go with it.
|
|
2292
2401
|
const ownRelationActs = relationActsOf(documentRelations({ [kind]: this.doc.kinds[kind] }));
|
|
2293
2402
|
delete this.doc.kinds[kind];
|
|
@@ -2306,17 +2415,27 @@ class Editor {
|
|
|
2306
2415
|
}
|
|
2307
2416
|
}
|
|
2308
2417
|
}
|
|
2418
|
+
// With the people kind gone, an act on the acting person's own record (`$me`) has no record to reach: it goes too.
|
|
2309
2419
|
for (const [name, act] of Object.entries(this.doc.acts ?? {})) {
|
|
2310
2420
|
const on = asArray(act.on);
|
|
2311
|
-
if (act.creates === kind || (act.effects ?? []).some((x) => x.create === kind) || (on.includes(kind) && on.length === 1)) {
|
|
2421
|
+
if (act.creates === kind || (act.effects ?? []).some((x) => x.create === kind) || (on.includes(kind) && on.length === 1) || (people && JSON.stringify(act).includes('"$me"'))) {
|
|
2312
2422
|
gone.push(titled("act", name, act));
|
|
2313
2423
|
this.removeActQuietly(name);
|
|
2314
2424
|
}
|
|
2315
2425
|
else if (on.includes(kind))
|
|
2316
2426
|
act.on = on.filter((k) => k !== kind);
|
|
2317
2427
|
}
|
|
2428
|
+
// Its hue goes with it: an accent for a kind nobody declares is refused by the checker.
|
|
2429
|
+
if (isObject(this.doc.brand?.accents) && kind in this.doc.brand.accents) {
|
|
2430
|
+
delete this.doc.brand.accents[kind];
|
|
2431
|
+
if (Object.keys(this.doc.brand.accents).length === 0)
|
|
2432
|
+
delete this.doc.brand.accents;
|
|
2433
|
+
if (Object.keys(this.doc.brand).length === 0)
|
|
2434
|
+
delete this.doc.brand;
|
|
2435
|
+
gone.push("its hue");
|
|
2436
|
+
}
|
|
2318
2437
|
for (const [name, rule] of Object.entries(this.doc.rules ?? {})) {
|
|
2319
|
-
if (rule.over === kind || exprMentions(this.doc, rule.when, rule.over === "graph" ? NONE : new Set([rule.over]), { t: "kind", from: kind, to: "\u0000" }) || exprMentions(this.doc, rule.require, rule.over === "graph" ? NONE : new Set([rule.over]), { t: "kind", from: kind, to: "\u0000" })) {
|
|
2438
|
+
if (rule.over === kind || rule.covers?.every === kind || exprMentions(this.doc, rule.when, rule.over === "graph" ? NONE : new Set([rule.over]), { t: "kind", from: kind, to: "\u0000" }) || exprMentions(this.doc, rule.require, rule.over === "graph" ? NONE : new Set([rule.over]), { t: "kind", from: kind, to: "\u0000" })) {
|
|
2320
2439
|
delete this.doc.rules[name];
|
|
2321
2440
|
gone.push(titled("rule", name, rule));
|
|
2322
2441
|
}
|
|
@@ -2342,7 +2461,12 @@ class Editor {
|
|
|
2342
2461
|
});
|
|
2343
2462
|
}
|
|
2344
2463
|
if (this.doc.policy?.sees) {
|
|
2345
|
-
|
|
2464
|
+
// A sight of other kinds through a relation that went with the kind would show nothing: it goes too.
|
|
2465
|
+
const declared = new Set(Object.values(this.doc.kinds).flatMap((spec) => Object.keys(spec.edges ?? {})));
|
|
2466
|
+
const through = this.doc.policy.sees.filter((s) => typeof s.via === "string" && !declared.has(s.via) && s.kinds.some((k) => k !== kind));
|
|
2467
|
+
if (through.length > 0)
|
|
2468
|
+
gone.push(`${through.length === 1 ? "the sight" : "the sights"} through ${listOf([...new Set(through.map((s) => `"${String(s.via)}"`))])}`);
|
|
2469
|
+
this.doc.policy.sees = this.doc.policy.sees.filter((s) => !through.includes(s)).map((s) => ({ ...s, kinds: s.kinds.filter((k) => k !== kind) })).filter((s) => s.kinds.length > 0);
|
|
2346
2470
|
if (this.doc.policy.sees.length === 0)
|
|
2347
2471
|
delete this.doc.policy.sees;
|
|
2348
2472
|
}
|
|
@@ -2397,8 +2521,8 @@ class Editor {
|
|
|
2397
2521
|
}
|
|
2398
2522
|
const f = { ...parsed.data };
|
|
2399
2523
|
delete f.renamedFrom;
|
|
2400
|
-
if (f.type === "enum" && f.default !== undefined &&
|
|
2401
|
-
return this.fail(i, "default", `"${String(f.default)}" is not one of the options`);
|
|
2524
|
+
if (f.type === "enum" && f.default !== undefined && defaultOutsideOptions(f, f.default) !== undefined)
|
|
2525
|
+
return this.fail(i, "default", `"${String(defaultOutsideOptions(f, f.default))}" is not one of the options`);
|
|
2402
2526
|
if (f.format && !formatFits(f.format, f.type))
|
|
2403
2527
|
return this.fail(i, "format", refusedFormat(f.format, f.type));
|
|
2404
2528
|
spec.fields[field] = f;
|
|
@@ -2489,11 +2613,18 @@ class Editor {
|
|
|
2489
2613
|
if (e.format && !formatFits(e.format, e.type))
|
|
2490
2614
|
return this.fail(i, "format", refusedFormat(e.format, e.type));
|
|
2491
2615
|
const was = f.type;
|
|
2616
|
+
const wasSeveral = f.multiple === true;
|
|
2492
2617
|
f.type = e.type;
|
|
2493
2618
|
if (e.type === "enum")
|
|
2494
2619
|
f.options = e.options ?? f.options;
|
|
2495
2620
|
else
|
|
2496
2621
|
delete f.options;
|
|
2622
|
+
// A choice that takes several of its options (FR-233), or one again: only an enum says it, and a retype says it anew.
|
|
2623
|
+
if (e.type === "enum" && e.multiple)
|
|
2624
|
+
f.multiple = true;
|
|
2625
|
+
else
|
|
2626
|
+
delete f.multiple;
|
|
2627
|
+
const several = f.multiple === true;
|
|
2497
2628
|
// An option's label goes with the option.
|
|
2498
2629
|
if (f.optionLabels) {
|
|
2499
2630
|
f.optionLabels = Object.fromEntries(Object.entries(f.optionLabels).filter(([o]) => (f.options ?? []).includes(o)));
|
|
@@ -2523,9 +2654,11 @@ class Editor {
|
|
|
2523
2654
|
// A list grouped by a choice that is no longer one opens ungrouped, and says so.
|
|
2524
2655
|
const held = this.listsHold(e.kind);
|
|
2525
2656
|
if (held.length > 0)
|
|
2526
|
-
this.said.push(`${capitalize(listOf(held))} ${held.length === 1 ? "goes" : "go"} back to the list's own, since ${e.field} is no longer ${was === "enum" ? "a choice" : `a ${was}`}.`);
|
|
2657
|
+
this.said.push(`${capitalize(listOf(held))} ${held.length === 1 ? "goes" : "go"} back to the list's own, since ${e.field} is no longer ${was === "enum" ? (several ? "a choice of one" : "a choice") : `a ${was}`}.`);
|
|
2527
2658
|
if (was === e.type && e.type === "text")
|
|
2528
2659
|
this.said.push(`${e.kind}'s ${e.field} ${e.format === "markdown" ? "is now written in Markdown, drawn as prose on the record's page" : "is drawn as written, not as Markdown"}; its values are kept.`);
|
|
2660
|
+
else if (was === e.type && e.type === "enum" && wasSeveral !== several)
|
|
2661
|
+
this.said.push(several ? `${e.kind}'s ${e.field} now takes several of its options at once; the one each record held is kept as a list of one.` : `${e.kind}'s ${e.field} takes one of its options again; the first each record held is kept.`);
|
|
2529
2662
|
else
|
|
2530
2663
|
this.said.push(`${e.kind}'s ${e.field} changes from ${was} to ${e.type}${said ? `, said as ${said}` : ""}; values that convert are kept.`);
|
|
2531
2664
|
}
|
|
@@ -2986,6 +3119,12 @@ class Editor {
|
|
|
2986
3119
|
act.severs = r.to;
|
|
2987
3120
|
if (Array.isArray(act.replaces))
|
|
2988
3121
|
act.replaces = act.replaces.map((e) => (e === r.from ? r.to : e));
|
|
3122
|
+
if (act.setsLinked?.[r.from] !== undefined) {
|
|
3123
|
+
const carried = {};
|
|
3124
|
+
for (const [k, v] of Object.entries(act.setsLinked))
|
|
3125
|
+
carried[k === r.from ? r.to : k] = v;
|
|
3126
|
+
act.setsLinked = carried;
|
|
3127
|
+
}
|
|
2989
3128
|
for (const e of act.effects ?? []) {
|
|
2990
3129
|
if (e.connect === r.from)
|
|
2991
3130
|
e.connect = r.to;
|
|
@@ -3009,6 +3148,8 @@ class Editor {
|
|
|
3009
3148
|
act.sets = renamed;
|
|
3010
3149
|
else if (t.where === "setsOther")
|
|
3011
3150
|
act.setsOther = renamed;
|
|
3151
|
+
else if (t.where === "setsLinked")
|
|
3152
|
+
act.setsLinked[t.relation] = renamed;
|
|
3012
3153
|
else
|
|
3013
3154
|
act.effects[t.index].set = renamed;
|
|
3014
3155
|
}
|
|
@@ -3072,7 +3213,17 @@ class Editor {
|
|
|
3072
3213
|
const ctx = rule.over === "graph" ? NONE : new Set([rule.over]);
|
|
3073
3214
|
if (rule.when !== undefined)
|
|
3074
3215
|
rule.when = exprAt(rule.when, ctx);
|
|
3075
|
-
|
|
3216
|
+
if (rule.require !== undefined)
|
|
3217
|
+
rule.require = exprAt(rule.require, ctx);
|
|
3218
|
+
// A coverage rule names its kind and its relations outright: each follows its rename.
|
|
3219
|
+
if (rule.covers) {
|
|
3220
|
+
if (r.t === "kind" && rule.covers.every === r.from)
|
|
3221
|
+
rule.covers.every = r.to;
|
|
3222
|
+
if (r.t === "edge")
|
|
3223
|
+
for (const key of ["through", "via"])
|
|
3224
|
+
if (rule.covers[key] === r.from)
|
|
3225
|
+
rule.covers[key] = r.to;
|
|
3226
|
+
}
|
|
3076
3227
|
const mentioned = JSON.stringify(rule) !== before;
|
|
3077
3228
|
if (rule.says !== undefined)
|
|
3078
3229
|
rule.says = tmplAt(rule.says, ctx, mentioned ? prose : undefined);
|
|
@@ -3253,6 +3404,15 @@ class Editor {
|
|
|
3253
3404
|
let uses = exprMentions(doc, act.allowedWhen, on, r) || templateMentions(doc, act.refusal, on, r);
|
|
3254
3405
|
if (r.t === "edge")
|
|
3255
3406
|
uses ||= act.connects === r.from || act.severs === r.from || (act.effects ?? []).some((e) => e.connect === r.from || e.sever === r.from);
|
|
3407
|
+
// A relation it carried values through carries nothing now: the entry goes; an act that did nothing else goes with it.
|
|
3408
|
+
if (r.t === "edge" && !uses && act.setsLinked?.[r.from] !== undefined) {
|
|
3409
|
+
delete act.setsLinked[r.from];
|
|
3410
|
+
if (Object.keys(act.setsLinked).length === 0)
|
|
3411
|
+
delete act.setsLinked;
|
|
3412
|
+
const stillDoes = act.creates || act.writes || act.sets || act.setsLinked || act.connects || act.severs || act.removes || (act.effects ?? []).length > 0;
|
|
3413
|
+
if (!stillDoes)
|
|
3414
|
+
uses = true;
|
|
3415
|
+
}
|
|
3256
3416
|
// A relation it only replaced goes from the list; one that replaced nothing else goes with it (FR-115).
|
|
3257
3417
|
if (r.t === "edge" && !uses && Array.isArray(act.replaces) && act.replaces.includes(r.from)) {
|
|
3258
3418
|
act.replaces = act.replaces.filter((e) => e !== r.from);
|
|
@@ -3283,7 +3443,13 @@ class Editor {
|
|
|
3283
3443
|
delete act.sets;
|
|
3284
3444
|
if (act.setsOther && Object.keys(act.setsOther).length === 0)
|
|
3285
3445
|
delete act.setsOther;
|
|
3286
|
-
|
|
3446
|
+
// A relation whose carried fields are all gone carries nothing: it goes from `setsLinked`, and an empty `setsLinked` goes with it.
|
|
3447
|
+
for (const [relation, set] of Object.entries(act.setsLinked ?? {}))
|
|
3448
|
+
if (Object.keys(set).length === 0)
|
|
3449
|
+
delete act.setsLinked[relation];
|
|
3450
|
+
if (act.setsLinked && Object.keys(act.setsLinked).length === 0)
|
|
3451
|
+
delete act.setsLinked;
|
|
3452
|
+
const doesSomething = act.creates || act.writes || act.sets || act.setsLinked || act.connects || act.severs || act.removes || (act.effects ?? []).some((e) => e.create || e.connect || e.sever || e.remove || (e.set && Object.keys(e.set).length > 0));
|
|
3287
3453
|
if (!doesSomething)
|
|
3288
3454
|
uses = true;
|
|
3289
3455
|
else if (act.effects) {
|
|
@@ -3298,7 +3464,9 @@ class Editor {
|
|
|
3298
3464
|
}
|
|
3299
3465
|
for (const [name, rule] of Object.entries(doc.rules ?? {})) {
|
|
3300
3466
|
const ctx = rule.over === "graph" ? NONE : new Set([rule.over]);
|
|
3301
|
-
|
|
3467
|
+
// A coverage rule goes with the kind it covers or a relation it walks.
|
|
3468
|
+
const walked = rule.covers !== undefined && ((r.t === "kind" && rule.covers.every === r.from) || (r.t === "edge" && (rule.covers.through === r.from || rule.covers.via === r.from)));
|
|
3469
|
+
if (walked || exprMentions(doc, rule.when, ctx, r) || exprMentions(doc, rule.require, ctx, r) || templateMentions(doc, rule.says, ctx, r)) {
|
|
3302
3470
|
delete doc.rules[name];
|
|
3303
3471
|
gone.push(titled("rule", name, rule));
|
|
3304
3472
|
}
|