@reddoorla/maintenance 0.86.0 → 0.88.2

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 (233) hide show
  1. package/dist/airtable-QG5ICI3Y.js +9 -0
  2. package/dist/{announce-MXTUQ6C7.js → announce-GKD5YJNB.js} +49 -28
  3. package/dist/announce-GKD5YJNB.js.map +1 -0
  4. package/dist/attachments-NLZIJ5NU.js +9 -0
  5. package/dist/{blux-ZGDSHI3U.js → blux-DN4MI532.js} +5 -5
  6. package/dist/{bump-deps-EC74CPLS.js → bump-deps-KGH5UYSF.js} +4 -4
  7. package/dist/{chunk-N45M3FDK.js → chunk-6I6L3CMX.js} +4 -4
  8. package/dist/{chunk-KTH5ZODA.js → chunk-7BZUOZPU.js} +2 -2
  9. package/dist/{chunk-2EVEFOZY.js → chunk-7FFUBF4I.js} +6 -3
  10. package/dist/chunk-7FFUBF4I.js.map +1 -0
  11. package/dist/{chunk-LDWZSLSU.js → chunk-7NYN2P6I.js} +7 -4
  12. package/dist/{chunk-LDWZSLSU.js.map → chunk-7NYN2P6I.js.map} +1 -1
  13. package/dist/{chunk-X3B723J4.js → chunk-7TMWBXX7.js} +184 -25
  14. package/dist/chunk-7TMWBXX7.js.map +1 -0
  15. package/dist/{chunk-3VQWSE3F.js → chunk-7UAYMAKR.js} +4 -4
  16. package/dist/{chunk-W7Z3BGCI.js → chunk-A2UNJIKH.js} +15 -7
  17. package/dist/chunk-A2UNJIKH.js.map +1 -0
  18. package/dist/{chunk-F5KM4LEQ.js → chunk-ANJNTAMM.js} +4 -70
  19. package/dist/chunk-ANJNTAMM.js.map +1 -0
  20. package/dist/{chunk-2ZPT7GLV.js → chunk-B23FFWU4.js} +4 -4
  21. package/dist/chunk-B23FFWU4.js.map +1 -0
  22. package/dist/{chunk-DJZWYIPP.js → chunk-BGTYPVLT.js} +1 -1
  23. package/dist/chunk-BGTYPVLT.js.map +1 -0
  24. package/dist/chunk-BRYXFH77.js +70 -0
  25. package/dist/chunk-BRYXFH77.js.map +1 -0
  26. package/dist/{chunk-2NDRHXMR.js → chunk-DGZWFI2G.js} +18 -141
  27. package/dist/chunk-DGZWFI2G.js.map +1 -0
  28. package/dist/{chunk-ACBRLLFT.js → chunk-F3JWOPTG.js} +7 -3
  29. package/dist/chunk-F3JWOPTG.js.map +1 -0
  30. package/dist/chunk-GQDDWYU2.js +372 -0
  31. package/dist/chunk-GQDDWYU2.js.map +1 -0
  32. package/dist/{chunk-Z7AXYKQB.js → chunk-IY6256GK.js} +1 -1
  33. package/dist/{chunk-BYK66EWO.js → chunk-J3WEZGIN.js} +52 -46
  34. package/dist/chunk-J3WEZGIN.js.map +1 -0
  35. package/dist/{chunk-BNM5POSJ.js → chunk-JRHIFA5R.js} +2 -2
  36. package/dist/{chunk-ZKXSX4LO.js → chunk-K6SLF6GI.js} +6 -6
  37. package/dist/{chunk-CQR4LIM7.js → chunk-KJGKZLCT.js} +7 -3
  38. package/dist/{chunk-CQR4LIM7.js.map → chunk-KJGKZLCT.js.map} +1 -1
  39. package/dist/{chunk-PPE5KZJK.js → chunk-KMCZA7CH.js} +13 -3
  40. package/dist/chunk-KMCZA7CH.js.map +1 -0
  41. package/dist/{chunk-3SF7D4DY.js → chunk-MFHS7IQ2.js} +36 -1
  42. package/dist/{chunk-3SF7D4DY.js.map → chunk-MFHS7IQ2.js.map} +1 -1
  43. package/dist/{chunk-SNSTJLQW.js → chunk-NO74DXDP.js} +48 -2
  44. package/dist/chunk-NO74DXDP.js.map +1 -0
  45. package/dist/{chunk-C6B4RHXR.js → chunk-O3GT46R2.js} +8 -2
  46. package/dist/chunk-O3GT46R2.js.map +1 -0
  47. package/dist/{chunk-ITIOFN7A.js → chunk-P6AORG32.js} +29 -10
  48. package/dist/chunk-P6AORG32.js.map +1 -0
  49. package/dist/{chunk-SLUQ7R6I.js → chunk-PR343K7F.js} +5 -5
  50. package/dist/{chunk-P2FE5AI6.js → chunk-Q47DU255.js} +4 -4
  51. package/dist/{chunk-EVBCK2KC.js → chunk-RARREDQE.js} +2 -1
  52. package/dist/{chunk-EVBCK2KC.js.map → chunk-RARREDQE.js.map} +1 -1
  53. package/dist/chunk-RUPPWAD6.js +7 -0
  54. package/dist/chunk-RUPPWAD6.js.map +1 -0
  55. package/dist/{chunk-VSBFM7DS.js → chunk-SJOXWJ5Y.js} +3 -3
  56. package/dist/{chunk-TERE5SDF.js → chunk-ULHPALI5.js} +4 -4
  57. package/dist/{chunk-XUGSZP7Z.js → chunk-W45FZEGE.js} +2 -2
  58. package/dist/{chunk-JHGHLKFS.js → chunk-WS6NU475.js} +2 -2
  59. package/dist/chunk-XP5WM2LV.js +146 -0
  60. package/dist/chunk-XP5WM2LV.js.map +1 -0
  61. package/dist/{chunk-ILUNZHYV.js → chunk-YFAXOD7Z.js} +10 -6
  62. package/dist/chunk-YFAXOD7Z.js.map +1 -0
  63. package/dist/{chunk-6TWWY7ZU.js → chunk-Z3JXLULD.js} +2 -2
  64. package/dist/cli/bin.js +34 -29
  65. package/dist/cli/bin.js.map +1 -1
  66. package/dist/cli/commands/audit.d.ts +4 -0
  67. package/dist/cli/commands/audit.js +25 -18
  68. package/dist/cli/commands/audit.js.map +1 -1
  69. package/dist/client-JYKPVCJX.js +11 -0
  70. package/dist/configs/playwright-a11y.js +1 -1
  71. package/dist/{convert-to-pnpm-6L6P74WR.js → convert-to-pnpm-NWB5FCQ2.js} +4 -4
  72. package/dist/{db-B35OGV3G.js → db-V6UIS433.js} +124 -29
  73. package/dist/db-V6UIS433.js.map +1 -0
  74. package/dist/{digest-BL6UJYKH.js → digest-2JEAEJK6.js} +60 -35
  75. package/dist/digest-2JEAEJK6.js.map +1 -0
  76. package/dist/digest-state-CRZRNGDI.js +25 -0
  77. package/dist/digest-state-CRZRNGDI.js.map +1 -0
  78. package/dist/digest-state-F37XUCS5.js +13 -0
  79. package/dist/{dump-P6EBSIOH.js → dump-EPL7AZFR.js} +38 -13
  80. package/dist/dump-EPL7AZFR.js.map +1 -0
  81. package/dist/{email-VTTTKKL5.js → email-SL7464YI.js} +16 -6
  82. package/dist/{email-VTTTKKL5.js.map → email-SL7464YI.js.map} +1 -1
  83. package/dist/{ensure-site-DFTB7MN3.js → ensure-site-FU6527OU.js} +19 -11
  84. package/dist/ensure-site-FU6527OU.js.map +1 -0
  85. package/dist/fleet-state-R5LU7IC3.js +41 -0
  86. package/dist/{forms-notify-target-75U6OU7L.js → forms-notify-target-SGDGXXK5.js} +10 -7
  87. package/dist/forms-notify-target-SGDGXXK5.js.map +1 -0
  88. package/dist/{github-signals-IJUPGCUX.js → github-signals-GWGCKEE2.js} +13 -12
  89. package/dist/{github-signals-IJUPGCUX.js.map → github-signals-GWGCKEE2.js.map} +1 -1
  90. package/dist/{header-image-AWLLQA4L.js → header-image-7A7XLVEU.js} +8 -8
  91. package/dist/{header-images-335L2AKB.js → header-images-SQBCE5OD.js} +21 -1
  92. package/dist/header-images-SQBCE5OD.js.map +1 -0
  93. package/dist/{health-endpoint-WJC2UMYS.js → health-endpoint-UVX6R5N5.js} +4 -4
  94. package/dist/health-mirror-GCDN7BJL.js +46 -0
  95. package/dist/health-mirror-GCDN7BJL.js.map +1 -0
  96. package/dist/{import-airtable-HE43FB4H.js → import-airtable-OHVY344G.js} +7 -5
  97. package/dist/index.d.ts +149 -0
  98. package/dist/index.js +245 -108
  99. package/dist/index.js.map +1 -1
  100. package/dist/{init-NJEOEO7I.js → init-FGTYNGUI.js} +11 -11
  101. package/dist/{launch-XNAX427U.js → launch-5H7LPEGP.js} +59 -29
  102. package/dist/launch-5H7LPEGP.js.map +1 -0
  103. package/dist/{lighthouse-airtable-LBVIYYP5.js → lighthouse-airtable-3GUWHETW.js} +3 -3
  104. package/dist/migrate-YITCXBLS.js +7 -0
  105. package/dist/{notify-C645PGD5.js → notify-4HP4GZ2H.js} +5 -5
  106. package/dist/{onboard-GK4PHPNI.js → onboard-BKCYBAVW.js} +4 -4
  107. package/dist/{orchestrate-77UO4BYJ.js → orchestrate-JNKCWU5K.js} +14 -13
  108. package/dist/parity-NBYZUBFU.js +14 -0
  109. package/dist/pdf-N5QLQ5ZQ.js +38 -0
  110. package/dist/pdf-N5QLQ5ZQ.js.map +1 -0
  111. package/dist/{pipeline-S6CVHDGB.js → pipeline-WYTXSLHX.js} +3 -3
  112. package/dist/{preflight-6WBNU2GZ.js → preflight-IMTYDDD4.js} +15 -14
  113. package/dist/{preflight-6WBNU2GZ.js.map → preflight-IMTYDDD4.js.map} +1 -1
  114. package/dist/{prismic-ci-3RHOZUUT.js → prismic-ci-MY2U6NN7.js} +7 -7
  115. package/dist/{prismic-models-QBGUT2Q3.js → prismic-models-NJN5JORF.js} +15 -10
  116. package/dist/prismic-models-NJN5JORF.js.map +1 -0
  117. package/dist/prospect/types.d.ts +281 -0
  118. package/dist/prospect/types.js +1 -0
  119. package/dist/{prospect-audit-IUNCJ2LV.js → prospect-audit-JMBRVXSG.js} +32 -13
  120. package/dist/prospect-audit-JMBRVXSG.js.map +1 -0
  121. package/dist/recipes/sync-configs.js +2 -2
  122. package/dist/render-VX72AESY.js +10 -0
  123. package/dist/render-VX72AESY.js.map +1 -0
  124. package/dist/render-from-row-DOYJDO2L.js +17 -0
  125. package/dist/render-from-row-DOYJDO2L.js.map +1 -0
  126. package/dist/{renovate-dispatch-QJY75ODR.js → renovate-dispatch-6GQWTRKX.js} +10 -10
  127. package/dist/{renovate-dispatch-QJY75ODR.js.map → renovate-dispatch-6GQWTRKX.js.map} +1 -1
  128. package/dist/{replay-C2LXMJZP.js → replay-4S6ENYCH.js} +3 -3
  129. package/dist/{report-4EVNDHOZ.js → report-TALY5P3L.js} +63 -21
  130. package/dist/report-TALY5P3L.js.map +1 -0
  131. package/dist/report-mirror-AAK3MXHY.js +55 -0
  132. package/dist/report-mirror-AAK3MXHY.js.map +1 -0
  133. package/dist/rerender-3FM6DNRH.js +32 -0
  134. package/dist/rerender-3FM6DNRH.js.map +1 -0
  135. package/dist/schema-75PJECAE.js +18 -0
  136. package/dist/schema-75PJECAE.js.map +1 -0
  137. package/dist/{self-updating-6DWI2Z56.js → self-updating-NTCCWJYZ.js} +4 -4
  138. package/dist/{selftest-RC36MTKP.js → selftest-NZTR4SM2.js} +26 -24
  139. package/dist/{selftest-RC36MTKP.js.map → selftest-NZTR4SM2.js.map} +1 -1
  140. package/dist/site-mirror-Q763PLGG.js +62 -0
  141. package/dist/site-mirror-Q763PLGG.js.map +1 -0
  142. package/dist/{smoke-suite-VYY2LXM2.js → smoke-suite-4JFZZYVW.js} +4 -4
  143. package/dist/{spam-classifier-XLBFCO7G.js → spam-classifier-T5H4VTP6.js} +4 -2
  144. package/dist/spam-classifier-T5H4VTP6.js.map +1 -0
  145. package/dist/{submissions-Q7DRFOCD.js → submissions-TEHPBSZJ.js} +5 -5
  146. package/dist/{svelte-codemods-25J2FO22.js → svelte-codemods-QAPDZC6F.js} +4 -4
  147. package/dist/{sync-34HNZZ56.js → sync-GBFV45WX.js} +6 -4
  148. package/dist/{sync-34HNZZ56.js.map → sync-GBFV45WX.js.map} +1 -1
  149. package/dist/{sync-configs-3SE6PSCO.js → sync-configs-34JIA6KZ.js} +6 -6
  150. package/dist/{upgrade-WBRQ62FX.js → upgrade-IR2OO3KP.js} +4 -4
  151. package/dist/{webflow-IRKOIT7S.js → webflow-624FEBFD.js} +4 -4
  152. package/dist/{webhook-VOGOW3W6.js → webhook-XM6EV4XT.js} +2 -2
  153. package/dist/{websites-SXWTP72S.js → websites-D6WYFAES.js} +2 -2
  154. package/dist/websites-D6WYFAES.js.map +1 -0
  155. package/dist/write-audits-to-airtable-OBHGR7DQ.js +17 -0
  156. package/dist/write-audits-to-airtable-OBHGR7DQ.js.map +1 -0
  157. package/package.json +5 -1
  158. package/dist/airtable-G7WBQQMI.js +0 -9
  159. package/dist/announce-MXTUQ6C7.js.map +0 -1
  160. package/dist/chunk-2EVEFOZY.js.map +0 -1
  161. package/dist/chunk-2NDRHXMR.js.map +0 -1
  162. package/dist/chunk-2ZPT7GLV.js.map +0 -1
  163. package/dist/chunk-ACBRLLFT.js.map +0 -1
  164. package/dist/chunk-BYK66EWO.js.map +0 -1
  165. package/dist/chunk-C6B4RHXR.js.map +0 -1
  166. package/dist/chunk-DJZWYIPP.js.map +0 -1
  167. package/dist/chunk-F5KM4LEQ.js.map +0 -1
  168. package/dist/chunk-ILUNZHYV.js.map +0 -1
  169. package/dist/chunk-ITIOFN7A.js.map +0 -1
  170. package/dist/chunk-PPE5KZJK.js.map +0 -1
  171. package/dist/chunk-SNSTJLQW.js.map +0 -1
  172. package/dist/chunk-W7Z3BGCI.js.map +0 -1
  173. package/dist/chunk-X3B723J4.js.map +0 -1
  174. package/dist/client-AUE3XUPO.js +0 -11
  175. package/dist/db-B35OGV3G.js.map +0 -1
  176. package/dist/digest-BL6UJYKH.js.map +0 -1
  177. package/dist/dump-P6EBSIOH.js.map +0 -1
  178. package/dist/ensure-site-DFTB7MN3.js.map +0 -1
  179. package/dist/forms-notify-target-75U6OU7L.js.map +0 -1
  180. package/dist/header-images-335L2AKB.js.map +0 -1
  181. package/dist/health-mirror-4B2L7T2Y.js +0 -75
  182. package/dist/health-mirror-4B2L7T2Y.js.map +0 -1
  183. package/dist/launch-XNAX427U.js.map +0 -1
  184. package/dist/migrate-GQH237A6.js +0 -7
  185. package/dist/parity-MWS24A3H.js +0 -12
  186. package/dist/prismic-models-QBGUT2Q3.js.map +0 -1
  187. package/dist/prospect-audit-IUNCJ2LV.js.map +0 -1
  188. package/dist/render-AQAVQDPZ.js +0 -10
  189. package/dist/report-4EVNDHOZ.js.map +0 -1
  190. package/dist/write-audits-to-airtable-MBEILLPU.js +0 -14
  191. /package/dist/{airtable-G7WBQQMI.js.map → airtable-QG5ICI3Y.js.map} +0 -0
  192. /package/dist/{client-AUE3XUPO.js.map → attachments-NLZIJ5NU.js.map} +0 -0
  193. /package/dist/{blux-ZGDSHI3U.js.map → blux-DN4MI532.js.map} +0 -0
  194. /package/dist/{bump-deps-EC74CPLS.js.map → bump-deps-KGH5UYSF.js.map} +0 -0
  195. /package/dist/{chunk-N45M3FDK.js.map → chunk-6I6L3CMX.js.map} +0 -0
  196. /package/dist/{chunk-KTH5ZODA.js.map → chunk-7BZUOZPU.js.map} +0 -0
  197. /package/dist/{chunk-3VQWSE3F.js.map → chunk-7UAYMAKR.js.map} +0 -0
  198. /package/dist/{chunk-Z7AXYKQB.js.map → chunk-IY6256GK.js.map} +0 -0
  199. /package/dist/{chunk-BNM5POSJ.js.map → chunk-JRHIFA5R.js.map} +0 -0
  200. /package/dist/{chunk-ZKXSX4LO.js.map → chunk-K6SLF6GI.js.map} +0 -0
  201. /package/dist/{chunk-SLUQ7R6I.js.map → chunk-PR343K7F.js.map} +0 -0
  202. /package/dist/{chunk-P2FE5AI6.js.map → chunk-Q47DU255.js.map} +0 -0
  203. /package/dist/{chunk-VSBFM7DS.js.map → chunk-SJOXWJ5Y.js.map} +0 -0
  204. /package/dist/{chunk-TERE5SDF.js.map → chunk-ULHPALI5.js.map} +0 -0
  205. /package/dist/{chunk-XUGSZP7Z.js.map → chunk-W45FZEGE.js.map} +0 -0
  206. /package/dist/{chunk-JHGHLKFS.js.map → chunk-WS6NU475.js.map} +0 -0
  207. /package/dist/{chunk-6TWWY7ZU.js.map → chunk-Z3JXLULD.js.map} +0 -0
  208. /package/dist/{import-airtable-HE43FB4H.js.map → client-JYKPVCJX.js.map} +0 -0
  209. /package/dist/{convert-to-pnpm-6L6P74WR.js.map → convert-to-pnpm-NWB5FCQ2.js.map} +0 -0
  210. /package/dist/{lighthouse-airtable-LBVIYYP5.js.map → digest-state-F37XUCS5.js.map} +0 -0
  211. /package/dist/{migrate-GQH237A6.js.map → fleet-state-R5LU7IC3.js.map} +0 -0
  212. /package/dist/{header-image-AWLLQA4L.js.map → header-image-7A7XLVEU.js.map} +0 -0
  213. /package/dist/{health-endpoint-WJC2UMYS.js.map → health-endpoint-UVX6R5N5.js.map} +0 -0
  214. /package/dist/{notify-C645PGD5.js.map → import-airtable-OHVY344G.js.map} +0 -0
  215. /package/dist/{init-NJEOEO7I.js.map → init-FGTYNGUI.js.map} +0 -0
  216. /package/dist/{orchestrate-77UO4BYJ.js.map → lighthouse-airtable-3GUWHETW.js.map} +0 -0
  217. /package/dist/{parity-MWS24A3H.js.map → migrate-YITCXBLS.js.map} +0 -0
  218. /package/dist/{pipeline-S6CVHDGB.js.map → notify-4HP4GZ2H.js.map} +0 -0
  219. /package/dist/{onboard-GK4PHPNI.js.map → onboard-BKCYBAVW.js.map} +0 -0
  220. /package/dist/{render-AQAVQDPZ.js.map → orchestrate-JNKCWU5K.js.map} +0 -0
  221. /package/dist/{spam-classifier-XLBFCO7G.js.map → parity-NBYZUBFU.js.map} +0 -0
  222. /package/dist/{websites-SXWTP72S.js.map → pipeline-WYTXSLHX.js.map} +0 -0
  223. /package/dist/{prismic-ci-3RHOZUUT.js.map → prismic-ci-MY2U6NN7.js.map} +0 -0
  224. /package/dist/{write-audits-to-airtable-MBEILLPU.js.map → prospect/types.js.map} +0 -0
  225. /package/dist/{replay-C2LXMJZP.js.map → replay-4S6ENYCH.js.map} +0 -0
  226. /package/dist/{self-updating-6DWI2Z56.js.map → self-updating-NTCCWJYZ.js.map} +0 -0
  227. /package/dist/{smoke-suite-VYY2LXM2.js.map → smoke-suite-4JFZZYVW.js.map} +0 -0
  228. /package/dist/{submissions-Q7DRFOCD.js.map → submissions-TEHPBSZJ.js.map} +0 -0
  229. /package/dist/{svelte-codemods-25J2FO22.js.map → svelte-codemods-QAPDZC6F.js.map} +0 -0
  230. /package/dist/{sync-configs-3SE6PSCO.js.map → sync-configs-34JIA6KZ.js.map} +0 -0
  231. /package/dist/{upgrade-WBRQ62FX.js.map → upgrade-IR2OO3KP.js.map} +0 -0
  232. /package/dist/{webflow-IRKOIT7S.js.map → webflow-624FEBFD.js.map} +0 -0
  233. /package/dist/{webhook-VOGOW3W6.js.map → webhook-XM6EV4XT.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,29 +1,22 @@
1
+ import "./chunk-ANJNTAMM.js";
1
2
  import "./chunk-2SLED3NB.js";
2
- import "./chunk-F5KM4LEQ.js";
3
3
  import {
4
4
  draftReportForSite
5
- } from "./chunk-BYK66EWO.js";
5
+ } from "./chunk-J3WEZGIN.js";
6
6
  import {
7
7
  approveBlockers
8
- } from "./chunk-N45M3FDK.js";
8
+ } from "./chunk-6I6L3CMX.js";
9
9
  import {
10
10
  findDueReports
11
11
  } from "./chunk-PZR4EBPY.js";
