@reddoorla/maintenance 0.86.0 → 0.87.0

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 (167) hide show
  1. package/dist/airtable-ZBUPQJSC.js +9 -0
  2. package/dist/{announce-MXTUQ6C7.js → announce-I2Z4JN3I.js} +49 -28
  3. package/dist/announce-I2Z4JN3I.js.map +1 -0
  4. package/dist/attachments-NLZIJ5NU.js +9 -0
  5. package/dist/{bump-deps-EC74CPLS.js → bump-deps-CGVELRLK.js} +2 -2
  6. package/dist/{chunk-2ZPT7GLV.js → chunk-3JQ2FO3D.js} +3 -3
  7. package/dist/chunk-3JQ2FO3D.js.map +1 -0
  8. package/dist/{chunk-KTH5ZODA.js → chunk-7BZUOZPU.js} +2 -2
  9. package/dist/{chunk-LDWZSLSU.js → chunk-7NYN2P6I.js} +7 -4
  10. package/dist/{chunk-LDWZSLSU.js.map → chunk-7NYN2P6I.js.map} +1 -1
  11. package/dist/{chunk-3VQWSE3F.js → chunk-7UAYMAKR.js} +4 -4
  12. package/dist/{chunk-BYK66EWO.js → chunk-BBUSGEES.js} +52 -46
  13. package/dist/chunk-BBUSGEES.js.map +1 -0
  14. package/dist/{chunk-SLUQ7R6I.js → chunk-C6CLRKPV.js} +5 -5
  15. package/dist/{chunk-P2FE5AI6.js → chunk-CROKUHJB.js} +2 -2
  16. package/dist/{chunk-W7Z3BGCI.js → chunk-DEWJMMG3.js} +2 -2
  17. package/dist/{chunk-ACBRLLFT.js → chunk-F3JWOPTG.js} +7 -3
  18. package/dist/chunk-F3JWOPTG.js.map +1 -0
  19. package/dist/{chunk-TERE5SDF.js → chunk-HEWSRXYV.js} +4 -4
  20. package/dist/chunk-HHMMUR4P.js +335 -0
  21. package/dist/chunk-HHMMUR4P.js.map +1 -0
  22. package/dist/{chunk-X3B723J4.js → chunk-HSI6Q7MI.js} +38 -9
  23. package/dist/chunk-HSI6Q7MI.js.map +1 -0
  24. package/dist/{chunk-Z7AXYKQB.js → chunk-IY6256GK.js} +1 -1
  25. package/dist/{chunk-CQR4LIM7.js → chunk-MT2JINSS.js} +5 -1
  26. package/dist/{chunk-CQR4LIM7.js.map → chunk-MT2JINSS.js.map} +1 -1
  27. package/dist/{chunk-F5KM4LEQ.js → chunk-NCMZMLY7.js} +3 -3
  28. package/dist/{chunk-2NDRHXMR.js → chunk-NDWYJLRF.js} +17 -140
  29. package/dist/chunk-NDWYJLRF.js.map +1 -0
  30. package/dist/{chunk-PPE5KZJK.js → chunk-NXNPDCF3.js} +3 -3
  31. package/dist/{chunk-ITIOFN7A.js → chunk-P6AORG32.js} +29 -10
  32. package/dist/chunk-P6AORG32.js.map +1 -0
  33. package/dist/{chunk-N45M3FDK.js → chunk-SDDPUAWH.js} +4 -4
  34. package/dist/{chunk-ZKXSX4LO.js → chunk-TD2NNXLZ.js} +9 -9
  35. package/dist/{chunk-ILUNZHYV.js → chunk-YFAXOD7Z.js} +10 -6
  36. package/dist/chunk-YFAXOD7Z.js.map +1 -0
  37. package/dist/{chunk-2EVEFOZY.js → chunk-ZBDZTJUO.js} +2 -2
  38. package/dist/chunk-ZGBEIWSC.js +146 -0
  39. package/dist/chunk-ZGBEIWSC.js.map +1 -0
  40. package/dist/cli/bin.js +32 -27
  41. package/dist/cli/bin.js.map +1 -1
  42. package/dist/cli/commands/audit.js +15 -11
  43. package/dist/cli/commands/audit.js.map +1 -1
  44. package/dist/{convert-to-pnpm-6L6P74WR.js → convert-to-pnpm-QFJOEP5V.js} +2 -2
  45. package/dist/{db-B35OGV3G.js → db-UXNIZ2RG.js} +6 -6
  46. package/dist/{digest-BL6UJYKH.js → digest-FW6QMZZJ.js} +17 -16
  47. package/dist/{digest-BL6UJYKH.js.map → digest-FW6QMZZJ.js.map} +1 -1
  48. package/dist/{email-VTTTKKL5.js → email-KJXASVH5.js} +3 -3
  49. package/dist/{ensure-site-DFTB7MN3.js → ensure-site-4DCEBAPM.js} +19 -11
  50. package/dist/ensure-site-4DCEBAPM.js.map +1 -0
  51. package/dist/fleet-state-7TJ2GYT2.js +39 -0
  52. package/dist/{forms-notify-target-75U6OU7L.js → forms-notify-target-2EAWCWBW.js} +7 -4
  53. package/dist/forms-notify-target-2EAWCWBW.js.map +1 -0
  54. package/dist/{github-signals-IJUPGCUX.js → github-signals-E2WPYTBK.js} +9 -9
  55. package/dist/{header-image-AWLLQA4L.js → header-image-6IC53W27.js} +7 -7
  56. package/dist/{header-images-335L2AKB.js → header-images-SQBCE5OD.js} +21 -1
  57. package/dist/header-images-SQBCE5OD.js.map +1 -0
  58. package/dist/{health-endpoint-WJC2UMYS.js → health-endpoint-RFXL2F4A.js} +2 -2
  59. package/dist/health-mirror-L5JBEJVA.js +41 -0
  60. package/dist/health-mirror-L5JBEJVA.js.map +1 -0
  61. package/dist/{import-airtable-HE43FB4H.js → import-airtable-OHVY344G.js} +7 -5
  62. package/dist/index.d.ts +149 -0
  63. package/dist/index.js +147 -50
  64. package/dist/index.js.map +1 -1
  65. package/dist/{init-NJEOEO7I.js → init-XRAQZYID.js} +9 -9
  66. package/dist/{launch-XNAX427U.js → launch-IX4Y5JJC.js} +55 -26
  67. package/dist/launch-IX4Y5JJC.js.map +1 -0
  68. package/dist/{lighthouse-airtable-LBVIYYP5.js → lighthouse-airtable-3GUWHETW.js} +3 -3
  69. package/dist/{onboard-GK4PHPNI.js → onboard-O4IAHJMO.js} +2 -2
  70. package/dist/{orchestrate-77UO4BYJ.js → orchestrate-YLWQ6KK2.js} +10 -9
  71. package/dist/{parity-MWS24A3H.js → parity-56KA2DGR.js} +4 -4
  72. package/dist/{pipeline-S6CVHDGB.js → pipeline-3AANZFCR.js} +2 -2
  73. package/dist/{preflight-6WBNU2GZ.js → preflight-TNORKGCX.js} +11 -10
  74. package/dist/{preflight-6WBNU2GZ.js.map → preflight-TNORKGCX.js.map} +1 -1
  75. package/dist/{prismic-ci-3RHOZUUT.js → prismic-ci-G2AK53VL.js} +5 -5
  76. package/dist/{prismic-models-QBGUT2Q3.js → prismic-models-O6Z3OK3A.js} +13 -8
  77. package/dist/prismic-models-O6Z3OK3A.js.map +1 -0
  78. package/dist/prospect/types.d.ts +267 -0
  79. package/dist/prospect/types.js +1 -0
  80. package/dist/{prospect-audit-IUNCJ2LV.js → prospect-audit-KSXUNQ5J.js} +4 -4
  81. package/dist/recipes/sync-configs.js +2 -2
  82. package/dist/{render-AQAVQDPZ.js → render-IFOGFB5T.js} +3 -3
  83. package/dist/render-IFOGFB5T.js.map +1 -0
  84. package/dist/render-from-row-BWSOIYDS.js +17 -0
  85. package/dist/render-from-row-BWSOIYDS.js.map +1 -0
  86. package/dist/{renovate-dispatch-QJY75ODR.js → renovate-dispatch-6GQWTRKX.js} +10 -10
  87. package/dist/{renovate-dispatch-QJY75ODR.js.map → renovate-dispatch-6GQWTRKX.js.map} +1 -1
  88. package/dist/{report-4EVNDHOZ.js → report-R3E3Y7HR.js} +63 -21
  89. package/dist/report-R3E3Y7HR.js.map +1 -0
  90. package/dist/report-mirror-4MNORYQ4.js +49 -0
  91. package/dist/report-mirror-4MNORYQ4.js.map +1 -0
  92. package/dist/rerender-3FM6DNRH.js +32 -0
  93. package/dist/rerender-3FM6DNRH.js.map +1 -0
  94. package/dist/{self-updating-6DWI2Z56.js → self-updating-UCIWESAX.js} +2 -2
  95. package/dist/{selftest-RC36MTKP.js → selftest-VGHWPN72.js} +22 -20
  96. package/dist/{selftest-RC36MTKP.js.map → selftest-VGHWPN72.js.map} +1 -1
  97. package/dist/site-mirror-NIREX5ME.js +52 -0
  98. package/dist/site-mirror-NIREX5ME.js.map +1 -0
  99. package/dist/{smoke-suite-VYY2LXM2.js → smoke-suite-U3UKD3NS.js} +2 -2
  100. package/dist/{submissions-Q7DRFOCD.js → submissions-IQEJX6FK.js} +2 -2
  101. package/dist/{svelte-codemods-25J2FO22.js → svelte-codemods-GOKXTBHW.js} +2 -2
  102. package/dist/{sync-34HNZZ56.js → sync-CTUVXMCK.js} +4 -4
  103. package/dist/{sync-configs-3SE6PSCO.js → sync-configs-SPTLFJCC.js} +4 -4
  104. package/dist/{upgrade-WBRQ62FX.js → upgrade-W6ZR3FJH.js} +2 -2
  105. package/dist/{webflow-IRKOIT7S.js → webflow-624FEBFD.js} +4 -4
  106. package/dist/{websites-SXWTP72S.js → websites-D6WYFAES.js} +2 -2
  107. package/dist/websites-D6WYFAES.js.map +1 -0
  108. package/dist/{write-audits-to-airtable-MBEILLPU.js → write-audits-to-airtable-CMKHGJNQ.js} +4 -4
  109. package/dist/write-audits-to-airtable-CMKHGJNQ.js.map +1 -0
  110. package/package.json +5 -1
  111. package/dist/airtable-G7WBQQMI.js +0 -9
  112. package/dist/announce-MXTUQ6C7.js.map +0 -1
  113. package/dist/chunk-2NDRHXMR.js.map +0 -1
  114. package/dist/chunk-2ZPT7GLV.js.map +0 -1
  115. package/dist/chunk-ACBRLLFT.js.map +0 -1
  116. package/dist/chunk-BYK66EWO.js.map +0 -1
  117. package/dist/chunk-ILUNZHYV.js.map +0 -1
  118. package/dist/chunk-ITIOFN7A.js.map +0 -1
  119. package/dist/chunk-X3B723J4.js.map +0 -1
  120. package/dist/ensure-site-DFTB7MN3.js.map +0 -1
  121. package/dist/forms-notify-target-75U6OU7L.js.map +0 -1
  122. package/dist/header-images-335L2AKB.js.map +0 -1
  123. package/dist/health-mirror-4B2L7T2Y.js +0 -75
  124. package/dist/health-mirror-4B2L7T2Y.js.map +0 -1
  125. package/dist/launch-XNAX427U.js.map +0 -1
  126. package/dist/prismic-models-QBGUT2Q3.js.map +0 -1
  127. package/dist/report-4EVNDHOZ.js.map +0 -1
  128. /package/dist/{airtable-G7WBQQMI.js.map → airtable-ZBUPQJSC.js.map} +0 -0
  129. /package/dist/{import-airtable-HE43FB4H.js.map → attachments-NLZIJ5NU.js.map} +0 -0
  130. /package/dist/{bump-deps-EC74CPLS.js.map → bump-deps-CGVELRLK.js.map} +0 -0
  131. /package/dist/{chunk-KTH5ZODA.js.map → chunk-7BZUOZPU.js.map} +0 -0
  132. /package/dist/{chunk-3VQWSE3F.js.map → chunk-7UAYMAKR.js.map} +0 -0
  133. /package/dist/{chunk-SLUQ7R6I.js.map → chunk-C6CLRKPV.js.map} +0 -0
  134. /package/dist/{chunk-P2FE5AI6.js.map → chunk-CROKUHJB.js.map} +0 -0
  135. /package/dist/{chunk-W7Z3BGCI.js.map → chunk-DEWJMMG3.js.map} +0 -0
  136. /package/dist/{chunk-TERE5SDF.js.map → chunk-HEWSRXYV.js.map} +0 -0
  137. /package/dist/{chunk-Z7AXYKQB.js.map → chunk-IY6256GK.js.map} +0 -0
  138. /package/dist/{chunk-F5KM4LEQ.js.map → chunk-NCMZMLY7.js.map} +0 -0
  139. /package/dist/{chunk-PPE5KZJK.js.map → chunk-NXNPDCF3.js.map} +0 -0
  140. /package/dist/{chunk-N45M3FDK.js.map → chunk-SDDPUAWH.js.map} +0 -0
  141. /package/dist/{chunk-ZKXSX4LO.js.map → chunk-TD2NNXLZ.js.map} +0 -0
  142. /package/dist/{chunk-2EVEFOZY.js.map → chunk-ZBDZTJUO.js.map} +0 -0
  143. /package/dist/{convert-to-pnpm-6L6P74WR.js.map → convert-to-pnpm-QFJOEP5V.js.map} +0 -0
  144. /package/dist/{db-B35OGV3G.js.map → db-UXNIZ2RG.js.map} +0 -0
  145. /package/dist/{email-VTTTKKL5.js.map → email-KJXASVH5.js.map} +0 -0
  146. /package/dist/{lighthouse-airtable-LBVIYYP5.js.map → fleet-state-7TJ2GYT2.js.map} +0 -0
  147. /package/dist/{github-signals-IJUPGCUX.js.map → github-signals-E2WPYTBK.js.map} +0 -0
  148. /package/dist/{header-image-AWLLQA4L.js.map → header-image-6IC53W27.js.map} +0 -0
  149. /package/dist/{health-endpoint-WJC2UMYS.js.map → health-endpoint-RFXL2F4A.js.map} +0 -0
  150. /package/dist/{orchestrate-77UO4BYJ.js.map → import-airtable-OHVY344G.js.map} +0 -0
  151. /package/dist/{init-NJEOEO7I.js.map → init-XRAQZYID.js.map} +0 -0
  152. /package/dist/{parity-MWS24A3H.js.map → lighthouse-airtable-3GUWHETW.js.map} +0 -0
  153. /package/dist/{onboard-GK4PHPNI.js.map → onboard-O4IAHJMO.js.map} +0 -0
  154. /package/dist/{pipeline-S6CVHDGB.js.map → orchestrate-YLWQ6KK2.js.map} +0 -0
  155. /package/dist/{render-AQAVQDPZ.js.map → parity-56KA2DGR.js.map} +0 -0
  156. /package/dist/{websites-SXWTP72S.js.map → pipeline-3AANZFCR.js.map} +0 -0
  157. /package/dist/{prismic-ci-3RHOZUUT.js.map → prismic-ci-G2AK53VL.js.map} +0 -0
  158. /package/dist/{write-audits-to-airtable-MBEILLPU.js.map → prospect/types.js.map} +0 -0
  159. /package/dist/{prospect-audit-IUNCJ2LV.js.map → prospect-audit-KSXUNQ5J.js.map} +0 -0
  160. /package/dist/{self-updating-6DWI2Z56.js.map → self-updating-UCIWESAX.js.map} +0 -0
  161. /package/dist/{smoke-suite-VYY2LXM2.js.map → smoke-suite-U3UKD3NS.js.map} +0 -0
  162. /package/dist/{submissions-Q7DRFOCD.js.map → submissions-IQEJX6FK.js.map} +0 -0
  163. /package/dist/{svelte-codemods-25J2FO22.js.map → svelte-codemods-GOKXTBHW.js.map} +0 -0
  164. /package/dist/{sync-34HNZZ56.js.map → sync-CTUVXMCK.js.map} +0 -0
  165. /package/dist/{sync-configs-3SE6PSCO.js.map → sync-configs-SPTLFJCC.js.map} +0 -0
  166. /package/dist/{upgrade-WBRQ62FX.js.map → upgrade-W6ZR3FJH.js.map} +0 -0
  167. /package/dist/{webflow-IRKOIT7S.js.map → webflow-624FEBFD.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,29 +1,19 @@
1
1
  import "./chunk-2SLED3NB.js";
2
- import "./chunk-F5KM4LEQ.js";
2
+ import "./chunk-NCMZMLY7.js";
3
3
  import {
4
4
  draftReportForSite
5
- } from "./chunk-BYK66EWO.js";
5
+ } from "./chunk-BBUSGEES.js";
6
6
  import {
7
7
  approveBlockers
8
- } from "./chunk-N45M3FDK.js";
8
+ } from "./chunk-SDDPUAWH.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";
23
- import {
24
- svelteCodemods
25
- } from "./chunk-IYSEC543.js";
26
- import "./chunk-J7SZQUCW.js";
16
+ } from "./chunk-TD2NNXLZ.js";
27
17
  import "./chunk-BQRFQSGY.js";
