@reddoorla/maintenance 0.91.0 → 0.93.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 (106) hide show
  1. package/dist/{bump-deps-KGH5UYSF.js → bump-deps-GI7PHMOC.js} +6 -6
  2. package/dist/{chunk-A6T2R63Z.js → chunk-3HS6LPFX.js} +2 -2
  3. package/dist/{chunk-SJOXWJ5Y.js → chunk-4763KQ6O.js} +184 -9
  4. package/dist/chunk-4763KQ6O.js.map +1 -0
  5. package/dist/{chunk-UXJZU23T.js → chunk-4NUKMPOO.js} +9 -9
  6. package/dist/{chunk-BQRFQSGY.js → chunk-4R3YAQNV.js} +2 -2
  7. package/dist/{chunk-XIDUYLSR.js → chunk-7CJ3DVZW.js} +2 -2
  8. package/dist/{chunk-Z3KYFUN6.js → chunk-B6NUZ2ZX.js} +2 -2
  9. package/dist/{chunk-WVVT6SJK.js → chunk-BJQCIJOY.js} +169 -17
  10. package/dist/chunk-BJQCIJOY.js.map +1 -0
  11. package/dist/{chunk-Y53WOQIJ.js → chunk-CVJF2IC5.js} +2 -2
  12. package/dist/chunk-I7NHLVVI.js +89 -0
  13. package/dist/chunk-I7NHLVVI.js.map +1 -0
  14. package/dist/{chunk-QD427NIO.js → chunk-JB4Q3C3M.js} +7 -7
  15. package/dist/{chunk-IYSEC543.js → chunk-LS6QPANO.js} +2 -2
  16. package/dist/{chunk-7RJ2HMMJ.js → chunk-NTLSMJHW.js} +2 -2
  17. package/dist/{chunk-2NJQLSEU.js → chunk-PPIZHDJL.js} +2 -2
  18. package/dist/{chunk-JRHIFA5R.js → chunk-QF6IQQFP.js} +10 -4
  19. package/dist/chunk-QF6IQQFP.js.map +1 -0
  20. package/dist/{chunk-RQLXEMEE.js → chunk-T7ZUUOZ2.js} +2 -2
  21. package/dist/chunk-T7ZUUOZ2.js.map +1 -0
  22. package/dist/{chunk-TVL5MVUF.js → chunk-TCF6S7AD.js} +4 -4
  23. package/dist/{chunk-GSWTT2J6.js → chunk-WHWVYL7B.js} +4 -4
  24. package/dist/{chunk-3G25KIWW.js → chunk-ZTPPCHDB.js} +4 -4
  25. package/dist/cli/bin.js +17 -17
  26. package/dist/cli/commands/audit.js +10 -10
  27. package/dist/{convert-to-pnpm-NWB5FCQ2.js → convert-to-pnpm-ONFP3JKF.js} +6 -6
  28. package/dist/{db-IMMQSRAB.js → db-HSZ6D5DU.js} +3 -3
  29. package/dist/digest-EOA4JLTV.js +1 -1
  30. package/dist/digest-collectors-ZA3BTS3R.js +1 -1
  31. package/dist/{email-PLPDJUDH.js → email-I4BCETI4.js} +16 -65
  32. package/dist/email-I4BCETI4.js.map +1 -0
  33. package/dist/forms/index.d.ts +5 -1
  34. package/dist/forms/index.js +1 -1
  35. package/dist/forms/index.js.map +1 -1
  36. package/dist/forms/prismic.d.ts +92 -0
  37. package/dist/forms/prismic.js +105 -0
  38. package/dist/forms/prismic.js.map +1 -0
  39. package/dist/{forms-notify-target-YCZUAHTY.js → forms-notify-target-RMZPKT6S.js} +4 -3
  40. package/dist/{forms-notify-target-YCZUAHTY.js.map → forms-notify-target-RMZPKT6S.js.map} +1 -1
  41. package/dist/{health-endpoint-UVX6R5N5.js → health-endpoint-DHKJUPRM.js} +6 -6
  42. package/dist/index.js +30 -23
  43. package/dist/index.js.map +1 -1
  44. package/dist/{init-NXZ4NXTL.js → init-ZRKWYCKA.js} +15 -15
  45. package/dist/{launch-WPEQ3TY2.js → launch-6X5OXPF2.js} +11 -11
  46. package/dist/{notify-4HP4GZ2H.js → notify-IISTHNBR.js} +4 -3
  47. package/dist/{onboard-BKCYBAVW.js → onboard-6NHGCKGB.js} +6 -6
  48. package/dist/orchestrate-KMQIUP3H.js +1 -1
  49. package/dist/{pipeline-HQJYZ443.js → pipeline-AQBFG5WM.js} +5 -3
  50. package/dist/preflight-VPHQYWTY.js +1 -1
  51. package/dist/{prismic-ci-6YGRIOHT.js → prismic-ci-A2HI7NNJ.js} +8 -8
  52. package/dist/{prismic-models-FPDSUXDO.js → prismic-models-QTKIMFBC.js} +4 -4
  53. package/dist/prospect/types.d.ts +17 -0
  54. package/dist/prospect/types.js +1 -1
  55. package/dist/{prospect-audit-TFIXSTE5.js → prospect-audit-RKUVSTLN.js} +4 -4
  56. package/dist/recipes/sync-configs.js +3 -3
  57. package/dist/{chunk-H6BLTEFN.js → render-7G43BSK5.js} +3 -3
  58. package/dist/{chunk-H6BLTEFN.js.map → render-7G43BSK5.js.map} +1 -1
  59. package/dist/{replay-4S6ENYCH.js → replay-PCSQOGCZ.js} +3 -2
  60. package/dist/{replay-4S6ENYCH.js.map → replay-PCSQOGCZ.js.map} +1 -1
  61. package/dist/{self-updating-VQIW24RZ.js → self-updating-D5XOWS7V.js} +5 -5
  62. package/dist/selftest-IFGSJP4X.js +1 -1
  63. package/dist/{smoke-suite-4JFZZYVW.js → smoke-suite-GXC5JZPH.js} +6 -6
  64. package/dist/{svelte-codemods-QAPDZC6F.js → svelte-codemods-A3EARSRP.js} +6 -6
  65. package/dist/{sync-configs-PVRLZRKL.js → sync-configs-KSESOHFF.js} +6 -6
  66. package/dist/{upgrade-IR2OO3KP.js → upgrade-ZLNVEIEA.js} +6 -6
  67. package/dist/{webflow-624FEBFD.js → webflow-IRKOIT7S.js} +4 -4
  68. package/package.json +5 -1
  69. package/dist/chunk-JRHIFA5R.js.map +0 -1
  70. package/dist/chunk-RQLXEMEE.js.map +0 -1
  71. package/dist/chunk-SJOXWJ5Y.js.map +0 -1
  72. package/dist/chunk-WVVT6SJK.js.map +0 -1
  73. package/dist/email-PLPDJUDH.js.map +0 -1
  74. package/dist/render-3GYW2J34.js +0 -11
  75. package/dist/render-3GYW2J34.js.map +0 -1
  76. /package/dist/{bump-deps-KGH5UYSF.js.map → bump-deps-GI7PHMOC.js.map} +0 -0
  77. /package/dist/{chunk-A6T2R63Z.js.map → chunk-3HS6LPFX.js.map} +0 -0
  78. /package/dist/{chunk-UXJZU23T.js.map → chunk-4NUKMPOO.js.map} +0 -0
  79. /package/dist/{chunk-BQRFQSGY.js.map → chunk-4R3YAQNV.js.map} +0 -0
  80. /package/dist/{chunk-XIDUYLSR.js.map → chunk-7CJ3DVZW.js.map} +0 -0
  81. /package/dist/{chunk-Z3KYFUN6.js.map → chunk-B6NUZ2ZX.js.map} +0 -0
  82. /package/dist/{chunk-Y53WOQIJ.js.map → chunk-CVJF2IC5.js.map} +0 -0
  83. /package/dist/{chunk-QD427NIO.js.map → chunk-JB4Q3C3M.js.map} +0 -0
  84. /package/dist/{chunk-IYSEC543.js.map → chunk-LS6QPANO.js.map} +0 -0
  85. /package/dist/{chunk-7RJ2HMMJ.js.map → chunk-NTLSMJHW.js.map} +0 -0
  86. /package/dist/{chunk-2NJQLSEU.js.map → chunk-PPIZHDJL.js.map} +0 -0
  87. /package/dist/{chunk-TVL5MVUF.js.map → chunk-TCF6S7AD.js.map} +0 -0
  88. /package/dist/{chunk-GSWTT2J6.js.map → chunk-WHWVYL7B.js.map} +0 -0
  89. /package/dist/{chunk-3G25KIWW.js.map → chunk-ZTPPCHDB.js.map} +0 -0
  90. /package/dist/{convert-to-pnpm-NWB5FCQ2.js.map → convert-to-pnpm-ONFP3JKF.js.map} +0 -0
  91. /package/dist/{db-IMMQSRAB.js.map → db-HSZ6D5DU.js.map} +0 -0
  92. /package/dist/{health-endpoint-UVX6R5N5.js.map → health-endpoint-DHKJUPRM.js.map} +0 -0
  93. /package/dist/{init-NXZ4NXTL.js.map → init-ZRKWYCKA.js.map} +0 -0
  94. /package/dist/{launch-WPEQ3TY2.js.map → launch-6X5OXPF2.js.map} +0 -0
  95. /package/dist/{notify-4HP4GZ2H.js.map → notify-IISTHNBR.js.map} +0 -0
  96. /package/dist/{onboard-BKCYBAVW.js.map → onboard-6NHGCKGB.js.map} +0 -0
  97. /package/dist/{pipeline-HQJYZ443.js.map → pipeline-AQBFG5WM.js.map} +0 -0
  98. /package/dist/{prismic-ci-6YGRIOHT.js.map → prismic-ci-A2HI7NNJ.js.map} +0 -0
  99. /package/dist/{prismic-models-FPDSUXDO.js.map → prismic-models-QTKIMFBC.js.map} +0 -0
  100. /package/dist/{prospect-audit-TFIXSTE5.js.map → prospect-audit-RKUVSTLN.js.map} +0 -0
  101. /package/dist/{self-updating-VQIW24RZ.js.map → self-updating-D5XOWS7V.js.map} +0 -0
  102. /package/dist/{smoke-suite-4JFZZYVW.js.map → smoke-suite-GXC5JZPH.js.map} +0 -0
  103. /package/dist/{svelte-codemods-QAPDZC6F.js.map → svelte-codemods-A3EARSRP.js.map} +0 -0
  104. /package/dist/{sync-configs-PVRLZRKL.js.map → sync-configs-KSESOHFF.js.map} +0 -0
  105. /package/dist/{upgrade-IR2OO3KP.js.map → upgrade-ZLNVEIEA.js.map} +0 -0
  106. /package/dist/{webflow-624FEBFD.js.map → webflow-IRKOIT7S.js.map} +0 -0
