@graview/core 0.1.27 → 0.1.29

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 (505) hide show
  1. package/README.md +17 -0
  2. package/dist/amount-input.d.ts +21 -0
  3. package/dist/amount-input.d.ts.map +1 -0
  4. package/dist/amount-input.js +96 -0
  5. package/dist/amount-input.js.map +1 -0
  6. package/dist/answer-seen.d.ts +38 -0
  7. package/dist/answer-seen.d.ts.map +1 -0
  8. package/dist/answer-seen.js +122 -0
  9. package/dist/answer-seen.js.map +1 -0
  10. package/dist/app-places.d.ts +56 -0
  11. package/dist/app-places.d.ts.map +1 -0
  12. package/dist/app-places.js +153 -0
  13. package/dist/app-places.js.map +1 -0
  14. package/dist/arrange.d.ts +2 -2
  15. package/dist/arrange.d.ts.map +1 -1
  16. package/dist/arrange.js +2 -2
  17. package/dist/arrange.js.map +1 -1
  18. package/dist/arrangement-words.d.ts +3 -0
  19. package/dist/arrangement-words.d.ts.map +1 -0
  20. package/dist/arrangement-words.js +50 -0
  21. package/dist/arrangement-words.js.map +1 -0
  22. package/dist/arrangement.d.ts +25 -3
  23. package/dist/arrangement.d.ts.map +1 -1
  24. package/dist/arrangement.js +36 -32
  25. package/dist/arrangement.js.map +1 -1
  26. package/dist/arranging.d.ts.map +1 -1
  27. package/dist/arranging.js +19 -8
  28. package/dist/arranging.js.map +1 -1
  29. package/dist/bind.d.ts +3 -0
  30. package/dist/bind.d.ts.map +1 -1
  31. package/dist/bind.js.map +1 -1
  32. package/dist/capabilities.js +1 -1
  33. package/dist/capabilities.js.map +1 -1
  34. package/dist/capital.d.ts +2 -0
  35. package/dist/capital.d.ts.map +1 -1
  36. package/dist/capital.js +2 -0
  37. package/dist/capital.js.map +1 -1
  38. package/dist/check.d.ts +3 -0
  39. package/dist/check.d.ts.map +1 -1
  40. package/dist/check.js +3 -0
  41. package/dist/check.js.map +1 -1
  42. package/dist/city.d.ts +58 -50
  43. package/dist/city.d.ts.map +1 -1
  44. package/dist/city.js +160 -147
  45. package/dist/city.js.map +1 -1
  46. package/dist/cli/check/brand.js +1 -1
  47. package/dist/cli/check/brand.js.map +1 -1
  48. package/dist/cli/check/clock-rules.d.ts +4 -0
  49. package/dist/cli/check/clock-rules.d.ts.map +1 -0
  50. package/dist/cli/check/clock-rules.js +26 -0
  51. package/dist/cli/check/clock-rules.js.map +1 -0
  52. package/dist/cli/check/field-sight.d.ts +11 -0
  53. package/dist/cli/check/field-sight.d.ts.map +1 -0
  54. package/dist/cli/check/field-sight.js +113 -0
  55. package/dist/cli/check/field-sight.js.map +1 -0
  56. package/dist/cli/check/index.d.ts +2 -0
  57. package/dist/cli/check/index.d.ts.map +1 -1
  58. package/dist/cli/check/index.js +2 -0
  59. package/dist/cli/check/index.js.map +1 -1
  60. package/dist/cli/check/lenses.d.ts +1 -1
  61. package/dist/cli/check/lenses.d.ts.map +1 -1
  62. package/dist/cli/check/lenses.js +43 -2
  63. package/dist/cli/check/lenses.js.map +1 -1
  64. package/dist/cli/check/relations.d.ts.map +1 -1
  65. package/dist/cli/check/relations.js +2 -1
  66. package/dist/cli/check/relations.js.map +1 -1
  67. package/dist/cli/check.d.ts.map +1 -1
  68. package/dist/cli/check.js +7 -4
  69. package/dist/cli/check.js.map +1 -1
  70. package/dist/cli/describe.d.ts.map +1 -1
  71. package/dist/cli/describe.js +44 -3
  72. package/dist/cli/describe.js.map +1 -1
  73. package/dist/cli/docs.d.ts.map +1 -1
  74. package/dist/cli/docs.js +4 -3
  75. package/dist/cli/docs.js.map +1 -1
  76. package/dist/cli/types.d.ts.map +1 -1
  77. package/dist/cli/types.js +10 -1
  78. package/dist/cli/types.js.map +1 -1
  79. package/dist/clock-readers.d.ts +18 -0
  80. package/dist/clock-readers.d.ts.map +1 -0
  81. package/dist/clock-readers.js +70 -0
  82. package/dist/clock-readers.js.map +1 -0
  83. package/dist/clock.d.ts +30 -0
  84. package/dist/clock.d.ts.map +1 -0
  85. package/dist/clock.js +41 -0
  86. package/dist/clock.js.map +1 -0
  87. package/dist/columns.d.ts.map +1 -1
  88. package/dist/columns.js +2 -1
  89. package/dist/columns.js.map +1 -1
  90. package/dist/conformance/fixtures.d.ts.map +1 -1
  91. package/dist/conformance/fixtures.js +326 -43
  92. package/dist/conformance/fixtures.js.map +1 -1
  93. package/dist/days.d.ts +53 -4
  94. package/dist/days.d.ts.map +1 -1
  95. package/dist/days.js +136 -16
  96. package/dist/days.js.map +1 -1
  97. package/dist/describe.d.ts +1 -0
  98. package/dist/describe.d.ts.map +1 -1
  99. package/dist/describe.js +1 -0
  100. package/dist/describe.js.map +1 -1
  101. package/dist/document/act-findings.d.ts.map +1 -1
  102. package/dist/document/act-findings.js +13 -3
  103. package/dist/document/act-findings.js.map +1 -1
  104. package/dist/document/amount.d.ts +4 -0
  105. package/dist/document/amount.d.ts.map +1 -0
  106. package/dist/document/amount.js +22 -0
  107. package/dist/document/amount.js.map +1 -0
  108. package/dist/document/block-context.d.ts.map +1 -1
  109. package/dist/document/block-context.js +1 -0
  110. package/dist/document/block-context.js.map +1 -1
  111. package/dist/document/blocks.d.ts +13 -3
  112. package/dist/document/blocks.d.ts.map +1 -1
  113. package/dist/document/blocks.js +56 -12
  114. package/dist/document/blocks.js.map +1 -1
  115. package/dist/document/compile-checked.d.ts.map +1 -1
  116. package/dist/document/compile-checked.js +3 -2
  117. package/dist/document/compile-checked.js.map +1 -1
  118. package/dist/document/compile.d.ts.map +1 -1
  119. package/dist/document/compile.js +30 -7
  120. package/dist/document/compile.js.map +1 -1
  121. package/dist/document/compiled.d.ts +4 -2
  122. package/dist/document/compiled.d.ts.map +1 -1
  123. package/dist/document/compiled.js +52 -21
  124. package/dist/document/compiled.js.map +1 -1
  125. package/dist/document/describe-coverage.d.ts.map +1 -1
  126. package/dist/document/describe-coverage.js +2 -1
  127. package/dist/document/describe-coverage.js.map +1 -1
  128. package/dist/document/describe-place.d.ts +6 -2
  129. package/dist/document/describe-place.d.ts.map +1 -1
  130. package/dist/document/describe-place.js +14 -8
  131. package/dist/document/describe-place.js.map +1 -1
  132. package/dist/document/diff.d.ts +2 -0
  133. package/dist/document/diff.d.ts.map +1 -1
  134. package/dist/document/diff.js +78 -7
  135. package/dist/document/diff.js.map +1 -1
  136. package/dist/document/durations.d.ts +8 -0
  137. package/dist/document/durations.d.ts.map +1 -0
  138. package/dist/document/durations.js +82 -0
  139. package/dist/document/durations.js.map +1 -0
  140. package/dist/document/edit.d.ts +1 -1
  141. package/dist/document/edit.d.ts.map +1 -1
  142. package/dist/document/edit.js +434 -34
  143. package/dist/document/edit.js.map +1 -1
  144. package/dist/document/expr/evaluate.d.ts +1 -1
  145. package/dist/document/expr/evaluate.d.ts.map +1 -1
  146. package/dist/document/expr/evaluate.js +17 -4
  147. package/dist/document/expr/evaluate.js.map +1 -1
  148. package/dist/document/field-spec.d.ts.map +1 -1
  149. package/dist/document/field-spec.js +15 -1
  150. package/dist/document/field-spec.js.map +1 -1
  151. package/dist/document/format-findings.d.ts +4 -0
  152. package/dist/document/format-findings.d.ts.map +1 -0
  153. package/dist/document/format-findings.js +39 -0
  154. package/dist/document/format-findings.js.map +1 -0
  155. package/dist/document/format-rules.d.ts +8 -0
  156. package/dist/document/format-rules.d.ts.map +1 -0
  157. package/dist/document/format-rules.js +19 -0
  158. package/dist/document/format-rules.js.map +1 -0
  159. package/dist/document/format-words.d.ts +9 -0
  160. package/dist/document/format-words.d.ts.map +1 -0
  161. package/dist/document/format-words.js +29 -0
  162. package/dist/document/format-words.js.map +1 -0
  163. package/dist/document/graview-template.d.ts +2 -2
  164. package/dist/document/migrate.d.ts +1 -0
  165. package/dist/document/migrate.d.ts.map +1 -1
  166. package/dist/document/migrate.js +45 -1
  167. package/dist/document/migrate.js.map +1 -1
  168. package/dist/document/money.d.ts +14 -0
  169. package/dist/document/money.d.ts.map +1 -0
  170. package/dist/document/money.js +14 -0
  171. package/dist/document/money.js.map +1 -0
  172. package/dist/document/policy-words.d.ts +33 -0
  173. package/dist/document/policy-words.d.ts.map +1 -0
  174. package/dist/document/policy-words.js +69 -0
  175. package/dist/document/policy-words.js.map +1 -0
  176. package/dist/document/rule-line.d.ts.map +1 -1
  177. package/dist/document/rule-line.js +16 -12
  178. package/dist/document/rule-line.js.map +1 -1
  179. package/dist/document/rules.d.ts.map +1 -1
  180. package/dist/document/rules.js +6 -5
  181. package/dist/document/rules.js.map +1 -1
  182. package/dist/document/schema.d.ts +62 -19
  183. package/dist/document/schema.d.ts.map +1 -1
  184. package/dist/document/schema.js +30 -4
  185. package/dist/document/schema.js.map +1 -1
  186. package/dist/document/template-parse.d.ts.map +1 -1
  187. package/dist/document/template-parse.js +9 -2
  188. package/dist/document/template-parse.js.map +1 -1
  189. package/dist/document/template.d.ts +3 -13
  190. package/dist/document/template.d.ts.map +1 -1
  191. package/dist/document/template.js +27 -28
  192. package/dist/document/template.js.map +1 -1
  193. package/dist/document/thumbnail.d.ts.map +1 -1
  194. package/dist/document/thumbnail.js +4 -3
  195. package/dist/document/thumbnail.js.map +1 -1
  196. package/dist/document/to-document.d.ts +0 -1
  197. package/dist/document/to-document.d.ts.map +1 -1
  198. package/dist/document/to-document.js +19 -2
  199. package/dist/document/to-document.js.map +1 -1
  200. package/dist/document/view-specs.d.ts.map +1 -1
  201. package/dist/document/view-specs.js +3 -0
  202. package/dist/document/view-specs.js.map +1 -1
  203. package/dist/document/views.d.ts +2 -0
  204. package/dist/document/views.d.ts.map +1 -1
  205. package/dist/document/views.js +18 -6
  206. package/dist/document/views.js.map +1 -1
  207. package/dist/document.schema.json +63 -6
  208. package/dist/excerpt-of.d.ts +14 -0
  209. package/dist/excerpt-of.d.ts.map +1 -0
  210. package/dist/excerpt-of.js +120 -0
  211. package/dist/excerpt-of.js.map +1 -0
  212. package/dist/excerpt.d.ts +9 -0
  213. package/dist/excerpt.d.ts.map +1 -0
  214. package/dist/excerpt.js +43 -0
  215. package/dist/excerpt.js.map +1 -0
  216. package/dist/field-sight.d.ts +33 -0
  217. package/dist/field-sight.d.ts.map +1 -0
  218. package/dist/field-sight.js +313 -0
  219. package/dist/field-sight.js.map +1 -0
  220. package/dist/forms.d.ts +1 -0
  221. package/dist/forms.d.ts.map +1 -1
  222. package/dist/forms.js +1 -0
  223. package/dist/forms.js.map +1 -1
  224. package/dist/graph/archived.d.ts +25 -0
  225. package/dist/graph/archived.d.ts.map +1 -0
  226. package/dist/graph/archived.js +16 -0
  227. package/dist/graph/archived.js.map +1 -0
  228. package/dist/graph/diff.d.ts.map +1 -1
  229. package/dist/graph/diff.js +2 -1
  230. package/dist/graph/diff.js.map +1 -1
  231. package/dist/graph/graph.d.ts +37 -13
  232. package/dist/graph/graph.d.ts.map +1 -1
  233. package/dist/graph/graph.js +86 -40
  234. package/dist/graph/graph.js.map +1 -1
  235. package/dist/graph/tracked.d.ts +8 -8
  236. package/dist/graph/tracked.d.ts.map +1 -1
  237. package/dist/graph/tracked.js +15 -20
  238. package/dist/graph/tracked.js.map +1 -1
  239. package/dist/graph/types.d.ts +16 -8
  240. package/dist/graph/types.d.ts.map +1 -1
  241. package/dist/graph/types.js.map +1 -1
  242. package/dist/import/coerce.d.ts +45 -0
  243. package/dist/import/coerce.d.ts.map +1 -0
  244. package/dist/import/coerce.js +252 -0
  245. package/dist/import/coerce.js.map +1 -0
  246. package/dist/import/csv.d.ts +16 -0
  247. package/dist/import/csv.d.ts.map +1 -0
  248. package/dist/import/csv.js +77 -0
  249. package/dist/import/csv.js.map +1 -0
  250. package/dist/import/index.d.ts +17 -0
  251. package/dist/import/index.d.ts.map +1 -0
  252. package/dist/import/index.js +13 -0
  253. package/dist/import/index.js.map +1 -0
  254. package/dist/import/mapping.d.ts +99 -0
  255. package/dist/import/mapping.d.ts.map +1 -0
  256. package/dist/import/mapping.js +182 -0
  257. package/dist/import/mapping.js.map +1 -0
  258. package/dist/import/rows.d.ts +116 -0
  259. package/dist/import/rows.d.ts.map +1 -0
  260. package/dist/import/rows.js +584 -0
  261. package/dist/import/rows.js.map +1 -0
  262. package/dist/index.d.ts +38 -21
  263. package/dist/index.d.ts.map +1 -1
  264. package/dist/index.js +27 -16
  265. package/dist/index.js.map +1 -1
  266. package/dist/invariants/engine.d.ts +5 -1
  267. package/dist/invariants/engine.d.ts.map +1 -1
  268. package/dist/invariants/engine.js +4 -3
  269. package/dist/invariants/engine.js.map +1 -1
  270. package/dist/invariants/types.d.ts +18 -3
  271. package/dist/invariants/types.d.ts.map +1 -1
  272. package/dist/json-schema.d.ts +1 -0
  273. package/dist/json-schema.d.ts.map +1 -1
  274. package/dist/json-schema.js +2 -0
  275. package/dist/json-schema.js.map +1 -1
  276. package/dist/labels.d.ts.map +1 -1
  277. package/dist/labels.js +2 -1
  278. package/dist/labels.js.map +1 -1
  279. package/dist/lines.d.ts +1 -0
  280. package/dist/lines.d.ts.map +1 -1
  281. package/dist/lines.js +5 -3
  282. package/dist/lines.js.map +1 -1
  283. package/dist/markdown.d.ts +102 -0
  284. package/dist/markdown.d.ts.map +1 -0
  285. package/dist/markdown.js +460 -0
  286. package/dist/markdown.js.map +1 -0
  287. package/dist/mutations/arg-shape.d.ts +95 -0
  288. package/dist/mutations/arg-shape.d.ts.map +1 -0
  289. package/dist/mutations/arg-shape.js +101 -0
  290. package/dist/mutations/arg-shape.js.map +1 -0
  291. package/dist/mutations/define-mutation.d.ts +48 -2
  292. package/dist/mutations/define-mutation.d.ts.map +1 -1
  293. package/dist/mutations/define-mutation.js +103 -36
  294. package/dist/mutations/define-mutation.js.map +1 -1
  295. package/dist/mutations/derive-edits.d.ts +16 -1
  296. package/dist/mutations/derive-edits.d.ts.map +1 -1
  297. package/dist/mutations/derive-edits.js +67 -28
  298. package/dist/mutations/derive-edits.js.map +1 -1
  299. package/dist/mutations/derive-repeats.d.ts +8 -0
  300. package/dist/mutations/derive-repeats.d.ts.map +1 -0
  301. package/dist/mutations/derive-repeats.js +118 -0
  302. package/dist/mutations/derive-repeats.js.map +1 -0
  303. package/dist/mutations/derived-names.d.ts +20 -0
  304. package/dist/mutations/derived-names.d.ts.map +1 -0
  305. package/dist/mutations/derived-names.js +19 -0
  306. package/dist/mutations/derived-names.js.map +1 -0
  307. package/dist/mutations/form.d.ts +21 -1
  308. package/dist/mutations/form.d.ts.map +1 -1
  309. package/dist/mutations/form.js +19 -2
  310. package/dist/mutations/form.js.map +1 -1
  311. package/dist/mutations/invalid-arguments.d.ts +14 -0
  312. package/dist/mutations/invalid-arguments.d.ts.map +1 -0
  313. package/dist/mutations/invalid-arguments.js +18 -0
  314. package/dist/mutations/invalid-arguments.js.map +1 -0
  315. package/dist/mutations/keep-link.d.ts +27 -0
  316. package/dist/mutations/keep-link.d.ts.map +1 -0
  317. package/dist/mutations/keep-link.js +56 -0
  318. package/dist/mutations/keep-link.js.map +1 -0
  319. package/dist/mutations/kept-arguments.d.ts +26 -0
  320. package/dist/mutations/kept-arguments.d.ts.map +1 -0
  321. package/dist/mutations/kept-arguments.js +77 -0
  322. package/dist/mutations/kept-arguments.js.map +1 -0
  323. package/dist/mutations/node-ref.d.ts +0 -80
  324. package/dist/mutations/node-ref.d.ts.map +1 -1
  325. package/dist/mutations/node-ref.js +0 -93
  326. package/dist/mutations/node-ref.js.map +1 -1
  327. package/dist/mutations/types.d.ts +17 -1
  328. package/dist/mutations/types.d.ts.map +1 -1
  329. package/dist/mutations/words.d.ts +0 -13
  330. package/dist/mutations/words.d.ts.map +1 -1
  331. package/dist/mutations/words.js +1 -12
  332. package/dist/mutations/words.js.map +1 -1
  333. package/dist/ops/kept-fields.d.ts +7 -0
  334. package/dist/ops/kept-fields.d.ts.map +1 -0
  335. package/dist/ops/kept-fields.js +24 -0
  336. package/dist/ops/kept-fields.js.map +1 -0
  337. package/dist/ops/lineage.d.ts +37 -0
  338. package/dist/ops/lineage.d.ts.map +1 -0
  339. package/dist/ops/lineage.js +26 -0
  340. package/dist/ops/lineage.js.map +1 -0
  341. package/dist/ops/log.d.ts.map +1 -1
  342. package/dist/ops/log.js +1 -0
  343. package/dist/ops/log.js.map +1 -1
  344. package/dist/ops/provenance.d.ts +68 -0
  345. package/dist/ops/provenance.d.ts.map +1 -0
  346. package/dist/ops/provenance.js +83 -0
  347. package/dist/ops/provenance.js.map +1 -0
  348. package/dist/ops/types.d.ts +25 -0
  349. package/dist/ops/types.d.ts.map +1 -1
  350. package/dist/ops/withheld.d.ts +25 -2
  351. package/dist/ops/withheld.d.ts.map +1 -1
  352. package/dist/ops/withheld.js +6 -15
  353. package/dist/ops/withheld.js.map +1 -1
  354. package/dist/permissions/policy.d.ts +6 -1
  355. package/dist/permissions/policy.d.ts.map +1 -1
  356. package/dist/permissions/policy.js +31 -11
  357. package/dist/permissions/policy.js.map +1 -1
  358. package/dist/permissions/sight.d.ts +40 -2
  359. package/dist/permissions/sight.d.ts.map +1 -1
  360. package/dist/permissions/sight.js +25 -2
  361. package/dist/permissions/sight.js.map +1 -1
  362. package/dist/permissions/types.d.ts +21 -0
  363. package/dist/permissions/types.d.ts.map +1 -1
  364. package/dist/permissions/types.js.map +1 -1
  365. package/dist/places.d.ts +0 -49
  366. package/dist/places.d.ts.map +1 -1
  367. package/dist/places.js +2 -141
  368. package/dist/places.js.map +1 -1
  369. package/dist/provenance.d.ts +14 -0
  370. package/dist/provenance.d.ts.map +1 -0
  371. package/dist/provenance.js +13 -0
  372. package/dist/provenance.js.map +1 -0
  373. package/dist/repeat-words.d.ts +13 -0
  374. package/dist/repeat-words.d.ts.map +1 -0
  375. package/dist/repeat-words.js +56 -0
  376. package/dist/repeat-words.js.map +1 -0
  377. package/dist/repeat.d.ts +59 -0
  378. package/dist/repeat.d.ts.map +1 -0
  379. package/dist/repeat.js +155 -0
  380. package/dist/repeat.js.map +1 -0
  381. package/dist/scaffold/project.d.ts.map +1 -1
  382. package/dist/scaffold/project.js +13 -0
  383. package/dist/scaffold/project.js.map +1 -1
  384. package/dist/scaffold/ui.js +2 -2
  385. package/dist/scene-districts.d.ts +2 -1
  386. package/dist/scene-districts.d.ts.map +1 -1
  387. package/dist/scene-districts.js +2 -1
  388. package/dist/scene-districts.js.map +1 -1
  389. package/dist/scene.d.ts +4 -2
  390. package/dist/scene.d.ts.map +1 -1
  391. package/dist/scene.js +2 -1
  392. package/dist/scene.js.map +1 -1
  393. package/dist/schema/amount-mark.d.ts +13 -0
  394. package/dist/schema/amount-mark.d.ts.map +1 -0
  395. package/dist/schema/amount-mark.js +20 -0
  396. package/dist/schema/amount-mark.js.map +1 -0
  397. package/dist/schema/amount-words.d.ts +12 -0
  398. package/dist/schema/amount-words.d.ts.map +1 -0
  399. package/dist/schema/amount-words.js +31 -0
  400. package/dist/schema/amount-words.js.map +1 -0
  401. package/dist/schema/app-link-field.d.ts +4 -0
  402. package/dist/schema/app-link-field.d.ts.map +1 -0
  403. package/dist/schema/app-link-field.js +19 -0
  404. package/dist/schema/app-link-field.js.map +1 -0
  405. package/dist/schema/app-link.d.ts +41 -0
  406. package/dist/schema/app-link.d.ts.map +1 -0
  407. package/dist/schema/app-link.js +40 -0
  408. package/dist/schema/app-link.js.map +1 -0
  409. package/dist/schema/define-node.d.ts +25 -94
  410. package/dist/schema/define-node.d.ts.map +1 -1
  411. package/dist/schema/define-node.js +3 -247
  412. package/dist/schema/define-node.js.map +1 -1
  413. package/dist/schema/field-words.d.ts +45 -0
  414. package/dist/schema/field-words.d.ts.map +1 -0
  415. package/dist/schema/field-words.js +124 -0
  416. package/dist/schema/field-words.js.map +1 -0
  417. package/dist/schema/json-schema.d.ts +6 -1
  418. package/dist/schema/json-schema.d.ts.map +1 -1
  419. package/dist/schema/json-schema.js +83 -2
  420. package/dist/schema/json-schema.js.map +1 -1
  421. package/dist/schema/markdown-field.d.ts +4 -0
  422. package/dist/schema/markdown-field.d.ts.map +1 -0
  423. package/dist/schema/markdown-field.js +20 -0
  424. package/dist/schema/markdown-field.js.map +1 -0
  425. package/dist/schema/markdown.d.ts +6 -0
  426. package/dist/schema/markdown.d.ts.map +1 -0
  427. package/dist/schema/markdown.js +27 -0
  428. package/dist/schema/markdown.js.map +1 -0
  429. package/dist/schema/reading.d.ts +86 -0
  430. package/dist/schema/reading.d.ts.map +1 -0
  431. package/dist/schema/reading.js +270 -0
  432. package/dist/schema/reading.js.map +1 -0
  433. package/dist/schema/repeat-field.d.ts +5 -0
  434. package/dist/schema/repeat-field.d.ts.map +1 -0
  435. package/dist/schema/repeat-field.js +20 -0
  436. package/dist/schema/repeat-field.js.map +1 -0
  437. package/dist/schema/schema.d.ts.map +1 -1
  438. package/dist/schema/schema.js +5 -1
  439. package/dist/schema/schema.js.map +1 -1
  440. package/dist/schema/to-json-schema.d.ts +14 -0
  441. package/dist/schema/to-json-schema.d.ts.map +1 -0
  442. package/dist/schema/to-json-schema.js +616 -0
  443. package/dist/schema/to-json-schema.js.map +1 -0
  444. package/dist/schema/typed-field-builders.d.ts +8 -0
  445. package/dist/schema/typed-field-builders.d.ts.map +1 -0
  446. package/dist/schema/typed-field-builders.js +30 -0
  447. package/dist/schema/typed-field-builders.js.map +1 -0
  448. package/dist/schema/typed-field-of.d.ts +4 -0
  449. package/dist/schema/typed-field-of.d.ts.map +1 -0
  450. package/dist/schema/typed-field-of.js +14 -0
  451. package/dist/schema/typed-field-of.js.map +1 -0
  452. package/dist/schema/typed-fields.d.ts +11 -0
  453. package/dist/schema/typed-fields.d.ts.map +1 -0
  454. package/dist/schema/typed-fields.js +16 -0
  455. package/dist/schema/typed-fields.js.map +1 -0
  456. package/dist/schema/types.d.ts +14 -0
  457. package/dist/schema/types.d.ts.map +1 -1
  458. package/dist/schema/value-words.d.ts +21 -0
  459. package/dist/schema/value-words.d.ts.map +1 -0
  460. package/dist/schema/value-words.js +32 -0
  461. package/dist/schema/value-words.js.map +1 -0
  462. package/dist/search.d.ts +91 -4
  463. package/dist/search.d.ts.map +1 -1
  464. package/dist/search.js +290 -59
  465. package/dist/search.js.map +1 -1
  466. package/dist/seat.d.ts +19 -0
  467. package/dist/seat.d.ts.map +1 -0
  468. package/dist/seat.js +19 -0
  469. package/dist/seat.js.map +1 -0
  470. package/dist/seen.d.ts +15 -33
  471. package/dist/seen.d.ts.map +1 -1
  472. package/dist/seen.js +44 -135
  473. package/dist/seen.js.map +1 -1
  474. package/dist/store.d.ts +46 -1
  475. package/dist/store.d.ts.map +1 -1
  476. package/dist/store.js +121 -22
  477. package/dist/store.js.map +1 -1
  478. package/dist/theme/types.d.ts +7 -1
  479. package/dist/theme/types.d.ts.map +1 -1
  480. package/dist/theme/types.js.map +1 -1
  481. package/dist/typed.d.ts +12 -3
  482. package/dist/typed.d.ts.map +1 -1
  483. package/dist/typed.js.map +1 -1
  484. package/dist/validate-graph.d.ts.map +1 -1
  485. package/dist/validate-graph.js +3 -10
  486. package/dist/validate-graph.js.map +1 -1
  487. package/dist/version.d.ts +1 -1
  488. package/dist/version.js +1 -1
  489. package/dist/views/types.d.ts +57 -8
  490. package/dist/views/types.d.ts.map +1 -1
  491. package/dist/views/types.js +22 -3
  492. package/dist/views/types.js.map +1 -1
  493. package/dist/village.d.ts +63 -0
  494. package/dist/village.d.ts.map +1 -0
  495. package/dist/village.js +143 -0
  496. package/dist/village.js.map +1 -0
  497. package/dist/watched.d.ts +12 -45
  498. package/dist/watched.d.ts.map +1 -1
  499. package/dist/watched.js +7 -119
  500. package/dist/watched.js.map +1 -1
  501. package/dist/words.d.ts +33 -0
  502. package/dist/words.d.ts.map +1 -0
  503. package/dist/words.js +36 -0
  504. package/dist/words.js.map +1 -0
  505. package/package.json +37 -2