28
18
  import "./chunk-Y53WOQIJ.js";
29
19
  import {
@@ -31,53 +21,71 @@ import {
31
21
  selfCaretRange,
32
22
  selfPackageVersion
33
23
  } from "./chunk-7RJ2HMMJ.js";
34
- import "./chunk-ILUNZHYV.js";
24
+ import "./chunk-YFAXOD7Z.js";
35
25
  import {
36
26
  bumpDeps
37
27
  } from "./chunk-2NJQLSEU.js";
38
28
  import "./chunk-R53VL3RI.js";
39
29
  import {
40
- fromAirtableBase
41
- } from "./chunk-SLUQ7R6I.js";
30
+ upgradeSvelte4to5
31
+ } from "./chunk-Z3KYFUN6.js";
32
+ import {
33
+ convertToPnpm
34
+ } from "./chunk-XIDUYLSR.js";
35
+ import {
36
+ svelteCodemods
37
+ } from "./chunk-IYSEC543.js";
38
+ import "./chunk-J7SZQUCW.js";
42
39
  import {
43
40
  parseAddresses,
44
41
  sendApprovedReports,
45
42
  withGlobalCc
46
- } from "./chunk-2NDRHXMR.js";
47
- import "./chunk-GECESMVS.js";
43
+ } from "./chunk-NDWYJLRF.js";
44
+ import "./chunk-NYUYNYQL.js";
48
45
  import {
49
- domainFromUrl,
50
- generateHeaderImage,
51
- loadHeadline,
52
- loadPlate
53
- } from "./chunk-5UINJNAJ.js";
46
+ isPendingApproval
47
+ } from "./chunk-F3JWOPTG.js";
48
+ import {
49
+ checklistFor,
50
+ gatingFields,
51
+ isHealthGateClear
52
+ } from "./chunk-VAD43MOR.js";
53
+ import {
54
+ fromAirtableBase
55
+ } from "./chunk-C6CLRKPV.js";
56
+ import "./chunk-IY6256GK.js";
57
+ import "./chunk-HN6I2XWI.js";
58
+ import "./chunk-ZGBEIWSC.js";
59
+ import "./chunk-GECESMVS.js";
54
60
  import {
55
61
  BLURRED_CID,
56
62
  CHECK_CID,
57
63
  loadBundledImages,
58
- renderReportHtml
59
- } from "./chunk-CQR4LIM7.js";
60
- import "./chunk-Z7AXYKQB.js";
64
+ renderReportHtml,
65
+ rendersCommentary
66
+ } from "./chunk-MT2JINSS.js";
61
67
  import {
62
68
  escapeHtml,
63
69
  safeUrl
64
70
  } from "./chunk-DJZWYIPP.js";
