@mistriwork/mistri 1.0.0-rc.12 → 1.0.0-rc.13

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 (221) hide show
  1. package/README.md +4 -3
  2. package/dist/client/assets/LayoutBuilder-MPzkVU2e.js.map +1 -1
  3. package/dist/client/assets/index-D2dHI6hR.js.map +1 -1
  4. package/dist/client/assets/shell-TreN_uLs.js.map +1 -1
  5. package/dist/docs/access-model.md +1 -1
  6. package/dist/docs/build-and-deploy.md +0 -1
  7. package/dist/docs/cli.md +1 -1
  8. package/dist/docs/glossary.md +3 -3
  9. package/dist/docs/query.md +9 -21
  10. package/dist/docs/status.md +3 -3
  11. package/dist/embed/react/chunks/LayoutBuilder-Cbxa_wve.js.map +1 -1
  12. package/dist/embed/react/chunks/app-0UZ1Re1k.js.map +1 -1
  13. package/dist/embed/react/chunks/page-Bx4o1kpQ.js.map +1 -1
  14. package/dist/embed/react/chunks/palette-Bb7Lf6eo.js.map +1 -1
  15. package/dist/embed/react/chunks/shell-DZRACiB9.js.map +1 -1
  16. package/dist/embed/react/chunks/write-BQO4CFc9.js.map +1 -1
  17. package/dist/embed/react/index.js +1 -1
  18. package/dist/server/build/dockerfile.js +1 -1
  19. package/dist/server/cli/usage.js +2 -2
  20. package/dist/ui/index.js +2 -2
  21. package/dist/ui/page.js +2 -2
  22. package/dist/ui/{shared-N4Ej8yRD.js → shared-1PD7TOTJ.js} +5 -5
  23. package/dist/ui/{shared-N4Ej8yRD.js.map → shared-1PD7TOTJ.js.map} +1 -1
  24. package/dist/ui/{shared-D5m86-4o.js → shared-1gMnOu9J.js} +6 -6
  25. package/dist/ui/{shared-D5m86-4o.js.map → shared-1gMnOu9J.js.map} +1 -1
  26. package/dist/ui/{shared-Ctaj8mR2.js → shared-5RQMi4BR.js} +3 -3
  27. package/dist/ui/{shared-Ctaj8mR2.js.map → shared-5RQMi4BR.js.map} +1 -1
  28. package/dist/ui/{shared-DpLeIATZ.js → shared-B-BUF9fx.js} +2 -2
  29. package/dist/ui/{shared-DpLeIATZ.js.map → shared-B-BUF9fx.js.map} +1 -1
  30. package/dist/ui/{shared-DWYnjItP.js → shared-B-MhiPIN.js} +9 -9
  31. package/dist/ui/{shared-DWYnjItP.js.map → shared-B-MhiPIN.js.map} +1 -1
  32. package/dist/ui/{shared-0fNuhSbG.js → shared-B07qIMlE.js} +5 -5
  33. package/dist/ui/{shared-0fNuhSbG.js.map → shared-B07qIMlE.js.map} +1 -1
  34. package/dist/ui/{shared-lYtMadRU.js → shared-B1SAS_J-.js} +3 -3
  35. package/dist/ui/{shared-lYtMadRU.js.map → shared-B1SAS_J-.js.map} +1 -1
  36. package/dist/ui/{shared-CpI0Y8M5.js → shared-B4jgU8h9.js} +6 -6
  37. package/dist/ui/{shared-CpI0Y8M5.js.map → shared-B4jgU8h9.js.map} +1 -1
  38. package/dist/ui/{shared-Cxu1c35l.js → shared-B5aBCUi7.js} +30 -30
  39. package/dist/ui/{shared-Cxu1c35l.js.map → shared-B5aBCUi7.js.map} +1 -1
  40. package/dist/ui/{shared-CAe-US1q.js → shared-B9H7_Pji.js} +5 -5
  41. package/dist/ui/{shared-CAe-US1q.js.map → shared-B9H7_Pji.js.map} +1 -1
  42. package/dist/ui/{shared-Q2bgBaYo.js → shared-BAXr20S-.js} +4 -4
  43. package/dist/ui/{shared-Q2bgBaYo.js.map → shared-BAXr20S-.js.map} +1 -1
  44. package/dist/ui/{shared-CyEefFFb.js → shared-BDDUzH-f.js} +19 -19
  45. package/dist/ui/{shared-CyEefFFb.js.map → shared-BDDUzH-f.js.map} +1 -1
  46. package/dist/ui/{shared-BdhPI6dq.js → shared-BGmkeDeW.js} +3 -3
  47. package/dist/ui/{shared-BdhPI6dq.js.map → shared-BGmkeDeW.js.map} +1 -1
  48. package/dist/ui/{shared-DONIpq2q.js → shared-BHCQ60yN.js} +2 -2
  49. package/dist/ui/{shared-DONIpq2q.js.map → shared-BHCQ60yN.js.map} +1 -1
  50. package/dist/ui/{shared-CNmaAv64.js → shared-BHsjawAG.js} +8 -8
  51. package/dist/ui/{shared-CNmaAv64.js.map → shared-BHsjawAG.js.map} +1 -1
  52. package/dist/ui/{shared-DgjU9kOy.js → shared-BIQuK_nq.js} +4 -4
  53. package/dist/ui/{shared-DgjU9kOy.js.map → shared-BIQuK_nq.js.map} +1 -1
  54. package/dist/ui/{shared-icSQAj21.js → shared-BOe4W2FI.js} +3 -3
  55. package/dist/ui/{shared-icSQAj21.js.map → shared-BOe4W2FI.js.map} +1 -1
  56. package/dist/ui/{shared-BlxoOJSk.js → shared-BPpC08ip.js} +3 -3
  57. package/dist/ui/{shared-BlxoOJSk.js.map → shared-BPpC08ip.js.map} +1 -1
  58. package/dist/ui/{shared-BtiFL37J.js → shared-BdA3LPCB.js} +2 -2
  59. package/dist/ui/{shared-BtiFL37J.js.map → shared-BdA3LPCB.js.map} +1 -1
  60. package/dist/ui/{shared-CvR94vn4.js → shared-Be3nbWsd.js} +11 -11
  61. package/dist/ui/{shared-CvR94vn4.js.map → shared-Be3nbWsd.js.map} +1 -1
  62. package/dist/ui/{shared-Bl_nF_IH.js → shared-Bf30ZLoS.js} +8 -4
  63. package/dist/ui/shared-Bf30ZLoS.js.map +1 -0
  64. package/dist/ui/{shared-DijrJ_V-.js → shared-BguP_BaR.js} +7 -7
  65. package/dist/ui/{shared-DijrJ_V-.js.map → shared-BguP_BaR.js.map} +1 -1
  66. package/dist/ui/{shared-DPAVoWY0.js → shared-BoLbc2WD.js} +17 -2
  67. package/dist/ui/{shared-DPAVoWY0.js.map → shared-BoLbc2WD.js.map} +1 -1
  68. package/dist/ui/{shared-BkdBTYwx.js → shared-BttyOW4W.js} +3 -3
  69. package/dist/ui/{shared-BkdBTYwx.js.map → shared-BttyOW4W.js.map} +1 -1
  70. package/dist/ui/{shared-CQWLz8uT.js → shared-BumxsBlA.js} +10 -10
  71. package/dist/ui/{shared-CQWLz8uT.js.map → shared-BumxsBlA.js.map} +1 -1
  72. package/dist/ui/{shared-Dzu7tFiC.js → shared-Bw73ktBk.js} +3 -3
  73. package/dist/ui/{shared-Dzu7tFiC.js.map → shared-Bw73ktBk.js.map} +1 -1
  74. package/dist/ui/{shared-BhAYErSx.js → shared-C1B0GH0e.js} +4 -4
  75. package/dist/ui/{shared-BhAYErSx.js.map → shared-C1B0GH0e.js.map} +1 -1
  76. package/dist/ui/{shared-BQJQcGgj.js → shared-C2FsyYr7.js} +4 -4
  77. package/dist/ui/{shared-BQJQcGgj.js.map → shared-C2FsyYr7.js.map} +1 -1
  78. package/dist/ui/{shared-B9lw2Ryh.js → shared-C2RU1qjv.js} +4 -4
  79. package/dist/ui/{shared-B9lw2Ryh.js.map → shared-C2RU1qjv.js.map} +1 -1
  80. package/dist/ui/{shared-DQYNI9wO.js → shared-C42f07hJ.js} +5 -5
  81. package/dist/ui/{shared-DQYNI9wO.js.map → shared-C42f07hJ.js.map} +1 -1
  82. package/dist/ui/{shared-CqCZ8wMY.js → shared-C4LkPCU4.js} +3 -3
  83. package/dist/ui/{shared-CqCZ8wMY.js.map → shared-C4LkPCU4.js.map} +1 -1
  84. package/dist/ui/{shared-BhtpgOpa.js → shared-C4wSfz6k.js} +9 -9
  85. package/dist/ui/{shared-BhtpgOpa.js.map → shared-C4wSfz6k.js.map} +1 -1
  86. package/dist/ui/{shared-BHc2Izkj.js → shared-C9DZtSFH.js} +2 -2
  87. package/dist/ui/{shared-BHc2Izkj.js.map → shared-C9DZtSFH.js.map} +1 -1
  88. package/dist/ui/{shared-DW1YuiNS.js → shared-CA52ClGo.js} +2 -2
  89. package/dist/ui/{shared-DW1YuiNS.js.map → shared-CA52ClGo.js.map} +1 -1
  90. package/dist/ui/{shared-BbSbilkh.js → shared-CBciTwm6.js} +9 -9
  91. package/dist/ui/{shared-BbSbilkh.js.map → shared-CBciTwm6.js.map} +1 -1
  92. package/dist/ui/{shared-C-J32eBn.js → shared-CL3RLKDA.js} +16 -16
  93. package/dist/ui/{shared-C-J32eBn.js.map → shared-CL3RLKDA.js.map} +1 -1
  94. package/dist/ui/{shared-Ck8S4OxW.js → shared-CLq3-_hW.js} +3 -3
  95. package/dist/ui/{shared-Ck8S4OxW.js.map → shared-CLq3-_hW.js.map} +1 -1
  96. package/dist/ui/{shared-Q5WefT7G.js → shared-CMjK-Ucd.js} +3 -3
  97. package/dist/ui/{shared-Q5WefT7G.js.map → shared-CMjK-Ucd.js.map} +1 -1
  98. package/dist/ui/{shared-D3MAuEA3.js → shared-CPjpSFGh.js} +2 -2
  99. package/dist/ui/{shared-D3MAuEA3.js.map → shared-CPjpSFGh.js.map} +1 -1
  100. package/dist/ui/{shared-VshXGwXP.js → shared-CSPhENq3.js} +2 -2
  101. package/dist/ui/{shared-VshXGwXP.js.map → shared-CSPhENq3.js.map} +1 -1
  102. package/dist/ui/{shared-DJrJaE73.js → shared-CZJE4Bmp.js} +3 -3
  103. package/dist/ui/{shared-DJrJaE73.js.map → shared-CZJE4Bmp.js.map} +1 -1
  104. package/dist/ui/{shared-D7ONOwI7.js → shared-C_BhUL2I.js} +7 -7
  105. package/dist/ui/{shared-D7ONOwI7.js.map → shared-C_BhUL2I.js.map} +1 -1
  106. package/dist/ui/{shared-j-2peBIB.js → shared-CbeujwdU.js} +4 -4
  107. package/dist/ui/{shared-j-2peBIB.js.map → shared-CbeujwdU.js.map} +1 -1
  108. package/dist/ui/{shared-D5GrOEa2.js → shared-Cbf-o4Dt.js} +4 -4
  109. package/dist/ui/{shared-D5GrOEa2.js.map → shared-Cbf-o4Dt.js.map} +1 -1
  110. package/dist/ui/{shared-HwiPIajr.js → shared-CdNmeNHh.js} +3 -3
  111. package/dist/ui/{shared-HwiPIajr.js.map → shared-CdNmeNHh.js.map} +1 -1
  112. package/dist/ui/{shared-ZCFt-CPb.js → shared-CdQOMD8J.js} +3 -3
  113. package/dist/ui/{shared-ZCFt-CPb.js.map → shared-CdQOMD8J.js.map} +1 -1
  114. package/dist/ui/{shared-ClhXSGGK.js → shared-CdiPYlTh.js} +2 -2
  115. package/dist/ui/{shared-ClhXSGGK.js.map → shared-CdiPYlTh.js.map} +1 -1
  116. package/dist/ui/{shared-DA8_pVRZ.js → shared-CepLAhwm.js} +2 -2
  117. package/dist/ui/{shared-DA8_pVRZ.js.map → shared-CepLAhwm.js.map} +1 -1
  118. package/dist/ui/{shared-BrYKjrnM.js → shared-Ch82h_Fu.js} +6 -6
  119. package/dist/ui/{shared-BrYKjrnM.js.map → shared-Ch82h_Fu.js.map} +1 -1
  120. package/dist/ui/{shared-C4UrE8Fb.js → shared-Chp88all.js} +3 -3
  121. package/dist/ui/{shared-C4UrE8Fb.js.map → shared-Chp88all.js.map} +1 -1
  122. package/dist/ui/{shared-CWa80DTO.js → shared-Ci16s2rN.js} +9 -9
  123. package/dist/ui/{shared-CWa80DTO.js.map → shared-Ci16s2rN.js.map} +1 -1
  124. package/dist/ui/{shared-tksTLD6Q.js → shared-CiqNkoE1.js} +5 -5
  125. package/dist/ui/{shared-tksTLD6Q.js.map → shared-CiqNkoE1.js.map} +1 -1
  126. package/dist/ui/{shared-yt7DjSrq.js → shared-CpMhOq8F.js} +2 -2
  127. package/dist/ui/{shared-yt7DjSrq.js.map → shared-CpMhOq8F.js.map} +1 -1
  128. package/dist/ui/{shared-Dm0z5WUl.js → shared-CrWv5JKH.js} +6 -6
  129. package/dist/ui/{shared-Dm0z5WUl.js.map → shared-CrWv5JKH.js.map} +1 -1
  130. package/dist/ui/{shared-CQL3FGHc.js → shared-Csfi3Pti.js} +2 -2
  131. package/dist/ui/{shared-CQL3FGHc.js.map → shared-Csfi3Pti.js.map} +1 -1
  132. package/dist/ui/{shared-BKNRbm-V.js → shared-CuZt26rm.js} +2 -2
  133. package/dist/ui/{shared-BKNRbm-V.js.map → shared-CuZt26rm.js.map} +1 -1
  134. package/dist/ui/{shared-Guq3upiR.js → shared-Cws_z-TS.js} +2 -2
  135. package/dist/ui/{shared-Guq3upiR.js.map → shared-Cws_z-TS.js.map} +1 -1
  136. package/dist/ui/{shared-Bw_qZeIj.js → shared-D1KWuCg6.js} +2 -2
  137. package/dist/ui/{shared-Bw_qZeIj.js.map → shared-D1KWuCg6.js.map} +1 -1
  138. package/dist/ui/{shared-CuCZKwXW.js → shared-D66ju_Cb.js} +23 -23
  139. package/dist/ui/{shared-CuCZKwXW.js.map → shared-D66ju_Cb.js.map} +1 -1
  140. package/dist/ui/{shared-DYfX7NSM.js → shared-DGaQZXnw.js} +2 -2
  141. package/dist/ui/{shared-DYfX7NSM.js.map → shared-DGaQZXnw.js.map} +1 -1
  142. package/dist/ui/{shared-B3Hp8tOv.js → shared-DHy41sET.js} +6 -6
  143. package/dist/ui/{shared-B3Hp8tOv.js.map → shared-DHy41sET.js.map} +1 -1
  144. package/dist/ui/{shared-CXyd7IKi.js → shared-DKEOMQ6l.js} +2 -2
  145. package/dist/ui/{shared-CXyd7IKi.js.map → shared-DKEOMQ6l.js.map} +1 -1
  146. package/dist/ui/{shared-Ccmc8YO0.js → shared-DSUICuaJ.js} +33 -33
  147. package/dist/ui/{shared-Ccmc8YO0.js.map → shared-DSUICuaJ.js.map} +1 -1
  148. package/dist/ui/{shared-CL2iw_js.js → shared-DUPMRoO0.js} +13 -13
  149. package/dist/ui/{shared-CL2iw_js.js.map → shared-DUPMRoO0.js.map} +1 -1
  150. package/dist/ui/{shared-Dtp4eRul.js → shared-DVfdm6ah.js} +2 -2
  151. package/dist/ui/{shared-Dtp4eRul.js.map → shared-DVfdm6ah.js.map} +1 -1
  152. package/dist/ui/{shared-BJ5Ex32G.js → shared-DXM7d7lJ.js} +3 -3
  153. package/dist/ui/{shared-BJ5Ex32G.js.map → shared-DXM7d7lJ.js.map} +1 -1
  154. package/dist/ui/{shared-BFr6ld0e.js → shared-DXbMlcev.js} +2 -2
  155. package/dist/ui/{shared-BFr6ld0e.js.map → shared-DXbMlcev.js.map} +1 -1
  156. package/dist/ui/{shared-BUp3jv-N.js → shared-D_SkPtJ9.js} +6 -6
  157. package/dist/ui/{shared-BUp3jv-N.js.map → shared-D_SkPtJ9.js.map} +1 -1
  158. package/dist/ui/{shared-CdNomVzH.js → shared-Dd21k2ic.js} +4 -4
  159. package/dist/ui/{shared-CdNomVzH.js.map → shared-Dd21k2ic.js.map} +1 -1
  160. package/dist/ui/{shared-gRMbxmpu.js → shared-Dh0nHtLZ.js} +8 -8
  161. package/dist/ui/{shared-gRMbxmpu.js.map → shared-Dh0nHtLZ.js.map} +1 -1
  162. package/dist/ui/{shared-BKGJgjog.js → shared-DkOdSpSK.js} +3 -3
  163. package/dist/ui/{shared-BKGJgjog.js.map → shared-DkOdSpSK.js.map} +1 -1
  164. package/dist/ui/{shared-BMsh72Uq.js → shared-Dkph5ckT.js} +2 -2
  165. package/dist/ui/{shared-BMsh72Uq.js.map → shared-Dkph5ckT.js.map} +1 -1
  166. package/dist/ui/{shared-Be1HpDOB.js → shared-Dl1_Nx--.js} +3 -3
  167. package/dist/ui/{shared-Be1HpDOB.js.map → shared-Dl1_Nx--.js.map} +1 -1
  168. package/dist/ui/{shared-BQIZWOoJ.js → shared-Dm0IGbAf.js} +2 -2
  169. package/dist/ui/{shared-BQIZWOoJ.js.map → shared-Dm0IGbAf.js.map} +1 -1
  170. package/dist/ui/{shared-DSUtcU5S.js → shared-DnIkTpPr.js} +5 -5
  171. package/dist/ui/{shared-DSUtcU5S.js.map → shared-DnIkTpPr.js.map} +1 -1
  172. package/dist/ui/{shared-DsxGbYlV.js → shared-Docl_0tq.js} +3 -3
  173. package/dist/ui/{shared-DsxGbYlV.js.map → shared-Docl_0tq.js.map} +1 -1
  174. package/dist/ui/{shared-DZskrV26.js → shared-DprrHC72.js} +2 -2
  175. package/dist/ui/{shared-DZskrV26.js.map → shared-DprrHC72.js.map} +1 -1
  176. package/dist/ui/{shared-Bmw43Hwf.js → shared-DyKsJTZs.js} +2 -2
  177. package/dist/ui/{shared-Bmw43Hwf.js.map → shared-DyKsJTZs.js.map} +1 -1
  178. package/dist/ui/{shared-BLG88Pya.js → shared-FO7vfI3F.js} +4 -4
  179. package/dist/ui/{shared-BLG88Pya.js.map → shared-FO7vfI3F.js.map} +1 -1
  180. package/dist/ui/{shared-DLSd80D1.js → shared-HalAVHjv.js} +3 -3
  181. package/dist/ui/{shared-DLSd80D1.js.map → shared-HalAVHjv.js.map} +1 -1
  182. package/dist/ui/{shared-D1mMBLdj.js → shared-JvcI-LWp.js} +15 -15
  183. package/dist/ui/{shared-D1mMBLdj.js.map → shared-JvcI-LWp.js.map} +1 -1
  184. package/dist/ui/{shared-C6Hv_4O0.js → shared-K7Ym27yg.js} +2 -2
  185. package/dist/ui/{shared-C6Hv_4O0.js.map → shared-K7Ym27yg.js.map} +1 -1
  186. package/dist/ui/{shared-B39ln7oi.js → shared-SzlnwfJO.js} +3 -3
  187. package/dist/ui/{shared-B39ln7oi.js.map → shared-SzlnwfJO.js.map} +1 -1
  188. package/dist/ui/{shared-DgPn_Jll.js → shared-TrGbd1FY.js} +3 -3
  189. package/dist/ui/{shared-DgPn_Jll.js.map → shared-TrGbd1FY.js.map} +1 -1
  190. package/dist/ui/{shared-DrFb-XtA.js → shared-WhbuObEW.js} +2 -2
  191. package/dist/ui/{shared-DrFb-XtA.js.map → shared-WhbuObEW.js.map} +1 -1
  192. package/dist/ui/{shared-DiFQljcq.js → shared-XcSKDp6N.js} +11 -11
  193. package/dist/ui/{shared-DiFQljcq.js.map → shared-XcSKDp6N.js.map} +1 -1
  194. package/dist/ui/{shared-CMAg58rY.js → shared-Z4uBJU6I.js} +7 -6
  195. package/dist/ui/shared-Z4uBJU6I.js.map +1 -0
  196. package/dist/ui/{shared-BnUptuUp.js → shared-bAgeiRa5.js} +2 -2
  197. package/dist/ui/{shared-BnUptuUp.js.map → shared-bAgeiRa5.js.map} +1 -1
  198. package/dist/ui/{shared-BxMvSjey.js → shared-cgXG7OgZ.js} +5 -5
  199. package/dist/ui/{shared-BxMvSjey.js.map → shared-cgXG7OgZ.js.map} +1 -1
  200. package/dist/ui/{shared-u4CG2kQJ.js → shared-eZ0SB_zm.js} +2 -2
  201. package/dist/ui/{shared-u4CG2kQJ.js.map → shared-eZ0SB_zm.js.map} +1 -1
  202. package/dist/ui/{shared-L90bWW8e.js → shared-frKFb79C.js} +2 -2
  203. package/dist/ui/{shared-L90bWW8e.js.map → shared-frKFb79C.js.map} +1 -1
  204. package/dist/ui/{shared-Dd34nhkw.js → shared-hAayNSRA.js} +3 -3
  205. package/dist/ui/{shared-Dd34nhkw.js.map → shared-hAayNSRA.js.map} +1 -1
  206. package/dist/ui/{shared-DzC64cpp.js → shared-j_J16p2B.js} +2 -2
  207. package/dist/ui/{shared-DzC64cpp.js.map → shared-j_J16p2B.js.map} +1 -1
  208. package/dist/ui/{shared-DX7HRIV-.js → shared-tqpl5N3g.js} +6 -6
  209. package/dist/ui/{shared-DX7HRIV-.js.map → shared-tqpl5N3g.js.map} +1 -1
  210. package/dist/ui/{shared-BS5dBMpA.js → shared-y-6_ZDts.js} +2 -2
  211. package/dist/ui/{shared-BS5dBMpA.js.map → shared-y-6_ZDts.js.map} +1 -1
  212. package/dist/ui/{shared-Cv5v_MP1.js → shared-zG6e1Rhz.js} +13 -13
  213. package/dist/ui/{shared-Cv5v_MP1.js.map → shared-zG6e1Rhz.js.map} +1 -1
  214. package/dist/ui/testing.js +1 -1
  215. package/package.json +4 -4
  216. package/dist/docs/cli-browser.md +0 -232
  217. package/dist/docs/database-browser.md +0 -115
  218. package/dist/docs/keyboard.md +0 -63
  219. package/dist/docs/mcp.md +0 -86
  220. package/dist/ui/shared-Bl_nF_IH.js.map +0 -1
  221. package/dist/ui/shared-CMAg58rY.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"shared-tksTLD6Q.js","names":[],"sources":["../../src/client/copy/public/publicAddress.ts","../../src/client/publicAddress.ts"],"sourcesContent":["/** What publicAddress says, in the public pages room (packages/kit/src/client/messages.ts says how the catalog is split). */\nimport { defineMessages } from '@mistriwork/kit/client/messages.js';\n\nexport const say = defineMessages({\n notifications: 'Notifications',\n assistant: 'Assistant',\n});\n","/**\n * An App's addresses, as the browser writes and reads them: one canonical\n * address per thing on screen.\n *\n * The grammar and the route a table's records take are shared\n * (shared/appAddress.ts): the server's canonical redirect and `mistri\n * check --routes` read the same book. What lives here is the walk's side —\n * which walk entry an address stands for, and which address the entry on\n * screen is — with the App loaded and its schema in hand. It is one module\n * with the list's words below because the two read each other, and the\n * walk's modules read both.\n *\n * The address is the one thing on screen: `/compass/leads/127`, not the walk\n * that reached it. The walk rides in history state (url.js), so Back and the\n * crumbs are unchanged and a shared link is clean. A dashboard says its\n * window as `?period=`, a section's own window as `?period.<section>=`\n * (`?period.lines-moved-by-day=last-7-days`) and its picks by column; a record\n * its id, natural key or slug.\n *\n * A list the App names is written as its view key with its question as\n * address parameters — `/forecourt/vehicles?body_type=Sedan&price=..10000&\n * sort=-price`, `/compass/leads?tab=working&search=stone` — rather than the\n * walk's query text, which carried the table, the view's standing condition\n * and its show clause into every link.\n *\n * Only the view's own filter columns (and its name column's box, `search=`)\n * are named, each in one of four shapes: `make=Toyota`; `make=Toyota,Honda`\n * for one of several; `price=..10000` for at most, `year=2018..` for at\n * least, both for a range. A tab is `tab=<its label as a slug>`, and no tab\n * is the view's first, the one it opens on; the sort is `sort=-price,year`,\n * and no sort is the tab's own. A question that does not fit — a\n * hand-written condition, an `or` the boxes did not write, a column the view\n * does not filter by, a show of the reader's own — is `q=` its text, whole:\n * never a guess that drops a condition.\n */\nimport { activeApp } from './appState.js';\nimport { findTable, pageView, quote, state, tableView } from '@mistriwork/kit/client/core.js';\nimport { isPublicVisitor } from './publicVisitor.js';\nimport { ownedTails } from '@mistriwork/kit/client/conditions.js';\nimport { splitQuery } from '@mistriwork/kit/client/clauses.js';\nimport { canonicalize, squeeze, stripCondition } from '@mistriwork/kit/client/filterbar.js';\nimport {\n ASSISTANT_PAGE,\n INBOX_PAGE,\n dashPageId,\n dashViewKey,\n formViewKey,\n isListRoom,\n roomPageId,\n viewRoom,\n} from '@mistriwork/kit/client/components/appRooms.js';\nimport {\n litTabFor,\n searchColumn,\n specTabs,\n splitListSpec,\n splitOf,\n tabQuestion,\n visibleQuestion,\n wholeQuery,\n type ListSpec,\n} from '@mistriwork/kit/client/components/appListModel.js';\nimport { andQuery } from '@mistriwork/engine/shared/query.js';\nimport { TAGS_COLUMN } from '@mistriwork/engine/shared/tagLists.js';\nimport { filterAddressWord, hasFilterOf } from '@mistriwork/engine/shared/listFilters.js';\nimport {\n ALL_TIME,\n readPeriod,\n addressFilterValue,\n addressSortValue,\n tabKey,\n tabSlug,\n tabWord,\n} from '@mistriwork/engine/shared/addressParams.js';\nimport type { TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport {\n INBOX_SEGMENT,\n ASSISTANT_ROOM,\n addressSlug,\n appAddressBook,\n readAppSegments,\n recordSegments,\n type AddressBook,\n type RouteTarget,\n} from '../shared/appAddress.js';\nimport { viewOrder } from '../shared/appModel.js';\nimport { say } from './copy/public/publicAddress.js';\n\nconst NUMBER = /^-?\\d+(?:\\.\\d+)?$/;\nconst SORT_FIELD = /^([A-Za-z_][\\w.]*)(?:\\s+(asc|desc))?$/i;\n\n/** A tab's label as an address word: \"Hybrid and electric\" → \"hybrid-and-electric\". */\nexport { tabSlug };\n\nconst unquoted = (value: string) => {\n const text = value.trim();\n return /^([\"']).*\\1$/s.test(text) ? text.slice(1, -1) : text;\n};\n\nconst literal = (value: string) => (NUMBER.test(value) ? value : String(quote(value)));\n\n/** The name column, whose box is the list's search: the list's own rule (appListModel.ts). */\nfunction nameColumn(spec: ListSpec): string | undefined {\n return searchColumn(findTable(spec.table) as TableMeta | undefined);\n}\n\n/** Whether a filter is a whole condition, with no value after it: one of the segments of the rows that point at the record. */\nfunction wholeFilter(name: string): boolean {\n const has = hasFilterOf(name);\n const child = has ? (findTable(has.child) as TableMeta | undefined) : undefined;\n return Boolean(has && child?.segments && Object.hasOwn(child.segments, has.asks));\n}\n\n/** The columns an address may name: the list's filter boxes, the name column's box first. */\nfunction addressColumns(spec: ListSpec): string[] {\n const name = nameColumn(spec);\n return [...new Set([...(name ? [name] : []), ...(spec.filters ?? [])])];\n}\n\ntype Shape = { op: 'eq' | 'in' | 'has' | 'max' | 'min' | 'range' | 'search'; value: string };\n\n/** A box's condition tail in one of the four shapes, or undefined. A rail's\n range is one `between` and says both ends at once. */\nfunction shapeOf(tail: string): Shape | undefined {\n const text = squeeze(tail);\n let m = /^between\\s+(-?\\d+(?:\\.\\d+)?)\\s+and\\s+(-?\\d+(?:\\.\\d+)?)$/i.exec(text);\n if (m) return { op: 'range', value: `${m[1]}..${m[2]}` };\n m = /^=\\s*(.+)$/.exec(text);\n if (m) {\n const value = unquoted(m[1]);\n return value.includes(',') ? undefined : { op: 'eq', value };\n }\n m = /^in\\s*\\((.+)\\)$/i.exec(text);\n if (m) {\n const items = m[1].split(',').map((item) => unquoted(item));\n return items.every((item) => item && !item.includes(',')) ? { op: 'in', value: items.join(',') } : undefined;\n }\n /* A record's tags (tagLists.ts): `?tags=bug,docs` is any of them. */\n m = /^has\\s*(?:\\((.+)\\)|(.+))$/i.exec(text);\n if (m) {\n const items = (m[1] ?? m[2]).split(',').map((item) => unquoted(item));\n return items.every((item) => item && !item.includes(',')) ? { op: 'has', value: items.join(',') } : undefined;\n }\n /* The search box writes `contains` on the name column; any other column's\n `contains` is no box's word and is said whole (`q=`). */\n m = /^contains\\s+(.+)$/i.exec(text);\n if (m) return { op: 'search', value: unquoted(m[1]) };\n m = /^<=\\s*(-?\\d+(?:\\.\\d+)?)$/.exec(text);\n if (m) return { op: 'max', value: m[1] };\n m = /^>=\\s*(-?\\d+(?:\\.\\d+)?)$/.exec(text);\n if (m) return { op: 'min', value: m[1] };\n return undefined;\n}\n\n/** The one value a column's shapes make, or undefined when they do not combine. */\nfunction paramOf(shapes: Shape[]): string | undefined {\n if (shapes.length === 1 && (shapes[0].op === 'eq' || shapes[0].op === 'in' || shapes[0].op === 'has' || shapes[0].op === 'range'))\n return shapes[0].value;\n if (shapes.some((shape) => shape.op === 'has')) return undefined;\n if (shapes.some((shape) => shape.op === 'range')) return undefined;\n const max = shapes.filter((shape) => shape.op === 'max');\n const min = shapes.filter((shape) => shape.op === 'min');\n if (max.length > 1 || min.length > 1 || max.length + min.length !== shapes.length) return undefined;\n return `${min[0]?.value ?? ''}..${max[0]?.value ?? ''}`;\n}\n\n/** A sort clause as the address says it — `sort price desc, year` is\n `-price,year` — '' for none, undefined when it says more than columns\n and their directions. */\nfunction sortParam(clause: string | undefined): string | undefined {\n if (!clause) return '';\n const said = clause\n .replace(/^(?:sort|order\\s+by)\\s+/i, '')\n .split(',')\n .map((field) => {\n const m = SORT_FIELD.exec(squeeze(field));\n return m ? `${m[2]?.toLowerCase() === 'desc' ? '-' : ''}${m[1]}` : undefined;\n });\n return said.some((field) => !field) ? undefined : said.join(',');\n}\n\ntype TabQuestion = { label: string; slug: string; query: string; order: string; sort: string | undefined };\n\n/** Each tab's question as the list shows it, its conditions and its order\n apart: Compass's Working tab is `status = working sort created_on desc`,\n and that sort is the tab's own, not a reader's refinement. */\nfunction tabQuestions(spec: ListSpec): TabQuestion[] {\n return specTabs(spec).map((tab, index) => {\n const parts = splitQuery(`${spec.table} ${tabQuestion(spec, index)}`) as { filter: string; sort?: string };\n return {\n label: tab.label,\n slug: tabKey(tab),\n query: squeeze(parts.filter ?? ''),\n order: parts.sort ?? '',\n sort: sortParam(parts.sort),\n };\n });\n}\n\n/** The list an entry is, when it is one of the App's list views. */\nfunction specOf(view: string | undefined): ListSpec | undefined {\n const app = activeApp();\n const room = app && view ? viewRoom(app, view) : undefined;\n return room && isListRoom(room) && !room.error ? (room as ListSpec) : undefined;\n}\n\n/** The spec whose plumbing a list's query carries: a split's list half is\n asked with its own show and page size once its pick is closed\n (Rooms.tsx), the view's own everywhere else. */\nfunction plumbingOf(spec: ListSpec, query: string): ListSpec {\n if (!splitOf(spec)) return spec;\n const own = splitQuery(`${spec.table} ${visibleQuestion(spec, query)}`) as { show?: string; limit?: string };\n return own.show || own.limit ? splitListSpec(spec) : spec;\n}\n\n/**\n * A list's question as address parameters: '' for the view as it opens, the\n * parameters otherwise, undefined when the entry is not one of the App's list\n * views (the caller writes it as ever).\n */\nexport function listParams(\n entry: { table: string; query: string; view?: string },\n options: { adoptOrder?: boolean } = {},\n): string | undefined {\n const view = specOf(entry.view);\n if (!view || view.table !== entry.table) return undefined;\n const spec = plumbingOf(view, entry.query);\n const refinement = visibleQuestion(spec, entry.query);\n if (canonicalize(refinement) === canonicalize(tabQuestion(spec, 0))) return '';\n const parts = splitQuery(`${spec.table} ${refinement}`) as { filter: string; sort?: string; show?: string; limit?: string };\n const whole = new URLSearchParams({ q: refinement }).toString();\n if (parts.show || parts.limit) return whole;\n const tabs = tabQuestions(spec);\n const names = addressColumns(spec);\n const filter = squeeze(parts.filter ?? '');\n const lit = litTabFor(tabs, filter, names);\n if (lit < 0) return whole;\n const tabQuery = tabs[lit].query;\n const owned = (name: string) => Boolean(tabQuery) && ownedTails(tabQuery, name).length > 0;\n const params = new URLSearchParams();\n if (lit > 0) params.set('tab', tabs[lit].slug);\n let residue = filter;\n const naming = names[0] === nameColumn(spec) ? names[0] : undefined;\n for (const name of names) {\n if (owned(name)) continue;\n const tails = ownedTails(filter, name) as string[];\n if (!tails.length) continue;\n // A whole condition (a segment of the rows that point at the record) is a yes.\n if (wholeFilter(name)) {\n if (tails.length !== 1 || tails[0]) return whole;\n params.set(filterAddressWord(name), 'yes');\n residue = stripCondition(residue, name);\n continue;\n }\n const shapes = tails.map(shapeOf);\n if (shapes.some((shape) => !shape)) return whole;\n if (shapes.some((shape) => shape!.op === 'search')) {\n // `search=` is the name column's box alone, one phrase.\n if (name !== naming || shapes.length !== 1) return whole;\n params.set('search', shapes[0]!.value);\n residue = stripCondition(residue, name);\n continue;\n }\n const value = paramOf(shapes as Shape[]);\n if (value === undefined) return whole;\n params.set(filterAddressWord(name), value);\n residue = stripCondition(residue, name);\n }\n /* What is left once the boxes speak must be the tab's own words exactly:\n anything else is a condition the parameters would drop. */\n if (canonicalize(residue) !== canonicalize(tabQuery)) return whole;\n /* The tab's own order goes unsaid and another is `sort=`. A tab that\n sorts, asked unsorted, has no parameter to say so. */\n /* A question the view did not ask itself — a dashboard's drill — has no\n order of its own to keep: it takes the tab's, and is said in the tab's words. */\n const sort = !parts.sort && options.adoptOrder ? tabs[lit].sort : sortParam(parts.sort);\n if (sort === undefined) return whole;\n if (sort !== tabs[lit].sort) {\n if (!sort) return whole;\n params.set('sort', sort);\n }\n return params.toString();\n}\n\n/** The question an address's parameters ask of a list, as the reader's refinement text. */\nexport function addressQuestion(spec: ListSpec, params: URLSearchParams): string {\n const q = params.get('q');\n if (q !== null) return squeeze(q);\n const tabs = tabQuestions(spec);\n // No tab, or one the view no longer has, is the tab the view opens on.\n const tab = tabs.find((candidate) => candidate.slug === params.get('tab')) ?? tabs[0];\n const conditions: string[] = [];\n const naming = nameColumn(spec);\n const search = params.get('search');\n if (naming && search?.trim()) conditions.push(`${naming} contains ${String(quote(search.trim()))}`);\n for (const name of addressColumns(spec)) {\n const raw = params.get(filterAddressWord(name));\n if (wholeFilter(name)) {\n if (raw === 'yes') conditions.push(name);\n continue;\n }\n const parsed = raw === null ? undefined : addressFilterValue(raw);\n if (!parsed) continue;\n if (parsed.kind === 'range') {\n // Both ends are one `between`, the one condition a rail's range reads back.\n if (parsed.min && parsed.max) conditions.push(`${name} between ${parsed.min} and ${parsed.max}`);\n else if (parsed.min) conditions.push(`${name} >= ${parsed.min}`);\n else conditions.push(`${name} <= ${parsed.max}`);\n continue;\n }\n const values = parsed.values;\n // `tags` is a record's tags only where its list filters by them; it is asked with `has`.\n if (name === TAGS_COLUMN) {\n conditions.push(`${name} has ${values.length > 1 ? `(${values.map(literal).join(', ')})` : literal(values[0])}`);\n continue;\n }\n conditions.push(values.length > 1 ? `${name} in (${values.map(literal).join(', ')})` : `${name} = ${literal(values[0])}`);\n }\n // The tab's words as the list writes them, so the tab lights; the boxes after them.\n const where = conditions.length ? andQuery(tab.query, ...conditions) : tab.query;\n const sort = params.get('sort');\n const parsedSort = sort ? addressSortValue(sort) : undefined;\n const order = parsedSort\n ? `sort ${parsedSort.map(({ field, descending }) => (descending ? `${field} desc` : field)).join(', ')}`\n : tab.order;\n return squeeze(`${where} ${order}`);\n}\n\n/** A named room uses the same entry as navigation; only lists read filter parameters. */\nexport function viewEntryFromAddress(\n key: string,\n search: string,\n): ReturnType<typeof pageView> | ({ view: string } & ReturnType<typeof tableView>) | null {\n const app = activeApp();\n const spec = app ? viewRoom(app, key) : undefined;\n if (!spec || spec.error) return null;\n if (!isListRoom(spec)) return pageView(roomPageId(spec)!, undefined, undefined, spec.label);\n return { ...tableView(spec.table, wholeQuery(spec, addressQuestion(spec, new URLSearchParams(search)))), view: key };\n}\n\n/* ---------- canonical addresses ---------- */\n\ntype Entry = {\n kind: 'table' | 'breakdown' | 'row' | 'page';\n table?: string;\n query?: string;\n tab?: string;\n view?: string;\n key?: Record<string, unknown>;\n label?: string;\n pageId?: string;\n rootBody?: string;\n /** A natural key's value, when the route has one and it is known (url.js fills it). */\n address?: string;\n};\n\nlet held: { app: object; schema: object; book: AddressBook } | undefined;\n\n/** The address book of the App on screen, or undefined outside one. Built once per App payload and schema. */\nfunction addressBook(): AddressBook | undefined {\n const app = activeApp();\n const schema = state.schema as { tables: AddressBook extends { tables: Map<string, infer T> } ? T[] : never } | null;\n if (!app || !schema) return undefined;\n if (held?.app !== app || held.schema !== schema) {\n held = {\n app,\n schema,\n book: appAddressBook({\n views: app.views,\n order: viewOrder(app),\n resources: app.resources,\n pages: app.pages ?? [],\n tables: schema.tables,\n inbox: app.inbox === true,\n assistant: app.assistant?.page === true,\n projected: true,\n }),\n };\n }\n return held.book;\n}\n\n/* ---------- dashboards ---------- */\n\n/** A window phrase as an address says it: \"last 30 days\" → \"last-30-days\" (dashboardModel's windowSlug). */\nconst windowSlug = (phrase: string) => phrase.replace(/\\s+/g, '-');\n\n/** A dashboard's slot — `last-30-days;owner_id=12` — as its parameters. */\nfunction dashboardParams(body: string | undefined): URLSearchParams {\n const params = new URLSearchParams();\n for (const part of String(body ?? '').split(';')) {\n const eq = part.indexOf('=');\n if (eq > 0) params.set(part.slice(0, eq), part.slice(eq + 1));\n else if (part) params.set('period', part);\n }\n return params;\n}\n\n/** The dashboard behind a view key as the walk holds it, its window and picks read from the parameters. */\nexport function dashboardEntry(key: string, search: string, home: boolean): Entry | undefined {\n const app = activeApp();\n const room = app ? viewRoom(app, key) : undefined;\n if (!room || !('sections' in room)) return undefined;\n const params = new URLSearchParams(search);\n /* A dashboard's picks by its filters' columns; a free-form page's place by the parameters it declares. */\n const picks = [...(room.filters ?? []), ...((room as { page?: { params: string[] } }).page?.params ?? [])].flatMap((column) =>\n params.has(column) ? [`${column}=${params.get(column)}`] : [],\n );\n /* Said `all-time`, or `all` as a person guesses it, once written. */\n const period = readPeriod(params.get('period') ?? '');\n /* All time is said only where the dashboard opens on a window of its own (`window:`, shared/addressParams.ts ALL_TIME). */\n const windowed =\n period && (room.windows?.some((phrase) => windowSlug(phrase) === period) || (room.window && period === ALL_TIME)) ? period : '';\n /* A section with windows of its own says its period as `period.<its key>`; one it does not offer is not read. */\n const periods = [...room.sections, ...(room.aside ?? [])].flatMap((section) => {\n const said = section.windowKey ? params.get(`period.${section.windowKey}`) : null;\n return said && section.windows?.some((phrase) => windowSlug(phrase) === said) ? [`period.${section.windowKey}=${said}`] : [];\n });\n const named = [...picks, ...periods];\n const body = [windowed, ...named].filter((part, i) => part || (i === 0 && named.length)).join(';');\n return {\n kind: 'page',\n pageId: dashPageId(key),\n table: undefined,\n label: room.label,\n rootBody: body || undefined,\n ...(home ? { tab: 'home' } : {}),\n };\n}\n\n/* ---------- writing ---------- */\n\n/** The key a page entry arrived with and has not resolved yet: `id` or `a=1,b=2`. */\nfunction keyOfBody(body: string | undefined, table: string, book: AddressBook): Record<string, unknown> | undefined {\n if (body === undefined || body === '') return undefined;\n if (body.includes('='))\n return Object.fromEntries(\n body.split(',').map((pair) => {\n const [column, ...rest] = pair.split('=');\n return [column, rest.join('=')];\n }),\n );\n const pk =\n book.tables\n .get(table)\n ?.columns.filter((column) => column.primaryKey)\n .map((column) => column.name) ?? [];\n return pk.length === 1 ? { [pk[0]]: body } : undefined;\n}\n\n/** A label that is a record's name, not the placeholder a link arrives with (its key). */\nfunction knownName(entry: Entry): string | undefined {\n const label = entry.label?.trim();\n if (!label || !entry.key) return undefined;\n return Object.values(entry.key).some((value) => String(value) === label) ? undefined : label;\n}\n\n/** The list room a view key names, when it is one. */\nfunction listRoom(view: string | undefined): (ListSpec & { id: string }) | undefined {\n const app = activeApp();\n const room = app && view ? viewRoom(app, view) : undefined;\n return room && isListRoom(room) && !room.error ? room : undefined;\n}\n\ninterface Said {\n segments: string[];\n search?: string;\n}\n\n/**\n * The address one walk entry stands for, after the App's id, or undefined\n * when it has none in words — a breakdown, a Workbench tab, a list no view\n * shows — and the caller writes the walk's own form for it. A tenant's\n * address is the same, its selector after these (tenantLink.ts).\n */\nfunction sayEntry(book: AddressBook, entry: Entry): Said | undefined {\n const app = activeApp();\n if (entry.kind === 'table') {\n if (entry.tab && entry.tab !== 'rows' && entry.tab !== 'new') return undefined;\n const tail = entry.tab === 'new' ? ['new'] : [];\n /* A list the App's own view opened, or — a dashboard's drill, a link\n that named only the table — the view its table's records live under:\n the drill becomes that view's filter parameters. */\n const route = book.records.get(entry.table ?? '');\n const own = listRoom(entry.view)?.table === entry.table;\n const view = own ? entry.view : route?.target.kind === 'list' ? route.target.view : undefined;\n if (view && book.views.has(view)) {\n const params = listParams({ table: entry.table!, query: entry.query ?? entry.table!, view }, { adoptOrder: !own });\n if (params === undefined) return undefined;\n return { segments: [encodeURIComponent(book.views.get(view)!), ...tail], search: params };\n }\n /* A table no view shows is its own name, as it opens: the columns the\n grid chose to show are its default, not a question. */\n const asked = (entry.query ?? entry.table ?? '').trim().replace(/\\s+show\\s+[^]*$/i, '');\n if (route?.target.kind === 'table' && asked === entry.table) {\n return { segments: [encodeURIComponent(route.segment), ...tail] };\n }\n return undefined;\n }\n if (entry.kind === 'row') {\n const split = listRoom(entry.view);\n const via = split && split.table === entry.table && splitOf(split) ? book.views.get(entry.view!) : undefined;\n const segments = recordSegments(book, entry.table!, {\n key: entry.key ?? {},\n natural: entry.address,\n via,\n slug: isPublicVisitor() ? addressSlug(knownName(entry)) : undefined,\n tab: entry.tab ? tabWord(entry.tab) : undefined,\n });\n return segments && { segments };\n }\n if (entry.kind === 'page') {\n if (entry.pageId === INBOX_PAGE) return book.routes.get(INBOX_SEGMENT)?.kind === 'inbox' ? { segments: [INBOX_SEGMENT] } : undefined;\n // The assistant's page, and one conversation on it (`tab` holds its id).\n if (entry.pageId === ASSISTANT_PAGE)\n return book.routes.get(ASSISTANT_ROOM)?.kind === 'assistant'\n ? { segments: [ASSISTANT_ROOM, ...(entry.tab ? [encodeURIComponent(entry.tab)] : [])] }\n : undefined;\n const dash = dashViewKey(entry.pageId);\n if (dash !== undefined && book.views.has(dash)) {\n const params = dashboardParams(entry.rootBody).toString();\n if (entry.tab === 'home' && app?.home === dash) return { segments: [], search: params };\n return { segments: [encodeURIComponent(book.views.get(dash)!)], search: params };\n }\n const form = formViewKey(entry.pageId);\n if (form !== undefined) return book.views.has(form) ? { segments: [encodeURIComponent(book.views.get(form)!)] } : undefined;\n const pageId = entry.pageId ?? '';\n /* A walk read back from its own form names a suggested page by its\n table (`@sales_rep~7`, pageToken), before the page has said its base. */\n const table =\n entry.table ??\n book.pageBase.get(pageId) ??\n (pageId.startsWith('suggested:') ? pageId.slice('suggested:'.length) : book.tables.has(pageId) ? pageId : undefined);\n if (!table) return undefined;\n const key = entry.key ?? keyOfBody(entry.rootBody, table, book);\n /* A second page over its base is said under its own address; the one\n the records open through — or one this browser built — under theirs. */\n const own = book.pages.get(pageId);\n const via = own && book.defaultPage.get(table) !== pageId ? own : undefined;\n if (!key) return own ? { segments: [encodeURIComponent(own)] } : undefined;\n const segments = recordSegments(book, table, {\n key,\n natural: entry.address,\n via,\n slug: isPublicVisitor() ? addressSlug(knownName({ ...entry, key })) : undefined,\n tab: entry.tab ? tabWord(entry.tab) : undefined,\n });\n return segments && { segments };\n }\n return undefined;\n}\n\n/**\n * The canonical address of what is on screen — the walk's last entry — or\n * undefined when it has none in words and the caller writes the walk's own\n * form for it (still no `@` for a list, and every such address still opens).\n */\nexport function appAddress(connection: string, stack: readonly Entry[]): string | undefined {\n const book = addressBook();\n if (!book) return undefined;\n const root = `/${encodeURIComponent(connection)}`;\n const last = stack[stack.length - 1];\n if (!last) return root;\n const said = sayEntry(book, last);\n if (!said) return undefined;\n const path = said.segments.length ? `${root}/${said.segments.join('/')}` : root;\n return said.search ? `${path}?${said.search}` : path;\n}\n\n/** A view's own address, `/compass/leads`, as the App on screen says it; undefined for a key it does not open as a room. */\nexport function viewPath(view: string): string | undefined {\n const book = addressBook(),\n app = activeApp();\n const segment = book?.views.get(view);\n return app && segment ? `/${encodeURIComponent(app.id)}/${encodeURIComponent(segment)}` : undefined;\n}\n\n/** Whether a walk entry is a record whose natural key is not known yet. */\nexport function needsNaturalKey(entry: Entry | undefined): { table: string; column: string } | undefined {\n const book = addressBook();\n if (!book || !entry || entry.address !== undefined || (entry.kind !== 'row' && entry.kind !== 'page') || !entry.key) return undefined;\n const table = entry.table ?? book.pageBase.get(entry.pageId ?? '');\n const column = table ? book.records.get(table)?.column : undefined;\n return table && column ? { table, column } : undefined;\n}\n\n/* ---------- reading ---------- */\n\n/** The notifications room as the walk holds it. */\nexport const inboxEntry = (): Entry => ({ kind: 'page', pageId: INBOX_PAGE, table: undefined, label: say('notifications') });\n\n/** The assistant's page as the walk holds it, on one conversation when `conversation` names it. */\nexport const assistantEntry = (conversation?: string): Entry => ({\n kind: 'page',\n pageId: ASSISTANT_PAGE,\n table: undefined,\n label: say('assistant'),\n ...(conversation ? { tab: conversation } : {}),\n});\n\nconst labelFor = (text: string) => (text.length > 24 ? `${text.slice(0, 23)}…` : text);\n\n/** The list a record route stands under, as it opens: the crumb back to it. */\nfunction listEntryFor(target: RouteTarget): Entry[] {\n if (target.kind !== 'list') return [];\n const entry = viewEntryFromAddress(target.view, '');\n return entry ? [entry as Entry] : [];\n}\n\n/**\n * An App address read into a walk: `/compass/leads/127` is the leads list\n * as it opens and lead 127 beside or after it. Null for anything that is not\n * an App address — a query-language path, the walk's `@`/`~` forms — which\n * the caller reads the older way and then rewrites to this one.\n */\nexport function appStackFrom(hash: string): { connection: string; stack: Entry[]; slugged?: boolean } | null {\n const book = addressBook();\n const app = activeApp();\n if (!book || !app) return null;\n const [raw, search = ''] = String(hash ?? '')\n .replace(/^\\//, '')\n .split(/\\?(.*)/s);\n const segments = raw.split('/').filter(Boolean);\n if (!segments.length) return null;\n let connection: string;\n try {\n connection = decodeURIComponent(segments[0]);\n } catch {\n return null;\n }\n const rest = segments.slice(1);\n if (!rest.length) {\n // The App's root: its landing, or its home dashboard with a window.\n const home = search && app.home ? dashboardEntry(app.home, search, true) : undefined;\n return { connection, stack: home ? [home] : [] };\n }\n const parsed = readAppSegments(book, rest);\n if (!parsed) return null;\n const target = parsed.target;\n if (parsed.kind === 'route') {\n if (target.kind === 'dashboard') {\n const entry = dashboardEntry(target.view, search, false);\n return entry ? { connection, stack: [entry] } : null;\n }\n if (target.kind === 'list' || target.kind === 'form') {\n const entry = viewEntryFromAddress(target.view, target.kind === 'list' ? search : '');\n return entry ? { connection, stack: [entry as Entry] } : null;\n }\n if (target.kind === 'page') return { connection, stack: [{ kind: 'page', pageId: target.page, table: target.table }] };\n if (target.kind === 'inbox') return { connection, stack: [inboxEntry()] };\n if (target.kind === 'assistant') return { connection, stack: [assistantEntry()] };\n return { connection, stack: [tableView(target.table) as Entry] };\n }\n if (parsed.kind === 'conversation') return { connection, stack: [assistantEntry(parsed.id)] };\n if (parsed.kind === 'new') {\n if (target.kind === 'list') {\n const entry = viewEntryFromAddress(target.view, search);\n return entry ? { connection, stack: [{ ...(entry as Entry), tab: 'new' }] } : null;\n }\n return { connection, stack: [tableView(parsed.target.table, parsed.target.table, 'new') as Entry] };\n }\n const shown = Object.values(parsed.key).join(' · ');\n const row: Entry = {\n kind: 'row',\n table: parsed.table,\n key: parsed.key,\n label: labelFor(shown),\n ...(parsed.tab ? { tab: parsed.tab } : {}),\n ...(parsed.natural ? { address: parsed.natural.value } : {}),\n };\n if (target.kind === 'list') {\n const room = listRoom(target.view);\n if (!room || room.table !== parsed.table) return { connection, stack: [row], slugged: parsed.slugged };\n return { connection, stack: [...listEntryFor(target), { ...row, view: target.view }], slugged: parsed.slugged };\n }\n if (target.kind === 'page' && !parsed.natural) {\n return {\n connection,\n stack: [\n {\n kind: 'page',\n pageId: target.page,\n table: parsed.table,\n key: parsed.key,\n label: row.label,\n ...(parsed.tab ? { tab: parsed.tab } : {}),\n },\n ],\n slugged: parsed.slugged,\n };\n }\n return { connection, stack: [row], slugged: parsed.slugged };\n}\n"],"mappings":";;;;;;;;;;;AAGA,IAAa,MAAM,eAAe;CAChC,eAAe;CACf,WAAW;AACb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC8VD,IAAI;;AAGJ,SAAS,cAAuC;CAC9C,MAAM,MAAM,UAAU;CACtB,MAAM,SAAS,MAAM;CACrB,IAAI,CAAC,OAAO,CAAC,QAAQ,OAAO,KAAA;CAC5B,IAAI,MAAM,QAAQ,OAAO,KAAK,WAAW,QACvC,OAAO;EACL;EACA;EACA,MAAM,eAAe;GACnB,OAAO,IAAI;GACX,OAAO,UAAU,GAAG;GACpB,WAAW,IAAI;GACf,OAAO,IAAI,SAAS,CAAC;GACrB,QAAQ,OAAO;GACf,OAAO,IAAI,UAAU;GACrB,WAAW,IAAI,WAAW,SAAS;GACnC,WAAW;EACb,CAAC;CACH;CAEF,OAAO,KAAK;AACd;;AA8LA,SAAgB,SAAS,MAAkC;CACzD,MAAM,OAAO,YAAY,GACvB,MAAM,UAAU;CAClB,MAAM,UAAU,MAAM,MAAM,IAAI,IAAI;CACpC,OAAO,OAAO,UAAU,IAAI,mBAAmB,IAAI,EAAE,EAAE,GAAG,mBAAmB,OAAO,MAAM,KAAA;AAC5F;;AAiBA,IAAa,kBAAkB,kBAAkC;CAC/D,MAAM;CACN,QAAQ;CACR,OAAO,KAAA;CACP,OAAO,IAAI,WAAW;CACtB,GAAI,eAAe,EAAE,KAAK,aAAa,IAAI,CAAC;AAC9C"}
