@graview/core 0.1.61 → 0.1.63

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 (464) hide show
  1. package/README.md +13 -1
  2. package/dist/app-places.d.ts.map +1 -1
  3. package/dist/app-places.js +2 -2
  4. package/dist/app-places.js.map +1 -1
  5. package/dist/app.d.ts +22 -0
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js.map +1 -1
  8. package/dist/arranging.js +2 -2
  9. package/dist/arranging.js.map +1 -1
  10. package/dist/capabilities.js +1 -1
  11. package/dist/capabilities.js.map +1 -1
  12. package/dist/cli/check/hidden-reads.d.ts.map +1 -1
  13. package/dist/cli/check/hidden-reads.js +26 -0
  14. package/dist/cli/check/hidden-reads.js.map +1 -1
  15. package/dist/cli/check/index.d.ts +2 -0
  16. package/dist/cli/check/index.d.ts.map +1 -1
  17. package/dist/cli/check/index.js +2 -0
  18. package/dist/cli/check/index.js.map +1 -1
  19. package/dist/cli/check/tidying.d.ts +14 -0
  20. package/dist/cli/check/tidying.d.ts.map +1 -0
  21. package/dist/cli/check/tidying.js +24 -0
  22. package/dist/cli/check/tidying.js.map +1 -0
  23. package/dist/cli/check/workflows.d.ts +11 -0
  24. package/dist/cli/check/workflows.d.ts.map +1 -0
  25. package/dist/cli/check/workflows.js +20 -0
  26. package/dist/cli/check/workflows.js.map +1 -0
  27. package/dist/cli/check.d.ts.map +1 -1
  28. package/dist/cli/check.js +3 -1
  29. package/dist/cli/check.js.map +1 -1
  30. package/dist/cli/describe.d.ts.map +1 -1
  31. package/dist/cli/describe.js +45 -2
  32. package/dist/cli/describe.js.map +1 -1
  33. package/dist/cli/document-schema.d.ts.map +1 -1
  34. package/dist/cli/document-schema.js +15 -2
  35. package/dist/cli/document-schema.js.map +1 -1
  36. package/dist/cli/index.js +1 -1
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/compose.d.ts +11 -3
  39. package/dist/compose.d.ts.map +1 -1
  40. package/dist/compose.js +12 -2
  41. package/dist/compose.js.map +1 -1
  42. package/dist/css/sanitize.d.ts +125 -0
  43. package/dist/css/sanitize.d.ts.map +1 -0
  44. package/dist/css/sanitize.js +989 -0
  45. package/dist/css/sanitize.js.map +1 -0
  46. package/dist/css/tables.d.ts +55 -0
  47. package/dist/css/tables.d.ts.map +1 -0
  48. package/dist/css/tables.js +137 -0
  49. package/dist/css/tables.js.map +1 -0
  50. package/dist/css.d.ts +20 -0
  51. package/dist/css.d.ts.map +1 -0
  52. package/dist/css.js +19 -0
  53. package/dist/css.js.map +1 -0
  54. package/dist/days.d.ts +15 -0
  55. package/dist/days.d.ts.map +1 -1
  56. package/dist/days.js +21 -0
  57. package/dist/days.js.map +1 -1
  58. package/dist/describe.d.ts +1 -1
  59. package/dist/describe.d.ts.map +1 -1
  60. package/dist/document/act-findings.d.ts.map +1 -1
  61. package/dist/document/act-findings.js +80 -1
  62. package/dist/document/act-findings.js.map +1 -1
  63. package/dist/document/amount.d.ts +1 -1
  64. package/dist/document/amount.d.ts.map +1 -1
  65. package/dist/document/amount.js +2 -2
  66. package/dist/document/amount.js.map +1 -1
  67. package/dist/document/blocks.d.ts.map +1 -1
  68. package/dist/document/blocks.js +17 -1
  69. package/dist/document/blocks.js.map +1 -1
  70. package/dist/document/brand-check.d.ts +15 -1
  71. package/dist/document/brand-check.d.ts.map +1 -1
  72. package/dist/document/brand-check.js +80 -0
  73. package/dist/document/brand-check.js.map +1 -1
  74. package/dist/document/brand.d.ts.map +1 -1
  75. package/dist/document/brand.js +2 -0
  76. package/dist/document/brand.js.map +1 -1
  77. package/dist/document/compile-checked.d.ts.map +1 -1
  78. package/dist/document/compile-checked.js +5 -2
  79. package/dist/document/compile-checked.js.map +1 -1
  80. package/dist/document/compile.d.ts.map +1 -1
  81. package/dist/document/compile.js +96 -16
  82. package/dist/document/compile.js.map +1 -1
  83. package/dist/document/compiled.d.ts +32 -3
  84. package/dist/document/compiled.d.ts.map +1 -1
  85. package/dist/document/compiled.js +123 -12
  86. package/dist/document/compiled.js.map +1 -1
  87. package/dist/document/connection-findings.js +165 -22
  88. package/dist/document/connection-findings.js.map +1 -1
  89. package/dist/document/connection-spec.d.ts +38 -1
  90. package/dist/document/connection-spec.d.ts.map +1 -1
  91. package/dist/document/connection-spec.js +71 -7
  92. package/dist/document/connection-spec.js.map +1 -1
  93. package/dist/document/covers.d.ts +48 -0
  94. package/dist/document/covers.d.ts.map +1 -0
  95. package/dist/document/covers.js +90 -0
  96. package/dist/document/covers.js.map +1 -0
  97. package/dist/document/derives-findings.d.ts +4 -0
  98. package/dist/document/derives-findings.d.ts.map +1 -0
  99. package/dist/document/derives-findings.js +68 -0
  100. package/dist/document/derives-findings.js.map +1 -0
  101. package/dist/document/describe-forms.js +2 -2
  102. package/dist/document/describe-forms.js.map +1 -1
  103. package/dist/document/describe-place.d.ts +3 -0
  104. package/dist/document/describe-place.d.ts.map +1 -1
  105. package/dist/document/describe-place.js +11 -0
  106. package/dist/document/describe-place.js.map +1 -1
  107. package/dist/document/described.d.ts +28 -0
  108. package/dist/document/described.d.ts.map +1 -1
  109. package/dist/document/diff.d.ts +6 -0
  110. package/dist/document/diff.d.ts.map +1 -1
  111. package/dist/document/diff.js +53 -2
  112. package/dist/document/diff.js.map +1 -1
  113. package/dist/document/edit.d.ts +1 -1
  114. package/dist/document/edit.d.ts.map +1 -1
  115. package/dist/document/edit.js +366 -29
  116. package/dist/document/edit.js.map +1 -1
  117. package/dist/document/expr/evaluate.d.ts +17 -1
  118. package/dist/document/expr/evaluate.d.ts.map +1 -1
  119. package/dist/document/expr/evaluate.js +110 -2
  120. package/dist/document/expr/evaluate.js.map +1 -1
  121. package/dist/document/expr/index.d.ts +1 -0
  122. package/dist/document/expr/index.d.ts.map +1 -1
  123. package/dist/document/expr/index.js +2 -0
  124. package/dist/document/expr/index.js.map +1 -1
  125. package/dist/document/expr/typing.d.ts +24 -0
  126. package/dist/document/expr/typing.d.ts.map +1 -0
  127. package/dist/document/expr/typing.js +219 -0
  128. package/dist/document/expr/typing.js.map +1 -0
  129. package/dist/document/expr/when.d.ts +52 -0
  130. package/dist/document/expr/when.d.ts.map +1 -0
  131. package/dist/document/expr/when.js +215 -0
  132. package/dist/document/expr/when.js.map +1 -0
  133. package/dist/document/expr-type-findings.d.ts +5 -0
  134. package/dist/document/expr-type-findings.d.ts.map +1 -0
  135. package/dist/document/expr-type-findings.js +90 -0
  136. package/dist/document/expr-type-findings.js.map +1 -0
  137. package/dist/document/format-words.d.ts +1 -0
  138. package/dist/document/format-words.d.ts.map +1 -1
  139. package/dist/document/format-words.js +2 -2
  140. package/dist/document/format-words.js.map +1 -1
  141. package/dist/document/index.d.ts +6 -3
  142. package/dist/document/index.d.ts.map +1 -1
  143. package/dist/document/index.js +5 -2
  144. package/dist/document/index.js.map +1 -1
  145. package/dist/document/kept-in-step-with.d.ts +20 -0
  146. package/dist/document/kept-in-step-with.d.ts.map +1 -0
  147. package/dist/document/kept-in-step-with.js +38 -0
  148. package/dist/document/kept-in-step-with.js.map +1 -0
  149. package/dist/document/kept-in-step.d.ts.map +1 -1
  150. package/dist/document/kept-in-step.js +3 -1
  151. package/dist/document/kept-in-step.js.map +1 -1
  152. package/dist/document/linked-findings.js +1 -1
  153. package/dist/document/linked-findings.js.map +1 -1
  154. package/dist/document/money.d.ts +10 -2
  155. package/dist/document/money.d.ts.map +1 -1
  156. package/dist/document/money.js +14 -5
  157. package/dist/document/money.js.map +1 -1
  158. package/dist/document/policy-words.d.ts +2 -0
  159. package/dist/document/policy-words.d.ts.map +1 -1
  160. package/dist/document/policy-words.js +11 -1
  161. package/dist/document/policy-words.js.map +1 -1
  162. package/dist/document/rule-line.d.ts +7 -3
  163. package/dist/document/rule-line.d.ts.map +1 -1
  164. package/dist/document/rule-line.js +17 -2
  165. package/dist/document/rule-line.js.map +1 -1
  166. package/dist/document/rule-reads.d.ts.map +1 -1
  167. package/dist/document/rule-reads.js +3 -1
  168. package/dist/document/rule-reads.js.map +1 -1
  169. package/dist/document/rules.d.ts +30 -0
  170. package/dist/document/rules.d.ts.map +1 -1
  171. package/dist/document/rules.js +74 -0
  172. package/dist/document/rules.js.map +1 -1
  173. package/dist/document/schema.d.ts +110 -2
  174. package/dist/document/schema.d.ts.map +1 -1
  175. package/dist/document/schema.js +125 -10
  176. package/dist/document/schema.js.map +1 -1
  177. package/dist/document/template-parse.d.ts.map +1 -1
  178. package/dist/document/template-parse.js +7 -1
  179. package/dist/document/template-parse.js.map +1 -1
  180. package/dist/document/template.d.ts +2 -0
  181. package/dist/document/template.d.ts.map +1 -1
  182. package/dist/document/template.js +10 -1
  183. package/dist/document/template.js.map +1 -1
  184. package/dist/document/to-document.d.ts.map +1 -1
  185. package/dist/document/to-document.js +5 -3
  186. package/dist/document/to-document.js.map +1 -1
  187. package/dist/document/workflow-spec.d.ts +72 -0
  188. package/dist/document/workflow-spec.d.ts.map +1 -0
  189. package/dist/document/workflow-spec.js +45 -0
  190. package/dist/document/workflow-spec.js.map +1 -0
  191. package/dist/document.schema.json +1143 -50
  192. package/dist/history.d.ts.map +1 -1
  193. package/dist/history.js +2 -0
  194. package/dist/history.js.map +1 -1
  195. package/dist/index.d.ts +11 -8
  196. package/dist/index.d.ts.map +1 -1
  197. package/dist/index.js +8 -5
  198. package/dist/index.js.map +1 -1
  199. package/dist/installation.d.ts +1 -1
  200. package/dist/invariants/types.d.ts +7 -2
  201. package/dist/invariants/types.d.ts.map +1 -1
  202. package/dist/job-findings.d.ts +5 -0
  203. package/dist/job-findings.d.ts.map +1 -1
  204. package/dist/job-findings.js +9 -3
  205. package/dist/job-findings.js.map +1 -1
  206. package/dist/keep-linked.d.ts +45 -0
  207. package/dist/keep-linked.d.ts.map +1 -0
  208. package/dist/keep-linked.js +188 -0
  209. package/dist/keep-linked.js.map +1 -0
  210. package/dist/lines.d.ts.map +1 -1
  211. package/dist/lines.js +6 -2
  212. package/dist/lines.js.map +1 -1
  213. package/dist/mutations/derive-edits.js +2 -2
  214. package/dist/mutations/propose-changes.d.ts +24 -0
  215. package/dist/mutations/propose-changes.d.ts.map +1 -0
  216. package/dist/mutations/propose-changes.js +30 -0
  217. package/dist/mutations/propose-changes.js.map +1 -0
  218. package/dist/mutations/types.d.ts +9 -0
  219. package/dist/mutations/types.d.ts.map +1 -1
  220. package/dist/mutations/vote-suggestion.d.ts +97 -0
  221. package/dist/mutations/vote-suggestion.d.ts.map +1 -0
  222. package/dist/mutations/vote-suggestion.js +59 -0
  223. package/dist/mutations/vote-suggestion.js.map +1 -0
  224. package/dist/mutations/words.js +3 -3
  225. package/dist/navigate.d.ts +7 -3
  226. package/dist/navigate.d.ts.map +1 -1
  227. package/dist/navigate.js +7 -3
  228. package/dist/navigate.js.map +1 -1
  229. package/dist/ops/digest.d.ts +3 -1
  230. package/dist/ops/digest.d.ts.map +1 -1
  231. package/dist/ops/digest.js +7 -2
  232. package/dist/ops/digest.js.map +1 -1
  233. package/dist/ops/lineage.d.ts +29 -2
  234. package/dist/ops/lineage.d.ts.map +1 -1
  235. package/dist/ops/lineage.js +12 -4
  236. package/dist/ops/lineage.js.map +1 -1
  237. package/dist/ops/log.d.ts.map +1 -1
  238. package/dist/ops/log.js +13 -2
  239. package/dist/ops/log.js.map +1 -1
  240. package/dist/ops/suggestions.d.ts +75 -1
  241. package/dist/ops/suggestions.d.ts.map +1 -1
  242. package/dist/ops/suggestions.js +123 -6
  243. package/dist/ops/suggestions.js.map +1 -1
  244. package/dist/ops/tidying.d.ts.map +1 -1
  245. package/dist/ops/tidying.js +6 -1
  246. package/dist/ops/tidying.js.map +1 -1
  247. package/dist/ops/types.d.ts +41 -0
  248. package/dist/ops/types.d.ts.map +1 -1
  249. package/dist/ops/types.js.map +1 -1
  250. package/dist/ops/undo.d.ts.map +1 -1
  251. package/dist/ops/undo.js +17 -0
  252. package/dist/ops/undo.js.map +1 -1
  253. package/dist/permissions/policy.d.ts +7 -0
  254. package/dist/permissions/policy.d.ts.map +1 -1
  255. package/dist/permissions/policy.js +12 -0
  256. package/dist/permissions/policy.js.map +1 -1
  257. package/dist/permissions/types.d.ts +27 -0
  258. package/dist/permissions/types.d.ts.map +1 -1
  259. package/dist/permissions/types.js.map +1 -1
  260. package/dist/place-names.d.ts +42 -0
  261. package/dist/place-names.d.ts.map +1 -0
  262. package/dist/place-names.js +43 -0
  263. package/dist/place-names.js.map +1 -0
  264. package/dist/places.d.ts +12 -0
  265. package/dist/places.d.ts.map +1 -1
  266. package/dist/places.js +37 -1
  267. package/dist/places.js.map +1 -1
  268. package/dist/scene.d.ts +2 -0
  269. package/dist/scene.d.ts.map +1 -1
  270. package/dist/scene.js +2 -0
  271. package/dist/scene.js.map +1 -1
  272. package/dist/schema/amount-mark.d.ts +2 -0
  273. package/dist/schema/amount-mark.d.ts.map +1 -1
  274. package/dist/schema/amount-mark.js.map +1 -1
  275. package/dist/schema/amount-words.js +2 -2
  276. package/dist/schema/amount-words.js.map +1 -1
  277. package/dist/schema/choice-words.js +2 -2
  278. package/dist/schema/define-node.d.ts +8 -2
  279. package/dist/schema/define-node.d.ts.map +1 -1
  280. package/dist/schema/define-node.js +2 -2
  281. package/dist/schema/define-node.js.map +1 -1
  282. package/dist/schema/humanize.d.ts +17 -0
  283. package/dist/schema/humanize.d.ts.map +1 -1
  284. package/dist/schema/humanize.js +28 -1
  285. package/dist/schema/humanize.js.map +1 -1
  286. package/dist/schema/reading.d.ts +8 -0
  287. package/dist/schema/reading.d.ts.map +1 -1
  288. package/dist/schema/reading.js +16 -2
  289. package/dist/schema/reading.js.map +1 -1
  290. package/dist/schema/types.d.ts +8 -0
  291. package/dist/schema/types.d.ts.map +1 -1
  292. package/dist/schema/value-words.js +2 -2
  293. package/dist/schema/value-words.js.map +1 -1
  294. package/dist/starting-document.d.ts +47 -2
  295. package/dist/starting-document.d.ts.map +1 -1
  296. package/dist/starting-document.js +66 -8
  297. package/dist/starting-document.js.map +1 -1
  298. package/dist/store.d.ts +75 -1
  299. package/dist/store.d.ts.map +1 -1
  300. package/dist/store.js +305 -29
  301. package/dist/store.js.map +1 -1
  302. package/dist/sync/connection.d.ts +35 -6
  303. package/dist/sync/connection.d.ts.map +1 -1
  304. package/dist/sync/connection.js +252 -47
  305. package/dist/sync/connection.js.map +1 -1
  306. package/dist/sync/engine.d.ts +83 -0
  307. package/dist/sync/engine.d.ts.map +1 -1
  308. package/dist/sync/engine.js +157 -4
  309. package/dist/sync/engine.js.map +1 -1
  310. package/dist/sync/google-calendar.d.ts +8 -0
  311. package/dist/sync/google-calendar.d.ts.map +1 -1
  312. package/dist/sync/google-calendar.js +30 -3
  313. package/dist/sync/google-calendar.js.map +1 -1
  314. package/dist/sync/guest-words.d.ts +9 -0
  315. package/dist/sync/guest-words.d.ts.map +1 -0
  316. package/dist/sync/guest-words.js +41 -0
  317. package/dist/sync/guest-words.js.map +1 -0
  318. package/dist/sync/guests.d.ts +44 -0
  319. package/dist/sync/guests.d.ts.map +1 -0
  320. package/dist/sync/guests.js +57 -0
  321. package/dist/sync/guests.js.map +1 -0
  322. package/dist/sync/index.d.ts +8 -5
  323. package/dist/sync/index.d.ts.map +1 -1
  324. package/dist/sync/index.js +2 -0
  325. package/dist/sync/index.js.map +1 -1
  326. package/dist/sync/notion-stub.d.ts +4 -1
  327. package/dist/sync/notion-stub.d.ts.map +1 -1
  328. package/dist/sync/notion-stub.js +40 -0
  329. package/dist/sync/notion-stub.js.map +1 -1
  330. package/dist/sync/notion.d.ts +43 -2
  331. package/dist/sync/notion.d.ts.map +1 -1
  332. package/dist/sync/notion.js +274 -140
  333. package/dist/sync/notion.js.map +1 -1
  334. package/dist/sync/status.d.ts +43 -0
  335. package/dist/sync/status.d.ts.map +1 -0
  336. package/dist/sync/status.js +45 -0
  337. package/dist/sync/status.js.map +1 -0
  338. package/dist/sync/types.d.ts +64 -0
  339. package/dist/sync/types.d.ts.map +1 -1
  340. package/dist/sync/types.js.map +1 -1
  341. package/dist/testing.js +1 -1
  342. package/dist/testing.js.map +1 -1
  343. package/dist/theme/contrast.d.ts +2 -0
  344. package/dist/theme/contrast.d.ts.map +1 -1
  345. package/dist/theme/contrast.js +4 -0
  346. package/dist/theme/contrast.js.map +1 -1
  347. package/dist/theme/ground.d.ts +1 -2
  348. package/dist/theme/ground.d.ts.map +1 -1
  349. package/dist/theme/ground.js +2 -5
  350. package/dist/theme/ground.js.map +1 -1
  351. package/dist/theme/marks.d.ts +14 -2
  352. package/dist/theme/marks.d.ts.map +1 -1
  353. package/dist/theme/marks.js +20 -3
  354. package/dist/theme/marks.js.map +1 -1
  355. package/dist/theme/types.d.ts +22 -0
  356. package/dist/theme/types.d.ts.map +1 -1
  357. package/dist/theme/types.js.map +1 -1
  358. package/dist/tidying.d.ts +79 -4
  359. package/dist/tidying.d.ts.map +1 -1
  360. package/dist/tidying.js +216 -9
  361. package/dist/tidying.js.map +1 -1
  362. package/dist/trait-vocabulary.d.ts +17 -0
  363. package/dist/trait-vocabulary.d.ts.map +1 -0
  364. package/dist/trait-vocabulary.js +16 -0
  365. package/dist/trait-vocabulary.js.map +1 -0
  366. package/dist/traits.d.ts +14 -7
  367. package/dist/traits.d.ts.map +1 -1
  368. package/dist/traits.js +33 -12
  369. package/dist/traits.js.map +1 -1
  370. package/dist/validate-graph.d.ts +2 -1
  371. package/dist/validate-graph.d.ts.map +1 -1
  372. package/dist/validate-graph.js +32 -1
  373. package/dist/validate-graph.js.map +1 -1
  374. package/dist/version.d.ts +1 -1
  375. package/dist/version.js +1 -1
  376. package/dist/words.d.ts +4 -1
  377. package/dist/words.d.ts.map +1 -1
  378. package/dist/words.js +4 -1
  379. package/dist/words.js.map +1 -1
  380. package/dist/workflow-shape.d.ts +217 -0
  381. package/dist/workflow-shape.d.ts.map +1 -0
  382. package/dist/workflow-shape.js +31 -0
  383. package/dist/workflow-shape.js.map +1 -0
  384. package/dist/workflows/findings.d.ts +13 -0
  385. package/dist/workflows/findings.d.ts.map +1 -0
  386. package/dist/workflows/findings.js +157 -0
  387. package/dist/workflows/findings.js.map +1 -0
  388. package/dist/workflows/generate-words.d.ts +12 -0
  389. package/dist/workflows/generate-words.d.ts.map +1 -0
  390. package/dist/workflows/generate-words.js +25 -0
  391. package/dist/workflows/generate-words.js.map +1 -0
  392. package/dist/workflows/generate.d.ts +59 -0
  393. package/dist/workflows/generate.d.ts.map +1 -0
  394. package/dist/workflows/generate.js +165 -0
  395. package/dist/workflows/generate.js.map +1 -0
  396. package/dist/workflows/judging.d.ts +17 -0
  397. package/dist/workflows/judging.d.ts.map +1 -0
  398. package/dist/workflows/judging.js +11 -0
  399. package/dist/workflows/judging.js.map +1 -0
  400. package/dist/workflows/next.d.ts +18 -0
  401. package/dist/workflows/next.d.ts.map +1 -0
  402. package/dist/workflows/next.js +75 -0
  403. package/dist/workflows/next.js.map +1 -0
  404. package/dist/workflows/on-the-map.d.ts +70 -0
  405. package/dist/workflows/on-the-map.d.ts.map +1 -0
  406. package/dist/workflows/on-the-map.js +94 -0
  407. package/dist/workflows/on-the-map.js.map +1 -0
  408. package/dist/workflows/policies.d.ts +39 -0
  409. package/dist/workflows/policies.d.ts.map +1 -0
  410. package/dist/workflows/policies.js +95 -0
  411. package/dist/workflows/policies.js.map +1 -0
  412. package/dist/workflows/resolve.d.ts +55 -0
  413. package/dist/workflows/resolve.d.ts.map +1 -0
  414. package/dist/workflows/resolve.js +57 -0
  415. package/dist/workflows/resolve.js.map +1 -0
  416. package/dist/workflows/schedule.d.ts +28 -0
  417. package/dist/workflows/schedule.d.ts.map +1 -0
  418. package/dist/workflows/schedule.js +160 -0
  419. package/dist/workflows/schedule.js.map +1 -0
  420. package/dist/workflows/step-act.d.ts +6 -0
  421. package/dist/workflows/step-act.d.ts.map +1 -0
  422. package/dist/workflows/step-act.js +79 -0
  423. package/dist/workflows/step-act.js.map +1 -0
  424. package/dist/workflows/step-agent.d.ts +4 -0
  425. package/dist/workflows/step-agent.d.ts.map +1 -0
  426. package/dist/workflows/step-agent.js +55 -0
  427. package/dist/workflows/step-agent.js.map +1 -0
  428. package/dist/workflows/step-archive.d.ts +8 -0
  429. package/dist/workflows/step-archive.d.ts.map +1 -0
  430. package/dist/workflows/step-archive.js +74 -0
  431. package/dist/workflows/step-archive.js.map +1 -0
  432. package/dist/workflows/step-classify.d.ts +23 -0
  433. package/dist/workflows/step-classify.d.ts.map +1 -0
  434. package/dist/workflows/step-classify.js +143 -0
  435. package/dist/workflows/step-classify.js.map +1 -0
  436. package/dist/workflows/step-generate.d.ts +7 -0
  437. package/dist/workflows/step-generate.d.ts.map +1 -0
  438. package/dist/workflows/step-generate.js +185 -0
  439. package/dist/workflows/step-generate.js.map +1 -0
  440. package/dist/workflows/steps.d.ts +75 -0
  441. package/dist/workflows/steps.d.ts.map +1 -0
  442. package/dist/workflows/steps.js +40 -0
  443. package/dist/workflows/steps.js.map +1 -0
  444. package/dist/workflows/triggered.d.ts +22 -0
  445. package/dist/workflows/triggered.d.ts.map +1 -0
  446. package/dist/workflows/triggered.js +52 -0
  447. package/dist/workflows/triggered.js.map +1 -0
  448. package/dist/workflows/values.d.ts +28 -0
  449. package/dist/workflows/values.d.ts.map +1 -0
  450. package/dist/workflows/values.js +75 -0
  451. package/dist/workflows/values.js.map +1 -0
  452. package/dist/workflows/words.d.ts +10 -0
  453. package/dist/workflows/words.d.ts.map +1 -0
  454. package/dist/workflows/words.js +30 -0
  455. package/dist/workflows/words.js.map +1 -0
  456. package/dist/workflows/world.d.ts +23 -0
  457. package/dist/workflows/world.d.ts.map +1 -0
  458. package/dist/workflows/world.js +50 -0
  459. package/dist/workflows/world.js.map +1 -0
  460. package/dist/workflows.d.ts +35 -0
  461. package/dist/workflows.d.ts.map +1 -0
  462. package/dist/workflows.js +20 -0
  463. package/dist/workflows.js.map +1 -0
  464. package/package.json +18 -2
