@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,8 +1,8 @@
1
1
  import { ht as Icon, it as useQuery } from "./shared-poc11xxo.js";
2
2
  import { F as primaryKey, Mt as shellById, gt as defineMessages, yn as keepPreviousData } from "./shared-DJZjSi3J.js";
3
- import { _ as openRecord, d as findTable, i as api, j as rowLabel, m as labelPath, p as keyOf, w as quote } from "./shared-DPAVoWY0.js";
3
+ import { _ as openRecord, d as findTable, i as api, j as rowLabel, m as labelPath, p as keyOf, w as quote } from "./shared-BoLbc2WD.js";
4
4
  import { c as isListRoom, f as viewRoom, n as appRooms } from "./shared-DpVb48sM.js";
5
- import { t as activeApp } from "./shared-DYfX7NSM.js";
5
+ import { t as activeApp } from "./shared-DGaQZXnw.js";
6
6
  import { useEffect, useId, useMemo, useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  // packages/kit/src/client/components/recordSearch.ts
@@ -244,4 +244,4 @@ function FindSection({ section }) {
244
244
  }
245
245
  export { FindSection };
246
246
 
247
- //# sourceMappingURL=shared-Be1HpDOB.js.map
247
+ //# sourceMappingURL=shared-Dl1_Nx--.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-Be1HpDOB.js","names":[],"sources":["../../../kit/src/client/components/recordSearch.ts","../../src/client/copy/dashboards/components/DashboardFind.ts","../../src/client/components/DashboardFind.tsx"],"sourcesContent":["/**\n * Finding records by their names: the one engine behind ⌘K's record hits\n * and a dashboard's find box. Each table is asked through `/api/run` with\n * the text filter its list's search asks (`<label> contains \"…\"`), so the\n * server reads it as it reads every row for this reader — their grants,\n * their row access — and asks the App's search engine where the resource\n * declares one (server/app/appSearchRuntime.ts). A table is offered only\n * where the reader is served a list of it, so a list kept from them by its\n * `roles` keeps its records out of their search too.\n */\nimport { api, findTable, keyOf, labelPath, openRecord, primaryKey, quote, rowLabel } from '../core.js';\nimport type { TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport { appRooms, isListRoom, viewRoom } from './appRooms.js';\n\ntype ServedApp = Parameters<typeof viewRoom>[0];\ntype ListRoom = Extract<NonNullable<ReturnType<typeof viewRoom>>, { table: string }>;\n\n/** A table whose records can be found: where their names live, and the list that serves it. */\nexport interface Findable {\n table: TableMeta;\n label: string;\n room: ListRoom;\n}\n\n/** One record found: its name, a line of its list, and the way to open it. */\nexport interface Found {\n label: string;\n line?: string;\n key: Record<string, unknown>;\n open: () => void;\n}\n\n/** The list rooms the reader is served: the menu's own (⌘K's), or every list, those only a dashboard embeds too. */\nfunction listRooms(app: ServedApp, every: boolean): ListRoom[] {\n const rooms = every ? Object.keys(app.views).map((key) => viewRoom(app, key)) : appRooms(app);\n return rooms.flatMap((room): ListRoom[] => (room && isListRoom(room) && !room.error ? [room as ListRoom] : []));\n}\n\n/** The table behind a list room, findable when it has a key and a name to search by. */\nfunction findableOf(room: ListRoom): Findable | undefined {\n const table = findTable(room.table) as TableMeta | undefined;\n const label = table ? ((labelPath(table) as string | undefined) ?? primaryKey(table)[0]) : undefined;\n return table && label && (primaryKey(table) as string[]).length ? { table, label, room } : undefined;\n}\n\n/** The tables whose records the reader may find: those of `tables` (ids), or of the App's first lists. */\nexport function findableTables(app: ServedApp, tables?: readonly string[], most = 6): Findable[] {\n const seen = new Set<string>();\n const out: Findable[] = [];\n const rooms = listRooms(app, Boolean(tables));\n for (const room of tables ? tables.flatMap((id) => rooms.filter((each) => each.table === id).slice(0, 1)) : rooms) {\n if (seen.has(room.table)) continue;\n seen.add(room.table);\n const found = findableOf(room);\n if (found) out.push(found);\n if (out.length >= most) break;\n }\n return out;\n}\n\n/**\n * The line a hit says under its name: what its list says first under a\n * card's title — a cards description, else the first of its lines — or its\n * first shown column that is not the name. A local column only, read with\n * the name in the same question.\n */\nexport function lineOf({ table, label, room }: Findable): string | undefined {\n const presentation = room.presentation as { description?: string; lines?: readonly string[] } | undefined;\n const keys = primaryKey(table) as string[];\n const candidates = [presentation?.description, presentation?.lines?.[0], ...(room.show ?? [])];\n return candidates.find(\n (name): name is string =>\n typeof name === 'string' &&\n !name.includes('.') &&\n name !== label &&\n !keys.includes(name) &&\n table.columns.some((column) => column.name === name),\n );\n}\n\n/** The records of one table whose names hold `term`, at most `limit`, by name. A refusal answers nothing, never the whole search's failure. */\nexport async function findRecords(findable: Findable, term: string, limit: number, line?: string): Promise<Found[]> {\n const { table, label } = findable;\n const show = [...new Set([label, ...(primaryKey(table) as string[]), ...(line ? [line] : [])])];\n try {\n const data = await api('/api/run', {\n q: `${table.id} ${label} contains ${quote(term)} show ${show.join(', ')} sort ${label}`,\n limit,\n offset: 0,\n count: 'none',\n });\n return ((data.rows ?? []) as Array<Record<string, unknown>>).flatMap((row): Found[] => {\n const key = keyOf(table, row) as Record<string, unknown> | null;\n if (!key) return [];\n const name = rowLabel(table, row) as string;\n const said = line && row[line] !== null && row[line] !== undefined && row[line] !== '' ? String(row[line]) : undefined;\n return [{ label: name, ...(said ? { line: said } : {}), key, open: () => void openRecord(table.id, key, name) }];\n });\n } catch {\n return [];\n }\n}\n","/** What DashboardFind says, in the dashboards 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 searchPlaceholder: 'Search…',\n nothingMatches: 'Nothing matches “{term}”.',\n results: { one: '{count} result', other: '{count} results' },\n});\n","/**\n * A dashboard's find box (`find()`): one big field over several resources'\n * records, the hits grouped by resource, each its record's name and one line\n * of its list, opening the record at its own address. A portal's home is the\n * place for it: an employee who wants an article or a laptop types, and\n * does not first have to know which list holds it.\n *\n * The engine is ⌘K's (recordSearch.ts), so what it finds is what this\n * reader may read, asked of the App's search engine where a resource has\n * one. A typed pause asks; an answer to older text is never shown as this\n * text's. The box is a combobox: the arrows walk the hits across the groups\n * and Enter opens the lit one, so the pointer is never needed.\n * Loaded the first time a dashboard holds one (onDemand.tsx).\n */\nimport { useEffect, useId, useMemo, useState, type KeyboardEvent } from 'react';\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport { activeApp } from '../appState.js';\nimport type { Section } from '@mistriwork/kit/client/components/dashboardModel.js';\nimport { findRecords, findableTables, lineOf, type Found } from '@mistriwork/kit/client/components/recordSearch.js';\nimport { Icon } from '@mistriwork/kit/client/components/Icon.js';\nimport './DashboardFind.css';\nimport { say } from '../copy/dashboards/components/DashboardFind.js';\nimport { shellById } from '@mistriwork/kit/client/shellHost.js';\n\n/** Hits per resource: a box answers with a shortlist, and its list is one click away for the rest. */\nconst PER_RESOURCE = 5;\n/* The pause after a keystroke before the question goes, so a word typed is one request, not one per letter. */\nconst PAUSE = 200;\n\nexport function FindSection({ section }: { section: Section }) {\n const app = activeApp();\n const searched = section.searched;\n const tables = useMemo(() => (app ? findableTables(app, searched ?? []) : []), [app, searched]);\n const [text, setText] = useState('');\n const [asked, setAsked] = useState('');\n const [lit, setLit] = useState(0);\n const id = useId();\n const term = text.trim();\n\n useEffect(() => {\n const timer = setTimeout(() => setAsked(term), PAUSE);\n return () => clearTimeout(timer);\n }, [term]);\n\n const answer = useQuery({\n queryKey: ['find', tables.map((each) => each.table.id), asked],\n enabled: asked.length >= 2 && tables.length > 0,\n placeholderData: keepPreviousData,\n queryFn: async () =>\n Promise.all(\n tables.map(async (findable) => ({\n findable,\n hits: await findRecords(findable, asked, PER_RESOURCE, lineOf(findable)),\n })),\n ),\n });\n /* Only an answer to what the box says now is drawn: one for older text would open a record nobody asked for. */\n const current = term.length >= 2 && answer.data && asked === term && !answer.isPlaceholderData ? answer.data : undefined;\n const groups = (current ?? []).filter((group) => group.hits.length);\n const hits: Found[] = groups.flatMap((group) => group.hits);\n const at = hits.length ? Math.min(lit, hits.length - 1) : -1;\n useEffect(() => {\n setLit(0);\n }, [asked]);\n useEffect(() => {\n if (at >= 0) shellById(`${id}-hit-${at}`)?.scrollIntoView?.({ block: 'nearest' });\n }, [at, id]);\n\n const open = Boolean(current);\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'ArrowDown' && hits.length) {\n event.preventDefault();\n setLit((at + 1) % hits.length);\n } else if (event.key === 'ArrowUp' && hits.length) {\n event.preventDefault();\n setLit((at - 1 + hits.length) % hits.length);\n } else if (event.key === 'Enter' && at >= 0) {\n event.preventDefault();\n hits[at].open();\n } else if (event.key === 'Escape' && text) {\n event.preventDefault();\n setText('');\n }\n };\n\n let index = -1;\n return (\n <div className=\"find\">\n <div className=\"find-box\">\n <Icon name=\"magnifying-glass\" size={20} />\n <input\n type=\"search\"\n role=\"combobox\"\n aria-label={section.title ?? section.placeholder ?? say('searchPlaceholder')}\n aria-expanded={open}\n aria-controls={`${id}-hits`}\n aria-autocomplete=\"list\"\n aria-activedescendant={at >= 0 ? `${id}-hit-${at}` : undefined}\n placeholder={section.placeholder ?? say('searchPlaceholder')}\n value={text}\n autoComplete=\"off\"\n onChange={(event) => setText(event.target.value)}\n onKeyDown={onKeyDown}\n />\n </div>\n <p className=\"visually-hidden\" aria-live=\"polite\">\n {current ? say('results', { count: hits.length }) : ''}\n </p>\n {open && !hits.length && <p className=\"find-empty\">{say('nothingMatches', { term })}</p>}\n <div\n id={`${id}-hits`}\n role=\"listbox\"\n className=\"find-hits\"\n hidden={!hits.length}\n aria-label={section.title ?? say('searchPlaceholder')}\n >\n {groups.map(({ findable, hits: found }) => (\n <div key={findable.table.id} role=\"group\" aria-labelledby={`${id}-${findable.table.id}`} className=\"find-group\">\n <p id={`${id}-${findable.table.id}`} className=\"find-group-title\" role=\"presentation\">\n {findable.room.label}\n </p>\n {found.map((hit) => {\n index += 1;\n const mine = index;\n return (\n <div\n key={JSON.stringify(hit.key)}\n id={`${id}-hit-${mine}`}\n role=\"option\"\n aria-selected={mine === at}\n className=\"find-hit\"\n onMouseMove={() => setLit(mine)}\n onMouseDown={(event) => event.preventDefault()}\n onClick={hit.open}\n >\n <span className=\"find-hit-name\">{hit.label}</span>\n {hit.line && <span className=\"find-hit-line\">{hit.line}</span>}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-Dl1_Nx--.js","names":[],"sources":["../../../kit/src/client/components/recordSearch.ts","../../src/client/copy/dashboards/components/DashboardFind.ts","../../src/client/components/DashboardFind.tsx"],"sourcesContent":["/**\n * Finding records by their names: the one engine behind ⌘K's record hits\n * and a dashboard's find box. Each table is asked through `/api/run` with\n * the text filter its list's search asks (`<label> contains \"…\"`), so the\n * server reads it as it reads every row for this reader — their grants,\n * their row access — and asks the App's search engine where the resource\n * declares one (server/app/appSearchRuntime.ts). A table is offered only\n * where the reader is served a list of it, so a list kept from them by its\n * `roles` keeps its records out of their search too.\n */\nimport { api, findTable, keyOf, labelPath, openRecord, primaryKey, quote, rowLabel } from '../core.js';\nimport type { TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport { appRooms, isListRoom, viewRoom } from './appRooms.js';\n\ntype ServedApp = Parameters<typeof viewRoom>[0];\ntype ListRoom = Extract<NonNullable<ReturnType<typeof viewRoom>>, { table: string }>;\n\n/** A table whose records can be found: where their names live, and the list that serves it. */\nexport interface Findable {\n table: TableMeta;\n label: string;\n room: ListRoom;\n}\n\n/** One record found: its name, a line of its list, and the way to open it. */\nexport interface Found {\n label: string;\n line?: string;\n key: Record<string, unknown>;\n open: () => void;\n}\n\n/** The list rooms the reader is served: the menu's own (⌘K's), or every list, those only a dashboard embeds too. */\nfunction listRooms(app: ServedApp, every: boolean): ListRoom[] {\n const rooms = every ? Object.keys(app.views).map((key) => viewRoom(app, key)) : appRooms(app);\n return rooms.flatMap((room): ListRoom[] => (room && isListRoom(room) && !room.error ? [room as ListRoom] : []));\n}\n\n/** The table behind a list room, findable when it has a key and a name to search by. */\nfunction findableOf(room: ListRoom): Findable | undefined {\n const table = findTable(room.table) as TableMeta | undefined;\n const label = table ? ((labelPath(table) as string | undefined) ?? primaryKey(table)[0]) : undefined;\n return table && label && (primaryKey(table) as string[]).length ? { table, label, room } : undefined;\n}\n\n/** The tables whose records the reader may find: those of `tables` (ids), or of the App's first lists. */\nexport function findableTables(app: ServedApp, tables?: readonly string[], most = 6): Findable[] {\n const seen = new Set<string>();\n const out: Findable[] = [];\n const rooms = listRooms(app, Boolean(tables));\n for (const room of tables ? tables.flatMap((id) => rooms.filter((each) => each.table === id).slice(0, 1)) : rooms) {\n if (seen.has(room.table)) continue;\n seen.add(room.table);\n const found = findableOf(room);\n if (found) out.push(found);\n if (out.length >= most) break;\n }\n return out;\n}\n\n/**\n * The line a hit says under its name: what its list says first under a\n * card's title — a cards description, else the first of its lines — or its\n * first shown column that is not the name. A local column only, read with\n * the name in the same question.\n */\nexport function lineOf({ table, label, room }: Findable): string | undefined {\n const presentation = room.presentation as { description?: string; lines?: readonly string[] } | undefined;\n const keys = primaryKey(table) as string[];\n const candidates = [presentation?.description, presentation?.lines?.[0], ...(room.show ?? [])];\n return candidates.find(\n (name): name is string =>\n typeof name === 'string' &&\n !name.includes('.') &&\n name !== label &&\n !keys.includes(name) &&\n table.columns.some((column) => column.name === name),\n );\n}\n\n/** The records of one table whose names hold `term`, at most `limit`, by name. A refusal answers nothing, never the whole search's failure. */\nexport async function findRecords(findable: Findable, term: string, limit: number, line?: string): Promise<Found[]> {\n const { table, label } = findable;\n const show = [...new Set([label, ...(primaryKey(table) as string[]), ...(line ? [line] : [])])];\n try {\n const data = await api('/api/run', {\n q: `${table.id} ${label} contains ${quote(term)} show ${show.join(', ')} sort ${label}`,\n limit,\n offset: 0,\n count: 'none',\n });\n return ((data.rows ?? []) as Array<Record<string, unknown>>).flatMap((row): Found[] => {\n const key = keyOf(table, row) as Record<string, unknown> | null;\n if (!key) return [];\n const name = rowLabel(table, row) as string;\n const said = line && row[line] !== null && row[line] !== undefined && row[line] !== '' ? String(row[line]) : undefined;\n return [{ label: name, ...(said ? { line: said } : {}), key, open: () => void openRecord(table.id, key, name) }];\n });\n } catch {\n return [];\n }\n}\n","/** What DashboardFind says, in the dashboards 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 searchPlaceholder: 'Search…',\n nothingMatches: 'Nothing matches “{term}”.',\n results: { one: '{count} result', other: '{count} results' },\n});\n","/**\n * A dashboard's find box (`find()`): one big field over several resources'\n * records, the hits grouped by resource, each its record's name and one line\n * of its list, opening the record at its own address. A portal's home is the\n * place for it: an employee who wants an article or a laptop types, and\n * does not first have to know which list holds it.\n *\n * The engine is ⌘K's (recordSearch.ts), so what it finds is what this\n * reader may read, asked of the App's search engine where a resource has\n * one. A typed pause asks; an answer to older text is never shown as this\n * text's. The box is a combobox: the arrows walk the hits across the groups\n * and Enter opens the lit one, so the pointer is never needed.\n * Loaded the first time a dashboard holds one (onDemand.tsx).\n */\nimport { useEffect, useId, useMemo, useState, type KeyboardEvent } from 'react';\nimport { keepPreviousData, useQuery } from '@tanstack/react-query';\nimport { activeApp } from '../appState.js';\nimport type { Section } from '@mistriwork/kit/client/components/dashboardModel.js';\nimport { findRecords, findableTables, lineOf, type Found } from '@mistriwork/kit/client/components/recordSearch.js';\nimport { Icon } from '@mistriwork/kit/client/components/Icon.js';\nimport './DashboardFind.css';\nimport { say } from '../copy/dashboards/components/DashboardFind.js';\nimport { shellById } from '@mistriwork/kit/client/shellHost.js';\n\n/** Hits per resource: a box answers with a shortlist, and its list is one click away for the rest. */\nconst PER_RESOURCE = 5;\n/* The pause after a keystroke before the question goes, so a word typed is one request, not one per letter. */\nconst PAUSE = 200;\n\nexport function FindSection({ section }: { section: Section }) {\n const app = activeApp();\n const searched = section.searched;\n const tables = useMemo(() => (app ? findableTables(app, searched ?? []) : []), [app, searched]);\n const [text, setText] = useState('');\n const [asked, setAsked] = useState('');\n const [lit, setLit] = useState(0);\n const id = useId();\n const term = text.trim();\n\n useEffect(() => {\n const timer = setTimeout(() => setAsked(term), PAUSE);\n return () => clearTimeout(timer);\n }, [term]);\n\n const answer = useQuery({\n queryKey: ['find', tables.map((each) => each.table.id), asked],\n enabled: asked.length >= 2 && tables.length > 0,\n placeholderData: keepPreviousData,\n queryFn: async () =>\n Promise.all(\n tables.map(async (findable) => ({\n findable,\n hits: await findRecords(findable, asked, PER_RESOURCE, lineOf(findable)),\n })),\n ),\n });\n /* Only an answer to what the box says now is drawn: one for older text would open a record nobody asked for. */\n const current = term.length >= 2 && answer.data && asked === term && !answer.isPlaceholderData ? answer.data : undefined;\n const groups = (current ?? []).filter((group) => group.hits.length);\n const hits: Found[] = groups.flatMap((group) => group.hits);\n const at = hits.length ? Math.min(lit, hits.length - 1) : -1;\n useEffect(() => {\n setLit(0);\n }, [asked]);\n useEffect(() => {\n if (at >= 0) shellById(`${id}-hit-${at}`)?.scrollIntoView?.({ block: 'nearest' });\n }, [at, id]);\n\n const open = Boolean(current);\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'ArrowDown' && hits.length) {\n event.preventDefault();\n setLit((at + 1) % hits.length);\n } else if (event.key === 'ArrowUp' && hits.length) {\n event.preventDefault();\n setLit((at - 1 + hits.length) % hits.length);\n } else if (event.key === 'Enter' && at >= 0) {\n event.preventDefault();\n hits[at].open();\n } else if (event.key === 'Escape' && text) {\n event.preventDefault();\n setText('');\n }\n };\n\n let index = -1;\n return (\n <div className=\"find\">\n <div className=\"find-box\">\n <Icon name=\"magnifying-glass\" size={20} />\n <input\n type=\"search\"\n role=\"combobox\"\n aria-label={section.title ?? section.placeholder ?? say('searchPlaceholder')}\n aria-expanded={open}\n aria-controls={`${id}-hits`}\n aria-autocomplete=\"list\"\n aria-activedescendant={at >= 0 ? `${id}-hit-${at}` : undefined}\n placeholder={section.placeholder ?? say('searchPlaceholder')}\n value={text}\n autoComplete=\"off\"\n onChange={(event) => setText(event.target.value)}\n onKeyDown={onKeyDown}\n />\n </div>\n <p className=\"visually-hidden\" aria-live=\"polite\">\n {current ? say('results', { count: hits.length }) : ''}\n </p>\n {open && !hits.length && <p className=\"find-empty\">{say('nothingMatches', { term })}</p>}\n <div\n id={`${id}-hits`}\n role=\"listbox\"\n className=\"find-hits\"\n hidden={!hits.length}\n aria-label={section.title ?? say('searchPlaceholder')}\n >\n {groups.map(({ findable, hits: found }) => (\n <div key={findable.table.id} role=\"group\" aria-labelledby={`${id}-${findable.table.id}`} className=\"find-group\">\n <p id={`${id}-${findable.table.id}`} className=\"find-group-title\" role=\"presentation\">\n {findable.room.label}\n </p>\n {found.map((hit) => {\n index += 1;\n const mine = index;\n return (\n <div\n key={JSON.stringify(hit.key)}\n id={`${id}-hit-${mine}`}\n role=\"option\"\n aria-selected={mine === at}\n className=\"find-hit\"\n onMouseMove={() => setLit(mine)}\n onMouseDown={(event) => event.preventDefault()}\n onClick={hit.open}\n >\n <span className=\"find-hit-name\">{hit.label}</span>\n {hit.line && <span className=\"find-hit-line\">{hit.line}</span>}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,SAAS,UAAU,KAAgB,OAA4B;CAE7D,QADc,QAAQ,OAAO,KAAK,IAAI,KAAK,CAAC,CAAC,KAAK,QAAQ,SAAS,KAAK,GAAG,CAAC,IAAI,SAAS,GAAG,EAAA,CAC/E,SAAS,SAAsB,QAAQ,WAAW,IAAI,KAAK,CAAC,KAAK,QAAQ,CAAC,IAAgB,IAAI,CAAC,CAAE;AAChH;;AAGA,SAAS,WAAW,MAAsC;CACxD,MAAM,QAAQ,UAAU,KAAK,KAAK;CAClC,MAAM,QAAQ,QAAU,UAAU,KAAK,KAA4B,WAAW,KAAK,CAAC,CAAC,KAAM,KAAA;CAC3F,OAAO,SAAS,SAAU,WAAW,KAAK,CAAC,CAAc,SAAS;EAAE;EAAO;EAAO;CAAK,IAAI,KAAA;AAC7F;;AAGA,SAAgB,eAAe,KAAgB,QAA4B,OAAO,GAAe;CAC/F,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,MAAkB,CAAC;CACzB,MAAM,QAAQ,UAAU,KAAK,QAAQ,MAAM,CAAC;CAC5C,KAAK,MAAM,QAAQ,SAAS,OAAO,SAAS,OAAO,MAAM,QAAQ,SAAS,KAAK,UAAU,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO;EACjH,IAAI,KAAK,IAAI,KAAK,KAAK,GAAG;EAC1B,KAAK,IAAI,KAAK,KAAK;EACnB,MAAM,QAAQ,WAAW,IAAI;EAC7B,IAAI,OAAO,IAAI,KAAK,KAAK;EACzB,IAAI,IAAI,UAAU,MAAM;CAC1B;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,OAAO,EAAE,OAAO,OAAO,QAAsC;CAC3E,MAAM,eAAe,KAAK;CAC1B,MAAM,OAAO,WAAW,KAAK;CAE7B,OAAO;EADa,cAAc;EAAa,cAAc,QAAQ;EAAI,GAAI,KAAK,QAAQ,CAAC;CACpF,CAAA,CAAW,MACf,SACC,OAAO,SAAS,YAChB,CAAC,KAAK,SAAS,GAAG,KAClB,SAAS,SACT,CAAC,KAAK,SAAS,IAAI,KACnB,MAAM,QAAQ,MAAM,WAAW,OAAO,SAAS,IAAI,CACvD;AACF;;AAGA,eAAsB,YAAY,UAAoB,MAAc,OAAe,MAAiC;CAClH,MAAM,EAAE,OAAO,UAAU;CACzB,MAAM,OAAO,CAAC,mBAAG,IAAI,IAAI;EAAC;EAAO,GAAI,WAAW,KAAK;EAAgB,GAAI,OAAO,CAAC,IAAI,IAAI,CAAC;CAAE,CAAC,CAAC;CAC9F,IAAI;EAOF,SAAS,MANU,IAAI,YAAY;GACjC,GAAG,GAAG,MAAM,GAAG,GAAG,MAAM,YAAY,MAAM,IAAI,EAAE,QAAQ,KAAK,KAAK,IAAI,EAAE,QAAQ;GAChF;GACA,QAAQ;GACR,OAAO;EACT,CAAC,EAAA,CACa,QAAQ,CAAC,EAAA,CAAsC,SAAS,QAAiB;GACrF,MAAM,MAAM,MAAM,OAAO,GAAG;GAC5B,IAAI,CAAC,KAAK,OAAO,CAAC;GAClB,MAAM,OAAO,SAAS,OAAO,GAAG;GAChC,MAAM,OAAO,QAAQ,IAAI,UAAU,QAAQ,IAAI,UAAU,KAAA,KAAa,IAAI,UAAU,KAAK,OAAO,IAAI,KAAK,IAAI,KAAA;GAC7G,OAAO,CAAC;IAAE,OAAO;IAAM,GAAI,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAAI;IAAK,YAAY,KAAK,WAAW,MAAM,IAAI,KAAK,IAAI;GAAE,CAAC;EACjH,CAAC;CACH,QAAQ;EACN,OAAO,CAAC;CACV;AACF;;;AClGA,IAAa,MAAM,eAAe;CAChC,mBAAmB;CACnB,gBAAgB;CAChB,SAAS;EAAE,KAAK;EAAkB,OAAO;CAAkB;AAC7D,CAAC;;;;;;;;;;;;;;;;;ACkBD,IAAM,eAAe;AAErB,IAAM,QAAQ;AAEd,SAAgB,YAAY,EAAE,WAAiC;CAC7D,MAAM,MAAM,UAAU;CACtB,MAAM,WAAW,QAAQ;CACzB,MAAM,SAAS,cAAe,MAAM,eAAe,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAI,CAAC,KAAK,QAAQ,CAAC;CAC9F,MAAM,CAAC,MAAM,WAAW,SAAS,EAAE;CACnC,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CACrC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC;CAChC,MAAM,KAAK,MAAM;CACjB,MAAM,OAAO,KAAK,KAAK;CAEvB,gBAAgB;EACd,MAAM,QAAQ,iBAAiB,SAAS,IAAI,GAAG,KAAK;EACpD,aAAa,aAAa,KAAK;CACjC,GAAG,CAAC,IAAI,CAAC;CAET,MAAM,SAAS,SAAS;EACtB,UAAU;GAAC;GAAQ,OAAO,KAAK,SAAS,KAAK,MAAM,EAAE;GAAG;EAAK;EAC7D,SAAS,MAAM,UAAU,KAAK,OAAO,SAAS;EAC9C,iBAAiB;EACjB,SAAS,YACP,QAAQ,IACN,OAAO,IAAI,OAAO,cAAc;GAC9B;GACA,MAAM,MAAM,YAAY,UAAU,OAAO,cAAc,OAAO,QAAQ,CAAC;EACzE,EAAE,CACJ;CACJ,CAAC;CAED,MAAM,UAAU,KAAK,UAAU,KAAK,OAAO,QAAQ,UAAU,QAAQ,CAAC,OAAO,oBAAoB,OAAO,OAAO,KAAA;CAC/G,MAAM,UAAU,WAAW,CAAC,EAAA,CAAG,QAAQ,UAAU,MAAM,KAAK,MAAM;CAClE,MAAM,OAAgB,OAAO,SAAS,UAAU,MAAM,IAAI;CAC1D,MAAM,KAAK,KAAK,SAAS,KAAK,IAAI,KAAK,KAAK,SAAS,CAAC,IAAI;CAC1D,gBAAgB;EACd,OAAO,CAAC;CACV,GAAG,CAAC,KAAK,CAAC;CACV,gBAAgB;EACd,IAAI,MAAM,GAAG,UAAU,GAAG,GAAG,OAAO,IAAI,CAAC,EAAE,iBAAiB,EAAE,OAAO,UAAU,CAAC;CAClF,GAAG,CAAC,IAAI,EAAE,CAAC;CAEX,MAAM,OAAO,QAAQ,OAAO;CAC5B,MAAM,aAAa,UAA2C;EAC5D,IAAI,MAAM,QAAQ,eAAe,KAAK,QAAQ;GAC5C,MAAM,eAAe;GACrB,QAAQ,KAAK,KAAK,KAAK,MAAM;EAC/B,OAAO,IAAI,MAAM,QAAQ,aAAa,KAAK,QAAQ;GACjD,MAAM,eAAe;GACrB,QAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,MAAM;EAC7C,OAAO,IAAI,MAAM,QAAQ,WAAW,MAAM,GAAG;GAC3C,MAAM,eAAe;GACrB,KAAK,GAAG,CAAC,KAAK;EAChB,OAAO,IAAI,MAAM,QAAQ,YAAY,MAAM;GACzC,MAAM,eAAe;GACrB,QAAQ,EAAE;EACZ;CACF;CAEA,IAAI,QAAQ;CACZ,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,MAAD;KAAM,MAAK;KAAmB,MAAM;IAAK,CAAA,GACzC,oBAAC,SAAD;KACE,MAAK;KACL,MAAK;KACL,cAAY,QAAQ,SAAS,QAAQ,eAAe,IAAI,mBAAmB;KAC3E,iBAAe;KACf,iBAAe,GAAG,GAAG;KACrB,qBAAkB;KAClB,yBAAuB,MAAM,IAAI,GAAG,GAAG,OAAO,OAAO,KAAA;KACrD,aAAa,QAAQ,eAAe,IAAI,mBAAmB;KAC3D,OAAO;KACP,cAAa;KACb,WAAW,UAAU,QAAQ,MAAM,OAAO,KAAK;KACpC;IACZ,CAAA,CACE;;GACL,oBAAC,KAAD;IAAG,WAAU;IAAkB,aAAU;IACtC,UAAA,UAAU,IAAI,WAAW,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI;GACnD,CAAA;GACF,QAAQ,CAAC,KAAK,UAAU,oBAAC,KAAD;IAAG,WAAU;IAAc,UAAA,IAAI,kBAAkB,EAAE,KAAK,CAAC;GAAK,CAAA;GACvF,oBAAC,OAAD;IACE,IAAI,GAAG,GAAG;IACV,MAAK;IACL,WAAU;IACV,QAAQ,CAAC,KAAK;IACd,cAAY,QAAQ,SAAS,IAAI,mBAAmB;IAEnD,UAAA,OAAO,KAAK,EAAE,UAAU,MAAM,YAC7B,qBAAC,OAAD;KAA6B,MAAK;KAAQ,mBAAiB,GAAG,GAAG,GAAG,SAAS,MAAM;KAAM,WAAU;KAAnG,UAAA,CACE,oBAAC,KAAD;MAAG,IAAI,GAAG,GAAG,GAAG,SAAS,MAAM;MAAM,WAAU;MAAmB,MAAK;MACpE,UAAA,SAAS,KAAK;KACd,CAAA,GACF,MAAM,KAAK,QAAQ;MAClB,SAAS;MACT,MAAM,OAAO;MACb,OACE,qBAAC,OAAD;OAEE,IAAI,GAAG,GAAG,OAAO;OACjB,MAAK;OACL,iBAAe,SAAS;OACxB,WAAU;OACV,mBAAmB,OAAO,IAAI;OAC9B,cAAc,UAAU,MAAM,eAAe;OAC7C,SAAS,IAAI;OARf,UAAA,CAUE,oBAAC,QAAD;QAAM,WAAU;QAAiB,UAAA,IAAI;OAAY,CAAA,GAChD,IAAI,QAAQ,oBAAC,QAAD;QAAM,WAAU;QAAiB,UAAA,IAAI;OAAW,CAAA,CAC1D;MAXE,GAAA,KAAK,UAAU,IAAI,GAAG,CAWxB;KAET,CAAC,CACE;IAvBK,GAAA,SAAS,MAAM,EAuBpB,CACN;GACE,CAAA;EACF;;AAET"}
@@ -1,6 +1,6 @@
1
1
  import { t as Field } from "./shared-CERG8O1B.js";
2
2
  import { n as definedFromInput, t as definedBreaks } from "./shared-BjCz2A6c.js";
3
- import { p as say } from "./shared-D1mMBLdj.js";
3
+ import { p as say } from "./shared-JvcI-LWp.js";
4
4
  import { useState } from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  // packages/mistri/src/client/components/CommandAnswers.tsx
@@ -163,4 +163,4 @@ function DefinedAnswers({ field, id, path, value, shared, onChange, Control }) {
163
163
  }
164
164
  export { DefinedAnswers, answerProblems };
165
165
 
166
- //# sourceMappingURL=shared-BQIZWOoJ.js.map
166
+ //# sourceMappingURL=shared-Dm0IGbAf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-BQIZWOoJ.js","names":[],"sources":["../../src/client/components/CommandAnswers.tsx"],"sourcesContent":["/**\n * Questions defined in rows (`field.defined`), drawn and judged: a\n * part loaded on demand. A command asks them only of a signed-in reader's\n * record, once prepare has read the rows, and never on a public form, so a\n * first page carries none of it; CommandFields draws them through `onDemand`\n * and judges the answers once this has arrived. The server judges them again\n * against the rows as they stand when it runs.\n */\nimport { useState } from 'react';\nimport type { AppCommandField } from '../../shared/command.js';\nimport type { CommandValue } from '../commandState.js';\nimport type { DefinedField } from '@mistriwork/engine/shared/definedFields.js';\nimport { definedBreaks, definedFromInput } from '@mistriwork/engine/shared/definedValues.js';\nimport { Field } from '@mistriwork/ui/Field.js';\nimport { say } from '../copy/forms/components/CommandFields.js';\n/* Types only: CommandFields loads this file on demand and hands its control in (`Control`), so neither reaches the\n other at run time. */\nimport type { CommandFieldControl, CommandFieldsShared, CommandQuestions, CommandValues, ValueField } from './CommandFields.js';\n\ntype DefinedInput = Extract<AppCommandField, { type: 'defined' }>;\ntype Entry = CommandValues[string];\n\nconst isValues = (value: unknown): value is CommandValues => !!value && typeof value === 'object' && !Array.isArray(value);\n\n/* What an answer breaks, said as a field's own problem is, under the question's label. */\nfunction answerSentence(question: DefinedField, reason: ReturnType<typeof definedBreaks>[number]['reason']): string {\n const label = question.label;\n switch (reason) {\n case 'required':\n return say('isRequired', { label });\n case 'number':\n return say('mustBeANumber', { label });\n case 'date':\n return say('mustBeADate', { label });\n case 'datetime':\n return say('mustBeADateAndTime', { label });\n case 'bool':\n return say('mustBeYes', { label });\n case 'choice':\n return say('chooseLabel', { label });\n case 'text':\n return say('hasCharactersAForm', { label });\n case 'unknown':\n return say('theFormHasAnInput');\n }\n}\n\n/* The answers to questions defined in rows, judged against the questions prepare read, by path (`answers.os`):\n the server judges them again against the rows as they stand when it runs. */\nexport function answerProblems(\n fields: readonly AppCommandField[],\n values: CommandValues,\n questions?: CommandQuestions,\n): [string, string][] {\n if (!questions) return [];\n return fields.flatMap((field) => {\n const asked = field.type === 'defined' ? questions[field.name] : undefined;\n if (!asked) return [];\n const value = values[field.name];\n const answers = isValues(value) ? value : {};\n return definedBreaks(asked, answers, { extras: false }).map(({ key, reason }): [string, string] => [\n `${field.name}.${key}`,\n answerSentence(\n asked.find((question) => question.key === key)!,\n reason,\n ),\n ]);\n });\n}\n\n/* A question as the control it is asked with: the command's own controls, so a question reads as any field does. */\nfunction questionControl(question: DefinedField): Exclude<ValueField, { type: 'reference' | 'defined' }> {\n const base = { name: question.key, label: question.label, ...(question.required ? { required: true } : {}) };\n switch (question.type) {\n case 'number':\n return { ...base, type: 'number' };\n case 'money':\n return { ...base, type: 'number', step: 0.01 };\n case 'date':\n case 'datetime':\n return { ...base, type: question.type };\n case 'bool':\n return { ...base, type: 'boolean' };\n case 'choice':\n return { ...base, type: 'choice', options: question.options.map((option) => ({ value: option, label: option })) };\n case 'textarea':\n return { ...base, type: 'textarea' };\n default:\n return { ...base, type: 'text' };\n }\n}\n\n/**\n * Questions defined in rows: one control per question prepare\n * read, under the input's label, each answer kept by the question's key. A box\n * keeps what was typed, so a number half-typed stays as typed; the answers\n * hold each one read as its question's kind, which is what is sent.\n */\nexport function DefinedAnswers({\n field,\n id,\n path,\n value,\n shared,\n onChange,\n Control,\n}: {\n field: DefinedInput;\n id: string;\n path: string;\n value: Entry;\n shared: CommandFieldsShared;\n onChange: (value: Entry, path: string) => void;\n Control: typeof CommandFieldControl;\n}) {\n const [boxes, setBoxes] = useState<Record<string, CommandValue | string[]>>({});\n const asked = shared.questions?.[field.name];\n if (!asked?.length) return null;\n const answers = (isValues(value) ? value : {}) as Record<string, unknown>;\n const helpId = field.help ? `${id}-help` : undefined;\n return (\n <fieldset className=\"command-group\" aria-describedby={helpId}>\n <legend>{field.label}</legend>\n <div className=\"command-group-body\">\n {field.help && (\n <span id={helpId} className=\"command-help\">\n {field.help}\n </span>\n )}\n <div className=\"command-fields\">\n {asked.map((question) => {\n const at = `${path}.${question.key}`;\n const questionId = `${id}-${question.key}`;\n const problem = shared.problems?.[at];\n const answer = answers[question.key];\n const shown = Object.hasOwn(boxes, question.key) ? boxes[question.key] : answer;\n const set = (next: CommandValue | string[]) => {\n setBoxes((previous) => ({ ...previous, [question.key]: next }));\n const typed = definedFromInput(question, next);\n const following: Record<string, unknown> = { ...answers };\n if (typed === undefined) delete following[question.key];\n else following[question.key] = typed;\n onChange(following as CommandValues, at);\n };\n const control = questionControl(question);\n const label = `${question.label}${question.required && question.type !== 'bool' ? ' (required)' : ''}`;\n return (\n <Field\n key={question.key}\n label={label}\n id={questionId}\n hint={question.help}\n problem={problem}\n group={control.type === 'choice' || question.type === 'choices'}\n check={question.type === 'bool'}\n className=\"command-field\"\n >\n {(slot) =>\n question.type === 'choices' ? (\n <div className=\"command-choices\" role=\"group\" id={questionId} aria-labelledby={`${questionId}-label`}>\n {question.options.map((option) => {\n const chosen = Array.isArray(shown) ? shown : [];\n return (\n <label className=\"command-choice\" key={option}>\n <input\n type=\"checkbox\"\n checked={chosen.includes(option)}\n disabled={shared.disabled}\n onChange={(event) =>\n set(question.options.filter((item) => (item === option ? event.target.checked : chosen.includes(item))))\n }\n />\n {option}\n </label>\n );\n })}\n </div>\n ) : (\n <Control\n field={control}\n id={questionId}\n describedBy={slot['aria-describedby']}\n invalid={Boolean(problem)}\n value={typeof shown === 'string' || typeof shown === 'number' || typeof shown === 'boolean' ? shown : undefined}\n disabled={shared.disabled}\n onChange={set}\n />\n )\n }\n </Field>\n );\n })}\n </div>\n </div>\n </fieldset>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-Dm0IGbAf.js","names":[],"sources":["../../src/client/components/CommandAnswers.tsx"],"sourcesContent":["/**\n * Questions defined in rows (`field.defined`), drawn and judged: a\n * part loaded on demand. A command asks them only of a signed-in reader's\n * record, once prepare has read the rows, and never on a public form, so a\n * first page carries none of it; CommandFields draws them through `onDemand`\n * and judges the answers once this has arrived. The server judges them again\n * against the rows as they stand when it runs.\n */\nimport { useState } from 'react';\nimport type { AppCommandField } from '../../shared/command.js';\nimport type { CommandValue } from '../commandState.js';\nimport type { DefinedField } from '@mistriwork/engine/shared/definedFields.js';\nimport { definedBreaks, definedFromInput } from '@mistriwork/engine/shared/definedValues.js';\nimport { Field } from '@mistriwork/ui/Field.js';\nimport { say } from '../copy/forms/components/CommandFields.js';\n/* Types only: CommandFields loads this file on demand and hands its control in (`Control`), so neither reaches the\n other at run time. */\nimport type { CommandFieldControl, CommandFieldsShared, CommandQuestions, CommandValues, ValueField } from './CommandFields.js';\n\ntype DefinedInput = Extract<AppCommandField, { type: 'defined' }>;\ntype Entry = CommandValues[string];\n\nconst isValues = (value: unknown): value is CommandValues => !!value && typeof value === 'object' && !Array.isArray(value);\n\n/* What an answer breaks, said as a field's own problem is, under the question's label. */\nfunction answerSentence(question: DefinedField, reason: ReturnType<typeof definedBreaks>[number]['reason']): string {\n const label = question.label;\n switch (reason) {\n case 'required':\n return say('isRequired', { label });\n case 'number':\n return say('mustBeANumber', { label });\n case 'date':\n return say('mustBeADate', { label });\n case 'datetime':\n return say('mustBeADateAndTime', { label });\n case 'bool':\n return say('mustBeYes', { label });\n case 'choice':\n return say('chooseLabel', { label });\n case 'text':\n return say('hasCharactersAForm', { label });\n case 'unknown':\n return say('theFormHasAnInput');\n }\n}\n\n/* The answers to questions defined in rows, judged against the questions prepare read, by path (`answers.os`):\n the server judges them again against the rows as they stand when it runs. */\nexport function answerProblems(\n fields: readonly AppCommandField[],\n values: CommandValues,\n questions?: CommandQuestions,\n): [string, string][] {\n if (!questions) return [];\n return fields.flatMap((field) => {\n const asked = field.type === 'defined' ? questions[field.name] : undefined;\n if (!asked) return [];\n const value = values[field.name];\n const answers = isValues(value) ? value : {};\n return definedBreaks(asked, answers, { extras: false }).map(({ key, reason }): [string, string] => [\n `${field.name}.${key}`,\n answerSentence(\n asked.find((question) => question.key === key)!,\n reason,\n ),\n ]);\n });\n}\n\n/* A question as the control it is asked with: the command's own controls, so a question reads as any field does. */\nfunction questionControl(question: DefinedField): Exclude<ValueField, { type: 'reference' | 'defined' }> {\n const base = { name: question.key, label: question.label, ...(question.required ? { required: true } : {}) };\n switch (question.type) {\n case 'number':\n return { ...base, type: 'number' };\n case 'money':\n return { ...base, type: 'number', step: 0.01 };\n case 'date':\n case 'datetime':\n return { ...base, type: question.type };\n case 'bool':\n return { ...base, type: 'boolean' };\n case 'choice':\n return { ...base, type: 'choice', options: question.options.map((option) => ({ value: option, label: option })) };\n case 'textarea':\n return { ...base, type: 'textarea' };\n default:\n return { ...base, type: 'text' };\n }\n}\n\n/**\n * Questions defined in rows: one control per question prepare\n * read, under the input's label, each answer kept by the question's key. A box\n * keeps what was typed, so a number half-typed stays as typed; the answers\n * hold each one read as its question's kind, which is what is sent.\n */\nexport function DefinedAnswers({\n field,\n id,\n path,\n value,\n shared,\n onChange,\n Control,\n}: {\n field: DefinedInput;\n id: string;\n path: string;\n value: Entry;\n shared: CommandFieldsShared;\n onChange: (value: Entry, path: string) => void;\n Control: typeof CommandFieldControl;\n}) {\n const [boxes, setBoxes] = useState<Record<string, CommandValue | string[]>>({});\n const asked = shared.questions?.[field.name];\n if (!asked?.length) return null;\n const answers = (isValues(value) ? value : {}) as Record<string, unknown>;\n const helpId = field.help ? `${id}-help` : undefined;\n return (\n <fieldset className=\"command-group\" aria-describedby={helpId}>\n <legend>{field.label}</legend>\n <div className=\"command-group-body\">\n {field.help && (\n <span id={helpId} className=\"command-help\">\n {field.help}\n </span>\n )}\n <div className=\"command-fields\">\n {asked.map((question) => {\n const at = `${path}.${question.key}`;\n const questionId = `${id}-${question.key}`;\n const problem = shared.problems?.[at];\n const answer = answers[question.key];\n const shown = Object.hasOwn(boxes, question.key) ? boxes[question.key] : answer;\n const set = (next: CommandValue | string[]) => {\n setBoxes((previous) => ({ ...previous, [question.key]: next }));\n const typed = definedFromInput(question, next);\n const following: Record<string, unknown> = { ...answers };\n if (typed === undefined) delete following[question.key];\n else following[question.key] = typed;\n onChange(following as CommandValues, at);\n };\n const control = questionControl(question);\n const label = `${question.label}${question.required && question.type !== 'bool' ? ' (required)' : ''}`;\n return (\n <Field\n key={question.key}\n label={label}\n id={questionId}\n hint={question.help}\n problem={problem}\n group={control.type === 'choice' || question.type === 'choices'}\n check={question.type === 'bool'}\n className=\"command-field\"\n >\n {(slot) =>\n question.type === 'choices' ? (\n <div className=\"command-choices\" role=\"group\" id={questionId} aria-labelledby={`${questionId}-label`}>\n {question.options.map((option) => {\n const chosen = Array.isArray(shown) ? shown : [];\n return (\n <label className=\"command-choice\" key={option}>\n <input\n type=\"checkbox\"\n checked={chosen.includes(option)}\n disabled={shared.disabled}\n onChange={(event) =>\n set(question.options.filter((item) => (item === option ? event.target.checked : chosen.includes(item))))\n }\n />\n {option}\n </label>\n );\n })}\n </div>\n ) : (\n <Control\n field={control}\n id={questionId}\n describedBy={slot['aria-describedby']}\n invalid={Boolean(problem)}\n value={typeof shown === 'string' || typeof shown === 'number' || typeof shown === 'boolean' ? shown : undefined}\n disabled={shared.disabled}\n onChange={set}\n />\n )\n }\n </Field>\n );\n })}\n </div>\n </div>\n </fieldset>\n );\n}\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { nt as Button } from "./shared-poc11xxo.js";
2
2
  import { J as rowCurrency, Jt as formatNumber, Rt as defaultCurrency, gt as defineMessages, q as minorToMajor, rn as shell } from "./shared-DJZjSi3J.js";
3
- import { L as DropItem, R as DropMenu } from "./shared-CNmaAv64.js";
4
- import { B as toast, L as state, b as pageSize, d as findTable, i as api, s as columnDisplay } from "./shared-DPAVoWY0.js";
3
+ import { L as DropItem, R as DropMenu } from "./shared-BHsjawAG.js";
4
+ import { B as toast, L as state, b as pageSize, d as findTable, i as api, s as columnDisplay } from "./shared-BoLbc2WD.js";
5
5
  import { t as Modal } from "./shared-CRr4kJXB.js";
6
- import { a as rememberColumns, c as selectionFor, i as orderColumns, l as streamExport, n as declaredColumns, o as sameList, s as saveExport, t as EXPORT_MAX_ROWS, u as withColumns } from "./shared-Guq3upiR.js";
7
- import { a as stepList, i as pageStepFor, r as say$1 } from "./shared-Ccmc8YO0.js";
6
+ import { a as rememberColumns, c as selectionFor, i as orderColumns, l as streamExport, n as declaredColumns, o as sameList, s as saveExport, t as EXPORT_MAX_ROWS, u as withColumns } from "./shared-Cws_z-TS.js";
7
+ import { a as stepList, i as pageStepFor, r as say$1 } from "./shared-DSUICuaJ.js";
8
8
  import { useRef, useState } from "react";
9
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  // packages/kit/src/client/copy/workbench/handoff.ts
@@ -453,4 +453,4 @@ function sheetAmounts(tableId, columns, rows) {
453
453
  }
454
454
  export { ExportControl };
455
455
 
456
- //# sourceMappingURL=shared-DSUtcU5S.js.map
456
+ //# sourceMappingURL=shared-DnIkTpPr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-DSUtcU5S.js","names":["say","_Fragment"],"sources":["../../../kit/src/client/copy/workbench/handoff.ts","../../../kit/src/client/handoff.js","../../../kit/src/client/components/Export.tsx"],"sourcesContent":["/** What handoff says, in the Workbench room (packages/kit/src/client/messages.ts says how the catalog is split). */\nimport { defineMessages } from '../../messages.js';\n\nexport const say = defineMessages({\n oneRow: 'One row of `{table}`: **{label}**',\n theQuestionOnScreen: 'The question on screen is:',\n ofMatching: ' of {total} matching',\n columnsOnScreen: 'Columns on screen: {columns}',\n thisDatabaseIsAvailable: 'This database is available over MCP as `tablewalk`. {next}',\n tableDescribesATable: '`table` describes a table, `find` says where a name or a value lives, and ',\n queryRunsTheSame: '`query` runs the same language as the box above. The values themselves are ',\n notIncludedHereFetch: 'not included here — fetch them if you need them.',\n positionCopiedPasteItInto: 'Position copied — paste it into an agent.',\n connectionNamed: 'Connection `{connection}`{dialect}.',\n defaultConnectionName: 'default',\n showingRows: { one: 'Showing {count} row{of}.', other: 'Showing {count} rows{of}.' },\n aPositionInTablewalk: '# {table} — a position in tablewalk',\n toReadThisRow: 'To read this row and everything pointing at it:',\n toSeeTheSame: 'To see the same rows:',\n});\n","/**\n * Hand the walk to an agent.\n *\n * The MCP server is the agent→database half of this tool. This is the other\n * half: a person walks to something interesting — three joins deep, a filter\n * they arrived at by clicking — and now wants an agent to carry on from\n * exactly there. Retyping the position into a chat window is where that\n * hand-off usually dies, because the query is the easy part and the shape,\n * the connection and the keys are the parts people leave out.\n *\n * What it writes is deliberately not the rows. The rows are on screen and\n * the person can copy them; pasting them into a chat window sends them\n * somewhere, and that should stay a decision rather than a side effect of\n * clicking \"copy\". What it writes is everything an agent needs to fetch the\n * rows itself, against the same connection, with the same question.\n */\n\n/** `name → type` with the markers the rest of the tool uses for keys and references. */\nimport { say } from './copy/workbench/handoff.js';\n\nfunction columnLine(column) {\n const parts = [column.name];\n if (column.type) parts.push(column.type);\n if (column.primaryKey) parts.push('key');\n if (column.nullable === false && !column.primaryKey) parts.push('not null');\n if (column.references) parts.push(`→ ${column.references.table}.${column.references.column}`);\n return `- ${parts.join(' · ')}`;\n}\n\n/**\n * The walk position as markdown, ready to paste into an agent.\n *\n * Pure so it can be pinned without a browser — the failure this feature has\n * is silently omitting a field, and the only way to see that is to read the\n * whole document a test wrote.\n */\nexport function agentContext(position) {\n const { connection, dialect, table, query, columns = [], shown, total, meta, trail = [], record } = position;\n const lines = [];\n\n lines.push(say('aPositionInTablewalk', { table }));\n lines.push('');\n lines.push(say('connectionNamed', { connection: connection ?? say('defaultConnectionName'), dialect: dialect ? ` (${dialect})` : '' }));\n lines.push('');\n\n if (record) {\n lines.push(say('oneRow', { table, label: record.label }));\n lines.push('');\n lines.push('```json');\n lines.push(JSON.stringify(record.key));\n lines.push('```');\n lines.push('');\n } else {\n lines.push(say('theQuestionOnScreen'));\n lines.push('');\n lines.push('```');\n lines.push(query);\n lines.push('```');\n lines.push('');\n }\n\n if (!record && typeof shown === 'number') {\n const of = typeof total === 'number' && total > shown ? say('ofMatching', { total: total.toLocaleString() }) : '';\n lines.push(say('showingRows', { count: shown, of }));\n lines.push('');\n }\n\n /* The columns the *query* returned, not the whole table: a projection is\n part of the position, and an agent handed the full table would widen the\n question without being told it had. */\n if (!record && columns.length) {\n lines.push(say('columnsOnScreen', { columns: columns.join(', ') }));\n lines.push('');\n }\n\n if (meta?.columns?.length) {\n lines.push(`## ${meta.id ?? table}`);\n lines.push('');\n for (const column of meta.columns) lines.push(columnLine(column));\n lines.push('');\n }\n\n /* How they got here. A filter arrived at by three clicks reads as one\n opaque `where` clause, and the trail is what makes it a story again. */\n if (trail.length > 1) {\n lines.push('## The walk');\n lines.push('');\n for (const step of trail) lines.push(`- ${step}`);\n lines.push('');\n }\n\n lines.push('## Carry on from here');\n lines.push('');\n lines.push(say('thisDatabaseIsAvailable', { next: record ? say('toReadThisRow') : say('toSeeTheSame') }));\n lines.push('');\n lines.push('```json');\n lines.push(\n JSON.stringify(\n record\n ? { name: 'record', arguments: { connection: connection ?? undefined, table, key: record.key } }\n : { name: 'query', arguments: { connection: connection ?? undefined, q: query, limit: shown ?? 20 } },\n null,\n 2,\n ),\n );\n lines.push('```');\n lines.push('');\n lines.push(say('tableDescribesATable') + say('queryRunsTheSame') + say('notIncludedHereFetch'));\n\n return lines.join('\\n');\n}\n\n/**\n * Everything the current walk knows about where it is.\n *\n * Reads `state` rather than taking it apart at each call site, because the\n * two callers — a result and a record — differ in one field and agreeing on\n * the other seven by hand is how they drift.\n */\nexport function positionOf(state, entry, extra = {}) {\n const meta = state.schema?.tables?.find((t) => t.id === entry.table) ?? null;\n return {\n connection: state.activeConnection ?? state.schema?.label ?? null,\n dialect: state.schema?.dialect ?? null,\n table: entry.table,\n query: entry.query ?? entry.table,\n meta,\n /* The trail as it reads in the breadcrumb: the query for a result, the\n row's name for a record. */\n trail: (state.stack ?? []).map((step) => (step.kind === 'table' ? step.query : step.label)),\n ...extra,\n };\n}\n\n/**\n * Copy a position, and say so.\n *\n * The clipboard can be refused — an insecure origin, a permission denied —\n * and a button that silently does nothing is worse than one that says it\n * could not. The text is not shown as a fallback because it is a page long.\n */\nexport async function copyPosition(position, toast) {\n try {\n await navigator.clipboard.writeText(agentContext(position));\n toast(say('positionCopiedPasteItInto'), 'info');\n } catch {\n toast(say('common.theBrowserRefusedClipboard'), 'error');\n }\n}\n","/**\n * Export the result you are looking at, as CSV or JSON.\n *\n * Both scopes are offered with their row count spelled out, because the\n * default page is 20 rows and a file called `invoice-2026-08-22.csv` gives\n * no hint that the other 49,980 rows were left behind. The serialisers and\n * the column memory stay in `export.js`, pure and tested without a browser;\n * this is the control over them, a part that loads on demand: ExportHold.tsx\n * holds its place until it has.\n */\nimport { useRef, useState } from 'react';\nimport { Button } from '@mistriwork/ui/Button.js';\nimport { Modal, type ModalHandle } from '@mistriwork/ui/Modal.js';\nimport { api, columnDisplay, findTable, pageSize, state, toast } from '../core.js';\nimport { minorToMajor, rowCurrency } from '@mistriwork/engine/shared/money.js';\nimport {\n EXPORT_MAX_ROWS,\n declaredColumns,\n orderColumns,\n rememberColumns,\n sameList,\n saveExport,\n selectionFor,\n streamExport,\n withColumns,\n} from '../export.js';\nimport { copyPosition, positionOf } from '../handoff.js';\nimport type { TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport { DropItem, DropMenu } from '@mistriwork/ui/DropMenu.js';\nimport { defaultCurrency, formatNumber } from '../locale.js';\nimport { pageStepFor, stepList } from './pageStep.js';\nimport { shell } from '../shellMode.js';\nimport { say } from '../copy/workbench/components/Export.js';\n\ninterface ExportData {\n table: string;\n columns?: string[];\n rows?: Array<Record<string, unknown>>;\n total?: number;\n}\ninterface Entry {\n table: string;\n query: string;\n}\n\nexport function ExportControl({ entry, data }: { entry: Entry; data: ExportData }) {\n const displayed = data.columns ?? [];\n const declared = declaredColumns(data.table) as string[];\n const pageRows = data.rows ?? [];\n const total = data.total ?? pageRows.length;\n const capped = Math.min(total, EXPORT_MAX_ROWS as number);\n /* Last time's selection if there is one, otherwise exactly what is on\n screen. A pick is remembered in the store, so re-reading it after one\n is enough — the tick is only to make this component look again. */\n const [, tick] = useState(0);\n const selected = selectionFor(data.table, displayed, declared) as string[];\n const [open, setOpen] = useState(false);\n const [busy, setBusy] = useState<string | null>(null);\n const [choosing, setChoosing] = useState(false);\n\n /**\n * Fetch if the file would differ from what is on screen, then write it.\n * The re-fetch is the whole reason the chooser is safe to offer: picking\n * columns out of the rows already in hand would quietly export blanks for\n * any column the query never selected.\n */\n const deliver = async (kind: 'csv' | 'json', all: boolean) => {\n const onScreen = sameList(selected, displayed);\n let rows = pageRows;\n if (all || !onScreen) {\n setBusy(`${kind}:${all}`);\n try {\n /* Where it cannot stream, \"all\" is one bounded re-run rather than\n pages stitched together by the client. */\n const q = withColumns(entry.query, sameList(selected, declared) ? [] : selected);\n /* Streamed where the server offers it: one bounded read held whole\n was up to 19 MB of objects on the server and a long pause. */\n if (all && (await streamExport(kind, data.table, q, selected))) {\n setOpen(false);\n return;\n }\n /* This page is read the way the list read it: a step by key, where the pager took one (pageStep.ts). */\n const page = (state as { page: number }).page;\n const step = all\n ? undefined\n : pageStepFor(\n stepList((state as { activeConnection?: string }).activeConnection ?? '', entry.table, entry.query, pageSize()),\n page,\n );\n const fresh = await api('/api/run', {\n q,\n limit: all ? capped : (step?.limit ?? pageSize()),\n offset: all ? 0 : page * pageSize(),\n ...step?.body,\n });\n if (!fresh.columns) throw new Error(fresh.errors?.[0]?.message ?? say('thatQueryNoLonger'));\n rows = fresh.rows ?? [];\n } catch (err) {\n toast((err as Error).message, 'error');\n return;\n } finally {\n setBusy(null);\n }\n }\n setOpen(false);\n // `selected` rather than what came back, so the file follows the declared order.\n saveExport(kind, data.table, selected, kind === 'csv' ? sheetAmounts(data.table, selected, rows) : rows);\n };\n\n /* JSON and the agent hand-off are Workbench tools; an App's list offers the columns and the CSV. */\n const workbench = !shell().inApp();\n const kinds = workbench ? (['csv', 'json'] as const) : (['csv'] as const);\n const pageLabel = say('thisPageRows', { count: pageRows.length });\n const allLabel = say('allMatchingRows', { capped: formatNumber(capped) });\n return (\n <>\n <DropMenu\n className=\"export\"\n triggerClassName=\"export-summary\"\n triggerTitle={say('downloadThisResult')}\n label={\n <>\n <span className=\"export-word\">{say('export')}</span>\n <span className=\"export-more\" aria-hidden=\"true\">\n ⋯\n </span>\n </>\n }\n popupClassName=\"export-menu\"\n open={open}\n onOpenChange={setOpen}\n >\n <div className=\"export-group\">\n <DropItem className=\"export-item export-columns-item\" onClick={() => setChoosing(true)}>\n <span>{say('chooseColumns')}</span>\n <span className=\"export-count\">\n {declared.length ? say('selectedOfDeclared', { selected: selected.length, declared: declared.length }) : `${selected.length}`}\n </span>\n </DropItem>\n </div>\n {kinds.map((kind) => (\n <div key={kind} className=\"export-group\">\n <span className=\"export-kind\">{kind.toUpperCase()}</span>\n <DropItem\n className=\"export-item\"\n disabled={busy === `${kind}:false`}\n closeOnClick={false}\n onClick={() => void deliver(kind, false)}\n >\n {busy === `${kind}:false` ? say('fetching') : pageLabel}\n </DropItem>\n {/* Only worth offering when it would actually differ from the page. */}\n {total > pageRows.length && (\n <DropItem\n className=\"export-item\"\n disabled={busy === `${kind}:true`}\n closeOnClick={false}\n onClick={() => void deliver(kind, true)}\n >\n {busy === `${kind}:true` ? say('fetching') : allLabel}\n </DropItem>\n )}\n </div>\n ))}\n {/* Not a file, but the same question: take this away with you. An agent is the other place a result goes. */}\n {workbench && (\n <div className=\"export-group\">\n <span className=\"export-kind\">{say('agent')}</span>\n <DropItem\n className=\"export-item\"\n title={say('theQueryTheShape')}\n onClick={() => void copyPosition(positionOf(state, entry, { columns: displayed, shown: pageRows.length, total }), toast)}\n >\n {say('copyThisPosition')}\n </DropItem>\n </div>\n )}\n {total > (EXPORT_MAX_ROWS as number) && (\n <p className=\"export-note\">{say('cappedAtRowsTheSame', { max: formatNumber(EXPORT_MAX_ROWS as number) })}</p>\n )}\n </DropMenu>\n {choosing && (\n <ColumnChooser\n tableId={data.table}\n displayed={displayed}\n declared={declared}\n selected={selected}\n onClose={() => setChoosing(false)}\n onDone={(picked) => {\n rememberColumns(data.table, picked);\n tick((n) => n + 1);\n }}\n />\n )}\n </>\n );\n}\n\ninterface ChooserColumn {\n name: string;\n type?: string;\n primaryKey?: boolean;\n references?: unknown;\n}\n\n/**\n * The column chooser: a checklist rather than a multi-select, so every\n * column is visible at once with its type and its keys — which is the\n * information the choice actually turns on.\n */\nfunction ColumnChooser({\n tableId,\n displayed,\n declared,\n selected,\n onClose,\n onDone,\n}: {\n tableId: string;\n displayed: string[];\n declared: string[];\n selected: string[];\n onClose: () => void;\n onDone: (picked: string[]) => void;\n}) {\n const table = state.schema ? (findTable(tableId) as TableMeta | undefined) : null;\n /* A view whose columns the catalog does not carry still has the ones on\n screen, so the chooser degrades to those rather than opening empty. */\n const columns: ChooserColumn[] = table?.columns?.length\n ? table.columns\n : declared.concat(displayed.filter((n) => !declared.includes(n))).map((name) => ({ name }));\n const [ticked, setTicked] = useState(() => new Set(selected));\n const dialog = useRef<ModalHandle>(null);\n const setAll = (names: string[]) => setTicked(new Set(names));\n const toggle = (name: string, on: boolean) =>\n setTicked((was) => {\n const next = new Set(was);\n if (on) next.add(name);\n else next.delete(name);\n return next;\n });\n const use = () => {\n const picked = columns.map((c) => c.name).filter((n) => ticked.has(n));\n // A file of nothing but row separators is not an export.\n if (!picked.length) {\n toast(say('pickAtLeastOne'), 'error');\n return;\n }\n dialog.current?.close();\n onDone(orderColumns(picked, declared) as string[]);\n };\n return (\n <Modal className=\"confirm cols\" ref={dialog} onClose={onClose}>\n <h2>{say('columnsToExport')}</h2>\n <p>{say('tickedColumnsAreWritten')}</p>\n <div className=\"col-actions\">\n <Button size=\"compact\" onClick={() => setAll(columns.map((c) => c.name))}>\n {say('common.all')}\n </Button>\n <Button size=\"compact\" onClick={() => setAll([])}>\n {say('none')}\n </Button>\n <Button size=\"compact\" onClick={() => setAll(displayed)}>\n {say('asDisplayed')}\n </Button>\n <span className=\"col-count\">\n {say('ofSelected', { filterCount: columns.filter((c) => ticked.has(c.name)).length, columnsCount: columns.length })}\n </span>\n </div>\n <div className=\"col-list\">\n {columns.map((column) => (\n <label key={column.name} className=\"col-row\">\n <input type=\"checkbox\" checked={ticked.has(column.name)} onChange={(e) => toggle(column.name, e.currentTarget.checked)} />\n <span className=\"col-name\">{column.name}</span>\n {/* The same markers the result grid puts on its headers, so the two read as one vocabulary. */}\n {column.primaryKey ? <span className=\"pk\"> {say('common.key')}</span> : null}\n {column.references ? <span className=\"fk\"> →</span> : null}\n <span className=\"col-type\">{column.type ?? ''}</span>\n {displayed.includes(column.name) ? null : <span className=\"col-extra\">{say('notShown')}</span>}\n </label>\n ))}\n </div>\n <p className=\"confirm-params\">{say('rememberedForThisTable')}</p>\n <div className=\"confirm-actions\">\n <Button onClick={() => dialog.current?.close()}>{say('common.cancel')}</Button>\n <Button intent=\"primary\" onClick={use}>\n {say('useTheseColumns')}\n </Button>\n </div>\n </Modal>\n );\n}\n\ntype MinorRule = { format?: string; minor?: true; currency?: string; currencyColumn?: string };\n/**\n * A CSV says money as the figure a sheet reads, as the server's streamed file\n * does (exportRoute.ts csvAmounts), and here where it cannot stream — a tenant\n * lease, a row policy, this page: a column kept in minor units, 1999, is\n * written 19.99 in its row's own currency.\n */\nfunction sheetAmounts(tableId: string, columns: readonly string[], rows: Array<Record<string, unknown>>) {\n const minor = columns.flatMap((column) => {\n const rule = columnDisplay(tableId, column) as MinorRule | null;\n return rule?.format === 'money' && rule.minor ? [[column, rule] as const] : [];\n });\n if (!minor.length) return rows;\n return rows.map((row) => {\n const said = { ...row };\n for (const [column, rule] of minor)\n if (Object.hasOwn(row, column))\n said[column] = minorToMajor(row[column], rowCurrency(rule, row, rule.currency || defaultCurrency())) ?? row[column];\n return said;\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-DnIkTpPr.js","names":["say","_Fragment"],"sources":["../../../kit/src/client/copy/workbench/handoff.ts","../../../kit/src/client/handoff.js","../../../kit/src/client/components/Export.tsx"],"sourcesContent":["/** What handoff says, in the Workbench room (packages/kit/src/client/messages.ts says how the catalog is split). */\nimport { defineMessages } from '../../messages.js';\n\nexport const say = defineMessages({\n oneRow: 'One row of `{table}`: **{label}**',\n theQuestionOnScreen: 'The question on screen is:',\n ofMatching: ' of {total} matching',\n columnsOnScreen: 'Columns on screen: {columns}',\n thisDatabaseIsAvailable: 'This database is available over MCP as `tablewalk`. {next}',\n tableDescribesATable: '`table` describes a table, `find` says where a name or a value lives, and ',\n queryRunsTheSame: '`query` runs the same language as the box above. The values themselves are ',\n notIncludedHereFetch: 'not included here — fetch them if you need them.',\n positionCopiedPasteItInto: 'Position copied — paste it into an agent.',\n connectionNamed: 'Connection `{connection}`{dialect}.',\n defaultConnectionName: 'default',\n showingRows: { one: 'Showing {count} row{of}.', other: 'Showing {count} rows{of}.' },\n aPositionInTablewalk: '# {table} — a position in tablewalk',\n toReadThisRow: 'To read this row and everything pointing at it:',\n toSeeTheSame: 'To see the same rows:',\n});\n","/**\n * Hand the walk to an agent.\n *\n * The MCP server is the agent→database half of this tool. This is the other\n * half: a person walks to something interesting — three joins deep, a filter\n * they arrived at by clicking — and now wants an agent to carry on from\n * exactly there. Retyping the position into a chat window is where that\n * hand-off usually dies, because the query is the easy part and the shape,\n * the connection and the keys are the parts people leave out.\n *\n * What it writes is deliberately not the rows. The rows are on screen and\n * the person can copy them; pasting them into a chat window sends them\n * somewhere, and that should stay a decision rather than a side effect of\n * clicking \"copy\". What it writes is everything an agent needs to fetch the\n * rows itself, against the same connection, with the same question.\n */\n\n/** `name → type` with the markers the rest of the tool uses for keys and references. */\nimport { say } from './copy/workbench/handoff.js';\n\nfunction columnLine(column) {\n const parts = [column.name];\n if (column.type) parts.push(column.type);\n if (column.primaryKey) parts.push('key');\n if (column.nullable === false && !column.primaryKey) parts.push('not null');\n if (column.references) parts.push(`→ ${column.references.table}.${column.references.column}`);\n return `- ${parts.join(' · ')}`;\n}\n\n/**\n * The walk position as markdown, ready to paste into an agent.\n *\n * Pure so it can be pinned without a browser — the failure this feature has\n * is silently omitting a field, and the only way to see that is to read the\n * whole document a test wrote.\n */\nexport function agentContext(position) {\n const { connection, dialect, table, query, columns = [], shown, total, meta, trail = [], record } = position;\n const lines = [];\n\n lines.push(say('aPositionInTablewalk', { table }));\n lines.push('');\n lines.push(say('connectionNamed', { connection: connection ?? say('defaultConnectionName'), dialect: dialect ? ` (${dialect})` : '' }));\n lines.push('');\n\n if (record) {\n lines.push(say('oneRow', { table, label: record.label }));\n lines.push('');\n lines.push('```json');\n lines.push(JSON.stringify(record.key));\n lines.push('```');\n lines.push('');\n } else {\n lines.push(say('theQuestionOnScreen'));\n lines.push('');\n lines.push('```');\n lines.push(query);\n lines.push('```');\n lines.push('');\n }\n\n if (!record && typeof shown === 'number') {\n const of = typeof total === 'number' && total > shown ? say('ofMatching', { total: total.toLocaleString() }) : '';\n lines.push(say('showingRows', { count: shown, of }));\n lines.push('');\n }\n\n /* The columns the *query* returned, not the whole table: a projection is\n part of the position, and an agent handed the full table would widen the\n question without being told it had. */\n if (!record && columns.length) {\n lines.push(say('columnsOnScreen', { columns: columns.join(', ') }));\n lines.push('');\n }\n\n if (meta?.columns?.length) {\n lines.push(`## ${meta.id ?? table}`);\n lines.push('');\n for (const column of meta.columns) lines.push(columnLine(column));\n lines.push('');\n }\n\n /* How they got here. A filter arrived at by three clicks reads as one\n opaque `where` clause, and the trail is what makes it a story again. */\n if (trail.length > 1) {\n lines.push('## The walk');\n lines.push('');\n for (const step of trail) lines.push(`- ${step}`);\n lines.push('');\n }\n\n lines.push('## Carry on from here');\n lines.push('');\n lines.push(say('thisDatabaseIsAvailable', { next: record ? say('toReadThisRow') : say('toSeeTheSame') }));\n lines.push('');\n lines.push('```json');\n lines.push(\n JSON.stringify(\n record\n ? { name: 'record', arguments: { connection: connection ?? undefined, table, key: record.key } }\n : { name: 'query', arguments: { connection: connection ?? undefined, q: query, limit: shown ?? 20 } },\n null,\n 2,\n ),\n );\n lines.push('```');\n lines.push('');\n lines.push(say('tableDescribesATable') + say('queryRunsTheSame') + say('notIncludedHereFetch'));\n\n return lines.join('\\n');\n}\n\n/**\n * Everything the current walk knows about where it is.\n *\n * Reads `state` rather than taking it apart at each call site, because the\n * two callers — a result and a record — differ in one field and agreeing on\n * the other seven by hand is how they drift.\n */\nexport function positionOf(state, entry, extra = {}) {\n const meta = state.schema?.tables?.find((t) => t.id === entry.table) ?? null;\n return {\n connection: state.activeConnection ?? state.schema?.label ?? null,\n dialect: state.schema?.dialect ?? null,\n table: entry.table,\n query: entry.query ?? entry.table,\n meta,\n /* The trail as it reads in the breadcrumb: the query for a result, the\n row's name for a record. */\n trail: (state.stack ?? []).map((step) => (step.kind === 'table' ? step.query : step.label)),\n ...extra,\n };\n}\n\n/**\n * Copy a position, and say so.\n *\n * The clipboard can be refused — an insecure origin, a permission denied —\n * and a button that silently does nothing is worse than one that says it\n * could not. The text is not shown as a fallback because it is a page long.\n */\nexport async function copyPosition(position, toast) {\n try {\n await navigator.clipboard.writeText(agentContext(position));\n toast(say('positionCopiedPasteItInto'), 'info');\n } catch {\n toast(say('common.theBrowserRefusedClipboard'), 'error');\n }\n}\n","/**\n * Export the result you are looking at, as CSV or JSON.\n *\n * Both scopes are offered with their row count spelled out, because the\n * default page is 20 rows and a file called `invoice-2026-08-22.csv` gives\n * no hint that the other 49,980 rows were left behind. The serialisers and\n * the column memory stay in `export.js`, pure and tested without a browser;\n * this is the control over them, a part that loads on demand: ExportHold.tsx\n * holds its place until it has.\n */\nimport { useRef, useState } from 'react';\nimport { Button } from '@mistriwork/ui/Button.js';\nimport { Modal, type ModalHandle } from '@mistriwork/ui/Modal.js';\nimport { api, columnDisplay, findTable, pageSize, state, toast } from '../core.js';\nimport { minorToMajor, rowCurrency } from '@mistriwork/engine/shared/money.js';\nimport {\n EXPORT_MAX_ROWS,\n declaredColumns,\n orderColumns,\n rememberColumns,\n sameList,\n saveExport,\n selectionFor,\n streamExport,\n withColumns,\n} from '../export.js';\nimport { copyPosition, positionOf } from '../handoff.js';\nimport type { TableMeta } from '@mistriwork/engine/shared/schema.js';\nimport { DropItem, DropMenu } from '@mistriwork/ui/DropMenu.js';\nimport { defaultCurrency, formatNumber } from '../locale.js';\nimport { pageStepFor, stepList } from './pageStep.js';\nimport { shell } from '../shellMode.js';\nimport { say } from '../copy/workbench/components/Export.js';\n\ninterface ExportData {\n table: string;\n columns?: string[];\n rows?: Array<Record<string, unknown>>;\n total?: number;\n}\ninterface Entry {\n table: string;\n query: string;\n}\n\nexport function ExportControl({ entry, data }: { entry: Entry; data: ExportData }) {\n const displayed = data.columns ?? [];\n const declared = declaredColumns(data.table) as string[];\n const pageRows = data.rows ?? [];\n const total = data.total ?? pageRows.length;\n const capped = Math.min(total, EXPORT_MAX_ROWS as number);\n /* Last time's selection if there is one, otherwise exactly what is on\n screen. A pick is remembered in the store, so re-reading it after one\n is enough — the tick is only to make this component look again. */\n const [, tick] = useState(0);\n const selected = selectionFor(data.table, displayed, declared) as string[];\n const [open, setOpen] = useState(false);\n const [busy, setBusy] = useState<string | null>(null);\n const [choosing, setChoosing] = useState(false);\n\n /**\n * Fetch if the file would differ from what is on screen, then write it.\n * The re-fetch is the whole reason the chooser is safe to offer: picking\n * columns out of the rows already in hand would quietly export blanks for\n * any column the query never selected.\n */\n const deliver = async (kind: 'csv' | 'json', all: boolean) => {\n const onScreen = sameList(selected, displayed);\n let rows = pageRows;\n if (all || !onScreen) {\n setBusy(`${kind}:${all}`);\n try {\n /* Where it cannot stream, \"all\" is one bounded re-run rather than\n pages stitched together by the client. */\n const q = withColumns(entry.query, sameList(selected, declared) ? [] : selected);\n /* Streamed where the server offers it: one bounded read held whole\n was up to 19 MB of objects on the server and a long pause. */\n if (all && (await streamExport(kind, data.table, q, selected))) {\n setOpen(false);\n return;\n }\n /* This page is read the way the list read it: a step by key, where the pager took one (pageStep.ts). */\n const page = (state as { page: number }).page;\n const step = all\n ? undefined\n : pageStepFor(\n stepList((state as { activeConnection?: string }).activeConnection ?? '', entry.table, entry.query, pageSize()),\n page,\n );\n const fresh = await api('/api/run', {\n q,\n limit: all ? capped : (step?.limit ?? pageSize()),\n offset: all ? 0 : page * pageSize(),\n ...step?.body,\n });\n if (!fresh.columns) throw new Error(fresh.errors?.[0]?.message ?? say('thatQueryNoLonger'));\n rows = fresh.rows ?? [];\n } catch (err) {\n toast((err as Error).message, 'error');\n return;\n } finally {\n setBusy(null);\n }\n }\n setOpen(false);\n // `selected` rather than what came back, so the file follows the declared order.\n saveExport(kind, data.table, selected, kind === 'csv' ? sheetAmounts(data.table, selected, rows) : rows);\n };\n\n /* JSON and the agent hand-off are Workbench tools; an App's list offers the columns and the CSV. */\n const workbench = !shell().inApp();\n const kinds = workbench ? (['csv', 'json'] as const) : (['csv'] as const);\n const pageLabel = say('thisPageRows', { count: pageRows.length });\n const allLabel = say('allMatchingRows', { capped: formatNumber(capped) });\n return (\n <>\n <DropMenu\n className=\"export\"\n triggerClassName=\"export-summary\"\n triggerTitle={say('downloadThisResult')}\n label={\n <>\n <span className=\"export-word\">{say('export')}</span>\n <span className=\"export-more\" aria-hidden=\"true\">\n ⋯\n </span>\n </>\n }\n popupClassName=\"export-menu\"\n open={open}\n onOpenChange={setOpen}\n >\n <div className=\"export-group\">\n <DropItem className=\"export-item export-columns-item\" onClick={() => setChoosing(true)}>\n <span>{say('chooseColumns')}</span>\n <span className=\"export-count\">\n {declared.length ? say('selectedOfDeclared', { selected: selected.length, declared: declared.length }) : `${selected.length}`}\n </span>\n </DropItem>\n </div>\n {kinds.map((kind) => (\n <div key={kind} className=\"export-group\">\n <span className=\"export-kind\">{kind.toUpperCase()}</span>\n <DropItem\n className=\"export-item\"\n disabled={busy === `${kind}:false`}\n closeOnClick={false}\n onClick={() => void deliver(kind, false)}\n >\n {busy === `${kind}:false` ? say('fetching') : pageLabel}\n </DropItem>\n {/* Only worth offering when it would actually differ from the page. */}\n {total > pageRows.length && (\n <DropItem\n className=\"export-item\"\n disabled={busy === `${kind}:true`}\n closeOnClick={false}\n onClick={() => void deliver(kind, true)}\n >\n {busy === `${kind}:true` ? say('fetching') : allLabel}\n </DropItem>\n )}\n </div>\n ))}\n {/* Not a file, but the same question: take this away with you. An agent is the other place a result goes. */}\n {workbench && (\n <div className=\"export-group\">\n <span className=\"export-kind\">{say('agent')}</span>\n <DropItem\n className=\"export-item\"\n title={say('theQueryTheShape')}\n onClick={() => void copyPosition(positionOf(state, entry, { columns: displayed, shown: pageRows.length, total }), toast)}\n >\n {say('copyThisPosition')}\n </DropItem>\n </div>\n )}\n {total > (EXPORT_MAX_ROWS as number) && (\n <p className=\"export-note\">{say('cappedAtRowsTheSame', { max: formatNumber(EXPORT_MAX_ROWS as number) })}</p>\n )}\n </DropMenu>\n {choosing && (\n <ColumnChooser\n tableId={data.table}\n displayed={displayed}\n declared={declared}\n selected={selected}\n onClose={() => setChoosing(false)}\n onDone={(picked) => {\n rememberColumns(data.table, picked);\n tick((n) => n + 1);\n }}\n />\n )}\n </>\n );\n}\n\ninterface ChooserColumn {\n name: string;\n type?: string;\n primaryKey?: boolean;\n references?: unknown;\n}\n\n/**\n * The column chooser: a checklist rather than a multi-select, so every\n * column is visible at once with its type and its keys — which is the\n * information the choice actually turns on.\n */\nfunction ColumnChooser({\n tableId,\n displayed,\n declared,\n selected,\n onClose,\n onDone,\n}: {\n tableId: string;\n displayed: string[];\n declared: string[];\n selected: string[];\n onClose: () => void;\n onDone: (picked: string[]) => void;\n}) {\n const table = state.schema ? (findTable(tableId) as TableMeta | undefined) : null;\n /* A view whose columns the catalog does not carry still has the ones on\n screen, so the chooser degrades to those rather than opening empty. */\n const columns: ChooserColumn[] = table?.columns?.length\n ? table.columns\n : declared.concat(displayed.filter((n) => !declared.includes(n))).map((name) => ({ name }));\n const [ticked, setTicked] = useState(() => new Set(selected));\n const dialog = useRef<ModalHandle>(null);\n const setAll = (names: string[]) => setTicked(new Set(names));\n const toggle = (name: string, on: boolean) =>\n setTicked((was) => {\n const next = new Set(was);\n if (on) next.add(name);\n else next.delete(name);\n return next;\n });\n const use = () => {\n const picked = columns.map((c) => c.name).filter((n) => ticked.has(n));\n // A file of nothing but row separators is not an export.\n if (!picked.length) {\n toast(say('pickAtLeastOne'), 'error');\n return;\n }\n dialog.current?.close();\n onDone(orderColumns(picked, declared) as string[]);\n };\n return (\n <Modal className=\"confirm cols\" ref={dialog} onClose={onClose}>\n <h2>{say('columnsToExport')}</h2>\n <p>{say('tickedColumnsAreWritten')}</p>\n <div className=\"col-actions\">\n <Button size=\"compact\" onClick={() => setAll(columns.map((c) => c.name))}>\n {say('common.all')}\n </Button>\n <Button size=\"compact\" onClick={() => setAll([])}>\n {say('none')}\n </Button>\n <Button size=\"compact\" onClick={() => setAll(displayed)}>\n {say('asDisplayed')}\n </Button>\n <span className=\"col-count\">\n {say('ofSelected', { filterCount: columns.filter((c) => ticked.has(c.name)).length, columnsCount: columns.length })}\n </span>\n </div>\n <div className=\"col-list\">\n {columns.map((column) => (\n <label key={column.name} className=\"col-row\">\n <input type=\"checkbox\" checked={ticked.has(column.name)} onChange={(e) => toggle(column.name, e.currentTarget.checked)} />\n <span className=\"col-name\">{column.name}</span>\n {/* The same markers the result grid puts on its headers, so the two read as one vocabulary. */}\n {column.primaryKey ? <span className=\"pk\"> {say('common.key')}</span> : null}\n {column.references ? <span className=\"fk\"> →</span> : null}\n <span className=\"col-type\">{column.type ?? ''}</span>\n {displayed.includes(column.name) ? null : <span className=\"col-extra\">{say('notShown')}</span>}\n </label>\n ))}\n </div>\n <p className=\"confirm-params\">{say('rememberedForThisTable')}</p>\n <div className=\"confirm-actions\">\n <Button onClick={() => dialog.current?.close()}>{say('common.cancel')}</Button>\n <Button intent=\"primary\" onClick={use}>\n {say('useTheseColumns')}\n </Button>\n </div>\n </Modal>\n );\n}\n\ntype MinorRule = { format?: string; minor?: true; currency?: string; currencyColumn?: string };\n/**\n * A CSV says money as the figure a sheet reads, as the server's streamed file\n * does (exportRoute.ts csvAmounts), and here where it cannot stream — a tenant\n * lease, a row policy, this page: a column kept in minor units, 1999, is\n * written 19.99 in its row's own currency.\n */\nfunction sheetAmounts(tableId: string, columns: readonly string[], rows: Array<Record<string, unknown>>) {\n const minor = columns.flatMap((column) => {\n const rule = columnDisplay(tableId, column) as MinorRule | null;\n return rule?.format === 'money' && rule.minor ? [[column, rule] as const] : [];\n });\n if (!minor.length) return rows;\n return rows.map((row) => {\n const said = { ...row };\n for (const [column, rule] of minor)\n if (Object.hasOwn(row, column))\n said[column] = minorToMajor(row[column], rowCurrency(rule, row, rule.currency || defaultCurrency())) ?? row[column];\n return said;\n });\n}\n"],"mappings":";;;;;;;;;;;AAGA,IAAa,MAAM,eAAe;CAChC,QAAQ;CACR,qBAAqB;CACrB,YAAY;CACZ,iBAAiB;CACjB,yBAAyB;CACzB,sBAAsB;CACtB,kBAAkB;CAClB,sBAAsB;CACtB,2BAA2B;CAC3B,iBAAiB;CACjB,uBAAuB;CACvB,aAAa;EAAE,KAAK;EAA4B,OAAO;CAA4B;CACnF,sBAAsB;CACtB,eAAe;CACf,cAAc;AAChB,CAAC;;;;;;;;;;;;;;;;;;;ACCD,SAAS,WAAW,QAAQ;CAC1B,MAAM,QAAQ,CAAC,OAAO,IAAI;CAC1B,IAAI,OAAO,MAAM,MAAM,KAAK,OAAO,IAAI;CACvC,IAAI,OAAO,YAAY,MAAM,KAAK,KAAK;CACvC,IAAI,OAAO,aAAa,SAAS,CAAC,OAAO,YAAY,MAAM,KAAK,UAAU;CAC1E,IAAI,OAAO,YAAY,MAAM,KAAK,KAAK,OAAO,WAAW,MAAM,GAAG,OAAO,WAAW,QAAQ;CAC5F,OAAO,KAAK,MAAM,KAAK,KAAK;AAC9B;;;;;;;;AASA,SAAgB,aAAa,UAAU;CACrC,MAAM,EAAE,YAAY,SAAS,OAAO,OAAO,UAAU,CAAC,GAAG,OAAO,OAAO,MAAM,QAAQ,CAAC,GAAG,WAAW;CACpG,MAAM,QAAQ,CAAC;CAEf,MAAM,KAAK,IAAI,wBAAwB,EAAE,MAAM,CAAC,CAAC;CACjD,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,IAAI,mBAAmB;EAAE,YAAY,cAAc,IAAI,uBAAuB;EAAG,SAAS,UAAU,KAAK,QAAQ,KAAK;CAAG,CAAC,CAAC;CACtI,MAAM,KAAK,EAAE;CAEb,IAAI,QAAQ;EACV,MAAM,KAAK,IAAI,UAAU;GAAE;GAAO,OAAO,OAAO;EAAM,CAAC,CAAC;EACxD,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,SAAS;EACpB,MAAM,KAAK,KAAK,UAAU,OAAO,GAAG,CAAC;EACrC,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,EAAE;CACf,OAAO;EACL,MAAM,KAAK,IAAI,qBAAqB,CAAC;EACrC,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,EAAE;CACf;CAEA,IAAI,CAAC,UAAU,OAAO,UAAU,UAAU;EACxC,MAAM,KAAK,OAAO,UAAU,YAAY,QAAQ,QAAQ,IAAI,cAAc,EAAE,OAAO,MAAM,eAAe,EAAE,CAAC,IAAI;EAC/G,MAAM,KAAK,IAAI,eAAe;GAAE,OAAO;GAAO;EAAG,CAAC,CAAC;EACnD,MAAM,KAAK,EAAE;CACf;CAKA,IAAI,CAAC,UAAU,QAAQ,QAAQ;EAC7B,MAAM,KAAK,IAAI,mBAAmB,EAAE,SAAS,QAAQ,KAAK,IAAI,EAAE,CAAC,CAAC;EAClE,MAAM,KAAK,EAAE;CACf;CAEA,IAAI,MAAM,SAAS,QAAQ;EACzB,MAAM,KAAK,MAAM,KAAK,MAAM,OAAO;EACnC,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,UAAU,KAAK,SAAS,MAAM,KAAK,WAAW,MAAM,CAAC;EAChE,MAAM,KAAK,EAAE;CACf;CAIA,IAAI,MAAM,SAAS,GAAG;EACpB,MAAM,KAAK,aAAa;EACxB,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,KAAK,MAAM;EAChD,MAAM,KAAK,EAAE;CACf;CAEA,MAAM,KAAK,uBAAuB;CAClC,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,IAAI,2BAA2B,EAAE,MAAM,SAAS,IAAI,eAAe,IAAI,IAAI,cAAc,EAAE,CAAC,CAAC;CACxG,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,SAAS;CACpB,MAAM,KACJ,KAAK,UACH,SACI;EAAE,MAAM;EAAU,WAAW;GAAE,YAAY,cAAc,KAAA;GAAW;GAAO,KAAK,OAAO;EAAI;CAAE,IAC7F;EAAE,MAAM;EAAS,WAAW;GAAE,YAAY,cAAc,KAAA;GAAW,GAAG;GAAO,OAAO,SAAS;EAAG;CAAE,GACtG,MACA,CACF,CACF;CACA,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,IAAI,sBAAsB,IAAI,IAAI,kBAAkB,IAAI,IAAI,sBAAsB,CAAC;CAE9F,OAAO,MAAM,KAAK,IAAI;AACxB;;;;;;;;AASA,SAAgB,WAAW,OAAO,OAAO,QAAQ,CAAC,GAAG;CACnD,MAAM,OAAO,MAAM,QAAQ,QAAQ,MAAM,MAAM,EAAE,OAAO,MAAM,KAAK,KAAK;CACxE,OAAO;EACL,YAAY,MAAM,oBAAoB,MAAM,QAAQ,SAAS;EAC7D,SAAS,MAAM,QAAQ,WAAW;EAClC,OAAO,MAAM;EACb,OAAO,MAAM,SAAS,MAAM;EAC5B;EAGA,QAAQ,MAAM,SAAS,CAAC,EAAA,CAAG,KAAK,SAAU,KAAK,SAAS,UAAU,KAAK,QAAQ,KAAK,KAAM;EAC1F,GAAG;CACL;AACF;;;;;;;;AASA,eAAsB,aAAa,UAAU,OAAO;CAClD,IAAI;EACF,MAAM,UAAU,UAAU,UAAU,aAAa,QAAQ,CAAC;EAC1D,MAAM,IAAI,2BAA2B,GAAG,MAAM;CAChD,QAAQ;EACN,MAAM,IAAI,mCAAmC,GAAG,OAAO;CACzD;AACF;;;;;;;;;;;;ACvGA,SAAgB,cAAc,EAAE,OAAO,QAA4C;CACjF,MAAM,YAAY,KAAK,WAAW,CAAC;CACnC,MAAM,WAAW,gBAAgB,KAAK,KAAK;CAC3C,MAAM,WAAW,KAAK,QAAQ,CAAC;CAC/B,MAAM,QAAQ,KAAK,SAAS,SAAS;CACrC,MAAM,SAAS,KAAK,IAAI,OAAO,eAAyB;CAIxD,MAAM,GAAG,QAAQ,SAAS,CAAC;CAC3B,MAAM,WAAW,aAAa,KAAK,OAAO,WAAW,QAAQ;CAC7D,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,CAAC,MAAM,WAAW,SAAwB,IAAI;CACpD,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;;;;;;;CAQ9C,MAAM,UAAU,OAAO,MAAsB,QAAiB;EAC5D,MAAM,WAAW,SAAS,UAAU,SAAS;EAC7C,IAAI,OAAO;EACX,IAAI,OAAO,CAAC,UAAU;GACpB,QAAQ,GAAG,KAAK,GAAG,KAAK;GACxB,IAAI;IAGF,MAAM,IAAI,YAAY,MAAM,OAAO,SAAS,UAAU,QAAQ,IAAI,CAAC,IAAI,QAAQ;IAG/E,IAAI,OAAQ,MAAM,aAAa,MAAM,KAAK,OAAO,GAAG,QAAQ,GAAI;KAC9D,QAAQ,KAAK;KACb;IACF;IAEA,MAAM,OAAQ,MAA2B;IACzC,MAAM,OAAO,MACT,KAAA,IACA,YACE,SAAU,MAAwC,oBAAoB,IAAI,MAAM,OAAO,MAAM,OAAO,SAAS,CAAC,GAC9G,IACF;IACJ,MAAM,QAAQ,MAAM,IAAI,YAAY;KAClC;KACA,OAAO,MAAM,SAAU,MAAM,SAAS,SAAS;KAC/C,QAAQ,MAAM,IAAI,OAAO,SAAS;KAClC,GAAG,MAAM;IACX,CAAC;IACD,IAAI,CAAC,MAAM,SAAS,MAAM,IAAI,MAAM,MAAM,SAAS,EAAE,EAAE,WAAWA,MAAI,mBAAmB,CAAC;IAC1F,OAAO,MAAM,QAAQ,CAAC;GACxB,SAAS,KAAK;IACZ,MAAO,IAAc,SAAS,OAAO;IACrC;GACF,UAAU;IACR,QAAQ,IAAI;GACd;EACF;EACA,QAAQ,KAAK;EAEb,WAAW,MAAM,KAAK,OAAO,UAAU,SAAS,QAAQ,aAAa,KAAK,OAAO,UAAU,IAAI,IAAI,IAAI;CACzG;CAGA,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM;CACjC,MAAM,QAAQ,YAAa,CAAC,OAAO,MAAM,IAAe,CAAC,KAAK;CAC9D,MAAM,YAAYA,MAAI,gBAAgB,EAAE,OAAO,SAAS,OAAO,CAAC;CAChE,MAAM,WAAWA,MAAI,mBAAmB,EAAE,QAAQ,aAAa,MAAM,EAAE,CAAC;CACxE,OACE,qBAAAC,YAAA,EAAA,UAAA,CACE,qBAAC,UAAD;EACE,WAAU;EACV,kBAAiB;EACjB,cAAcD,MAAI,oBAAoB;EACtC,OACE,qBAAAC,YAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,WAAU;GAAe,UAAAD,MAAI,QAAQ;EAAQ,CAAA,GACnD,oBAAC,QAAD;GAAM,WAAU;GAAc,eAAY;GAAO,UAAA;EAE3C,CAAA,CACN,EAAA,CAAA;EAEJ,gBAAe;EACT;EACN,cAAc;EAdhB,UAAA;GAgBE,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,qBAAC,UAAD;KAAU,WAAU;KAAkC,eAAe,YAAY,IAAI;KAArF,UAAA,CACE,oBAAC,QAAD,EAAA,UAAOA,MAAI,eAAe,EAAQ,CAAA,GAClC,oBAAC,QAAD;MAAM,WAAU;MACb,UAAA,SAAS,SAASA,MAAI,sBAAsB;OAAE,UAAU,SAAS;OAAQ,UAAU,SAAS;MAAO,CAAC,IAAI,GAAG,SAAS;KACjH,CAAA,CACE;;GACP,CAAA;GACJ,MAAM,KAAK,SACV,qBAAC,OAAD;IAAgB,WAAU;IAA1B,UAAA;KACE,oBAAC,QAAD;MAAM,WAAU;MAAe,UAAA,KAAK,YAAY;KAAQ,CAAA;KACxD,oBAAC,UAAD;MACE,WAAU;MACV,UAAU,SAAS,GAAG,KAAK;MAC3B,cAAc;MACd,eAAe,KAAK,QAAQ,MAAM,KAAK;MAEtC,UAAA,SAAS,GAAG,KAAK,UAAUA,MAAI,UAAU,IAAI;KACtC,CAAA;KAET,QAAQ,SAAS,UAChB,oBAAC,UAAD;MACE,WAAU;MACV,UAAU,SAAS,GAAG,KAAK;MAC3B,cAAc;MACd,eAAe,KAAK,QAAQ,MAAM,IAAI;MAErC,UAAA,SAAS,GAAG,KAAK,SAASA,MAAI,UAAU,IAAI;KACrC,CAAA;IAET;GArBK,GAAA,IAqBL,CACN;GAEA,aACC,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,QAAD;KAAM,WAAU;KAAe,UAAAA,MAAI,OAAO;IAAQ,CAAA,GAClD,oBAAC,UAAD;KACE,WAAU;KACV,OAAOA,MAAI,kBAAkB;KAC7B,eAAe,KAAK,aAAa,WAAW,OAAO,OAAO;MAAE,SAAS;MAAW,OAAO,SAAS;MAAQ;KAAM,CAAC,GAAG,KAAK;KAEtH,UAAAA,MAAI,kBAAkB;IACf,CAAA,CACP;;GAEN,QAAS,mBACR,oBAAC,KAAD;IAAG,WAAU;IAAe,UAAAA,MAAI,uBAAuB,EAAE,KAAK,aAAa,eAAyB,EAAE,CAAC;GAAK,CAAA;EAEtG;CACT,CAAA,GAAA,YACC,oBAAC,eAAD;EACE,SAAS,KAAK;EACH;EACD;EACA;EACV,eAAe,YAAY,KAAK;EAChC,SAAS,WAAW;GAClB,gBAAgB,KAAK,OAAO,MAAM;GAClC,MAAM,MAAM,IAAI,CAAC;EACnB;CACD,CAAA,CAEH,EAAA,CAAA;AAEN;;;;;;AAcA,SAAS,cAAc,EACrB,SACA,WACA,UACA,UACA,SACA,UAQC;CACD,MAAM,QAAQ,MAAM,SAAU,UAAU,OAAO,IAA8B;CAG7E,MAAM,UAA2B,OAAO,SAAS,SAC7C,MAAM,UACN,SAAS,OAAO,UAAU,QAAQ,MAAM,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,KAAK,EAAE;CAC5F,MAAM,CAAC,QAAQ,aAAa,eAAe,IAAI,IAAI,QAAQ,CAAC;CAC5D,MAAM,SAAS,OAAoB,IAAI;CACvC,MAAM,UAAU,UAAoB,UAAU,IAAI,IAAI,KAAK,CAAC;CAC5D,MAAM,UAAU,MAAc,OAC5B,WAAW,QAAQ;EACjB,MAAM,OAAO,IAAI,IAAI,GAAG;EACxB,IAAI,IAAI,KAAK,IAAI,IAAI;OAChB,KAAK,OAAO,IAAI;EACrB,OAAO;CACT,CAAC;CACH,MAAM,YAAY;EAChB,MAAM,SAAS,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,QAAQ,MAAM,OAAO,IAAI,CAAC,CAAC;EAErE,IAAI,CAAC,OAAO,QAAQ;GAClB,MAAMA,MAAI,gBAAgB,GAAG,OAAO;GACpC;EACF;EACA,OAAO,SAAS,MAAM;EACtB,OAAO,aAAa,QAAQ,QAAQ,CAAa;CACnD;CACA,OACE,qBAAC,OAAD;EAAO,WAAU;EAAe,KAAK;EAAiB;EAAtD,UAAA;GACE,oBAAC,MAAD,EAAA,UAAKA,MAAI,iBAAiB,EAAM,CAAA;GAChC,oBAAC,KAAD,EAAA,UAAIA,MAAI,yBAAyB,EAAK,CAAA;GACtC,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,QAAD;MAAQ,MAAK;MAAU,eAAe,OAAO,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC;MACpE,UAAAA,MAAI,YAAY;KACX,CAAA;KACR,oBAAC,QAAD;MAAQ,MAAK;MAAU,eAAe,OAAO,CAAC,CAAC;MAC5C,UAAAA,MAAI,MAAM;KACL,CAAA;KACR,oBAAC,QAAD;MAAQ,MAAK;MAAU,eAAe,OAAO,SAAS;MACnD,UAAAA,MAAI,aAAa;KACZ,CAAA;KACR,oBAAC,QAAD;MAAM,WAAU;MACb,UAAAA,MAAI,cAAc;OAAE,aAAa,QAAQ,QAAQ,MAAM,OAAO,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;OAAQ,cAAc,QAAQ;MAAO,CAAC;KAC9G,CAAA;IACH;;GACL,oBAAC,OAAD;IAAK,WAAU;IACZ,UAAA,QAAQ,KAAK,WACZ,qBAAC,SAAD;KAAyB,WAAU;KAAnC,UAAA;MACE,oBAAC,SAAD;OAAO,MAAK;OAAW,SAAS,OAAO,IAAI,OAAO,IAAI;OAAG,WAAW,MAAM,OAAO,OAAO,MAAM,EAAE,cAAc,OAAO;MAAI,CAAA;MACzH,oBAAC,QAAD;OAAM,WAAU;OAAY,UAAA,OAAO;MAAW,CAAA;MAE7C,OAAO,aAAa,qBAAC,QAAD;OAAM,WAAU;OAAhB,UAAA,CAAqB,KAAEA,MAAI,YAAY,CAAQ;MAAI,CAAA,IAAA;MACvE,OAAO,aAAa,oBAAC,QAAD;OAAM,WAAU;OAAK,UAAA;MAAQ,CAAA,IAAI;MACtD,oBAAC,QAAD;OAAM,WAAU;OAAY,UAAA,OAAO,QAAQ;MAAS,CAAA;MACnD,UAAU,SAAS,OAAO,IAAI,IAAI,OAAO,oBAAC,QAAD;OAAM,WAAU;OAAa,UAAAA,MAAI,UAAU;MAAQ,CAAA;KACxF;IARK,GAAA,OAAO,IAQZ,CACR;GACE,CAAA;GACL,oBAAC,KAAD;IAAG,WAAU;IAAkB,UAAAA,MAAI,wBAAwB;GAAK,CAAA;GAChE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,QAAD;KAAQ,eAAe,OAAO,SAAS,MAAM;KAAI,UAAAA,MAAI,eAAe;IAAU,CAAA,GAC9E,oBAAC,QAAD;KAAQ,QAAO;KAAU,SAAS;KAC/B,UAAAA,MAAI,iBAAiB;IAChB,CAAA,CACL;;EACA;;AAEX;;;;;;;AASA,SAAS,aAAa,SAAiB,SAA4B,MAAsC;CACvG,MAAM,QAAQ,QAAQ,SAAS,WAAW;EACxC,MAAM,OAAO,cAAc,SAAS,MAAM;EAC1C,OAAO,MAAM,WAAW,WAAW,KAAK,QAAQ,CAAC,CAAC,QAAQ,IAAI,CAAU,IAAI,CAAC;CAC/E,CAAC;CACD,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OAAO,KAAK,KAAK,QAAQ;EACvB,MAAM,OAAO,EAAE,GAAG,IAAI;EACtB,KAAK,MAAM,CAAC,QAAQ,SAAS,OAC3B,IAAI,OAAO,OAAO,KAAK,MAAM,GAC3B,KAAK,UAAU,aAAa,IAAI,SAAS,YAAY,MAAM,KAAK,KAAK,YAAY,gBAAgB,CAAC,CAAC,KAAK,IAAI;EAChH,OAAO;CACT,CAAC;AACH"}
@@ -1,8 +1,8 @@
1
1
  import { A as tenantLeaseActive, D as subscribeSessionIdentity, E as sessionIdentity, ht as Icon, w as captureSessionIdentity } from "./shared-poc11xxo.js";
2
2
  import { At as removeHostListener, Ct as addHostListener, gt as defineMessages } from "./shared-DJZjSi3J.js";
3
- import { $ as stepWithin, Q as stepTrail, Z as canStep, d as findTable, f as go, i as api, j as rowLabel, l as currentEntry, p as keyOf, tt as trailPosition } from "./shared-DPAVoWY0.js";
3
+ import { $ as stepWithin, Q as stepTrail, Z as canStep, d as findTable, f as go, i as api, j as rowLabel, l as currentEntry, p as keyOf, tt as trailPosition } from "./shared-BoLbc2WD.js";
4
4
  import { n as commandKeyText } from "./shared-Du2YAQk4.js";
5
- import "./shared-C-J32eBn.js";
5
+ import "./shared-CL3RLKDA.js";
6
6
  import { useEffect, useRef, useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  // packages/kit/src/client/copy/record/components/RecordTrailNav.ts
@@ -462,4 +462,4 @@ function settleFailure(operation, value, endpoint) {
462
462
  }
463
463
  export { RecordTrailNav as n, takesSingleKeys as r, commandOperations as t };
464
464
 
465
- //# sourceMappingURL=shared-DsxGbYlV.js.map
465
+ //# sourceMappingURL=shared-Docl_0tq.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-DsxGbYlV.js","names":["say","say","listeners","revision","changed"],"sources":["../../../kit/src/client/copy/record/components/RecordTrailNav.ts","../../../kit/src/client/components/RecordTrailNav.tsx","../../src/client/tenantMutationState.ts","../../src/client/copy/forms/commandState.ts","../../src/client/commandState.ts"],"sourcesContent":["/** What RecordTrailNav says, in the record page room (packages/kit/src/client/messages.ts says how the catalog is split). */\nimport { defineMessages } from '../../../messages.js';\n\nexport const say = defineMessages({\n recordsInThisList: 'Records in this list',\n previousRecord: 'Previous record',\n previousRecordK: 'Previous record (K)',\n nextRecord: 'Next record',\n nextRecordJ: 'Next record (J)',\n atOfOf: '{at} of {of}',\n});\n","/**\n * Where a record sits in the list it was opened from, and the way to its\n * neighbors: \"3 of 20\" between Previous and Next beside the record's name,\n * and J (next) / K (previous) from the keyboard while it is on screen.\n *\n * The keys are the ones file lists and mail readers use for the same move;\n * single letters, so they stand aside for a text field, a dialog over the\n * page, and any modifier, as the page's other single keys do (app.js). A\n * step replaces the record in the walk rather than adding one, so Back\n * returns to the list, not through every record read on the way. Nothing\n * animates (docs/design.md: keyboard navigation moves nothing), and the\n * position is a polite live region, so the new place is said.\n *\n * The step and the controls are shared with the glance and the record\n * dialog (PeekPanel, RecordModal): one page reader, one guard against a\n * late page, one set of buttons, on every surface that steps.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { api, currentEntry, findTable, go, keyOf, rowLabel } from '../core.js';\nimport { canStep, stepTrail, stepWithin, trailPosition, type RecordTrail, type TrailPageReader } from '../recordTrail.js';\nimport { say } from '../copy/record/components/RecordTrailNav.js';\nimport { Icon } from './Icon.js';\nimport { addHostListener, removeHostListener } from '../shellHost.js';\n\ninterface TrailEntry {\n kind: string;\n table: string;\n key?: Record<string, unknown>;\n label?: string | null;\n trail?: RecordTrail;\n}\n\n/** One page of the list's own question, as the records a step can land on. */\nconst readPage: TrailPageReader = async (tableId, list, offset) => {\n const table = findTable(tableId);\n const data = (await api('/api/run', { q: list.query, limit: list.limit, offset, count: 'none' })) as {\n rows?: Array<Record<string, unknown>>;\n };\n if (!table || !Array.isArray(data?.rows)) return [];\n return data.rows.flatMap((row) => {\n const key = keyOf(table, row) as Record<string, unknown> | null;\n const label = rowLabel(table, row) as string | null;\n return key ? [{ key, ...(label ? { label } : {}) }] : [];\n });\n};\n\n/**\n * A step along a trail. `land` puts the next record in front of the reader;\n * `stillHere` says whether the surface that asked is still the one on\n * screen, so a page that arrives after the reader has moved on lands nowhere.\n * A step within the page reads nothing and lands at once.\n */\nexport function useTrailStep(trail: RecordTrail | undefined, land: (next: RecordTrail) => void, stillHere: () => boolean) {\n const [reading, setReading] = useState(false);\n const busy = useRef(false);\n const step = async (by: 1 | -1) => {\n if (!trail || busy.current || !canStep(trail, by)) return;\n const within = stepWithin(trail, by);\n if (within) {\n land(within);\n return;\n }\n busy.current = true;\n setReading(true);\n try {\n const next = await stepTrail(trail, by, readPage);\n if (next && stillHere()) land(next);\n } catch {\n /* A page that cannot be read leaves the reader where they are; the buttons stay. */\n } finally {\n busy.current = false;\n setReading(false);\n }\n };\n return { step, reading };\n}\n\n/** \"3 of 20\" between Previous and Next — or \"3\" where the list has no total. */\nexport function TrailSteps({\n trail,\n reading,\n onStep,\n onPointerDown,\n}: {\n trail: RecordTrail;\n reading: boolean;\n onStep: (by: 1 | -1) => void;\n /** The record dialog settles an autosave before the pointer's click moves it on. */\n onPointerDown?: () => void;\n}) {\n const { at, of } = trailPosition(trail);\n return (\n <nav className=\"record-trail\" aria-label={say('recordsInThisList')} aria-busy={reading || undefined}>\n <span className=\"step-control\">\n <button\n type=\"button\"\n className=\"step-button record-trail-step\"\n aria-label={say('previousRecord')}\n title={say('previousRecordK')}\n aria-keyshortcuts=\"K\"\n disabled={!canStep(trail, -1)}\n onPointerDown={onPointerDown}\n onClick={() => onStep(-1)}\n >\n <Icon name=\"caret-left\" size={16} />\n </button>\n <span className=\"step-position record-trail-at\" aria-live=\"polite\">\n {of === undefined ? `${at}` : say('atOfOf', { at, of })}\n </span>\n <button\n type=\"button\"\n className=\"step-button record-trail-step\"\n aria-label={say('nextRecord')}\n title={say('nextRecordJ')}\n aria-keyshortcuts=\"J\"\n disabled={!canStep(trail, 1)}\n onPointerDown={onPointerDown}\n onClick={() => onStep(1)}\n >\n <Icon name=\"caret-right\" size={16} />\n </button>\n </span>\n </nav>\n );\n}\n\n/** A key the page's single-letter shortcuts must not take: typing, a dialog over the page, or a modifier. */\nexport function takesSingleKeys(event: KeyboardEvent): boolean {\n if (event.metaKey || event.ctrlKey || event.altKey || event.defaultPrevented) return false;\n const active = document.activeElement as HTMLElement | null;\n if (active && (/^(input|textarea|select)$/i.test(active.tagName) || active.isContentEditable)) return false;\n return !active?.closest?.('[role=\"dialog\"], dialog[open], .help-panel, .palette, .conn-manager');\n}\n\n/** Any walk entry: a record page's or a row's, a split's detail included. Draws nothing where the record was not opened from a list. */\nexport function RecordTrailNav({ entry }: { entry: object }) {\n const walk = entry as TrailEntry;\n const trail = walk.trail;\n const { step, reading } = useTrailStep(\n trail,\n (next) => {\n const record = next.records[next.index];\n go({ ...walk, key: record.key, label: record.label, trail: next }, 'replace');\n },\n () => currentEntry() === entry,\n );\n const stepRef = useRef(step);\n stepRef.current = step;\n useEffect(() => {\n if (!trail) return;\n const onKey = (event: KeyboardEvent) => {\n if ((event.key !== 'j' && event.key !== 'k') || !takesSingleKeys(event)) return;\n event.preventDefault();\n void stepRef.current(event.key === 'j' ? 1 : -1);\n };\n addHostListener(document, 'keydown', onKey);\n return () => removeHostListener(document, 'keydown', onKey);\n }, [trail]);\n if (!trail) return null;\n return <TrailSteps trail={trail} reading={reading} onStep={(by) => void step(by)} />;\n}\n","import {\n captureSessionIdentity,\n sessionIdentity,\n subscribeSessionIdentity,\n tenantLeaseActive,\n type SessionIdentitySnapshot,\n} from '@mistriwork/kit/client/sessionIdentity.js';\n\nexport type TenantMutationKind = 'update' | 'insert' | 'delete' | 'command';\nexport interface TenantMutationAttempt {\n readonly id: symbol;\n readonly kind: TenantMutationKind;\n readonly identity: SessionIdentitySnapshot;\n}\n\nconst kinds: readonly TenantMutationKind[] = ['update', 'insert', 'delete', 'command'];\nconst attempts = new Map<TenantMutationAttempt, 'pending' | 'unknown'>();\nconst listeners = new Set<() => void>();\nlet owner = captureSessionIdentity();\nlet revision = 0;\n\nfunction changed(): void {\n revision++;\n for (const listener of [...listeners]) {\n // A broken observer must not lose a newly started handle or veto security\n // retirement (including the remaining session-identity subscribers).\n try {\n listener();\n } catch {\n /* Observers do not own mutation lifetime. */\n }\n }\n}\n\nfunction syncOwner(): void {\n const current = sessionIdentity();\n if (current === owner) return;\n owner = current;\n if (!attempts.size) return;\n attempts.clear();\n changed();\n}\nsubscribeSessionIdentity(syncOwner);\n\nfunction live(attempt: TenantMutationAttempt | undefined): attempt is TenantMutationAttempt {\n syncOwner();\n return attempt !== undefined && attempts.has(attempt) && attempt.identity === sessionIdentity();\n}\n\n/** Tab-memory exit guard only, never write authority or a command-envelope\n * store. Unmounting a subscriber does not settle work. Identity retirement\n * drops all entries immediately, without claiming any request rolled back. */\nexport const tenantMutationState = Object.freeze({\n start(kind: TenantMutationKind, identity = captureSessionIdentity()): TenantMutationAttempt | undefined {\n syncOwner();\n if (\n identity !== sessionIdentity() ||\n identity.mode !== 'authenticated' ||\n !identity.tenantRequired ||\n !identity.tenant ||\n !kinds.includes(kind)\n )\n return;\n if (!tenantLeaseActive(identity) || identity !== sessionIdentity()) return;\n const attempt = Object.freeze({ id: Symbol('tenant-mutation'), kind, identity });\n attempts.set(attempt, 'pending');\n changed();\n return attempt;\n },\n unknown(attempt: TenantMutationAttempt | undefined): boolean {\n if (!live(attempt) || attempts.get(attempt) === 'unknown') return false;\n attempts.set(attempt, 'unknown');\n changed();\n return true;\n },\n /** Caller has verified success or definite refusal for this exact attempt.\n * In particular, a command retry's refusal does not settle an earlier unknown\n * execution; commandOperations remains responsible for that distinction. */\n settle(attempt: TenantMutationAttempt | undefined): boolean {\n if (!live(attempt)) return false;\n attempts.delete(attempt);\n changed();\n return true;\n },\n hasUnresolved(identity = captureSessionIdentity()): boolean {\n syncOwner();\n return identity === sessionIdentity() && attempts.size > 0;\n },\n version(): number {\n syncOwner();\n return revision;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n});\n","/** What commandState says, in the forms 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 signInAgainBefore: 'Sign in again before continuing.',\n thisRecordHasAnUnsupported: 'This record has an unsupported key.',\n refreshThisRecordBefore: 'Refresh this record before continuing.',\n refreshTheseRecordsBefore: 'Refresh these records before continuing.',\n checkingResult: 'Checking result…',\n completed: 'Completed.',\n completedCloseAndRefresh: 'Completed. Close and refresh the record to see the changes.',\n theResultIsNotYet: 'The result is not yet confirmed. Check the result or retry this same submission. Keep this tab open.',\n});\n","import {\n captureSessionIdentity,\n sessionIdentity,\n subscribeSessionIdentity,\n type SessionIdentitySnapshot,\n} from '@mistriwork/kit/client/sessionIdentity.js';\nimport type { AppCommandAction } from '../shared/app.js';\nimport { BULK_LIMIT, commandKeyText } from '../shared/command.js';\nimport { tenantMutationState, type TenantMutationAttempt } from './tenantMutationState.js';\nimport { say } from './copy/forms/commandState.js';\n\n/** One input's value as a form holds it; several choices are a list of option values. */\nexport type CommandValue = string | number | boolean | readonly (string | number)[];\n/** An input as the server takes it: values, objects for groups, lists of rows for repeating groups. */\nexport type CommandInputValue = Readonly<Record<string, unknown>>;\n/** One target (`key`, `expectedVersion`) or, for a `targets: 'many'` command, an ordered list. */\ninterface CommandEnvelope {\n command: string;\n commandVersion: number;\n key?: Record<string, string | number>;\n expectedVersion?: number;\n keys?: ReadonlyArray<Record<string, string | number>>;\n expectedVersions?: readonly number[];\n idempotencyKey: string;\n input: CommandInputValue;\n}\nexport interface CommandOwner {\n connection: string;\n target: string;\n command: string;\n commandVersion: number;\n key: Record<string, unknown>;\n /** A group: every selected record, in order. `key` is then empty. */\n keys?: Array<Record<string, unknown>>;\n /** What each of `keys` is called, so a resumed group still names its records. Not identity. */\n labels?: string[];\n}\nexport interface CommandOperation {\n readonly identity: SessionIdentitySnapshot;\n readonly owner: CommandOwner;\n readonly envelope: CommandEnvelope;\n readonly action?: AppCommandAction;\n phase: 'pending' | 'success' | 'error' | 'unknown';\n message: string;\n /** The command's own words for what it did, when its output said any: a command run on one click has no form to\n say them in, so its notice does (components/Actions.tsx). */\n said?: string;\n /** What the command's handler returned (the receipt's `result`), handed to a component that ran it (`useCommand`). */\n output?: unknown;\n code?: string;\n /** The one record of a group the refusal named, when it named one. */\n refusedKey?: Record<string, unknown>;\n /** The input the refusal is about, by its path, when it named one: the form says it beside that field. */\n field?: string;\n /** Once delivery is uncertain, a later missing receipt/refusal cannot prove rollback. */\n uncertain: boolean;\n /** Sent by a component's `run`, or from the form its `open` holds: its caller resolves it, so a record's action bar\n must not draw its form while it is in flight (a composer's reply would show twice). */\n headless?: boolean;\n}\n\n// A new manifest version must not open a second operation over an unresolved submission.\nconst keyPairs = (key: Record<string, unknown>) =>\n Object.keys(key)\n .sort()\n .map((column) => [column, String(key[column])]);\nconst ownerKey = (owner: CommandOwner) =>\n JSON.stringify([owner.connection, owner.target, owner.command, owner.keys ? owner.keys.map(keyPairs) : keyPairs(owner.key)]);\nconst operations = new Map<string, CommandOperation>();\n/* When a command last applied to each record, as a count of applied commands:\n an open form prepared before that number moved holds a revision it knows\n is old (CommandForm prepares again before it lets a submit through). */\nconst recordKey = (connection: string, target: string, key: Record<string, unknown>) => JSON.stringify([connection, target, keyPairs(key)]);\nconst appliedTo = new Map<string, number>();\nlet applied = 0;\nconst tracked = new WeakMap<CommandOperation, TenantMutationAttempt>();\nconst listeners = new Set<() => void>();\nlet revision = 0;\nfunction changed() {\n revision++;\n for (const listener of listeners) listener();\n}\nsubscribeSessionIdentity(() => {\n operations.clear();\n appliedTo.clear();\n changed();\n});\n\nexport const commandOperations = {\n subscribe(listener: () => void) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n version: () => revision,\n /** The count of applied commands at the last one that applied to any of these records (0: none has). */\n appliedOn(connection: string, target: string, keys: ReadonlyArray<Record<string, unknown>>): number {\n return Math.max(0, ...keys.map((key) => appliedTo.get(recordKey(connection, target, key)) ?? 0));\n },\n get(owner: CommandOwner): CommandOperation | undefined {\n return operations.get(ownerKey(owner));\n },\n /** Group submissions on this list that are still pending or unconfirmed: a queue reopens their form. */\n unresolvedGroups(connection: string, target: string): CommandOperation[] {\n return [...operations.values()].filter(\n (operation) =>\n operation.owner.keys &&\n operation.owner.connection === connection &&\n operation.owner.target === target &&\n (operation.phase === 'pending' || operation.phase === 'unknown'),\n );\n },\n forget(owner: CommandOwner) {\n const operation = operations.get(ownerKey(owner));\n if (operation?.phase === 'pending' || operation?.phase === 'unknown') return;\n operations.delete(ownerKey(owner));\n changed();\n },\n /** A group owner takes one prepared revision per key, aligned with `owner.keys`. */\n start(\n owner: CommandOwner,\n expectedVersion: number | readonly number[],\n input: CommandInputValue,\n action?: AppCommandAction,\n headless = false,\n ): CommandOperation {\n const identity = captureSessionIdentity();\n if (identity.mode !== 'authenticated' || identity.appId !== owner.connection) throw new Error(say('signInAgainBefore'));\n const previous = operations.get(ownerKey(owner));\n if (previous) return previous;\n const checked = (raw: Record<string, unknown>) => {\n const key: Record<string, string | number> = {};\n for (const [column, value] of Object.entries(raw)) {\n if (typeof value !== 'string' && !(typeof value === 'number' && Number.isSafeInteger(value)))\n throw new Error(say('thisRecordHasAnUnsupported'));\n key[column] = value;\n }\n if (!Object.keys(key).length) throw new Error(say('refreshThisRecordBefore'));\n return Object.freeze(key);\n };\n const revision = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;\n let targets: Pick<CommandEnvelope, 'key' | 'expectedVersion' | 'keys' | 'expectedVersions'>;\n if (owner.keys) {\n const versions = Array.isArray(expectedVersion) ? [...(expectedVersion as readonly number[])] : [];\n const keys = owner.keys.map(checked);\n if (\n !keys.length ||\n keys.length > BULK_LIMIT ||\n versions.length !== keys.length ||\n !versions.every(revision) ||\n new Set(keys.map(commandKeyText)).size !== keys.length\n )\n throw new Error(say('refreshTheseRecordsBefore'));\n targets = { keys: Object.freeze(keys), expectedVersions: Object.freeze(versions) };\n } else {\n const key = checked(owner.key);\n if (!revision(expectedVersion)) throw new Error(say('refreshThisRecordBefore'));\n targets = { key, expectedVersion };\n }\n const envelope: CommandEnvelope = Object.freeze({\n command: owner.command,\n commandVersion: owner.commandVersion,\n ...targets,\n idempotencyKey: crypto.randomUUID(),\n input: frozenCopy(input) as CommandInputValue,\n });\n const presentation: AppCommandAction | undefined = action && JSON.parse(JSON.stringify(action));\n if (presentation) {\n presentation.command.input.forEach(Object.freeze);\n Object.freeze(presentation.command.input);\n Object.freeze(presentation.command);\n Object.freeze(presentation);\n }\n const operation: CommandOperation = {\n identity,\n envelope,\n action: presentation,\n owner: Object.freeze(\n envelope.keys\n ? { ...owner, key: {}, keys: [...envelope.keys], labels: owner.labels && [...owner.labels] }\n : { ...owner, key: envelope.key! },\n ),\n phase: 'pending',\n message: say('common.submitting'),\n uncertain: false,\n ...(headless ? { headless } : {}),\n };\n operations.set(ownerKey(owner), operation);\n changed();\n return operation;\n },\n async send(\n operation: CommandOperation,\n endpoint: 'execute' | 'status',\n request: (path: string, envelope: CommandEnvelope) => Promise<unknown>,\n onApplied: () => void,\n ): Promise<void> {\n const current = () => sessionIdentity() === operation.identity && operations.get(ownerKey(operation.owner)) === operation;\n if (!current() || operation.phase === 'success' || operation.phase === 'error') return;\n // The caller dispatches the initial pending operation once; retries only follow a settled unknown.\n if (inFlight.has(operation)) return;\n if (!tracked.has(operation)) {\n const attempt = tenantMutationState.start('command', operation.identity);\n if (attempt) tracked.set(operation, attempt);\n }\n inFlight.add(operation);\n operation.phase = 'pending';\n operation.message = endpoint === 'status' ? say('checkingResult') : say('common.submitting');\n changed();\n try {\n const value = await request(`/api/commands/${endpoint}`, operation.envelope);\n if (!current()) return;\n const data = value as Record<string, unknown> | null;\n // A group's receipt must name every key, in the order sent; one key is matched as before.\n const envelope = operation.envelope;\n const named = envelope.keys\n ? Array.isArray(data?.keys) &&\n data.keys.length === envelope.keys.length &&\n envelope.keys.every((key, index) => sameKey((data.keys as unknown[])[index], key))\n : sameKey(data?.key, envelope.key!);\n if (\n (data?.outcome === undefined || data.outcome === 'applied') &&\n data?.status === 'applied' &&\n data.command === operation.envelope.command &&\n data.commandVersion === operation.envelope.commandVersion &&\n data.resource === operation.owner.target &&\n typeof data.receiptId === 'string' &&\n named\n ) {\n tenantMutationState.settle(tracked.get(operation));\n /* The command's own words for what it did — its output's `message`,\n \"Payment scheduled. It shows as pending until it posts\" — else a plain one. */\n const said = (data.result as { message?: unknown } | null | undefined)?.message;\n operation.phase = 'success';\n if (data.result !== undefined) operation.output = data.result;\n if (typeof said === 'string' && said.trim()) operation.said = said.trim();\n operation.message = operation.said ?? say('completed');\n applied++;\n for (const key of envelope.keys ?? [envelope.key!])\n appliedTo.set(recordKey(operation.owner.connection, operation.owner.target, key), applied);\n changed();\n try {\n onApplied();\n } catch {\n operation.message = say('completedCloseAndRefresh');\n changed();\n }\n return;\n }\n settleFailure(operation, data, endpoint);\n } catch (error) {\n if (!current()) return;\n settleFailure(operation, error, endpoint);\n } finally {\n inFlight.delete(operation);\n }\n },\n};\n\n/** A detached, deeply frozen copy: a retry re-sends exactly what was sent, groups and rows included. */\nfunction frozenCopy(value: unknown): unknown {\n const copy = JSON.parse(JSON.stringify(value)) as unknown;\n const freeze = (node: unknown): unknown => {\n if (node && typeof node === 'object') {\n for (const child of Object.values(node)) freeze(child);\n Object.freeze(node);\n }\n return node;\n };\n return freeze(copy);\n}\n\nconst inFlight = new WeakSet<CommandOperation>();\nfunction sameKey(value: unknown, expected: CommandEnvelope['key']): boolean {\n if (!expected || !value || typeof value !== 'object' || Array.isArray(value)) return false;\n const key = value as Record<string, unknown>;\n return (\n Object.keys(key).length === Object.keys(expected).length &&\n Object.entries(expected).every(([column, id]) => String(key[column]) === String(id))\n );\n}\nfunction settleFailure(operation: CommandOperation, value: unknown, endpoint: 'execute' | 'status') {\n const failure = value as { code?: unknown; message?: unknown; outcome?: unknown; key?: unknown; field?: unknown } | null;\n const refused =\n failure?.key &&\n typeof failure.key === 'object' &&\n !Array.isArray(failure.key) &&\n operation.envelope.keys?.find((key) => commandKeyText(key) === commandKeyText(failure.key));\n operation.refusedKey = refused ? { ...refused } : undefined;\n // Local transport refusals carry a definite outcome and message, but need\n // not invent a server diagnostic code for a request that was never sent.\n const rejected =\n endpoint === 'execute' && !operation.uncertain && failure?.outcome === 'not_applied' && typeof failure.message === 'string';\n operation.phase = rejected ? 'error' : 'unknown';\n if (rejected) tenantMutationState.settle(tracked.get(operation));\n else tenantMutationState.unknown(tracked.get(operation));\n operation.code = typeof failure?.code === 'string' ? failure.code : undefined;\n operation.field = rejected && typeof failure?.field === 'string' ? failure.field : undefined;\n operation.uncertain ||= !rejected;\n operation.message = rejected ? (failure!.message as string) : say('theResultIsNotYet');\n changed();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-Docl_0tq.js","names":["say","say","listeners","revision","changed"],"sources":["../../../kit/src/client/copy/record/components/RecordTrailNav.ts","../../../kit/src/client/components/RecordTrailNav.tsx","../../src/client/tenantMutationState.ts","../../src/client/copy/forms/commandState.ts","../../src/client/commandState.ts"],"sourcesContent":["/** What RecordTrailNav says, in the record page room (packages/kit/src/client/messages.ts says how the catalog is split). */\nimport { defineMessages } from '../../../messages.js';\n\nexport const say = defineMessages({\n recordsInThisList: 'Records in this list',\n previousRecord: 'Previous record',\n previousRecordK: 'Previous record (K)',\n nextRecord: 'Next record',\n nextRecordJ: 'Next record (J)',\n atOfOf: '{at} of {of}',\n});\n","/**\n * Where a record sits in the list it was opened from, and the way to its\n * neighbors: \"3 of 20\" between Previous and Next beside the record's name,\n * and J (next) / K (previous) from the keyboard while it is on screen.\n *\n * The keys are the ones file lists and mail readers use for the same move;\n * single letters, so they stand aside for a text field, a dialog over the\n * page, and any modifier, as the page's other single keys do (app.js). A\n * step replaces the record in the walk rather than adding one, so Back\n * returns to the list, not through every record read on the way. Nothing\n * animates (docs/design.md: keyboard navigation moves nothing), and the\n * position is a polite live region, so the new place is said.\n *\n * The step and the controls are shared with the glance and the record\n * dialog (PeekPanel, RecordModal): one page reader, one guard against a\n * late page, one set of buttons, on every surface that steps.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { api, currentEntry, findTable, go, keyOf, rowLabel } from '../core.js';\nimport { canStep, stepTrail, stepWithin, trailPosition, type RecordTrail, type TrailPageReader } from '../recordTrail.js';\nimport { say } from '../copy/record/components/RecordTrailNav.js';\nimport { Icon } from './Icon.js';\nimport { addHostListener, removeHostListener } from '../shellHost.js';\n\ninterface TrailEntry {\n kind: string;\n table: string;\n key?: Record<string, unknown>;\n label?: string | null;\n trail?: RecordTrail;\n}\n\n/** One page of the list's own question, as the records a step can land on. */\nconst readPage: TrailPageReader = async (tableId, list, offset) => {\n const table = findTable(tableId);\n const data = (await api('/api/run', { q: list.query, limit: list.limit, offset, count: 'none' })) as {\n rows?: Array<Record<string, unknown>>;\n };\n if (!table || !Array.isArray(data?.rows)) return [];\n return data.rows.flatMap((row) => {\n const key = keyOf(table, row) as Record<string, unknown> | null;\n const label = rowLabel(table, row) as string | null;\n return key ? [{ key, ...(label ? { label } : {}) }] : [];\n });\n};\n\n/**\n * A step along a trail. `land` puts the next record in front of the reader;\n * `stillHere` says whether the surface that asked is still the one on\n * screen, so a page that arrives after the reader has moved on lands nowhere.\n * A step within the page reads nothing and lands at once.\n */\nexport function useTrailStep(trail: RecordTrail | undefined, land: (next: RecordTrail) => void, stillHere: () => boolean) {\n const [reading, setReading] = useState(false);\n const busy = useRef(false);\n const step = async (by: 1 | -1) => {\n if (!trail || busy.current || !canStep(trail, by)) return;\n const within = stepWithin(trail, by);\n if (within) {\n land(within);\n return;\n }\n busy.current = true;\n setReading(true);\n try {\n const next = await stepTrail(trail, by, readPage);\n if (next && stillHere()) land(next);\n } catch {\n /* A page that cannot be read leaves the reader where they are; the buttons stay. */\n } finally {\n busy.current = false;\n setReading(false);\n }\n };\n return { step, reading };\n}\n\n/** \"3 of 20\" between Previous and Next — or \"3\" where the list has no total. */\nexport function TrailSteps({\n trail,\n reading,\n onStep,\n onPointerDown,\n}: {\n trail: RecordTrail;\n reading: boolean;\n onStep: (by: 1 | -1) => void;\n /** The record dialog settles an autosave before the pointer's click moves it on. */\n onPointerDown?: () => void;\n}) {\n const { at, of } = trailPosition(trail);\n return (\n <nav className=\"record-trail\" aria-label={say('recordsInThisList')} aria-busy={reading || undefined}>\n <span className=\"step-control\">\n <button\n type=\"button\"\n className=\"step-button record-trail-step\"\n aria-label={say('previousRecord')}\n title={say('previousRecordK')}\n aria-keyshortcuts=\"K\"\n disabled={!canStep(trail, -1)}\n onPointerDown={onPointerDown}\n onClick={() => onStep(-1)}\n >\n <Icon name=\"caret-left\" size={16} />\n </button>\n <span className=\"step-position record-trail-at\" aria-live=\"polite\">\n {of === undefined ? `${at}` : say('atOfOf', { at, of })}\n </span>\n <button\n type=\"button\"\n className=\"step-button record-trail-step\"\n aria-label={say('nextRecord')}\n title={say('nextRecordJ')}\n aria-keyshortcuts=\"J\"\n disabled={!canStep(trail, 1)}\n onPointerDown={onPointerDown}\n onClick={() => onStep(1)}\n >\n <Icon name=\"caret-right\" size={16} />\n </button>\n </span>\n </nav>\n );\n}\n\n/** A key the page's single-letter shortcuts must not take: typing, a dialog over the page, or a modifier. */\nexport function takesSingleKeys(event: KeyboardEvent): boolean {\n if (event.metaKey || event.ctrlKey || event.altKey || event.defaultPrevented) return false;\n const active = document.activeElement as HTMLElement | null;\n if (active && (/^(input|textarea|select)$/i.test(active.tagName) || active.isContentEditable)) return false;\n return !active?.closest?.('[role=\"dialog\"], dialog[open], .help-panel, .palette, .conn-manager');\n}\n\n/** Any walk entry: a record page's or a row's, a split's detail included. Draws nothing where the record was not opened from a list. */\nexport function RecordTrailNav({ entry }: { entry: object }) {\n const walk = entry as TrailEntry;\n const trail = walk.trail;\n const { step, reading } = useTrailStep(\n trail,\n (next) => {\n const record = next.records[next.index];\n go({ ...walk, key: record.key, label: record.label, trail: next }, 'replace');\n },\n () => currentEntry() === entry,\n );\n const stepRef = useRef(step);\n stepRef.current = step;\n useEffect(() => {\n if (!trail) return;\n const onKey = (event: KeyboardEvent) => {\n if ((event.key !== 'j' && event.key !== 'k') || !takesSingleKeys(event)) return;\n event.preventDefault();\n void stepRef.current(event.key === 'j' ? 1 : -1);\n };\n addHostListener(document, 'keydown', onKey);\n return () => removeHostListener(document, 'keydown', onKey);\n }, [trail]);\n if (!trail) return null;\n return <TrailSteps trail={trail} reading={reading} onStep={(by) => void step(by)} />;\n}\n","import {\n captureSessionIdentity,\n sessionIdentity,\n subscribeSessionIdentity,\n tenantLeaseActive,\n type SessionIdentitySnapshot,\n} from '@mistriwork/kit/client/sessionIdentity.js';\n\nexport type TenantMutationKind = 'update' | 'insert' | 'delete' | 'command';\nexport interface TenantMutationAttempt {\n readonly id: symbol;\n readonly kind: TenantMutationKind;\n readonly identity: SessionIdentitySnapshot;\n}\n\nconst kinds: readonly TenantMutationKind[] = ['update', 'insert', 'delete', 'command'];\nconst attempts = new Map<TenantMutationAttempt, 'pending' | 'unknown'>();\nconst listeners = new Set<() => void>();\nlet owner = captureSessionIdentity();\nlet revision = 0;\n\nfunction changed(): void {\n revision++;\n for (const listener of [...listeners]) {\n // A broken observer must not lose a newly started handle or veto security\n // retirement (including the remaining session-identity subscribers).\n try {\n listener();\n } catch {\n /* Observers do not own mutation lifetime. */\n }\n }\n}\n\nfunction syncOwner(): void {\n const current = sessionIdentity();\n if (current === owner) return;\n owner = current;\n if (!attempts.size) return;\n attempts.clear();\n changed();\n}\nsubscribeSessionIdentity(syncOwner);\n\nfunction live(attempt: TenantMutationAttempt | undefined): attempt is TenantMutationAttempt {\n syncOwner();\n return attempt !== undefined && attempts.has(attempt) && attempt.identity === sessionIdentity();\n}\n\n/** Tab-memory exit guard only, never write authority or a command-envelope\n * store. Unmounting a subscriber does not settle work. Identity retirement\n * drops all entries immediately, without claiming any request rolled back. */\nexport const tenantMutationState = Object.freeze({\n start(kind: TenantMutationKind, identity = captureSessionIdentity()): TenantMutationAttempt | undefined {\n syncOwner();\n if (\n identity !== sessionIdentity() ||\n identity.mode !== 'authenticated' ||\n !identity.tenantRequired ||\n !identity.tenant ||\n !kinds.includes(kind)\n )\n return;\n if (!tenantLeaseActive(identity) || identity !== sessionIdentity()) return;\n const attempt = Object.freeze({ id: Symbol('tenant-mutation'), kind, identity });\n attempts.set(attempt, 'pending');\n changed();\n return attempt;\n },\n unknown(attempt: TenantMutationAttempt | undefined): boolean {\n if (!live(attempt) || attempts.get(attempt) === 'unknown') return false;\n attempts.set(attempt, 'unknown');\n changed();\n return true;\n },\n /** Caller has verified success or definite refusal for this exact attempt.\n * In particular, a command retry's refusal does not settle an earlier unknown\n * execution; commandOperations remains responsible for that distinction. */\n settle(attempt: TenantMutationAttempt | undefined): boolean {\n if (!live(attempt)) return false;\n attempts.delete(attempt);\n changed();\n return true;\n },\n hasUnresolved(identity = captureSessionIdentity()): boolean {\n syncOwner();\n return identity === sessionIdentity() && attempts.size > 0;\n },\n version(): number {\n syncOwner();\n return revision;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n});\n","/** What commandState says, in the forms 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 signInAgainBefore: 'Sign in again before continuing.',\n thisRecordHasAnUnsupported: 'This record has an unsupported key.',\n refreshThisRecordBefore: 'Refresh this record before continuing.',\n refreshTheseRecordsBefore: 'Refresh these records before continuing.',\n checkingResult: 'Checking result…',\n completed: 'Completed.',\n completedCloseAndRefresh: 'Completed. Close and refresh the record to see the changes.',\n theResultIsNotYet: 'The result is not yet confirmed. Check the result or retry this same submission. Keep this tab open.',\n});\n","import {\n captureSessionIdentity,\n sessionIdentity,\n subscribeSessionIdentity,\n type SessionIdentitySnapshot,\n} from '@mistriwork/kit/client/sessionIdentity.js';\nimport type { AppCommandAction } from '../shared/app.js';\nimport { BULK_LIMIT, commandKeyText } from '../shared/command.js';\nimport { tenantMutationState, type TenantMutationAttempt } from './tenantMutationState.js';\nimport { say } from './copy/forms/commandState.js';\n\n/** One input's value as a form holds it; several choices are a list of option values. */\nexport type CommandValue = string | number | boolean | readonly (string | number)[];\n/** An input as the server takes it: values, objects for groups, lists of rows for repeating groups. */\nexport type CommandInputValue = Readonly<Record<string, unknown>>;\n/** One target (`key`, `expectedVersion`) or, for a `targets: 'many'` command, an ordered list. */\ninterface CommandEnvelope {\n command: string;\n commandVersion: number;\n key?: Record<string, string | number>;\n expectedVersion?: number;\n keys?: ReadonlyArray<Record<string, string | number>>;\n expectedVersions?: readonly number[];\n idempotencyKey: string;\n input: CommandInputValue;\n}\nexport interface CommandOwner {\n connection: string;\n target: string;\n command: string;\n commandVersion: number;\n key: Record<string, unknown>;\n /** A group: every selected record, in order. `key` is then empty. */\n keys?: Array<Record<string, unknown>>;\n /** What each of `keys` is called, so a resumed group still names its records. Not identity. */\n labels?: string[];\n}\nexport interface CommandOperation {\n readonly identity: SessionIdentitySnapshot;\n readonly owner: CommandOwner;\n readonly envelope: CommandEnvelope;\n readonly action?: AppCommandAction;\n phase: 'pending' | 'success' | 'error' | 'unknown';\n message: string;\n /** The command's own words for what it did, when its output said any: a command run on one click has no form to\n say them in, so its notice does (components/Actions.tsx). */\n said?: string;\n /** What the command's handler returned (the receipt's `result`), handed to a component that ran it (`useCommand`). */\n output?: unknown;\n code?: string;\n /** The one record of a group the refusal named, when it named one. */\n refusedKey?: Record<string, unknown>;\n /** The input the refusal is about, by its path, when it named one: the form says it beside that field. */\n field?: string;\n /** Once delivery is uncertain, a later missing receipt/refusal cannot prove rollback. */\n uncertain: boolean;\n /** Sent by a component's `run`, or from the form its `open` holds: its caller resolves it, so a record's action bar\n must not draw its form while it is in flight (a composer's reply would show twice). */\n headless?: boolean;\n}\n\n// A new manifest version must not open a second operation over an unresolved submission.\nconst keyPairs = (key: Record<string, unknown>) =>\n Object.keys(key)\n .sort()\n .map((column) => [column, String(key[column])]);\nconst ownerKey = (owner: CommandOwner) =>\n JSON.stringify([owner.connection, owner.target, owner.command, owner.keys ? owner.keys.map(keyPairs) : keyPairs(owner.key)]);\nconst operations = new Map<string, CommandOperation>();\n/* When a command last applied to each record, as a count of applied commands:\n an open form prepared before that number moved holds a revision it knows\n is old (CommandForm prepares again before it lets a submit through). */\nconst recordKey = (connection: string, target: string, key: Record<string, unknown>) => JSON.stringify([connection, target, keyPairs(key)]);\nconst appliedTo = new Map<string, number>();\nlet applied = 0;\nconst tracked = new WeakMap<CommandOperation, TenantMutationAttempt>();\nconst listeners = new Set<() => void>();\nlet revision = 0;\nfunction changed() {\n revision++;\n for (const listener of listeners) listener();\n}\nsubscribeSessionIdentity(() => {\n operations.clear();\n appliedTo.clear();\n changed();\n});\n\nexport const commandOperations = {\n subscribe(listener: () => void) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n version: () => revision,\n /** The count of applied commands at the last one that applied to any of these records (0: none has). */\n appliedOn(connection: string, target: string, keys: ReadonlyArray<Record<string, unknown>>): number {\n return Math.max(0, ...keys.map((key) => appliedTo.get(recordKey(connection, target, key)) ?? 0));\n },\n get(owner: CommandOwner): CommandOperation | undefined {\n return operations.get(ownerKey(owner));\n },\n /** Group submissions on this list that are still pending or unconfirmed: a queue reopens their form. */\n unresolvedGroups(connection: string, target: string): CommandOperation[] {\n return [...operations.values()].filter(\n (operation) =>\n operation.owner.keys &&\n operation.owner.connection === connection &&\n operation.owner.target === target &&\n (operation.phase === 'pending' || operation.phase === 'unknown'),\n );\n },\n forget(owner: CommandOwner) {\n const operation = operations.get(ownerKey(owner));\n if (operation?.phase === 'pending' || operation?.phase === 'unknown') return;\n operations.delete(ownerKey(owner));\n changed();\n },\n /** A group owner takes one prepared revision per key, aligned with `owner.keys`. */\n start(\n owner: CommandOwner,\n expectedVersion: number | readonly number[],\n input: CommandInputValue,\n action?: AppCommandAction,\n headless = false,\n ): CommandOperation {\n const identity = captureSessionIdentity();\n if (identity.mode !== 'authenticated' || identity.appId !== owner.connection) throw new Error(say('signInAgainBefore'));\n const previous = operations.get(ownerKey(owner));\n if (previous) return previous;\n const checked = (raw: Record<string, unknown>) => {\n const key: Record<string, string | number> = {};\n for (const [column, value] of Object.entries(raw)) {\n if (typeof value !== 'string' && !(typeof value === 'number' && Number.isSafeInteger(value)))\n throw new Error(say('thisRecordHasAnUnsupported'));\n key[column] = value;\n }\n if (!Object.keys(key).length) throw new Error(say('refreshThisRecordBefore'));\n return Object.freeze(key);\n };\n const revision = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;\n let targets: Pick<CommandEnvelope, 'key' | 'expectedVersion' | 'keys' | 'expectedVersions'>;\n if (owner.keys) {\n const versions = Array.isArray(expectedVersion) ? [...(expectedVersion as readonly number[])] : [];\n const keys = owner.keys.map(checked);\n if (\n !keys.length ||\n keys.length > BULK_LIMIT ||\n versions.length !== keys.length ||\n !versions.every(revision) ||\n new Set(keys.map(commandKeyText)).size !== keys.length\n )\n throw new Error(say('refreshTheseRecordsBefore'));\n targets = { keys: Object.freeze(keys), expectedVersions: Object.freeze(versions) };\n } else {\n const key = checked(owner.key);\n if (!revision(expectedVersion)) throw new Error(say('refreshThisRecordBefore'));\n targets = { key, expectedVersion };\n }\n const envelope: CommandEnvelope = Object.freeze({\n command: owner.command,\n commandVersion: owner.commandVersion,\n ...targets,\n idempotencyKey: crypto.randomUUID(),\n input: frozenCopy(input) as CommandInputValue,\n });\n const presentation: AppCommandAction | undefined = action && JSON.parse(JSON.stringify(action));\n if (presentation) {\n presentation.command.input.forEach(Object.freeze);\n Object.freeze(presentation.command.input);\n Object.freeze(presentation.command);\n Object.freeze(presentation);\n }\n const operation: CommandOperation = {\n identity,\n envelope,\n action: presentation,\n owner: Object.freeze(\n envelope.keys\n ? { ...owner, key: {}, keys: [...envelope.keys], labels: owner.labels && [...owner.labels] }\n : { ...owner, key: envelope.key! },\n ),\n phase: 'pending',\n message: say('common.submitting'),\n uncertain: false,\n ...(headless ? { headless } : {}),\n };\n operations.set(ownerKey(owner), operation);\n changed();\n return operation;\n },\n async send(\n operation: CommandOperation,\n endpoint: 'execute' | 'status',\n request: (path: string, envelope: CommandEnvelope) => Promise<unknown>,\n onApplied: () => void,\n ): Promise<void> {\n const current = () => sessionIdentity() === operation.identity && operations.get(ownerKey(operation.owner)) === operation;\n if (!current() || operation.phase === 'success' || operation.phase === 'error') return;\n // The caller dispatches the initial pending operation once; retries only follow a settled unknown.\n if (inFlight.has(operation)) return;\n if (!tracked.has(operation)) {\n const attempt = tenantMutationState.start('command', operation.identity);\n if (attempt) tracked.set(operation, attempt);\n }\n inFlight.add(operation);\n operation.phase = 'pending';\n operation.message = endpoint === 'status' ? say('checkingResult') : say('common.submitting');\n changed();\n try {\n const value = await request(`/api/commands/${endpoint}`, operation.envelope);\n if (!current()) return;\n const data = value as Record<string, unknown> | null;\n // A group's receipt must name every key, in the order sent; one key is matched as before.\n const envelope = operation.envelope;\n const named = envelope.keys\n ? Array.isArray(data?.keys) &&\n data.keys.length === envelope.keys.length &&\n envelope.keys.every((key, index) => sameKey((data.keys as unknown[])[index], key))\n : sameKey(data?.key, envelope.key!);\n if (\n (data?.outcome === undefined || data.outcome === 'applied') &&\n data?.status === 'applied' &&\n data.command === operation.envelope.command &&\n data.commandVersion === operation.envelope.commandVersion &&\n data.resource === operation.owner.target &&\n typeof data.receiptId === 'string' &&\n named\n ) {\n tenantMutationState.settle(tracked.get(operation));\n /* The command's own words for what it did — its output's `message`,\n \"Payment scheduled. It shows as pending until it posts\" — else a plain one. */\n const said = (data.result as { message?: unknown } | null | undefined)?.message;\n operation.phase = 'success';\n if (data.result !== undefined) operation.output = data.result;\n if (typeof said === 'string' && said.trim()) operation.said = said.trim();\n operation.message = operation.said ?? say('completed');\n applied++;\n for (const key of envelope.keys ?? [envelope.key!])\n appliedTo.set(recordKey(operation.owner.connection, operation.owner.target, key), applied);\n changed();\n try {\n onApplied();\n } catch {\n operation.message = say('completedCloseAndRefresh');\n changed();\n }\n return;\n }\n settleFailure(operation, data, endpoint);\n } catch (error) {\n if (!current()) return;\n settleFailure(operation, error, endpoint);\n } finally {\n inFlight.delete(operation);\n }\n },\n};\n\n/** A detached, deeply frozen copy: a retry re-sends exactly what was sent, groups and rows included. */\nfunction frozenCopy(value: unknown): unknown {\n const copy = JSON.parse(JSON.stringify(value)) as unknown;\n const freeze = (node: unknown): unknown => {\n if (node && typeof node === 'object') {\n for (const child of Object.values(node)) freeze(child);\n Object.freeze(node);\n }\n return node;\n };\n return freeze(copy);\n}\n\nconst inFlight = new WeakSet<CommandOperation>();\nfunction sameKey(value: unknown, expected: CommandEnvelope['key']): boolean {\n if (!expected || !value || typeof value !== 'object' || Array.isArray(value)) return false;\n const key = value as Record<string, unknown>;\n return (\n Object.keys(key).length === Object.keys(expected).length &&\n Object.entries(expected).every(([column, id]) => String(key[column]) === String(id))\n );\n}\nfunction settleFailure(operation: CommandOperation, value: unknown, endpoint: 'execute' | 'status') {\n const failure = value as { code?: unknown; message?: unknown; outcome?: unknown; key?: unknown; field?: unknown } | null;\n const refused =\n failure?.key &&\n typeof failure.key === 'object' &&\n !Array.isArray(failure.key) &&\n operation.envelope.keys?.find((key) => commandKeyText(key) === commandKeyText(failure.key));\n operation.refusedKey = refused ? { ...refused } : undefined;\n // Local transport refusals carry a definite outcome and message, but need\n // not invent a server diagnostic code for a request that was never sent.\n const rejected =\n endpoint === 'execute' && !operation.uncertain && failure?.outcome === 'not_applied' && typeof failure.message === 'string';\n operation.phase = rejected ? 'error' : 'unknown';\n if (rejected) tenantMutationState.settle(tracked.get(operation));\n else tenantMutationState.unknown(tracked.get(operation));\n operation.code = typeof failure?.code === 'string' ? failure.code : undefined;\n operation.field = rejected && typeof failure?.field === 'string' ? failure.field : undefined;\n operation.uncertain ||= !rejected;\n operation.message = rejected ? (failure!.message as string) : say('theResultIsNotYet');\n changed();\n}\n"],"mappings":";;;;;;;;;AAGA,IAAaA,QAAM,eAAe;CAChC,mBAAmB;CACnB,gBAAgB;CAChB,iBAAiB;CACjB,YAAY;CACZ,aAAa;CACb,QAAQ;AACV,CAAC;;;;;;;;;;;;;;;;;;;;ACuBD,IAAM,WAA4B,OAAO,SAAS,MAAM,WAAW;CACjE,MAAM,QAAQ,UAAU,OAAO;CAC/B,MAAM,OAAQ,MAAM,IAAI,YAAY;EAAE,GAAG,KAAK;EAAO,OAAO,KAAK;EAAO;EAAQ,OAAO;CAAO,CAAC;CAG/F,IAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,MAAM,IAAI,GAAG,OAAO,CAAC;CAClD,OAAO,KAAK,KAAK,SAAS,QAAQ;EAChC,MAAM,MAAM,MAAM,OAAO,GAAG;EAC5B,MAAM,QAAQ,SAAS,OAAO,GAAG;EACjC,OAAO,MAAM,CAAC;GAAE;GAAK,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EAAG,CAAC,IAAI,CAAC;CACzD,CAAC;AACH;;;;;;;AAQA,SAAgB,aAAa,OAAgC,MAAmC,WAA0B;CACxH,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,OAAO,OAAO,OAAe;EACjC,IAAI,CAAC,SAAS,KAAK,WAAW,CAAC,QAAQ,OAAO,EAAE,GAAG;EACnD,MAAM,SAAS,WAAW,OAAO,EAAE;EACnC,IAAI,QAAQ;GACV,KAAK,MAAM;GACX;EACF;EACA,KAAK,UAAU;EACf,WAAW,IAAI;EACf,IAAI;GACF,MAAM,OAAO,MAAM,UAAU,OAAO,IAAI,QAAQ;GAChD,IAAI,QAAQ,UAAU,GAAG,KAAK,IAAI;EACpC,QAAQ,CAER,UAAU;GACR,KAAK,UAAU;GACf,WAAW,KAAK;EAClB;CACF;CACA,OAAO;EAAE;EAAM;CAAQ;AACzB;;AAGA,SAAgB,WAAW,EACzB,OACA,SACA,QACA,iBAOC;CACD,MAAM,EAAE,IAAI,OAAO,cAAc,KAAK;CACtC,OACE,oBAAC,OAAD;EAAK,WAAU;EAAe,cAAYC,MAAI,mBAAmB;EAAG,aAAW,WAAW,KAAA;EACxF,UAAA,qBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA;IACE,oBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,cAAYA,MAAI,gBAAgB;KAChC,OAAOA,MAAI,iBAAiB;KAC5B,qBAAkB;KAClB,UAAU,CAAC,QAAQ,OAAO,EAAE;KACb;KACf,eAAe,OAAO,EAAE;KAExB,UAAA,oBAAC,MAAD;MAAM,MAAK;MAAa,MAAM;KAAK,CAAA;IAC7B,CAAA;IACR,oBAAC,QAAD;KAAM,WAAU;KAAgC,aAAU;KACvD,UAAA,OAAO,KAAA,IAAY,GAAG,OAAOA,MAAI,UAAU;MAAE;MAAI;KAAG,CAAC;IAClD,CAAA;IACN,oBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,cAAYA,MAAI,YAAY;KAC5B,OAAOA,MAAI,aAAa;KACxB,qBAAkB;KAClB,UAAU,CAAC,QAAQ,OAAO,CAAC;KACZ;KACf,eAAe,OAAO,CAAC;KAEvB,UAAA,oBAAC,MAAD;MAAM,MAAK;MAAc,MAAM;KAAK,CAAA;IAC9B,CAAA;GACJ;;CACH,CAAA;AAET;;AAGA,SAAgB,gBAAgB,OAA+B;CAC7D,IAAI,MAAM,WAAW,MAAM,WAAW,MAAM,UAAU,MAAM,kBAAkB,OAAO;CACrF,MAAM,SAAS,SAAS;CACxB,IAAI,WAAW,6BAA6B,KAAK,OAAO,OAAO,KAAK,OAAO,oBAAoB,OAAO;CACtG,OAAO,CAAC,QAAQ,UAAU,uEAAqE;AACjG;;AAGA,SAAgB,eAAe,EAAE,SAA4B;CAC3D,MAAM,OAAO;CACb,MAAM,QAAQ,KAAK;CACnB,MAAM,EAAE,MAAM,YAAY,aACxB,QACC,SAAS;EACR,MAAM,SAAS,KAAK,QAAQ,KAAK;EACjC,GAAG;GAAE,GAAG;GAAM,KAAK,OAAO;GAAK,OAAO,OAAO;GAAO,OAAO;EAAK,GAAG,SAAS;CAC9E,SACM,aAAa,MAAM,KAC3B;CACA,MAAM,UAAU,OAAO,IAAI;CAC3B,QAAQ,UAAU;CAClB,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,MAAM,SAAS,UAAyB;GACtC,IAAK,MAAM,QAAQ,OAAO,MAAM,QAAQ,OAAQ,CAAC,gBAAgB,KAAK,GAAG;GACzE,MAAM,eAAe;GACrB,QAAa,QAAQ,MAAM,QAAQ,MAAM,IAAI,EAAE;EACjD;EACA,gBAAgB,UAAU,WAAW,KAAK;EAC1C,aAAa,mBAAmB,UAAU,WAAW,KAAK;CAC5D,GAAG,CAAC,KAAK,CAAC;CACV,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,oBAAC,YAAD;EAAmB;EAAgB;EAAS,SAAS,OAAO,KAAK,KAAK,EAAE;CAAI,CAAA;AACrF;;ACjJA,IAAM,QAAuC;CAAC;CAAU;CAAU;CAAU;AAAS;AACrF,IAAM,2BAAW,IAAI,IAAkD;AACvE,IAAMC,8BAAY,IAAI,IAAgB;AACtC,IAAI,QAAQ,uBAAuB;AACnC,IAAIC,aAAW;AAEf,SAASC,YAAgB;CACvB;CACA,KAAK,MAAM,YAAY,CAAC,GAAGF,WAAS,GAGlC,IAAI;EACF,SAAS;CACX,QAAQ,CAER;AAEJ;AAEA,SAAS,YAAkB;CACzB,MAAM,UAAU,gBAAgB;CAChC,IAAI,YAAY,OAAO;CACvB,QAAQ;CACR,IAAI,CAAC,SAAS,MAAM;CACpB,SAAS,MAAM;CACf,UAAQ;AACV;AACA,yBAAyB,SAAS;AAElC,SAAS,KAAK,SAA8E;CAC1F,UAAU;CACV,OAAO,YAAY,KAAA,KAAa,SAAS,IAAI,OAAO,KAAK,QAAQ,aAAa,gBAAgB;AAChG;;;;AAKA,IAAa,sBAAsB,OAAO,OAAO;CAC/C,MAAM,MAA0B,WAAW,uBAAuB,GAAsC;EACtG,UAAU;EACV,IACE,aAAa,gBAAgB,KAC7B,SAAS,SAAS,mBAClB,CAAC,SAAS,kBACV,CAAC,SAAS,UACV,CAAC,MAAM,SAAS,IAAI,GAEpB;EACF,IAAI,CAAC,kBAAkB,QAAQ,KAAK,aAAa,gBAAgB,GAAG;EACpE,MAAM,UAAU,OAAO,OAAO;GAAE,IAAI,OAAO,iBAAiB;GAAG;GAAM;EAAS,CAAC;EAC/E,SAAS,IAAI,SAAS,SAAS;EAC/B,UAAQ;EACR,OAAO;CACT;CACA,QAAQ,SAAqD;EAC3D,IAAI,CAAC,KAAK,OAAO,KAAK,SAAS,IAAI,OAAO,MAAM,WAAW,OAAO;EAClE,SAAS,IAAI,SAAS,SAAS;EAC/B,UAAQ;EACR,OAAO;CACT;;;;CAIA,OAAO,SAAqD;EAC1D,IAAI,CAAC,KAAK,OAAO,GAAG,OAAO;EAC3B,SAAS,OAAO,OAAO;EACvB,UAAQ;EACR,OAAO;CACT;CACA,cAAc,WAAW,uBAAuB,GAAY;EAC1D,UAAU;EACV,OAAO,aAAa,gBAAgB,KAAK,SAAS,OAAO;CAC3D;CACA,UAAkB;EAChB,UAAU;EACV,OAAOC;CACT;CACA,UAAU,UAAkC;EAC1C,YAAU,IAAI,QAAQ;EACtB,aAAa;GACX,YAAU,OAAO,QAAQ;EAC3B;CACF;AACF,CAAC;;;AC/FD,IAAa,MAAM,eAAe;CAChC,mBAAmB;CACnB,4BAA4B;CAC5B,yBAAyB;CACzB,2BAA2B;CAC3B,gBAAgB;CAChB,WAAW;CACX,0BAA0B;CAC1B,mBAAmB;AACrB,CAAC;;ACkDD,IAAM,YAAY,QAChB,OAAO,KAAK,GAAG,CAAC,CACb,KAAK,CAAC,CACN,KAAK,WAAW,CAAC,QAAQ,OAAO,IAAI,OAAO,CAAC,CAAC;AAClD,IAAM,YAAY,UAChB,KAAK,UAAU;CAAC,MAAM;CAAY,MAAM;CAAQ,MAAM;CAAS,MAAM,OAAO,MAAM,KAAK,IAAI,QAAQ,IAAI,SAAS,MAAM,GAAG;AAAC,CAAC;AAC7H,IAAM,6BAAa,IAAI,IAA8B;AAIrD,IAAM,aAAa,YAAoB,QAAgB,QAAiC,KAAK,UAAU;CAAC;CAAY;CAAQ,SAAS,GAAG;AAAC,CAAC;AAC1I,IAAM,4BAAY,IAAI,IAAoB;AAC1C,IAAI,UAAU;AACd,IAAM,0BAAU,IAAI,QAAiD;AACrE,IAAM,4BAAY,IAAI,IAAgB;AACtC,IAAI,WAAW;AACf,SAAS,UAAU;CACjB;CACA,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;AACA,+BAA+B;CAC7B,WAAW,MAAM;CACjB,UAAU,MAAM;CAChB,QAAQ;AACV,CAAC;AAED,IAAa,oBAAoB;CAC/B,UAAU,UAAsB;EAC9B,UAAU,IAAI,QAAQ;EACtB,aAAa;GACX,UAAU,OAAO,QAAQ;EAC3B;CACF;CACA,eAAe;;CAEf,UAAU,YAAoB,QAAgB,MAAsD;EAClG,OAAO,KAAK,IAAI,GAAG,GAAG,KAAK,KAAK,QAAQ,UAAU,IAAI,UAAU,YAAY,QAAQ,GAAG,CAAC,KAAK,CAAC,CAAC;CACjG;CACA,IAAI,OAAmD;EACrD,OAAO,WAAW,IAAI,SAAS,KAAK,CAAC;CACvC;;CAEA,iBAAiB,YAAoB,QAAoC;EACvE,OAAO,CAAC,GAAG,WAAW,OAAO,CAAC,CAAC,CAAC,QAC7B,cACC,UAAU,MAAM,QAChB,UAAU,MAAM,eAAe,cAC/B,UAAU,MAAM,WAAW,WAC1B,UAAU,UAAU,aAAa,UAAU,UAAU,UAC1D;CACF;CACA,OAAO,OAAqB;EAC1B,MAAM,YAAY,WAAW,IAAI,SAAS,KAAK,CAAC;EAChD,IAAI,WAAW,UAAU,aAAa,WAAW,UAAU,WAAW;EACtE,WAAW,OAAO,SAAS,KAAK,CAAC;EACjC,QAAQ;CACV;;CAEA,MACE,OACA,iBACA,OACA,QACA,WAAW,OACO;EAClB,MAAM,WAAW,uBAAuB;EACxC,IAAI,SAAS,SAAS,mBAAmB,SAAS,UAAU,MAAM,YAAY,MAAM,IAAI,MAAM,IAAI,mBAAmB,CAAC;EACtH,MAAM,WAAW,WAAW,IAAI,SAAS,KAAK,CAAC;EAC/C,IAAI,UAAU,OAAO;EACrB,MAAM,WAAW,QAAiC;GAChD,MAAM,MAAuC,CAAC;GAC9C,KAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,GAAG,GAAG;IACjD,IAAI,OAAO,UAAU,YAAY,EAAE,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,IACxF,MAAM,IAAI,MAAM,IAAI,4BAA4B,CAAC;IACnD,IAAI,UAAU;GAChB;GACA,IAAI,CAAC,OAAO,KAAK,GAAG,CAAC,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI,yBAAyB,CAAC;GAC5E,OAAO,OAAO,OAAO,GAAG;EAC1B;EACA,MAAM,YAAY,UAAoC,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS;EAC3H,IAAI;EACJ,IAAI,MAAM,MAAM;GACd,MAAM,WAAW,MAAM,QAAQ,eAAe,IAAI,CAAC,GAAI,eAAqC,IAAI,CAAC;GACjG,MAAM,OAAO,MAAM,KAAK,IAAI,OAAO;GACnC,IACE,CAAC,KAAK,UACN,KAAK,SAAA,OACL,SAAS,WAAW,KAAK,UACzB,CAAC,SAAS,MAAM,QAAQ,KACxB,IAAI,IAAI,KAAK,IAAI,cAAc,CAAC,CAAC,CAAC,SAAS,KAAK,QAEhD,MAAM,IAAI,MAAM,IAAI,2BAA2B,CAAC;GAClD,UAAU;IAAE,MAAM,OAAO,OAAO,IAAI;IAAG,kBAAkB,OAAO,OAAO,QAAQ;GAAE;EACnF,OAAO;GACL,MAAM,MAAM,QAAQ,MAAM,GAAG;GAC7B,IAAI,CAAC,SAAS,eAAe,GAAG,MAAM,IAAI,MAAM,IAAI,yBAAyB,CAAC;GAC9E,UAAU;IAAE;IAAK;GAAgB;EACnC;EACA,MAAM,WAA4B,OAAO,OAAO;GAC9C,SAAS,MAAM;GACf,gBAAgB,MAAM;GACtB,GAAG;GACH,gBAAgB,OAAO,WAAW;GAClC,OAAO,WAAW,KAAK;EACzB,CAAC;EACD,MAAM,eAA6C,UAAU,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;EAC9F,IAAI,cAAc;GAChB,aAAa,QAAQ,MAAM,QAAQ,OAAO,MAAM;GAChD,OAAO,OAAO,aAAa,QAAQ,KAAK;GACxC,OAAO,OAAO,aAAa,OAAO;GAClC,OAAO,OAAO,YAAY;EAC5B;EACA,MAAM,YAA8B;GAClC;GACA;GACA,QAAQ;GACR,OAAO,OAAO,OACZ,SAAS,OACL;IAAE,GAAG;IAAO,KAAK,CAAC;IAAG,MAAM,CAAC,GAAG,SAAS,IAAI;IAAG,QAAQ,MAAM,UAAU,CAAC,GAAG,MAAM,MAAM;GAAE,IACzF;IAAE,GAAG;IAAO,KAAK,SAAS;GAAK,CACrC;GACA,OAAO;GACP,SAAS,IAAI,mBAAmB;GAChC,WAAW;GACX,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;EACjC;EACA,WAAW,IAAI,SAAS,KAAK,GAAG,SAAS;EACzC,QAAQ;EACR,OAAO;CACT;CACA,MAAM,KACJ,WACA,UACA,SACA,WACe;EACf,MAAM,gBAAgB,gBAAgB,MAAM,UAAU,YAAY,WAAW,IAAI,SAAS,UAAU,KAAK,CAAC,MAAM;EAChH,IAAI,CAAC,QAAQ,KAAK,UAAU,UAAU,aAAa,UAAU,UAAU,SAAS;EAEhF,IAAI,SAAS,IAAI,SAAS,GAAG;EAC7B,IAAI,CAAC,QAAQ,IAAI,SAAS,GAAG;GAC3B,MAAM,UAAU,oBAAoB,MAAM,WAAW,UAAU,QAAQ;GACvE,IAAI,SAAS,QAAQ,IAAI,WAAW,OAAO;EAC7C;EACA,SAAS,IAAI,SAAS;EACtB,UAAU,QAAQ;EAClB,UAAU,UAAU,aAAa,WAAW,IAAI,gBAAgB,IAAI,IAAI,mBAAmB;EAC3F,QAAQ;EACR,IAAI;GACF,MAAM,QAAQ,MAAM,QAAQ,iBAAiB,YAAY,UAAU,QAAQ;GAC3E,IAAI,CAAC,QAAQ,GAAG;GAChB,MAAM,OAAO;GAEb,MAAM,WAAW,UAAU;GAC3B,MAAM,QAAQ,SAAS,OACnB,MAAM,QAAQ,MAAM,IAAI,KACxB,KAAK,KAAK,WAAW,SAAS,KAAK,UACnC,SAAS,KAAK,OAAO,KAAK,UAAU,QAAS,KAAK,KAAmB,QAAQ,GAAG,CAAC,IACjF,QAAQ,MAAM,KAAK,SAAS,GAAI;GACpC,KACG,MAAM,YAAY,KAAA,KAAa,KAAK,YAAY,cACjD,MAAM,WAAW,aACjB,KAAK,YAAY,UAAU,SAAS,WACpC,KAAK,mBAAmB,UAAU,SAAS,kBAC3C,KAAK,aAAa,UAAU,MAAM,UAClC,OAAO,KAAK,cAAc,YAC1B,OACA;IACA,oBAAoB,OAAO,QAAQ,IAAI,SAAS,CAAC;IAGjD,MAAM,OAAQ,KAAK,QAAqD;IACxE,UAAU,QAAQ;IAClB,IAAI,KAAK,WAAW,KAAA,GAAW,UAAU,SAAS,KAAK;IACvD,IAAI,OAAO,SAAS,YAAY,KAAK,KAAK,GAAG,UAAU,OAAO,KAAK,KAAK;IACxE,UAAU,UAAU,UAAU,QAAQ,IAAI,WAAW;IACrD;IACA,KAAK,MAAM,OAAO,SAAS,QAAQ,CAAC,SAAS,GAAI,GAC/C,UAAU,IAAI,UAAU,UAAU,MAAM,YAAY,UAAU,MAAM,QAAQ,GAAG,GAAG,OAAO;IAC3F,QAAQ;IACR,IAAI;KACF,UAAU;IACZ,QAAQ;KACN,UAAU,UAAU,IAAI,0BAA0B;KAClD,QAAQ;IACV;IACA;GACF;GACA,cAAc,WAAW,MAAM,QAAQ;EACzC,SAAS,OAAO;GACd,IAAI,CAAC,QAAQ,GAAG;GAChB,cAAc,WAAW,OAAO,QAAQ;EAC1C,UAAU;GACR,SAAS,OAAO,SAAS;EAC3B;CACF;AACF;;AAGA,SAAS,WAAW,OAAyB;CAC3C,MAAM,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;CAC7C,MAAM,UAAU,SAA2B;EACzC,IAAI,QAAQ,OAAO,SAAS,UAAU;GACpC,KAAK,MAAM,SAAS,OAAO,OAAO,IAAI,GAAG,OAAO,KAAK;GACrD,OAAO,OAAO,IAAI;EACpB;EACA,OAAO;CACT;CACA,OAAO,OAAO,IAAI;AACpB;AAEA,IAAM,2BAAW,IAAI,QAA0B;AAC/C,SAAS,QAAQ,OAAgB,UAA2C;CAC1E,IAAI,CAAC,YAAY,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO;CACrF,MAAM,MAAM;CACZ,OACE,OAAO,KAAK,GAAG,CAAC,CAAC,WAAW,OAAO,KAAK,QAAQ,CAAC,CAAC,UAClD,OAAO,QAAQ,QAAQ,CAAC,CAAC,OAAO,CAAC,QAAQ,QAAQ,OAAO,IAAI,OAAO,MAAM,OAAO,EAAE,CAAC;AAEvF;AACA,SAAS,cAAc,WAA6B,OAAgB,UAAgC;CAClG,MAAM,UAAU;CAChB,MAAM,UACJ,SAAS,OACT,OAAO,QAAQ,QAAQ,YACvB,CAAC,MAAM,QAAQ,QAAQ,GAAG,KAC1B,UAAU,SAAS,MAAM,MAAM,QAAQ,eAAe,GAAG,MAAM,eAAe,QAAQ,GAAG,CAAC;CAC5F,UAAU,aAAa,UAAU,EAAE,GAAG,QAAQ,IAAI,KAAA;CAGlD,MAAM,WACJ,aAAa,aAAa,CAAC,UAAU,aAAa,SAAS,YAAY,iBAAiB,OAAO,QAAQ,YAAY;CACrH,UAAU,QAAQ,WAAW,UAAU;CACvC,IAAI,UAAU,oBAAoB,OAAO,QAAQ,IAAI,SAAS,CAAC;MAC1D,oBAAoB,QAAQ,QAAQ,IAAI,SAAS,CAAC;CACvD,UAAU,OAAO,OAAO,SAAS,SAAS,WAAW,QAAQ,OAAO,KAAA;CACpE,UAAU,QAAQ,YAAY,OAAO,SAAS,UAAU,WAAW,QAAQ,QAAQ,KAAA;CACnF,UAAU,cAAc,CAAC;CACzB,UAAU,UAAU,WAAY,QAAS,UAAqB,IAAI,mBAAmB;CACrF,QAAQ;AACV"}
@@ -1,7 +1,7 @@
1
1
  import "./shared-poc11xxo.js";
2
2
  import { Kt as formatLocale } from "./shared-DJZjSi3J.js";
3
3
  import { t as evaluateComputed } from "./shared-DtWgKxbD.js";
4
- import { f as parseComputed } from "./shared-C-J32eBn.js";
4
+ import { f as parseComputed } from "./shared-CL3RLKDA.js";
5
5
  import { useMemo } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  // packages/mistri/src/client/components/CommandLive.tsx
@@ -66,4 +66,4 @@ function CommandLive({ field, fields, values, id, currency = "USD" }) {
66
66
  }
67
67
  export { CommandLive, liveFigures };
68
68
 
69
- //# sourceMappingURL=shared-DZskrV26.js.map
69
+ //# sourceMappingURL=shared-DprrHC72.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-DZskrV26.js","names":[],"sources":["../../src/client/components/CommandLive.tsx"],"sourcesContent":["/**\n * A number's `live` lines, said under it as it is typed: \"$54,167 monthly · $650,000 annually\". Each is an expression\n * over the input's numbers (`biweekly_gross * 26 / 12`), read by the evaluator computed columns use, in the field's\n * format. Display only: nothing here is sent, and a line whose numbers are not all given says nothing.\n */\nimport { type ComputedNode } from '@mistriwork/engine/shared/computed.js';\nimport { parseComputed } from '@mistriwork/engine/shared/computedParse.js';\nimport { evaluateComputed } from '@mistriwork/engine/shared/computedValue.js';\nimport { formatLocale } from '@mistriwork/kit/client/locale.js';\nimport { useMemo } from 'react';\nimport './CommandLive.css';\nimport type { AppCommandField } from '../../shared/command.js';\nimport type { CommandValues } from './CommandFields.js';\n\ntype LiveField = AppCommandField & { type: 'number' | 'money'; live?: Readonly<Record<string, string>> };\n\nconst parsed = new Map<string, ComputedNode | null>();\nfunction tree(expression: string, numbers: ReadonlySet<string>): ComputedNode | null {\n const key = `${[...numbers].join(',')}|${expression}`;\n if (!parsed.has(key)) {\n try {\n parsed.set(\n key,\n parseComputed(expression, (name) => numbers.has(name)),\n );\n } catch {\n parsed.set(key, null);\n }\n }\n return parsed.get(key)!;\n}\n\nconst asNumber = (value: unknown): number | null => {\n if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n if (typeof value !== 'string' || !value.trim()) return null;\n const n = Number(value.replace(/[,\\s]/g, ''));\n return Number.isFinite(n) ? n : null;\n};\n\nexport function liveFigures(field: LiveField, fields: readonly AppCommandField[], values: CommandValues): Array<[string, number]> {\n const numbers = new Set(fields.filter((each) => each.type === 'number' || each.type === 'money').map((each) => each.name));\n const out: Array<[string, number]> = [];\n for (const [word, expression] of Object.entries(field.live ?? {})) {\n const node = tree(expression, numbers);\n if (!node) continue;\n let missing = false;\n const value = evaluateComputed(node, { today: '', now: new Date(), me: null }, (name) => {\n const n = asNumber(values[name]);\n if (n === null) missing = true;\n return n;\n });\n if (!missing && typeof value === 'number' && Number.isFinite(value)) out.push([word, value]);\n }\n return out;\n}\n\nexport function CommandLive({\n field,\n fields,\n values,\n id,\n currency = 'USD',\n}: {\n field: LiveField;\n fields: readonly AppCommandField[];\n values: CommandValues;\n id: string;\n currency?: string;\n}) {\n const figures = useMemo(() => liveFigures(field, fields, values), [field, fields, values]);\n const say = (n: number) =>\n new Intl.NumberFormat(\n formatLocale(),\n field.type === 'money'\n ? { style: 'currency', currency, maximumFractionDigits: Math.abs(n) >= 1000 ? 0 : 2 }\n : { maximumFractionDigits: 2 },\n ).format(n);\n return (\n <p id={id} className=\"command-live\" aria-live=\"polite\">\n {figures.map(([word, n], at) => (\n <span key={word}>\n {at > 0 && ' · '}\n {say(n)} {word}\n </span>\n ))}\n </p>\n );\n}\n"],"mappings":";;;;;;;AAgBA,IAAM,yBAAS,IAAI,IAAiC;AACpD,SAAS,KAAK,YAAoB,SAAmD;CACnF,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;CACzC,IAAI,CAAC,OAAO,IAAI,GAAG,GACjB,IAAI;EACF,OAAO,IACL,KACA,cAAc,aAAa,SAAS,QAAQ,IAAI,IAAI,CAAC,CACvD;CACF,QAAQ;EACN,OAAO,IAAI,KAAK,IAAI;CACtB;CAEF,OAAO,OAAO,IAAI,GAAG;AACvB;AAEA,IAAM,YAAY,UAAkC;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;CACvE,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,GAAG,OAAO;CACvD,MAAM,IAAI,OAAO,MAAM,QAAQ,UAAU,EAAE,CAAC;CAC5C,OAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAgB,YAAY,OAAkB,QAAoC,QAAgD;CAChI,MAAM,UAAU,IAAI,IAAI,OAAO,QAAQ,SAAS,KAAK,SAAS,YAAY,KAAK,SAAS,OAAO,CAAC,CAAC,KAAK,SAAS,KAAK,IAAI,CAAC;CACzH,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,MAAM,QAAQ,CAAC,CAAC,GAAG;EACjE,MAAM,OAAO,KAAK,YAAY,OAAO;EACrC,IAAI,CAAC,MAAM;EACX,IAAI,UAAU;EACd,MAAM,QAAQ,iBAAiB,MAAM;GAAE,OAAO;GAAI,qBAAK,IAAI,KAAK;GAAG,IAAI;EAAK,IAAI,SAAS;GACvF,MAAM,IAAI,SAAS,OAAO,KAAK;GAC/B,IAAI,MAAM,MAAM,UAAU;GAC1B,OAAO;EACT,CAAC;EACD,IAAI,CAAC,WAAW,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;CAC7F;CACA,OAAO;AACT;AAEA,SAAgB,YAAY,EAC1B,OACA,QACA,QACA,IACA,WAAW,SAOV;CACD,MAAM,UAAU,cAAc,YAAY,OAAO,QAAQ,MAAM,GAAG;EAAC;EAAO;EAAQ;CAAM,CAAC;CACzF,MAAM,OAAO,MACX,IAAI,KAAK,aACP,aAAa,GACb,MAAM,SAAS,UACX;EAAE,OAAO;EAAY;EAAU,uBAAuB,KAAK,IAAI,CAAC,KAAK,MAAO,IAAI;CAAE,IAClF,EAAE,uBAAuB,EAAE,CACjC,CAAC,CAAC,OAAO,CAAC;CACZ,OACE,oBAAC,KAAD;EAAO;EAAI,WAAU;EAAe,aAAU;EAC3C,UAAA,QAAQ,KAAK,CAAC,MAAM,IAAI,OACvB,qBAAC,QAAD,EAAA,UAAA;GACG,KAAK,KAAK;GACV,IAAI,CAAC;GAAE;GAAE;EACN,EAAA,GAHK,IAGL,CACP;CACA,CAAA;AAEP"}
1
+ {"version":3,"file":"shared-DprrHC72.js","names":[],"sources":["../../src/client/components/CommandLive.tsx"],"sourcesContent":["/**\n * A number's `live` lines, said under it as it is typed: \"$54,167 monthly · $650,000 annually\". Each is an expression\n * over the input's numbers (`biweekly_gross * 26 / 12`), read by the evaluator computed columns use, in the field's\n * format. Display only: nothing here is sent, and a line whose numbers are not all given says nothing.\n */\nimport { type ComputedNode } from '@mistriwork/engine/shared/computed.js';\nimport { parseComputed } from '@mistriwork/engine/shared/computedParse.js';\nimport { evaluateComputed } from '@mistriwork/engine/shared/computedValue.js';\nimport { formatLocale } from '@mistriwork/kit/client/locale.js';\nimport { useMemo } from 'react';\nimport './CommandLive.css';\nimport type { AppCommandField } from '../../shared/command.js';\nimport type { CommandValues } from './CommandFields.js';\n\ntype LiveField = AppCommandField & { type: 'number' | 'money'; live?: Readonly<Record<string, string>> };\n\nconst parsed = new Map<string, ComputedNode | null>();\nfunction tree(expression: string, numbers: ReadonlySet<string>): ComputedNode | null {\n const key = `${[...numbers].join(',')}|${expression}`;\n if (!parsed.has(key)) {\n try {\n parsed.set(\n key,\n parseComputed(expression, (name) => numbers.has(name)),\n );\n } catch {\n parsed.set(key, null);\n }\n }\n return parsed.get(key)!;\n}\n\nconst asNumber = (value: unknown): number | null => {\n if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n if (typeof value !== 'string' || !value.trim()) return null;\n const n = Number(value.replace(/[,\\s]/g, ''));\n return Number.isFinite(n) ? n : null;\n};\n\nexport function liveFigures(field: LiveField, fields: readonly AppCommandField[], values: CommandValues): Array<[string, number]> {\n const numbers = new Set(fields.filter((each) => each.type === 'number' || each.type === 'money').map((each) => each.name));\n const out: Array<[string, number]> = [];\n for (const [word, expression] of Object.entries(field.live ?? {})) {\n const node = tree(expression, numbers);\n if (!node) continue;\n let missing = false;\n const value = evaluateComputed(node, { today: '', now: new Date(), me: null }, (name) => {\n const n = asNumber(values[name]);\n if (n === null) missing = true;\n return n;\n });\n if (!missing && typeof value === 'number' && Number.isFinite(value)) out.push([word, value]);\n }\n return out;\n}\n\nexport function CommandLive({\n field,\n fields,\n values,\n id,\n currency = 'USD',\n}: {\n field: LiveField;\n fields: readonly AppCommandField[];\n values: CommandValues;\n id: string;\n currency?: string;\n}) {\n const figures = useMemo(() => liveFigures(field, fields, values), [field, fields, values]);\n const say = (n: number) =>\n new Intl.NumberFormat(\n formatLocale(),\n field.type === 'money'\n ? { style: 'currency', currency, maximumFractionDigits: Math.abs(n) >= 1000 ? 0 : 2 }\n : { maximumFractionDigits: 2 },\n ).format(n);\n return (\n <p id={id} className=\"command-live\" aria-live=\"polite\">\n {figures.map(([word, n], at) => (\n <span key={word}>\n {at > 0 && ' · '}\n {say(n)} {word}\n </span>\n ))}\n </p>\n );\n}\n"],"mappings":";;;;;;;AAgBA,IAAM,yBAAS,IAAI,IAAiC;AACpD,SAAS,KAAK,YAAoB,SAAmD;CACnF,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;CACzC,IAAI,CAAC,OAAO,IAAI,GAAG,GACjB,IAAI;EACF,OAAO,IACL,KACA,cAAc,aAAa,SAAS,QAAQ,IAAI,IAAI,CAAC,CACvD;CACF,QAAQ;EACN,OAAO,IAAI,KAAK,IAAI;CACtB;CAEF,OAAO,OAAO,IAAI,GAAG;AACvB;AAEA,IAAM,YAAY,UAAkC;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;CACvE,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,GAAG,OAAO;CACvD,MAAM,IAAI,OAAO,MAAM,QAAQ,UAAU,EAAE,CAAC;CAC5C,OAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAgB,YAAY,OAAkB,QAAoC,QAAgD;CAChI,MAAM,UAAU,IAAI,IAAI,OAAO,QAAQ,SAAS,KAAK,SAAS,YAAY,KAAK,SAAS,OAAO,CAAC,CAAC,KAAK,SAAS,KAAK,IAAI,CAAC;CACzH,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,MAAM,QAAQ,CAAC,CAAC,GAAG;EACjE,MAAM,OAAO,KAAK,YAAY,OAAO;EACrC,IAAI,CAAC,MAAM;EACX,IAAI,UAAU;EACd,MAAM,QAAQ,iBAAiB,MAAM;GAAE,OAAO;GAAI,qBAAK,IAAI,KAAK;GAAG,IAAI;EAAK,IAAI,SAAS;GACvF,MAAM,IAAI,SAAS,OAAO,KAAK;GAC/B,IAAI,MAAM,MAAM,UAAU;GAC1B,OAAO;EACT,CAAC;EACD,IAAI,CAAC,WAAW,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;CAC7F;CACA,OAAO;AACT;AAEA,SAAgB,YAAY,EAC1B,OACA,QACA,QACA,IACA,WAAW,SAOV;CACD,MAAM,UAAU,cAAc,YAAY,OAAO,QAAQ,MAAM,GAAG;EAAC;EAAO;EAAQ;CAAM,CAAC;CACzF,MAAM,OAAO,MACX,IAAI,KAAK,aACP,aAAa,GACb,MAAM,SAAS,UACX;EAAE,OAAO;EAAY;EAAU,uBAAuB,KAAK,IAAI,CAAC,KAAK,MAAO,IAAI;CAAE,IAClF,EAAE,uBAAuB,EAAE,CACjC,CAAC,CAAC,OAAO,CAAC;CACZ,OACE,oBAAC,KAAD;EAAO;EAAI,WAAU;EAAe,aAAU;EAC3C,UAAA,QAAQ,KAAK,CAAC,MAAM,IAAI,OACvB,qBAAC,QAAD,EAAA,UAAA;GACG,KAAK,KAAK;GACV,IAAI,CAAC;GAAE;GAAE;EACN,EAAA,GAHK,IAGL,CACP;CACA,CAAA;AAEP"}