1
+ {"version":3,"file":"shared-CiqNkoE1.js","names":[],"sources":["../../src/client/copy/public/publicAddress.ts","../../src/client/publicAddress.ts"],"sourcesContent":["/** What publicAddress says, in the public pages room (packages/kit/src/client/messages.ts says how the catalog is split). */\nimport { defineMessages } from '@mistriwork/kit/client/messages.js';\n\nexport const say = defineMessages({\n notifications: 'Notifications',\n assistant: 'Assistant',\n});\n","/**\n * An App's addresses, as the browser writes and reads them: one canonical\n * address per thing on screen.\n *\n * The grammar and the route a table's records take are shared\n * (shared/appAddress.ts): the server's canonical redirect and `mistri\n * check --routes` read the same book. What lives here is the walk's side —\n * which walk entry an address stands for, and which address the entry on\n * screen is — with the App loaded and its schema in hand. It is one module\n * with the list's words below because the two read each other, and the\n * walk's modules read both.\n *\n * The address is the one thing on screen: `/compass/leads/127`, not the walk\n * that reached it. The walk rides in history state (url.js), so Back and the\n * crumbs are unchanged and a shared link is clean. A dashboard says its\n * window as `?period=`, a section's own window as `?period.<section>=`\n * (`?period.lines-moved-by-day=last-7-days`) and its picks by column; a record\n * its id, natural key or slug.\n *\n * A list the App names is written as its view key with its question as\n * address parameters — `/forecourt/vehicles?body_type=Sedan&price=..10000&\n * sort=-price`, `/compass/leads?tab=working&search=stone` — rather than the\n * walk's query text, which carried the table, the view's standing condition\n * and its show clause into every link.\n *\n * Only the view's own filter columns (and its name column's box, `search=`)\n * are named, each in one of four shapes: `make=Toyota`; `make=Toyota,Honda`\n * for one of several; `price=..10000` for at most, `year=2018..` for at\n * least, both for a range. A tab is `tab=<its label as a slug>`, and no tab\n * is the view's first, the one it opens on; the sort is `sort=-price,year`,\n * and no sort is the tab's own. A question that does not fit — a\n * hand-written condition, an `or` the boxes did not write, a column the view\n * does not filter by, a show of the reader's own — is `q=` its text, whole:\n * never a guess that drops a condition.\n */\nimport { activeApp } from './appState.js';\nimport { findTable, pageView, quote, state, tableView } from '@mistriwork/kit/client/core.js';\nimport { isPublicVisitor } from './publicVisitor.js';\nimport { ownedTails } from '@mistriwork/kit/client/conditions.js';\nimport { splitQuery } from '@mistriwork/kit/client/clauses.js';\nimport { canonicalize, squeeze, stripCondition } from '@mistriwork/kit/client/filterbar.js';\nimport {\n ASSISTANT_PAGE,\n INBOX_PAGE,\n dashPageId,\n dashViewKey,\n formViewKey,\n isListRoom,\n roomPageId,\n viewRoom,\n} from '@mistriwork/kit/client/components/appRooms.js';\nimport {\n litTabFor,\n searchColumn,\n specTabs,\n splitListSpec,\n splitOf,\n tabQuestion,\n visibleQuestion,\n wholeQuery,\n type ListSpec,\n} from '@mistriwork/kit/client/components/appListModel.js';\nimport { andQuery } from '@mistriwork/engine/shared/query.js';\nimport { TAGS_COLUMN } from '@mistriwork/engine/shared/tagLists.js';\nimport { filterAddressWord, hasFilterOf } from '@mistriwork/engine/shared/listFilters.js';\nimport {\n ALL_TIME,\n readPeriod,\n addressFilterValue,\n addressSortValue,\n tabKey,\n tabSlug,\n tabWord,\n} from '@mistriwork/engine/shared/addressParams.js';\nimport type { TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport {\n INBOX_SEGMENT,\n ASSISTANT_ROOM,\n addressSlug,\n appAddressBook,\n readAppSegments,\n recordSegments,\n type AddressBook,\n type RouteTarget,\n} from '../shared/appAddress.js';\nimport { viewOrder } from '../shared/appModel.js';\nimport { say } from './copy/public/publicAddress.js';\n\nconst NUMBER = /^-?\\d+(?:\\.\\d+)?$/;\nconst SORT_FIELD = /^([A-Za-z_][\\w.]*)(?:\\s+(asc|desc))?$/i;\n\n/** A tab's label as an address word: \"Hybrid and electric\" → \"hybrid-and-electric\". */\nexport { tabSlug };\n\nconst unquoted = (value: string) => {\n const text = value.trim();\n return /^([\"']).*\\1$/s.test(text) ? text.slice(1, -1) : text;\n};\n\nconst literal = (value: string) => (NUMBER.test(value) ? value : String(quote(value)));\n\n/** The name column, whose box is the list's search: the list's own rule (appListModel.ts). */\nfunction nameColumn(spec: ListSpec): string | undefined {\n return searchColumn(findTable(spec.table) as TableMeta | undefined);\n}\n\n/** Whether a filter is a whole condition, with no value after it: one of the segments of the rows that point at the record. */\nfunction wholeFilter(name: string): boolean {\n const has = hasFilterOf(name);\n const child = has ? (findTable(has.child) as TableMeta | undefined) : undefined;\n return Boolean(has && child?.segments && Object.hasOwn(child.segments, has.asks));\n}\n\n/** The columns an address may name: the list's filter boxes, the name column's box first. */\nfunction addressColumns(spec: ListSpec): string[] {\n const name = nameColumn(spec);\n return [...new Set([...(name ? [name] : []), ...(spec.filters ?? [])])];\n}\n\ntype Shape = { op: 'eq' | 'in' | 'has' | 'max' | 'min' | 'range' | 'search'; value: string };\n\n/** A box's condition tail in one of the four shapes, or undefined. A rail's\n range is one `between` and says both ends at once. */\nfunction shapeOf(tail: string): Shape | undefined {\n const text = squeeze(tail);\n let m = /^between\\s+(-?\\d+(?:\\.\\d+)?)\\s+and\\s+(-?\\d+(?:\\.\\d+)?)$/i.exec(text);\n if (m) return { op: 'range', value: `${m[1]}..${m[2]}` };\n m = /^=\\s*(.+)$/.exec(text);\n if (m) {\n const value = unquoted(m[1]);\n return value.includes(',') ? undefined : { op: 'eq', value };\n }\n m = /^in\\s*\\((.+)\\)$/i.exec(text);\n if (m) {\n const items = m[1].split(',').map((item) => unquoted(item));\n return items.every((item) => item && !item.includes(',')) ? { op: 'in', value: items.join(',') } : undefined;\n }\n /* A record's tags (tagLists.ts): `?tags=bug,docs` is any of them. */\n m = /^has\\s*(?:\\((.+)\\)|(.+))$/i.exec(text);\n if (m) {\n const items = (m[1] ?? m[2]).split(',').map((item) => unquoted(item));\n return items.every((item) => item && !item.includes(',')) ? { op: 'has', value: items.join(',') } : undefined;\n }\n /* The search box writes `contains` on the name column; any other column's\n `contains` is no box's word and is said whole (`q=`). */\n m = /^contains\\s+(.+)$/i.exec(text);\n if (m) return { op: 'search', value: unquoted(m[1]) };\n m = /^<=\\s*(-?\\d+(?:\\.\\d+)?)$/.exec(text);\n if (m) return { op: 'max', value: m[1] };\n m = /^>=\\s*(-?\\d+(?:\\.\\d+)?)$/.exec(text);\n if (m) return { op: 'min', value: m[1] };\n return undefined;\n}\n\n/** The one value a column's shapes make, or undefined when they do not combine. */\nfunction paramOf(shapes: Shape[]): string | undefined {\n if (shapes.length === 1 && (shapes[0].op === 'eq' || shapes[0].op === 'in' || shapes[0].op === 'has' || shapes[0].op === 'range'))\n return shapes[0].value;\n if (shapes.some((shape) => shape.op === 'has')) return undefined;\n if (shapes.some((shape) => shape.op === 'range')) return undefined;\n const max = shapes.filter((shape) => shape.op === 'max');\n const min = shapes.filter((shape) => shape.op === 'min');\n if (max.length > 1 || min.length > 1 || max.length + min.length !== shapes.length) return undefined;\n return `${min[0]?.value ?? ''}..${max[0]?.value ?? ''}`;\n}\n\n/** A sort clause as the address says it — `sort price desc, year` is\n `-price,year` — '' for none, undefined when it says more than columns\n and their directions. */\nfunction sortParam(clause: string | undefined): string | undefined {\n if (!clause) return '';\n const said = clause\n .replace(/^(?:sort|order\\s+by)\\s+/i, '')\n .split(',')\n .map((field) => {\n const m = SORT_FIELD.exec(squeeze(field));\n return m ? `${m[2]?.toLowerCase() === 'desc' ? '-' : ''}${m[1]}` : undefined;\n });\n return said.some((field) => !field) ? undefined : said.join(',');\n}\n\ntype TabQuestion = { label: string; slug: string; query: string; order: string; sort: string | undefined };\n\n/** Each tab's question as the list shows it, its conditions and its order\n apart: Compass's Working tab is `status = working sort created_on desc`,\n and that sort is the tab's own, not a reader's refinement. */\nfunction tabQuestions(spec: ListSpec): TabQuestion[] {\n return specTabs(spec).map((tab, index) => {\n const parts = splitQuery(`${spec.table} ${tabQuestion(spec, index)}`) as { filter: string; sort?: string };\n return {\n label: tab.label,\n slug: tabKey(tab),\n query: squeeze(parts.filter ?? ''),\n order: parts.sort ?? '',\n sort: sortParam(parts.sort),\n };\n });\n}\n\n/** The list an entry is, when it is one of the App's list views. */\nfunction specOf(view: string | undefined): ListSpec | undefined {\n const app = activeApp();\n const room = app && view ? viewRoom(app, view) : undefined;\n return room && isListRoom(room) && !room.error ? (room as ListSpec) : undefined;\n}\n\n/** The spec whose plumbing a list's query carries: a split's list half is\n asked with its own show and page size once its pick is closed\n (Rooms.tsx), the view's own everywhere else. */\nfunction plumbingOf(spec: ListSpec, query: string): ListSpec {\n if (!splitOf(spec)) return spec;\n const own = splitQuery(`${spec.table} ${visibleQuestion(spec, query)}`) as { show?: string; limit?: string };\n return own.show || own.limit ? splitListSpec(spec) : spec;\n}\n\n/**\n * A list's question as address parameters: '' for the view as it opens, the\n * parameters otherwise, undefined when the entry is not one of the App's list\n * views (the caller writes it as ever).\n */\nexport function listParams(\n entry: { table: string; query: string; view?: string },\n options: { adoptOrder?: boolean } = {},\n): string | undefined {\n const view = specOf(entry.view);\n if (!view || view.table !== entry.table) return undefined;\n const spec = plumbingOf(view, entry.query);\n const refinement = visibleQuestion(spec, entry.query);\n if (canonicalize(refinement) === canonicalize(tabQuestion(spec, 0))) return '';\n const parts = splitQuery(`${spec.table} ${refinement}`) as { filter: string; sort?: string; show?: string; limit?: string };\n const whole = new URLSearchParams({ q: refinement }).toString();\n if (parts.show || parts.limit) return whole;\n const tabs = tabQuestions(spec);\n const names = addressColumns(spec);\n const filter = squeeze(parts.filter ?? '');\n const lit = litTabFor(tabs, filter, names);\n if (lit < 0) return whole;\n const tabQuery = tabs[lit].query;\n const owned = (name: string) => Boolean(tabQuery) && ownedTails(tabQuery, name).length > 0;\n const params = new URLSearchParams();\n if (lit > 0) params.set('tab', tabs[lit].slug);\n let residue = filter;\n const naming = names[0] === nameColumn(spec) ? names[0] : undefined;\n for (const name of names) {\n if (owned(name)) continue;\n const tails = ownedTails(filter, name) as string[];\n if (!tails.length) continue;\n // A whole condition (a segment of the rows that point at the record) is a yes.\n if (wholeFilter(name)) {\n if (tails.length !== 1 || tails[0]) return whole;\n params.set(filterAddressWord(name), 'yes');\n residue = stripCondition(residue, name);\n continue;\n }\n const shapes = tails.map(shapeOf);\n if (shapes.some((shape) => !shape)) return whole;\n if (shapes.some((shape) => shape!.op === 'search')) {\n // `search=` is the name column's box alone, one phrase.\n if (name !== naming || shapes.length !== 1) return whole;\n params.set('search', shapes[0]!.value);\n residue = stripCondition(residue, name);\n continue;\n }\n const value = paramOf(shapes as Shape[]);\n if (value === undefined) return whole;\n params.set(filterAddressWord(name), value);\n residue = stripCondition(residue, name);\n }\n /* What is left once the boxes speak must be the tab's own words exactly:\n anything else is a condition the parameters would drop. */\n if (canonicalize(residue) !== canonicalize(tabQuery)) return whole;\n /* The tab's own order goes unsaid and another is `sort=`. A tab that\n sorts, asked unsorted, has no parameter to say so. */\n /* A question the view did not ask itself — a dashboard's drill — has no\n order of its own to keep: it takes the tab's, and is said in the tab's words. */\n const sort = !parts.sort && options.adoptOrder ? tabs[lit].sort : sortParam(parts.sort);\n if (sort === undefined) return whole;\n if (sort !== tabs[lit].sort) {\n if (!sort) return whole;\n params.set('sort', sort);\n }\n return params.toString();\n}\n\n/** The question an address's parameters ask of a list, as the reader's refinement text. */\nexport function addressQuestion(spec: ListSpec, params: URLSearchParams): string {\n const q = params.get('q');\n if (q !== null) return squeeze(q);\n const tabs = tabQuestions(spec);\n // No tab, or one the view no longer has, is the tab the view opens on.\n const tab = tabs.find((candidate) => candidate.slug === params.get('tab')) ?? tabs[0];\n const conditions: string[] = [];\n const naming = nameColumn(spec);\n const search = params.get('search');\n if (naming && search?.trim()) conditions.push(`${naming} contains ${String(quote(search.trim()))}`);\n for (const name of addressColumns(spec)) {\n const raw = params.get(filterAddressWord(name));\n if (wholeFilter(name)) {\n if (raw === 'yes') conditions.push(name);\n continue;\n }\n const parsed = raw === null ? undefined : addressFilterValue(raw);\n if (!parsed) continue;\n if (parsed.kind === 'range') {\n // Both ends are one `between`, the one condition a rail's range reads back.\n if (parsed.min && parsed.max) conditions.push(`${name} between ${parsed.min} and ${parsed.max}`);\n else if (parsed.min) conditions.push(`${name} >= ${parsed.min}`);\n else conditions.push(`${name} <= ${parsed.max}`);\n continue;\n }\n const values = parsed.values;\n // `tags` is a record's tags only where its list filters by them; it is asked with `has`.\n if (name === TAGS_COLUMN) {\n conditions.push(`${name} has ${values.length > 1 ? `(${values.map(literal).join(', ')})` : literal(values[0])}`);\n continue;\n }\n conditions.push(values.length > 1 ? `${name} in (${values.map(literal).join(', ')})` : `${name} = ${literal(values[0])}`);\n }\n // The tab's words as the list writes them, so the tab lights; the boxes after them.\n const where = conditions.length ? andQuery(tab.query, ...conditions) : tab.query;\n const sort = params.get('sort');\n const parsedSort = sort ? addressSortValue(sort) : undefined;\n const order = parsedSort\n ? `sort ${parsedSort.map(({ field, descending }) => (descending ? `${field} desc` : field)).join(', ')}`\n : tab.order;\n return squeeze(`${where} ${order}`);\n}\n\n/** A named room uses the same entry as navigation; only lists read filter parameters. */\nexport function viewEntryFromAddress(\n key: string,\n search: string,\n): ReturnType<typeof pageView> | ({ view: string } & ReturnType<typeof tableView>) | null {\n const app = activeApp();\n const spec = app ? viewRoom(app, key) : undefined;\n if (!spec || spec.error) return null;\n if (!isListRoom(spec)) return pageView(roomPageId(spec)!, undefined, undefined, spec.label);\n return { ...tableView(spec.table, wholeQuery(spec, addressQuestion(spec, new URLSearchParams(search)))), view: key };\n}\n\n/* ---------- canonical addresses ---------- */\n\ntype Entry = {\n kind: 'table' | 'breakdown' | 'row' | 'page';\n table?: string;\n query?: string;\n tab?: string;\n view?: string;\n key?: Record<string, unknown>;\n label?: string;\n pageId?: string;\n rootBody?: string;\n /** A natural key's value, when the route has one and it is known (url.js fills it). */\n address?: string;\n};\n\nlet held: { app: object; schema: object; book: AddressBook } | undefined;\n\n/** The address book of the App on screen, or undefined outside one. Built once per App payload and schema. */\nfunction addressBook(): AddressBook | undefined {\n const app = activeApp();\n const schema = state.schema as { tables: AddressBook extends { tables: Map<string, infer T> } ? T[] : never } | null;\n if (!app || !schema) return undefined;\n if (held?.app !== app || held.schema !== schema) {\n held = {\n app,\n schema,\n book: appAddressBook({\n views: app.views,\n order: viewOrder(app),\n resources: app.resources,\n pages: app.pages ?? [],\n tables: schema.tables,\n inbox: app.inbox === true,\n assistant: app.assistant?.page === true,\n projected: true,\n }),\n };\n }\n return held.book;\n}\n\n/* ---------- dashboards ---------- */\n\n/** A window phrase as an address says it: \"last 30 days\" → \"last-30-days\" (dashboardModel's windowSlug). */\nconst windowSlug = (phrase: string) => phrase.replace(/\\s+/g, '-');\n\n/** A dashboard's slot — `last-30-days;owner_id=12` — as its parameters. */\nfunction dashboardParams(body: string | undefined): URLSearchParams {\n const params = new URLSearchParams();\n for (const part of String(body ?? '').split(';')) {\n const eq = part.indexOf('=');\n if (eq > 0) params.set(part.slice(0, eq), part.slice(eq + 1));\n else if (part) params.set('period', part);\n }\n return params;\n}\n\n/** The dashboard behind a view key as the walk holds it, its window and picks read from the parameters. */\nexport function dashboardEntry(key: string, search: string, home: boolean): Entry | undefined {\n const app = activeApp();\n const room = app ? viewRoom(app, key) : undefined;\n if (!room || !('sections' in room)) return undefined;\n const params = new URLSearchParams(search);\n /* A dashboard's picks by its filters' columns; a free-form page's place by the parameters it declares. */\n const picks = [...(room.filters ?? []), ...((room as { page?: { params: string[] } }).page?.params ?? [])].flatMap((column) =>\n params.has(column) ? [`${column}=${params.get(column)}`] : [],\n );\n /* Said `all-time`, or `all` as a person guesses it, once written. */\n const period = readPeriod(params.get('period') ?? '');\n /* All time is said only where the dashboard opens on a window of its own (`window:`, shared/addressParams.ts ALL_TIME). */\n const windowed =\n period && (room.windows?.some((phrase) => windowSlug(phrase) === period) || (room.window && period === ALL_TIME)) ? period : '';\n /* A section with windows of its own says its period as `period.<its key>`; one it does not offer is not read. */\n const periods = [...room.sections, ...(room.aside ?? [])].flatMap((section) => {\n const said = section.windowKey ? params.get(`period.${section.windowKey}`) : null;\n return said && section.windows?.some((phrase) => windowSlug(phrase) === said) ? [`period.${section.windowKey}=${said}`] : [];\n });\n const named = [...picks, ...periods];\n const body = [windowed, ...named].filter((part, i) => part || (i === 0 && named.length)).join(';');\n return {\n kind: 'page',\n pageId: dashPageId(key),\n table: undefined,\n label: room.label,\n rootBody: body || undefined,\n ...(home ? { tab: 'home' } : {}),\n };\n}\n\n/* ---------- writing ---------- */\n\n/** The key a page entry arrived with and has not resolved yet: `id` or `a=1,b=2`. */\nfunction keyOfBody(body: string | undefined, table: string, book: AddressBook): Record<string, unknown> | undefined {\n if (body === undefined || body === '') return undefined;\n if (body.includes('='))\n return Object.fromEntries(\n body.split(',').map((pair) => {\n const [column, ...rest] = pair.split('=');\n return [column, rest.join('=')];\n }),\n );\n const pk =\n book.tables\n .get(table)\n ?.columns.filter((column) => column.primaryKey)\n .map((column) => column.name) ?? [];\n return pk.length === 1 ? { [pk[0]]: body } : undefined;\n}\n\n/** A label that is a record's name, not the placeholder a link arrives with (its key). */\nfunction knownName(entry: Entry): string | undefined {\n const label = entry.label?.trim();\n if (!label || !entry.key) return undefined;\n return Object.values(entry.key).some((value) => String(value) === label) ? undefined : label;\n}\n\n/** The list room a view key names, when it is one. */\nfunction listRoom(view: string | undefined): (ListSpec & { id: string }) | undefined {\n const app = activeApp();\n const room = app && view ? viewRoom(app, view) : undefined;\n return room && isListRoom(room) && !room.error ? room : undefined;\n}\n\ninterface Said {\n segments: string[];\n search?: string;\n}\n\n/**\n * The address one walk entry stands for, after the App's id, or undefined\n * when it has none in words — a breakdown, a Workbench tab, a list no view\n * shows — and the caller writes the walk's own form for it. A tenant's\n * address is the same, its selector after these (tenantLink.ts).\n */\nfunction sayEntry(book: AddressBook, entry: Entry): Said | undefined {\n const app = activeApp();\n if (entry.kind === 'table') {\n if (entry.tab && entry.tab !== 'rows' && entry.tab !== 'new') return undefined;\n const tail = entry.tab === 'new' ? ['new'] : [];\n /* A list the App's own view opened, or — a dashboard's drill, a link\n that named only the table — the view its table's records live under:\n the drill becomes that view's filter parameters. */\n const route = book.records.get(entry.table ?? '');\n const own = listRoom(entry.view)?.table === entry.table;\n const view = own ? entry.view : route?.target.kind === 'list' ? route.target.view : undefined;\n if (view && book.views.has(view)) {\n const params = listParams({ table: entry.table!, query: entry.query ?? entry.table!, view }, { adoptOrder: !own });\n if (params === undefined) return undefined;\n return { segments: [encodeURIComponent(book.views.get(view)!), ...tail], search: params };\n }\n /* A table no view shows is its own name, as it opens: the columns the\n grid chose to show are its default, not a question. */\n const asked = (entry.query ?? entry.table ?? '').trim().replace(/\\s+show\\s+[^]*$/i, '');\n if (route?.target.kind === 'table' && asked === entry.table) {\n return { segments: [encodeURIComponent(route.segment), ...tail] };\n }\n return undefined;\n }\n if (entry.kind === 'row') {\n const split = listRoom(entry.view);\n const via = split && split.table === entry.table && splitOf(split) ? book.views.get(entry.view!) : undefined;\n const segments = recordSegments(book, entry.table!, {\n key: entry.key ?? {},\n natural: entry.address,\n via,\n slug: isPublicVisitor() ? addressSlug(knownName(entry)) : undefined,\n tab: entry.tab ? tabWord(entry.tab) : undefined,\n });\n return segments && { segments };\n }\n if (entry.kind === 'page') {\n if (entry.pageId === INBOX_PAGE) return book.routes.get(INBOX_SEGMENT)?.kind === 'inbox' ? { segments: [INBOX_SEGMENT] } : undefined;\n // The assistant's page, and one conversation on it (`tab` holds its id).\n if (entry.pageId === ASSISTANT_PAGE)\n return book.routes.get(ASSISTANT_ROOM)?.kind === 'assistant'\n ? { segments: [ASSISTANT_ROOM, ...(entry.tab ? [encodeURIComponent(entry.tab)] : [])] }\n : undefined;\n const dash = dashViewKey(entry.pageId);\n if (dash !== undefined && book.views.has(dash)) {\n const params = dashboardParams(entry.rootBody).toString();\n if (entry.tab === 'home' && app?.home === dash) return { segments: [], search: params };\n return { segments: [encodeURIComponent(book.views.get(dash)!)], search: params };\n }\n const form = formViewKey(entry.pageId);\n if (form !== undefined) return book.views.has(form) ? { segments: [encodeURIComponent(book.views.get(form)!)] } : undefined;\n const pageId = entry.pageId ?? '';\n /* A walk read back from its own form names a suggested page by its\n table (`@sales_rep~7`, pageToken), before the page has said its base. */\n const table =\n entry.table ??\n book.pageBase.get(pageId) ??\n (pageId.startsWith('suggested:') ? pageId.slice('suggested:'.length) : book.tables.has(pageId) ? pageId : undefined);\n if (!table) return undefined;\n const key = entry.key ?? keyOfBody(entry.rootBody, table, book);\n /* A second page over its base is said under its own address; the one\n the records open through — or one this browser built — under theirs. */\n const own = book.pages.get(pageId);\n const via = own && book.defaultPage.get(table) !== pageId ? own : undefined;\n if (!key) return own ? { segments: [encodeURIComponent(own)] } : undefined;\n const segments = recordSegments(book, table, {\n key,\n natural: entry.address,\n via,\n slug: isPublicVisitor() ? addressSlug(knownName({ ...entry, key })) : undefined,\n tab: entry.tab ? tabWord(entry.tab) : undefined,\n });\n return segments && { segments };\n }\n return undefined;\n}\n\n/**\n * The canonical address of what is on screen — the walk's last entry — or\n * undefined when it has none in words and the caller writes the walk's own\n * form for it (still no `@` for a list, and every such address still opens).\n */\nexport function appAddress(connection: string, stack: readonly Entry[]): string | undefined {\n const book = addressBook();\n if (!book) return undefined;\n const root = `/${encodeURIComponent(connection)}`;\n const last = stack[stack.length - 1];\n if (!last) return root;\n const said = sayEntry(book, last);\n if (!said) return undefined;\n const path = said.segments.length ? `${root}/${said.segments.join('/')}` : root;\n return said.search ? `${path}?${said.search}` : path;\n}\n\n/** A view's own address, `/compass/leads`, as the App on screen says it; undefined for a key it does not open as a room. */\nexport function viewPath(view: string): string | undefined {\n const book = addressBook(),\n app = activeApp();\n const segment = book?.views.get(view);\n return app && segment ? `/${encodeURIComponent(app.id)}/${encodeURIComponent(segment)}` : undefined;\n}\n\n/** Whether a walk entry is a record whose natural key is not known yet. */\nexport function needsNaturalKey(entry: Entry | undefined): { table: string; column: string } | undefined {\n const book = addressBook();\n if (!book || !entry || entry.address !== undefined || (entry.kind !== 'row' && entry.kind !== 'page') || !entry.key) return undefined;\n const table = entry.table ?? book.pageBase.get(entry.pageId ?? '');\n const column = table ? book.records.get(table)?.column : undefined;\n return table && column ? { table, column } : undefined;\n}\n\n/* ---------- reading ---------- */\n\n/** The notifications room as the walk holds it. */\nexport const inboxEntry = (): Entry => ({ kind: 'page', pageId: INBOX_PAGE, table: undefined, label: say('notifications') });\n\n/** The assistant's page as the walk holds it, on one conversation when `conversation` names it. */\nexport const assistantEntry = (conversation?: string): Entry => ({\n kind: 'page',\n pageId: ASSISTANT_PAGE,\n table: undefined,\n label: say('assistant'),\n ...(conversation ? { tab: conversation } : {}),\n});\n\nconst labelFor = (text: string) => (text.length > 24 ? `${text.slice(0, 23)}…` : text);\n\n/** The list a record route stands under, as it opens: the crumb back to it. */\nfunction listEntryFor(target: RouteTarget): Entry[] {\n if (target.kind !== 'list') return [];\n const entry = viewEntryFromAddress(target.view, '');\n return entry ? [entry as Entry] : [];\n}\n\n/**\n * An App address read into a walk: `/compass/leads/127` is the leads list\n * as it opens and lead 127 beside or after it. Null for anything that is not\n * an App address — a query-language path, the walk's `@`/`~` forms — which\n * the caller reads the older way and then rewrites to this one.\n */\nexport function appStackFrom(hash: string): { connection: string; stack: Entry[]; slugged?: boolean } | null {\n const book = addressBook();\n const app = activeApp();\n if (!book || !app) return null;\n const [raw, search = ''] = String(hash ?? '')\n .replace(/^\\//, '')\n .split(/\\?(.*)/s);\n const segments = raw.split('/').filter(Boolean);\n if (!segments.length) return null;\n let connection: string;\n try {\n connection = decodeURIComponent(segments[0]);\n } catch {\n return null;\n }\n const rest = segments.slice(1);\n if (!rest.length) {\n // The App's root: its landing, or its home dashboard with a window.\n const home = search && app.home ? dashboardEntry(app.home, search, true) : undefined;\n return { connection, stack: home ? [home] : [] };\n }\n const parsed = readAppSegments(book, rest);\n if (!parsed) return null;\n const target = parsed.target;\n if (parsed.kind === 'route') {\n if (target.kind === 'dashboard') {\n const entry = dashboardEntry(target.view, search, false);\n return entry ? { connection, stack: [entry] } : null;\n }\n if (target.kind === 'list' || target.kind === 'form') {\n const entry = viewEntryFromAddress(target.view, target.kind === 'list' ? search : '');\n return entry ? { connection, stack: [entry as Entry] } : null;\n }\n if (target.kind === 'page') return { connection, stack: [{ kind: 'page', pageId: target.page, table: target.table }] };\n if (target.kind === 'inbox') return { connection, stack: [inboxEntry()] };\n if (target.kind === 'assistant') return { connection, stack: [assistantEntry()] };\n return { connection, stack: [tableView(target.table) as Entry] };\n }\n if (parsed.kind === 'conversation') return { connection, stack: [assistantEntry(parsed.id)] };\n if (parsed.kind === 'new') {\n if (target.kind === 'list') {\n const entry = viewEntryFromAddress(target.view, search);\n return entry ? { connection, stack: [{ ...(entry as Entry), tab: 'new' }] } : null;\n }\n return { connection, stack: [tableView(parsed.target.table, parsed.target.table, 'new') as Entry] };\n }\n const shown = Object.values(parsed.key).join(' · ');\n const row: Entry = {\n kind: 'row',\n table: parsed.table,\n key: parsed.key,\n label: labelFor(shown),\n ...(parsed.tab ? { tab: parsed.tab } : {}),\n ...(parsed.natural ? { address: parsed.natural.value } : {}),\n };\n if (target.kind === 'list') {\n const room = listRoom(target.view);\n if (!room || room.table !== parsed.table) return { connection, stack: [row], slugged: parsed.slugged };\n return { connection, stack: [...listEntryFor(target), { ...row, view: target.view }], slugged: parsed.slugged };\n }\n if (target.kind === 'page' && !parsed.natural) {\n return {\n connection,\n stack: [\n {\n kind: 'page',\n pageId: target.page,\n table: parsed.table,\n key: parsed.key,\n label: row.label,\n ...(parsed.tab ? { tab: parsed.tab } : {}),\n },\n ],\n slugged: parsed.slugged,\n };\n }\n return { connection, stack: [row], slugged: parsed.slugged };\n}\n"],"mappings":";;;;;;;;;;;AAGA,IAAa,MAAM,eAAe;CAChC,eAAe;CACf,WAAW;AACb,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC8VD,IAAI;;AAGJ,SAAS,cAAuC;CAC9C,MAAM,MAAM,UAAU;CACtB,MAAM,SAAS,MAAM;CACrB,IAAI,CAAC,OAAO,CAAC,QAAQ,OAAO,KAAA;CAC5B,IAAI,MAAM,QAAQ,OAAO,KAAK,WAAW,QACvC,OAAO;EACL;EACA;EACA,MAAM,eAAe;GACnB,OAAO,IAAI;GACX,OAAO,UAAU,GAAG;GACpB,WAAW,IAAI;GACf,OAAO,IAAI,SAAS,CAAC;GACrB,QAAQ,OAAO;GACf,OAAO,IAAI,UAAU;GACrB,WAAW,IAAI,WAAW,SAAS;GACnC,WAAW;EACb,CAAC;CACH;CAEF,OAAO,KAAK;AACd;;AA8LA,SAAgB,SAAS,MAAkC;CACzD,MAAM,OAAO,YAAY,GACvB,MAAM,UAAU;CAClB,MAAM,UAAU,MAAM,MAAM,IAAI,IAAI;CACpC,OAAO,OAAO,UAAU,IAAI,mBAAmB,IAAI,EAAE,EAAE,GAAG,mBAAmB,OAAO,MAAM,KAAA;AAC5F;;AAiBA,IAAa,kBAAkB,kBAAkC;CAC/D,MAAM;CACN,QAAQ;CACR,OAAO,KAAA;CACP,OAAO,IAAI,WAAW;CACtB,GAAI,eAAe,EAAE,KAAK,aAAa,IAAI,CAAC;AAC9C"}
@@ -1,5 +1,5 @@
1
1
  import { E as sessionIdentity, g as rowRead, h as rowPart, m as canonicalText } from "./shared-poc11xxo.js";