12
- import {
13
- upgradeSvelte4to5
14
- } from "./chunk-Z3KYFUN6.js";
15
12
  import {
16
13
  DEFAULT_INIT_STEPS,
17
14
  a11yFixturesPage,
18
15
  init
19
- } from "./chunk-ZKXSX4LO.js";
20
- import {
21
- convertToPnpm
22
- } from "./chunk-XIDUYLSR.js";
16
+ } from "./chunk-K6SLF6GI.js";
23
17
  import {
24
18
  svelteCodemods
25
19
  } from "./chunk-IYSEC543.js";
26
- import "./chunk-J7SZQUCW.js";
27
20
  import "./chunk-BQRFQSGY.js";
28
21
  import "./chunk-Y53WOQIJ.js";
29
22
  import {
@@ -31,19 +24,39 @@ import {
31
24
  selfCaretRange,
32
25
  selfPackageVersion
33
26
  } from "./chunk-7RJ2HMMJ.js";
34
- import "./chunk-ILUNZHYV.js";
27
+ import "./chunk-YFAXOD7Z.js";
35
28
  import {
36
29
  bumpDeps
37
30
  } from "./chunk-2NJQLSEU.js";
38
31
  import "./chunk-R53VL3RI.js";
39
32
  import {
40
- fromAirtableBase
41
- } from "./chunk-SLUQ7R6I.js";
33
+ upgradeSvelte4to5
34
+ } from "./chunk-Z3KYFUN6.js";
35
+ import "./chunk-J7SZQUCW.js";
36
+ import {
37
+ convertToPnpm
38
+ } from "./chunk-XIDUYLSR.js";
42
39
  import {
43
40
  parseAddresses,
44
41
  sendApprovedReports,
45
42
  withGlobalCc
46
- } from "./chunk-2NDRHXMR.js";
43
+ } from "./chunk-DGZWFI2G.js";
44
+ import "./chunk-BRYXFH77.js";
45
+ import "./chunk-NYUYNYQL.js";
46
+ import {
47
+ isPendingApproval
48
+ } from "./chunk-F3JWOPTG.js";
49
+ import {
50
+ checklistFor,
51
+ gatingFields,
52
+ isHealthGateClear
53
+ } from "./chunk-VAD43MOR.js";
54
+ import {
55
+ fromAirtableBase
56
+ } from "./chunk-PR343K7F.js";
57
+ import "./chunk-IY6256GK.js";
58
+ import "./chunk-HN6I2XWI.js";
59
+ import "./chunk-XP5WM2LV.js";
47
60
  import "./chunk-GECESMVS.js";
