@graview/core 0.1.29 → 0.1.30

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 (343) hide show
  1. package/dist/app-places.d.ts +2 -2
  2. package/dist/app-places.d.ts.map +1 -1
  3. package/dist/app-places.js +53 -3
  4. package/dist/app-places.js.map +1 -1
  5. package/dist/app.d.ts +16 -0
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js.map +1 -1
  8. package/dist/arrange.d.ts +3 -1
  9. package/dist/arrange.d.ts.map +1 -1
  10. package/dist/arrange.js +3 -1
  11. package/dist/arrange.js.map +1 -1
  12. package/dist/arrangement.d.ts +18 -0
  13. package/dist/arrangement.d.ts.map +1 -1
  14. package/dist/arrangement.js +34 -0
  15. package/dist/arrangement.js.map +1 -1
  16. package/dist/arranging.d.ts.map +1 -1
  17. package/dist/arranging.js +17 -6
  18. package/dist/arranging.js.map +1 -1
  19. package/dist/blocks.d.ts +1 -1
  20. package/dist/blocks.d.ts.map +1 -1
  21. package/dist/cli/check/brand.d.ts.map +1 -1
  22. package/dist/cli/check/brand.js +29 -1
  23. package/dist/cli/check/brand.js.map +1 -1
  24. package/dist/cli/check/lenses.d.ts.map +1 -1
  25. package/dist/cli/check/lenses.js +6 -1
  26. package/dist/cli/check/lenses.js.map +1 -1
  27. package/dist/cli/check/relations.d.ts +23 -0
  28. package/dist/cli/check/relations.d.ts.map +1 -1
  29. package/dist/cli/check/relations.js +53 -3
  30. package/dist/cli/check/relations.js.map +1 -1
  31. package/dist/cli/create.d.ts.map +1 -1
  32. package/dist/cli/create.js +32 -0
  33. package/dist/cli/create.js.map +1 -1
  34. package/dist/cli/describe.d.ts.map +1 -1
  35. package/dist/cli/describe.js +27 -4
  36. package/dist/cli/describe.js.map +1 -1
  37. package/dist/cli/doctor.d.ts +18 -1
  38. package/dist/cli/doctor.d.ts.map +1 -1
  39. package/dist/cli/doctor.js +58 -5
  40. package/dist/cli/doctor.js.map +1 -1
  41. package/dist/cli/document-schema.d.ts +2 -0
  42. package/dist/cli/document-schema.d.ts.map +1 -1
  43. package/dist/cli/document-schema.js +154 -5
  44. package/dist/cli/document-schema.js.map +1 -1
  45. package/dist/cli/index.d.ts +1 -1
  46. package/dist/cli/index.d.ts.map +1 -1
  47. package/dist/cli/types.d.ts.map +1 -1
  48. package/dist/cli/types.js +6 -1
  49. package/dist/cli/types.js.map +1 -1
  50. package/dist/conformance/fixtures.d.ts.map +1 -1
  51. package/dist/conformance/fixtures.js +165 -0
  52. package/dist/conformance/fixtures.js.map +1 -1
  53. package/dist/coverage-words.d.ts +18 -0
  54. package/dist/coverage-words.d.ts.map +1 -0
  55. package/dist/coverage-words.js +18 -0
  56. package/dist/coverage-words.js.map +1 -0
  57. package/dist/document/act-findings.d.ts.map +1 -1
  58. package/dist/document/act-findings.js +21 -3
  59. package/dist/document/act-findings.js.map +1 -1
  60. package/dist/document/blocks.d.ts +20 -7
  61. package/dist/document/blocks.d.ts.map +1 -1
  62. package/dist/document/blocks.js +85 -15
  63. package/dist/document/blocks.js.map +1 -1
  64. package/dist/document/brand-check.d.ts +5 -0
  65. package/dist/document/brand-check.d.ts.map +1 -1
  66. package/dist/document/brand-check.js +16 -2
  67. package/dist/document/brand-check.js.map +1 -1
  68. package/dist/document/compile-checked.d.ts.map +1 -1
  69. package/dist/document/compile-checked.js +11 -4
  70. package/dist/document/compile-checked.js.map +1 -1
  71. package/dist/document/compile.d.ts +0 -2
  72. package/dist/document/compile.d.ts.map +1 -1
  73. package/dist/document/compile.js +19 -17
  74. package/dist/document/compile.js.map +1 -1
  75. package/dist/document/compiled.d.ts +2 -0
  76. package/dist/document/compiled.d.ts.map +1 -1
  77. package/dist/document/compiled.js +49 -12
  78. package/dist/document/compiled.js.map +1 -1
  79. package/dist/document/computed-values.d.ts +3 -0
  80. package/dist/document/computed-values.d.ts.map +1 -1
  81. package/dist/document/computed-values.js +12 -2
  82. package/dist/document/computed-values.js.map +1 -1
  83. package/dist/document/describe-coverage.d.ts +3 -0
  84. package/dist/document/describe-coverage.d.ts.map +1 -1
  85. package/dist/document/describe-coverage.js +12 -5
  86. package/dist/document/describe-coverage.js.map +1 -1
  87. package/dist/document/describe-place.d.ts.map +1 -1
  88. package/dist/document/describe-place.js +3 -1
  89. package/dist/document/describe-place.js.map +1 -1
  90. package/dist/document/diff.d.ts.map +1 -1
  91. package/dist/document/diff.js +39 -8
  92. package/dist/document/diff.js.map +1 -1
  93. package/dist/document/edit.d.ts +1 -1
  94. package/dist/document/edit.d.ts.map +1 -1
  95. package/dist/document/edit.js +251 -15
  96. package/dist/document/edit.js.map +1 -1
  97. package/dist/document/expr/evaluate.d.ts +25 -0
  98. package/dist/document/expr/evaluate.d.ts.map +1 -1
  99. package/dist/document/expr/evaluate.js +38 -0
  100. package/dist/document/expr/evaluate.js.map +1 -1
  101. package/dist/document/format-words.d.ts +1 -1
  102. package/dist/document/format-words.d.ts.map +1 -1
  103. package/dist/document/format-words.js +3 -1
  104. package/dist/document/format-words.js.map +1 -1
  105. package/dist/document/framework-path.d.ts +11 -0
  106. package/dist/document/framework-path.d.ts.map +1 -0
  107. package/dist/document/framework-path.js +122 -0
  108. package/dist/document/framework-path.js.map +1 -0
  109. package/dist/document/index.d.ts +2 -2
  110. package/dist/document/index.d.ts.map +1 -1
  111. package/dist/document/index.js +1 -1
  112. package/dist/document/index.js.map +1 -1
  113. package/dist/document/linked-findings.d.ts +5 -0
  114. package/dist/document/linked-findings.d.ts.map +1 -0
  115. package/dist/document/linked-findings.js +100 -0
  116. package/dist/document/linked-findings.js.map +1 -0
  117. package/dist/document/migrate.d.ts.map +1 -1
  118. package/dist/document/migrate.js +4 -1
  119. package/dist/document/migrate.js.map +1 -1
  120. package/dist/document/policy-words.d.ts +2 -0
  121. package/dist/document/policy-words.d.ts.map +1 -1
  122. package/dist/document/policy-words.js +16 -1
  123. package/dist/document/policy-words.js.map +1 -1
  124. package/dist/document/right-names.d.ts +10 -0
  125. package/dist/document/right-names.d.ts.map +1 -0
  126. package/dist/document/right-names.js +57 -0
  127. package/dist/document/right-names.js.map +1 -0
  128. package/dist/document/rules.d.ts.map +1 -1
  129. package/dist/document/rules.js +13 -5
  130. package/dist/document/rules.js.map +1 -1
  131. package/dist/document/schema.d.ts +80 -3
  132. package/dist/document/schema.d.ts.map +1 -1
  133. package/dist/document/schema.js +57 -7
  134. package/dist/document/schema.js.map +1 -1
  135. package/dist/document/template-parse.d.ts.map +1 -1
  136. package/dist/document/template-parse.js +36 -3
  137. package/dist/document/template-parse.js.map +1 -1
  138. package/dist/document/template.d.ts +13 -1
  139. package/dist/document/template.d.ts.map +1 -1
  140. package/dist/document/template.js +29 -9
  141. package/dist/document/template.js.map +1 -1
  142. package/dist/document/to-document.d.ts.map +1 -1
  143. package/dist/document/to-document.js +11 -0
  144. package/dist/document/to-document.js.map +1 -1
  145. package/dist/document/views.d.ts +36 -4
  146. package/dist/document/views.d.ts.map +1 -1
  147. package/dist/document/views.js +64 -21
  148. package/dist/document/views.js.map +1 -1
  149. package/dist/document.schema.json +1037 -51
  150. package/dist/field-sight.js +2 -2
  151. package/dist/field-sight.js.map +1 -1
  152. package/dist/forms.d.ts +1 -0
  153. package/dist/forms.d.ts.map +1 -1
  154. package/dist/forms.js +2 -0
  155. package/dist/forms.js.map +1 -1
  156. package/dist/graph/archived.d.ts +9 -0
  157. package/dist/graph/archived.d.ts.map +1 -1
  158. package/dist/graph/archived.js +9 -0
  159. package/dist/graph/archived.js.map +1 -1
  160. package/dist/graph/diff.d.ts +16 -0
  161. package/dist/graph/diff.d.ts.map +1 -1
  162. package/dist/graph/diff.js +18 -0
  163. package/dist/graph/diff.js.map +1 -1
  164. package/dist/graph/graph.d.ts.map +1 -1
  165. package/dist/graph/graph.js +5 -8
  166. package/dist/graph/graph.js.map +1 -1
  167. package/dist/grid-sort.d.ts +20 -0
  168. package/dist/grid-sort.d.ts.map +1 -0
  169. package/dist/grid-sort.js +58 -0
  170. package/dist/grid-sort.js.map +1 -0
  171. package/dist/history.d.ts +88 -0
  172. package/dist/history.d.ts.map +1 -0
  173. package/dist/history.js +146 -0
  174. package/dist/history.js.map +1 -0
  175. package/dist/index.d.ts +16 -6
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +12 -3
  178. package/dist/index.js.map +1 -1
  179. package/dist/kind-list.d.ts +78 -0
  180. package/dist/kind-list.d.ts.map +1 -0
  181. package/dist/kind-list.js +176 -0
  182. package/dist/kind-list.js.map +1 -0
  183. package/dist/linked.d.ts +95 -0
  184. package/dist/linked.d.ts.map +1 -0
  185. package/dist/linked.js +181 -0
  186. package/dist/linked.js.map +1 -0
  187. package/dist/mutations/derive-edits.d.ts.map +1 -1
  188. package/dist/mutations/derive-edits.js +89 -38
  189. package/dist/mutations/derive-edits.js.map +1 -1
  190. package/dist/mutations/derived-names.d.ts +3 -1
  191. package/dist/mutations/derived-names.d.ts.map +1 -1
  192. package/dist/mutations/derived-names.js +5 -3
  193. package/dist/mutations/derived-names.js.map +1 -1
  194. package/dist/mutations/edit-start.d.ts +28 -0
  195. package/dist/mutations/edit-start.d.ts.map +1 -0
  196. package/dist/mutations/edit-start.js +24 -0
  197. package/dist/mutations/edit-start.js.map +1 -0
  198. package/dist/mutations/form.d.ts +12 -0
  199. package/dist/mutations/form.d.ts.map +1 -1
  200. package/dist/mutations/form.js +4 -0
  201. package/dist/mutations/form.js.map +1 -1
  202. package/dist/mutations/kept-arguments.js +2 -2
  203. package/dist/mutations/kept-arguments.js.map +1 -1
  204. package/dist/mutations/types.d.ts +1 -1
  205. package/dist/mutations/types.d.ts.map +1 -1
  206. package/dist/navigate.d.ts +13 -0
  207. package/dist/navigate.d.ts.map +1 -0
  208. package/dist/navigate.js +13 -0
  209. package/dist/navigate.js.map +1 -0
  210. package/dist/ops/digest.d.ts +33 -0
  211. package/dist/ops/digest.d.ts.map +1 -0
  212. package/dist/ops/digest.js +47 -0
  213. package/dist/ops/digest.js.map +1 -0
  214. package/dist/ops/lineage.d.ts +6 -0
  215. package/dist/ops/lineage.d.ts.map +1 -1
  216. package/dist/ops/lineage.js +5 -4
  217. package/dist/ops/lineage.js.map +1 -1
  218. package/dist/ops/log.d.ts +6 -0
  219. package/dist/ops/log.d.ts.map +1 -1
  220. package/dist/ops/log.js +19 -12
  221. package/dist/ops/log.js.map +1 -1
  222. package/dist/ops/suggestions.d.ts +26 -0
  223. package/dist/ops/suggestions.d.ts.map +1 -0
  224. package/dist/ops/suggestions.js +21 -0
  225. package/dist/ops/suggestions.js.map +1 -0
  226. package/dist/ops/tidying.d.ts +48 -0
  227. package/dist/ops/tidying.d.ts.map +1 -0
  228. package/dist/ops/tidying.js +84 -0
  229. package/dist/ops/tidying.js.map +1 -0
  230. package/dist/ops/types.d.ts +17 -0
  231. package/dist/ops/types.d.ts.map +1 -1
  232. package/dist/ops/undo.d.ts.map +1 -1
  233. package/dist/ops/undo.js +10 -0
  234. package/dist/ops/undo.js.map +1 -1
  235. package/dist/past.d.ts +35 -0
  236. package/dist/past.d.ts.map +1 -0
  237. package/dist/past.js +173 -0
  238. package/dist/past.js.map +1 -0
  239. package/dist/permissions/policy.d.ts +8 -1
  240. package/dist/permissions/policy.d.ts.map +1 -1
  241. package/dist/permissions/policy.js +20 -5
  242. package/dist/permissions/policy.js.map +1 -1
  243. package/dist/places.d.ts +6 -2
  244. package/dist/places.d.ts.map +1 -1
  245. package/dist/places.js +48 -3
  246. package/dist/places.js.map +1 -1
  247. package/dist/scaffold/agents.d.ts.map +1 -1
  248. package/dist/scaffold/agents.js +13 -0
  249. package/dist/scaffold/agents.js.map +1 -1
  250. package/dist/scaffold/project.d.ts.map +1 -1
  251. package/dist/scaffold/project.js +5 -0
  252. package/dist/scaffold/project.js.map +1 -1
  253. package/dist/scaffold/ui.d.ts.map +1 -1
  254. package/dist/scaffold/ui.js +15 -5
  255. package/dist/scaffold/ui.js.map +1 -1
  256. package/dist/schema/app-address.d.ts +5 -0
  257. package/dist/schema/app-address.d.ts.map +1 -0
  258. package/dist/schema/app-address.js +10 -0
  259. package/dist/schema/app-address.js.map +1 -0
  260. package/dist/schema/app-link.d.ts +1 -4
  261. package/dist/schema/app-link.d.ts.map +1 -1
  262. package/dist/schema/app-link.js +5 -7
  263. package/dist/schema/app-link.js.map +1 -1
  264. package/dist/schema/define-node.d.ts +16 -0
  265. package/dist/schema/define-node.d.ts.map +1 -1
  266. package/dist/schema/define-node.js +6 -0
  267. package/dist/schema/define-node.js.map +1 -1
  268. package/dist/schema/files.d.ts +8 -0
  269. package/dist/schema/files.d.ts.map +1 -1
  270. package/dist/schema/files.js.map +1 -1
  271. package/dist/schema/json-schema.d.ts.map +1 -1
  272. package/dist/schema/json-schema.js +19 -1
  273. package/dist/schema/json-schema.js.map +1 -1
  274. package/dist/schema/linked-field.d.ts +6 -0
  275. package/dist/schema/linked-field.d.ts.map +1 -0
  276. package/dist/schema/linked-field.js +34 -0
  277. package/dist/schema/linked-field.js.map +1 -0
  278. package/dist/schema/linked-reads.d.ts +20 -0
  279. package/dist/schema/linked-reads.d.ts.map +1 -0
  280. package/dist/schema/linked-reads.js +69 -0
  281. package/dist/schema/linked-reads.js.map +1 -0
  282. package/dist/schema/linked-schema.d.ts +5 -0
  283. package/dist/schema/linked-schema.d.ts.map +1 -0
  284. package/dist/schema/linked-schema.js +9 -0
  285. package/dist/schema/linked-schema.js.map +1 -0
  286. package/dist/schema/linked.d.ts +147 -0
  287. package/dist/schema/linked.d.ts.map +1 -0
  288. package/dist/schema/linked.js +151 -0
  289. package/dist/schema/linked.js.map +1 -0
  290. package/dist/schema/reading.d.ts.map +1 -1
  291. package/dist/schema/reading.js +82 -27
  292. package/dist/schema/reading.js.map +1 -1
  293. package/dist/schema/schema.d.ts.map +1 -1
  294. package/dist/schema/schema.js +11 -4
  295. package/dist/schema/schema.js.map +1 -1
  296. package/dist/schema/types.d.ts +17 -0
  297. package/dist/schema/types.d.ts.map +1 -1
  298. package/dist/search.d.ts.map +1 -1
  299. package/dist/search.js +30 -2
  300. package/dist/search.js.map +1 -1
  301. package/dist/seat-of.d.ts +9 -0
  302. package/dist/seat-of.d.ts.map +1 -0
  303. package/dist/seat-of.js +14 -0
  304. package/dist/seat-of.js.map +1 -0
  305. package/dist/seen.d.ts +0 -12
  306. package/dist/seen.d.ts.map +1 -1
  307. package/dist/seen.js +5 -0
  308. package/dist/seen.js.map +1 -1
  309. package/dist/store.d.ts +58 -0
  310. package/dist/store.d.ts.map +1 -1
  311. package/dist/store.js +96 -11
  312. package/dist/store.js.map +1 -1
  313. package/dist/theme/derive.d.ts +16 -0
  314. package/dist/theme/derive.d.ts.map +1 -1
  315. package/dist/theme/derive.js +14 -0
  316. package/dist/theme/derive.js.map +1 -1
  317. package/dist/theme/marks.d.ts +19 -2
  318. package/dist/theme/marks.d.ts.map +1 -1
  319. package/dist/theme/marks.js +67 -2
  320. package/dist/theme/marks.js.map +1 -1
  321. package/dist/theme/types.d.ts +7 -2
  322. package/dist/theme/types.d.ts.map +1 -1
  323. package/dist/theme/types.js.map +1 -1
  324. package/dist/tidying.d.ts +62 -0
  325. package/dist/tidying.d.ts.map +1 -0
  326. package/dist/tidying.js +131 -0
  327. package/dist/tidying.js.map +1 -0
  328. package/dist/typed.d.ts +1 -1
  329. package/dist/typed.d.ts.map +1 -1
  330. package/dist/version.d.ts +1 -1
  331. package/dist/version.js +1 -1
  332. package/dist/via.d.ts.map +1 -1
  333. package/dist/via.js +2 -1
  334. package/dist/via.js.map +1 -1
  335. package/dist/views/address.d.ts +13 -0
  336. package/dist/views/address.d.ts.map +1 -0
  337. package/dist/views/address.js +29 -0
  338. package/dist/views/address.js.map +1 -0
  339. package/dist/views/types.d.ts +52 -3
  340. package/dist/views/types.d.ts.map +1 -1
  341. package/dist/views/types.js +4 -3
  342. package/dist/views/types.js.map +1 -1
  343. package/package.json +21 -1