@@ -2,40 +2,40 @@ import {
2
2
  appendSkipNotice,
3
3
  fleetWorkdir,
4
4
  prepareFleetSites
5
- } from "./chunk-TVL5MVUF.js";
5
+ } from "./chunk-TCF6S7AD.js";
6
6
  import {
7
7
  resolveSites
8
8
  } from "./chunk-Q47DU255.js";
9
9
  import {
10
10
  init
11
- } from "./chunk-UXJZU23T.js";
12
- import "./chunk-XIDUYLSR.js";
13
- import "./chunk-IYSEC543.js";
11
+ } from "./chunk-4NUKMPOO.js";
12
+ import "./chunk-7CJ3DVZW.js";
13
+ import "./chunk-LS6QPANO.js";
14
14
  import "./chunk-J7SZQUCW.js";
15
- import "./chunk-BQRFQSGY.js";
16
- import "./chunk-Y53WOQIJ.js";
17
- import "./chunk-7RJ2HMMJ.js";
15
+ import "./chunk-4R3YAQNV.js";
16
+ import "./chunk-CVJF2IC5.js";
18
17
  import "./chunk-R53VL3RI.js";
19
- import "./chunk-A6T2R63Z.js";
20
- import "./chunk-3G25KIWW.js";
18
+ import "./chunk-NTLSMJHW.js";
19
+ import "./chunk-3HS6LPFX.js";
20
+ import "./chunk-ZTPPCHDB.js";
21
21
  import "./chunk-MLT4QY5X.js";
22
22
  import "./chunk-OTGB6YQO.js";
23
- import "./chunk-QD427NIO.js";
23
+ import "./chunk-JB4Q3C3M.js";
24
24
  import "./chunk-LKGVSM2O.js";
25
25
  import "./chunk-GASBX52O.js";
26
26
  import "./chunk-XHHMM5HX.js";
27
27
  import "./chunk-Z3JXLULD.js";
28
+ import "./chunk-2NQUCS7O.js";
28
29
  import "./chunk-UDRHAQT7.js";
30
+ import "./chunk-PTDMVGKL.js";
31
+ import "./chunk-LBYOLBW7.js";
29
32
  import "./chunk-RARREDQE.js";
30
33
  import "./chunk-A3WDWNN5.js";
31
34
  import "./chunk-2OP4JKIQ.js";
32
- import "./chunk-XTK5VIQB.js";
33
- import "./chunk-2NQUCS7O.js";
34
- import "./chunk-PTDMVGKL.js";
35
- import "./chunk-LBYOLBW7.js";
36
35
  import {
37
36
  siteLabel
38
37
  } from "./chunk-XXTZBPUY.js";
38
+ import "./chunk-XTK5VIQB.js";
39
39
  import "./chunk-NMAWXBJA.js";
40
40
  import "./chunk-YLYXW5PH.js";
41
41
 
@@ -110,4 +110,4 @@ async function runInitCommand(site, opts) {
110
110
  export {
111
111
  runInitCommand
112
112
  };
113
- //# sourceMappingURL=init-NXZ4NXTL.js.map
113
+ //# sourceMappingURL=init-ZRKWYCKA.js.map
@@ -1,19 +1,19 @@
1
- import {
2
- selfUpdating
3
- } from "./chunk-GSWTT2J6.js";
4
- import "./chunk-MNDIED4M.js";
5
1
  import {
6
2
  writeAuditsToAirtable
7
3
  } from "./chunk-QLIUDTY6.js";
4
+ import {
5
+ selfUpdating
6
+ } from "./chunk-WHWVYL7B.js";
7
+ import "./chunk-MNDIED4M.js";
8
8
  import {
9
9
  hasRealScores,
10
10
  lighthouseScoresFromResult
11
11
  } from "./chunk-7BZUOZPU.js";
12
12
  import "./chunk-SVAAONYB.js";
13
+ import "./chunk-5L3FPQIJ.js";
13
14
  import {
14
15
  resolveSites
15
16
  } from "./chunk-Q47DU255.js";
16
- import "./chunk-5L3FPQIJ.js";
17
17
  import {
18
18
  queueDraft
19
19
  } from "./chunk-YFAXOD7Z.js";
@@ -42,22 +42,22 @@ import {
42
42
  import "./chunk-MLT4QY5X.js";
43
43
  import {
44
44
  runAudits
45
- } from "./chunk-QD427NIO.js";
45
+ } from "./chunk-JB4Q3C3M.js";
46
46
  import "./chunk-LKGVSM2O.js";
47
47
  import "./chunk-GASBX52O.js";
48
48
  import "./chunk-XHHMM5HX.js";
49
49
  import "./chunk-Z3JXLULD.js";
50
+ import "./chunk-2NQUCS7O.js";
50
51
  import "./chunk-UDRHAQT7.js";
52
+ import "./chunk-PTDMVGKL.js";
53
+ import "./chunk-LBYOLBW7.js";
51
54
  import "./chunk-RARREDQE.js";
52
55
  import "./chunk-A3WDWNN5.js";
53
56
  import "./chunk-2OP4JKIQ.js";
54
- import "./chunk-XTK5VIQB.js";
55
- import "./chunk-2NQUCS7O.js";
56
- import "./chunk-PTDMVGKL.js";
57
- import "./chunk-LBYOLBW7.js";
58
57
  import {
59
58
  siteLabel
60
59
  } from "./chunk-XXTZBPUY.js";
60
+ import "./chunk-XTK5VIQB.js";
61
61
  import "./chunk-6VAL7XJT.js";
62
62
  import "./chunk-NMAWXBJA.js";
63
63
  import "./chunk-YLYXW5PH.js";
@@ -252,4 +252,4 @@ async function runLaunchCommand(site, opts) {
252
252
  export {
253
253
  runLaunchCommand
254
254
  };
255
- //# sourceMappingURL=launch-WPEQ3TY2.js.map
255
+ //# sourceMappingURL=launch-6X5OXPF2.js.map
@@ -5,10 +5,11 @@ import {
5
5
  makeNotify,
6
6
  notifySubmission,
7
7
  resolveRecipients
8
- } from "./chunk-SJOXWJ5Y.js";
9
- import "./chunk-JRHIFA5R.js";
8
+ } from "./chunk-4763KQ6O.js";
9
+ import "./chunk-QF6IQQFP.js";
10
10
  import "./chunk-5ML7FMBD.js";
11
11
  import "./chunk-NO74DXDP.js";
12
+ import "./chunk-I7NHLVVI.js";
12
13
  import "./chunk-BGTYPVLT.js";
13
14
  import "./chunk-2OP4JKIQ.js";
14
15
  export {
@@ -19,4 +20,4 @@ export {
19
20
  notifySubmission,
20
21
  resolveRecipients
21
22
  };
22
- //# sourceMappingURL=notify-4HP4GZ2H.js.map
23
+ //# sourceMappingURL=notify-IISTHNBR.js.map
@@ -5,21 +5,21 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-TVL5MVUF.js";
8
+ } from "./chunk-TCF6S7AD.js";
9
9
  import {
10
10
  resolveSites
11
11
  } from "./chunk-Q47DU255.js";
12
12
  import {
13
13
  onboard
14
- } from "./chunk-7RJ2HMMJ.js";
15
- import "./chunk-3G25KIWW.js";
14
+ } from "./chunk-NTLSMJHW.js";
15
+ import "./chunk-ZTPPCHDB.js";
16
16
  import "./chunk-OTGB6YQO.js";
17
17
  import "./chunk-LKGVSM2O.js";
18
18
  import "./chunk-Z3JXLULD.js";
19
- import "./chunk-RARREDQE.js";
20
- import "./chunk-XTK5VIQB.js";
21
19
  import "./chunk-LBYOLBW7.js";
20
+ import "./chunk-RARREDQE.js";
22
21
  import "./chunk-XXTZBPUY.js";
22
+ import "./chunk-XTK5VIQB.js";
23
23
 
24
24
  // src/cli/commands/onboard.ts
25
25
  import { resolve } from "path";