48
61
  import {
49
62
  domainFromUrl,
@@ -55,29 +68,25 @@ import {
55
68
  BLURRED_CID,
56
69
  CHECK_CID,
57
70
  loadBundledImages,
58
- renderReportHtml
59
- } from "./chunk-CQR4LIM7.js";
60
- import "./chunk-Z7AXYKQB.js";
71
+ renderReportHtml,
72
+ rendersCommentary
73
+ } from "./chunk-KJGKZLCT.js";
61
74
  import {
62
75
  escapeHtml,
63
76
  safeUrl
64
- } from "./chunk-DJZWYIPP.js";
77
+ } from "./chunk-BGTYPVLT.js";
65
78
  import {
66
- isPendingApproval
67
- } from "./chunk-ACBRLLFT.js";
68
- import {
69
- checklistFor,
70
- gatingFields,
71
- isHealthGateClear
72
- } from "./chunk-VAD43MOR.js";
73
- import "./chunk-HN6I2XWI.js";
79
+ CANONICAL_STATUSES,
80
+ SEVERITY_RANK,
81
+ siteSlug,
82
+ toAirtableStatus
83
+ } from "./chunk-7NYN2P6I.js";
74
84
  import "./chunk-WEKL2HYJ.js";
75
- import "./chunk-NYUYNYQL.js";
76
85
  import {
77
86
  syncConfigs
78
- } from "./chunk-3VQWSE3F.js";
79
- import "./chunk-3G25KIWW.js";
87
+ } from "./chunk-7UAYMAKR.js";
80
88
  import "./chunk-73UFRZKY.js";