65
71
  import {
66
- isPendingApproval
67
- } from "./chunk-ACBRLLFT.js";
72
+ domainFromUrl,
73
+ generateHeaderImage,
74
+ loadHeadline,
75
+ loadPlate
76
+ } from "./chunk-5UINJNAJ.js";
68
77
  import {
69
- checklistFor,
70
- gatingFields,
71
- isHealthGateClear
72
- } from "./chunk-VAD43MOR.js";
73
- import "./chunk-HN6I2XWI.js";
78
+ CANONICAL_STATUSES,
79
+ SEVERITY_RANK,
80
+ siteSlug,
81
+ toAirtableStatus
82
+ } from "./chunk-7NYN2P6I.js";
74
83
  import "./chunk-WEKL2HYJ.js";
75
- import "./chunk-NYUYNYQL.js";
76
84
  import {
77
85
  syncConfigs
78
- } from "./chunk-3VQWSE3F.js";
79
- import "./chunk-3G25KIWW.js";
86
+ } from "./chunk-7UAYMAKR.js";
80
87
  import "./chunk-73UFRZKY.js";
88
+ import "./chunk-3G25KIWW.js";
81
89
  import "./chunk-OTGB6YQO.js";
82
90
  import {
83
91
  ALL_AUDIT_NAMES,
@@ -87,7 +95,7 @@ import {
87
95
  runAudits,
88
96
  runAuditsAcross,
89
97
  securityAudit
90
- } from "./chunk-2ZPT7GLV.js";
98
+ } from "./chunk-3JQ2FO3D.js";
91
99
  import "./chunk-LKGVSM2O.js";