2
- import { it as setComposedReads, rt as composedConnection } from "./shared-DPAVoWY0.js";
2
+ import { it as setComposedReads, rt as composedConnection } from "./shared-BoLbc2WD.js";
3
3
  import { n as commandKeyText } from "./shared-Du2YAQk4.js";
4
4
  // packages/kit/src/client/composedReads.ts
5
5
  /**
@@ -162,4 +162,4 @@ setComposedReads({
162
162
  });
163
163
  export { composing as a, composedReply as i, composedCheck as n, holdComposed as o, composedEpoch as r, mayBeComposed as s, composeUnderway as t };
164
164
 
165
- //# sourceMappingURL=shared-yt7DjSrq.js.map
165
+ //# sourceMappingURL=shared-CpMhOq8F.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-yt7DjSrq.js","names":[],"sources":["../../../kit/src/client/composedReads.ts"],"sourcesContent":["/**\n * The answers a composed record page brought, kept for the requests they\n * answer (shared/recordReads.ts says why a record page is one request).\n *\n * `request` (core.js) asks here before it sends a read: a section asking\n * exactly what the server already asked on its behalf is answered at once,\n * and only a request built differently, or one the server could not answer,\n * goes out as it always did. Each answer is for one asking — a refetch, a\n * tab coming back, a second look, is a fresh read — and none outlives\n * LIFETIME, the session it was read in, its connection or the next write.\n *\n * The record's own row is kept whole: every reader of it (the root probe,\n * each fields section, the header, a custom section's record fields) is\n * answered with exactly its columns, once per set of columns.\n *\n * It tells the request path of itself as it loads (composedHook.ts), so\n * the request path asks it only where a module that reads record pages is\n * loaded: the public page carries none of it.\n */\nimport { canonicalText, rowPart, rowRead } from '@mistriwork/engine/shared/recordReadKeys.js';\nimport { commandKeyText } from '@mistriwork/engine/shared/commandWire.js';\nimport { composedConnection, setComposedReads } from './composedHook.js';\nimport { sessionIdentity } from './sessionIdentity.js';\n\n/** One read the server asked for the page, and its answer (server/http/recordCompose.ts). */\nexport interface ComposedRead {\n path: string;\n body: Record<string, unknown>;\n status: number;\n reply: unknown;\n}\n\n/* Long enough for a panel opened just after the page to find its answer, and\n no longer: past it a read is the reader's own again. */\nconst LIFETIME = 15_000;\n/* How long a record's own read waits for a compose already on its way. */\nconst WAIT = 5_000;\n\ninterface Held {\n identity: ReturnType<typeof sessionIdentity>;\n connection: string | null;\n until: number;\n exact: Map<string, unknown>;\n rows: Array<{ base: string; filter: string; columns: Set<string>; reply: RowReply; asked: Set<string> }>;\n checks: Map<string, boolean>;\n}\ninterface RowReply {\n view: { columns: Array<{ path: string }> } & Record<string, unknown>;\n columns: string[];\n rows: Array<Record<string, unknown>>;\n resolved?: Array<{ path: string; name?: string }>;\n}\n\nlet held: Held[] = [];\nconst pending = new Map<Promise<unknown>, { connection: string | null; since: number }>();\n/* Counts every write sent and every write answered (core.js). A compose's\n answers are the record as the server read it, which is only the record\n now if no write was sent or answered while it was out: one begun before a\n command, or while it ran, read the row from before it, and landing after\n the write it would answer the next read of the page — the refresh the\n write itself asked for — with the old row. On PostgreSQL, where a tenant's\n compose takes seconds, that was the page left showing the old status and\n fields after a command, a post or a tag, and the next change refused as\n stale. */\nlet writes = 0;\n\n/* A selector is the transport's, never part of what is asked. */\nconst asked = (body: Record<string, unknown> | undefined) => {\n if (!body) return {};\n const { app: _app, conn: _conn, ...rest } = body;\n return rest;\n};\nconst exactKey = (path: string, body: Record<string, unknown>) => `${path}\\u0000${canonicalText(asked(body))}`;\nconst checkKey = (command: string, version: number, key: unknown) =>\n `${command}\\u0000${version}\\u0000${commandKeyText(key as Record<string, string | number>)}`;\nconst clone = <T>(value: T): T =>\n typeof structuredClone === 'function' ? structuredClone(value) : (JSON.parse(JSON.stringify(value)) as T);\n\nfunction live(connection: string | null): Held[] {\n const now = Date.now(),\n identity = sessionIdentity();\n held = held.filter((batch) => batch.until > now && batch.identity === identity);\n return held.filter((batch) => batch.connection === connection);\n}\n\n/** Where the writes stand: a compose takes it as it is sent and hands it back with its answers. */\nexport const composedEpoch = (): number => writes;\n\n/** A composed page's reads, kept for the requests they answer — unless a write was sent or answered since `since`, the epoch its compose began in. */\nexport function holdComposed(connection: string | null, reads: readonly ComposedRead[], since: number): void {\n if (since !== writes) return;\n const batch: Held = {\n identity: sessionIdentity(),\n connection,\n until: Date.now() + LIFETIME,\n exact: new Map(),\n rows: [],\n checks: new Map(),\n };\n for (const read of reads) {\n if (read.status >= 400 || !read.reply || typeof read.reply !== 'object') continue;\n const row = read.path === '/api/view/run' ? rowRead(read.body) : undefined;\n if (row && Array.isArray((read.reply as RowReply).rows)) {\n batch.rows.push({\n base: row.base,\n filter: row.filter,\n columns: new Set(row.columns),\n reply: read.reply as RowReply,\n asked: new Set(),\n });\n continue;\n }\n if (read.path === '/api/commands/availability') {\n const checks = (read.body as { checks?: Array<{ command: string; commandVersion: number; keys: unknown[] }> }).checks ?? [];\n const answers = (read.reply as { checks?: Array<{ command: string; commandVersion: number; available: boolean[] }> }).checks ?? [];\n checks.forEach((check, i) =>\n check.keys.forEach((key, k) => {\n const answer = answers[i];\n if (\n answer?.command === check.command &&\n answer.commandVersion === check.commandVersion &&\n typeof answer.available?.[k] === 'boolean'\n ) {\n batch.checks.set(checkKey(check.command, check.commandVersion, key), answer.available[k]);\n }\n }),\n );\n continue;\n }\n batch.exact.set(exactKey(read.path, read.body), read.reply);\n }\n held.push(batch);\n}\n\n/** A compose on its way: a record's own read asked meanwhile waits for it rather than asking beside it. */\nexport function composing(connection: string | null, work: Promise<unknown>): void {\n const entry = { connection, since: Date.now() };\n pending.set(work, entry);\n const done = () => {\n pending.delete(work);\n };\n work.then(done, done);\n}\n\n/** Every answer forgotten, and every compose still out with them: after a write, what was read before it is not the record any more. */\nfunction forgetComposed(): void {\n writes += 1;\n held = [];\n /* A record's own read no longer waits for a compose begun before the write: it could not answer it. */\n pending.clear();\n}\n\nconst COMPOSABLE = new Set(['/api/view/run', '/api/run', '/api/breakdown', '/api/record-history']);\n/** Whether a compose kept, or on its way, could answer this request: asked before waiting on one, so any other request goes out in the same moment it always did. */\nexport function mayBeComposed(path: string): boolean {\n return COMPOSABLE.has(path) && (held.length > 0 || pending.size > 0);\n}\n\n/**\n * The answer the page's compose brought for this request, once, or\n * undefined. A read of the record's own row waits for a compose still on\n * its way; everything else a page asks is asked after its plan, which comes\n * with the answers.\n */\nexport async function composedReply(path: string, body: Record<string, unknown> | undefined): Promise<unknown> {\n if (!COMPOSABLE.has(path)) return undefined;\n const connection = ((body?.app ?? body?.conn) as string | undefined) ?? composedConnection();\n const given = asked(body);\n const row = path === '/api/view/run' ? rowRead(given) : undefined;\n if (row) {\n await composeUnderway(connection);\n const once = row.columns.slice().sort().join('\\u0000');\n for (const batch of live(connection)) {\n const found = batch.rows.find(\n (kept) =>\n kept.base === row.base &&\n kept.filter === row.filter &&\n !kept.asked.has(once) &&\n row.columns.every((column) => kept.columns.has(column)),\n );\n if (!found) continue;\n found.asked.add(once);\n return clone(rowPart(found.reply, row.columns));\n }\n return undefined;\n }\n if (!held.length) return undefined;\n const key = exactKey(path, given);\n for (const batch of live(connection)) {\n if (!batch.exact.has(key)) continue;\n const reply = batch.exact.get(key);\n batch.exact.delete(key);\n return clone(reply);\n }\n return undefined;\n}\n\n/** The composes on their way for this connection, as one wait no longer than WAIT; undefined when none is out. */\nexport function composeUnderway(connection: string | null): Promise<void> | undefined {\n const now = Date.now();\n const waiting = [...pending].filter(([, entry]) => entry.connection === connection && now - entry.since < WAIT).map(([work]) => work);\n if (!waiting.length) return undefined;\n return Promise.race([Promise.allSettled(waiting), new Promise((resolve) => setTimeout(resolve, WAIT))]).then(() => {});\n}\n\n/** Whether a command is available on a record, as the page's compose was told, once; undefined where it was not asked. */\nexport function composedCheck(connection: string | null, command: string, version: number, key: unknown): boolean | undefined {\n if (!held.length) return undefined;\n const wanted = checkKey(command, version, key);\n for (const batch of live(connection)) {\n const answer = batch.checks.get(wanted);\n if (answer === undefined) continue;\n batch.checks.delete(wanted);\n return answer;\n }\n return undefined;\n}\n\nsetComposedReads({ reply: composedReply, may: mayBeComposed, forget: forgetComposed });\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-CpMhOq8F.js","names":[],"sources":["../../../kit/src/client/composedReads.ts"],"sourcesContent":["/**\n * The answers a composed record page brought, kept for the requests they\n * answer (shared/recordReads.ts says why a record page is one request).\n *\n * `request` (core.js) asks here before it sends a read: a section asking\n * exactly what the server already asked on its behalf is answered at once,\n * and only a request built differently, or one the server could not answer,\n * goes out as it always did. Each answer is for one asking — a refetch, a\n * tab coming back, a second look, is a fresh read — and none outlives\n * LIFETIME, the session it was read in, its connection or the next write.\n *\n * The record's own row is kept whole: every reader of it (the root probe,\n * each fields section, the header, a custom section's record fields) is\n * answered with exactly its columns, once per set of columns.\n *\n * It tells the request path of itself as it loads (composedHook.ts), so\n * the request path asks it only where a module that reads record pages is\n * loaded: the public page carries none of it.\n */\nimport { canonicalText, rowPart, rowRead } from '@mistriwork/engine/shared/recordReadKeys.js';\nimport { commandKeyText } from '@mistriwork/engine/shared/commandWire.js';\nimport { composedConnection, setComposedReads } from './composedHook.js';\nimport { sessionIdentity } from './sessionIdentity.js';\n\n/** One read the server asked for the page, and its answer (server/http/recordCompose.ts). */\nexport interface ComposedRead {\n path: string;\n body: Record<string, unknown>;\n status: number;\n reply: unknown;\n}\n\n/* Long enough for a panel opened just after the page to find its answer, and\n no longer: past it a read is the reader's own again. */\nconst LIFETIME = 15_000;\n/* How long a record's own read waits for a compose already on its way. */\nconst WAIT = 5_000;\n\ninterface Held {\n identity: ReturnType<typeof sessionIdentity>;\n connection: string | null;\n until: number;\n exact: Map<string, unknown>;\n rows: Array<{ base: string; filter: string; columns: Set<string>; reply: RowReply; asked: Set<string> }>;\n checks: Map<string, boolean>;\n}\ninterface RowReply {\n view: { columns: Array<{ path: string }> } & Record<string, unknown>;\n columns: string[];\n rows: Array<Record<string, unknown>>;\n resolved?: Array<{ path: string; name?: string }>;\n}\n\nlet held: Held[] = [];\nconst pending = new Map<Promise<unknown>, { connection: string | null; since: number }>();\n/* Counts every write sent and every write answered (core.js). A compose's\n answers are the record as the server read it, which is only the record\n now if no write was sent or answered while it was out: one begun before a\n command, or while it ran, read the row from before it, and landing after\n the write it would answer the next read of the page — the refresh the\n write itself asked for — with the old row. On PostgreSQL, where a tenant's\n compose takes seconds, that was the page left showing the old status and\n fields after a command, a post or a tag, and the next change refused as\n stale. */\nlet writes = 0;\n\n/* A selector is the transport's, never part of what is asked. */\nconst asked = (body: Record<string, unknown> | undefined) => {\n if (!body) return {};\n const { app: _app, conn: _conn, ...rest } = body;\n return rest;\n};\nconst exactKey = (path: string, body: Record<string, unknown>) => `${path}\\u0000${canonicalText(asked(body))}`;\nconst checkKey = (command: string, version: number, key: unknown) =>\n `${command}\\u0000${version}\\u0000${commandKeyText(key as Record<string, string | number>)}`;\nconst clone = <T>(value: T): T =>\n typeof structuredClone === 'function' ? structuredClone(value) : (JSON.parse(JSON.stringify(value)) as T);\n\nfunction live(connection: string | null): Held[] {\n const now = Date.now(),\n identity = sessionIdentity();\n held = held.filter((batch) => batch.until > now && batch.identity === identity);\n return held.filter((batch) => batch.connection === connection);\n}\n\n/** Where the writes stand: a compose takes it as it is sent and hands it back with its answers. */\nexport const composedEpoch = (): number => writes;\n\n/** A composed page's reads, kept for the requests they answer — unless a write was sent or answered since `since`, the epoch its compose began in. */\nexport function holdComposed(connection: string | null, reads: readonly ComposedRead[], since: number): void {\n if (since !== writes) return;\n const batch: Held = {\n identity: sessionIdentity(),\n connection,\n until: Date.now() + LIFETIME,\n exact: new Map(),\n rows: [],\n checks: new Map(),\n };\n for (const read of reads) {\n if (read.status >= 400 || !read.reply || typeof read.reply !== 'object') continue;\n const row = read.path === '/api/view/run' ? rowRead(read.body) : undefined;\n if (row && Array.isArray((read.reply as RowReply).rows)) {\n batch.rows.push({\n base: row.base,\n filter: row.filter,\n columns: new Set(row.columns),\n reply: read.reply as RowReply,\n asked: new Set(),\n });\n continue;\n }\n if (read.path === '/api/commands/availability') {\n const checks = (read.body as { checks?: Array<{ command: string; commandVersion: number; keys: unknown[] }> }).checks ?? [];\n const answers = (read.reply as { checks?: Array<{ command: string; commandVersion: number; available: boolean[] }> }).checks ?? [];\n checks.forEach((check, i) =>\n check.keys.forEach((key, k) => {\n const answer = answers[i];\n if (\n answer?.command === check.command &&\n answer.commandVersion === check.commandVersion &&\n typeof answer.available?.[k] === 'boolean'\n ) {\n batch.checks.set(checkKey(check.command, check.commandVersion, key), answer.available[k]);\n }\n }),\n );\n continue;\n }\n batch.exact.set(exactKey(read.path, read.body), read.reply);\n }\n held.push(batch);\n}\n\n/** A compose on its way: a record's own read asked meanwhile waits for it rather than asking beside it. */\nexport function composing(connection: string | null, work: Promise<unknown>): void {\n const entry = { connection, since: Date.now() };\n pending.set(work, entry);\n const done = () => {\n pending.delete(work);\n };\n work.then(done, done);\n}\n\n/** Every answer forgotten, and every compose still out with them: after a write, what was read before it is not the record any more. */\nfunction forgetComposed(): void {\n writes += 1;\n held = [];\n /* A record's own read no longer waits for a compose begun before the write: it could not answer it. */\n pending.clear();\n}\n\nconst COMPOSABLE = new Set(['/api/view/run', '/api/run', '/api/breakdown', '/api/record-history']);\n/** Whether a compose kept, or on its way, could answer this request: asked before waiting on one, so any other request goes out in the same moment it always did. */\nexport function mayBeComposed(path: string): boolean {\n return COMPOSABLE.has(path) && (held.length > 0 || pending.size > 0);\n}\n\n/**\n * The answer the page's compose brought for this request, once, or\n * undefined. A read of the record's own row waits for a compose still on\n * its way; everything else a page asks is asked after its plan, which comes\n * with the answers.\n */\nexport async function composedReply(path: string, body: Record<string, unknown> | undefined): Promise<unknown> {\n if (!COMPOSABLE.has(path)) return undefined;\n const connection = ((body?.app ?? body?.conn) as string | undefined) ?? composedConnection();\n const given = asked(body);\n const row = path === '/api/view/run' ? rowRead(given) : undefined;\n if (row) {\n await composeUnderway(connection);\n const once = row.columns.slice().sort().join('\\u0000');\n for (const batch of live(connection)) {\n const found = batch.rows.find(\n (kept) =>\n kept.base === row.base &&\n kept.filter === row.filter &&\n !kept.asked.has(once) &&\n row.columns.every((column) => kept.columns.has(column)),\n );\n if (!found) continue;\n found.asked.add(once);\n return clone(rowPart(found.reply, row.columns));\n }\n return undefined;\n }\n if (!held.length) return undefined;\n const key = exactKey(path, given);\n for (const batch of live(connection)) {\n if (!batch.exact.has(key)) continue;\n const reply = batch.exact.get(key);\n batch.exact.delete(key);\n return clone(reply);\n }\n return undefined;\n}\n\n/** The composes on their way for this connection, as one wait no longer than WAIT; undefined when none is out. */\nexport function composeUnderway(connection: string | null): Promise<void> | undefined {\n const now = Date.now();\n const waiting = [...pending].filter(([, entry]) => entry.connection === connection && now - entry.since < WAIT).map(([work]) => work);\n if (!waiting.length) return undefined;\n return Promise.race([Promise.allSettled(waiting), new Promise((resolve) => setTimeout(resolve, WAIT))]).then(() => {});\n}\n\n/** Whether a command is available on a record, as the page's compose was told, once; undefined where it was not asked. */\nexport function composedCheck(connection: string | null, command: string, version: number, key: unknown): boolean | undefined {\n if (!held.length) return undefined;\n const wanted = checkKey(command, version, key);\n for (const batch of live(connection)) {\n const answer = batch.checks.get(wanted);\n if (answer === undefined) continue;\n batch.checks.delete(wanted);\n return answer;\n }\n return undefined;\n}\n\nsetComposedReads({ reply: composedReply, may: mayBeComposed, forget: forgetComposed });\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkCA,IAAM,WAAW;AAEjB,IAAM,OAAO;AAiBb,IAAI,OAAe,CAAC;AACpB,IAAM,0BAAU,IAAI,IAAoE;AAUxF,IAAI,SAAS;AAGb,IAAM,SAAS,SAA8C;CAC3D,IAAI,CAAC,MAAM,OAAO,CAAC;CACnB,MAAM,EAAE,KAAK,MAAM,MAAM,OAAO,GAAG,SAAS;CAC5C,OAAO;AACT;AACA,IAAM,YAAY,MAAc,SAAkC,GAAG,KAAK,QAAQ,cAAc,MAAM,IAAI,CAAC;AAC3G,IAAM,YAAY,SAAiB,SAAiB,QAClD,GAAG,QAAQ,QAAQ,QAAQ,QAAQ,eAAe,GAAsC;AAC1F,IAAM,SAAY,UAChB,OAAO,oBAAoB,aAAa,gBAAgB,KAAK,IAAK,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAEpG,SAAS,KAAK,YAAmC;CAC/C,MAAM,MAAM,KAAK,IAAI,GACnB,WAAW,gBAAgB;CAC7B,OAAO,KAAK,QAAQ,UAAU,MAAM,QAAQ,OAAO,MAAM,aAAa,QAAQ;CAC9E,OAAO,KAAK,QAAQ,UAAU,MAAM,eAAe,UAAU;AAC/D;;AAGA,IAAa,sBAA8B;;AAG3C,SAAgB,aAAa,YAA2B,OAAgC,OAAqB;CAC3G,IAAI,UAAU,QAAQ;CACtB,MAAM,QAAc;EAClB,UAAU,gBAAgB;EAC1B;EACA,OAAO,KAAK,IAAI,IAAI;EACpB,uBAAO,IAAI,IAAI;EACf,MAAM,CAAC;EACP,wBAAQ,IAAI,IAAI;CAClB;CACA,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,UAAU,OAAO,CAAC,KAAK,SAAS,OAAO,KAAK,UAAU,UAAU;EACzE,MAAM,MAAM,KAAK,SAAS,kBAAkB,QAAQ,KAAK,IAAI,IAAI,KAAA;EACjE,IAAI,OAAO,MAAM,QAAS,KAAK,MAAmB,IAAI,GAAG;GACvD,MAAM,KAAK,KAAK;IACd,MAAM,IAAI;IACV,QAAQ,IAAI;IACZ,SAAS,IAAI,IAAI,IAAI,OAAO;IAC5B,OAAO,KAAK;IACZ,uBAAO,IAAI,IAAI;GACjB,CAAC;GACD;EACF;EACA,IAAI,KAAK,SAAS,8BAA8B;GAC9C,MAAM,SAAU,KAAK,KAA0F,UAAU,CAAC;GAC1H,MAAM,UAAW,KAAK,MAAgG,UAAU,CAAC;GACjI,OAAO,SAAS,OAAO,MACrB,MAAM,KAAK,SAAS,KAAK,MAAM;IAC7B,MAAM,SAAS,QAAQ;IACvB,IACE,QAAQ,YAAY,MAAM,WAC1B,OAAO,mBAAmB,MAAM,kBAChC,OAAO,OAAO,YAAY,OAAO,WAEjC,MAAM,OAAO,IAAI,SAAS,MAAM,SAAS,MAAM,gBAAgB,GAAG,GAAG,OAAO,UAAU,EAAE;GAE5F,CAAC,CACH;GACA;EACF;EACA,MAAM,MAAM,IAAI,SAAS,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,KAAK;CAC5D;CACA,KAAK,KAAK,KAAK;AACjB;;AAGA,SAAgB,UAAU,YAA2B,MAA8B;CACjF,MAAM,QAAQ;EAAE;EAAY,OAAO,KAAK,IAAI;CAAE;CAC9C,QAAQ,IAAI,MAAM,KAAK;CACvB,MAAM,aAAa;EACjB,QAAQ,OAAO,IAAI;CACrB;CACA,KAAK,KAAK,MAAM,IAAI;AACtB;;AAGA,SAAS,iBAAuB;CAC9B,UAAU;CACV,OAAO,CAAC;CAER,QAAQ,MAAM;AAChB;AAEA,IAAM,6BAAa,IAAI,IAAI;CAAC;CAAiB;CAAY;CAAkB;AAAqB,CAAC;;AAEjG,SAAgB,cAAc,MAAuB;CACnD,OAAO,WAAW,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ,OAAO;AACpE;;;;;;;AAQA,eAAsB,cAAc,MAAc,MAA6D;CAC7G,IAAI,CAAC,WAAW,IAAI,IAAI,GAAG,OAAO,KAAA;CAClC,MAAM,aAAe,MAAM,OAAO,MAAM,QAAgC,mBAAmB;CAC3F,MAAM,QAAQ,MAAM,IAAI;CACxB,MAAM,MAAM,SAAS,kBAAkB,QAAQ,KAAK,IAAI,KAAA;CACxD,IAAI,KAAK;EACP,MAAM,gBAAgB,UAAU;EAChC,MAAM,OAAO,IAAI,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAQ;EACrD,KAAK,MAAM,SAAS,KAAK,UAAU,GAAG;GACpC,MAAM,QAAQ,MAAM,KAAK,MACtB,SACC,KAAK,SAAS,IAAI,QAClB,KAAK,WAAW,IAAI,UACpB,CAAC,KAAK,MAAM,IAAI,IAAI,KACpB,IAAI,QAAQ,OAAO,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,CAC1D;GACA,IAAI,CAAC,OAAO;GACZ,MAAM,MAAM,IAAI,IAAI;GACpB,OAAO,MAAM,QAAQ,MAAM,OAAO,IAAI,OAAO,CAAC;EAChD;EACA;CACF;CACA,IAAI,CAAC,KAAK,QAAQ,OAAO,KAAA;CACzB,MAAM,MAAM,SAAS,MAAM,KAAK;CAChC,KAAK,MAAM,SAAS,KAAK,UAAU,GAAG;EACpC,IAAI,CAAC,MAAM,MAAM,IAAI,GAAG,GAAG;EAC3B,MAAM,QAAQ,MAAM,MAAM,IAAI,GAAG;EACjC,MAAM,MAAM,OAAO,GAAG;EACtB,OAAO,MAAM,KAAK;CACpB;AAEF;;AAGA,SAAgB,gBAAgB,YAAsD;CACpF,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ,GAAG,WAAW,MAAM,eAAe,cAAc,MAAM,MAAM,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI;CACpI,IAAI,CAAC,QAAQ,QAAQ,OAAO,KAAA;CAC5B,OAAO,QAAQ,KAAK,CAAC,QAAQ,WAAW,OAAO,GAAG,IAAI,SAAS,YAAY,WAAW,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;AACvH;;AAGA,SAAgB,cAAc,YAA2B,SAAiB,SAAiB,KAAmC;CAC5H,IAAI,CAAC,KAAK,QAAQ,OAAO,KAAA;CACzB,MAAM,SAAS,SAAS,SAAS,SAAS,GAAG;CAC7C,KAAK,MAAM,SAAS,KAAK,UAAU,GAAG;EACpC,MAAM,SAAS,MAAM,OAAO,IAAI,MAAM;EACtC,IAAI,WAAW,KAAA,GAAW;EAC1B,MAAM,OAAO,OAAO,MAAM;EAC1B,OAAO;CACT;AAEF;AAEA,iBAAiB;CAAE,OAAO;CAAe,KAAK;CAAe,QAAQ;AAAe,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { J as maskQuoted, tt as useMessageLanguage } from "./shared-poc11xxo.js";
2
2
  import { _t as message, ut as humanName } from "./shared-DJZjSi3J.js";