89
+ import "./chunk-3G25KIWW.js";
81
90
  import "./chunk-OTGB6YQO.js";
82
91
  import {
83
92
  ALL_AUDIT_NAMES,
@@ -87,18 +96,18 @@ import {
87
96
  runAudits,
88
97
  runAuditsAcross,
89
98
  securityAudit
90
- } from "./chunk-2ZPT7GLV.js";
99
+ } from "./chunk-B23FFWU4.js";
91
100
  import "./chunk-LKGVSM2O.js";
92
101
  import "./chunk-GASBX52O.js";
93
102
  import "./chunk-XHHMM5HX.js";
94
103
  import {
95
104
  fromJsonFile,
96
105
  localPath
97
- } from "./chunk-6TWWY7ZU.js";
98
- import "./chunk-XUGSZP7Z.js";
99
- import "./chunk-EVBCK2KC.js";
100
- import "./chunk-JHGHLKFS.js";
101
- import "./chunk-3SF7D4DY.js";
106
+ } from "./chunk-Z3JXLULD.js";
107
+ import "./chunk-W45FZEGE.js";
108
+ import "./chunk-RARREDQE.js";
109
+ import "./chunk-WS6NU475.js";
110
+ import "./chunk-MFHS7IQ2.js";
102
111
  import "./chunk-A3WDWNN5.js";
