@graview/core 0.1.14 → 0.1.16

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.
Files changed (238) hide show
  1. package/README.md +27 -8
  2. package/dist/app.d.ts +8 -8
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/arrangement.d.ts +2 -2
  5. package/dist/arrangement.d.ts.map +1 -1
  6. package/dist/arrangement.js +5 -5
  7. package/dist/arranging.js +3 -3
  8. package/dist/capabilities.js +1 -1
  9. package/dist/capabilities.js.map +1 -1
  10. package/dist/city.d.ts +5 -5
  11. package/dist/city.js +3 -3
  12. package/dist/cli/check/beginnings.js +2 -2
  13. package/dist/cli/check/brand.d.ts.map +1 -1
  14. package/dist/cli/check/brand.js +15 -9
  15. package/dist/cli/check/brand.js.map +1 -1
  16. package/dist/cli/check/city.js +1 -1
  17. package/dist/cli/check/context.js +1 -1
  18. package/dist/cli/check/hidden-reads.js +1 -1
  19. package/dist/cli/check/hidden-reads.js.map +1 -1
  20. package/dist/cli/check/relations.js +2 -2
  21. package/dist/cli/check/relations.js.map +1 -1
  22. package/dist/cli/check/settings.js +8 -8
  23. package/dist/cli/check/settings.js.map +1 -1
  24. package/dist/cli/check/views.d.ts +1 -1
  25. package/dist/cli/check/views.js +1 -1
  26. package/dist/cli/create.d.ts +1 -1
  27. package/dist/cli/create.js +5 -5
  28. package/dist/cli/describe.d.ts +1 -1
  29. package/dist/cli/describe.js +2 -2
  30. package/dist/cli/describe.js.map +1 -1
  31. package/dist/cli/index.d.ts +1 -1
  32. package/dist/columns.d.ts +1 -1
  33. package/dist/columns.js +1 -1
  34. package/dist/compiled.d.ts +19 -0
  35. package/dist/compiled.d.ts.map +1 -0
  36. package/dist/compiled.js +60 -0
  37. package/dist/compiled.js.map +1 -0
  38. package/dist/conformance/fixtures.d.ts.map +1 -1
  39. package/dist/conformance/fixtures.js +22 -0
  40. package/dist/conformance/fixtures.js.map +1 -1
  41. package/dist/describe.d.ts +1 -1
  42. package/dist/describe.d.ts.map +1 -1
  43. package/dist/document/blocks.d.ts.map +1 -1
  44. package/dist/document/blocks.js +2 -1
  45. package/dist/document/blocks.js.map +1 -1
  46. package/dist/document/brand-check.d.ts +10 -0
  47. package/dist/document/brand-check.d.ts.map +1 -0
  48. package/dist/document/brand-check.js +33 -0
  49. package/dist/document/brand-check.js.map +1 -0
  50. package/dist/document/brand.d.ts +10 -0
  51. package/dist/document/brand.d.ts.map +1 -0
  52. package/dist/document/brand.js +49 -0
  53. package/dist/document/brand.js.map +1 -0
  54. package/dist/document/compile-checked.d.ts.map +1 -1
  55. package/dist/document/compile-checked.js +6 -2
  56. package/dist/document/compile-checked.js.map +1 -1
  57. package/dist/document/compile.d.ts +10 -29
  58. package/dist/document/compile.d.ts.map +1 -1
  59. package/dist/document/compile.js +74 -393
  60. package/dist/document/compile.js.map +1 -1
  61. package/dist/document/compiled.d.ts +130 -0
  62. package/dist/document/compiled.d.ts.map +1 -0
  63. package/dist/document/compiled.js +467 -0
  64. package/dist/document/compiled.js.map +1 -0
  65. package/dist/document/describe-place.d.ts +16 -0
  66. package/dist/document/describe-place.d.ts.map +1 -1
  67. package/dist/document/describe-place.js +14 -1
  68. package/dist/document/describe-place.js.map +1 -1
  69. package/dist/document/diff.d.ts.map +1 -1
  70. package/dist/document/diff.js +45 -6
  71. package/dist/document/diff.js.map +1 -1
  72. package/dist/document/edit.d.ts +7 -2
  73. package/dist/document/edit.d.ts.map +1 -1
  74. package/dist/document/edit.js +174 -46
  75. package/dist/document/edit.js.map +1 -1
  76. package/dist/document/expr/analyze.d.ts +2 -2
  77. package/dist/document/expr/analyze.js +2 -2
  78. package/dist/document/expr/print.js +1 -1
  79. package/dist/document/index.d.ts +5 -2
  80. package/dist/document/index.d.ts.map +1 -1
  81. package/dist/document/index.js +3 -1
  82. package/dist/document/index.js.map +1 -1
  83. package/dist/document/quote.d.ts +5 -0
  84. package/dist/document/quote.d.ts.map +1 -0
  85. package/dist/document/quote.js +17 -0
  86. package/dist/document/quote.js.map +1 -0
  87. package/dist/document/remembered.d.ts +6 -0
  88. package/dist/document/remembered.d.ts.map +1 -0
  89. package/dist/document/remembered.js +16 -0
  90. package/dist/document/remembered.js.map +1 -0
  91. package/dist/document/rules.d.ts +2 -2
  92. package/dist/document/rules.d.ts.map +1 -1
  93. package/dist/document/rules.js +4 -3
  94. package/dist/document/rules.js.map +1 -1
  95. package/dist/document/schema.d.ts +45 -2
  96. package/dist/document/schema.d.ts.map +1 -1
  97. package/dist/document/schema.js +32 -5
  98. package/dist/document/schema.js.map +1 -1
  99. package/dist/document/template-parse.d.ts +18 -0
  100. package/dist/document/template-parse.d.ts.map +1 -0
  101. package/dist/document/template-parse.js +124 -0
  102. package/dist/document/template-parse.js.map +1 -0
  103. package/dist/document/template.d.ts +1 -17
  104. package/dist/document/template.d.ts.map +1 -1
  105. package/dist/document/template.js +6 -115
  106. package/dist/document/template.js.map +1 -1
  107. package/dist/document/thumbnail.d.ts +13 -1
  108. package/dist/document/thumbnail.d.ts.map +1 -1
  109. package/dist/document/thumbnail.js +106 -16
  110. package/dist/document/thumbnail.js.map +1 -1
  111. package/dist/document/to-document.d.ts +1 -4
  112. package/dist/document/to-document.d.ts.map +1 -1
  113. package/dist/document/to-document.js +7 -14
  114. package/dist/document/to-document.js.map +1 -1
  115. package/dist/document/views.d.ts.map +1 -1
  116. package/dist/document/views.js +1 -1
  117. package/dist/document/views.js.map +1 -1
  118. package/dist/graph/graph.js +3 -3
  119. package/dist/graph/primitives.d.ts +2 -2
  120. package/dist/graph/primitives.js +2 -2
  121. package/dist/index.d.ts +11 -6
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +9 -6
  124. package/dist/index.js.map +1 -1
  125. package/dist/invariants/engine.js +1 -1
  126. package/dist/invariants/types.d.ts +3 -3
  127. package/dist/invariants/types.d.ts.map +1 -1
  128. package/dist/labels.d.ts +1 -1
  129. package/dist/modules.d.ts +1 -1
  130. package/dist/modules.js +1 -1
  131. package/dist/mutations/define-mutation.d.ts +12 -0
  132. package/dist/mutations/define-mutation.d.ts.map +1 -1
  133. package/dist/mutations/define-mutation.js +37 -9
  134. package/dist/mutations/define-mutation.js.map +1 -1
  135. package/dist/mutations/derive-edits.js +4 -4
  136. package/dist/mutations/derive-edits.js.map +1 -1
  137. package/dist/mutations/node-ref.js +1 -1
  138. package/dist/mutations/types.d.ts +1 -1
  139. package/dist/mutations/words.js +3 -3
  140. package/dist/ops/types.d.ts +1 -1
  141. package/dist/ops/withheld.d.ts +3 -3
  142. package/dist/permissions/policy.d.ts +1 -1
  143. package/dist/permissions/policy.js +3 -3
  144. package/dist/permissions/types.d.ts +2 -2
  145. package/dist/places.d.ts +14 -1
  146. package/dist/places.d.ts.map +1 -1
  147. package/dist/places.js +27 -4
  148. package/dist/places.js.map +1 -1
  149. package/dist/refusal.d.ts +11 -6
  150. package/dist/refusal.d.ts.map +1 -1
  151. package/dist/refusal.js +11 -6
  152. package/dist/refusal.js.map +1 -1
  153. package/dist/refused.d.ts +13 -6
  154. package/dist/refused.d.ts.map +1 -1
  155. package/dist/refused.js +14 -7
  156. package/dist/refused.js.map +1 -1
  157. package/dist/scaffold/domain.js +3 -3
  158. package/dist/scaffold/index.d.ts +1 -1
  159. package/dist/scaffold/index.d.ts.map +1 -1
  160. package/dist/scaffold/names.d.ts +1 -1
  161. package/dist/scaffold/names.js +1 -1
  162. package/dist/scaffold/names.js.map +1 -1
  163. package/dist/scaffold/project.d.ts.map +1 -1
  164. package/dist/scaffold/project.js +2 -1
  165. package/dist/scaffold/project.js.map +1 -1
  166. package/dist/scene-districts.d.ts +1 -1
  167. package/dist/schema/define-node.d.ts +4 -4
  168. package/dist/schema/define-node.d.ts.map +1 -1
  169. package/dist/schema/define-node.js +8 -8
  170. package/dist/schema/define-node.js.map +1 -1
  171. package/dist/schema/figures.d.ts +1 -1
  172. package/dist/schema/figures.js +7 -7
  173. package/dist/schema/figures.js.map +1 -1
  174. package/dist/schema/json-schema.d.ts.map +1 -1
  175. package/dist/schema/json-schema.js +4 -1
  176. package/dist/schema/json-schema.js.map +1 -1
  177. package/dist/schema/types.d.ts +1 -1
  178. package/dist/search.d.ts +13 -1
  179. package/dist/search.d.ts.map +1 -1
  180. package/dist/search.js +33 -14
  181. package/dist/search.js.map +1 -1
  182. package/dist/seen.d.ts +2 -2
  183. package/dist/seen.js +12 -12
  184. package/dist/seen.js.map +1 -1
  185. package/dist/settings.d.ts +1 -1
  186. package/dist/settings.js +3 -3
  187. package/dist/settings.js.map +1 -1
  188. package/dist/store.d.ts +2 -2
  189. package/dist/store.d.ts.map +1 -1
  190. package/dist/store.js +8 -8
  191. package/dist/store.js.map +1 -1
  192. package/dist/sync/google-calendar.js +1 -1
  193. package/dist/sync/types.d.ts +2 -2
  194. package/dist/testing.d.ts +1 -1
  195. package/dist/testing.d.ts.map +1 -1
  196. package/dist/testing.js +2 -2
  197. package/dist/testing.js.map +1 -1
  198. package/dist/theme/accent.d.ts +37 -0
  199. package/dist/theme/accent.d.ts.map +1 -0
  200. package/dist/theme/accent.js +138 -0
  201. package/dist/theme/accent.js.map +1 -0
  202. package/dist/theme/contrast.d.ts +7 -7
  203. package/dist/theme/contrast.d.ts.map +1 -1
  204. package/dist/theme/contrast.js +14 -14
  205. package/dist/theme/contrast.js.map +1 -1
  206. package/dist/theme/derive.d.ts +4 -4
  207. package/dist/theme/derive.d.ts.map +1 -1
  208. package/dist/theme/derive.js +19 -19
  209. package/dist/theme/derive.js.map +1 -1
  210. package/dist/theme/fonts.d.ts +19 -0
  211. package/dist/theme/fonts.d.ts.map +1 -0
  212. package/dist/theme/fonts.js +73 -0
  213. package/dist/theme/fonts.js.map +1 -0
  214. package/dist/theme/kit.d.ts +9 -9
  215. package/dist/theme/kit.d.ts.map +1 -1
  216. package/dist/theme/kit.js +15 -15
  217. package/dist/theme/kit.js.map +1 -1
  218. package/dist/theme/layers.d.ts +2 -2
  219. package/dist/theme/layers.d.ts.map +1 -1
  220. package/dist/theme/layers.js +2 -2
  221. package/dist/theme/layers.js.map +1 -1
  222. package/dist/theme/look.d.ts +3 -3
  223. package/dist/theme/look.d.ts.map +1 -1
  224. package/dist/theme/look.js +1 -1
  225. package/dist/theme/marks.d.ts +28 -0
  226. package/dist/theme/marks.d.ts.map +1 -0
  227. package/dist/theme/marks.js +237 -0
  228. package/dist/theme/marks.js.map +1 -0
  229. package/dist/theme/palettes.d.ts +2 -2
  230. package/dist/theme/palettes.js +2 -2
  231. package/dist/theme/types.d.ts +28 -6
  232. package/dist/theme/types.d.ts.map +1 -1
  233. package/dist/theme/types.js.map +1 -1
  234. package/dist/validate-graph.js +2 -2
  235. package/dist/version.d.ts +1 -1
  236. package/dist/version.js +1 -1
  237. package/dist/watched.js +8 -8
  238. package/package.json +5 -1