@@ -1,4 +1,4 @@
1
- import { DERIVED_ACTS, derivedActOf, derivedActsOf, givenWhenNeverArchived } from "../mutations/derived-names.js";
1
+ import { DERIVED_ACTS, derivedActOf, derivedActsOf, givenWhenNeverArchived, givenWhenNeverTidied } from "../mutations/derived-names.js";
2
2
  import { z } from "zod";
3
3
  import { ExprSyntaxError, parseExpr } from "./expr/parse.js";
4
4
  import { printExpr } from "./expr/print.js";
@@ -16,10 +16,12 @@ import { farEnd } from "./far-end.js";
16
16
  import { ActSpec, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
17
17
  import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES } from "../places.js";
18
18
  import { placeSlug } from "../views/types.js";
19
+ import { documentListFields, kindListFindings, kindListWords, listGroup, listSort, sortTypeOfDocumentField } from "../kind-list.js";
19
20
  import { computedOf, validateComputed } from "./computed.js";
20
21
  import { parseTemplate, templateBraces, TemplateError } from "./template-parse.js";
21
22
  import { validateViews, VIEW_SLOTS } from "./views.js";
22
- import { brandFindings } from "./brand-check.js";
23
+ import { brandFindings, emojiIconSentence } from "./brand-check.js";
24
+ import { emojiOf } from "../theme/marks.js";
23
25
  import { canonicalize } from "./canonical.js";