103
112
  import {
104
113
  isLeadFormType
@@ -111,15 +120,9 @@ import "./chunk-UDRHAQT7.js";
111
120
  import "./chunk-PTDMVGKL.js";
112
121
  import "./chunk-LBYOLBW7.js";
113
122
  import "./chunk-XXTZBPUY.js";
114
- import {
115
- CANONICAL_STATUSES,
116
- SEVERITY_RANK,
117
- siteSlug,
118
- toAirtableStatus
119
- } from "./chunk-LDWZSLSU.js";
120
123
  import "./chunk-6VAL7XJT.js";
121
124
  import "./chunk-NMAWXBJA.js";
122
- import "./chunk-C6B4RHXR.js";
125
+ import "./chunk-O3GT46R2.js";
123
126
 
124
127
  // src/recipes/index.ts
125
128
  var ALL_RECIPE_NAMES = [
@@ -314,6 +317,15 @@ var SUBMISSION_STATUS_SCRIPT = `document.querySelectorAll("button.subm-status").
314
317
  // src/dashboard/site-details.ts
315
318
  var SITE_STATUS_OPTIONS = CANONICAL_STATUSES.map(toAirtableStatus);
316
319
  var FREQ_OPTIONS = ["None", "Monthly", "Quarterly", "Yearly"];
320
+ var WATCH_CONDITION_OPTIONS = [
321
+ "Performance",
322
+ "Accessibility",
323
+ "Best Practices",
324
+ "SEO",
325
+ "stale repo",
326
+ "no custom domain"
327
+ ];
328
+ var CLEAR_SECRET = "__clear__";
317
329
 
318
330
  // src/dashboard/render.ts
319
331
  var DASH = "\u2014";
@@ -465,14 +477,27 @@ function recipientsLine(site) {
465
477
  const ccPart = cc.length > 0 ? ` \xB7 CC ${cc.map(escapeHtml).join(", ")}` : "";
466
478
  return `<span class="recipients">To ${to.map(escapeHtml).join(", ")}${ccPart}</span>`;
467
479
  }
480
+ function rerenderButton(r) {
481
+ if (r.sentAt !== null) return "";
482
+ const url = `/api/reports/${encodeURIComponent(r.id)}/rerender`;
483
+ return `<button class="rerender" data-rerender-url="${escapeHtml(url)}" title="Re-render this report from its current row (runs in Actions; takes a minute or two)">refresh preview</button>`;
484
+ }
485
+ function reportPreviewUrl(reportId) {
486
+ return `/api/reports/${encodeURIComponent(reportId)}/preview`;
487
+ }
488
+ function commentaryEditor(r) {
489
+ if (!rendersCommentary(r.reportType)) return "";
490
+ const url = `/api/reports/${encodeURIComponent(r.id)}/commentary`;
491
+ return `<div class="commentary"><label for="commentary-${escapeHtml(r.id)}">Commentary <span class="muted">(re-rendered into the email at send)</span></label><textarea id="commentary-${escapeHtml(r.id)}" data-commentary-for="${escapeHtml(r.id)}" data-commentary-url="${escapeHtml(url)}" rows="3">${escapeHtml(r.commentary ?? "")}</textarea><span class="commentary-saved" data-for="${escapeHtml(r.id)}"></span></div>`;
492
+ }
468
493
  function pendingRow(r, site, now) {
469
494
  const type = escapeHtml(r.reportType);
470
495
  const period = r.period ? escapeHtml(r.period) : "\u2014";
471
496
  const findings = approveBlockers(site, r);
472
497
  const blocked = findings.some((f) => f.level === "fail");
473
- const preview = r.renderedHtmlAttachment ? `<a href="${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}" rel="noopener noreferrer" title="rendered at draft time \u2014 Commentary/subject edits after drafting are not reflected">draft preview \u25B8</a>` : `<span class="muted">no preview yet</span>`;
498
+ const preview = r.renderedHtmlAttachment ? `<a href="${escapeHtml(reportPreviewUrl(r.id))}" rel="noopener noreferrer" title="rendered at draft time \u2014 Commentary/subject edits after drafting are not reflected">draft preview \u25B8</a>` : `<span class="muted">no preview yet</span>`;
474
499
  const sendLine = sendTimingLine(now);
475
- return `<li><div class="pending-head"><strong>${type}</strong> <span class="muted">${period}</span> ${preflightChip(findings)} ${preview} ${approveButton(r, blocked)}</div><div class="pending-info">${recipientsLine(site)} ${sendLine}</div>${checklistBlock(r)}${overrideControl(r)}</li>`;
500
+ return `<li><div class="pending-head"><strong>${type}</strong> <span class="muted">${period}</span> ${preflightChip(findings)} ${preview} ${rerenderButton(r)} ${approveButton(r, blocked)}</div><div class="pending-info">${recipientsLine(site)} ${sendLine}</div>${checklistBlock(r)}${commentaryEditor(r)}${overrideControl(r)}</li>`;
476
501
  }
477
502
  function pendingSection(reports, site, now) {
478
503
  const pending = reports.filter(isPendingApproval);
@@ -502,12 +527,13 @@ function reportRow(r, site) {
502
527
  const id = escapeHtml(r.reportId);
503
528
  const ga = gaUsersCell(r);
504
529
  const search = searchCell(r);
505
- const link = r.renderedHtmlAttachment ? `<a href="${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}">view</a>` : `<span class="muted">no attachment</span>`;
530
+ const link = r.renderedHtmlAttachment ? `<a href="${escapeHtml(reportPreviewUrl(r.id))}">view</a>` : `<span class="muted">no attachment</span>`;
506
531
  const action = isPendingApproval(r) ? approveButton(
507
532
  r,
508
533
  approveBlockers(site, r).some((f) => f.level === "fail")
509
534
  ) : "";
510
- return `<tr><td>${date}</td><td>${type}</td><td><code>${id}</code></td><td>${ga}</td><td>${search}</td><td>${link}</td><td>${action}</td></tr>`;
535
+ const commentary = r.sentAt === null && !isPendingApproval(r) ? `<tr class="commentary-row"><td colspan="7">${commentaryEditor(r)}</td></tr>` : "";
536
+ return `<tr><td>${date}</td><td>${type}</td><td><code>${id}</code></td><td>${ga}</td><td>${search}</td><td>${link}</td><td>${action}</td></tr>${commentary}`;
511
537
  }
512
538
  var SUBMISSIONS_PER_SITE_CAP = 25;
513
539
  function submissionsSection(submissions, site) {
@@ -586,8 +612,45 @@ function selectRow(label, field, options, current, url) {
586
612
  function inputRow(label, field, value, url) {
587
613
  return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="text" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" value="${escapeHtml(value ?? "")}" />${savedSpan(field)}</dd></div>`;
588
614
  }
615
+ var DETAIL_VALUE_FN = `function detailValue(el) {
616
+ if (el.type === "checkbox") return el.checked ? "true" : "false";
617
+ if (el.multiple) return Array.from(el.selectedOptions).map((o) => o.value).join(",");
618
+ return el.value;
619
+ }`;
620
+ var SAVE_DETAIL_FN = `function saveDetail(el, root) {
621
+ const span = root.querySelector('.detail-saved[data-for="' + el.dataset.detailField + '"]');
622
+ return fetch(el.dataset.detailsUrl, {
623
+ method: "POST",
624
+ headers: { "content-type": "application/json" },
625
+ body: JSON.stringify({ field: el.dataset.detailField, value: detailValue(el) }),
626
+ })
627
+ .then((r) => {
628
+ if (span) span.textContent = r.ok ? " \\u2713" : " \\u2717";
629
+ if (r.ok && "defaultValue" in el) el.defaultValue = el.value;
630
+ })
631
+ .catch(() => {
632
+ if (span) span.textContent = " \\u2717";
633
+ });
634
+ }`;
635
+ function toDateInputValue(value) {
636
+ return (value ?? "").slice(0, 10);
637
+ }
589
638
  function dateRow(label, field, value, url) {
590
- return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="date" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" value="${escapeHtml(value ?? "")}" />${savedSpan(field)}</dd></div>`;
639
+ return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="date" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" value="${escapeHtml(toDateInputValue(value))}" />${savedSpan(field)}</dd></div>`;
640
+ }
641
+ function checkboxRow(label, field, checked, url) {
642
+ return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="checkbox" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}"${checked ? " checked" : ""} />${savedSpan(field)}</dd></div>`;
643
+ }
644
+ function multiSelectRow(label, field, options, selected, url) {
645
+ const sel = new Set(selected);
646
+ const opts = options.map(
647
+ (o) => `<option value="${escapeHtml(o)}"${sel.has(o) ? " selected" : ""}>${escapeHtml(o)}</option>`
648
+ ).join("");
649
+ return `<div class="detail wide"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><select multiple id="detail-${field}" data-detail-field="${field}" data-details-url="${url}">${opts}</select>${savedSpan(field)}</dd></div>`;
650
+ }
651
+ function secretRow(label, field, isSet, url) {
652
+ const ph = isSet ? `\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (type to replace, or ${CLEAR_SECRET} to erase)` : "not set";
653
+ return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="password" autocomplete="off" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" placeholder="${escapeHtml(ph)}" />${savedSpan(field)}</dd></div>`;
591
654
  }