92
100
  import "./chunk-GASBX52O.js";
93
101
  import "./chunk-XHHMM5HX.js";
@@ -111,12 +119,6 @@ import "./chunk-UDRHAQT7.js";
111
119
  import "./chunk-PTDMVGKL.js";
112
120
  import "./chunk-LBYOLBW7.js";
113
121
  import "./chunk-XXTZBPUY.js";
114
- import {
115
- CANONICAL_STATUSES,
116
- SEVERITY_RANK,
117
- siteSlug,
118
- toAirtableStatus
119
- } from "./chunk-LDWZSLSU.js";
120
122
  import "./chunk-6VAL7XJT.js";
121
123
  import "./chunk-NMAWXBJA.js";
122
124
  import "./chunk-C6B4RHXR.js";
@@ -314,6 +316,14 @@ var SUBMISSION_STATUS_SCRIPT = `document.querySelectorAll("button.subm-status").
314
316
  // src/dashboard/site-details.ts
315
317
  var SITE_STATUS_OPTIONS = CANONICAL_STATUSES.map(toAirtableStatus);
316
318
  var FREQ_OPTIONS = ["None", "Monthly", "Quarterly", "Yearly"];
319
+ var WATCH_CONDITION_OPTIONS = [
320
+ "Performance",
321
+ "Accessibility",
322
+ "Best Practices",
323
+ "SEO",
324
+ "stale repo",
325
+ "no custom domain"
326
+ ];
317
327
 
318
328
  // src/dashboard/render.ts
319
329
  var DASH = "\u2014";
@@ -465,14 +475,27 @@ function recipientsLine(site) {
465
475
  const ccPart = cc.length > 0 ? ` \xB7 CC ${cc.map(escapeHtml).join(", ")}` : "";
466
476
  return `<span class="recipients">To ${to.map(escapeHtml).join(", ")}${ccPart}</span>`;
467
477
  }
478
+ function rerenderButton(r) {
479
+ if (r.sentAt !== null) return "";
480
+ const url = `/api/reports/${encodeURIComponent(r.id)}/rerender`;
481
+ 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>`;
482
+ }
483
+ function reportPreviewUrl(reportId) {
484
+ return `/api/reports/${encodeURIComponent(reportId)}/preview`;
485
+ }
486
+ function commentaryEditor(r) {
487
+ if (!rendersCommentary(r.reportType)) return "";
488
+ const url = `/api/reports/${encodeURIComponent(r.id)}/commentary`;
489
+ 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>`;
490
+ }
468
491
  function pendingRow(r, site, now) {
469
492
  const type = escapeHtml(r.reportType);
470
493
  const period = r.period ? escapeHtml(r.period) : "\u2014";
471
494
  const findings = approveBlockers(site, r);
472
495
  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>`;
496
+ 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
497
  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>`;
498
+ 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
499
  }
477
500
  function pendingSection(reports, site, now) {
478
501
  const pending = reports.filter(isPendingApproval);
@@ -502,12 +525,13 @@ function reportRow(r, site) {
502
525
  const id = escapeHtml(r.reportId);
503
526
  const ga = gaUsersCell(r);
504
527
  const search = searchCell(r);
505
- const link = r.renderedHtmlAttachment ? `<a href="${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}">view</a>` : `<span class="muted">no attachment</span>`;
528
+ const link = r.renderedHtmlAttachment ? `<a href="${escapeHtml(reportPreviewUrl(r.id))}">view</a>` : `<span class="muted">no attachment</span>`;
506
529
  const action = isPendingApproval(r) ? approveButton(
507
530
  r,
508
531
  approveBlockers(site, r).some((f) => f.level === "fail")
509
532
  ) : "";
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>`;
533
+ const commentary = r.sentAt === null && !isPendingApproval(r) ? `<tr class="commentary-row"><td colspan="7">${commentaryEditor(r)}</td></tr>` : "";
534
+ 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
535
  }