24
26
  import { quotedAtTheEnd } from "./quote.js";
25
27
  import { isRecord as isObject, respellDocument } from "./respell.js";
@@ -29,7 +31,7 @@ import { accentProblem } from "../theme/accent.js";
29
31
  import { pageFieldFindings, pageNames, renamedOnPage, withoutOnPage } from "./page-fields.js";
30
32
  import { capitalize, listed as listOf } from "../capital.js";
31
33
  import { quoted } from "./quote.js";
32
- import { FIRST_GRANT, FIRST_SIGHT, grantBody, grantGone, grantSentence, NO_GRANT_LEFT, NO_SIGHT_LEFT, peopleKinds, peopleSentence, archivesSentence, nounWords, pluralWords as pluralOf, sightGone, sightSentence, sightWords, whoWords } from "./policy-words.js";
34
+ 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";
33
35
  /** The ops, for tool schemas and the guide. */
34
36
  export const EDIT_OPS = [
35
37
  "add-kind",
@@ -62,11 +64,13 @@ export const EDIT_OPS = [
62
64
  "set-page-fields",
63
65
  "add-lens",
64
66
  "remove-lens",
67
+ "set-lens-hidden",
65
68
  "set-home",
66
69
  "arrange-pages",
67
70
  "set-computed",
68
71
  "set-people",
69
72
  "set-archives",
73
+ "set-tidying",
70
74
  "add-grant",
71
75
  "remove-grant",
72
76
  "add-sight",
@@ -77,6 +81,10 @@ const KEYWORDS = new Set(["and", "or", "not", "in", "where", "true", "false", "n
77
81
  const MAX_EDITS = 50;
78
82
  // ── shapes ─────────────────────────────────────────────────────────────────
79
83
  const kindName = z.string().regex(NAME, 'kind names are lower-case words joined by hyphens, like "guest-list"');
84
+ const listKey = z.string().min(1).max(120);
85
+ const listDirection = z.enum(["asc", "desc"]).optional();
86
+ const listSortShape = z.union([listKey, z.object({ by: listKey, direction: listDirection, then: z.union([listKey, z.object({ by: listKey, direction: listDirection }).strict()]).optional() }).strict()]);
87
+ const listGroupShape = z.union([listKey, z.object({ by: listKey, order: z.array(z.string().min(1).max(80)).max(40).optional(), closed: z.array(z.string().min(1).max(80)).max(40).optional() }).strict()]);
80
88
  const fieldName = z.string().regex(FIELD_NAME, 'field names are one word or camelCase, like "dueDate"');
81
89
  const edgeName = z.string().regex(EDGE_NAME, 'relation names are one camelCase word, like "tendedBy"');
82
90
  const actName = z.string().regex(NAME, 'act names are lower-case words joined by hyphens, like "mark-paid"');
@@ -111,7 +119,7 @@ const SHAPES = {
111
119
  locale: z.union([z.string(), z.null()]).optional(),
112
120
  // The rest of the brand (FR-124, FR-125): each as the document holds it, null to clear it.
113
121
  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(),
114
- favicon: z.union([z.string().min(1), z.null()]).optional(),
122
+ favicon: z.union([z.string().min(1), z.object({ emoji: z.string() }).strict(), z.null()]).optional(),
115
123
  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(),
116
124
  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(),
117
125
  accents: z.union([z.record(z.string(), z.union([z.number().min(0).max(360), z.null()])), z.null()]).optional(),
@@ -142,11 +150,15 @@ const SHAPES = {
142
150
  on: kindName.optional(),
143
151
  bindings: z.record(z.string(), z.unknown()).optional(),
144
152
  options: z.record(z.string(), z.unknown()).optional(),
153
+ // False: the place's page leaves off the forms that begin its kind.
154
+ create: z.boolean().optional(),
145
155
  at: z.number().int().min(0).optional(),
146
156
  replace: lensTitle.optional(),
147
157
  })
148
158
  .strict(),
149
159
  "remove-lens": z.object({ op: z.literal("remove-lens"), title: lensTitle, on: kindName.optional() }).strict(),
160
+ // A lens kept off the bar's places, the page list and the home's gallery, still reached by its address, a link or navigate.
161
+ "set-lens-hidden": z.object({ op: z.literal("set-lens-hidden"), title: lensTitle, on: kindName.optional(), hidden: z.boolean() }).strict(),
150
162
  "set-home": z.object({ op: z.literal("set-home"), blocks: z.union([z.array(z.unknown()).min(1), z.null()]) }).strict(),
151
163
  "arrange-pages": z
152
164
  .object({
@@ -159,6 +171,10 @@ const SHAPES = {
159
171
  pages: z.union([z.string().min(1).max(40), z.null()]).optional(),
160
172
  // What a kind's list offers to filter by: `{ "request": ["status", "became.stage"] }`; null or [] gives a kind back its derived filters.
161
173
  filters: z.record(kindName, z.union([z.array(z.string().min(1).max(120)).max(40), z.null()])).optional(),
174
+ // The order a kind's list opens in: `{ "feature": { "by": "score", "direction": "desc", "then": "label" } }` or a key; null gives it back its own.
175
+ sort: z.record(kindName, z.union([listSortShape, z.null()])).optional(),
176
+ // How a kind's list opens grouped: `{ "request": { "by": "status", "order": [...], "closed": ["done"] } }` or a field; null opens it ungrouped.
177
+ group: z.record(kindName, z.union([listGroupShape, z.null()])).optional(),
162
178
  // The scene's word as a chat or a tool written before 0.1.17 sends it (FR-132's key, RESPELLED as `scene`).
163
179
  overview: z.union([z.string().min(1).max(40), z.null()]).optional(),
164
180
  })
@@ -177,6 +193,8 @@ const SHAPES = {
177
193
  "set-people": z.object({ op: z.literal("set-people"), kind: kindName, people: z.boolean() }).strict(),
178
194
  // Whether a kind's records are archived and merged, or only kept and removed (FR-173).
179
195
  "set-archives": z.object({ op: z.literal("set-archives"), kind: kindName, archives: z.boolean() }).strict(),
196
+ // What the app's own actor may do on its own to a kind, or to one relation of it (`@graview/core/tidying`).
197
+ "set-tidying": z.object({ op: z.literal("set-tidying"), kind: kindName, relation: z.optional(z.string()), tidying: z.enum(["never", "no-links", "allowed"]) }).strict(),
180
198
  "add-grant": z.object({ op: z.literal("add-grant") }).passthrough(),
181
199
  "remove-grant": z.object({ op: z.literal("remove-grant") }).passthrough(),
182
200
  "add-sight": z.object({ op: z.literal("add-sight") }).passthrough(),
@@ -500,7 +518,7 @@ class Editor {
500
518
  const emptyPart = ["typography", "shape", "accents"].find((key) => isObject(e[key]) && Object.keys(e[key]).length === 0);
501
519
  if (emptyPart)
502
520
  return this.fail(i, emptyPart, `${emptyPart} names no part to set; name one, or give null to clear all of it`);
503
- 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 } : {}) });
521
+ const judged = brandFindings({ ...(typeof e.logo === "string" || isObject(e.logo) ? { logo: e.logo } : {}), ...(typeof e.favicon === "string" || isObject(e.favicon) ? { 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 } : {}) });
504
522
  if (judged.length > 0)
505
523
  return void this.findings.push(...judged);
506
524
  if (e.accent === null) {
@@ -558,6 +576,8 @@ class Editor {
558
576
  const alt = (mark) => (isObject(mark) && typeof mark["alt"] === "string" ? mark["alt"] : undefined);
559
577
  if (e[key] === null)
560
578
  this.said.push(`The app has no ${what}.`);
579
+ else if (key === "favicon" && emojiOf(e[key]) !== undefined)
580
+ this.said.push(emojiIconSentence(emojiOf(e[key]), brand["logo"] !== undefined));
561
581
  else if (was === undefined)
562
582
  this.said.push(`The app gets a ${what}.`);
563
583
  else if (canonicalize(was) === canonicalize(e[key]))
@@ -831,6 +851,8 @@ class Editor {
831
851
  return this.addLens(i, e);
832
852
  case "remove-lens":
833
853
  return this.removeLens(i, e);
854
+ case "set-lens-hidden":
855
+ return this.setLensHidden(i, e);
834
856
  case "set-home":
835
857
  return this.setHome(i, e.blocks);
836
858
  case "arrange-pages":
@@ -841,6 +863,8 @@ class Editor {
841
863
  return this.setPeople(i, e);
842
864
  case "set-archives":
843
865
  return this.setArchives(i, e);
866
+ case "set-tidying":
867
+ return this.setTidying(i, e);
844
868
  case "add-grant":
845
869
  case "remove-grant":
846
870
  case "add-sight":
@@ -971,7 +995,8 @@ class Editor {
971
995
  return;
972
996
  was = lenses[at];
973
997
  }
974
- const lens = { name, title: e.title, ...(e.on !== undefined ? { on: e.on } : {}), ...(e.bindings !== undefined ? { bindings: e.bindings } : {}), ...(e.options !== undefined ? { options: e.options } : {}) };
998
+ // A lens drawn anew in another's place stays off the lists if that one was.
999
+ const lens = { name, title: e.title, ...(e.on !== undefined ? { on: e.on } : {}), ...(e.bindings !== undefined ? { bindings: e.bindings } : {}), ...(e.options !== undefined ? { options: e.options } : {}), ...(was?.["hidden"] === true ? { hidden: true } : {}), ...(e.create === false ? { create: false } : {}) };
975
1000
  // A title is an address: one lens a title on a kind.
976
1001
  const clash = lenses.findIndex((other, index) => index !== at && isObject(other) && typeof other["title"] === "string" && placeSlug(other["title"]) === placeSlug(e.title) && (e.on === undefined || other["on"] === undefined || other["on"] === e.on));
977
1002
  if (clash >= 0)
@@ -993,8 +1018,9 @@ class Editor {
993
1018
  return;
994
1019
  const over = lens.on ?? kindsBound(lens)[0];
995
1020
  const where = over ? `, over ${pluralWords(this.doc, over)}` : "";
1021
+ const formless = lens.create === false ? "; its page offers no form to add one" : "";
996
1022
  if (!was) {
997
- this.said.push(`A lens "${e.title}" is added${where}.`);
1023
+ this.said.push(`A lens "${e.title}" is added${where}${formless}.`);
998
1024
  return;
999
1025
  }
1000
1026
  const retitled = was["title"] !== e.title;
@@ -1035,6 +1061,31 @@ class Editor {
1035
1061
  }
1036
1062
  return true;
1037
1063
  }
1064
+ /**
1065
+ * A LENS KEPT OFF THE LISTS, or put back on them: still a place at its
1066
+ * address, reached by a link or `navigate`, but not offered by the bar,
1067
+ * the page list or the home's gallery. A lens a custom home links to.
1068
+ */
1069
+ setLensHidden(i, e) {
1070
+ const index = this.lensIndex(i, e.title, e.on);
1071
+ if (index < 0)
1072
+ return;
1073
+ const lens = this.doc.lenses[index];
1074
+ const title = String(lens["title"]);
1075
+ const primary = Array.isArray(this.doc.pages?.primary) ? this.doc.pages.primary : [];
1076
+ if (e.hidden && primary.some((word) => namesLens(word, title))) {
1077
+ return this.fail(i, "hidden", `pages.primary stands "${title}" on the bar, so it cannot be kept off the lists`, 'take it out of "primary" with arrange-pages first');
1078
+ }
1079
+ const was = lens["hidden"] === true;
1080
+ if (e.hidden)
1081
+ lens["hidden"] = true;
1082
+ else
1083
+ delete lens["hidden"];
1084
+ if (was === e.hidden)
1085
+ this.said.push(e.hidden ? `The lens "${title}" was already kept off the lists.` : `The lens "${title}" was already listed.`);
1086
+ else
1087
+ this.said.push(e.hidden ? `The lens "${title}" is kept off the bar and the page list; a link or navigate still reaches it.` : `The lens "${title}" is listed on the bar and the page list again.`);
1088
+ }
1038
1089
  removeLens(i, e) {
1039
1090
  const index = this.lensIndex(i, e.title, e.on);
1040
1091
  if (index < 0)
@@ -1046,8 +1097,20 @@ class Editor {
1046
1097
  // `overview` is the scene's word as 0.1.15 and 0.1.16 took it (FR-134): read as `scene`, which wins when both are said.
1047
1098
  const { overview, ...rest } = given;
1048
1099
  const e = rest.scene === undefined && overview !== undefined ? { ...rest, scene: overview } : rest;
1049
- if (e.order === undefined && e.hide === undefined && e.first === undefined && e.scene === undefined && e.pages === undefined && e.primary === undefined && e.filters === undefined)
1050
- return this.fail(i, "", 'arrange-pages says at least one of "order", "hide", "first", "primary", "scene", "pages" or "filters"');
1100
+ if (e.order === undefined && e.hide === undefined && e.first === undefined && e.scene === undefined && e.pages === undefined && e.primary === undefined && e.filters === undefined && e.sort === undefined && e.group === undefined)
1101
+ return this.fail(i, "", 'arrange-pages says at least one of "order", "hide", "first", "primary", "scene", "pages", "filters", "sort" or "group"');
1102
+ // A list's order and grouping are judged as the checker judges them, each refusal its sentence.
1103
+ for (const part of ["sort", "group"]) {
1104
+ for (const [kind, spec] of Object.entries((e[part] ?? {}))) {
1105
+ if (!this.doc.kinds[kind])
1106
+ return this.fail(i, `${part}.${kind}`, `"${kind}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`);
1107
+ if (spec === null)
1108
+ continue;
1109
+ const [finding] = kindListFindings(kind, { [part]: spec }, documentListFields(this.doc.kinds[kind]), `${part}.${kind}`);
1110
+ if (finding)
1111
+ return this.fail(i, finding.path.replace(new RegExp(`^${part}\\.${kind}\\.${part}`), `${part}.${kind}`), finding.message.replace(/\.$/, ""), finding.fix.replace(/\.$/, ""));
1112
+ }
1113
+ }
1051
1114
  for (const [kind, keys] of Object.entries((e.filters ?? {}))) {
1052
1115
  if (!this.doc.kinds[kind])
1053
1116
  return this.fail(i, `filters.${kind}`, `"${kind}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`);
@@ -1135,23 +1198,133 @@ class Editor {
1135
1198
  said.push(`the switch calls the ${face} "${pages[face]}"`);
1136
1199
  }
1137
1200
  }
1138
- for (const [kind, keys] of Object.entries((e.filters ?? {}))) {
1201
+ // Each kind's list keeps what this edit does not say: its filters, its order, its grouping.
1202
+ const listOfKind = (kind, part, value) => {
1139
1203
  const lists = (pages.lists ??= {});
1140
- if (keys === null || keys.length === 0) {
1204
+ const list = (lists[kind] ??= {});
1205
+ if (value === null)
1206
+ delete list[part];
1207
+ else
1208
+ list[part] = value;
1209
+ if (Object.keys(list).length === 0)
1141
1210
  delete lists[kind];
1211
+ if (Object.keys(lists).length === 0)
1212
+ delete pages.lists;
1213
+ };
1214
+ for (const [kind, keys] of Object.entries((e.filters ?? {}))) {
1215
+ if (keys === null || keys.length === 0) {
1216
+ listOfKind(kind, "filters", null);
1142
1217
  said.push(`the list of ${pluralWords(this.doc, kind)} offers its own filters again`);
1143
1218
  }
1144
1219
  else {
1145
- lists[kind] = { filters: [...keys] };
1220
+ listOfKind(kind, "filters", [...keys]);
1146
1221
  said.push(`the list of ${pluralWords(this.doc, kind)} filters by ${listOf(keys.map((key) => `"${key}"`))}`);
1147
1222
  }
1148
- if (Object.keys(lists).length === 0)
1149
- delete pages.lists;
1223
+ }
1224
+ for (const part of ["sort", "group"]) {
1225
+ for (const [kind, spec] of Object.entries((e[part] ?? {}))) {
1226
+ listOfKind(kind, part, spec === null ? null : clone(spec));
1227
+ const plural = pluralWords(this.doc, kind);
1228
+ if (spec === null)
1229
+ said.push(part === "sort" ? `the list of ${plural} opens in its own order again` : `the list of ${plural} opens ungrouped again`);
1230
+ else
1231
+ said.push(`the list of ${plural} opens ${kindListWords({ [part]: spec }, this.listKeyType(kind))[part]}`);
1232
+ }
1150
1233
  }
1151
1234
  if (Object.keys(pages).length === 0)
1152
1235
  delete this.doc.pages;
1153
1236
  this.said.push(`${capitalize(listOf(said))}.`);
1154
1237
  }
1238
+ /**
1239
+ * A KIND'S LIST FOLLOWS ITS FIELDS. Its order and grouping name a field
1240
+ * renamed by its new name; one gone (or a computed field no longer worked
1241
+ * out) lets go of the part that named it — a tiebreak, the order, the
1242
+ * grouping — and says so. A list left saying nothing is unsaid.
1243
+ */
1244
+ listsFollow(kind, from, to) {
1245
+ const list = this.doc.pages?.lists?.[kind];
1246
+ if (!list)
1247
+ return [];
1248
+ const out = [];
1249
+ const sort = listSort(list.sort);
1250
+ if (sort && (sort.by === from || sort.then?.by === from)) {
1251
+ if (to !== undefined) {
1252
+ if (typeof list.sort === "string")
1253
+ list.sort = to;
1254
+ else {
1255
+ if (list.sort.by === from)
1256
+ list.sort.by = to;
1257
+ if (list.sort.then === from)
1258
+ list.sort.then = to;
1259
+ else if (isObject(list.sort.then) && list.sort.then["by"] === from)
1260
+ list.sort.then.by = to;
1261
+ }
1262
+ }
1263
+ else if (sort.by === from)
1264
+ delete list.sort;
1265
+ else
1266
+ delete list.sort.then;
1267
+ out.push(`the ${kind} list's order`);
1268
+ }
1269
+ if (listGroup(list.group)?.by === from) {
1270
+ if (to === undefined)
1271
+ delete list.group;
1272
+ else if (typeof list.group === "string")
1273
+ list.group = to;
1274
+ else
1275
+ list.group.by = to;
1276
+ out.push(`the ${kind} list's grouping`);
1277
+ }
1278
+ this.listsTidied(kind);
1279
+ return out;
1280
+ }
1281
+ /** A field retyped or a choice dropped: the list's order and grouping keep what still holds, and let go of what does not. */
1282
+ listsHold(kind, removedOptions = []) {
1283
+ const list = this.doc.pages?.lists?.[kind];
1284
+ if (!list)
1285
+ return [];
1286
+ const out = [];
1287
+ if (isObject(list.group) && removedOptions.length > 0) {
1288
+ for (const part of ["order", "closed"]) {
1289
+ if (!Array.isArray(list.group[part]))
1290
+ continue;
1291
+ const kept = list.group[part].filter((value) => !removedOptions.includes(value));
1292
+ if (kept.length === list.group[part].length)
1293
+ continue;
1294
+ if (kept.length > 0)
1295
+ list.group[part] = kept;
1296
+ else
1297
+ delete list.group[part];
1298
+ if (!out.includes(`the ${kind} list's grouping`))
1299
+ out.push(`the ${kind} list's grouping`);
1300
+ }
1301
+ }
1302
+ for (const finding of kindListFindings(kind, list, documentListFields(this.doc.kinds[kind]))) {
1303
+ const part = finding.path.slice(`pages.lists.${kind}.`.length).startsWith("sort") ? "sort" : "group";
1304
+ if (list[part] === undefined)
1305
+ continue;
1306
+ delete list[part];
1307
+ out.push(`the ${kind} list's ${part === "sort" ? "order" : "grouping"}`);
1308
+ }
1309
+ this.listsTidied(kind);
1310
+ return out;
1311
+ }
1312
+ listsTidied(kind) {
1313
+ const lists = this.doc.pages?.lists;
1314
+ if (!lists)
1315
+ return;
1316
+ if (lists[kind] && Object.keys(lists[kind]).length === 0)
1317
+ delete lists[kind];
1318
+ if (Object.keys(lists).length === 0)
1319
+ delete this.doc.pages.lists;
1320
+ if (Object.keys(this.doc.pages).length === 0)
1321
+ delete this.doc.pages;
1322
+ }
1323
+ /** What a key of a kind's list reads as, for the words its order is said in: a computed field is a number. */
1324
+ listKeyType(kind) {
1325
+ const spec = this.doc.kinds[kind];
1326
+ return (key) => (spec?.computed?.[key] !== undefined ? "number" : sortTypeOfDocumentField(spec?.fields?.[key]?.type));
1327
+ }
1155
1328
  /** What a kind's list may be filtered by: its fields, its relations either way, `is`, and a choice at a relation's single far kind. */
1156
1329
  filterKeys(kind) {
1157
1330
  const kinds = this.doc.kinds;
@@ -1287,6 +1460,51 @@ class Editor {
1287
1460
  this.toolsMoved.add(`archive-${e.kind}, unarchive-${e.kind} and merge-${e.kind} ${e.archives ? "are offered" : "are gone"}`);
1288
1461
  this.said.push(archivesSentence(this.doc.kinds, e.kind, e.archives, already));
1289
1462
  }
1463
+ /**
1464
+ * WHAT THE APP'S OWN ACTOR MAY DO ON ITS OWN (`@graview/core/tidying`):
1465
+ * a kind never tidied, or never linked automatically, or tidied again; a
1466
+ * relation never made or broken automatically, or tidied again. A kind
1467
+ * never tidied is given no leave-alone-<kind> or allow-tidying-<kind>,
1468
+ * which a grant may not then name.
1469
+ */
1470
+ setTidying(i, e) {
1471
+ const spec = this.kind(i, e.kind);
1472
+ if (!spec)
1473
+ return;
1474
+ const tidying = e.tidying;
1475
+ if (e.relation !== undefined) {
1476
+ const relation = spec.edges?.[e.relation];
1477
+ if (!relation)
1478
+ return this.failAs("edit", i, "relation", `${e.kind} has no relation "${e.relation}"; it has ${Object.keys(spec.edges ?? {}).join(", ") || "none"}`);
1479
+ if (tidying === "no-links")
1480
+ return this.failAs("edit", i, "tidying", `a relation is tidied or never ("never"); "no-links" is said of a kind`, `{"op": "set-tidying", "kind": "${e.kind}", "relation": "${e.relation}", "tidying": "never"}`);
1481
+ const already = (relation.tidying === "never") === (tidying === "never");
1482
+ if (tidying === "never")
1483
+ relation.tidying = "never";
1484
+ else
1485
+ delete relation.tidying;
1486
+ this.said.push(tidyingSentence(this.doc.kinds, e.kind, e.relation, tidying, already));
1487
+ return;
1488
+ }
1489
+ const was = spec.tidying ?? "allowed";
1490
+ const already = was === tidying;
1491
+ if (tidying === "never" && !already) {
1492
+ // A grant that names leave-alone-<kind> or allow-tidying-<kind> would name an act the app no longer has.
1493
+ const gone = new Set(["leave-alone", "allow-tidying"].map((verb) => `${verb}-${e.kind}`).filter((act) => !this.doc.acts?.[act]));
1494
+ const grant = (this.doc.policy?.grants ?? []).find((g) => Array.isArray(g.mutations) && g.mutations.some((m) => gone.has(m)));
1495
+ if (grant) {
1496
+ const named = grant.mutations.filter((m) => gone.has(m));
1497
+ return this.failAs("grant-unknown-mutation", i, "tidying", `a grant names ${listOf(named.map((m) => `"${m}"`))}, which ${pluralOf(this.doc.kinds, e.kind)} would no longer have`, `{"op": "remove-grant", "roles": ${JSON.stringify(grant.roles)}, "mutations": ${JSON.stringify(grant.mutations)}} first, in the same change`);
1498
+ }
1499
+ }
1500
+ if (tidying === "allowed")
1501
+ delete spec.tidying;
1502
+ else
1503
+ spec.tidying = tidying;
1504
+ if (!already && (was === "never" || tidying === "never"))
1505
+ this.toolsMoved.add(`leave-alone-${e.kind} and allow-tidying-${e.kind} ${tidying === "never" ? "are gone" : "are offered"}`);
1506
+ this.said.push(tidyingSentence(this.doc.kinds, e.kind, undefined, tidying, already));
1507
+ }
1290
1508
  /** A grant or a sight as the document holds one, or undefined having said why not. */
1291
1509
  policyPart(i, e, sight) {
1292
1510
  const { op: _op, ...rest } = e;
@@ -1322,7 +1540,7 @@ class Editor {
1322
1540
  if (!sight && part.mutations !== "*") {
1323
1541
  for (const [n, act] of part.mutations.entries()) {
1324
1542
  const derived = derivedActOf(act);
1325
- if (this.doc.acts?.[act] || (derived && this.doc.kinds[derived.kind] && (this.doc.kinds[derived.kind].archives !== false || givenWhenNeverArchived(derived.act))))
1543
+ 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))))
1326
1544
  continue;
1327
1545
  return this.failAs("grant-act", i, `mutations.${n}`, `"${act}" is not an act this app has`, `one of ${[...Object.keys(this.doc.acts ?? {}), ...Object.keys(this.doc.kinds).flatMap(derivedActsOf)].join(", ")}`);
1328
1546
  }
@@ -1455,6 +1673,13 @@ class Editor {
1455
1673
  spec.plural = e.plural;
1456
1674
  else if (spec.plural && (spec.plural === `${words(from)}s` || spec.plural === `${from}s`))
1457
1675
  spec.plural = `${words(to)}s`;
1676
+ // Its list's filters, order and grouping follow it.
1677
+ const lists = this.doc.pages?.lists;
1678
+ if (lists?.[from] !== undefined && lists[to] === undefined) {
1679
+ lists[to] = lists[from];
1680
+ delete lists[from];
1681
+ touched.push(`its list`);
1682
+ }
1458
1683
  // Where the app opens, when it named the kind by its plural, follows the plural (FR-80).
1459
1684
  const pluralNow = spec.plural ?? `${words(to)}s`;
1460
1685
  if (typeof this.doc.pages?.first === "string" && placeSlug(this.doc.pages.first) === placeSlug(pluralWas) && placeSlug(pluralWas) !== placeSlug(pluralNow)) {
@@ -1675,6 +1900,7 @@ class Editor {
1675
1900
  spec.page = renamedOnPage(spec.page, field, to);
1676
1901
  touched.push(`${kind}'s page`);
1677
1902
  }
1903
+ touched.push(...this.listsFollow(kind, field, to));
1678
1904
  for (const x of this.fills) {
1679
1905
  if (x.kind === kind && x.field === field)
1680
1906
  x.field = to;
@@ -1722,6 +1948,10 @@ class Editor {
1722
1948
  else if (f.default !== undefined)
1723
1949
  f.default = coerce(f.default, was, f);
1724
1950
  const said = f.format && f.format !== "markdown" ? formatWords(f, this.doc.brand) : undefined;
1951
+ // A list grouped by a choice that is no longer one opens ungrouped, and says so.
1952
+ const held = this.listsHold(e.kind);
1953
+ if (held.length > 0)
1954
+ 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}`}.`);
1725
1955
  if (was === e.type && e.type === "text")
1726
1956
  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.`);
1727
1957
  else
@@ -1815,6 +2045,9 @@ class Editor {
1815
2045
  delete f.default;
1816
2046
  out.push("has no default now");
1817
2047
  }
2048
+ const held = this.listsHold(e.kind, remove);
2049
+ if (held.length > 0)
2050
+ out.push(`${listOf(held)} ${held.length === 1 ? "lets" : "let"} go of ${remove.length === 1 ? "it" : "them"}`);
1818
2051
  // An act that sets a dropped option cannot stand.
1819
2052
  for (const [name, act] of Object.entries(this.doc.acts ?? {})) {
1820
2053
  for (const t of setTargets(act, this.doc))
@@ -2283,6 +2516,9 @@ class Editor {
2283
2516
  dropMentions(r, worked = false) {
2284
2517
  const doc = this.doc;
2285
2518
  const gone = [];
2519
+ // A kind's list ordered or grouped by it lets that go (a stored field removed, a computed one no longer worked out).
2520
+ if (r.t === "field")
2521
+ gone.push(...this.listsFollow(r.kind, r.from, undefined));
2286
2522
  for (const [kind, spec] of Object.entries(doc.kinds)) {
2287
2523
  for (const key of ["label", "describe"]) {
2288
2524
  if (templateMentions(doc, spec[key], new Set([kind]), r)) {