592
655
  function textareaRow(label, field, value, url) {
593
656
  return `<div class="detail wide"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><textarea id="detail-${field}" data-detail-field="${field}" data-details-url="${url}">${escapeHtml(value ?? "")}</textarea>${savedSpan(field)}</dd></div>`;
@@ -621,6 +684,15 @@ function siteDetailsSection(site) {
621
684
  // Raw JSON, and shown as the raw cell rather than the parsed object: what the
622
685
  // operator edits has to be the thing `parseNotifyRouting` will read back.
623
686
  textareaRow("Notify routing (JSON)", "notifyRouting", site.notifyRoutingRaw, url),
687
+ checkboxRow("Require Turnstile", "requireTurnstile", site.requireTurnstile, url),
688
+ secretRow("Mailchimp API key", "mailchimpApiKey", site.mailchimpApiKey !== null, url),
689
+ multiSelectRow(
690
+ "Accepted watch conditions",
691
+ "acceptedWatchConditions",
692
+ WATCH_CONDITION_OPTIONS,
693
+ site.acceptedWatchConditions,
694
+ url
695
+ ),
624
696
  textareaRow("Copy \u2014 Intro", "copyIntro", site.copyIntro, url),
625
697
  textareaRow("Copy \u2014 Contact", "copyContact", site.copyContact, url),
626
698
  textareaRow("Copy \u2014 Footer", "copyFooter", site.copyFooter, url),
@@ -847,9 +919,18 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
847
919
  ${spamScreenSection(spamTotals, submissions, now)}
848
920
  ${submissionsSection(submissions, site)}
849
921
  <script>
922
+ // The SAME pending report is rendered twice \u2014 once in the pending list, once
923
+ // in the reports history \u2014 so one report id owns two Approve buttons. Every
924
+ // state change has to reach both, or the two places you look disagree: one
925
+ // says "Approved", the other still says "Approve" and (with the gate clear)
926
+ // is still enabled.
927
+ function approveButtonsFor(id) {
928
+ return Array.from(document.querySelectorAll('button.approve[data-report-id="' + id + '"]'));
929
+ }
850
930
  document.querySelectorAll("button.approve").forEach((b) => {
851
931
  b.addEventListener("click", async () => {
852
- b.disabled = true;
932
+ const twins = approveButtonsFor(b.dataset.reportId);
933
+ twins.forEach((t) => { t.disabled = true; });
853
934
  // is-loading swaps the label for a spinner; aria-busy is the same news for a
854
935
  // screen reader, which cannot see it. Both are cleared in the finally below so
855
936
  // no exit path can strand the button spinning forever.
@@ -858,30 +939,41 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
858
939
  try {
859
940
  const res = await fetch(b.dataset.approveUrl, { method: "POST" });
860
941
  if (res.ok) {
861
- b.textContent = "Approved";
862
- // Terminal success: darker green, and it stays disabled (approving twice is
863
- // a no-op server-side, but the button should not invite the second click).
864
- b.classList.add("is-approved");
942
+ // Terminal success: darker green, and they stay disabled (approving twice
943
+ // is a no-op server-side, but the button should not invite the second
944
+ // click). Applied to every twin, not just the one that was clicked.
945
+ twins.forEach((t) => {
946
+ t.textContent = "Approved";
947
+ t.classList.add("is-approved");
948
+ });
865
949
  } else {
866
950
  // A 409 carries { reason, blockers } \u2014 surface WHY instead of a bare
867
951
  // "Failed" next to a possibly-stale green chip. textContent/title
868
952
  // assignment only (no innerHTML), so server strings stay inert.
869
953
  const data = await res.json().catch(() => null);
870
- b.textContent = data && data.reason === "send-blocked" ? "Blocked" : "Failed";
954
+ const label = data && data.reason === "send-blocked" ? "Blocked" : "Failed";
871
955
  // Double-backslash, NOT single: this whole block lives inside the renderer's
872
956
  // template literal, so a single-backslash escape is consumed HERE at build
873
957
  // time and emits a real newline into the served HTML \u2014 an unterminated string
874
958
  // literal that kills the parse of the entire script element, taking every
875
959
  // handler on the page with it (exactly how approve/override/renovate went
876
960
  // dead). Note this comment must also avoid backticks for the same reason.
877
- if (data && Array.isArray(data.blockers)) b.title = data.blockers.join("\\n");
878
- b.disabled = false;
961
+ const title = data && Array.isArray(data.blockers) ? data.blockers.join("\\n") : null;
962
+ // Re-enable EVERY twin, not just the clicked one \u2014 leaving the other
963
+ // disabled would strand it reading "Approve" and unusable.
964
+ twins.forEach((t) => {
965
+ t.textContent = label;
966
+ if (title !== null) t.title = title;
967
+ t.disabled = false;
968
+ });
879
969
  }
880
970
  } catch {
881
971
  // Network rejection (offline, DNS, abort): mirror the !res.ok path so
882
- // the button doesn't sit permanently disabled reading "Approve".
883
- b.textContent = "Failed";
884
- b.disabled = false;
972
+ // the buttons don't sit permanently disabled reading "Approve".
973
+ twins.forEach((t) => {
974
+ t.textContent = "Failed";
975
+ t.disabled = false;
976
+ });
885
977
  } finally {
886
978
  b.classList.remove("is-loading");
887
979
  b.removeAttribute("aria-busy");
@@ -894,7 +986,12 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
894
986
  // refused client-side AND server-side).
895
987
  document.querySelectorAll("button.override-toggle").forEach((b) => {
896
988
  b.addEventListener("click", () => {
897
- const form = b.nextElementSibling;
989
+ // Scoped lookup, not nextElementSibling: adjacency is a markup accident,
990
+ // and anything inserted between the toggle and its form would kill "Send
991
+ // anyway\u2026" silently. The submit handler six lines down already uses
992
+ // closest(); this is the same contract from the other end.
993
+ const wrap = b.closest(".override");
994
+ const form = wrap ? wrap.querySelector(".override-form") : null;
898
995
  if (form) form.hidden = !form.hidden;
899
996
  });
900
997
  });
@@ -919,13 +1016,14 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
919
1016
  if (res.ok) {
920
1017
  if (status) status.textContent = "Overridden \u2713";
921
1018
  if (input) input.disabled = true;
922
- // Reflect the send-anyway state on the row's own Approve button too \u2014
923
- // it stays server-gated disabled, but the label should stop implying
924
- // "waiting on you" once an override has gone through.
925
- const approveBtn = document.querySelector(
926
- 'button.approve[data-report-id="' + b.dataset.reportId + '"]',
927
- );
928
- if (approveBtn) approveBtn.textContent = "Overridden";
1019
+ // Reflect the send-anyway state on the row's own Approve buttons too \u2014
1020
+ // they stay server-gated disabled, but the label should stop implying
1021
+ // "waiting on you" once an override has gone through. ALL of them: the
1022
+ // same report is rendered in both the pending list and the history, so
1023
+ // a singular querySelector updated the first and left the second lying.
1024
+ approveButtonsFor(b.dataset.reportId).forEach((t) => {
1025
+ t.textContent = "Overridden";
1026
+ });
929
1027
  } else {
930
1028
  // A 409 carries { reason, blockers } \u2014 surface WHY (textContent only,
931
1029
  // never innerHTML, so server strings stay inert).
@@ -963,26 +1061,66 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
963
1061
  });
964
1062
  // Site-details editor: save on change (selects) / blur (inputs+textareas, only
965
1063
  // when the value actually changed). The per-field span shows \u2713 / \u2717.
966
- function saveDetail(el) {
967
- const span = document.querySelector('.detail-saved[data-for="' + el.dataset.detailField + '"]');
968
- fetch(el.dataset.detailsUrl, {
969
- method: "POST",
970
- headers: { "content-type": "application/json" },
971
- body: JSON.stringify({ field: el.dataset.detailField, value: el.value }),
972
- })
973
- .then((r) => {
974
- if (span) span.textContent = r.ok ? " \u2713" : " \u2717";
975
- })
976
- .catch(() => {
977
- if (span) span.textContent = " \u2717";
978
- });
979
- }
1064
+ ${DETAIL_VALUE_FN}
1065
+ ${SAVE_DETAIL_FN}
980
1066
  document.querySelectorAll("select[data-detail-field]").forEach((s) => {
981
- s.addEventListener("change", () => saveDetail(s));
1067
+ s.addEventListener("change", () => saveDetail(s, document));
1068
+ });
1069
+ // Checkboxes save on CHANGE. They cannot use the blur path below: that path
1070
+ // only fires when \`value !== defaultValue\`, and for a checkbox both are the
1071
+ // "on" content attribute, so it would never save at all.
1072
+ document.querySelectorAll("input[type=checkbox][data-detail-field]").forEach((c) => {
1073
+ c.addEventListener("change", () => saveDetail(c, document));
982
1074
  });
983
- document.querySelectorAll("input[data-detail-field], textarea[data-detail-field]").forEach((i) => {
1075
+ document.querySelectorAll("input:not([type=checkbox])[data-detail-field], textarea[data-detail-field]").forEach((i) => {
1076
+ // A save needs BOTH a real edit gesture and a changed value. \`value !==
1077
+ // defaultValue\` alone is not evidence of an edit: the browser rewrites
1078
+ // \`.value\` on its own when a control is handed a value it cannot represent
1079
+ // (an ISO datetime in a date input sanitizes to ""), and the blur that
1080
+ // follows an untouched tab-through then posts that empty string as if the
1081
+ // operator had cleared the field. Requiring an \`input\` event first means
1082
+ // only a human keystroke or picker choice can arm the save.
1083
+ i.addEventListener("input", () => { i.dataset.edited = "1"; });
984
1084
  i.addEventListener("blur", () => {
985
- if (i.value !== i.defaultValue) saveDetail(i);
1085
+ if (i.dataset.edited === "1" && i.value !== i.defaultValue) saveDetail(i, document);
1086
+ });
1087
+ });
1088
+ // Refresh preview: dispatch the re-render workflow, then tell the operator to
1089
+ // reload once it lands. No polling \u2014 the run takes a minute or two and a
1090
+ // spinner that long reads as a hang.
1091
+ document.querySelectorAll("button.rerender").forEach((b) => {
1092
+ b.addEventListener("click", async () => {
1093
+ b.disabled = true;
1094
+ b.textContent = "Refreshing\u2026";
1095
+ try {
1096
+ const res = await fetch(b.dataset.rerenderUrl, { method: "POST" });
1097
+ b.textContent = res.ok ? "Queued \u2713 reload in ~2 min" : "Failed";
1098
+ if (!res.ok) b.disabled = false;
1099
+ } catch {
1100
+ b.textContent = "Failed";
1101
+ b.disabled = false;
1102
+ }
1103
+ });
1104
+ });
1105
+ // Report commentary: save on blur, only when it actually changed. Same
1106
+ // shape as the site-details editor, but keyed on the REPORT id rather than a
1107
+ // field name, so it posts { text } to the report's own endpoint.
1108
+ document.querySelectorAll("textarea[data-commentary-for]").forEach((t) => {
1109
+ t.addEventListener("blur", () => {
1110
+ if (t.value === t.defaultValue) return;
1111
+ const span = document.querySelector('.commentary-saved[data-for="' + t.dataset.commentaryFor + '"]');
1112
+ fetch(t.dataset.commentaryUrl, {
1113
+ method: "POST",
1114
+ headers: { "content-type": "application/json" },
1115
+ body: JSON.stringify({ text: t.value }),
1116
+ })
1117
+ .then((r) => {
1118
+ if (span) span.textContent = r.ok ? " \u2713" : " \u2717";
1119
+ if (r.ok) t.defaultValue = t.value;
1120
+ })
1121
+ .catch(() => {
1122
+ if (span) span.textContent = " \u2717";
1123
+ });
986
1124
  });
987
1125
  });
988
1126
  ${SUBMISSION_STATUS_SCRIPT}
@@ -1537,41 +1675,38 @@ var AUDIT_SCRIPT = `<script>
1537
1675
  function rfStop(){ try { localStorage.removeItem(RF_KEY); } catch(e){} }
1538
1676
  // Per-workflow ETA hint (the lighthouse fleet sweep runs ~48 min; security ~1-2 min).
1539
1677
  function rfEta(label){ return label === 'lighthouse' ? '~48m' : label === 'security' ? '~2m' : ''; }
1540
- // Map a raw GitHub step name to a coarse human phase. Order matters (the audit step
1541
- // name also contains 'browser'; 'playwright install' also contains 'install').
1542
- function rfPhase(step){
1543
- if (!step) return '';
1544
- var s = step.toLowerCase();
1545
- if (s.indexOf('audit') !== -1 || s.indexOf('lighthouse') !== -1) return 'auditing the fleet\u2026';
1546
- if (s.indexOf('build') !== -1) return 'building\u2026';
1547
- if (s.indexOf('playwright') !== -1 || s.indexOf('browser') !== -1) return 'installing browsers\u2026';
1548
- if (s.indexOf('install') !== -1 || s.indexOf('depend') !== -1) return 'installing dependencies\u2026';
1549
- if (s.indexOf('set up') !== -1 || s.indexOf('checkout') !== -1) return 'setting up\u2026';
1550
- return step;
1678
+ // Escape before interpolating. The values here are server-side enums and a
1679
+ // GitHub html_url today, but this builds HTML from a REMOTE API response, and
1680
+ // the house rule everywhere else on these pages is that no server string ever
1681
+ // reaches the browser as markup. Cheaper to escape than to keep re-deciding.
1682
+ function rfEsc(v){
1683
+ return String(v == null ? '' : v)
1684
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
1685
+ .replace(/"/g,'&quot;').replace(/'/g,'&#39;');
1686
+ }
1687
+ // A run link must be a GitHub run URL. Anything else is dropped rather than
1688
+ // rendered, so a compromised or unexpected API response cannot place an
1689
+ // arbitrary href (javascript:, data:, an attacker's host) in the panel.
1690
+ function rfRunUrl(u){
1691
+ return typeof u === 'string' && u.indexOf('https://github.com/') === 0 ? rfEsc(u) : '';
1551
1692
  }
1552
- // Safe to build raw HTML: workflow/state/step are server-fixed (enums + GitHub step
1553
- // names) and url is GitHub's own html_url for our central repo \u2014 none are
1554
- // user-supplied. Don't interpolate untrusted fields here without escaping.
1555
1693
  function rfRender(status, startedAt){
1556
1694
  var failed = function(s){ return s === 'failure' || s === 'cancelled' || s === 'timed_out'; };
1557
1695
  var mins = Math.floor((Date.now() - startedAt) / 60000);
1558
1696
  var elapsed = mins < 1 ? '<1m' : mins + 'm';
1559
1697
  return status.perWorkflow.map(function(w){
1560
- var label = w.workflow.replace('.yml','').replace('fleet-','');
1698
+ var label = String(w.workflow || '').replace('.yml','').replace('fleet-','');
1561
1699
  var done = w.state === 'success';
1562
1700
  var isFailed = failed(w.state);
1563
1701
  var icon = done ? '\u2713' : isFailed ? '\u2717' : '<span class="rf-spin"></span>';
1564
- var line = icon + ' ' + label + ' \u2014 ' + w.state.replace('_',' ');
1702
+ var line = icon + ' ' + rfEsc(label) + ' \u2014 ' + rfEsc(String(w.state || '').replace('_',' '));
1703
+ var url = rfRunUrl(w.url);
1565
1704
  if (!done && !isFailed){
1566
- var phase = rfPhase(w.step);
1567
1705
  var eta = rfEta(label);
1568
- var detail = [];
1569
- if (phase) detail.push(phase);
1570
- detail.push(elapsed + (eta ? ' / ' + eta : ''));
1571
- var link = w.url ? ' \xB7 <a href="'+w.url+'" target="_blank" rel="noopener">view run \u2197</a>' : '';
1572
- line += '<div class="rf-sub">' + detail.join(' \xB7 ') + link + '</div>';
1573
- } else if (isFailed && w.url){
1574
- line += ' <a href="'+w.url+'" target="_blank" rel="noopener">run</a>';
1706
+ var link = url ? ' \xB7 <a href="'+url+'" target="_blank" rel="noopener">view run \u2197</a>' : '';
1707
+ line += '<div class="rf-sub">' + elapsed + (eta ? ' / ' + eta : '') + link + '</div>';
1708
+ } else if (isFailed && url){
1709
+ line += ' <a href="'+url+'" target="_blank" rel="noopener">run</a>';
1575
1710
  }
1576
1711
  return '<div class="rf-row">' + line + '</div>';
1577
1712
  }).join('');
@@ -1700,6 +1835,8 @@ var SESSION_TTL_SECONDS = 30 * 24 * 60 * 60;
1700
1835
 
1701
1836
  // src/dashboard/prospect-audit-trigger.ts
1702
1837
  var PROSPECT_AUDIT_DUPLICATE_WINDOW_MS = 10 * 60 * 1e3;
1838
+ var PROSPECT_AUDIT_DAILY_CAP = 25;
1839
+ var DAILY_CAP_LOOKBACK = PROSPECT_AUDIT_DAILY_CAP * 2;
1703
1840
  export {
1704
1841
  ALL_AUDIT_NAMES,
1705
1842
  ALL_RECIPE_NAMES,