3
- import { ut as useQueries } from "./shared-CNmaAv64.js";
4
- import { L as state, c as columnLabel, d as findTable, s as columnDisplay } from "./shared-DPAVoWY0.js";
5
- import { vt as presentationColumns } from "./shared-DiFQljcq.js";
3
+ import { ut as useQueries } from "./shared-BHsjawAG.js";
4
+ import { L as state, c as columnLabel, d as findTable, s as columnDisplay } from "./shared-BoLbc2WD.js";
5
+ import { vt as presentationColumns } from "./shared-XcSKDp6N.js";
6
6
  import { l as splitConditions } from "./shared-DOvMIC4W.js";
7
- import { o as referenceNameQuery } from "./shared-DgjU9kOy.js";
7
+ import { o as referenceNameQuery } from "./shared-BIQuK_nq.js";
8
8
  import { r as hasFilterOf } from "./shared-DUlM0U0V.js";
9
9
  import { useEffect, useState } from "react";
10
10
  // packages/kit/src/client/components/conditionText.ts
@@ -96,7 +96,7 @@ function acrossColumn(table, name) {
96
96
  }
97
97
  var words;
98
98
  var arriving;
99
- var load = () => arriving ??= import("./shared-DLSd80D1.js").then((module) => words = module);
99
+ var load = () => arriving ??= import("./shared-HalAVHjv.js").then((module) => words = module);
100
100
  var AS_WRITTEN = {
101
101
  sayTails: (_, column, tails) => written.tails(column, tails),
102
102
  sayCondition: (_, condition) => written.condition(condition),
@@ -149,4 +149,4 @@ function useConditionWords(table, texts) {
149
149
  }
150
150
  export { ungroup as a, squeeze as i, andWords as n, written as o, joined as r, useConditionWords as t };
151
151
 
152
- //# sourceMappingURL=shared-Dm0z5WUl.js.map
152
+ //# sourceMappingURL=shared-CrWv5JKH.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-Dm0z5WUl.js","names":[],"sources":["../../../kit/src/client/components/conditionText.ts","../../../kit/src/client/components/useConditionWords.ts"],"sourcesContent":["/**\n * A condition as written, and the catalog's connectives: the part of\n * conditionWords.ts a list says before its words have loaded.\n *\n * The words (conditionWords.ts) are most of a filter's weight and are\n * needed only once an App's list is narrowed, so they arrive on demand\n * (useConditionWords.ts); until they have, and always in the Workbench, a\n * condition is said as written. Both halves say \"as written\" here, so the\n * moment before the words arrive reads exactly as the Workbench always has.\n */\nimport { maskQuoted, splitConditions } from '../clauses.js';\nimport { message } from '../messages.js';\n\nexport const squeeze = (text: string) =>\n String(text ?? '')\n .replace(/\\s+/g, ' ')\n .trim();\n\n/** \"A\", \"A or B\", \"A, B or C\" — the last pair by the catalog's connective, the rest by its comma. */\nexport function joined(words: string[], connective: 'filter.and' | 'filter.or'): string {\n if (words.length <= 1) return words[0] ?? '';\n const head = words.slice(0, -1).reduce((first, next) => message('filter.comma', { first, next }));\n return message(connective, { first: head, last: words[words.length - 1] });\n}\n\n/** Several conditions as one sentence: \"Status is Open and Owner is me\". */\nexport const andWords = (words: string[]): string => joined(words, 'filter.and');\n\n/** A single outer pair of parentheses says grouping, not meaning. */\nexport function ungroup(text: string): string {\n const t = squeeze(text);\n if (!t.startsWith('(') || !t.endsWith(')')) return t;\n const masked = maskQuoted(t.slice(1, -1)) as string;\n let depth = 0;\n for (const ch of masked) {\n depth += ch === '(' ? 1 : ch === ')' ? -1 : 0;\n if (depth < 0) return t;\n }\n return squeeze(t.slice(1, -1));\n}\n\n/** The language as written, where there is no App to lend its words. */\nexport const written = {\n tails: (column: string, tails: readonly string[]): string => tails.map((tail) => `${column} ${squeeze(tail)}`).join(' and '),\n condition: (condition: string): string => ungroup(condition),\n filter: (filter: string): string[] => (splitConditions(squeeze(filter)) as string[]).map(ungroup),\n question: (text: string): string[] => (squeeze(text) ? [squeeze(text)] : []),\n};\n","/**\n * The words an App list's surfaces say conditions in (conditionWords.ts),\n * and the speaker they say them through: the table's columns as this reader\n * is served them, their labels and display rules from the App, and the names\n * of the records the conditions point at, read through the same query a\n * reference box uses. Outside an App — the Workbench — there is no speaker,\n * and the language is said as written.\n *\n * The words are loaded on demand, the first time an App's list has a\n * condition to say: an unnarrowed list, the Workbench and every first page\n * go without them. Until they arrive a condition is said as written\n * (conditionText.ts), and each surface asking is drawn again when they do.\n */\nimport { useQueries } from '@tanstack/react-query';\nimport { useEffect, useState } from 'react';\nimport { columnDisplay, columnLabel, findTable, state } from '../core.js';\nimport { hasFilterOf } from '@mistriwork/engine/shared/listFilters.js';\nimport { presentationColumns } from './columnPresentation.js';\nimport { humanName } from '@mistriwork/engine/shared/model.js';\nimport type { ColumnMeta, TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport { andWords, written } from './conditionText.js';\nimport type { ConditionSpeaker } from './conditionWords.js';\nimport { referenceNameQuery } from './FilterBoxes.js';\nimport { useMessageLanguage } from './useMessageLanguage.js';\nimport type { DisplayRule } from './valueModel.js';\n\n/**\n * The App's words for a table's columns, before any record is named; null\n * outside an App. An App is where its words are registered (appnav's label\n * source, which answers every column); the Workbench registers none. Asked\n * of core rather than of appnav, so the grid that draws a column menu does\n * not pull the App's shell in with it.\n */\nfunction appSpeaker(table: TableMeta | undefined): ConditionSpeaker | null {\n if (!table?.columns.length || columnLabel(table.id, table.columns[0].name) == null) return null;\n return {\n column: (name) => table.columns.find((column) => column.name === name) ?? acrossColumn(table, name),\n label: (name) => (columnLabel(table.id, name) as string | null) ?? humanName(name),\n rule: (name) => columnDisplay(table.id, name) as DisplayRule | null,\n };\n}\n\n/* A list filter across its related tables (shared/listFilters.ts) said as the column it asks: a path's column as\n it is served on the record it reaches, the column of the rows that point at the record, or — a segment of\n theirs — a yes with no value. A path or rows this reader is not served are no column, and said as another. */\nfunction acrossColumn(table: TableMeta, name: string): (ColumnMeta & { allowed?: readonly string[] }) | undefined {\n const has = hasFilterOf(name);\n if (!has) return name.includes('.') ? (presentationColumns(state.schema, table, [name]).get(name) as ColumnMeta | undefined) : undefined;\n const child = (findTable(has.child) ?? state.schema?.tables.find((t: TableMeta) => t.name === has.child)) as TableMeta | undefined;\n if (!child) return undefined;\n if (child.segments && Object.hasOwn(child.segments, has.asks)) return { name, type: 'BOOLEAN', nullable: false, primaryKey: false };\n return presentationColumns(state.schema, child, [has.asks]).get(has.asks) as ColumnMeta | undefined;\n}\n\ntype Words = typeof import('./conditionWords.js');\nlet words: Words | undefined;\nlet arriving: Promise<Words> | undefined;\nconst load = () => (arriving ??= import('./conditionWords.js').then((module) => (words = module)));\n\n/** What a surface says conditions with: the App's words once loaded, else the language as written. */\ninterface ConditionWords {\n speaker: ConditionSpeaker | null;\n sayTails(speaker: ConditionSpeaker | null, column: string, tails: readonly string[]): string;\n sayCondition(speaker: ConditionSpeaker | null, condition: string): string;\n sayFilter(speaker: ConditionSpeaker | null, filter: string): string[];\n sayQuestion(speaker: ConditionSpeaker | null, text: string): string[];\n andWords(said: string[]): string;\n}\n\nconst AS_WRITTEN: Omit<ConditionWords, 'speaker'> = {\n sayTails: (_, column, tails) => written.tails(column, tails),\n sayCondition: (_, condition) => written.condition(condition),\n sayFilter: (_, filter) => written.filter(filter),\n sayQuestion: (_, text) => written.question(text),\n andWords,\n};\n\n/**\n * The words for these texts — the conditions a surface is about to say —\n * with every reference among them named once its record has been read.\n */\nexport function useConditionWords(table: TableMeta | undefined, texts: readonly string[]): ConditionWords {\n // A change of language is a change of words.\n useMessageLanguage();\n const base = appSpeaker(table);\n const asks = !!base && texts.some((text) => text.trim());\n const [, arrived] = useState(0);\n useEffect(() => {\n if (!asks || words) return;\n let live = true;\n void load().then(() => {\n if (live) arrived((n) => n + 1);\n });\n return () => {\n live = false;\n };\n }, [asks]);\n const loaded = base ? words : undefined;\n const wanted =\n base && loaded\n ? [\n ...new Map(\n texts.flatMap((text) =>\n [...loaded.referencedIds(base, text)].flatMap(([column, ids]) =>\n ids.map((id) => [`${column}\\n${id}`, { column, id }] as const),\n ),\n ),\n ).values(),\n ]\n : [];\n const names = useQueries({ queries: wanted.map(({ column, id }) => referenceNameQuery(base?.column(column), id)) });\n if (!base || !loaded) return { speaker: null, ...AS_WRITTEN };\n const known = new Map(wanted.map(({ column, id }, i) => [`${column}\\n${id}`, names[i]?.data ?? undefined]));\n const { sayTails, sayCondition, sayFilter, sayQuestion } = loaded;\n return {\n speaker: { ...base, recordName: (column, id) => known.get(`${column}\\n${id}`) ?? undefined },\n sayTails,\n sayCondition,\n sayFilter,\n sayQuestion,\n andWords,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-CrWv5JKH.js","names":[],"sources":["../../../kit/src/client/components/conditionText.ts","../../../kit/src/client/components/useConditionWords.ts"],"sourcesContent":["/**\n * A condition as written, and the catalog's connectives: the part of\n * conditionWords.ts a list says before its words have loaded.\n *\n * The words (conditionWords.ts) are most of a filter's weight and are\n * needed only once an App's list is narrowed, so they arrive on demand\n * (useConditionWords.ts); until they have, and always in the Workbench, a\n * condition is said as written. Both halves say \"as written\" here, so the\n * moment before the words arrive reads exactly as the Workbench always has.\n */\nimport { maskQuoted, splitConditions } from '../clauses.js';\nimport { message } from '../messages.js';\n\nexport const squeeze = (text: string) =>\n String(text ?? '')\n .replace(/\\s+/g, ' ')\n .trim();\n\n/** \"A\", \"A or B\", \"A, B or C\" — the last pair by the catalog's connective, the rest by its comma. */\nexport function joined(words: string[], connective: 'filter.and' | 'filter.or'): string {\n if (words.length <= 1) return words[0] ?? '';\n const head = words.slice(0, -1).reduce((first, next) => message('filter.comma', { first, next }));\n return message(connective, { first: head, last: words[words.length - 1] });\n}\n\n/** Several conditions as one sentence: \"Status is Open and Owner is me\". */\nexport const andWords = (words: string[]): string => joined(words, 'filter.and');\n\n/** A single outer pair of parentheses says grouping, not meaning. */\nexport function ungroup(text: string): string {\n const t = squeeze(text);\n if (!t.startsWith('(') || !t.endsWith(')')) return t;\n const masked = maskQuoted(t.slice(1, -1)) as string;\n let depth = 0;\n for (const ch of masked) {\n depth += ch === '(' ? 1 : ch === ')' ? -1 : 0;\n if (depth < 0) return t;\n }\n return squeeze(t.slice(1, -1));\n}\n\n/** The language as written, where there is no App to lend its words. */\nexport const written = {\n tails: (column: string, tails: readonly string[]): string => tails.map((tail) => `${column} ${squeeze(tail)}`).join(' and '),\n condition: (condition: string): string => ungroup(condition),\n filter: (filter: string): string[] => (splitConditions(squeeze(filter)) as string[]).map(ungroup),\n question: (text: string): string[] => (squeeze(text) ? [squeeze(text)] : []),\n};\n","/**\n * The words an App list's surfaces say conditions in (conditionWords.ts),\n * and the speaker they say them through: the table's columns as this reader\n * is served them, their labels and display rules from the App, and the names\n * of the records the conditions point at, read through the same query a\n * reference box uses. Outside an App — the Workbench — there is no speaker,\n * and the language is said as written.\n *\n * The words are loaded on demand, the first time an App's list has a\n * condition to say: an unnarrowed list, the Workbench and every first page\n * go without them. Until they arrive a condition is said as written\n * (conditionText.ts), and each surface asking is drawn again when they do.\n */\nimport { useQueries } from '@tanstack/react-query';\nimport { useEffect, useState } from 'react';\nimport { columnDisplay, columnLabel, findTable, state } from '../core.js';\nimport { hasFilterOf } from '@mistriwork/engine/shared/listFilters.js';\nimport { presentationColumns } from './columnPresentation.js';\nimport { humanName } from '@mistriwork/engine/shared/model.js';\nimport type { ColumnMeta, TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport { andWords, written } from './conditionText.js';\nimport type { ConditionSpeaker } from './conditionWords.js';\nimport { referenceNameQuery } from './FilterBoxes.js';\nimport { useMessageLanguage } from './useMessageLanguage.js';\nimport type { DisplayRule } from './valueModel.js';\n\n/**\n * The App's words for a table's columns, before any record is named; null\n * outside an App. An App is where its words are registered (appnav's label\n * source, which answers every column); the Workbench registers none. Asked\n * of core rather than of appnav, so the grid that draws a column menu does\n * not pull the App's shell in with it.\n */\nfunction appSpeaker(table: TableMeta | undefined): ConditionSpeaker | null {\n if (!table?.columns.length || columnLabel(table.id, table.columns[0].name) == null) return null;\n return {\n column: (name) => table.columns.find((column) => column.name === name) ?? acrossColumn(table, name),\n label: (name) => (columnLabel(table.id, name) as string | null) ?? humanName(name),\n rule: (name) => columnDisplay(table.id, name) as DisplayRule | null,\n };\n}\n\n/* A list filter across its related tables (shared/listFilters.ts) said as the column it asks: a path's column as\n it is served on the record it reaches, the column of the rows that point at the record, or — a segment of\n theirs — a yes with no value. A path or rows this reader is not served are no column, and said as another. */\nfunction acrossColumn(table: TableMeta, name: string): (ColumnMeta & { allowed?: readonly string[] }) | undefined {\n const has = hasFilterOf(name);\n if (!has) return name.includes('.') ? (presentationColumns(state.schema, table, [name]).get(name) as ColumnMeta | undefined) : undefined;\n const child = (findTable(has.child) ?? state.schema?.tables.find((t: TableMeta) => t.name === has.child)) as TableMeta | undefined;\n if (!child) return undefined;\n if (child.segments && Object.hasOwn(child.segments, has.asks)) return { name, type: 'BOOLEAN', nullable: false, primaryKey: false };\n return presentationColumns(state.schema, child, [has.asks]).get(has.asks) as ColumnMeta | undefined;\n}\n\ntype Words = typeof import('./conditionWords.js');\nlet words: Words | undefined;\nlet arriving: Promise<Words> | undefined;\nconst load = () => (arriving ??= import('./conditionWords.js').then((module) => (words = module)));\n\n/** What a surface says conditions with: the App's words once loaded, else the language as written. */\ninterface ConditionWords {\n speaker: ConditionSpeaker | null;\n sayTails(speaker: ConditionSpeaker | null, column: string, tails: readonly string[]): string;\n sayCondition(speaker: ConditionSpeaker | null, condition: string): string;\n sayFilter(speaker: ConditionSpeaker | null, filter: string): string[];\n sayQuestion(speaker: ConditionSpeaker | null, text: string): string[];\n andWords(said: string[]): string;\n}\n\nconst AS_WRITTEN: Omit<ConditionWords, 'speaker'> = {\n sayTails: (_, column, tails) => written.tails(column, tails),\n sayCondition: (_, condition) => written.condition(condition),\n sayFilter: (_, filter) => written.filter(filter),\n sayQuestion: (_, text) => written.question(text),\n andWords,\n};\n\n/**\n * The words for these texts — the conditions a surface is about to say —\n * with every reference among them named once its record has been read.\n */\nexport function useConditionWords(table: TableMeta | undefined, texts: readonly string[]): ConditionWords {\n // A change of language is a change of words.\n useMessageLanguage();\n const base = appSpeaker(table);\n const asks = !!base && texts.some((text) => text.trim());\n const [, arrived] = useState(0);\n useEffect(() => {\n if (!asks || words) return;\n let live = true;\n void load().then(() => {\n if (live) arrived((n) => n + 1);\n });\n return () => {\n live = false;\n };\n }, [asks]);\n const loaded = base ? words : undefined;\n const wanted =\n base && loaded\n ? [\n ...new Map(\n texts.flatMap((text) =>\n [...loaded.referencedIds(base, text)].flatMap(([column, ids]) =>\n ids.map((id) => [`${column}\\n${id}`, { column, id }] as const),\n ),\n ),\n ).values(),\n ]\n : [];\n const names = useQueries({ queries: wanted.map(({ column, id }) => referenceNameQuery(base?.column(column), id)) });\n if (!base || !loaded) return { speaker: null, ...AS_WRITTEN };\n const known = new Map(wanted.map(({ column, id }, i) => [`${column}\\n${id}`, names[i]?.data ?? undefined]));\n const { sayTails, sayCondition, sayFilter, sayQuestion } = loaded;\n return {\n speaker: { ...base, recordName: (column, id) => known.get(`${column}\\n${id}`) ?? undefined },\n sayTails,\n sayCondition,\n sayFilter,\n sayQuestion,\n andWords,\n };\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { gt as defineMessages } from "./shared-DJZjSi3J.js";
2
2
  import { a as served } from "./shared-sZ8B7ZTw.js";
3
- import { c as say$1 } from "./shared-CpI0Y8M5.js";
3
+ import { c as say$1 } from "./shared-B4jgU8h9.js";
4
4
  // packages/mistri/src/client/copy/forms/publicPlacement.ts
5
5
  /** Receipt recovery is loaded with placed forms, never with ordinary first paint. */
6
6
  var say = defineMessages({
@@ -276,4 +276,4 @@ function publicPlacementOperation(prepared) {
276
276
  }
277
277
  export { PublicPlacementOperation, preparePublicPlacement, publicPlacementOperation, retainedPublicForm };
278
278
 
279
- //# sourceMappingURL=shared-CQL3FGHc.js.map
279
+ //# sourceMappingURL=shared-Csfi3Pti.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-CQL3FGHc.js","names":["sayForm"],"sources":["../../src/client/copy/forms/publicPlacement.ts","../../src/client/publicPlacement.ts"],"sourcesContent":["/** Receipt recovery is loaded with placed forms, never with ordinary first paint. */\nimport { defineMessages } from '@mistriwork/kit/client/messages.js';\n\nexport const say = defineMessages({\n placementUnavailable: 'This form could not be opened. Reload the page and try again.',\n placementUnknown:\n 'We haven’t confirmed whether this was received. Your answers are held here. Check the result before starting another form.',\n placementRestored:\n 'An earlier submission is waiting for confirmation. Check its result before starting another form. Its answers were not saved on this device.',\n placementNotFound: 'No receipt was found yet. Your submission may still be finishing. Check again before starting another form.',\n placementRecoveryUnavailable: 'The result could not be checked. This does not mean the submission failed. Try checking again later.',\n placementRecoveryExpired:\n 'The time to check this result has ended. It may have been received. Contact the form’s owner before submitting another form.',\n placementAlreadyPending: 'An earlier submission is waiting for confirmation. Close this form and open it again to check the result.',\n placementNewForm: 'This form can no longer be sent. Close and reopen it, or reload the page, to start a new form.',\n});\n","/** Loaded only after a named public GET carries placement. Answers never leave\n * memory; recovery metadata cannot choose the destination of a request. */\nimport type { PublicFormPlacement } from '../shared/publicPlacement.js';\nimport type { CommandInputValue } from './commandState.js';\nimport type { loadPublicForm } from './publicForm.js';\nimport type { CommandValues, FormReceipt } from './components/FormSurface.js';\nimport { served } from '@mistriwork/kit/client/addressMount.js';\nimport { say } from './copy/forms/publicPlacement.js';\nimport { say as sayForm } from './copy/forms/components/FormSurface.js';\n\nexport type PreparedPublicForm = NonNullable<Awaited<ReturnType<typeof loadPublicForm>>>;\ntype Phase = 'ready' | 'pending' | 'unknown' | 'applied';\ninterface State {\n phase: Phase;\n receipt?: FormReceipt;\n message?: string;\n}\ninterface RecoveryRecord {\n form: string;\n placement: PublicFormPlacement;\n}\nconst MAX_RECORDS = 8;\nconst operations = new WeakMap<PreparedPublicForm, PublicPlacementOperation>();\nconst retained = new Map<string, PublicPlacementOperation>();\nconst owner = (app: string, form: string) => JSON.stringify([window.location.origin, served('/'), app, form]);\nconst storageKey = (app: string) => `mistri:public-placement:${encodeURIComponent(app)}`;\nconst object = (value: unknown): value is Record<string, unknown> => !!value && typeof value === 'object' && !Array.isArray(value);\nconst exact = (value: Record<string, unknown>, keys: string[]) =>\n Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));\n\nfunction placementShape(value: unknown): value is PublicFormPlacement {\n if (!object(value) || !exact(value, ['version', 'binding', 'submission', 'expiresAt', 'recoverUntil', 'recoveryUrl'])) return false;\n const date = (v: unknown): v is string =>\n typeof v === 'string' && v.length <= 32 && Number.isFinite(Date.parse(v)) && new Date(v).toISOString() === v;\n return (\n value.version === 1 &&\n typeof value.binding === 'string' &&\n value.binding.length > 0 &&\n value.binding.length <= 4096 &&\n /^[A-Za-z0-9_.-]+$/.test(value.binding) &&\n typeof value.submission === 'string' &&\n /^[A-Za-z0-9_-]{16,128}$/.test(value.submission) &&\n date(value.expiresAt) &&\n date(value.recoverUntil) &&\n Date.parse(value.expiresAt) <= Date.parse(value.recoverUntil) &&\n typeof value.recoveryUrl === 'string' &&\n value.recoveryUrl.length <= 2048\n );\n}\n\n/** Only a fresh server response establishes canonical origin. Its exact path\n * must also match this page's independently served mount and named form. */\nfunction trustedRecoveryUrl(placement: PublicFormPlacement, form: string): string {\n try {\n const url = new URL(placement.recoveryUrl);\n if (\n !['https:', 'http:'].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash ||\n url.pathname !== served(`/api/forms/${encodeURIComponent(form)}/receipt`) ||\n url.href !== placement.recoveryUrl\n )\n throw new Error();\n return url.href;\n } catch {\n throw new Error(say('placementUnavailable'));\n }\n}\n\nfunction writeRecords(app: string, records: RecoveryRecord[]): void {\n try {\n if (records.length) sessionStorage.setItem(storageKey(app), JSON.stringify(records.slice(-MAX_RECORDS)));\n else sessionStorage.removeItem(storageKey(app));\n } catch {\n /* Memory still owns this attempt when storage is unavailable. */\n }\n}\n\nfunction readRecords(app: string): RecoveryRecord[] {\n let records: RecoveryRecord[] = [];\n try {\n const raw = sessionStorage.getItem(storageKey(app));\n const value: unknown = raw && raw.length <= 65_536 ? JSON.parse(raw) : [];\n if (Array.isArray(value) && value.length <= MAX_RECORDS) {\n records = value.filter(\n (record): record is RecoveryRecord =>\n object(record) &&\n exact(record, ['form', 'placement']) &&\n typeof record.form === 'string' &&\n record.form.length > 0 &&\n record.form.length <= 200 &&\n placementShape(record.placement) &&\n Date.parse(record.placement.recoverUntil) > Date.now(),\n );\n }\n } catch {\n /* Malformed or disabled storage supplies no authority. */\n }\n writeRecords(app, records);\n return records;\n}\n\nfunction receipt(value: unknown): FormReceipt | undefined {\n if (\n !object(value) ||\n !object(value.receipt) ||\n value.receipt.status !== 'applied' ||\n typeof value.receipt.id !== 'string' ||\n !value.receipt.id ||\n value.receipt.id.length > 256 ||\n (value.reference !== undefined && (typeof value.reference !== 'string' || value.reference.length > 64))\n )\n return;\n return {\n ...(typeof value.reference === 'string' ? { reference: value.reference } : {}),\n receipt: { id: value.receipt.id, status: 'applied' },\n };\n}\n\nexport class PublicPlacementOperation {\n private state: State;\n private listeners = new Set<() => void>();\n private input?: CommandInputValue;\n private uncertain: boolean;\n private readonly key: string;\n private readonly executeUrl: string;\n readonly placement: PublicFormPlacement;\n values?: CommandValues;\n\n constructor(\n readonly prepared: PreparedPublicForm,\n restored?: PublicFormPlacement,\n ) {\n if (!placementShape(prepared.placement) || prepared.form.save) throw new Error(say('placementUnavailable'));\n const expected = trustedRecoveryUrl(prepared.placement, prepared.form.id);\n // A stored URL is compared to fresh trusted configuration, never to its own origin.\n if (restored && (!placementShape(restored) || restored.recoveryUrl !== expected)) throw new Error(say('placementRecoveryUnavailable'));\n this.placement = Object.freeze({ ...(restored ?? prepared.placement), recoveryUrl: expected });\n this.prepared = { ...prepared, placement: this.placement };\n this.key = owner(prepared.app.id, prepared.form.id);\n this.executeUrl = new URL(served(`/api/forms/${encodeURIComponent(prepared.form.id)}`), window.location.origin).href;\n this.uncertain = !!restored;\n this.state = restored ? { phase: 'unknown', message: say('placementRestored') } : { phase: 'ready' };\n if (restored) retained.set(this.key, this);\n }\n\n snapshot = (): State => this.state;\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n private update(state: State): void {\n this.state = state;\n for (const listener of this.listeners) listener();\n }\n private remember(): void {\n retained.set(this.key, this);\n const records = readRecords(this.prepared.app.id).filter((record) => record.form !== this.prepared.form.id);\n records.push({ form: this.prepared.form.id, placement: this.placement });\n writeRecords(this.prepared.app.id, records);\n }\n private resolve(): void {\n if (retained.get(this.key) === this) retained.delete(this.key);\n writeRecords(\n this.prepared.app.id,\n readRecords(this.prepared.app.id).filter((record) => record.placement.submission !== this.placement.submission),\n );\n }\n private applied(found: FormReceipt): FormReceipt {\n this.resolve();\n this.input = undefined;\n this.values = undefined;\n this.update({ phase: 'applied', receipt: found });\n return found;\n }\n capture = (values: CommandValues): void => {\n if (this.state.phase === 'ready') this.values = structuredClone(values);\n };\n canRetry = (): boolean => !!this.input && Date.now() < Date.parse(this.placement.expiresAt);\n canRecover = (): boolean => Date.now() < Date.parse(this.placement.recoverUntil);\n\n send = async (_submission: string, input: CommandInputValue): Promise<FormReceipt> => {\n if (this.state.phase !== 'ready') throw new Error(say('placementUnknown'));\n if (retained.has(this.key) && retained.get(this.key) !== this) throw new Error(say('placementAlreadyPending'));\n if (Date.now() >= Date.parse(this.placement.expiresAt)) throw new Error(say('placementNewForm'));\n this.input = structuredClone(input);\n return this.execute();\n };\n retry = async (): Promise<void> => {\n if (this.state.phase !== 'unknown' || !this.canRetry()) return;\n try {\n await this.execute();\n } catch {\n /* The operation's state says why. */\n }\n };\n private async execute(): Promise<FormReceipt> {\n this.remember();\n this.update({ ...this.state, phase: 'pending' });\n try {\n const res = await fetch(this.executeUrl, {\n method: 'POST',\n credentials: 'omit',\n redirect: 'error',\n cache: 'no-store',\n signal: AbortSignal.timeout(30_000),\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n input: this.input,\n submission: this.placement.submission,\n placement: { version: 1, binding: this.placement.binding },\n }),\n });\n const data: unknown = await res.json();\n const found = res.ok ? receipt(data) : undefined;\n if (found) return this.applied(found);\n // A later refusal cannot settle an earlier unanswered execution. Only a\n // first, explicit not-applied answer may reopen editing under this key.\n if (!this.uncertain && !res.ok && object(data) && data.outcome === 'not_applied') {\n this.resolve();\n this.input = undefined;\n this.update({ phase: 'ready' });\n const placementRefusal = typeof data.code === 'string' && data.code.startsWith('placement_');\n throw Object.assign(\n new Error(placementRefusal ? say('placementNewForm') : String(data.message || data.error || sayForm('refusedUnavailable'))),\n {\n status: res.status,\n code: data.code,\n field: data.field,\n retryAfter: Number(res.headers.get('retry-after') ?? data.retryAfter),\n definitive: true,\n },\n );\n }\n throw new Error();\n } catch (error) {\n if (object(error) && error.definitive === true) throw error;\n this.uncertain = true;\n this.update({ phase: 'unknown', message: say('placementUnknown') });\n throw new Error(say('placementUnknown'));\n }\n }\n\n check = async (): Promise<void> => {\n if (this.state.phase !== 'unknown') return;\n if (!this.canRecover()) {\n this.update({ phase: 'unknown', message: say('placementRecoveryExpired') });\n return;\n }\n this.update({ ...this.state, phase: 'pending' });\n try {\n const res = await fetch(this.placement.recoveryUrl, {\n method: 'POST',\n credentials: 'omit',\n redirect: 'error',\n cache: 'no-store',\n signal: AbortSignal.timeout(30_000),\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ submission: this.placement.submission, placement: { version: 1, binding: this.placement.binding } }),\n });\n const data: unknown = await res.json();\n const found = res.ok ? receipt(data) : undefined;\n if (found) {\n this.applied(found);\n return;\n }\n this.update({\n phase: 'unknown',\n message: res.ok && object(data) && data.status === 'not_found' ? say('placementNotFound') : say('placementRecoveryUnavailable'),\n });\n } catch {\n this.update({ phase: 'unknown', message: say('placementRecoveryUnavailable') });\n }\n };\n}\n\n/** Memory-held attempts are returned before another GET can rotate their binding. */\nexport function retainedPublicForm(form: string, app?: string): PreparedPublicForm | undefined {\n return app ? retained.get(owner(app, form))?.prepared : undefined;\n}\n\nexport function preparePublicPlacement(prepared: PreparedPublicForm): PreparedPublicForm {\n const existing = retainedPublicForm(prepared.form.id, prepared.app.id);\n if (existing) return existing;\n if (!operations.has(prepared)) {\n const record = readRecords(prepared.app.id).find((record) => record.form === prepared.form.id);\n const operation = new PublicPlacementOperation(prepared, record?.placement);\n operations.set(prepared, operation);\n operations.set(operation.prepared, operation);\n }\n return operations.get(prepared)!.prepared;\n}\n\n/** Both public entry points draw the same retained operation, without owning it. */\nexport function publicPlacementOperation(prepared: PreparedPublicForm): PublicPlacementOperation {\n return operations.get(preparePublicPlacement(prepared))!;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-Csfi3Pti.js","names":["sayForm"],"sources":["../../src/client/copy/forms/publicPlacement.ts","../../src/client/publicPlacement.ts"],"sourcesContent":["/** Receipt recovery is loaded with placed forms, never with ordinary first paint. */\nimport { defineMessages } from '@mistriwork/kit/client/messages.js';\n\nexport const say = defineMessages({\n placementUnavailable: 'This form could not be opened. Reload the page and try again.',\n placementUnknown:\n 'We haven’t confirmed whether this was received. Your answers are held here. Check the result before starting another form.',\n placementRestored:\n 'An earlier submission is waiting for confirmation. Check its result before starting another form. Its answers were not saved on this device.',\n placementNotFound: 'No receipt was found yet. Your submission may still be finishing. Check again before starting another form.',\n placementRecoveryUnavailable: 'The result could not be checked. This does not mean the submission failed. Try checking again later.',\n placementRecoveryExpired:\n 'The time to check this result has ended. It may have been received. Contact the form’s owner before submitting another form.',\n placementAlreadyPending: 'An earlier submission is waiting for confirmation. Close this form and open it again to check the result.',\n placementNewForm: 'This form can no longer be sent. Close and reopen it, or reload the page, to start a new form.',\n});\n","/** Loaded only after a named public GET carries placement. Answers never leave\n * memory; recovery metadata cannot choose the destination of a request. */\nimport type { PublicFormPlacement } from '../shared/publicPlacement.js';\nimport type { CommandInputValue } from './commandState.js';\nimport type { loadPublicForm } from './publicForm.js';\nimport type { CommandValues, FormReceipt } from './components/FormSurface.js';\nimport { served } from '@mistriwork/kit/client/addressMount.js';\nimport { say } from './copy/forms/publicPlacement.js';\nimport { say as sayForm } from './copy/forms/components/FormSurface.js';\n\nexport type PreparedPublicForm = NonNullable<Awaited<ReturnType<typeof loadPublicForm>>>;\ntype Phase = 'ready' | 'pending' | 'unknown' | 'applied';\ninterface State {\n phase: Phase;\n receipt?: FormReceipt;\n message?: string;\n}\ninterface RecoveryRecord {\n form: string;\n placement: PublicFormPlacement;\n}\nconst MAX_RECORDS = 8;\nconst operations = new WeakMap<PreparedPublicForm, PublicPlacementOperation>();\nconst retained = new Map<string, PublicPlacementOperation>();\nconst owner = (app: string, form: string) => JSON.stringify([window.location.origin, served('/'), app, form]);\nconst storageKey = (app: string) => `mistri:public-placement:${encodeURIComponent(app)}`;\nconst object = (value: unknown): value is Record<string, unknown> => !!value && typeof value === 'object' && !Array.isArray(value);\nconst exact = (value: Record<string, unknown>, keys: string[]) =>\n Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));\n\nfunction placementShape(value: unknown): value is PublicFormPlacement {\n if (!object(value) || !exact(value, ['version', 'binding', 'submission', 'expiresAt', 'recoverUntil', 'recoveryUrl'])) return false;\n const date = (v: unknown): v is string =>\n typeof v === 'string' && v.length <= 32 && Number.isFinite(Date.parse(v)) && new Date(v).toISOString() === v;\n return (\n value.version === 1 &&\n typeof value.binding === 'string' &&\n value.binding.length > 0 &&\n value.binding.length <= 4096 &&\n /^[A-Za-z0-9_.-]+$/.test(value.binding) &&\n typeof value.submission === 'string' &&\n /^[A-Za-z0-9_-]{16,128}$/.test(value.submission) &&\n date(value.expiresAt) &&\n date(value.recoverUntil) &&\n Date.parse(value.expiresAt) <= Date.parse(value.recoverUntil) &&\n typeof value.recoveryUrl === 'string' &&\n value.recoveryUrl.length <= 2048\n );\n}\n\n/** Only a fresh server response establishes canonical origin. Its exact path\n * must also match this page's independently served mount and named form. */\nfunction trustedRecoveryUrl(placement: PublicFormPlacement, form: string): string {\n try {\n const url = new URL(placement.recoveryUrl);\n if (\n !['https:', 'http:'].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash ||\n url.pathname !== served(`/api/forms/${encodeURIComponent(form)}/receipt`) ||\n url.href !== placement.recoveryUrl\n )\n throw new Error();\n return url.href;\n } catch {\n throw new Error(say('placementUnavailable'));\n }\n}\n\nfunction writeRecords(app: string, records: RecoveryRecord[]): void {\n try {\n if (records.length) sessionStorage.setItem(storageKey(app), JSON.stringify(records.slice(-MAX_RECORDS)));\n else sessionStorage.removeItem(storageKey(app));\n } catch {\n /* Memory still owns this attempt when storage is unavailable. */\n }\n}\n\nfunction readRecords(app: string): RecoveryRecord[] {\n let records: RecoveryRecord[] = [];\n try {\n const raw = sessionStorage.getItem(storageKey(app));\n const value: unknown = raw && raw.length <= 65_536 ? JSON.parse(raw) : [];\n if (Array.isArray(value) && value.length <= MAX_RECORDS) {\n records = value.filter(\n (record): record is RecoveryRecord =>\n object(record) &&\n exact(record, ['form', 'placement']) &&\n typeof record.form === 'string' &&\n record.form.length > 0 &&\n record.form.length <= 200 &&\n placementShape(record.placement) &&\n Date.parse(record.placement.recoverUntil) > Date.now(),\n );\n }\n } catch {\n /* Malformed or disabled storage supplies no authority. */\n }\n writeRecords(app, records);\n return records;\n}\n\nfunction receipt(value: unknown): FormReceipt | undefined {\n if (\n !object(value) ||\n !object(value.receipt) ||\n value.receipt.status !== 'applied' ||\n typeof value.receipt.id !== 'string' ||\n !value.receipt.id ||\n value.receipt.id.length > 256 ||\n (value.reference !== undefined && (typeof value.reference !== 'string' || value.reference.length > 64))\n )\n return;\n return {\n ...(typeof value.reference === 'string' ? { reference: value.reference } : {}),\n receipt: { id: value.receipt.id, status: 'applied' },\n };\n}\n\nexport class PublicPlacementOperation {\n private state: State;\n private listeners = new Set<() => void>();\n private input?: CommandInputValue;\n private uncertain: boolean;\n private readonly key: string;\n private readonly executeUrl: string;\n readonly placement: PublicFormPlacement;\n values?: CommandValues;\n\n constructor(\n readonly prepared: PreparedPublicForm,\n restored?: PublicFormPlacement,\n ) {\n if (!placementShape(prepared.placement) || prepared.form.save) throw new Error(say('placementUnavailable'));\n const expected = trustedRecoveryUrl(prepared.placement, prepared.form.id);\n // A stored URL is compared to fresh trusted configuration, never to its own origin.\n if (restored && (!placementShape(restored) || restored.recoveryUrl !== expected)) throw new Error(say('placementRecoveryUnavailable'));\n this.placement = Object.freeze({ ...(restored ?? prepared.placement), recoveryUrl: expected });\n this.prepared = { ...prepared, placement: this.placement };\n this.key = owner(prepared.app.id, prepared.form.id);\n this.executeUrl = new URL(served(`/api/forms/${encodeURIComponent(prepared.form.id)}`), window.location.origin).href;\n this.uncertain = !!restored;\n this.state = restored ? { phase: 'unknown', message: say('placementRestored') } : { phase: 'ready' };\n if (restored) retained.set(this.key, this);\n }\n\n snapshot = (): State => this.state;\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n private update(state: State): void {\n this.state = state;\n for (const listener of this.listeners) listener();\n }\n private remember(): void {\n retained.set(this.key, this);\n const records = readRecords(this.prepared.app.id).filter((record) => record.form !== this.prepared.form.id);\n records.push({ form: this.prepared.form.id, placement: this.placement });\n writeRecords(this.prepared.app.id, records);\n }\n private resolve(): void {\n if (retained.get(this.key) === this) retained.delete(this.key);\n writeRecords(\n this.prepared.app.id,\n readRecords(this.prepared.app.id).filter((record) => record.placement.submission !== this.placement.submission),\n );\n }\n private applied(found: FormReceipt): FormReceipt {\n this.resolve();\n this.input = undefined;\n this.values = undefined;\n this.update({ phase: 'applied', receipt: found });\n return found;\n }\n capture = (values: CommandValues): void => {\n if (this.state.phase === 'ready') this.values = structuredClone(values);\n };\n canRetry = (): boolean => !!this.input && Date.now() < Date.parse(this.placement.expiresAt);\n canRecover = (): boolean => Date.now() < Date.parse(this.placement.recoverUntil);\n\n send = async (_submission: string, input: CommandInputValue): Promise<FormReceipt> => {\n if (this.state.phase !== 'ready') throw new Error(say('placementUnknown'));\n if (retained.has(this.key) && retained.get(this.key) !== this) throw new Error(say('placementAlreadyPending'));\n if (Date.now() >= Date.parse(this.placement.expiresAt)) throw new Error(say('placementNewForm'));\n this.input = structuredClone(input);\n return this.execute();\n };\n retry = async (): Promise<void> => {\n if (this.state.phase !== 'unknown' || !this.canRetry()) return;\n try {\n await this.execute();\n } catch {\n /* The operation's state says why. */\n }\n };\n private async execute(): Promise<FormReceipt> {\n this.remember();\n this.update({ ...this.state, phase: 'pending' });\n try {\n const res = await fetch(this.executeUrl, {\n method: 'POST',\n credentials: 'omit',\n redirect: 'error',\n cache: 'no-store',\n signal: AbortSignal.timeout(30_000),\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n input: this.input,\n submission: this.placement.submission,\n placement: { version: 1, binding: this.placement.binding },\n }),\n });\n const data: unknown = await res.json();\n const found = res.ok ? receipt(data) : undefined;\n if (found) return this.applied(found);\n // A later refusal cannot settle an earlier unanswered execution. Only a\n // first, explicit not-applied answer may reopen editing under this key.\n if (!this.uncertain && !res.ok && object(data) && data.outcome === 'not_applied') {\n this.resolve();\n this.input = undefined;\n this.update({ phase: 'ready' });\n const placementRefusal = typeof data.code === 'string' && data.code.startsWith('placement_');\n throw Object.assign(\n new Error(placementRefusal ? say('placementNewForm') : String(data.message || data.error || sayForm('refusedUnavailable'))),\n {\n status: res.status,\n code: data.code,\n field: data.field,\n retryAfter: Number(res.headers.get('retry-after') ?? data.retryAfter),\n definitive: true,\n },\n );\n }\n throw new Error();\n } catch (error) {\n if (object(error) && error.definitive === true) throw error;\n this.uncertain = true;\n this.update({ phase: 'unknown', message: say('placementUnknown') });\n throw new Error(say('placementUnknown'));\n }\n }\n\n check = async (): Promise<void> => {\n if (this.state.phase !== 'unknown') return;\n if (!this.canRecover()) {\n this.update({ phase: 'unknown', message: say('placementRecoveryExpired') });\n return;\n }\n this.update({ ...this.state, phase: 'pending' });\n try {\n const res = await fetch(this.placement.recoveryUrl, {\n method: 'POST',\n credentials: 'omit',\n redirect: 'error',\n cache: 'no-store',\n signal: AbortSignal.timeout(30_000),\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ submission: this.placement.submission, placement: { version: 1, binding: this.placement.binding } }),\n });\n const data: unknown = await res.json();\n const found = res.ok ? receipt(data) : undefined;\n if (found) {\n this.applied(found);\n return;\n }\n this.update({\n phase: 'unknown',\n message: res.ok && object(data) && data.status === 'not_found' ? say('placementNotFound') : say('placementRecoveryUnavailable'),\n });\n } catch {\n this.update({ phase: 'unknown', message: say('placementRecoveryUnavailable') });\n }\n };\n}\n\n/** Memory-held attempts are returned before another GET can rotate their binding. */\nexport function retainedPublicForm(form: string, app?: string): PreparedPublicForm | undefined {\n return app ? retained.get(owner(app, form))?.prepared : undefined;\n}\n\nexport function preparePublicPlacement(prepared: PreparedPublicForm): PreparedPublicForm {\n const existing = retainedPublicForm(prepared.form.id, prepared.app.id);\n if (existing) return existing;\n if (!operations.has(prepared)) {\n const record = readRecords(prepared.app.id).find((record) => record.form === prepared.form.id);\n const operation = new PublicPlacementOperation(prepared, record?.placement);\n operations.set(prepared, operation);\n operations.set(operation.prepared, operation);\n }\n return operations.get(prepared)!.prepared;\n}\n\n/** Both public entry points draw the same retained operation, without owning it. */\nexport function publicPlacementOperation(prepared: PreparedPublicForm): PublicPlacementOperation {\n return operations.get(preparePublicPlacement(prepared))!;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { M as saveJson, h as loadJson } from "./shared-DPAVoWY0.js";
1
+ import { M as saveJson, h as loadJson } from "./shared-BoLbc2WD.js";
2
2
  // packages/kit/src/client/relatedRows.ts
3
3
  var KEY = "mistri.related-rows";
4
4
  var RELATED_SIZES = [
@@ -29,4 +29,4 @@ function subscribeRelatedRows(listener) {
29
29
  }
30
30
  export { subscribeRelatedRows as i, relatedRows as n, setRelatedRows as r, RELATED_SIZES as t };
31
31
 
32
- //# sourceMappingURL=shared-BKNRbm-V.js.map
32
+ //# sourceMappingURL=shared-CuZt26rm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-BKNRbm-V.js","names":[],"sources":["../../../kit/src/client/relatedRows.ts"],"sourcesContent":["import { loadJson, saveJson } from './core.js';\n\nconst KEY = 'mistri.related-rows';\nexport const RELATED_SIZES = [5, 10, 20, 50];\nlet current: number | undefined;\nconst listeners = new Set<() => void>();\n\nexport function relatedRows(): number {\n if (current === undefined) {\n const stored = Number(loadJson(KEY, 10));\n current = RELATED_SIZES.includes(stored) ? stored : 10;\n }\n return current;\n}\n\nexport function setRelatedRows(value: number): void {\n current = RELATED_SIZES.includes(value) ? value : 10;\n saveJson(KEY, current);\n for (const listener of listeners) listener();\n}\n\nexport function subscribeRelatedRows(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n"],"mappings":";;AAEA,IAAM,MAAM;AACZ,IAAa,gBAAgB;CAAC;CAAG;CAAI;CAAI;AAAE;AAC3C,IAAI;AACJ,IAAM,4BAAY,IAAI,IAAgB;AAEtC,SAAgB,cAAsB;CACpC,IAAI,YAAY,KAAA,GAAW;EACzB,MAAM,SAAS,OAAO,SAAS,KAAK,EAAE,CAAC;EACvC,UAAU,cAAc,SAAS,MAAM,IAAI,SAAS;CACtD;CACA,OAAO;AACT;AAEA,SAAgB,eAAe,OAAqB;CAClD,UAAU,cAAc,SAAS,KAAK,IAAI,QAAQ;CAClD,SAAS,KAAK,OAAO;CACrB,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;AAEA,SAAgB,qBAAqB,UAAkC;CACrE,UAAU,IAAI,QAAQ;CACtB,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF"}
1
+ {"version":3,"file":"shared-CuZt26rm.js","names":[],"sources":["../../../kit/src/client/relatedRows.ts"],"sourcesContent":["import { loadJson, saveJson } from './core.js';\n\nconst KEY = 'mistri.related-rows';\nexport const RELATED_SIZES = [5, 10, 20, 50];\nlet current: number | undefined;\nconst listeners = new Set<() => void>();\n\nexport function relatedRows(): number {\n if (current === undefined) {\n const stored = Number(loadJson(KEY, 10));\n current = RELATED_SIZES.includes(stored) ? stored : 10;\n }\n return current;\n}\n\nexport function setRelatedRows(value: number): void {\n current = RELATED_SIZES.includes(value) ? value : 10;\n saveJson(KEY, current);\n for (const listener of listeners) listener();\n}\n\nexport function subscribeRelatedRows(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n"],"mappings":";;AAEA,IAAM,MAAM;AACZ,IAAa,gBAAgB;CAAC;CAAG;CAAI;CAAI;AAAE;AAC3C,IAAI;AACJ,IAAM,4BAAY,IAAI,IAAgB;AAEtC,SAAgB,cAAsB;CACpC,IAAI,YAAY,KAAA,GAAW;EACzB,MAAM,SAAS,OAAO,SAAS,KAAK,EAAE,CAAC;EACvC,UAAU,cAAc,SAAS,MAAM,IAAI,SAAS;CACtD;CACA,OAAO;AACT;AAEA,SAAgB,eAAe,OAAqB;CAClD,UAAU,cAAc,SAAS,KAAK,IAAI,QAAQ;CAClD,SAAS,KAAK,OAAO;CACrB,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;AAEA,SAAgB,qBAAqB,UAAkC;CACrE,UAAU,IAAI,QAAQ;CACtB,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { J as maskQuoted, X as MAX_LIMIT, w as captureSessionIdentity, x as fetchServer } from "./shared-poc11xxo.js";
2
2
  import { gt as defineMessages, kt as portalRoot } from "./shared-DJZjSi3J.js";
3
- import { B as toast, L as state, M as saveJson, X as el, ct as scopeApiRequest, d as findTable, h as loadJson } from "./shared-DPAVoWY0.js";
3
+ import { B as toast, L as state, M as saveJson, X as el, ct as scopeApiRequest, d as findTable, h as loadJson } from "./shared-BoLbc2WD.js";
4
4
  import "./shared-DOvMIC4W.js";
5
5
  // packages/engine/src/shared/csv.ts
6
6
  var NEEDS_QUOTES = /[",\r\n]/;
@@ -235,4 +235,4 @@ async function streamExport(kind, table, q, columns) {
235
235
  }
236
236
  export { rememberColumns as a, selectionFor as c, orderColumns as i, streamExport as l, declaredColumns as n, sameList as o, download as r, saveExport as s, EXPORT_MAX_ROWS as t, withColumns as u };
237
237
 
238
- //# sourceMappingURL=shared-Guq3upiR.js.map
238
+ //# sourceMappingURL=shared-Cws_z-TS.js.map