@@ -70,4 +70,4 @@ async function runOnboardCommand(site, opts) {
70
70
  export {
71
71
  runOnboardCommand
72
72
  };
73
- //# sourceMappingURL=onboard-BKCYBAVW.js.map
73
+ //# sourceMappingURL=onboard-6NHGCKGB.js.map
@@ -18,8 +18,8 @@ import "./chunk-7NYN2P6I.js";
18
18
  import "./chunk-WEKL2HYJ.js";
19
19
  import "./chunk-DMPHP7UP.js";
20
20
  import "./chunk-22GKJO5F.js";
21
- import "./chunk-RARREDQE.js";
22
21
  import "./chunk-OCNSIBMP.js";
22
+ import "./chunk-RARREDQE.js";
23
23
  import "./chunk-6VAL7XJT.js";
24
24
  export {
25
25
  GLOBAL_REPORT_CC,
@@ -9,9 +9,10 @@ import {
9
9
  envAccuracyDeps,
10
10
  envAnalyzeDeps,
11
11
  envEngines,
12
+ mergeFixes,
12
13
  runProspectAudit
13
- } from "./chunk-WVVT6SJK.js";
14
- import "./chunk-RQLXEMEE.js";
14
+ } from "./chunk-BJQCIJOY.js";
15
+ import "./chunk-T7ZUUOZ2.js";
15
16
  import "./chunk-RARREDQE.js";
16
17
  export {
17
18
  ACCURACY_SKIPPED,
@@ -24,6 +25,7 @@ export {
24
25
  envAccuracyDeps,
25
26
  envAnalyzeDeps,
26
27
  envEngines,
28
+ mergeFixes,
27
29
  runProspectAudit
28
30
  };
29
- //# sourceMappingURL=pipeline-HQJYZ443.js.map
31
+ //# sourceMappingURL=pipeline-AQBFG5WM.js.map
@@ -16,8 +16,8 @@ import "./chunk-7NYN2P6I.js";
16
16
  import "./chunk-WEKL2HYJ.js";
17
17
  import "./chunk-DMPHP7UP.js";
18
18
  import "./chunk-22GKJO5F.js";
19
- import "./chunk-RARREDQE.js";
20
19
  import "./chunk-OCNSIBMP.js";
20
+ import "./chunk-RARREDQE.js";
21
21
  import "./chunk-6VAL7XJT.js";
22
22
 
23
23
  // src/cli/commands/preflight.ts
@@ -9,7 +9,7 @@ import {
9
9
  appendSkipNotice,
10
10
  fleetWorkdir,
11
11
  prepareFleetSites
12
- } from "./chunk-TVL5MVUF.js";
12
+ } from "./chunk-TCF6S7AD.js";
13
13
  import {
14
14
  resolveSites
15
15
  } from "./chunk-Q47DU255.js";
@@ -25,7 +25,13 @@ import {
25
25
  makeGitHub
26
26
  } from "./chunk-XHHMM5HX.js";
27
27
  import "./chunk-Z3JXLULD.js";
28
+ import {
29
+ defaultSpawn
30
+ } from "./chunk-LBYOLBW7.js";
28
31
  import "./chunk-RARREDQE.js";
32
+ import {
33
+ siteLabel
34
+ } from "./chunk-XXTZBPUY.js";
29
35
  import {
30
36
  branchName,
31
37
  checkoutBranch,
@@ -37,12 +43,6 @@ import {
37
43
  isWorkingTreeClean,
38
44
  push
39
45
  } from "./chunk-XTK5VIQB.js";
40
- import {
41
- defaultSpawn
42
- } from "./chunk-LBYOLBW7.js";
43
- import {
44
- siteLabel
45
- } from "./chunk-XXTZBPUY.js";
46
46
 
47
47
  // src/cli/commands/prismic-ci.ts
48
48
  import { readdir } from "fs/promises";
@@ -444,4 +444,4 @@ export {
444
444
  formatPrismicCiResults,
445
445
  runPrismicCiCommand
446
446
  };
447
- //# sourceMappingURL=prismic-ci-6YGRIOHT.js.map
447
+ //# sourceMappingURL=prismic-ci-A2HI7NNJ.js.map
@@ -15,7 +15,7 @@ import {
15
15
  appendSkipNotice,
16
16
  fleetWorkdir,
17
17
  prepareFleetSites
18
- } from "./chunk-TVL5MVUF.js";
18
+ } from "./chunk-TCF6S7AD.js";
19
19
  import {
20
20
  resolveSites
21
21
  } from "./chunk-Q47DU255.js";
@@ -27,14 +27,14 @@ import {
27
27
  siteSlug
28
28
  } from "./chunk-7NYN2P6I.js";
29
29
  import "./chunk-Z3JXLULD.js";
30
- import "./chunk-RARREDQE.js";
31
- import "./chunk-XTK5VIQB.js";
32
30
  import {
33
31
  makeSpawn
34
32
  } from "./chunk-LBYOLBW7.js";
33
+ import "./chunk-RARREDQE.js";
35
34
  import {
36
35
  siteLabel
37
36
  } from "./chunk-XXTZBPUY.js";
37
+ import "./chunk-XTK5VIQB.js";
38
38
 
39
39
  // src/cli/commands/prismic-models.ts
40
40
  import { readdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
@@ -1022,4 +1022,4 @@ export {
1022
1022
  sweepRowWriteback,
1023
1023
  writeSweepToAirtable
1024
1024
  };
1025
- //# sourceMappingURL=prismic-models-FPDSUXDO.js.map
1025
+ //# sourceMappingURL=prismic-models-QTKIMFBC.js.map
@@ -504,6 +504,18 @@ type AccuracyResult = {
504
504
  /** Which branded answers we had full text for. A run whose probes predate
505
505
  * `fullAnswer` reports zero and no assertions, rather than "nothing wrong". */
506
506
  answersRead: number;
507
+ /** The engine confused this business with others of the same name. For a
508
+ * common name this is the headline of the branded search, and it was only
509
+ * ever visible inside a truncated quote. `engineQuote` is verified against
510
+ * the answer text like every other quote; a conflation whose quote is not
511
+ * in the answer is discarded, not trusted. */
512
+ conflation: Conflation;
513
+ };
514
+ type Conflation = {
515
+ detected: boolean;
516
+ /** The OTHER businesses the engine named — never this one. */
517
+ otherNames: string[];
518
+ engineQuote: string | null;
507
519
  };
508
520
 
509
521
  /**
@@ -877,6 +889,11 @@ type Fix = {
877
889
  * image, a broken link, a stale year) answer to no requirement at all.
878
890
  * Absent on reports stored before the model was asked to tag them. */
879
891
  addresses?: string | null;
892
+ /** Where the fix came from. "measured": produced by code from a check that
893
+ * ran on this audit — a finding. "recommendation": written by the model —
894
+ * judgement, rendered below the findings and labelled as such. Absent on
895
+ * reports stored before the split, which were all model-written. */
896
+ origin?: "measured" | "recommendation";
880
897
  };
881
898
  type AnalyzeResult = {
882
899
  /** The business's proper name, as a searchable proper noun — "Acme Roofing",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  GOAL_LABELS,
3
3
  orderRequirements
4
- } from "../chunk-RQLXEMEE.js";
4
+ } from "../chunk-T7ZUUOZ2.js";
5
5
  export {
6
6
  GOAL_LABELS,
7
7
  orderRequirements
@@ -109,7 +109,7 @@ async function runProspectAuditCommand(url, opts) {
109
109
  "No TURSO_DATABASE_URL, so the report cannot be saved or shared. Re-run with --out <file>, or set the Turso credentials."
110
110
  );
111
111
  }
112
- const { runProspectAudit } = await import("./pipeline-HQJYZ443.js");
112
+ const { runProspectAudit } = await import("./pipeline-AQBFG5WM.js");
113
113
  const onStage = (name, status, detail) => {
114
114
  if (status === "start") console.error(`\u2026 ${name}`);
115
115
  else if (status === "ok") console.error(`\u2713 ${name}`);
@@ -135,7 +135,7 @@ async function runProspectAuditCommand(url, opts) {
135
135
  },
136
136
  { ...opts.deps ?? {}, onStage }
137
137
  );
138
- const { renderProspectReport } = await import("./render-3GYW2J34.js");
138
+ const { renderProspectReport } = await import("./render-7G43BSK5.js");
139
139
  const html = renderProspectReport(result);
140
140
  const warnings = [];
141
141
  let file = null;
@@ -187,7 +187,7 @@ async function runProspectAuditCommand(url, opts) {
187
187
  let email = null;
188
188
  if (opts.email) {
189
189
  try {
190
- const { sendAuditEmail, parseProspectAuditRecipients } = await import("./email-PLPDJUDH.js");
190
+ const { sendAuditEmail, parseProspectAuditRecipients } = await import("./email-I4BCETI4.js");
191
191
  const recipients = parseProspectAuditRecipients(process.env.PROSPECT_AUDIT_RECIPIENTS);
192
192
  email = await sendAuditEmail(result, { link, recipients, auditId, pdf });
193
193
  if (!email.sent) {
@@ -213,4 +213,4 @@ export {
213
213
  GOALS,
214
214
  runProspectAuditCommand
215
215
  };
216
- //# sourceMappingURL=prospect-audit-TFIXSTE5.js.map
216
+ //# sourceMappingURL=prospect-audit-RKUVSTLN.js.map
@@ -3,11 +3,11 @@ import {
3
3
  isConfigName,
4
4
  planTemplateDiffs,
5
5
  syncConfigs
6
- } from "../chunk-A6T2R63Z.js";
7
- import "../chunk-3G25KIWW.js";
6
+ } from "../chunk-3HS6LPFX.js";
7
+ import "../chunk-ZTPPCHDB.js";
8
8
  import "../chunk-MLT4QY5X.js";
9
- import "../chunk-XTK5VIQB.js";
10
9
  import "../chunk-XXTZBPUY.js";
10
+ import "../chunk-XTK5VIQB.js";
11
11
  export {
12
12
  ALL_CONFIG_NAMES,
13
13
  isConfigName,
@@ -2,11 +2,12 @@ import {
2
2
  ANALYZE_SKIPPED,
3
3
  PROBES_SKIPPED,
4
4
  resolveBusinessName
5
- } from "./chunk-WVVT6SJK.js";
5
+ } from "./chunk-BJQCIJOY.js";
6
6
  import {
7
7
  escapeHtml,
8
8
  safeUrl
9
9
  } from "./chunk-BGTYPVLT.js";
10
+ import "./chunk-T7ZUUOZ2.js";
10
11
  import {
11
12
  hostnameOf
12
13
  } from "./chunk-RARREDQE.js";
@@ -296,8 +297,7 @@ function renderProspectReport(result) {
296
297
  </body>
297
298
  </html>`;
298
299
  }
299
-
300
300
  export {
301
301
  renderProspectReport
302
302
  };
303
- //# sourceMappingURL=chunk-H6BLTEFN.js.map
303
+ //# sourceMappingURL=render-7G43BSK5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/prospect/render.ts"],"sourcesContent":["import { escapeHtml, safeUrl } from \"../util/html.js\";\nimport { hostnameOf } from \"../util/url.js\";\nimport { ANALYZE_SKIPPED, PROBES_SKIPPED } from \"./pipeline.js\";\nimport { resolveBusinessName } from \"./probes.js\";\nimport type {\n AnalyzeResult,\n ChecksResult,\n Fix,\n LighthouseScores,\n ProbeAnswer,\n ProbesResult,\n ProspectAuditResult,\n StageResult,\n} from \"./types.js\";\n\nconst RED = \"#d71920\";\nconst IMPACT_ORDER: Record<Fix[\"impact\"], number> = { high: 0, medium: 1, low: 2 };\nconst KIND_LABEL: Record<ProbeAnswer[\"kind\"], string> = {\n branded: \"Branded — asked about the business by name\",\n category: \"Category — the questions a buyer would actually type\",\n competitor: \"Competitor — head-to-head comparisons\",\n};\nconst KIND_ORDER: ProbeAnswer[\"kind\"][] = [\"category\", \"branded\", \"competitor\"];\n\n/** Terse product mapping so a recipient who has never seen a crawler's raw\n * user-agent string can place it — factual labelling, not a voice change. */\nconst AI_AGENT_LABELS: Record<string, string> = {\n GPTBot: \"ChatGPT\",\n \"OAI-SearchBot\": \"ChatGPT's search results\",\n ClaudeBot: \"Claude\",\n PerplexityBot: \"Perplexity\",\n \"Google-Extended\": \"Gemini and Google's AI Overviews\",\n CCBot: \"Common Crawl, used to train many AI models\",\n};\n\nfunction describeAgent(agent: string): string {\n const label = AI_AGENT_LABELS[agent];\n return label ? `${agent} (feeds ${label})` : agent;\n}\n\n/** Every genuine stage failure collapses to this one phrase — the real error\n * (status codes, retry counts, timeouts — operator vocabulary) stays in the\n * persisted JSON and CLI output for operators, but never reaches a stranger\n * who would read it as broken software rather than a diagnostic. */\nconst STAGE_FAILED_MESSAGE = \"we could not complete this part of the analysis\";\n\nconst STYLES = `\n :root { color-scheme: light; }\n * { box-sizing: border-box; }\n body { margin: 0; background: #faf8f5; color: #1a1a1a;\n font: 16px/1.6 -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif; }\n .wrap { max-width: 860px; margin: 0 auto; padding: 48px 24px 80px; }\n h1, h2, h3 { font-family: Besley, Georgia, \"Times New Roman\", serif; font-weight: 600; line-height: 1.2; }\n h1 { font-size: 40px; margin: 0 0 8px; }\n h2 { font-size: 26px; margin: 48px 0 12px; border-top: 2px solid #e6e1da; padding-top: 24px; }\n h3 { font-size: 18px; margin: 24px 0 8px; }\n .lede { color: #57544f; margin: 0 0 8px; }\n .scores { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin: 32px 0; }\n .score { background: #fff; border: 1px solid #e6e1da; border-radius: 8px; padding: 16px; }\n .score .n { font-family: Besley, Georgia, serif; font-size: 38px; line-height: 1; color: ${RED}; }\n .score .n.na { font-size: 18px; color: #8a857e; }\n .score .l { font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: #57544f; margin-top: 8px; }\n .score .hint { font-size: 12px; color: #8a857e; margin-top: 4px; }\n .card { background: #fff; border: 1px solid #e6e1da; border-radius: 8px; padding: 16px; margin: 12px 0; }\n .q { font-weight: 600; }\n .tag { display: inline-block; font-size: 12px; letter-spacing: .06em; text-transform: uppercase;\n border-radius: 999px; padding: 2px 10px; border: 1px solid currentColor; }\n .yes { color: #14663c; } .partial { color: #8a6d00; } .no { color: ${RED}; }\n .unknown { color: #8a857e; border-style: dashed; }\n ul { padding-left: 20px; } li { margin: 6px 0; }\n .muted { color: #8a857e; }\n .cta { margin-top: 56px; background: ${RED}; color: #fff; border-radius: 8px; padding: 24px 28px; }\n .cta h2 { border: 0; padding: 0; margin: 0 0 8px; color: #fff; }\n .cta a { color: #fff; }\n table { width: 100%; border-collapse: collapse; margin-top: 8px; }\n td, th { text-align: left; padding: 6px 8px; border-bottom: 1px solid #eee; vertical-align: top; }\n @media print {\n body { background: #fff; }\n .card, .score, .cta { break-inside: avoid; }\n /* Browsers default \"print background graphics\" OFF, which drops the red\n .cta background but keeps its white text — the only ask in the report\n would otherwise print white-on-white and vanish. Fall back to dark\n text on no background with a visible border instead of relying on the\n background for contrast. */\n .cta { background: none; color: #1a1a1a; border: 2px solid ${RED}; }\n .cta h2, .cta a { color: #1a1a1a; }\n }\n`;\n\n/** One of the THREE site-score cards. AI Visibility used to be a fourth and\n * was removed on purpose, matching the web report's control split: these\n * three measure the site and move when the site is fixed; visibility is\n * measured out in the world and reported with receipts in \"What the AI\n * engines said about you\" — a fourth card said it was the same kind of claim,\n * and ours to move. It is neither. `scores.aiVisibility` stays computed and\n * stored; it just is not a card, here or in the email or CLI summary. */\nfunction scoreCard(label: string, value: number | null, hint?: string): string {\n const n =\n value === null ? `<div class=\"n na\">Not measured</div>` : `<div class=\"n\">${value}</div>`;\n const hintHtml = hint ? `<div class=\"hint\">${escapeHtml(hint)}</div>` : \"\";\n return `<div class=\"score\">${n}<div class=\"l\">${escapeHtml(label)}</div>${hintHtml}</div>`;\n}\n\n/** Body for a stage that succeeded, or a uniform \"Not measured\" note when it didn't. */\nfunction stageBody<T>(stage: StageResult<T>, body: (data: T) => string): string {\n return stage.ok ? body(stage.data) : notMeasuredNote();\n}\n\n/** The stage's real error is deliberately not a parameter here — see\n * STAGE_FAILED_MESSAGE above. Every genuine failure reads the same\n * client-safe way, regardless of what actually broke. */\nfunction notMeasuredNote(): string {\n return `<p class=\"muted\">Not measured — ${STAGE_FAILED_MESSAGE}.</p>`;\n}\n\n/** A stage a human deliberately turned off (--no-probes, or analyze skipped\n * because checks failed upstream) must read as \"you asked us to skip this\" —\n * never as \"we tried and could not\", which is what a bare \"Not measured —\n * {error}\" would otherwise imply. Compares against the pipeline's exported\n * constants rather than a retyped string literal. */\nfunction skippableStageNote(error: string, skipConstant: string, skipMessage: string): string {\n if (error === skipConstant) {\n return `<p class=\"muted\">${escapeHtml(skipMessage)}</p>`;\n }\n return notMeasuredNote();\n}\n\n/** `new Date(iso).toLocaleDateString()` never throws on a bad timestamp — it\n * silently returns \"Invalid Date\" — so a try/catch here is dead code. Guard\n * on getTime() instead, and fall back to the raw ISO string so a corrupt\n * timestamp is at least visibly a timestamp rather than the literal words\n * \"Invalid Date\". */\nfunction formatIsoDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n timeZone: \"UTC\",\n });\n}\n\n/** The probes section leads the report, so a branded-only run (analyze\n * failed upstream and supplied no buyer questions, or every category query\n * itself failed) must not let the branded recognition line read as a\n * positive discoverability finding on its own — an engine echoing a name\n * it was just handed proves nothing about whether a buyer who never named\n * the business would find it. */\n/** `businessNameUsed` is whether the engines were actually queried with the\n * business NAME — not merely whether one existed. `resolveBusinessName` falls\n * back to the bare domain for anything it cannot use, so a name can be present\n * and still never reach a query. Without this gate, \"even with the name handed\n * to them\" is a false claim: whenever businessName was empty, and (until the\n * caller was fixed) whenever the name was rejected and the probes searched for\n * the domain instead. Never derived from the raw ProbesResult, which has no\n * way to say \"no name was available\". */\nfunction buildProbesSection(p: ProbesResult, businessNameUsed: boolean): string {\n const byKind = new Map<ProbeAnswer[\"kind\"], ProbeAnswer[]>();\n for (const a of p.answers) {\n const bucket = byKind.get(a.kind) ?? [];\n bucket.push(a);\n byKind.set(a.kind, bucket);\n }\n\n const recognition = p.brandedRecognized\n ? \"<p>When the engines were asked about the business by name, they recognized it.</p>\"\n : businessNameUsed\n ? \"<p>When the engines were asked about the business by name, they did not recognize it — a real citation of the site never showed up, even with the name handed to them.</p>\"\n : \"<p>The engines did not recognize the business — there was no business name available to ask them with, so this reflects a harder starting point than a named query would.</p>\";\n\n const categoryCaveat = byKind.has(\"category\")\n ? \"\"\n : `<p class=\"muted\"><strong>No buyer-question (category) query was tested here</strong> — only name-recognition. An engine echoing back a name it was just given says nothing about whether a buyer who had never heard of the business would be shown it; treat the line above as a floor, not a visibility signal.</p>`;\n\n // A degraded run says so. The score now divides by what was ASKED, so failed\n // probes push it DOWN rather than silently inflating it — which is the safe\n // direction, but only if the reader is told the run was incomplete. Silence\n // here would trade one misleading number for another.\n // Bound to a local so the narrowing survives into the template — `missing > 0`\n // says nothing to the compiler about `p.categoryProbes` itself, and a stored\n // report from before the field existed genuinely does not have it.\n const tally = p.categoryProbes;\n const missing = tally ? tally.attempted - tally.answered : 0;\n const degradedNotice =\n tally && missing > 0\n ? `<p class=\"muted\"><strong>${missing} of ${tally.attempted} buyer-question searches did not come back</strong> — the engine errored on them. They are counted as \"not found\", so the visibility figure above is a floor: the real number could be higher, not lower. Worth re-running before drawing a conclusion from it.</p>`\n : \"\";\n\n const groups = KIND_ORDER.filter((k) => byKind.has(k))\n .map((kind) => {\n const answers = byKind.get(kind) ?? [];\n const cards = answers\n .map(\n (a) => `<div class=\"card\">\n <div class=\"q\">${escapeHtml(a.engine)} · “${escapeHtml(a.query)}”</div>\n <p class=\"muted\">Asked ${escapeHtml(formatIsoDate(a.askedAt))}</p>\n <p>${escapeHtml(a.snippet)}${a.truncated ? \"…\" : \"\"}</p>\n <p class=\"muted\">${\n // The scorer's own decision, recorded on the answer — NOT\n // `domainCited || brandMentioned`, which is a looser rule. Using the\n // loose one printed \"You were named in this answer\" above a card\n // contributing zero to the score beside it. Reports persisted before\n // the field existed fall back to what they already said.\n (a.countedAsVisible ?? (a.domainCited || a.brandMentioned))\n ? \"You were named in this answer.\"\n : \"You were not named in this answer.\"\n }${a.citedDomains.length ? ` Sources the engine retrieved: ${escapeHtml(a.citedDomains.join(\", \"))}` : \"\"}</p>\n </div>`,\n )\n .join(\"\");\n return `<h3>${escapeHtml(KIND_LABEL[kind])}</h3>${cards}`;\n })\n .join(\"\");\n\n const competitors = p.competitorsSeen.length\n ? `<h3>Who the engines cited instead</h3><ul>${p.competitorsSeen\n .map((c) => `<li>${escapeHtml(c.domain)} — ${c.count} time${c.count === 1 ? \"\" : \"s\"}</li>`)\n .join(\"\")}</ul>`\n : \"\";\n\n return recognition + categoryCaveat + degradedNotice + groups + competitors;\n}\n\n/** crawlerAccessMeasured is false only when the robots.txt fetch itself\n * failed, so the blocked/allowed lists are empty out of ignorance, not\n * because we confirmed access. Saying \"every crawler can reach the site\" in\n * that case would manufacture a finding from our own missing data.\n * sitemapMeasured/llmsTxtMeasured get the same treatment as\n * crawlerAccessMeasured: a failed fetch reads as \"not measured\", never as a\n * confirmed \"missing\" — that's a claim about the prospect's site we haven't\n * earned. Item 1: none of the three \"not measured\" lines below take the raw\n * fetch error as input anymore — status codes and transport vocabulary\n * (\"503 Service Unavailable\", \"ETIMEDOUT\") are operator vocabulary, same as\n * a stage failure (see STAGE_FAILED_MESSAGE); the real error still lives in\n * the persisted JSON and CLI output, never in this client-facing page. */\nfunction buildFindabilitySection(c: ChecksResult): string {\n const accessBlock = !c.crawlerAccessMeasured\n ? `<p class=\"muted\">Crawler access: not measured — ${STAGE_FAILED_MESSAGE}.</p>`\n : c.crawlerAccess.blockedAi.length\n ? `<p><strong>Blocked AI crawlers:</strong> ${escapeHtml(\n c.crawlerAccess.blockedAi.map(describeAgent).join(\", \"),\n )}</p>`\n : // Says what was measured, which is robots.txt, and no more. \"Every AI\n // crawler can reach the site\" was a claim robots.txt cannot support:\n // a CDN's bot management enforces its own answer and can contradict the\n // file without the owner knowing. Confirmed on a live prospect: its\n // robots.txt blocked nothing relevant, and its CDN returned 403 to one\n // named AI crawler on every request while serving a browser and two\n // other crawlers normally. The report said they were fine.\n `<p>Nothing in your robots.txt blocks the AI crawlers we checked.</p>`;\n const classical =\n c.crawlerAccessMeasured && c.crawlerAccess.blockedClassical.length\n ? `<p><strong>Blocked search crawlers:</strong> ${escapeHtml(\n c.crawlerAccess.blockedClassical.join(\", \"),\n )}</p>`\n : \"\";\n const sitemapLine = c.sitemapMeasured\n ? `<li>sitemap.xml: ${c.sitemapPresent ? \"present\" : \"missing\"}</li>`\n : `<li>sitemap.xml: not measured — ${STAGE_FAILED_MESSAGE}.</li>`;\n // llms.txt is no longer listed as a finding. It used to sit here as\n // \"present\"/\"missing\" beside sitemap.xml, which put the two on equal footing\n // — and \"missing\" beside a checklist reads as a job to do. Search crawlers\n // demonstrably read a sitemap; no answer engine has documented reading\n // llms.txt to build an answer. The footnote below says that outright instead\n // of leaving a prospect to infer it, or to go and build one on our say-so.\n const llmsFootnote = `<p class=\"muted\"><strong>A note on llms.txt.</strong> You may have been told to\n add one. We check for it — this site ${\n !c.llmsTxtMeasured ? \"could not be checked\" : c.llmsTxtPresent ? \"has one\" : \"does not\"\n } — but we do not score it and we will not recommend it. It is a 2024 proposal that no\n answer engine has committed to reading, and adding one has no measured effect on whether you\n are cited. If that changes we will say so and score it then.</p>`;\n return `${accessBlock}${classical}\n <ul>\n ${sitemapLine}\n <li>Pages missing a meta description: ${c.meta.missingDescription} of ${c.meta.pageCount}</li>\n <li>Pages missing a canonical URL: ${c.meta.missingCanonical} of ${c.meta.pageCount}</li>\n <li>Pages missing share images/titles: ${c.meta.missingSocial} of ${c.meta.pageCount}</li>\n <li>Security headers missing: ${\n c.securityHeaders.missing.length ? escapeHtml(c.securityHeaders.missing.join(\", \")) : \"none\"\n }</li>\n ${\n c.meta.pagesWithoutExtract > 0\n ? `<li><strong>${c.meta.pagesWithoutExtract}</strong> additional page${\n c.meta.pagesWithoutExtract === 1 ? \"\" : \"s\"\n } produced no readable content at all and ${\n c.meta.pagesWithoutExtract === 1 ? \"is\" : \"are\"\n } not counted in the ${c.meta.pageCount} pages above.</li>`\n : \"\"\n }\n </ul>${llmsFootnote}`;\n}\n\n/** Lighthouse runs as its own independent pipeline stage (see pipeline.ts) —\n * it does not depend on `checks` succeeding. Rendering it off `result.checks`\n * would throw away real, measured performance/SEO/accessibility numbers\n * whenever an unrelated stage failed, so this reads `result.lighthouse`\n * directly and is rendered unconditionally, not nested inside the checks\n * degrade path. */\nfunction buildLighthouseBlock(lh: StageResult<LighthouseScores>): string {\n return lh.ok\n ? `<p class=\"muted\">Lighthouse — performance ${lh.data.performance ?? \"not measured\"},\n SEO ${lh.data.seo ?? \"not measured\"}, accessibility ${lh.data.accessibility ?? \"not measured\"}.</p>`\n : `<p class=\"muted\">Lighthouse not measured — ${STAGE_FAILED_MESSAGE}.</p>`;\n}\n\nfunction buildReadabilitySection(c: ChecksResult): string {\n const jsLine =\n c.jsDependence.avgMissing === null\n ? `<p class=\"muted\">JavaScript-dependence: not measured — no page produced a comparable raw/rendered pair.</p>`\n : `<p><strong>${Math.round(c.jsDependence.avgMissing * 100)}%</strong> of the words a visitor reads only appear after JavaScript runs.\n Most AI crawlers do not run JavaScript, so that share of your site is invisible to them.</p>`;\n return `${jsLine}\n <ul>\n <li>Structured data found: ${c.schema.typesFound.length ? escapeHtml(c.schema.typesFound.join(\", \")) : \"none\"}</li>\n <li>Expected structured data missing: ${\n c.schema.missingExpected.length ? escapeHtml(c.schema.missingExpected.join(\", \")) : \"none\"\n }</li>\n <li>Pages without a top-level heading: ${c.headings.pagesWithoutH1} of ${c.meta.pageCount}</li>\n ${\n c.meta.pagesWithoutExtract > 0\n ? `<li><strong>${c.meta.pagesWithoutExtract}</strong> page${\n c.meta.pagesWithoutExtract === 1 ? \"\" : \"s\"\n } produced no extract at all — excluded from every figure above, not counted as readable or unreadable.</li>`\n : \"\"\n }\n </ul>`;\n}\n\n/** The report's whole premise is claims the recipient can go check — so the\n * one quoted passage of evidence links to the page it came from (via\n * safeUrl; a null/non-http page renders as plain escaped text, no dead\n * \"#\" anchor). `answered`/`impact`/`effort` are escaped even though a zod\n * enum constrains them upstream — the renderer should not depend on\n * another file staying in sync to stay safe. */\n/** What the verdict column says. \"unknown\" is OUR gap — the model returned no\n * verdict for that question — so it must not be printed as a finding about the\n * site, and must not look like one either. Matches the web report's labels. */\nconst ANSWERED_LABEL: Record<string, string> = {\n yes: \"yes\",\n partial: \"partial\",\n no: \"no\",\n unknown: \"not measured\",\n};\n\nfunction buildAnswersSection(a: AnalyzeResult): string {\n const rows = a.buyerQuestions\n .map((q) => {\n const answered = escapeHtml(q.answered);\n const label = escapeHtml(ANSWERED_LABEL[q.answered] ?? q.answered);\n const evidenceText = q.evidence ? escapeHtml(q.evidence) : null;\n const evidenceHref = q.evidence && q.page ? safeUrl(q.page) : null;\n // An unmeasured question has no evidence for the same reason it has no\n // verdict: we never got one back. Printing \"no passage on the site\" there\n // reports our own missing measurement as a defect of theirs.\n const evidenceCell =\n q.answered === \"unknown\"\n ? '<span class=\"muted\">we did not get a verdict on this question</span>'\n : evidenceText === null\n ? '<span class=\"muted\">no passage on the site</span>'\n : evidenceHref && evidenceHref !== \"#\"\n ? `<a href=\"${escapeHtml(evidenceHref)}\" target=\"_blank\" rel=\"noopener noreferrer\">${evidenceText}</a>`\n : evidenceText;\n return `<tr>\n <td>${escapeHtml(q.question)}</td>\n <td><span class=\"tag ${answered}\">${label}</span></td>\n <td>${evidenceCell}</td>\n </tr>`;\n })\n .join(\"\");\n return `<p>${escapeHtml(a.narrative.answers)}</p>\n <table><tr><th>What buyers ask</th><th>Answered</th><th>Evidence</th></tr>${rows}</table>`;\n}\n\nexport function renderProspectReport(result: ProspectAuditResult): string {\n const host = hostnameOf(result.url);\n // `businessName` is the searchable NAME (possibly \"\"), never the\n // description. An empty/missing name is itself a finding — fall back to\n // the hostname rather than printing nothing, or worse, printing \"\" as if\n // it were a verified fact about the business.\n const name = result.businessName && result.businessName.trim() ? result.businessName : host;\n const date = formatIsoDate(result.generatedAt);\n\n const findabilitySection = stageBody(result.checks, buildFindabilitySection);\n const lighthouseBlock = buildLighthouseBlock(result.lighthouse);\n const readabilitySection = stageBody(result.checks, buildReadabilitySection);\n\n // A real business name was resolved and handed to the engines only when\n // `result.businessName` is non-empty — the same value the pipeline\n // actually queried with (see pipeline.ts). Used to gate the probes\n // section's \"even with the name handed to them\" phrasing (Fix 6).\n // Whether the engines were actually queried with the NAME, which is not the\n // same as whether a name existed. resolveBusinessName falls back to the bare\n // domain for anything it cannot use, and this line used to read the\n // un-resolved value — so a report could claim the engines were given the name\n // \"even with the name handed to them\" while the probes had in fact searched\n // for \"stlouisroofing.com\". Resolve it the same way probes.ts does.\n const businessNameUsed = Boolean(\n result.businessName &&\n result.businessName.trim() &&\n resolveBusinessName(result.businessName, result.url) === result.businessName.trim(),\n );\n\n const fixes = result.analyze.ok\n ? (() => {\n const sorted = [...result.analyze.data.fixes].sort(\n (x, y) => IMPACT_ORDER[x.impact] - IMPACT_ORDER[y.impact],\n );\n if (sorted.length === 0) {\n // A genuinely well-optimized site: a bare empty list reads as a\n // rendering bug, not a compliment. Say so directly, and keep this\n // section as a pitch — it's the one the salesperson is sending\n // the report to make.\n return `<p>Nothing here reads as urgent — the essentials are already in place.\n Reddoor can still help push this further: tightening structured data, monitoring\n how the answer engines describe you, and keeping pace as they change.</p>`;\n }\n return `<ol>${sorted\n .map((f) => {\n const impact = escapeHtml(f.impact);\n const effort = escapeHtml(f.effort);\n return `<li><strong>${escapeHtml(f.title)}</strong> — ${escapeHtml(f.why)}\n <span class=\"muted\">(${impact} impact, ${effort} effort)</span></li>`;\n })\n .join(\"\")}</ol>`;\n })()\n : skippableStageNote(\n result.analyze.error,\n ANALYZE_SKIPPED,\n \"Skipped — the checks stage failed, so there was nothing to build fixes from.\",\n );\n\n const probesSectionFinal = result.probes.ok\n ? buildProbesSection(result.probes.data, businessNameUsed)\n : skippableStageNote(\n result.probes.error,\n PROBES_SKIPPED,\n \"This audit did not run the AI-visibility probes.\",\n );\n\n const answersSectionFinal = result.analyze.ok\n ? buildAnswersSection(result.analyze.data)\n : skippableStageNote(\n result.analyze.error,\n ANALYZE_SKIPPED,\n \"Skipped — the checks stage failed, so there were no buyer questions to check.\",\n );\n\n const description = result.analyze.ok ? result.analyze.data.business : null;\n const narrative = result.analyze.ok\n ? `<p class=\"lede\">${escapeHtml(description ?? \"\")}</p>\n <p class=\"lede\">${escapeHtml(result.analyze.data.narrative.findability)}\n ${escapeHtml(result.analyze.data.narrative.readability)}</p>`\n : \"\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<meta name=\"robots\" content=\"noindex\" />\n<title>Can AI find ${escapeHtml(name)}? — Reddoor audit</title>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n<link href=\"https://fonts.googleapis.com/css2?family=Besley:wght@400;600&display=swap\" rel=\"stylesheet\" />\n<style>${STYLES}</style>\n</head>\n<body>\n<div class=\"wrap\">\n <h1>Can AI and Google actually find ${escapeHtml(name)}?</h1>\n <p class=\"lede\"><a href=\"${escapeHtml(safeUrl(result.url))}\" target=\"_blank\" rel=\"noopener noreferrer\">${escapeHtml(result.url)}</a> · audited ${escapeHtml(date)} by Reddoor Creative</p>\n ${narrative}\n\n <p class=\"muted\">Each score below runs 0–100 — read it as a score, not a percentage.</p>\n <div class=\"scores\">\n ${scoreCard(\"Findability\", result.scores.findability, \"How easily AI and search crawlers can find and reach your site — crawl rules, sitemap, key metadata\")}\n ${scoreCard(\"Readability\", result.scores.readability, \"How much of your site's content those crawlers can actually read once they're in — most don't run JavaScript\")}\n ${scoreCard(\"Answers\", result.scores.answers, \"How many buyer questions your site's own content answers — separate from what the AI engines say back\")}\n </div>\n\n <h2>What the AI engines said about you</h2>\n ${probesSectionFinal}\n\n <h2>What the crawlers can reach</h2>\n ${findabilitySection}\n ${lighthouseBlock}\n\n <h2>What the crawlers can read</h2>\n ${readabilitySection}\n\n <h2>The questions your buyers ask</h2>\n ${answersSectionFinal}\n\n <h2>What to fix first</h2>\n ${fixes}\n\n <div class=\"cta\">\n <h2>Want this fixed?</h2>\n <p>Reddoor Creative rebuilds sites so answer engines can read, quote and recommend them.\n Reply to the email this link came from, or start at\n <a href=\"https://reddoorla.com/\">reddoorla.com</a>.</p>\n </div>\n</div>\n</body>\n</html>`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/prospect/render.ts"],"sourcesContent":["import { escapeHtml, safeUrl } from \"../util/html.js\";\nimport { hostnameOf } from \"../util/url.js\";\nimport { ANALYZE_SKIPPED, PROBES_SKIPPED } from \"./pipeline.js\";\nimport { resolveBusinessName } from \"./probes.js\";\nimport type {\n AnalyzeResult,\n ChecksResult,\n Fix,\n LighthouseScores,\n ProbeAnswer,\n ProbesResult,\n ProspectAuditResult,\n StageResult,\n} from \"./types.js\";\n\nconst RED = \"#d71920\";\nconst IMPACT_ORDER: Record<Fix[\"impact\"], number> = { high: 0, medium: 1, low: 2 };\nconst KIND_LABEL: Record<ProbeAnswer[\"kind\"], string> = {\n branded: \"Branded — asked about the business by name\",\n category: \"Category — the questions a buyer would actually type\",\n competitor: \"Competitor — head-to-head comparisons\",\n};\nconst KIND_ORDER: ProbeAnswer[\"kind\"][] = [\"category\", \"branded\", \"competitor\"];\n\n/** Terse product mapping so a recipient who has never seen a crawler's raw\n * user-agent string can place it — factual labelling, not a voice change. */\nconst AI_AGENT_LABELS: Record<string, string> = {\n GPTBot: \"ChatGPT\",\n \"OAI-SearchBot\": \"ChatGPT's search results\",\n ClaudeBot: \"Claude\",\n PerplexityBot: \"Perplexity\",\n \"Google-Extended\": \"Gemini and Google's AI Overviews\",\n CCBot: \"Common Crawl, used to train many AI models\",\n};\n\nfunction describeAgent(agent: string): string {\n const label = AI_AGENT_LABELS[agent];\n return label ? `${agent} (feeds ${label})` : agent;\n}\n\n/** Every genuine stage failure collapses to this one phrase — the real error\n * (status codes, retry counts, timeouts — operator vocabulary) stays in the\n * persisted JSON and CLI output for operators, but never reaches a stranger\n * who would read it as broken software rather than a diagnostic. */\nconst STAGE_FAILED_MESSAGE = \"we could not complete this part of the analysis\";\n\nconst STYLES = `\n :root { color-scheme: light; }\n * { box-sizing: border-box; }\n body { margin: 0; background: #faf8f5; color: #1a1a1a;\n font: 16px/1.6 -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif; }\n .wrap { max-width: 860px; margin: 0 auto; padding: 48px 24px 80px; }\n h1, h2, h3 { font-family: Besley, Georgia, \"Times New Roman\", serif; font-weight: 600; line-height: 1.2; }\n h1 { font-size: 40px; margin: 0 0 8px; }\n h2 { font-size: 26px; margin: 48px 0 12px; border-top: 2px solid #e6e1da; padding-top: 24px; }\n h3 { font-size: 18px; margin: 24px 0 8px; }\n .lede { color: #57544f; margin: 0 0 8px; }\n .scores { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin: 32px 0; }\n .score { background: #fff; border: 1px solid #e6e1da; border-radius: 8px; padding: 16px; }\n .score .n { font-family: Besley, Georgia, serif; font-size: 38px; line-height: 1; color: ${RED}; }\n .score .n.na { font-size: 18px; color: #8a857e; }\n .score .l { font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: #57544f; margin-top: 8px; }\n .score .hint { font-size: 12px; color: #8a857e; margin-top: 4px; }\n .card { background: #fff; border: 1px solid #e6e1da; border-radius: 8px; padding: 16px; margin: 12px 0; }\n .q { font-weight: 600; }\n .tag { display: inline-block; font-size: 12px; letter-spacing: .06em; text-transform: uppercase;\n border-radius: 999px; padding: 2px 10px; border: 1px solid currentColor; }\n .yes { color: #14663c; } .partial { color: #8a6d00; } .no { color: ${RED}; }\n .unknown { color: #8a857e; border-style: dashed; }\n ul { padding-left: 20px; } li { margin: 6px 0; }\n .muted { color: #8a857e; }\n .cta { margin-top: 56px; background: ${RED}; color: #fff; border-radius: 8px; padding: 24px 28px; }\n .cta h2 { border: 0; padding: 0; margin: 0 0 8px; color: #fff; }\n .cta a { color: #fff; }\n table { width: 100%; border-collapse: collapse; margin-top: 8px; }\n td, th { text-align: left; padding: 6px 8px; border-bottom: 1px solid #eee; vertical-align: top; }\n @media print {\n body { background: #fff; }\n .card, .score, .cta { break-inside: avoid; }\n /* Browsers default \"print background graphics\" OFF, which drops the red\n .cta background but keeps its white text — the only ask in the report\n would otherwise print white-on-white and vanish. Fall back to dark\n text on no background with a visible border instead of relying on the\n background for contrast. */\n .cta { background: none; color: #1a1a1a; border: 2px solid ${RED}; }\n .cta h2, .cta a { color: #1a1a1a; }\n }\n`;\n\n/** One of the THREE site-score cards. AI Visibility used to be a fourth and\n * was removed on purpose, matching the web report's control split: these\n * three measure the site and move when the site is fixed; visibility is\n * measured out in the world and reported with receipts in \"What the AI\n * engines said about you\" — a fourth card said it was the same kind of claim,\n * and ours to move. It is neither. `scores.aiVisibility` stays computed and\n * stored; it just is not a card, here or in the email or CLI summary. */\nfunction scoreCard(label: string, value: number | null, hint?: string): string {\n const n =\n value === null ? `<div class=\"n na\">Not measured</div>` : `<div class=\"n\">${value}</div>`;\n const hintHtml = hint ? `<div class=\"hint\">${escapeHtml(hint)}</div>` : \"\";\n return `<div class=\"score\">${n}<div class=\"l\">${escapeHtml(label)}</div>${hintHtml}</div>`;\n}\n\n/** Body for a stage that succeeded, or a uniform \"Not measured\" note when it didn't. */\nfunction stageBody<T>(stage: StageResult<T>, body: (data: T) => string): string {\n return stage.ok ? body(stage.data) : notMeasuredNote();\n}\n\n/** The stage's real error is deliberately not a parameter here — see\n * STAGE_FAILED_MESSAGE above. Every genuine failure reads the same\n * client-safe way, regardless of what actually broke. */\nfunction notMeasuredNote(): string {\n return `<p class=\"muted\">Not measured — ${STAGE_FAILED_MESSAGE}.</p>`;\n}\n\n/** A stage a human deliberately turned off (--no-probes, or analyze skipped\n * because checks failed upstream) must read as \"you asked us to skip this\" —\n * never as \"we tried and could not\", which is what a bare \"Not measured —\n * {error}\" would otherwise imply. Compares against the pipeline's exported\n * constants rather than a retyped string literal. */\nfunction skippableStageNote(error: string, skipConstant: string, skipMessage: string): string {\n if (error === skipConstant) {\n return `<p class=\"muted\">${escapeHtml(skipMessage)}</p>`;\n }\n return notMeasuredNote();\n}\n\n/** `new Date(iso).toLocaleDateString()` never throws on a bad timestamp — it\n * silently returns \"Invalid Date\" — so a try/catch here is dead code. Guard\n * on getTime() instead, and fall back to the raw ISO string so a corrupt\n * timestamp is at least visibly a timestamp rather than the literal words\n * \"Invalid Date\". */\nfunction formatIsoDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n timeZone: \"UTC\",\n });\n}\n\n/** The probes section leads the report, so a branded-only run (analyze\n * failed upstream and supplied no buyer questions, or every category query\n * itself failed) must not let the branded recognition line read as a\n * positive discoverability finding on its own — an engine echoing a name\n * it was just handed proves nothing about whether a buyer who never named\n * the business would find it. */\n/** `businessNameUsed` is whether the engines were actually queried with the\n * business NAME — not merely whether one existed. `resolveBusinessName` falls\n * back to the bare domain for anything it cannot use, so a name can be present\n * and still never reach a query. Without this gate, \"even with the name handed\n * to them\" is a false claim: whenever businessName was empty, and (until the\n * caller was fixed) whenever the name was rejected and the probes searched for\n * the domain instead. Never derived from the raw ProbesResult, which has no\n * way to say \"no name was available\". */\nfunction buildProbesSection(p: ProbesResult, businessNameUsed: boolean): string {\n const byKind = new Map<ProbeAnswer[\"kind\"], ProbeAnswer[]>();\n for (const a of p.answers) {\n const bucket = byKind.get(a.kind) ?? [];\n bucket.push(a);\n byKind.set(a.kind, bucket);\n }\n\n const recognition = p.brandedRecognized\n ? \"<p>When the engines were asked about the business by name, they recognized it.</p>\"\n : businessNameUsed\n ? \"<p>When the engines were asked about the business by name, they did not recognize it — a real citation of the site never showed up, even with the name handed to them.</p>\"\n : \"<p>The engines did not recognize the business — there was no business name available to ask them with, so this reflects a harder starting point than a named query would.</p>\";\n\n const categoryCaveat = byKind.has(\"category\")\n ? \"\"\n : `<p class=\"muted\"><strong>No buyer-question (category) query was tested here</strong> — only name-recognition. An engine echoing back a name it was just given says nothing about whether a buyer who had never heard of the business would be shown it; treat the line above as a floor, not a visibility signal.</p>`;\n\n // A degraded run says so. The score now divides by what was ASKED, so failed\n // probes push it DOWN rather than silently inflating it — which is the safe\n // direction, but only if the reader is told the run was incomplete. Silence\n // here would trade one misleading number for another.\n // Bound to a local so the narrowing survives into the template — `missing > 0`\n // says nothing to the compiler about `p.categoryProbes` itself, and a stored\n // report from before the field existed genuinely does not have it.\n const tally = p.categoryProbes;\n const missing = tally ? tally.attempted - tally.answered : 0;\n const degradedNotice =\n tally && missing > 0\n ? `<p class=\"muted\"><strong>${missing} of ${tally.attempted} buyer-question searches did not come back</strong> — the engine errored on them. They are counted as \"not found\", so the visibility figure above is a floor: the real number could be higher, not lower. Worth re-running before drawing a conclusion from it.</p>`\n : \"\";\n\n const groups = KIND_ORDER.filter((k) => byKind.has(k))\n .map((kind) => {\n const answers = byKind.get(kind) ?? [];\n const cards = answers\n .map(\n (a) => `<div class=\"card\">\n <div class=\"q\">${escapeHtml(a.engine)} · “${escapeHtml(a.query)}”</div>\n <p class=\"muted\">Asked ${escapeHtml(formatIsoDate(a.askedAt))}</p>\n <p>${escapeHtml(a.snippet)}${a.truncated ? \"…\" : \"\"}</p>\n <p class=\"muted\">${\n // The scorer's own decision, recorded on the answer — NOT\n // `domainCited || brandMentioned`, which is a looser rule. Using the\n // loose one printed \"You were named in this answer\" above a card\n // contributing zero to the score beside it. Reports persisted before\n // the field existed fall back to what they already said.\n (a.countedAsVisible ?? (a.domainCited || a.brandMentioned))\n ? \"You were named in this answer.\"\n : \"You were not named in this answer.\"\n }${a.citedDomains.length ? ` Sources the engine retrieved: ${escapeHtml(a.citedDomains.join(\", \"))}` : \"\"}</p>\n </div>`,\n )\n .join(\"\");\n return `<h3>${escapeHtml(KIND_LABEL[kind])}</h3>${cards}`;\n })\n .join(\"\");\n\n const competitors = p.competitorsSeen.length\n ? `<h3>Who the engines cited instead</h3><ul>${p.competitorsSeen\n .map((c) => `<li>${escapeHtml(c.domain)} — ${c.count} time${c.count === 1 ? \"\" : \"s\"}</li>`)\n .join(\"\")}</ul>`\n : \"\";\n\n return recognition + categoryCaveat + degradedNotice + groups + competitors;\n}\n\n/** crawlerAccessMeasured is false only when the robots.txt fetch itself\n * failed, so the blocked/allowed lists are empty out of ignorance, not\n * because we confirmed access. Saying \"every crawler can reach the site\" in\n * that case would manufacture a finding from our own missing data.\n * sitemapMeasured/llmsTxtMeasured get the same treatment as\n * crawlerAccessMeasured: a failed fetch reads as \"not measured\", never as a\n * confirmed \"missing\" — that's a claim about the prospect's site we haven't\n * earned. Item 1: none of the three \"not measured\" lines below take the raw\n * fetch error as input anymore — status codes and transport vocabulary\n * (\"503 Service Unavailable\", \"ETIMEDOUT\") are operator vocabulary, same as\n * a stage failure (see STAGE_FAILED_MESSAGE); the real error still lives in\n * the persisted JSON and CLI output, never in this client-facing page. */\nfunction buildFindabilitySection(c: ChecksResult): string {\n const accessBlock = !c.crawlerAccessMeasured\n ? `<p class=\"muted\">Crawler access: not measured — ${STAGE_FAILED_MESSAGE}.</p>`\n : c.crawlerAccess.blockedAi.length\n ? `<p><strong>Blocked AI crawlers:</strong> ${escapeHtml(\n c.crawlerAccess.blockedAi.map(describeAgent).join(\", \"),\n )}</p>`\n : // Says what was measured, which is robots.txt, and no more. \"Every AI\n // crawler can reach the site\" was a claim robots.txt cannot support:\n // a CDN's bot management enforces its own answer and can contradict the\n // file without the owner knowing. Confirmed on a live prospect: its\n // robots.txt blocked nothing relevant, and its CDN returned 403 to one\n // named AI crawler on every request while serving a browser and two\n // other crawlers normally. The report said they were fine.\n `<p>Nothing in your robots.txt blocks the AI crawlers we checked.</p>`;\n const classical =\n c.crawlerAccessMeasured && c.crawlerAccess.blockedClassical.length\n ? `<p><strong>Blocked search crawlers:</strong> ${escapeHtml(\n c.crawlerAccess.blockedClassical.join(\", \"),\n )}</p>`\n : \"\";\n const sitemapLine = c.sitemapMeasured\n ? `<li>sitemap.xml: ${c.sitemapPresent ? \"present\" : \"missing\"}</li>`\n : `<li>sitemap.xml: not measured — ${STAGE_FAILED_MESSAGE}.</li>`;\n // llms.txt is no longer listed as a finding. It used to sit here as\n // \"present\"/\"missing\" beside sitemap.xml, which put the two on equal footing\n // — and \"missing\" beside a checklist reads as a job to do. Search crawlers\n // demonstrably read a sitemap; no answer engine has documented reading\n // llms.txt to build an answer. The footnote below says that outright instead\n // of leaving a prospect to infer it, or to go and build one on our say-so.\n const llmsFootnote = `<p class=\"muted\"><strong>A note on llms.txt.</strong> You may have been told to\n add one. We check for it — this site ${\n !c.llmsTxtMeasured ? \"could not be checked\" : c.llmsTxtPresent ? \"has one\" : \"does not\"\n } — but we do not score it and we will not recommend it. It is a 2024 proposal that no\n answer engine has committed to reading, and adding one has no measured effect on whether you\n are cited. If that changes we will say so and score it then.</p>`;\n return `${accessBlock}${classical}\n <ul>\n ${sitemapLine}\n <li>Pages missing a meta description: ${c.meta.missingDescription} of ${c.meta.pageCount}</li>\n <li>Pages missing a canonical URL: ${c.meta.missingCanonical} of ${c.meta.pageCount}</li>\n <li>Pages missing share images/titles: ${c.meta.missingSocial} of ${c.meta.pageCount}</li>\n <li>Security headers missing: ${\n c.securityHeaders.missing.length ? escapeHtml(c.securityHeaders.missing.join(\", \")) : \"none\"\n }</li>\n ${\n c.meta.pagesWithoutExtract > 0\n ? `<li><strong>${c.meta.pagesWithoutExtract}</strong> additional page${\n c.meta.pagesWithoutExtract === 1 ? \"\" : \"s\"\n } produced no readable content at all and ${\n c.meta.pagesWithoutExtract === 1 ? \"is\" : \"are\"\n } not counted in the ${c.meta.pageCount} pages above.</li>`\n : \"\"\n }\n </ul>${llmsFootnote}`;\n}\n\n/** Lighthouse runs as its own independent pipeline stage (see pipeline.ts) —\n * it does not depend on `checks` succeeding. Rendering it off `result.checks`\n * would throw away real, measured performance/SEO/accessibility numbers\n * whenever an unrelated stage failed, so this reads `result.lighthouse`\n * directly and is rendered unconditionally, not nested inside the checks\n * degrade path. */\nfunction buildLighthouseBlock(lh: StageResult<LighthouseScores>): string {\n return lh.ok\n ? `<p class=\"muted\">Lighthouse — performance ${lh.data.performance ?? \"not measured\"},\n SEO ${lh.data.seo ?? \"not measured\"}, accessibility ${lh.data.accessibility ?? \"not measured\"}.</p>`\n : `<p class=\"muted\">Lighthouse not measured — ${STAGE_FAILED_MESSAGE}.</p>`;\n}\n\nfunction buildReadabilitySection(c: ChecksResult): string {\n const jsLine =\n c.jsDependence.avgMissing === null\n ? `<p class=\"muted\">JavaScript-dependence: not measured — no page produced a comparable raw/rendered pair.</p>`\n : `<p><strong>${Math.round(c.jsDependence.avgMissing * 100)}%</strong> of the words a visitor reads only appear after JavaScript runs.\n Most AI crawlers do not run JavaScript, so that share of your site is invisible to them.</p>`;\n return `${jsLine}\n <ul>\n <li>Structured data found: ${c.schema.typesFound.length ? escapeHtml(c.schema.typesFound.join(\", \")) : \"none\"}</li>\n <li>Expected structured data missing: ${\n c.schema.missingExpected.length ? escapeHtml(c.schema.missingExpected.join(\", \")) : \"none\"\n }</li>\n <li>Pages without a top-level heading: ${c.headings.pagesWithoutH1} of ${c.meta.pageCount}</li>\n ${\n c.meta.pagesWithoutExtract > 0\n ? `<li><strong>${c.meta.pagesWithoutExtract}</strong> page${\n c.meta.pagesWithoutExtract === 1 ? \"\" : \"s\"\n } produced no extract at all — excluded from every figure above, not counted as readable or unreadable.</li>`\n : \"\"\n }\n </ul>`;\n}\n\n/** The report's whole premise is claims the recipient can go check — so the\n * one quoted passage of evidence links to the page it came from (via\n * safeUrl; a null/non-http page renders as plain escaped text, no dead\n * \"#\" anchor). `answered`/`impact`/`effort` are escaped even though a zod\n * enum constrains them upstream — the renderer should not depend on\n * another file staying in sync to stay safe. */\n/** What the verdict column says. \"unknown\" is OUR gap — the model returned no\n * verdict for that question — so it must not be printed as a finding about the\n * site, and must not look like one either. Matches the web report's labels. */\nconst ANSWERED_LABEL: Record<string, string> = {\n yes: \"yes\",\n partial: \"partial\",\n no: \"no\",\n unknown: \"not measured\",\n};\n\nfunction buildAnswersSection(a: AnalyzeResult): string {\n const rows = a.buyerQuestions\n .map((q) => {\n const answered = escapeHtml(q.answered);\n const label = escapeHtml(ANSWERED_LABEL[q.answered] ?? q.answered);\n const evidenceText = q.evidence ? escapeHtml(q.evidence) : null;\n const evidenceHref = q.evidence && q.page ? safeUrl(q.page) : null;\n // An unmeasured question has no evidence for the same reason it has no\n // verdict: we never got one back. Printing \"no passage on the site\" there\n // reports our own missing measurement as a defect of theirs.\n const evidenceCell =\n q.answered === \"unknown\"\n ? '<span class=\"muted\">we did not get a verdict on this question</span>'\n : evidenceText === null\n ? '<span class=\"muted\">no passage on the site</span>'\n : evidenceHref && evidenceHref !== \"#\"\n ? `<a href=\"${escapeHtml(evidenceHref)}\" target=\"_blank\" rel=\"noopener noreferrer\">${evidenceText}</a>`\n : evidenceText;\n return `<tr>\n <td>${escapeHtml(q.question)}</td>\n <td><span class=\"tag ${answered}\">${label}</span></td>\n <td>${evidenceCell}</td>\n </tr>`;\n })\n .join(\"\");\n return `<p>${escapeHtml(a.narrative.answers)}</p>\n <table><tr><th>What buyers ask</th><th>Answered</th><th>Evidence</th></tr>${rows}</table>`;\n}\n\nexport function renderProspectReport(result: ProspectAuditResult): string {\n const host = hostnameOf(result.url);\n // `businessName` is the searchable NAME (possibly \"\"), never the\n // description. An empty/missing name is itself a finding — fall back to\n // the hostname rather than printing nothing, or worse, printing \"\" as if\n // it were a verified fact about the business.\n const name = result.businessName && result.businessName.trim() ? result.businessName : host;\n const date = formatIsoDate(result.generatedAt);\n\n const findabilitySection = stageBody(result.checks, buildFindabilitySection);\n const lighthouseBlock = buildLighthouseBlock(result.lighthouse);\n const readabilitySection = stageBody(result.checks, buildReadabilitySection);\n\n // A real business name was resolved and handed to the engines only when\n // `result.businessName` is non-empty — the same value the pipeline\n // actually queried with (see pipeline.ts). Used to gate the probes\n // section's \"even with the name handed to them\" phrasing (Fix 6).\n // Whether the engines were actually queried with the NAME, which is not the\n // same as whether a name existed. resolveBusinessName falls back to the bare\n // domain for anything it cannot use, and this line used to read the\n // un-resolved value — so a report could claim the engines were given the name\n // \"even with the name handed to them\" while the probes had in fact searched\n // for \"stlouisroofing.com\". Resolve it the same way probes.ts does.\n const businessNameUsed = Boolean(\n result.businessName &&\n result.businessName.trim() &&\n resolveBusinessName(result.businessName, result.url) === result.businessName.trim(),\n );\n\n const fixes = result.analyze.ok\n ? (() => {\n const sorted = [...result.analyze.data.fixes].sort(\n (x, y) => IMPACT_ORDER[x.impact] - IMPACT_ORDER[y.impact],\n );\n if (sorted.length === 0) {\n // A genuinely well-optimized site: a bare empty list reads as a\n // rendering bug, not a compliment. Say so directly, and keep this\n // section as a pitch — it's the one the salesperson is sending\n // the report to make.\n return `<p>Nothing here reads as urgent — the essentials are already in place.\n Reddoor can still help push this further: tightening structured data, monitoring\n how the answer engines describe you, and keeping pace as they change.</p>`;\n }\n return `<ol>${sorted\n .map((f) => {\n const impact = escapeHtml(f.impact);\n const effort = escapeHtml(f.effort);\n return `<li><strong>${escapeHtml(f.title)}</strong> — ${escapeHtml(f.why)}\n <span class=\"muted\">(${impact} impact, ${effort} effort)</span></li>`;\n })\n .join(\"\")}</ol>`;\n })()\n : skippableStageNote(\n result.analyze.error,\n ANALYZE_SKIPPED,\n \"Skipped — the checks stage failed, so there was nothing to build fixes from.\",\n );\n\n const probesSectionFinal = result.probes.ok\n ? buildProbesSection(result.probes.data, businessNameUsed)\n : skippableStageNote(\n result.probes.error,\n PROBES_SKIPPED,\n \"This audit did not run the AI-visibility probes.\",\n );\n\n const answersSectionFinal = result.analyze.ok\n ? buildAnswersSection(result.analyze.data)\n : skippableStageNote(\n result.analyze.error,\n ANALYZE_SKIPPED,\n \"Skipped — the checks stage failed, so there were no buyer questions to check.\",\n );\n\n const description = result.analyze.ok ? result.analyze.data.business : null;\n const narrative = result.analyze.ok\n ? `<p class=\"lede\">${escapeHtml(description ?? \"\")}</p>\n <p class=\"lede\">${escapeHtml(result.analyze.data.narrative.findability)}\n ${escapeHtml(result.analyze.data.narrative.readability)}</p>`\n : \"\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<meta name=\"robots\" content=\"noindex\" />\n<title>Can AI find ${escapeHtml(name)}? — Reddoor audit</title>\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n<link href=\"https://fonts.googleapis.com/css2?family=Besley:wght@400;600&display=swap\" rel=\"stylesheet\" />\n<style>${STYLES}</style>\n</head>\n<body>\n<div class=\"wrap\">\n <h1>Can AI and Google actually find ${escapeHtml(name)}?</h1>\n <p class=\"lede\"><a href=\"${escapeHtml(safeUrl(result.url))}\" target=\"_blank\" rel=\"noopener noreferrer\">${escapeHtml(result.url)}</a> · audited ${escapeHtml(date)} by Reddoor Creative</p>\n ${narrative}\n\n <p class=\"muted\">Each score below runs 0–100 — read it as a score, not a percentage.</p>\n <div class=\"scores\">\n ${scoreCard(\"Findability\", result.scores.findability, \"How easily AI and search crawlers can find and reach your site — crawl rules, sitemap, key metadata\")}\n ${scoreCard(\"Readability\", result.scores.readability, \"How much of your site's content those crawlers can actually read once they're in — most don't run JavaScript\")}\n ${scoreCard(\"Answers\", result.scores.answers, \"How many buyer questions your site's own content answers — separate from what the AI engines say back\")}\n </div>\n\n <h2>What the AI engines said about you</h2>\n ${probesSectionFinal}\n\n <h2>What the crawlers can reach</h2>\n ${findabilitySection}\n ${lighthouseBlock}\n\n <h2>What the crawlers can read</h2>\n ${readabilitySection}\n\n <h2>The questions your buyers ask</h2>\n ${answersSectionFinal}\n\n <h2>What to fix first</h2>\n ${fixes}\n\n <div class=\"cta\">\n <h2>Want this fixed?</h2>\n <p>Reddoor Creative rebuilds sites so answer engines can read, quote and recommend them.\n Reply to the email this link came from, or start at\n <a href=\"https://reddoorla.com/\">reddoorla.com</a>.</p>\n </div>\n</div>\n</body>\n</html>`;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  ingestSubmission
3
- } from "./chunk-JRHIFA5R.js";
3
+ } from "./chunk-QF6IQQFP.js";
4
4
  import "./chunk-NO74DXDP.js";
5
+ import "./chunk-I7NHLVVI.js";
5
6
  import "./chunk-2OP4JKIQ.js";
6
7
 
7
8
  // src/db/deadletter.ts
@@ -63,4 +64,4 @@ async function replayOne(db, deps, row) {
63
64
  export {
64
65
  replayDeadLetters
65
66
  };
66
- //# sourceMappingURL=replay-4S6ENYCH.js.map
67
+ //# sourceMappingURL=replay-PCSQOGCZ.js.map