@@ -16,33 +16,36 @@ const refusedFormat = (format, type) => {
16
16
  import { farEnd } from "./far-end.js";
17
17
  import { jobFindings, jobWorldOfDocument } from "../job-findings.js";
18
18
  import { JobSpec } from "./purpose-spec.js";
19
+ import { WORKFLOW_JUDGING } from "../workflows/judging.js";
19
20
  import { aliasWord, documentAliasFindings } from "../alias-findings.js";
20
21
  import { humanizeField, withArticle } from "../schema/define-node.js";
21
- import { ConnectionSpec } from "./connection-spec.js";
22
+ import { ConnectionSpec, connectionKinds } from "./connection-spec.js";
22
23
  import { linkedRelationFindings } from "./linked-relation-findings.js";
23
24
  import { linkedRelationOfSpec, reachesAnotherApp } from "../schema/linked.js";
24
- import { ActSpec, ALIAS, MOST_ALIASES, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
25
- import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES, unknownLensOption, viewReference } from "../places.js";
25
+ import { ActSpec, ALIAS, MOST_ALIASES, MOST_COMPUTED, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
26
+ import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES, TIMELINE_AXIS_ROLES, timelineBindingsWords, unknownLensOption, viewReference } from "../places.js";
26
27
  import { LENSES_THAT_LIST, listsRecords } from "../lenses-that-list.js";
27
28
  import { placeSlug, VIEW_REFERENCE } from "../views/types.js";
28
29
  import { documentListFields, kindListFindings, kindListWords, listGroup, listSort, sortTypeOfDocumentField } from "../kind-list.js";
29
30
  import { computedOf, validateComputed } from "./computed.js";
30
31
  import { parseTemplate, templateBraces, TemplateError } from "./template-parse.js";
31
32
  import { validateViews, VIEW_SLOTS } from "./views.js";
32
- import { brandFindings, emojiIconSentence, groundWords, subtitleSentence } from "./brand-check.js";
33
+ import { brandFindings, emojiIconSentence, groundWords, splashSentence, subtitleSentence, viewsCssSentence, viewsMotionSentence } from "./brand-check.js";
33
34
  import { darkLogoSentence, logoParts } from "./logo.js";
34
35
  import { emojiOf } from "../theme/marks.js";
35
36
  import { canonicalize } from "./canonical.js";
36
37
  import { quotedAtTheEnd } from "./quote.js";
37
38
  import { isRecord as isObject, respellDocument } from "./respell.js";
38
39
  import { refName } from "./compiled.js";
39
- import { localeSentence, timeZoneSentence } from "./diff.js";
40
+ import { derivesSentence, localeSentence, timeZoneSentence } from "./diff.js";
40
41
  import { accentProblem } from "../theme/accent.js";
41
42
  import { darkPageFor, groundProblem } from "../theme/ground-check.js";
42
43
  import { pageFieldFindings, pageNames, renamedOnPage, withoutOnPage } from "./page-fields.js";
44
+ /** Whether a field or a computed field is said as money: its format, or a unit that is a currency. */
45
+ const isMoney = (f) => f["format"] === "money" || (f["format"] === undefined && typeof f["unit"] === "string" && /^[A-Z]{3}$/.test(f["unit"]));
43
46
  import { capitalize, listed as listOf } from "../capital.js";
44
47
  import { quoted } from "./quote.js";
45
- import { FIRST_GRANT, FIRST_SIGHT, grantBody, grantGone, grantSentence, NO_GRANT_LEFT, NO_SIGHT_LEFT, peopleKinds, peopleSentence, archivesSentence, tidyingSentence, nounWords, pluralWords as pluralOf, sightGone, sightSentence, sightWords, whoWords } from "./policy-words.js";
48
+ import { FIRST_GRANT, FIRST_SIGHT, grantBody, grantGone, grantSentence, NO_GRANT_LEFT, NO_SIGHT_LEFT, peopleKinds, peopleSentence, appTidyingSentence, archivesSentence, tidyingSentence, nounWords, pluralWords as pluralOf, sightGone, sightSentence, sightWords, whoWords } from "./policy-words.js";
46
49
  /** The ops, for tool schemas and the guide. */
47
50
  export const EDIT_OPS = [
48
51
  "add-kind",
@@ -84,6 +87,8 @@ export const EDIT_OPS = [
84
87
  "set-people",
85
88
  "set-archives",
86
89
  "set-tidying",
90
+ "set-app-tidying",
91
+ "set-derives",
87
92
  "add-grant",
88
93
  "remove-grant",
89
94
  "add-sight",
@@ -95,6 +100,11 @@ export const EDIT_OPS = [
95
100
  "add-alias",
96
101
  "set-aliases",
97
102
  "set-connection",
103
+ "add-workflow",
104
+ "set-workflow",
105
+ "remove-workflow",
106
+ "pause-workflow",
107
+ "resume-workflow",
98
108
  ];
99
109
  /** Words the rule language keeps: a field or relation renamed to one could not be named in a rule. */
100
110
  const KEYWORDS = new Set(["and", "or", "not", "in", "where", "true", "false", "null"]);
@@ -126,7 +136,7 @@ function buildShapes() {
126
136
  "rename-field": z.object({ op: z.literal("rename-field"), kind: kindName, field: fieldName, to: fieldName }).strict(),
127
137
  "retype-field": z.object({ op: z.literal("retype-field"), kind: kindName, field: fieldName, type: z.enum(FIELD_TYPES), options: z.array(z.string().min(1).max(80)).min(1).max(100).optional(), multiple: z.boolean().optional(), of: z.enum(["string", "number", "date", "image", "video", "file"]).optional(), format: z.enum(["money", "percent", "duration", "markdown"]).optional() }).strict(),
128
138
  "set-options": z.object({ op: z.literal("set-options"), kind: kindName, field: fieldName, add: z.array(z.string().min(1).max(80)).optional(), remove: z.array(z.string().min(1).max(80)).optional(), labels: z.record(z.string().min(1).max(80), z.union([z.string().min(1).max(80), z.null()])).optional() }).strict(),
129
- "set-format": z.object({ op: z.literal("set-format"), kind: kindName, field: fieldName, format: z.union([z.enum(["money", "percent", "duration", "markdown"]), z.null()]).optional(), unit: z.union([z.string().min(1).max(12), z.null()]).optional() }).strict(),
139
+ "set-format": z.object({ op: z.literal("set-format"), kind: kindName, field: fieldName, format: z.union([z.enum(["money", "percent", "duration", "markdown"]), z.null()]).optional(), unit: z.union([z.string().min(1).max(12), z.null()]).optional(), places: z.union([z.literal(0), z.null()]).optional() }).strict(),
130
140
  "set-range": z.object({ op: z.literal("set-range"), kind: kindName, field: fieldName, min: z.union([z.number(), z.null()]).optional(), max: z.union([z.number(), z.null()]).optional(), step: z.union([z.number().positive(), z.null()]).optional() }).strict(),
131
141
  "set-required": z.object({ op: z.literal("set-required"), kind: kindName, field: fieldName, required: z.boolean() }).strict(),
132
142
  "set-default": z.object({ op: z.literal("set-default"), kind: kindName, field: fieldName, default: z.unknown() }).strict(),
@@ -167,6 +177,15 @@ function buildShapes() {
167
177
  scheme: z.union([z.enum(["light", "dark", "auto"]), z.null()]).optional(),
168
178
  // Whether the home draws the description as the line under the name (Roadmap f-home-subtitle-separate): false keeps it off, true or null draws it again.
169
179
  subtitle: z.union([z.boolean(), z.null()]).optional(),
180
+ // The opening screen (Roadmap f-splash-in-document), whole; null for Graview's own.
181
+ splash: z.union([z.object({ style: z.string(), image: z.string().min(1).optional(), title: z.string().min(1).max(80).optional(), kicker: z.string().min(1).max(80).optional(), shader: z.string().min(1).max(40).optional() }).strict(), z.null()]).optional(),
182
+ // What every worker view inherits (Roadmap f-app-wide-view-theme): a stylesheet drawn before each view's own (its length the sanitizer's to refuse, in `brandFindings`), and a fade between records; null clears either, or all of it.
183
+ views: z
184
+ .union([
185
+ z.object({ css: z.union([z.string().min(1), z.null()]).optional(), motion: z.union([z.enum(["none", "fade"]), z.null()]).optional() }).strict(),
186
+ z.null(),
187
+ ])
188
+ .optional(),
170
189
  })
171
190
  .strict(),
172
191
  "set-name": z.object({ op: z.literal("set-name"), name: z.string().min(1).max(80) }).strict(),
@@ -245,6 +264,10 @@ function buildShapes() {
245
264
  "set-archives": z.object({ op: z.literal("set-archives"), kind: kindName, archives: z.boolean() }).strict(),
246
265
  // What the app's own actor may do on its own to a kind, or to one relation of it (`@graview/core/tidying`).
247
266
  "set-tidying": z.object({ op: z.literal("set-tidying"), kind: kindName, relation: z.optional(z.string()), tidying: z.enum(["never", "no-links", "allowed"]) }).strict(),
267
+ // What the app says of suggestions as a whole (`tidying` at the top of the document): the votes at which the app's own actor makes one; null takes it away.
268
+ "set-app-tidying": z.object({ op: z.literal("set-app-tidying"), autoAcceptAt: z.union([z.number().int().min(1).max(10_000), z.null()]) }).strict(),
269
+ // What a kind follows (Roadmap f-keep-linked): the kind, the relation joining them and the fields worked out from it; null, it follows nothing.
270
+ "set-derives": z.object({ op: z.literal("set-derives"), kind: kindName, derives: z.union([z.object({ from: kindName, via: z.string(), set: z.record(z.string(), z.unknown()) }).strict(), z.null()]) }).strict(),
248
271
  "add-grant": z.object({ op: z.literal("add-grant") }).passthrough(),
249
272
  "remove-grant": z.object({ op: z.literal("remove-grant") }).passthrough(),
250
273
  "add-sight": z.object({ op: z.literal("add-sight") }).passthrough(),
@@ -273,6 +296,12 @@ function buildShapes() {
273
296
  "set-aliases": z.object({ op: z.literal("set-aliases"), kind: kindName, field: fieldName.optional(), aliases: z.union([z.array(z.string().min(1).max(40)).max(MOST_ALIASES), z.null()]) }).strict(),
274
297
  // A connection declared whole, by its name: set where there was none, or replaced (FR-229); null takes it away.
275
298
  "set-connection": z.object({ op: z.literal("set-connection"), name: kindName, connection: z.union([z.record(z.string(), z.unknown()), z.null()]) }).strict(),
299
+ // A workflow by its name (Roadmap f-jobs-declared): added where there was none, replaced whole, removed, paused or resumed.
300
+ "add-workflow": z.object({ op: z.literal("add-workflow"), name: kindName, workflow: z.record(z.string(), z.unknown()) }).strict(),
301
+ "set-workflow": z.object({ op: z.literal("set-workflow"), name: kindName, workflow: z.record(z.string(), z.unknown()) }).strict(),
302
+ "remove-workflow": z.object({ op: z.literal("remove-workflow"), name: kindName }).strict(),
303
+ "pause-workflow": z.object({ op: z.literal("pause-workflow"), name: kindName }).strict(),
304
+ "resume-workflow": z.object({ op: z.literal("resume-workflow"), name: kindName }).strict(),
276
305
  };
277
306
  }
278
307
  let shapes;
@@ -550,6 +579,11 @@ function setTargets(act, doc) {
550
579
  const end = doc && relation ? farEnd(doc.kinds, relation, asArray(act.on)) : undefined;
551
580
  out.push({ set: act.setsOther, kinds: end && end.kinds !== "*" ? new Set(end.kinds) : "*", where: "setsOther" });
552
581
  }
582
+ // What it carries to the records linked to its subject, by relation: the kinds at each relation's far end.
583
+ for (const [relation, set] of Object.entries(act.setsLinked ?? {})) {
584
+ const end = doc ? farEnd(doc.kinds, relation, asArray(act.on)) : undefined;
585
+ out.push({ set, kinds: end && end.kinds !== "*" ? new Set(end.kinds) : "*", where: "setsLinked", relation });
586
+ }
553
587
  (act.effects ?? []).forEach((e, index) => {
554
588
  if (e.create)
555
589
  out.push({ set: e.set ?? {}, kinds: new Set([e.create]), where: "create", index });
@@ -602,10 +636,18 @@ class Editor {
602
636
  const named = Object.keys(e).filter((key) => key !== "op" && e[key] !== undefined);
603
637
  if (named.length === 0)
604
638
  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"}');
605
- const emptyPart = ["typography", "shape", "accents"].find((key) => isObject(e[key]) && Object.keys(e[key]).length === 0);
639
+ const emptyPart = ["typography", "shape", "accents", "views"].find((key) => isObject(e[key]) && Object.keys(e[key]).length === 0);
606
640
  if (emptyPart)
607
641
  return this.fail(i, emptyPart, `${emptyPart} names no part to set; name one, or give null to clear all of it`);
608
- const judged = brandFindings({ ...(typeof e.timeZone === "string" ? { timeZone: e.timeZone } : {}), ...(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 } : {}) });
642
+ const judged = brandFindings({
643
+ ...(typeof e.timeZone === "string" ? { timeZone: e.timeZone } : {}),
644
+ ...(isObject(e.splash) ? { splash: e.splash } : {}),
645
+ ...(typeof e.logo === "string" || isObject(e.logo) ? { logo: e.logo } : {}),
646
+ ...(typeof e.favicon === "string" || isObject(e.favicon) ? { favicon: e.favicon } : {}),
647
+ ...(isObject(e.typography) ? { typography: Object.fromEntries(Object.entries(e.typography).filter(([, v]) => typeof v === "string")) } : {}),
648
+ // 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.
649
+ ...(isObject(e.views) && typeof e.views["css"] === "string" ? { views: { css: e.views["css"] } } : {}),
650
+ }, { at, ...(this.options.fonts ? { fonts: this.options.fonts } : {}) });
609
651
  if (judged.length > 0)
610
652
  return void this.findings.push(...judged);
611
653
  if (e.accent === null) {
@@ -779,6 +821,45 @@ class Editor {
779
821
  delete brand["subtitle"];
780
822
  this.said.push(subtitleSentence(e.subtitle !== false));
781
823
  }
824
+ // The opening screen is set whole or cleared: its keys are few, and half of one is nothing a page could draw.
825
+ if (e.splash !== undefined) {
826
+ if (e.splash === null)
827
+ delete brand["splash"];
828
+ else
829
+ brand["splash"] = e.splash;
830
+ if (!(e.splash === null && !isObject(this.doc.brand?.splash)))
831
+ this.said.push(splashSentence(e.splash));
832
+ }
833
+ /* 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. */
834
+ if (e.views === null) {
835
+ const had = brand["views"] ?? {};
836
+ if (had["css"] !== undefined || had["motion"] === undefined)
837
+ this.said.push(viewsCssSentence(false));
838
+ if (had["motion"] === "fade")
839
+ this.said.push(viewsMotionSentence(undefined));
840
+ delete brand["views"];
841
+ }
842
+ else if (isObject(e.views)) {
843
+ const into = { ...(brand["views"] ?? {}) };
844
+ if (e.views["css"] !== undefined) {
845
+ if (e.views["css"] === null)
846
+ delete into["css"];
847
+ else
848
+ into["css"] = e.views["css"];
849
+ this.said.push(viewsCssSentence(e.views["css"] !== null));
850
+ }
851
+ if (e.views["motion"] !== undefined) {
852
+ if (e.views["motion"] === "fade")
853
+ into["motion"] = "fade";
854
+ else
855
+ delete into["motion"];
856
+ this.said.push(viewsMotionSentence(e.views["motion"] === "fade" ? "fade" : undefined));
857
+ }
858
+ if (Object.keys(into).length === 0)
859
+ delete brand["views"];
860
+ else
861
+ brand["views"] = into;
862
+ }
782
863
  if (Object.keys(brand).length === 0)
783
864
  delete this.doc.brand;
784
865
  else
@@ -984,8 +1065,12 @@ class Editor {
984
1065
  const fields = e.fields;
985
1066
  const seen = new Set();
986
1067
  for (const [n, field] of fields.entries()) {
987
- if (!spec.fields[field])
988
- return this.fail(i, `fields.${n}`, `a glance at ${e.kind} is to say "${field}", and ${e.kind} has no field called that`, `use one of: ${Object.keys(spec.fields).join(", ")}`);
1068
+ // A computed field may be glanced at (FR-83): worked out for each card, as its page works it out — unless it is kept off what a record says of itself.
1069
+ const worked = spec.computed?.[field];
1070
+ if (!spec.fields[field] && worked === undefined)
1071
+ return this.fail(i, `fields.${n}`, `a glance at ${e.kind} is to say "${field}", and ${e.kind} has no field or computed field called that`, `use one of: ${[...Object.keys(spec.fields), ...Object.keys(spec.computed ?? {})].join(", ")}`);
1072
+ if (!spec.fields[field] && isObject(worked) && worked["hidden"] === true)
1073
+ return this.fail(i, `fields.${n}`, `${e.kind}'s "${field}" is worked out and kept off its card, page and CSV, so a glance cannot say it`, `show it first: set-computed with "hidden": false`);
989
1074
  if (seen.has(field))
990
1075
  return this.fail(i, `fields.${n}`, `"${field}" is in the glance twice; name each field once`);
991
1076
  seen.add(field);
@@ -1024,6 +1109,10 @@ class Editor {
1024
1109
  return this.setArchives(i, e);
1025
1110
  case "set-tidying":
1026
1111
  return this.setTidying(i, e);
1112
+ case "set-app-tidying":
1113
+ return this.setAppTidying(e);
1114
+ case "set-derives":
1115
+ return this.setDerives(i, e);
1027
1116
  case "add-grant":
1028
1117
  case "remove-grant":
1029
1118
  case "add-sight":
@@ -1043,6 +1132,15 @@ class Editor {
1043
1132
  return this.setAliases(i, e.kind, e.field, e.aliases ?? []);
1044
1133
  case "set-connection":
1045
1134
  return this.setConnection(i, e.name, e.connection);
1135
+ case "add-workflow":
1136
+ return this.placeWorkflow(i, e.name, e.workflow, "add");
1137
+ case "set-workflow":
1138
+ return this.placeWorkflow(i, e.name, e.workflow, "set");
1139
+ case "remove-workflow":
1140
+ return this.removeWorkflow(i, e.name);
1141
+ case "pause-workflow":
1142
+ case "resume-workflow":
1143
+ return this.setWorkflowStatus(i, e.name, e.op === "pause-workflow" ? "paused" : "active");
1046
1144
  }
1047
1145
  }
1048
1146
  // ── a connection, declared whole (FR-229) ──
@@ -1070,7 +1168,9 @@ class Editor {
1070
1168
  const was = this.doc.connections?.[name] !== undefined;
1071
1169
  (this.doc.connections ??= {})[name] = parsed.data;
1072
1170
  const system = parsed.data.system === "notion" ? "a Notion database" : "a Google calendar";
1073
- 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"}.`);
1171
+ const kinds = connectionKinds(parsed.data).map((kind) => pluralOf(this.doc.kinds, kind));
1172
+ const said = kinds.length <= 1 ? kinds.join("") : `${kinds.slice(0, -1).join(", ")} and ${kinds.at(-1)}`;
1173
+ this.said.push(`The connection ${name} ${was ? "now keeps" : "keeps"} ${said} in step with ${system}${parsed.data.direction === "read" ? ", which owns them" : ", both ways"}.`);
1074
1174
  }
1075
1175
  // ── other words for a kind or a field (Roadmap f-learned-suggestions) ──
1076
1176
  aliasesOf(kind, field) {
@@ -1197,6 +1297,58 @@ class Editor {
1197
1297
  this.keepPurpose({ ...this.doc.purpose });
1198
1298
  this.said.push(`The job "${job.name}" (${quotedAtTheEnd(job.ask)}) is removed.`);
1199
1299
  }
1300
+ // ── what the app does on its own, and when (Roadmap f-jobs-declared) ──
1301
+ /** The workflow a name names, or undefined having said why. */
1302
+ workflowNamed(i, name) {
1303
+ const workflows = this.doc.workflows ?? {};
1304
+ if (Object.hasOwn(workflows, name))
1305
+ return workflows[name];
1306
+ const names = Object.keys(workflows);
1307
+ this.fail(i, "name", `there is no workflow "${name}"${names.length > 0 ? `; the workflows are ${names.map((one) => `"${one}"`).join(", ")}` : ""}`);
1308
+ return undefined;
1309
+ }
1310
+ /** A workflow added or replaced whole, judged as `graview check` judges it (`workflow-*`): kept only when nothing is wrong with it. */
1311
+ placeWorkflow(i, name, workflow, how) {
1312
+ const workflows = { ...(this.doc.workflows ?? {}) };
1313
+ if (how === "add" && Object.hasOwn(workflows, name))
1314
+ return this.fail(i, "name", `there is already a workflow "${name}"`, `Change it with {"op": "set-workflow", "name": "${name}", …}, or name this one otherwise.`);
1315
+ if (how === "set" && !Object.hasOwn(workflows, name) && !this.workflowNamed(i, name))
1316
+ return;
1317
+ const shaped = typeof workflow.title === "string" && Array.isArray(workflow.when) && workflow.when.length > 0 && Array.isArray(workflow.steps) && workflow.steps.length > 0;
1318
+ if (!shaped)
1319
+ return this.fail(i, "workflow", "a workflow is { title, when: [...], steps: [...] }, each list with at least one", `{"op": "${how}-workflow", "name": "${name}", "workflow": {"title": "…", "when": [{"every": "day 03:00"}], "steps": [{"act": "…"}]}}`);
1320
+ // Judged as `graview check` judges it, where the checker is loaded; else kept as shaped, and the check says the rest.
1321
+ const judged = WORKFLOW_JUDGING.judge?.(name, workflow, { ...this.doc, workflows });
1322
+ for (const finding of judged?.errors ?? [])
1323
+ this.failAs(finding.code, i, `workflow${finding.path ? `.${finding.path}` : ""}`, finding.message, finding.fix);
1324
+ if (judged && judged.errors.length > 0)
1325
+ return;
1326
+ workflows[name] = workflow;
1327
+ this.doc.workflows = workflows;
1328
+ const steps = workflow.steps.length;
1329
+ this.said.push(`${how === "add" ? "A new workflow" : "The workflow"} “${workflow.title}”${how === "add" ? "" : " is now"}: ${judged?.said ?? `${steps === 1 ? "one step" : `${steps} steps`}${workflow.status === "paused" ? ", paused" : ""}`}.`);
1330
+ for (const warning of judged?.warnings ?? [])
1331
+ this.said.push(warning);
1332
+ }
1333
+ removeWorkflow(i, name) {
1334
+ const workflow = this.workflowNamed(i, name);
1335
+ if (!workflow)
1336
+ return;
1337
+ const { [name]: _gone, ...rest } = this.doc.workflows;
1338
+ if (Object.keys(rest).length === 0)
1339
+ delete this.doc.workflows;
1340
+ else
1341
+ this.doc.workflows = rest;
1342
+ this.said.push(`The workflow “${workflow.title ?? name}” is removed.`);
1343
+ }
1344
+ setWorkflowStatus(i, name, status) {
1345
+ const workflow = this.workflowNamed(i, name);
1346
+ if (!workflow)
1347
+ return;
1348
+ const { status: _was, ...rest } = workflow;
1349
+ this.doc.workflows = { ...this.doc.workflows, [name]: status === "paused" ? { ...rest, status } : rest };
1350
+ this.said.push(status === "paused" ? `The workflow “${workflow.title ?? name}” is paused: it runs on no clock and no change until it is resumed.` : `The workflow “${workflow.title ?? name}” runs again.`);
1351
+ }
1200
1352
  /** How a record's page orders and groups its facts (FR-148): judged as the compile judges `kinds.<kind>.page`. */
1201
1353
  setPageFields(i, e) {
1202
1354
  const spec = this.kind(i, e.kind);
@@ -1340,6 +1492,8 @@ class Editor {
1340
1492
  }
1341
1493
  if (name === "columns" && !(this.columnsHold(i, e.bindings) && this.boardOptionsHold(i, e.bindings, e.options)))
1342
1494
  return;
1495
+ if (name === "timeline" && !this.timelineHolds(i, e.bindings, e.options))
1496
+ return;
1343
1497
  if (was)
1344
1498
  lenses[at] = lens;
1345
1499
  else
@@ -1412,6 +1566,44 @@ class Editor {
1412
1566
  }
1413
1567
  return true;
1414
1568
  }
1569
+ /**
1570
+ * A TIMELINE THAT WOULD DRAW, held before it is added: a timeline lays out
1571
+ * spans by number across columns, so each kind it binds is a kind, its
1572
+ * `start` and `end` are number fields, and its columns are named in the
1573
+ * options or read from a choice field bound to `column`. A timeline over a
1574
+ * project's start and end dates was accepted, said nothing and drew
1575
+ * nothing — never a place; it is refused here in the words the checker
1576
+ * uses (`lens-cannot-draw`), pointing spans of dates at the calendar.
1577
+ */
1578
+ timelineHolds(i, bindings, options) {
1579
+ const refuse = (path, message, kind, start, end) => (this.failAs("lens-cannot-draw", i, path, `a timeline here would not draw: ${message}`, timelineBindingsWords(kind, typeof start === "string" ? start : "start", typeof end === "string" ? end : "end")), false);
1580
+ if (!isObject(bindings) || Object.keys(bindings).length === 0)
1581
+ return refuse("bindings", "it binds no kind", "<kind>");
1582
+ let columns = Array.isArray(options?.["columns"]) && options["columns"].length > 0;
1583
+ for (const [kind, roles] of Object.entries(bindings)) {
1584
+ if (!isObject(this.doc.kinds[kind]))
1585
+ return (this.fail(i, `bindings.${kind}`, `"${kind}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`), false);
1586
+ if (!isObject(roles))
1587
+ return refuse(`bindings.${kind}`, `it does not say which fields of ${kind} it lays out`, kind);
1588
+ const fields = this.doc.kinds[kind].fields;
1589
+ for (const role of TIMELINE_AXIS_ROLES) {
1590
+ const field = roles[role];
1591
+ if (typeof field !== "string")
1592
+ return refuse(`bindings.${kind}.${role}`, `it does not say which field of ${kind} is its ${role}`, kind, roles["start"], roles["end"]);
1593
+ if (!(field in fields))
1594
+ return (this.fail(i, `bindings.${kind}.${role}`, `${kind} has no field "${field}"; it has ${Object.keys(fields).join(", ")}`), false);
1595
+ const type = fields[field]["type"];
1596
+ if (type !== "number" && type !== "integer" && type !== "duration")
1597
+ return refuse(`bindings.${kind}.${role}`, `${kind}'s ${field} is ${withArticle(String(type ?? "text"))} field, and a timeline's axis is a number`, kind, roles["start"], roles["end"]);
1598
+ }
1599
+ const column = typeof roles["column"] === "string" ? roles["column"] : typeof roles["columns"] === "string" ? roles["columns"] : undefined;
1600
+ if (column !== undefined && fields[column]?.["type"] === "enum")
1601
+ columns = true;
1602
+ }
1603
+ if (!columns)
1604
+ return refuse("options.columns", "it has no columns — no option names them and no `column` role is bound to a choice field", Object.keys(bindings)[0]);
1605
+ return true;
1606
+ }
1415
1607
  /**
1416
1608
  * WHAT A BOARD SHOWS OF ITSELF (Roadmap f-lens-search-filter): fields of
1417
1609
  * its kind for Find and the chips, columns of its field to open shut or
@@ -1974,8 +2166,8 @@ class Editor {
1974
2166
  return this.fail(i, "name", `${e.kind} already has a ${spec.fields[name] ? "field" : "relation"} called "${name}"`);
1975
2167
  if (KEYWORDS.has(name))
1976
2168
  return this.fail(i, "name", `"${name}" is a word the rule language keeps; pick another name`);
1977
- if (Object.keys(spec.computed ?? {}).length >= 20)
1978
- return this.fail(i, "name", "a kind works out at most 20 computed fields");
2169
+ if (Object.keys(spec.computed ?? {}).length >= MOST_COMPUTED)
2170
+ return this.fail(i, "name", `a kind works out at most ${MOST_COMPUTED} computed fields`);
1979
2171
  }
1980
2172
  const was = existing === undefined ? {} : typeof existing === "string" ? { expr: existing } : { ...existing };
1981
2173
  const next = { ...was };
@@ -2067,6 +2259,86 @@ class Editor {
2067
2259
  * never tidied is given no leave-alone-<kind> or allow-tidying-<kind>,
2068
2260
  * which a grant may not then name.
2069
2261
  */
2262
+ /**
2263
+ * WHAT A KIND FOLLOWS (Roadmap f-keep-linked): each record of it linked
2264
+ * through `via` to a record of `from` takes the fields `set` works out
2265
+ * from it, kept in step by the store in the same change; null, it
2266
+ * follows nothing and its records keep the values they hold.
2267
+ */
2268
+ setDerives(i, e) {
2269
+ const spec = this.kind(i, e.kind);
2270
+ if (!spec)
2271
+ return;
2272
+ if (e.derives === null) {
2273
+ const was = spec.derives?.from;
2274
+ delete spec.derives;
2275
+ this.said.push(was ? derivesSentence(e.kind, undefined) : `${e.kind} follows nothing, as before.`);
2276
+ return;
2277
+ }
2278
+ const { from, via, set } = e.derives;
2279
+ if (!this.doc.kinds[from])
2280
+ return this.fail(i, "derives.from", `"${from}" is not a kind this app has`);
2281
+ const joins = (spec.edges?.[via] && (spec.edges[via].to === "*" || spec.edges[via].to.includes(from))) || (this.doc.kinds[from].edges?.[via] && (this.doc.kinds[from].edges[via].to === "*" || this.doc.kinds[from].edges[via].to.includes(e.kind)));
2282
+ if (!joins)
2283
+ return this.fail(i, "derives.via", `"${via}" does not join ${words(e.kind)} to ${words(from)}`, `name a relation of ${e.kind} to ${from}, or of ${from} to ${e.kind} (add one with add-relation in the same change)`);
2284
+ spec.derives = { from, via, set };
2285
+ this.said.push(derivesSentence(e.kind, spec.derives));
2286
+ }
2287
+ /**
2288
+ * WHAT A KIND FOLLOWS, held to a rename or a removal (Roadmap
2289
+ * f-keep-linked): the names it says follow a rename; a value reading
2290
+ * what is removed goes, and a `derives` left setting nothing, or
2291
+ * following a kind or relation that is gone, goes with it.
2292
+ */
2293
+ followDerives(r, removing) {
2294
+ const touched = [];
2295
+ for (const [kind, spec] of Object.entries(this.doc.kinds)) {
2296
+ const d = spec.derives;
2297
+ if (!d)
2298
+ continue;
2299
+ const before = JSON.stringify(d);
2300
+ const ctx = new Set([d.from]);
2301
+ if ((r.t === "kind" && d.from === r.from) || (r.t === "edge" && d.via === r.from)) {
2302
+ if (removing) {
2303
+ delete spec.derives;
2304
+ touched.push(`what ${kind} follows`);
2305
+ continue;
2306
+ }
2307
+ }
2308
+ const set = {};
2309
+ for (const [field, value] of Object.entries(d.set ?? {})) {
2310
+ let key = field;
2311
+ if (r.t === "field" && r.kind === kind && field === r.from) {
2312
+ if (removing)
2313
+ continue;
2314
+ key = r.to;
2315
+ }
2316
+ if (isObject(value) && typeof value.expr === "string") {
2317
+ const source = value.expr;
2318
+ if (removing) {
2319
+ if (!exprMentions(this.doc, source, ctx, r))
2320
+ set[key] = value;
2321
+ }
2322
+ else
2323
+ set[key] = { expr: rewriteExpr(this.doc, source, ctx, r) };
2324
+ }
2325
+ else
2326
+ set[key] = value;
2327
+ }
2328
+ if (!removing && r.t === "kind" && d.from === r.from)
2329
+ d.from = r.to;
2330
+ if (!removing && r.t === "edge" && d.via === r.from)
2331
+ d.via = r.to;
2332
+ d.set = set;
2333
+ if (Object.keys(set).length === 0) {
2334
+ delete spec.derives;
2335
+ touched.push(`what ${kind} follows`);
2336
+ }
2337
+ else if (JSON.stringify(d) !== before)
2338
+ touched.push(`what ${kind} follows`);
2339
+ }
2340
+ return touched;
2341
+ }
2070
2342
  setTidying(i, e) {
2071
2343
  const spec = this.kind(i, e.kind);
2072
2344
  if (!spec)
@@ -2105,6 +2377,26 @@ class Editor {
2105
2377
  this.toolsMoved.add(`leave-alone-${e.kind} and allow-tidying-${e.kind} ${tidying === "never" ? "are gone" : "are offered"}`);
2106
2378
  this.said.push(tidyingSentence(this.doc.kinds, e.kind, undefined, tidying, already));
2107
2379
  }
2380
+ /**
2381
+ * WHAT THE APP SAYS OF SUGGESTIONS AS A WHOLE (Roadmap f-suggestion-votes):
2382
+ * `tidying.autoAcceptAt`, the votes for a suggestion at which the app's
2383
+ * own actor makes it; null takes it away, and a suggestion waits for
2384
+ * somebody who may decide. A `tidying` left saying nothing goes.
2385
+ */
2386
+ setAppTidying(e) {
2387
+ const autoAcceptAt = e.autoAcceptAt;
2388
+ const was = this.doc.tidying?.autoAcceptAt;
2389
+ const tidying = { ...(this.doc.tidying ?? {}) };
2390
+ if (autoAcceptAt === null)
2391
+ delete tidying.autoAcceptAt;
2392
+ else
2393
+ tidying.autoAcceptAt = autoAcceptAt;
2394
+ if (Object.keys(tidying).length === 0)
2395
+ delete this.doc.tidying;
2396
+ else
2397
+ this.doc.tidying = tidying;
2398
+ this.said.push(appTidyingSentence(autoAcceptAt ?? undefined, was === (autoAcceptAt ?? undefined)));
2399
+ }
2108
2400
  /** A grant or a sight as the document holds one, or undefined having said why not. */
2109
2401
  policyPart(i, e, sight) {
2110
2402
  const { op: _op, ...rest } = e;
@@ -2137,13 +2429,15 @@ class Editor {
2137
2429
  return this.failAs("sight-field", i, `${key}.${n}`, `"${field}" is not a field of ${kinds.join(" or ")}`, `one of ${[...new Set(kinds.flatMap((kind) => Object.keys(this.doc.kinds[kind]?.fields ?? {})))].join(", ")}`);
2138
2430
  }
2139
2431
  }
2140
- if (!sight && part.mutations !== "*") {
2141
- for (const [n, act] of part.mutations.entries()) {
2432
+ for (const key of sight ? [] : ["mutations", "propose"]) {
2433
+ if (part[key] === undefined || part[key] === "*")
2434
+ continue;
2435
+ for (const [n, act] of part[key].entries()) {
2142
2436
  const derived = derivedActOf(act);
2143
2437
  if (this.doc.acts?.[act] || (derived && this.doc.kinds[derived.kind] && (this.doc.kinds[derived.kind].archives !== false || givenWhenNeverArchived(derived.act)) && (this.doc.kinds[derived.kind].tidying !== "never" || givenWhenNeverTidied(derived.act))) || givesRelationAct(act, this.doc.kinds))
2144
2438
  continue;
2145
2439
  const relationActs = relationActsOf(documentRelations(this.doc.kinds)).filter((name) => givesRelationAct(name, this.doc.kinds));
2146
- 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), ...relationActs].join(", ")}`);
2440
+ return this.failAs("grant-act", i, `${key}.${n}`, `"${act}" is not an act this app has`, `one of ${[...Object.keys(this.doc.acts ?? {}), ...Object.keys(this.doc.kinds).flatMap(derivedActsOf), ...relationActs].join(", ")}`);
2147
2441
  }
2148
2442
  }
2149
2443
  const said = [];
@@ -2184,10 +2478,10 @@ class Editor {
2184
2478
  removePolicyPart(i, part, sight) {
2185
2479
  const list = (sight ? this.doc.policy?.sees : this.doc.policy?.grants) ?? [];
2186
2480
  const sameSet = (a, b) => a === b || (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((x) => b.includes(x)));
2187
- const keys = sight ? ["own", "via"] : ["kinds", "self", "ownEnd", "describe"];
2481
+ const keys = sight ? ["own", "via"] : ["kinds", "self", "ownEnd", "propose", "describe"];
2188
2482
  const matches = list.filter((one) => sameSet(one.roles, part.roles) &&
2189
2483
  (sight ? sameSet(one.kinds, part.kinds) : sameSet(one.mutations, part.mutations)) &&
2190
- keys.every((key) => part[key] === undefined || (typeof part[key] === "boolean" ? (one[key] ?? false) === part[key] : key === "kinds" ? sameSet(one[key] ?? "*", part[key]) : one[key] === part[key])));
2484
+ keys.every((key) => part[key] === undefined || (typeof part[key] === "boolean" ? (one[key] ?? false) === part[key] : key === "kinds" ? sameSet(one[key] ?? "*", part[key]) : key === "propose" ? sameSet(one[key], part[key]) : one[key] === part[key])));
2191
2485
  const what = sight ? "sight" : "grant";
2192
2486
  const say = (one) => (sight ? `${whoWords(one.roles)} ${sightWords(this.doc, one)}` : grantBody(this.doc, one));
2193
2487
  if (matches.length === 0) {
@@ -2374,7 +2668,7 @@ class Editor {
2374
2668
  gone.push("its hue");
2375
2669
  }
2376
2670
  for (const [name, rule] of Object.entries(this.doc.rules ?? {})) {
2377
- 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" })) {
2671
+ 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" })) {
2378
2672
  delete this.doc.rules[name];
2379
2673
  gone.push(titled("rule", name, rule));
2380
2674
  }
@@ -2416,6 +2710,7 @@ class Editor {
2416
2710
  * kind, or drew by it, goes, and `pages.first` with it when it named it.
2417
2711
  */
2418
2712
  const swept = { t: "kind", from: kind, to: "\u0000" };
2713
+ gone.push(...this.followDerives(swept, true));
2419
2714
  for (const [other, spec] of Object.entries(this.doc.kinds)) {
2420
2715
  for (const [name, c] of Object.entries(spec.computed ?? {})) {
2421
2716
  if (exprMentions(this.doc, typeof c === "string" ? c : c.expr, new Set([other]), swept))
@@ -2615,8 +2910,8 @@ class Editor {
2615
2910
  const f = this.field(i, e.kind, e.field);
2616
2911
  if (!f)
2617
2912
  return;
2618
- if (e.format === undefined && e.unit === undefined)
2619
- return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration" for a number, "markdown" for a text field, or null for neither), a "unit" ("EUR", "kg", or null), or both');
2913
+ if (e.format === undefined && e.unit === undefined && e.places === undefined)
2914
+ return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration" for a number, "markdown" for a text field, or null for neither), a "unit" ("EUR", "kg", or null), "places" (0 for money in whole units, or null), or some of them');
2620
2915
  if (f.type === "text") {
2621
2916
  if (e.unit !== undefined && e.unit !== null)
2622
2917
  return this.fail(i, "unit", refusedFormat("a unit", f.type));
@@ -2632,12 +2927,14 @@ class Editor {
2632
2927
  if (!formatFits(typeof e.format === "string" ? e.format : "money", f.type))
2633
2928
  return this.fail(i, "field", refusedFormat(typeof e.format === "string" ? e.format : "a unit", f.type));
2634
2929
  const before = formatWords(f, this.doc.brand);
2635
- for (const key of ["format", "unit"]) {
2930
+ for (const key of ["format", "unit", "places"]) {
2636
2931
  if (e[key] === null)
2637
2932
  delete f[key];
2638
2933
  else if (e[key] !== undefined)
2639
2934
  f[key] = e[key];
2640
2935
  }
2936
+ if (f.places !== undefined && !isMoney(f))
2937
+ return this.fail(i, "places", `"places" is said of money, and ${e.kind}'s ${e.field} is ${formatWords(f, this.doc.brand) ?? "a plain number"}`, 'say "format": "money" with it, or leave "places" out');
2641
2938
  const now = formatWords(f, this.doc.brand);
2642
2939
  this.said.push(`${e.kind}'s ${e.field} is said as ${now ?? "a plain number"}${before ? `, where it was ${before.replace(/ — .*/, "")}` : ""}; its values are kept.`);
2643
2940
  }
@@ -2649,19 +2946,21 @@ class Editor {
2649
2946
  * its expression is left.
2650
2947
  */
2651
2948
  setComputedFormat(i, e, worked) {
2652
- if (e.format === undefined && e.unit === undefined)
2653
- return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration", or null for none), a "unit" ("EUR", "kg", or null), or both');
2949
+ if (e.format === undefined && e.unit === undefined && e.places === undefined)
2950
+ return this.fail(i, "format", 'say a "format" ("money", "percent" or "duration", or null for none), a "unit" ("EUR", "kg", or null), "places" (0 for money in whole units, or null), or some of them');
2654
2951
  if (e.format === "markdown")
2655
2952
  return this.fail(i, "format", `${e.field} is worked out, not written, so it cannot be Markdown; a computed field is said as "money", "percent" or "duration"`);
2656
2953
  const spec = this.doc.kinds[e.kind];
2657
2954
  const next = typeof worked === "string" ? { expr: worked } : { ...worked };
2658
2955
  const before = formatWords(next, this.doc.brand);
2659
- for (const key of ["format", "unit"]) {
2956
+ for (const key of ["format", "unit", "places"]) {
2660
2957
  if (e[key] === null)
2661
2958
  delete next[key];
2662
2959
  else if (e[key] !== undefined)
2663
2960
  next[key] = e[key];
2664
2961
  }
2962
+ if (next.places !== undefined && !isMoney(next))
2963
+ return this.fail(i, "places", `"places" is said of money, and ${e.kind}'s ${e.field} is ${formatWords(next, this.doc.brand) ?? "a plain number"}`, 'say "format": "money" with it, or leave "places" out');
2665
2964
  spec.computed[e.field] = Object.keys(next).length === 1 ? next.expr : next;
2666
2965
  const now = formatWords(next, this.doc.brand);
2667
2966
  this.said.push(`${e.kind}'s ${e.field} is worked out as before and said as ${now ?? "a plain number"}${before ? `, where it was ${before.replace(/ — .*/, "")}` : ""}.`);
@@ -3004,6 +3303,8 @@ class Editor {
3004
3303
  const prose = r.t === "field" ? proseSwap(r.from, r.to) : r.t === "kind" ? proseSwap(r.from, r.to) : undefined;
3005
3304
  const exprAt = (source, ctx) => (source === undefined ? undefined : rewriteExpr(doc, source, ctx, r));
3006
3305
  const tmplAt = (source, ctx, words) => (source === undefined ? undefined : rewriteTemplate(doc, source, ctx, r, words));
3306
+ // What a kind follows (Roadmap f-keep-linked): its kind, relation and values follow the rename.
3307
+ touched.push(...this.followDerives(r, false));
3007
3308
  // Kinds: templates, the lifecycle, where relations lead.
3008
3309
  for (const [kind, spec] of Object.entries(doc.kinds)) {
3009
3310
  const here = new Set([kind]);
@@ -3058,6 +3359,12 @@ class Editor {
3058
3359
  act.severs = r.to;
3059
3360
  if (Array.isArray(act.replaces))
3060
3361
  act.replaces = act.replaces.map((e) => (e === r.from ? r.to : e));
3362
+ if (act.setsLinked?.[r.from] !== undefined) {
3363
+ const carried = {};
3364
+ for (const [k, v] of Object.entries(act.setsLinked))
3365
+ carried[k === r.from ? r.to : k] = v;
3366
+ act.setsLinked = carried;
3367
+ }
3061
3368
  for (const e of act.effects ?? []) {
3062
3369
  if (e.connect === r.from)
3063
3370
  e.connect = r.to;
@@ -3081,6 +3388,8 @@ class Editor {
3081
3388
  act.sets = renamed;
3082
3389
  else if (t.where === "setsOther")
3083
3390
  act.setsOther = renamed;
3391
+ else if (t.where === "setsLinked")
3392
+ act.setsLinked[t.relation] = renamed;
3084
3393
  else
3085
3394
  act.effects[t.index].set = renamed;
3086
3395
  }
@@ -3144,7 +3453,17 @@ class Editor {
3144
3453
  const ctx = rule.over === "graph" ? NONE : new Set([rule.over]);
3145
3454
  if (rule.when !== undefined)
3146
3455
  rule.when = exprAt(rule.when, ctx);
3147
- rule.require = exprAt(rule.require, ctx);
3456
+ if (rule.require !== undefined)
3457
+ rule.require = exprAt(rule.require, ctx);
3458
+ // A coverage rule names its kind and its relations outright: each follows its rename.
3459
+ if (rule.covers) {
3460
+ if (r.t === "kind" && rule.covers.every === r.from)
3461
+ rule.covers.every = r.to;
3462
+ if (r.t === "edge")
3463
+ for (const key of ["through", "via"])
3464
+ if (rule.covers[key] === r.from)
3465
+ rule.covers[key] = r.to;
3466
+ }
3148
3467
  const mentioned = JSON.stringify(rule) !== before;
3149
3468
  if (rule.says !== undefined)
3150
3469
  rule.says = tmplAt(rule.says, ctx, mentioned ? prose : undefined);
@@ -3294,6 +3613,7 @@ class Editor {
3294
3613
  // A kind's list ordered or grouped by it lets that go (a stored field removed, a computed one no longer worked out).
3295
3614
  if (r.t === "field")
3296
3615
  gone.push(...this.listsFollow(r.kind, r.from, undefined));
3616
+ gone.push(...this.followDerives(r, true));
3297
3617
  for (const [kind, spec] of Object.entries(doc.kinds)) {
3298
3618
  for (const key of ["label", "describe"]) {
3299
3619
  if (templateMentions(doc, spec[key], new Set([kind]), r)) {
@@ -3325,6 +3645,15 @@ class Editor {
3325
3645
  let uses = exprMentions(doc, act.allowedWhen, on, r) || templateMentions(doc, act.refusal, on, r);
3326
3646
  if (r.t === "edge")
3327
3647
  uses ||= act.connects === r.from || act.severs === r.from || (act.effects ?? []).some((e) => e.connect === r.from || e.sever === r.from);
3648
+ // A relation it carried values through carries nothing now: the entry goes; an act that did nothing else goes with it.
3649
+ if (r.t === "edge" && !uses && act.setsLinked?.[r.from] !== undefined) {
3650
+ delete act.setsLinked[r.from];
3651
+ if (Object.keys(act.setsLinked).length === 0)
3652
+ delete act.setsLinked;
3653
+ const stillDoes = act.creates || act.writes || act.sets || act.setsLinked || act.connects || act.severs || act.removes || (act.effects ?? []).length > 0;
3654
+ if (!stillDoes)
3655
+ uses = true;
3656
+ }
3328
3657
  // A relation it only replaced goes from the list; one that replaced nothing else goes with it (FR-115).
3329
3658
  if (r.t === "edge" && !uses && Array.isArray(act.replaces) && act.replaces.includes(r.from)) {
3330
3659
  act.replaces = act.replaces.filter((e) => e !== r.from);
@@ -3355,7 +3684,13 @@ class Editor {
3355
3684
  delete act.sets;
3356
3685
  if (act.setsOther && Object.keys(act.setsOther).length === 0)
3357
3686
  delete act.setsOther;
3358
- const doesSomething = act.creates || act.writes || act.sets || 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));
3687
+ // A relation whose carried fields are all gone carries nothing: it goes from `setsLinked`, and an empty `setsLinked` goes with it.
3688
+ for (const [relation, set] of Object.entries(act.setsLinked ?? {}))
3689
+ if (Object.keys(set).length === 0)
3690
+ delete act.setsLinked[relation];
3691
+ if (act.setsLinked && Object.keys(act.setsLinked).length === 0)
3692
+ delete act.setsLinked;
3693
+ 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));
3359
3694
  if (!doesSomething)
3360
3695
  uses = true;
3361
3696
  else if (act.effects) {
@@ -3370,7 +3705,9 @@ class Editor {
3370
3705
  }
3371
3706
  for (const [name, rule] of Object.entries(doc.rules ?? {})) {
3372
3707
  const ctx = rule.over === "graph" ? NONE : new Set([rule.over]);
3373
- if (exprMentions(doc, rule.when, ctx, r) || exprMentions(doc, rule.require, ctx, r) || templateMentions(doc, rule.says, ctx, r)) {
3708
+ // A coverage rule goes with the kind it covers or a relation it walks.
3709
+ 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)));
3710
+ if (walked || exprMentions(doc, rule.when, ctx, r) || exprMentions(doc, rule.require, ctx, r) || templateMentions(doc, rule.says, ctx, r)) {
3374
3711
  delete doc.rules[name];
3375
3712
  gone.push(titled("rule", name, rule));
3376
3713
  }