@@ -9,8 +9,12 @@ import { ActSpec, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, KindS
9
9
  import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES } from "../places.js";
10
10
  import { placeSlug } from "../views/types.js";
11
11
  import { computedOf, validateComputed } from "./computed.js";
12
- import { parseTemplate, templateBraces, TemplateError } from "./template.js";
12
+ import { parseTemplate, templateBraces, TemplateError } from "./template-parse.js";
13
13
  import { validateViews, VIEW_SLOTS } from "./views.js";
14
+ import { brandFindings } from "./brand-check.js";
15
+ import { canonicalize } from "./canonical.js";
16
+ import { quotedAtTheEnd } from "./quote.js";
17
+ import { accentProblem } from "../theme/accent.js";
14
18
  /** The ops, for tool schemas and the guide. */
15
19
  export const EDIT_OPS = [
16
20
  "add-kind",
@@ -32,6 +36,8 @@ export const EDIT_OPS = [
32
36
  "add-rule",
33
37
  "remove-rule",
34
38
  "set-brand",
39
+ "set-name",
40
+ "set-description",
35
41
  "set-label",
36
42
  "set-describe",
37
43
  "set-view",
@@ -74,11 +80,20 @@ const SHAPES = {
74
80
  .object({
75
81
  op: z.literal("set-brand"),
76
82
  accent: z.union([z.string(), z.null()]).optional(),
77
- name: z.string().min(1).max(60).optional(),
83
+ name: z.union([z.string().min(1).max(60), z.null()]).optional(),
78
84
  currency: z.union([z.string(), z.null()]).optional(),
79
85
  locale: z.union([z.string(), z.null()]).optional(),
86
+ // The rest of the brand (FR-124, FR-125): each as the document holds it, null to clear it.
87
+ logo: z.union([z.string().min(1), z.object({ src: z.string().min(1), alt: z.string().min(1).max(120).optional() }).strict(), z.null()]).optional(),
88
+ favicon: z.union([z.string().min(1), z.null()]).optional(),
89
+ typography: z.union([z.object({ display: z.union([z.string().min(1).max(200), z.null()]).optional(), body: z.union([z.string().min(1).max(200), z.null()]).optional(), mono: z.union([z.string().min(1).max(200), z.null()]).optional() }).strict(), z.null()]).optional(),
90
+ shape: z.union([z.object({ radius: z.union([z.number().min(0).max(32), z.null()]).optional(), density: z.union([z.number().min(0.75).max(1.5), z.null()]).optional() }).strict(), z.null()]).optional(),
91
+ accents: z.union([z.record(z.string(), z.union([z.number().min(0).max(360), z.null()])), z.null()]).optional(),
92
+ scheme: z.union([z.enum(["light", "dark", "auto"]), z.null()]).optional(),
80
93
  })
81
94
  .strict(),
95
+ "set-name": z.object({ op: z.literal("set-name"), name: z.string().min(1).max(80) }).strict(),
96
+ "set-description": z.object({ op: z.literal("set-description"), description: z.union([z.string().min(1).max(500), z.null()]) }).strict(),
82
97
  "set-label": z.object({ op: z.literal("set-label"), kind: kindName, field: fieldName.optional(), label: z.union([z.string().min(1).max(300), z.null()]) }).strict(),
83
98
  "set-describe": z.object({ op: z.literal("set-describe"), kind: kindName, describe: z.union([z.string().min(1).max(300), z.null()]) }).strict(),
84
99
  // A kind's slot; the front page (`slot: "home"`, no kind); or a blocks lens's blocks, by its title (FR-84).
@@ -104,6 +119,7 @@ const SHAPES = {
104
119
  order: z.union([z.array(kindName).max(40), z.null()]).optional(),
105
120
  hide: z.union([z.array(kindName).max(40), z.null()]).optional(),
106
121
  first: z.union([z.string().min(1).max(80), z.null()]).optional(),
122
+ overview: z.union([z.string().min(1).max(40), z.null()]).optional(),
107
123
  })
108
124
  .strict(),
109
125
  "set-computed": z
@@ -395,6 +411,7 @@ function setTargets(act, doc) {
395
411
  // ── the edits ────────────────────────────────────────────────────────────────
396
412
  class Editor {
397
413
  doc;
414
+ options;
398
415
  said = [];
399
416
  /** The agent tools this change renames or reshapes: a model that listed them before calls what is gone. */
400
417
  toolsMoved = new Set();
@@ -404,8 +421,9 @@ class Editor {
404
421
  kindOrigin = new Map();
405
422
  fieldOrigin = new Map();
406
423
  edgeOrigin = new Map();
407
- constructor(doc, base) {
424
+ constructor(doc, base, options = {}) {
408
425
  this.doc = doc;
426
+ this.options = options;
409
427
  for (const [kind, spec] of Object.entries(base.kinds)) {
410
428
  this.kindOrigin.set(kind, kind);
411
429
  for (const f of Object.keys(spec.fields))
@@ -417,6 +435,128 @@ class Editor {
417
435
  fail(i, path, message, fix) {
418
436
  this.findings.push(error("edit", `edits.${i}${path ? `.${path}` : ""}`, message, fix));
419
437
  }
438
+ /**
439
+ * THE BRAND, KEY BY KEY (FR-100, FR-124, FR-125): what an edit names is
440
+ * set, null clears it, and what it does not name stays as it was. Each
441
+ * is judged as `graview check` judges it — a mark that could act or load,
442
+ * a face off the list — and an accent that does not read as given is
443
+ * refused with the pair, the ratio and a shade that would (FR-126).
444
+ */
445
+ setBrand(i, e) {
446
+ const brand = clone(this.doc.brand ?? {});
447
+ const at = `edits.${i}`;
448
+ // An edit that names nothing to change is refused, never applied with nothing said.
449
+ const named = Object.keys(e).filter((key) => key !== "op" && e[key] !== undefined);
450
+ if (named.length === 0)
451
+ 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"}');
452
+ const emptyPart = ["typography", "shape", "accents"].find((key) => isObject(e[key]) && Object.keys(e[key]).length === 0);
453
+ if (emptyPart)
454
+ return this.fail(i, emptyPart, `${emptyPart} names no part to set; name one, or give null to clear all of it`);
455
+ const judged = brandFindings({ ...(typeof e.logo === "string" || isObject(e.logo) ? { logo: e.logo } : {}), ...(typeof e.favicon === "string" ? { favicon: e.favicon } : {}), ...(isObject(e.typography) ? { typography: Object.fromEntries(Object.entries(e.typography).filter(([, v]) => typeof v === "string")) } : {}) }, { at, ...(this.options.fonts ? { fonts: this.options.fonts } : {}) });
456
+ if (judged.length > 0)
457
+ return void this.findings.push(...judged);
458
+ if (e.accent === null) {
459
+ delete brand["accent"];
460
+ this.said.push("The app goes back to Graview's colors.");
461
+ }
462
+ else if (e.accent !== undefined) {
463
+ if (!/^#[0-9a-fA-F]{6}$/.test(e.accent))
464
+ return this.fail(i, "accent", 'an accent is a color like "#c2577a"');
465
+ const refused = accentProblem(e.accent);
466
+ if (refused)
467
+ return this.fail(i, "accent", refused.sentence, refused.suggestion ? `{"op": "set-brand", "accent": "${refused.suggestion}"}` : "pick a color of another hue");
468
+ brand["accent"] = e.accent;
469
+ this.said.push(`The app's accent color becomes ${e.accent}.`);
470
+ }
471
+ if (e.currency === null) {
472
+ delete brand["currency"];
473
+ this.said.push("Money is said with no currency.");
474
+ }
475
+ else if (e.currency !== undefined) {
476
+ if (!/^[A-Z]{3}$/.test(e.currency))
477
+ return this.fail(i, "currency", 'a currency is its three-letter code, like "USD" or "EUR"');
478
+ brand["currency"] = e.currency;
479
+ this.said.push(`Money is said in ${e.currency}.`);
480
+ }
481
+ if (e.locale === null)
482
+ delete brand["locale"];
483
+ else if (e.locale !== undefined) {
484
+ if (!/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(e.locale))
485
+ return this.fail(i, "locale", 'a locale is a language tag, like "en-US" or "de-DE"');
486
+ brand["locale"] = e.locale;
487
+ this.said.push(`Money is written for ${e.locale}.`);
488
+ }
489
+ if (e.name === null) {
490
+ delete brand["name"];
491
+ this.said.push("The wordmark says the app's name.");
492
+ }
493
+ else if (e.name !== undefined) {
494
+ brand["name"] = e.name;
495
+ this.said.push(`The app's wordmark says ${e.name}.`);
496
+ }
497
+ for (const [key, what] of [["logo", "logo"], ["favicon", "page icon"]]) {
498
+ if (e[key] === undefined)
499
+ continue;
500
+ const was = brand[key];
501
+ if (e[key] === null)
502
+ delete brand[key];
503
+ else
504
+ brand[key] = e[key];
505
+ const src = (mark) => (typeof mark === "string" ? mark : isObject(mark) ? mark["src"] : undefined);
506
+ const alt = (mark) => (isObject(mark) && typeof mark["alt"] === "string" ? mark["alt"] : undefined);
507
+ if (e[key] === null)
508
+ this.said.push(`The app has no ${what}.`);
509
+ else if (was === undefined)
510
+ this.said.push(`The app gets a ${what}.`);
511
+ else if (canonicalize(was) === canonicalize(e[key]))
512
+ this.said.push(`The ${what} stays as it was.`);
513
+ else if (src(was) === src(e[key]))
514
+ this.said.push(`The ${what} is said as ${quotedAtTheEnd(alt(e[key]) ?? brand["name"] ?? this.doc.name)}`);
515
+ else
516
+ this.said.push(`The ${what} changes.`);
517
+ }
518
+ const merge = (key, say, cleared) => {
519
+ if (e[key] === undefined)
520
+ return;
521
+ if (e[key] === null) {
522
+ delete brand[key];
523
+ this.said.push(cleared);
524
+ return;
525
+ }
526
+ const into = { ...(brand[key] ?? {}) };
527
+ for (const [part, value] of Object.entries(e[key])) {
528
+ if (value === null)
529
+ delete into[part];
530
+ else
531
+ into[part] = value;
532
+ this.said.push(say(part, value));
533
+ }
534
+ if (Object.keys(into).length === 0)
535
+ delete brand[key];
536
+ else
537
+ brand[key] = into;
538
+ };
539
+ const ROLE = { display: "Headings", body: "Body text", mono: "Code" };
540
+ merge("typography", (role, value) => (value === null ? `${ROLE[role]} go${role === "display" ? "" : "es"} back to Graview's face.` : `${ROLE[role]} ${role === "display" ? "are" : "is"} now set in ${value}.`), "Words go back to Graview's faces.");
541
+ merge("shape", (part, value) => (part === "radius" ? (value === null ? "Corners go back to Graview's own." : `Corners are now ${value}px.`) : value === null ? "Spacing goes back to Graview's own." : `Spacing is now ${value} times Graview's own.`), "Corners and spacing go back to Graview's own.");
542
+ if (isObject(e.accents)) {
543
+ const unknown = Object.keys(e.accents).find((kind) => !this.doc.kinds[kind]);
544
+ if (unknown)
545
+ return this.fail(i, "accents", `"${unknown}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`);
546
+ }
547
+ merge("accents", (kind, hue) => (hue === null ? `${cap(kind)} goes back to its own hue.` : `${cap(kind)} is drawn at hue ${hue}°.`), "Every kind goes back to its own hue.");
548
+ if (e.scheme !== undefined) {
549
+ if (e.scheme === null || e.scheme === "auto")
550
+ delete brand["scheme"];
551
+ else
552
+ brand["scheme"] = e.scheme;
553
+ this.said.push(e.scheme === "light" || e.scheme === "dark" ? `The app opens ${e.scheme} when the reader has not chosen.` : "The app follows the reader's system for light and dark.");
554
+ }
555
+ if (Object.keys(brand).length === 0)
556
+ delete this.doc.brand;
557
+ else
558
+ this.doc.brand = brand;
559
+ }
420
560
  kind(i, kind) {
421
561
  const spec = this.doc.kinds[kind];
422
562
  if (!spec)
@@ -532,46 +672,23 @@ class Editor {
532
672
  this.said.push(`The rule "${title}" is removed.`);
533
673
  return;
534
674
  }
535
- case "set-brand": {
536
- // The colours and the money (FR-100) are set apart: what an edit does not name stays as it was.
537
- const brand = { ...(this.doc.brand ?? {}) };
538
- if (e.accent === null) {
539
- delete brand["accent"];
540
- this.said.push("The app goes back to Graview's colours.");
541
- }
542
- else if (e.accent !== undefined) {
543
- if (!/^#[0-9a-fA-F]{6}$/.test(e.accent))
544
- return this.fail(i, "accent", 'an accent is a colour like "#c2577a"');
545
- brand["accent"] = e.accent;
546
- this.said.push(`The app's accent colour becomes ${e.accent}.`);
547
- }
548
- if (e.currency === null) {
549
- delete brand["currency"];
550
- this.said.push("Money is said with no currency.");
551
- }
552
- else if (e.currency !== undefined) {
553
- if (!/^[A-Z]{3}$/.test(e.currency))
554
- return this.fail(i, "currency", 'a currency is its three-letter code, like "USD" or "EUR"');
555
- brand["currency"] = e.currency;
556
- this.said.push(`Money is said in ${e.currency}.`);
557
- }
558
- if (e.locale === null)
559
- delete brand["locale"];
560
- else if (e.locale !== undefined) {
561
- if (!/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(e.locale))
562
- return this.fail(i, "locale", 'a locale is a language tag, like "en-US" or "de-DE"');
563
- brand["locale"] = e.locale;
564
- this.said.push(`Money is written for ${e.locale}.`);
565
- }
566
- if (e.name) {
567
- brand["name"] = e.name;
568
- this.said.push(`The app's wordmark says ${e.name}.`);
569
- }
570
- // A brand that holds only a name holds nothing to apply.
571
- if (Object.keys(brand).every((key) => key === "name"))
572
- delete this.doc.brand;
675
+ case "set-brand":
676
+ return this.setBrand(i, e);
677
+ case "set-name": {
678
+ const already = this.doc.name === e.name;
679
+ this.doc.name = e.name;
680
+ this.said.push(already ? `The app is already called ${quotedAtTheEnd(e.name)}` : `The app is now called ${quotedAtTheEnd(e.name)}`);
681
+ return;
682
+ }
683
+ case "set-description": {
684
+ const already = (this.doc.description ?? null) === e.description;
685
+ if (e.description === null)
686
+ delete this.doc.description;
573
687
  else
574
- this.doc.brand = brand;
688
+ this.doc.description = e.description;
689
+ this.said.push(e.description === null
690
+ ? already ? "The app already has no line under its name." : "The app has no line under its name."
691
+ : already ? `The line under the app's name already reads ${quotedAtTheEnd(e.description)}` : `The line under the app's name reads ${quotedAtTheEnd(e.description)}`);
575
692
  return;
576
693
  }
577
694
  case "set-label": {
@@ -823,8 +940,8 @@ class Editor {
823
940
  this.said.push(`${cap(said.replace(/ \(the app opens at its home again\)$/, ""))} is removed${said.endsWith("again)") ? "; the app opens at its home again" : ""}.`);
824
941
  }
825
942
  arrangePages(i, e) {
826
- if (e.order === undefined && e.hide === undefined && e.first === undefined)
827
- return this.fail(i, "", 'arrange-pages says at least one of "order", "hide" or "first"');
943
+ if (e.order === undefined && e.hide === undefined && e.first === undefined && e.overview === undefined)
944
+ return this.fail(i, "", 'arrange-pages says at least one of "order", "hide", "first" or "overview"');
828
945
  const kinds = Object.keys(this.doc.kinds);
829
946
  for (const part of ["order", "hide"]) {
830
947
  for (const [n, kind] of (e[part] ?? []).entries()) {
@@ -873,6 +990,17 @@ class Editor {
873
990
  said.push(`the app opens on "${e.first}"`);
874
991
  }
875
992
  }
993
+ if (e.overview !== undefined) {
994
+ // What the scene is called on the bar (FR-132); its address stays /places/overview.
995
+ if (e.overview === null || e.overview.trim() === "" || e.overview.trim() === "Overview") {
996
+ delete pages.overview;
997
+ said.push("the overview is called Overview");
998
+ }
999
+ else {
1000
+ pages.overview = e.overview.trim();
1001
+ said.push(`the overview is called "${pages.overview}"`);
1002
+ }
1003
+ }
876
1004
  if (Object.keys(pages).length === 0)
877
1005
  delete this.doc.pages;
878
1006
  this.said.push(`${cap(listOf(said))}.`);
@@ -2277,7 +2405,7 @@ function pruneBlock(doc, b, ctx, r, kind) {
2277
2405
  * `edits.<i>.…`) and no document. The result is NOT compiled here: the caller
2278
2406
  * previews it like any other proposed document.
2279
2407
  */
2280
- export function editDocument(document, edits) {
2408
+ export function editDocument(document, edits, options = {}) {
2281
2409
  if (!Array.isArray(edits))
2282
2410
  return { ok: false, findings: [error("edit", "edits", "edits are a list, like [{\"op\": \"add-field\", …}]")] };
2283
2411
  // No edits is no change: the document as it was, and nothing said.
@@ -2285,7 +2413,7 @@ export function editDocument(document, edits) {
2285
2413
  return { ok: true, document: clone(document), said: [], fills: [] };
2286
2414
  if (edits.length > MAX_EDITS)
2287
2415
  return { ok: false, findings: [error("edit", "edits", `at most ${MAX_EDITS} edits at once`)] };
2288
- const editor = new Editor(clone(document), document);
2416
+ const editor = new Editor(clone(document), document, options);
2289
2417
  edits.forEach((raw, i) => {
2290
2418
  if (editor.findings.length > 0)
2291
2419
  return;