512
536
  var SUBMISSIONS_PER_SITE_CAP = 25;
513
537
  function submissionsSection(submissions, site) {
@@ -586,9 +610,28 @@ function selectRow(label, field, options, current, url) {
586
610
  function inputRow(label, field, value, url) {
587
611
  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
612
  }
613
+ var DETAIL_VALUE_FN = `function detailValue(el) {
614
+ if (el.type === "checkbox") return el.checked ? "true" : "false";
615
+ if (el.multiple) return Array.from(el.selectedOptions).map((o) => o.value).join(",");
616
+ return el.value;
617
+ }`;
589
618
  function dateRow(label, field, value, url) {
590
619
  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>`;
591
620
  }
621
+ function checkboxRow(label, field, checked, url) {
622
+ 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>`;
623
+ }
624
+ function multiSelectRow(label, field, options, selected, url) {
625
+ const sel = new Set(selected);
626
+ const opts = options.map(
627
+ (o) => `<option value="${escapeHtml(o)}"${sel.has(o) ? " selected" : ""}>${escapeHtml(o)}</option>`
628
+ ).join("");
629
+ 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>`;
630
+ }
631
+ function secretRow(label, field, isSet, url) {
632
+ const ph = isSet ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (set \u2014 type to replace)" : "not set";
633
+ 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>`;
634
+ }
592
635
  function textareaRow(label, field, value, url) {
593
636
  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>`;
594
637
  }
@@ -621,6 +664,15 @@ function siteDetailsSection(site) {
621
664
  // Raw JSON, and shown as the raw cell rather than the parsed object: what the
622
665
  // operator edits has to be the thing `parseNotifyRouting` will read back.
623
666
  textareaRow("Notify routing (JSON)", "notifyRouting", site.notifyRoutingRaw, url),
667
+ checkboxRow("Require Turnstile", "requireTurnstile", site.requireTurnstile, url),
668
+ secretRow("Mailchimp API key", "mailchimpApiKey", site.mailchimpApiKey !== null, url),
669
+ multiSelectRow(
670
+ "Accepted watch conditions",
671
+ "acceptedWatchConditions",
672
+ WATCH_CONDITION_OPTIONS,
673
+ site.acceptedWatchConditions,
674
+ url
675
+ ),
624
676
  textareaRow("Copy \u2014 Intro", "copyIntro", site.copyIntro, url),
625
677
  textareaRow("Copy \u2014 Contact", "copyContact", site.copyContact, url),
626
678
  textareaRow("Copy \u2014 Footer", "copyFooter", site.copyFooter, url),
@@ -963,12 +1015,13 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
963
1015
  });
964
1016
  // Site-details editor: save on change (selects) / blur (inputs+textareas, only
965
1017
  // when the value actually changed). The per-field span shows \u2713 / \u2717.
1018
+ ${DETAIL_VALUE_FN}
966
1019
  function saveDetail(el) {
967
1020
  const span = document.querySelector('.detail-saved[data-for="' + el.dataset.detailField + '"]');
968
1021
  fetch(el.dataset.detailsUrl, {
969
1022
  method: "POST",
970
1023
  headers: { "content-type": "application/json" },
971
- body: JSON.stringify({ field: el.dataset.detailField, value: el.value }),
1024
+ body: JSON.stringify({ field: el.dataset.detailField, value: detailValue(el) }),
972
1025
  })
973
1026
  .then((r) => {
974
1027
  if (span) span.textContent = r.ok ? " \u2713" : " \u2717";
@@ -980,11 +1033,55 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
980
1033
  document.querySelectorAll("select[data-detail-field]").forEach((s) => {
981
1034
  s.addEventListener("change", () => saveDetail(s));
982
1035
  });
983
- document.querySelectorAll("input[data-detail-field], textarea[data-detail-field]").forEach((i) => {
1036
+ // Checkboxes save on CHANGE. They cannot use the blur path below: that path
1037
+ // only fires when \`value !== defaultValue\`, and for a checkbox both are the
1038
+ // "on" content attribute, so it would never save at all.
1039
+ document.querySelectorAll("input[type=checkbox][data-detail-field]").forEach((c) => {
1040
+ c.addEventListener("change", () => saveDetail(c));
1041
+ });
1042
+ document.querySelectorAll("input:not([type=checkbox])[data-detail-field], textarea[data-detail-field]").forEach((i) => {
984
1043
  i.addEventListener("blur", () => {
985
1044
  if (i.value !== i.defaultValue) saveDetail(i);
986
1045
  });
987
1046
  });
1047
+ // Refresh preview: dispatch the re-render workflow, then tell the operator to
1048
+ // reload once it lands. No polling \u2014 the run takes a minute or two and a
1049
+ // spinner that long reads as a hang.
1050
+ document.querySelectorAll("button.rerender").forEach((b) => {
1051
+ b.addEventListener("click", async () => {
1052
+ b.disabled = true;
1053
+ b.textContent = "Refreshing\u2026";
1054
+ try {
1055
+ const res = await fetch(b.dataset.rerenderUrl, { method: "POST" });
1056
+ b.textContent = res.ok ? "Queued \u2713 reload in ~2 min" : "Failed";
1057
+ if (!res.ok) b.disabled = false;
1058
+ } catch {
1059
+ b.textContent = "Failed";
1060
+ b.disabled = false;
1061
+ }
1062
+ });
1063
+ });
1064
+ // Report commentary: save on blur, only when it actually changed. Same
1065
+ // shape as the site-details editor, but keyed on the REPORT id rather than a
1066
+ // field name, so it posts { text } to the report's own endpoint.
1067
+ document.querySelectorAll("textarea[data-commentary-for]").forEach((t) => {
1068
+ t.addEventListener("blur", () => {
1069
+ if (t.value === t.defaultValue) return;
1070
+ const span = document.querySelector('.commentary-saved[data-for="' + t.dataset.commentaryFor + '"]');
1071
+ fetch(t.dataset.commentaryUrl, {
1072
+ method: "POST",
1073
+ headers: { "content-type": "application/json" },
1074
+ body: JSON.stringify({ text: t.value }),
1075
+ })
1076
+ .then((r) => {
1077
+ if (span) span.textContent = r.ok ? " \u2713" : " \u2717";
1078
+ if (r.ok) t.defaultValue = t.value;
1079
+ })
1080
+ .catch(() => {
1081
+ if (span) span.textContent = " \u2717";
1082
+ });
1083
+ });
1084
+ });
988
1085
  ${SUBMISSION_STATUS_SCRIPT}
989
1086
  </script>
990
1087
  </body>