@@ -1,11 +1,19 @@
1
+ import { DERIVED_ACTS, derivedActOf, derivedActsOf, givenWhenNeverArchived } from "../mutations/derived-names.js";
1
2
  import { z } from "zod";
2
3
  import { ExprSyntaxError, parseExpr } from "./expr/parse.js";
3
4
  import { printExpr } from "./expr/print.js";
4
5
  import { error } from "./findings.js";
5
6
  import { coerce } from "./migrate.js";
6
7
  import { outsideRange, rangeWords } from "./range.js";
8
+ import { formatWords } from "./format-words.js";
9
+ import { formatFits, formatMisplaced } from "./format-rules.js";
10
+ /** Why a format is refused where it does not fit, in the checker's words (`field-format`). */
11
+ const refusedFormat = (format, type) => {
12
+ const { sentence, fix } = formatMisplaced(format, type);
13
+ return `${sentence}; ${fix}`;
14
+ };
7
15
  import { farEnd } from "./far-end.js";
8
- import { ActSpec, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, KindSpec, NAME, RuleSpec, } from "./schema.js";
16
+ import { ActSpec, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
9
17
  import { SHIPPED_LENSES, isShippedLens, SHIPPED_LENS_NAMES } from "../places.js";
10
18
  import { placeSlug } from "../views/types.js";
11
19
  import { computedOf, validateComputed } from "./computed.js";
@@ -16,9 +24,12 @@ import { canonicalize } from "./canonical.js";
16
24
  import { quotedAtTheEnd } from "./quote.js";
17
25
  import { isRecord as isObject, respellDocument } from "./respell.js";
18
26
  import { refName } from "./compiled.js";
27
+ import { localeSentence } from "./diff.js";
19
28
  import { accentProblem } from "../theme/accent.js";
20
29
  import { pageFieldFindings, pageNames, renamedOnPage, withoutOnPage } from "./page-fields.js";
21
- import { capitalize } from "../capital.js";
30
+ import { capitalize, listed as listOf } from "../capital.js";
31
+ 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";
22
33
  /** The ops, for tool schemas and the guide. */
23
34
  export const EDIT_OPS = [
24
35
  "add-kind",
@@ -29,8 +40,10 @@ export const EDIT_OPS = [
29
40
  "retype-field",
30
41
  "set-options",
31
42
  "set-range",
43
+ "set-format",
32
44
  "set-required",
33
45
  "set-default",
46
+ "set-repeats",
34
47
  "remove-field",
35
48
  "add-relation",
36
49
  "rename-relation",
@@ -52,6 +65,12 @@ export const EDIT_OPS = [
52
65
  "set-home",
53
66
  "arrange-pages",
54
67
  "set-computed",
68
+ "set-people",
69
+ "set-archives",
70
+ "add-grant",
71
+ "remove-grant",
72
+ "add-sight",
73
+ "remove-sight",
55
74
  ];
56
75
  /** Words the rule language keeps: a field or relation renamed to one could not be named in a rule. */
57
76
  const KEYWORDS = new Set(["and", "or", "not", "in", "where", "true", "false", "null"]);
@@ -68,11 +87,13 @@ const SHAPES = {
68
87
  "remove-kind": z.object({ op: z.literal("remove-kind"), kind: kindName }).strict(),
69
88
  "add-field": z.object({ op: z.literal("add-field"), kind: kindName, field: fieldName, fill: z.unknown().optional(), spec: z.record(z.string(), z.unknown()).optional() }).passthrough(),
70
89
  "rename-field": z.object({ op: z.literal("rename-field"), kind: kindName, field: fieldName, to: fieldName }).strict(),
71
- "retype-field": z.object({ op: z.literal("retype-field"), kind: kindName, field: fieldName, type: z.enum(FIELD_TYPES), options: z.array(z.string().min(1).max(80)).min(1).max(100).optional(), of: z.enum(["string", "number", "date", "image", "file"]).optional(), format: z.enum(["money", "percent", "duration"]).optional() }).strict(),
90
+ "retype-field": z.object({ op: z.literal("retype-field"), kind: kindName, field: fieldName, type: z.enum(FIELD_TYPES), options: z.array(z.string().min(1).max(80)).min(1).max(100).optional(), of: z.enum(["string", "number", "date", "image", "file"]).optional(), format: z.enum(["money", "percent", "duration", "markdown"]).optional() }).strict(),
72
91
  "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() }).strict(),
92
+ "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(),
73
93
  "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(),
74
94
  "set-required": z.object({ op: z.literal("set-required"), kind: kindName, field: fieldName, required: z.boolean() }).strict(),
75
95
  "set-default": z.object({ op: z.literal("set-default"), kind: kindName, field: fieldName, default: z.unknown() }).strict(),
96
+ "set-repeats": z.object({ op: z.literal("set-repeats"), kind: kindName, field: fieldName, repeats: z.boolean() }).strict(),
76
97
  "remove-field": z.object({ op: z.literal("remove-field"), kind: kindName, field: fieldName }).strict(),
77
98
  "add-relation": z.object({ op: z.literal("add-relation"), kind: kindName, relation: edgeName }).passthrough(),
78
99
  "rename-relation": z.object({ op: z.literal("rename-relation"), kind: kindName, relation: edgeName, to: edgeName }).strict(),
@@ -136,6 +157,8 @@ const SHAPES = {
136
157
  primary: z.union([z.array(z.string().min(1).max(80)).max(40), z.null()]).optional(),
137
158
  scene: z.union([z.string().min(1).max(40), z.null()]).optional(),
138
159
  pages: z.union([z.string().min(1).max(40), z.null()]).optional(),
160
+ // What a kind's list offers to filter by: `{ "request": ["status", "became.stage"] }`; null or [] gives a kind back its derived filters.
161
+ filters: z.record(kindName, z.union([z.array(z.string().min(1).max(120)).max(40), z.null()])).optional(),
139
162
  // The scene's word as a chat or a tool written before 0.1.17 sends it (FR-132's key, RESPELLED as `scene`).
140
163
  overview: z.union([z.string().min(1).max(40), z.null()]).optional(),
141
164
  })
@@ -150,6 +173,14 @@ const SHAPES = {
150
173
  description: z.union([z.string().min(1).max(500), z.null()]).optional(),
151
174
  })
152
175
  .strict(),
176
+ // Who the people are, and who may do and see what (FR-170): a grant and a sight as the document holds one.
177
+ "set-people": z.object({ op: z.literal("set-people"), kind: kindName, people: z.boolean() }).strict(),
178
+ // Whether a kind's records are archived and merged, or only kept and removed (FR-173).
179
+ "set-archives": z.object({ op: z.literal("set-archives"), kind: kindName, archives: z.boolean() }).strict(),
180
+ "add-grant": z.object({ op: z.literal("add-grant") }).passthrough(),
181
+ "remove-grant": z.object({ op: z.literal("remove-grant") }).passthrough(),
182
+ "add-sight": z.object({ op: z.literal("add-sight") }).passthrough(),
183
+ "remove-sight": z.object({ op: z.literal("remove-sight") }).passthrough(),
153
184
  };
154
185
  const NONE = new Set();
155
186
  const clone = (v) => structuredClone(v);
@@ -258,9 +289,10 @@ function mapNames(doc, e, ctx, rename) {
258
289
  const first = x.args[0];
259
290
  const literal = first?.t === "lit" && typeof first.value === "string" ? first.value : undefined;
260
291
  let args;
261
- if ((x.fn === "out" || x.fn === "in" || x.fn === "all") && literal !== undefined && x.args.length === 1) {
292
+ if ((x.fn === "out" || x.fn === "in" || x.fn === "all") && literal !== undefined && (x.args.length === 1 || (x.fn === "all" && x.args.length === 2))) {
262
293
  const name = rename({ role: x.fn === "all" ? "kind" : "edge", kinds: NONE, name: literal });
263
- args = name === literal ? [...x.args] : [{ ...first, value: name }];
294
+ // all('task', 'all') keeps what it asked of the archived (FR-173).
295
+ args = name === literal ? [...x.args] : [{ ...first, value: name }, ...x.args.slice(1)];
264
296
  }
265
297
  else if ((x.fn === "out" || x.fn === "in") && x.args.length === 2 && x.args[1].t === "lit" && typeof x.args[1].value === "string") {
266
298
  // out(S, 'edge'): the set is read where the walk stands, and its relation renamed like any walk's (FR-101).
@@ -494,13 +526,17 @@ class Editor {
494
526
  brand["currency"] = e.currency;
495
527
  this.said.push(`Money is said in ${e.currency}.`);
496
528
  }
497
- if (e.locale === null)
529
+ if (e.locale === null) {
530
+ if (brand["locale"] !== undefined)
531
+ this.said.push(localeSentence(undefined));
498
532
  delete brand["locale"];
533
+ }
499
534
  else if (e.locale !== undefined) {
500
535
  if (!/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(e.locale))
501
536
  return this.fail(i, "locale", 'a locale is a language tag, like "en-US" or "de-DE"');
502
537
  brand["locale"] = e.locale;
503
- this.said.push(`Money is written for ${e.locale}.`);
538
+ // Its days, times and numbers as well as its money (FR-178).
539
+ this.said.push(localeSentence(e.locale));
504
540
  }
505
541
  if (e.name === null) {
506
542
  delete brand["name"];
@@ -638,6 +674,8 @@ class Editor {
638
674
  return this.setOptions(i, e);
639
675
  case "set-range":
640
676
  return this.setRange(i, e);
677
+ case "set-format":
678
+ return this.setFormat(i, e);
641
679
  case "set-required": {
642
680
  const f = this.field(i, e.kind, e.field);
643
681
  if (!f)
@@ -668,6 +706,20 @@ class Editor {
668
706
  }
669
707
  return;
670
708
  }
709
+ case "set-repeats": {
710
+ // A date that repeats keeps how it does beside it, as `<field>Repeat` (`repeat.ts`).
711
+ const f = this.field(i, e.kind, e.field);
712
+ if (!f)
713
+ return;
714
+ if (e.repeats && f.type !== "date" && f.type !== "datetime")
715
+ return this.fail(i, "repeats", `${e.kind}'s ${e.field} is ${f.type === "enum" ? "a choice" : `a ${f.type}`}, and only a date or a date and time can repeat`);
716
+ if (e.repeats)
717
+ f.repeats = true;
718
+ else
719
+ delete f.repeats;
720
+ this.said.push(e.repeats ? `${e.kind}'s ${e.field} may repeat: weekly, monthly, on chosen days, until a day.` : `${e.kind}'s ${e.field} no longer repeats.`);
721
+ return;
722
+ }
671
723
  case "remove-field":
672
724
  return this.removeField(i, e.kind, e.field);
673
725
  case "add-relation":
@@ -785,6 +837,15 @@ class Editor {
785
837
  return this.arrangePages(i, e);
786
838
  case "set-computed":
787
839
  return this.setComputed(i, e);
840
+ case "set-people":
841
+ return this.setPeople(i, e);
842
+ case "set-archives":
843
+ return this.setArchives(i, e);
844
+ case "add-grant":
845
+ case "remove-grant":
846
+ case "add-sight":
847
+ case "remove-sight":
848
+ return this.policyEdit(i, e);
788
849
  }
789
850
  }
790
851
  /** How a record's page orders and groups its facts (FR-148): judged as the compile judges `kinds.<kind>.page`. */
@@ -985,8 +1046,17 @@ class Editor {
985
1046
  // `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.
986
1047
  const { overview, ...rest } = given;
987
1048
  const e = rest.scene === undefined && overview !== undefined ? { ...rest, scene: overview } : rest;
988
- if (e.order === undefined && e.hide === undefined && e.first === undefined && e.scene === undefined && e.pages === undefined && e.primary === undefined)
989
- return this.fail(i, "", 'arrange-pages says at least one of "order", "hide", "first", "primary", "scene" or "pages"');
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"');
1051
+ for (const [kind, keys] of Object.entries((e.filters ?? {}))) {
1052
+ if (!this.doc.kinds[kind])
1053
+ return this.fail(i, `filters.${kind}`, `"${kind}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`);
1054
+ for (const [n, key] of (keys ?? []).entries()) {
1055
+ const offered = this.filterKeys(kind);
1056
+ if (!offered.includes(key))
1057
+ return this.fail(i, `filters.${kind}.${n}`, `${kind} cannot be filtered by "${key}"`, `name one of ${offered.join(", ")}`);
1058
+ }
1059
+ }
990
1060
  for (const [n, word] of (e.primary ?? []).entries()) {
991
1061
  if (typeof word !== "string" || !this.opens(word) || word.trim().toLowerCase() === "home") {
992
1062
  const titles = (this.doc.lenses ?? []).flatMap((lens) => (isObject(lens) && typeof lens["title"] === "string" ? [`"${lens["title"]}"`] : []));
@@ -1065,10 +1135,46 @@ class Editor {
1065
1135
  said.push(`the switch calls the ${face} "${pages[face]}"`);
1066
1136
  }
1067
1137
  }
1138
+ for (const [kind, keys] of Object.entries((e.filters ?? {}))) {
1139
+ const lists = (pages.lists ??= {});
1140
+ if (keys === null || keys.length === 0) {
1141
+ delete lists[kind];
1142
+ said.push(`the list of ${pluralWords(this.doc, kind)} offers its own filters again`);
1143
+ }
1144
+ else {
1145
+ lists[kind] = { filters: [...keys] };
1146
+ said.push(`the list of ${pluralWords(this.doc, kind)} filters by ${listOf(keys.map((key) => `"${key}"`))}`);
1147
+ }
1148
+ if (Object.keys(lists).length === 0)
1149
+ delete pages.lists;
1150
+ }
1068
1151
  if (Object.keys(pages).length === 0)
1069
1152
  delete this.doc.pages;
1070
1153
  this.said.push(`${capitalize(listOf(said))}.`);
1071
1154
  }
1155
+ /** 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
+ filterKeys(kind) {
1157
+ const kinds = this.doc.kinds;
1158
+ const keys = new Set(["is", ...Object.keys(kinds[kind]?.fields ?? {})]);
1159
+ const edges = new Map();
1160
+ for (const [edge, spec] of Object.entries((kinds[kind]?.edges ?? {})))
1161
+ edges.set(edge, spec.to === "*" ? [] : [spec.to].flat());
1162
+ for (const [other, spec] of Object.entries(kinds)) {
1163
+ for (const [edge, declared] of Object.entries((spec.edges ?? {}))) {
1164
+ if (other !== kind && !edges.has(edge) && (declared.to === "*" || [declared.to].flat().includes(kind)))
1165
+ edges.set(edge, [other]);
1166
+ }
1167
+ }
1168
+ for (const [edge, far] of edges) {
1169
+ keys.add(edge);
1170
+ if (far.length !== 1 || far[0] === kind)
1171
+ continue;
1172
+ for (const [field, spec] of Object.entries((kinds[far[0]]?.fields ?? {})))
1173
+ if (spec.type === "enum")
1174
+ keys.add(`${edge}.${field}`);
1175
+ }
1176
+ return [...keys];
1177
+ }
1072
1178
  /** Whether `first` names something the app can open on: home, a kind or its plural, a titled lens. */
1073
1179
  opens(first) {
1074
1180
  const word = first.trim();
@@ -1136,6 +1242,151 @@ class Editor {
1136
1242
  else
1137
1243
  this.said.push(`${plural}' "${name}" is ${next.label ? `shown as "${next.label}"` : "shown by its name"}${next.description ? `, described as "${next.description}"` : ""}.`);
1138
1244
  }
1245
+ // ── who the people are, and who may do and see what (FR-170) ──
1246
+ /** The last edit that changed the people or the policy: where what they make together is judged. */
1247
+ lastPolicyEdit = -1;
1248
+ failAs(code, i, path, message, fix) {
1249
+ this.findings.push(error(code, `edits.${i}${path ? `.${path}` : ""}`, message, fix));
1250
+ }
1251
+ setPeople(i, e) {
1252
+ const spec = this.kind(i, e.kind);
1253
+ if (!spec)
1254
+ return;
1255
+ this.lastPolicyEdit = i;
1256
+ const already = (spec.people === true) === e.people;
1257
+ if (e.people && !already) {
1258
+ // Exactly one kind is the people: a principal's id is a record of it, never of one of two.
1259
+ const other = peopleKinds(this.doc.kinds)[0];
1260
+ if (other)
1261
+ return this.failAs("people-ambiguous", i, "kind", `${pluralOf(this.doc.kinds, other)} are already the people who sign in, and an app has one kind for them`, `{"op": "set-people", "kind": "${other}", "people": false} first, in the same change`);
1262
+ spec.people = true;
1263
+ }
1264
+ else if (!e.people)
1265
+ delete spec.people;
1266
+ this.said.push(peopleSentence(this.doc.kinds, e.kind, e.people, already));
1267
+ }
1268
+ /** A kind's records kept or removed, never archived (FR-173) — or archived and merged again. */
1269
+ setArchives(i, e) {
1270
+ const spec = this.kind(i, e.kind);
1271
+ if (!spec)
1272
+ return;
1273
+ const already = (spec.archives !== false) === e.archives;
1274
+ if (!e.archives && !already) {
1275
+ // A grant that names one of the three acts would name an act the app no longer has.
1276
+ const gone = new Set(["archive", "unarchive", "merge"].map((verb) => `${verb}-${e.kind}`).filter((act) => !this.doc.acts?.[act]));
1277
+ const grant = (this.doc.policy?.grants ?? []).find((g) => Array.isArray(g.mutations) && g.mutations.some((m) => gone.has(m)));
1278
+ if (grant) {
1279
+ const named = grant.mutations.filter((m) => gone.has(m));
1280
+ return this.failAs("grant-unknown-mutation", i, "archives", `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`);
1281
+ }
1282
+ spec.archives = false;
1283
+ }
1284
+ else if (e.archives)
1285
+ delete spec.archives;
1286
+ if (!already)
1287
+ this.toolsMoved.add(`archive-${e.kind}, unarchive-${e.kind} and merge-${e.kind} ${e.archives ? "are offered" : "are gone"}`);
1288
+ this.said.push(archivesSentence(this.doc.kinds, e.kind, e.archives, already));
1289
+ }
1290
+ /** A grant or a sight as the document holds one, or undefined having said why not. */
1291
+ policyPart(i, e, sight) {
1292
+ const { op: _op, ...rest } = e;
1293
+ const parsed = (sight ? SightSpec : GrantSpec).safeParse(rest);
1294
+ if (!parsed.success)
1295
+ return void this.refuse(i, parsed.error.issues, sight ? "a sight" : "a grant");
1296
+ return parsed.data;
1297
+ }
1298
+ policyEdit(i, e) {
1299
+ const sight = e.op === "add-sight" || e.op === "remove-sight";
1300
+ const part = this.policyPart(i, e, sight);
1301
+ if (!part)
1302
+ return;
1303
+ this.lastPolicyEdit = i;
1304
+ if (e.op === "remove-grant" || e.op === "remove-sight")
1305
+ return this.removePolicyPart(i, part, sight);
1306
+ // What it names is the app's: its kinds, and a grant's acts (a kind's derived edit and remove among them).
1307
+ if (!sight && Array.isArray(part.kinds) && part.kinds.length === 0)
1308
+ return this.fail(i, "kinds", "a grant on no kind gives nothing", "leave kinds out for every kind, or name the kinds it is on");
1309
+ for (const [n, kind] of (Array.isArray(part.kinds) ? part.kinds : []).entries()) {
1310
+ if (!this.doc.kinds[kind])
1311
+ return this.failAs(sight ? "sight-kind" : "edit", i, `kinds.${n}`, `"${kind}" is not a kind this app has; it has ${Object.keys(this.doc.kinds).join(", ")}`);
1312
+ }
1313
+ // A field a sight shows or hides is one of the kinds it shows (Roadmap f-field-sight).
1314
+ for (const key of sight ? ["fields", "hide"] : []) {
1315
+ for (const [n, field] of (part[key] ?? []).entries()) {
1316
+ const kinds = part.kinds;
1317
+ if (kinds.some((kind) => this.doc.kinds[kind]?.fields[field]))
1318
+ continue;
1319
+ 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(", ")}`);
1320
+ }
1321
+ }
1322
+ if (!sight && part.mutations !== "*") {
1323
+ for (const [n, act] of part.mutations.entries()) {
1324
+ 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))))
1326
+ continue;
1327
+ 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
+ }
1329
+ }
1330
+ const said = [];
1331
+ // A role the app has not declared becomes one of its roles, where it declares them.
1332
+ if (Array.isArray(this.doc.roles) && part.roles !== "*") {
1333
+ for (const role of part.roles) {
1334
+ if (this.doc.roles.includes(role))
1335
+ continue;
1336
+ this.doc.roles.push(role);
1337
+ said.push(`${capitalize(whoWords([role]))} is a new role.`);
1338
+ }
1339
+ }
1340
+ if (!this.doc.policy) {
1341
+ // No policy permits everything; a sight alone would take every act away, so its policy says everyone may still do them.
1342
+ const everything = { roles: "*", mutations: "*" };
1343
+ this.doc.policy = { grants: sight ? [everything] : [] };
1344
+ said.push(FIRST_GRANT, ...(sight ? [grantSentence(this.doc, everything)] : []));
1345
+ }
1346
+ if (sight) {
1347
+ if (!this.doc.policy.sees?.length)
1348
+ said.push(FIRST_SIGHT);
1349
+ (this.doc.policy.sees ??= []).push(part);
1350
+ said.push(sightSentence(this.doc, part));
1351
+ }
1352
+ else {
1353
+ this.doc.policy.grants.push(part);
1354
+ said.push(grantSentence(this.doc, part));
1355
+ }
1356
+ this.said.push(...said);
1357
+ }
1358
+ /**
1359
+ * A GRANT OR A SIGHT, NAMED BY WHAT IT SAYS. A chat names one the way it
1360
+ * would say it — "the grant that lets everyone take back a vote" — so a
1361
+ * removal names it by its keys: roles and acts (or kinds) in any order,
1362
+ * and any other key it gives must match too. Never by its place in the
1363
+ * list, which another change can move.
1364
+ */
1365
+ removePolicyPart(i, part, sight) {
1366
+ const list = (sight ? this.doc.policy?.sees : this.doc.policy?.grants) ?? [];
1367
+ const sameSet = (a, b) => a === b || (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((x) => b.includes(x)));
1368
+ const keys = sight ? ["own", "via"] : ["kinds", "self", "ownEnd", "describe"];
1369
+ const matches = list.filter((one) => sameSet(one.roles, part.roles) &&
1370
+ (sight ? sameSet(one.kinds, part.kinds) : sameSet(one.mutations, part.mutations)) &&
1371
+ 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])));
1372
+ const what = sight ? "sight" : "grant";
1373
+ const say = (one) => (sight ? `${whoWords(one.roles)} ${sightWords(this.doc, one)}` : grantBody(this.doc, one));
1374
+ if (matches.length === 0) {
1375
+ const there = list.map(say);
1376
+ return this.fail(i, "", `no ${what} ${sight ? "shows" : "says"} ${say(part)}${there.length ? `; the ${what}s ${sight ? "show" : "say"} ${there.length > 1 ? `${there.slice(0, -1).join("; ")}; and ${there.at(-1)}` : there[0]}` : `; the app has no ${what}s`}`);
1377
+ }
1378
+ if (matches.length > 1)
1379
+ return this.fail(i, "", `${matches.length} ${what}s ${sight ? "show" : "say"} ${say(part)}`, `say ${sight ? "own or via" : "ownEnd, self, kinds or describe"} as the one to remove says them`);
1380
+ const gone = matches[0];
1381
+ list.splice(list.indexOf(gone), 1);
1382
+ this.said.push(sight ? sightGone(this.doc, gone) : grantGone(this.doc, gone));
1383
+ if (sight && list.length === 0) {
1384
+ delete this.doc.policy.sees;
1385
+ this.said.push(NO_SIGHT_LEFT);
1386
+ }
1387
+ else if (!sight && list.length === 0)
1388
+ this.said.push(NO_GRANT_LEFT);
1389
+ }
1139
1390
  // ── kinds ──
1140
1391
  addKind(i, e) {
1141
1392
  const { op: _op, kind, act, ...rest } = e;
@@ -1172,8 +1423,8 @@ class Editor {
1172
1423
  const to = e.to;
1173
1424
  const touched = this.rewriteAll({ t: "kind", from, to });
1174
1425
  // The acts the framework derives for every kind are named for it, so they move with it.
1175
- this.toolsMoved.add(`edit-${from} is now edit-${to}`);
1176
- this.toolsMoved.add(`remove-${from} is now remove-${to}`);
1426
+ for (const act of DERIVED_ACTS)
1427
+ this.toolsMoved.add(`${act}-${from} is now ${act}-${to}`);
1177
1428
  // The kind itself, in order, continuing its records.
1178
1429
  const kinds = {};
1179
1430
  for (const [k, v] of Object.entries(this.doc.kinds))
@@ -1228,6 +1479,11 @@ class Editor {
1228
1479
  return;
1229
1480
  if (Object.keys(this.doc.kinds).length === 1)
1230
1481
  return this.fail(i, "kind", "an app keeps at least one kind");
1482
+ // The last sight gone would show everyone everything, every other kind included: the policy's to change first, as a relation a sight reads is.
1483
+ const sees = this.doc.policy?.sees ?? [];
1484
+ if (sees.length > 0 && sees.every((sight) => sight.kinds.every((k) => k === kind))) {
1485
+ return this.fail(i, "kind", `every sight shows only ${pluralWords(this.doc, kind)}, so with them gone everyone would see everything`, `remove those sights first, in the same change ({"op": "remove-sight", …}), which says so`);
1486
+ }
1231
1487
  const gone = [];
1232
1488
  /*
1233
1489
  * The relations it declares go with it — and so does what walks them
@@ -1262,8 +1518,8 @@ class Editor {
1262
1518
  for (const [name, act] of Object.entries(this.doc.acts ?? {})) {
1263
1519
  const on = asArray(act.on);
1264
1520
  if (act.creates === kind || (act.effects ?? []).some((x) => x.create === kind) || (on.includes(kind) && on.length === 1)) {
1521
+ gone.push(titled("act", name, act));
1265
1522
  this.removeActQuietly(name);
1266
- gone.push(`act ${name}`);
1267
1523
  }
1268
1524
  else if (on.includes(kind))
1269
1525
  act.on = on.filter((k) => k !== kind);
@@ -1271,7 +1527,7 @@ class Editor {
1271
1527
  for (const [name, rule] of Object.entries(this.doc.rules ?? {})) {
1272
1528
  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" })) {
1273
1529
  delete this.doc.rules[name];
1274
- gone.push(`rule ${name}`);
1530
+ gone.push(titled("rule", name, rule));
1275
1531
  }
1276
1532
  }
1277
1533
  if (this.doc.views?.[kind] && !Array.isArray(this.doc.views[kind])) {
@@ -1279,9 +1535,21 @@ class Editor {
1279
1535
  if (Object.keys(this.doc.views).length === 0)
1280
1536
  delete this.doc.views;
1281
1537
  }
1282
- for (const g of this.doc.policy?.grants ?? [])
1283
- if (Array.isArray(g.kinds))
1284
- g.kinds = g.kinds.filter((k) => k !== kind);
1538
+ /*
1539
+ * A grant no longer names the kind, nor its derived edit and remove; one
1540
+ * left naming no kind or no act gives nothing, and goes, as a grant does
1541
+ * whose last act is removed — kept, it read as a grant on every kind.
1542
+ */
1543
+ if (this.doc.policy) {
1544
+ const derived = derivedActsOf(kind);
1545
+ this.doc.policy.grants = this.doc.policy.grants.flatMap((g) => {
1546
+ const kinds = Array.isArray(g.kinds) ? g.kinds.filter((k) => k !== kind) : g.kinds;
1547
+ const mutations = Array.isArray(g.mutations) ? g.mutations.filter((m) => !derived.includes(m) || this.doc.acts?.[m]) : g.mutations;
1548
+ if ((Array.isArray(g.kinds) && g.kinds.length > 0 && kinds.length === 0) || (Array.isArray(mutations) && mutations.length === 0))
1549
+ return [];
1550
+ return [{ ...g, ...(kinds !== undefined ? { kinds } : {}), mutations }];
1551
+ });
1552
+ }
1285
1553
  if (this.doc.policy?.sees) {
1286
1554
  this.doc.policy.sees = this.doc.policy.sees.map((s) => ({ ...s, kinds: s.kinds.filter((k) => k !== kind) })).filter((s) => s.kinds.length > 0);
1287
1555
  if (this.doc.policy.sees.length === 0)
@@ -1319,7 +1587,7 @@ class Editor {
1319
1587
  for (const f of [...this.fills])
1320
1588
  if (f.kind === kind)
1321
1589
  this.fills.splice(this.fills.indexOf(f), 1);
1322
- this.said.push(`The ${kind} kind is removed, and every ${kind} with it${gone.length ? `; so are ${listOf(gone)}` : ""}.`);
1590
+ this.said.push(`The ${kind} kind is removed, and every ${kind} with it${gone.length ? `; so ${gone.length === 1 ? "is" : "are"} ${listOf(gone)}` : ""}.`);
1323
1591
  }
1324
1592
  // ── fields ──
1325
1593
  addField(i, e) {
@@ -1340,6 +1608,8 @@ class Editor {
1340
1608
  delete f.renamedFrom;
1341
1609
  if (f.type === "enum" && f.default !== undefined && !f.options.includes(f.default))
1342
1610
  return this.fail(i, "default", `"${String(f.default)}" is not one of the options`);
1611
+ if (f.format && !formatFits(f.format, f.type))
1612
+ return this.fail(i, "format", refusedFormat(f.format, f.type));
1343
1613
  spec.fields[field] = f;
1344
1614
  this.fieldOrigin.set(`${kind}.${field}`, undefined);
1345
1615
  let filled = "";
@@ -1359,7 +1629,9 @@ class Editor {
1359
1629
  filled = `, set to ${JSON.stringify(fill)} on the records already there`;
1360
1630
  }
1361
1631
  }
1362
- this.said.push(`${kind} gains a field, ${field} (${f.type}${f.required ? ", required" : ""})${filled}.`);
1632
+ // What it is said as, in words: "(number, money in USD — 10000 reads $10,000)".
1633
+ const said = formatWords(f, this.doc.brand);
1634
+ this.said.push(`${kind} gains a field, ${field} (${f.type}${said ? `, ${said}` : ""}${f.required ? ", required" : ""})${filled}.`);
1363
1635
  }
1364
1636
  renameField(i, kind, field, to) {
1365
1637
  const f = this.field(i, kind, field);
@@ -1421,6 +1693,9 @@ class Editor {
1421
1693
  return;
1422
1694
  if (e.type === "enum" && !e.options && !f.options)
1423
1695
  return this.fail(i, "options", `to make ${e.field} a choice, list its "options"`);
1696
+ // A format goes where it says something (FR-180): money, a percent and a duration on a number, Markdown on a text field.
1697
+ if (e.format && !formatFits(e.format, e.type))
1698
+ return this.fail(i, "format", refusedFormat(e.format, e.type));
1424
1699
  const was = f.type;
1425
1700
  f.type = e.type;
1426
1701
  if (e.type === "enum")
@@ -1433,25 +1708,69 @@ class Editor {
1433
1708
  delete f.of;
1434
1709
  if (e.format)
1435
1710
  f.format = e.format;
1436
- else if (e.type !== "number" && e.type !== "integer")
1711
+ else if (e.type === "text" || !formatFits(f.format ?? "", e.type))
1437
1712
  delete f.format;
1438
1713
  // A range is a number's (FR-114): a field that is no longer one lets it go.
1439
- if (e.type !== "number" && e.type !== "integer")
1714
+ if (e.type !== "number" && e.type !== "integer" && e.type !== "duration")
1440
1715
  for (const key of ["min", "max", "step"])
1441
1716
  delete f[key];
1717
+ // Only a date repeats: a field that is no longer one lets its repeat go.
1718
+ if (e.type !== "date" && e.type !== "datetime")
1719
+ delete f.repeats;
1442
1720
  if (f.default !== undefined && coerce(f.default, was, f) === undefined)
1443
1721
  delete f.default;
1444
1722
  else if (f.default !== undefined)
1445
1723
  f.default = coerce(f.default, was, f);
1446
- this.said.push(`${e.kind}'s ${e.field} changes from ${was} to ${e.type}; values that convert are kept.`);
1724
+ const said = f.format && f.format !== "markdown" ? formatWords(f, this.doc.brand) : undefined;
1725
+ if (was === e.type && e.type === "text")
1726
+ 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
+ else
1728
+ this.said.push(`${e.kind}'s ${e.field} changes from ${was} to ${e.type}${said ? `, said as ${said}` : ""}; values that convert are kept.`);
1729
+ }
1730
+ /**
1731
+ * HOW A FIELD IS SAID, and nothing else about it: a number's `format`
1732
+ * (money, a percent, a duration) and `unit` (a currency like "EUR" for
1733
+ * money, a measure like "kg"), or a text field's `format`, `markdown`
1734
+ * (FR-180) — each set by a value, cleared by `null`, kept when left out.
1735
+ * The values stay as they are; every face says them anew.
1736
+ */
1737
+ setFormat(i, e) {
1738
+ const f = this.field(i, e.kind, e.field);
1739
+ if (!f)
1740
+ return;
1741
+ if (e.format === undefined && e.unit === undefined)
1742
+ 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');
1743
+ if (f.type === "text") {
1744
+ if (e.unit !== undefined && e.unit !== null)
1745
+ return this.fail(i, "unit", refusedFormat("a unit", f.type));
1746
+ if (e.format && e.format !== "markdown")
1747
+ return this.fail(i, "format", refusedFormat(e.format, f.type));
1748
+ if (e.format === "markdown")
1749
+ f.format = "markdown";
1750
+ else if (e.format === null)
1751
+ delete f.format;
1752
+ this.said.push(`${e.kind}'s ${e.field} ${f.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.`);
1753
+ return;
1754
+ }
1755
+ if (!formatFits(typeof e.format === "string" ? e.format : "money", f.type))
1756
+ return this.fail(i, "field", refusedFormat(typeof e.format === "string" ? e.format : "a unit", f.type));
1757
+ const before = formatWords(f, this.doc.brand);
1758
+ for (const key of ["format", "unit"]) {
1759
+ if (e[key] === null)
1760
+ delete f[key];
1761
+ else if (e[key] !== undefined)
1762
+ f[key] = e[key];
1763
+ }
1764
+ const now = formatWords(f, this.doc.brand);
1765
+ 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.`);
1447
1766
  }
1448
1767
  /** A number's range (FR-114): a key given sets it, `null` clears it, one left out stays. */
1449
1768
  setRange(i, e) {
1450
1769
  const f = this.field(i, e.kind, e.field);
1451
1770
  if (!f)
1452
1771
  return;
1453
- if (f.type !== "number" && f.type !== "integer")
1454
- return this.fail(i, "field", `${e.field} is ${f.type === "integer" ? "an" : "a"} ${f.type}, not a number; a range belongs on a number or integer field`);
1772
+ if (f.type !== "number" && f.type !== "integer" && f.type !== "duration")
1773
+ return this.fail(i, "field", `${e.field} is ${f.type === "integer" ? "an" : "a"} ${f.type}, not a number; a range belongs on a number, integer or duration field`);
1455
1774
  const next = { ...f };
1456
1775
  for (const key of ["min", "max", "step"]) {
1457
1776
  if (e[key] === null)
@@ -1679,7 +1998,7 @@ class Editor {
1679
1998
  continue;
1680
1999
  const kept = rule.repairs.filter((r) => r.act !== act);
1681
2000
  if (kept.length !== rule.repairs.length)
1682
- gone.push(`the "${rule.repairs.find((r) => r.act === act)?.label ?? act}" repair of rule ${name}`);
2001
+ gone.push(`the "${rule.repairs.find((r) => r.act === act)?.label ?? act}" repair of ${titled("rule", name, rule)}`);
1683
2002
  if (kept.length > 0)
1684
2003
  rule.repairs = kept;
1685
2004
  else
@@ -1874,7 +2193,7 @@ class Editor {
1874
2193
  rule.over = r.to;
1875
2194
  for (const rep of rule.repairs ?? []) {
1876
2195
  if (r.t === "kind")
1877
- rep.act = rep.act === `edit-${r.from}` ? `edit-${r.to}` : rep.act === `remove-${r.from}` ? `remove-${r.to}` : rep.act;
2196
+ rep.act = renamedAct(rep.act, r.from, r.to);
1878
2197
  const args = renamedArgs.get(rep.act);
1879
2198
  if (args && rep.args)
1880
2199
  for (const [a, b] of args)
@@ -1901,7 +2220,7 @@ class Editor {
1901
2220
  if (Array.isArray(g.kinds))
1902
2221
  g.kinds = g.kinds.map(swap);
1903
2222
  if (Array.isArray(g.mutations))
1904
- g.mutations = g.mutations.map((m) => (m === `edit-${r.from}` ? `edit-${r.to}` : m === `remove-${r.from}` ? `remove-${r.to}` : m));
2223
+ g.mutations = g.mutations.map((m) => renamedAct(m, r.from, r.to));
1905
2224
  }
1906
2225
  for (const s of doc.policy.sees ?? [])
1907
2226
  s.kinds = s.kinds.map(swap);
@@ -2035,14 +2354,14 @@ class Editor {
2035
2354
  }
2036
2355
  }
2037
2356
  if (uses) {
2038
- gone.push(`act ${name}`, ...this.removeActQuietly(name));
2357
+ gone.push(titled("act", name, act), ...this.removeActQuietly(name));
2039
2358
  }
2040
2359
  }
2041
2360
  for (const [name, rule] of Object.entries(doc.rules ?? {})) {
2042
2361
  const ctx = rule.over === "graph" ? NONE : new Set([rule.over]);
2043
2362
  if (exprMentions(doc, rule.when, ctx, r) || exprMentions(doc, rule.require, ctx, r) || templateMentions(doc, rule.says, ctx, r)) {
2044
2363
  delete doc.rules[name];
2045
- gone.push(`rule ${name}`);
2364
+ gone.push(titled("rule", name, rule));
2046
2365
  }
2047
2366
  }
2048
2367
  if (doc.rules && Object.keys(doc.rules).length === 0)
@@ -2242,6 +2561,70 @@ class Editor {
2242
2561
  return said;
2243
2562
  }
2244
2563
  }
2564
+ /** What the people and the policy make together, as `graview check` judges it (`checkPeople`), said over the document. */
2565
+ function peopleProblems(doc, at) {
2566
+ const out = [];
2567
+ const kinds = doc.kinds;
2568
+ const people = peopleKinds(kinds);
2569
+ const policy = doc.policy;
2570
+ const reads = policy?.sees?.some((sight) => sight.via !== undefined) ? "a sight via a relation" : policy?.grants.some((grant) => grant.ownEnd) ? "a grant held to a person's own end of a link" : undefined;
2571
+ if (people.length === 0 && reads)
2572
+ out.push(error("people-unsaid", at, `${reads} reads each person's own record, and no kind is the people who sign in`, '{"op": "set-people", "kind": "<the kind whose records are the people>", "people": true}'));
2573
+ for (const sight of policy?.sees ?? []) {
2574
+ const via = sight.via;
2575
+ if (via === undefined)
2576
+ continue;
2577
+ const from = Object.keys(kinds).filter((kind) => kinds[kind].edges?.[via]);
2578
+ if (from.length === 0) {
2579
+ out.push(error("sight-via-unknown-edge", at, `no kind has a relation ${quoted(via)}, so a sight via it shows nothing`, `name a relation that joins ${listOf(sight.kinds.map((kind) => pluralOf(kinds, kind)))} to the people`));
2580
+ continue;
2581
+ }
2582
+ if (people.length === 0)
2583
+ continue;
2584
+ const reaches = (target) => from.some((kind) => {
2585
+ const to = kinds[kind].edges[via].to;
2586
+ return to === "*" || to.includes(target);
2587
+ });
2588
+ for (const kind of sight.kinds) {
2589
+ if ((from.includes(kind) && people.some(reaches)) || (people.some((one) => from.includes(one)) && reaches(kind)))
2590
+ continue;
2591
+ out.push(error("sight-via-misses-people", at, `${quoted(via)} never joins ${pluralOf(kinds, kind)} to ${pluralOf(kinds, people[0])}, so the sight shows no ${nounOf(doc, kind)} to anyone`, `name a relation between ${nounOf(doc, kind)} and ${nounOf(doc, people[0])}, or say own: true`));
2592
+ }
2593
+ }
2594
+ for (const grant of policy?.grants ?? []) {
2595
+ if (!grant.ownEnd)
2596
+ continue;
2597
+ if (grant.mutations === "*") {
2598
+ out.push(error("grant-own-end-without-link", at, "a grant held to a person's own end of a link names every act, and most link nothing", "name the acts it holds: the ones that connect or sever"));
2599
+ continue;
2600
+ }
2601
+ for (const name of grant.mutations) {
2602
+ const ends = linkEnds(doc.acts?.[name]);
2603
+ if (ends === "far")
2604
+ continue;
2605
+ const title = quoted(doc.acts?.[name]?.title ?? name);
2606
+ out.push(ends === "near"
2607
+ ? error("grant-own-end-without-link", at, `${title} takes no record at the other end of what it links, so a grant held to a person's own end of it is never met`, 'take the record at its far end as an argument, or say "$me" there and drop ownEnd')
2608
+ : error("grant-own-end-without-link", at, `${title} connects and severs nothing, so a grant held to a person's own end of a link has no end to hold`, "hold it with self: true for the subject, or name an act that connects or severs"));
2609
+ }
2610
+ }
2611
+ return out;
2612
+ }
2613
+ /** Whether an act links, and whether it takes the record at the far end of a link as an argument: "far", "near" (only its subject, "$me" or what it made) or "none". */
2614
+ function linkEnds(act) {
2615
+ if (!act)
2616
+ return "none";
2617
+ if (act.connects || act.severs)
2618
+ return "far";
2619
+ const own = new Set(["subject", "me", "new", ...(act.effects ?? []).flatMap((e) => (e.create && e.as ? [e.as] : []))]);
2620
+ const links = (act.effects ?? []).filter((e) => e.connect || e.sever);
2621
+ if (links.length === 0)
2622
+ return "none";
2623
+ return links.some((e) => [e.from, e.to].some((end) => {
2624
+ const ref = refName(end);
2625
+ return ref !== undefined && !own.has(ref);
2626
+ })) ? "far" : "near";
2627
+ }
2245
2628
  /** A `$name` reference or bare argument name in a guard, renamed (for an argument that followed its field). */
2246
2629
  function rewriteBinding(source, from, to) {
2247
2630
  try {
@@ -2263,7 +2646,6 @@ function swapToken(name, from, to) {
2263
2646
  }
2264
2647
  return name;
2265
2648
  }
2266
- const listOf = (xs) => (xs.length <= 1 ? xs.join("") : `${xs.slice(0, -1).join(", ")} and ${xs.at(-1)}`);
2267
2649
  /** Map every block of a view, nested ones included; a block mapped to null is dropped. */
2268
2650
  function mapBlocks(blocks, f) {
2269
2651
  const out = [];
@@ -2284,13 +2666,12 @@ function mapBlocks(blocks, f) {
2284
2666
  return out;
2285
2667
  }
2286
2668
  // ── words for what changed ───────────────────────────────────────────────────
2669
+ /** An act or a rule that goes with something, as a person reads it: 'the act "Draw a match"', by its id only when it has no title. */
2670
+ const titled = (what, name, spec) => `the ${what} "${spec.title ?? name}"`;
2287
2671
  const withA = (noun) => `${/^[aeiou]/i.test(noun) ? "an" : "a"} ${noun}`;
2288
- const nounOf = (doc, kind) => doc.kinds[kind]?.noun ?? words(kind);
2672
+ const nounOf = (doc, kind) => nounWords(doc.kinds, kind);
2289
2673
  /** A kind's plural as a person reads it: "packages", "people and teams". */
2290
- function pluralWords(doc, kind) {
2291
- const plural = doc.kinds[kind]?.plural;
2292
- return plural ? plural.charAt(0).toLowerCase() + plural.slice(1) : `${words(kind)}s`;
2293
- }
2674
+ const pluralWords = (doc, kind) => pluralOf(doc.kinds, kind);
2294
2675
  /** The kinds a lens binds, for saying where it stands. */
2295
2676
  function kindsBound(lens) {
2296
2677
  const bindings = lens["bindings"];
@@ -2545,6 +2926,20 @@ export function editDocument(document, edits, options = {}) {
2545
2926
  });
2546
2927
  if (editor.findings.length > 0)
2547
2928
  return { ok: false, findings: editor.findings };
2929
+ /*
2930
+ * WHAT THE PEOPLE AND THE POLICY MAKE TOGETHER, judged once the change is
2931
+ * whole (FR-170): moving the people from one kind to another is two edits,
2932
+ * and the first alone leaves a sight reading nobody. What the change made
2933
+ * wrong is refused at its last such edit; what was wrong before is not
2934
+ * this change's to answer for.
2935
+ */
2936
+ if (editor.lastPolicyEdit >= 0) {
2937
+ const at = `edits.${editor.lastPolicyEdit}`;
2938
+ const before = new Set(peopleProblems(current, at).map((finding) => finding.message));
2939
+ const made = peopleProblems(editor.doc, at).filter((finding) => !before.has(finding.message));
2940
+ if (made.length > 0)
2941
+ return { ok: false, findings: made };
2942
+ }
2548
2943
  /*
2549
2944
  * THE TOOL INTERFACE MOVED, AND SAYS SO (FR-34). A derived act is named for
2550
2945
  * its kind and asks for its fields by name, so a rename changes what an
@@ -2581,4 +2976,9 @@ export function renameIn(kinds, over, text, change) {
2581
2976
  return rewriteTemplate(doc, text.template, ctx, r);
2582
2977
  return undefined;
2583
2978
  }
2979
+ /** A derived act of a renamed kind, renamed with it: `archive-task` becomes `archive-chore`. Any other act as it was. */
2980
+ function renamedAct(act, from, to) {
2981
+ const derived = derivedActOf(act);
2982
+ return derived && derived.kind === from ? `${derived.act}-${to}` : act;
2983
+ }
2584
2984
  //# sourceMappingURL=edit.js.map