failproofai 0.0.11-beta.8 → 0.0.11

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 (239) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/standalone/.next/build-manifest.json +6 -6
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  19. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  20. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  21. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  27. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  30. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route/app-paths-manifest.json +3 -0
  32. package/.next/standalone/.next/server/app/api/audit/invite/route/build-manifest.json +9 -0
  33. package/.next/standalone/.next/server/app/api/audit/invite/route/server-reference-manifest.json +4 -0
  34. package/.next/standalone/.next/server/app/api/audit/invite/route.js +7 -0
  35. package/.next/standalone/.next/server/app/api/audit/invite/route.js.map +5 -0
  36. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -0
  37. package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +3 -0
  38. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +2 -2
  43. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/logout/route.js +2 -2
  45. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +2 -2
  47. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/auth/status/route.js +2 -2
  49. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  52. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  54. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/index.html +1 -1
  57. package/.next/standalone/.next/server/app/index.rsc +15 -15
  58. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  59. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  60. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  61. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  62. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  63. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  64. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  65. package/.next/standalone/.next/server/app/page.js +2 -2
  66. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  69. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  70. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  71. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  74. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  75. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
  76. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +3 -3
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  84. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  85. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  86. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  87. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  88. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  89. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  90. package/.next/standalone/.next/server/chunks/[externals]__1_g_b3t._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dwpg-h._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lnenda._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13i_sva._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_audit_invite_route_actions_0-2n5sy.js +3 -0
  96. package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/node_modules_0ttxbz7._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/node_modules_1epycqa._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/node_modules_1wpdcgo._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +3 -3
  102. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1d4gx_t._.js → [root-of-the-server]__00uwqi6._.js} +2 -2
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0808sha._.js +4 -0
  107. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1cd25c7._.js → [root-of-the-server]__0e4-6d8._.js} +3 -3
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehe24g._.js +4 -0
  109. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-scthx._.js → [root-of-the-server]__0f62vu9._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +4 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l13qf2._.js → [root-of-the-server]__0k65l27._.js} +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__15i0juc._.js → [root-of-the-server]__0kjb_s4._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +4 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +4 -0
  115. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0989_dx._.js → [root-of-the-server]__1e-x7j4._.js} +2 -2
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +2 -2
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1uvfwgr._.js +4 -0
  119. package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/_0il3fl1._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +49 -21
  122. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  124. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0ihmbv4.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  127. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  128. package/.next/standalone/.next/server/pages/404.html +1 -1
  129. package/.next/standalone/.next/server/pages/500.html +1 -1
  130. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  131. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  132. package/.next/standalone/.next/static/chunks/{0d49wc5zca0u1.js → 02fywjt0by40a.js} +1 -1
  133. package/.next/standalone/.next/static/chunks/0f7d7hnbh4djs.js +1 -0
  134. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +1 -0
  135. package/.next/standalone/.next/static/chunks/0xdx2ehtbdoeg.js +1 -0
  136. package/.next/standalone/.next/static/chunks/{1nd1e30h8s_mc.js → 1-a5rvq67k7ed.js} +1 -1
  137. package/.next/standalone/.next/static/chunks/{1m2yj97j7f_km.js → 15csyj1_rf0-w.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/1o0xa47736gi9.css +2 -0
  139. package/.next/standalone/.next/static/chunks/24cv31x607n7k.js +1 -0
  140. package/.next/standalone/.next/static/chunks/{3w8d8k_dca5rp.js → 2h0dkzyy0vocp.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/2n_s8v1ae38_a.js +69 -0
  142. package/.next/standalone/.next/static/chunks/2y-jmvrjxz60x.js +6 -0
  143. package/.next/standalone/.next/static/chunks/{24z-bgbisv379.js → 3eik_d9qrvoft.js} +1 -1
  144. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +1 -0
  145. package/.next/standalone/.next/static/chunks/{0j969hb6nujdf.js → 3v61675vr6jav.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
  147. package/.next/standalone/.next/static/chunks/{turbopack-00qy7zfa7m--m.js → turbopack-3lrm4f20fz89b.js} +1 -1
  148. package/.next/standalone/app/api/audit/invite/route.ts +192 -0
  149. package/.next/standalone/app/api/audit/run/route.ts +35 -0
  150. package/.next/standalone/app/api/auth/login-request/route.ts +2 -2
  151. package/.next/standalone/app/api/auth/login-verify/route.ts +10 -2
  152. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +39 -63
  153. package/.next/standalone/app/audit/_components/audit-poster.tsx +326 -0
  154. package/.next/standalone/app/audit/_components/auth-dialog.tsx +23 -49
  155. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +336 -0
  156. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +187 -0
  157. package/.next/standalone/app/audit/_components/invite-dialog.tsx +230 -0
  158. package/.next/standalone/app/audit/_components/quirks-section.tsx +75 -0
  159. package/.next/standalone/app/audit/_components/share-templates.ts +63 -32
  160. package/.next/standalone/app/audit/_components/sigil.tsx +9 -66
  161. package/.next/standalone/app/audit/_components/strengths-section.tsx +20 -32
  162. package/.next/standalone/app/audit/audit-styles.css +778 -1786
  163. package/.next/standalone/app/components/sessions-list.tsx +77 -80
  164. package/.next/standalone/app/globals.css +241 -34
  165. package/.next/standalone/app/layout.tsx +1 -10
  166. package/.next/standalone/app/policies/hooks-client.tsx +45 -28
  167. package/.next/standalone/app/project/[name]/page.tsx +23 -79
  168. package/.next/standalone/app/projects/page.tsx +14 -23
  169. package/.next/standalone/assets/audit/poster-styles.css +1 -1
  170. package/.next/standalone/assets/audit/styles.css +11 -11
  171. package/.next/standalone/components/navbar.tsx +2 -37
  172. package/.next/standalone/components/reach-developers.tsx +10 -25
  173. package/.next/standalone/lib/auth/api-server-client.ts +28 -0
  174. package/.next/standalone/lib/client-telemetry.ts +4 -0
  175. package/.next/standalone/node_modules/@next/env/package.json +2 -2
  176. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  177. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  178. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  179. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  180. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  181. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  184. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  185. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  186. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  187. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  188. package/.next/standalone/node_modules/next/package.json +15 -15
  189. package/.next/standalone/package.json +19 -14
  190. package/.next/standalone/server.js +1 -1
  191. package/README.md +2 -2
  192. package/bin/failproofai.mjs +24 -5
  193. package/dist/cli.mjs +2328 -381
  194. package/lib/auth/api-server-client.ts +28 -0
  195. package/lib/client-telemetry.ts +4 -0
  196. package/package.json +19 -14
  197. package/scripts/launch.ts +30 -4
  198. package/scripts/postinstall.mjs +10 -1
  199. package/scripts/skew-log-filter.ts +46 -0
  200. package/scripts/validate-mdx.ts +139 -0
  201. package/src/audit/cli.ts +330 -0
  202. package/src/audit/open-browser.ts +69 -0
  203. package/src/audit/social-proof.ts +34 -0
  204. package/src/auth/cli.ts +16 -13
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07tgnzi._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oeun7z._.js +0 -3
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12pit4m._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13ra2jq._.js +0 -3
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1b9z5-i._.js +0 -3
  210. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ixjiy8._.js +0 -3
  211. package/.next/standalone/.next/server/chunks/_1-1804f._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00jkjmt._.js +0 -4
  213. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013du6r._.js +0 -4
  214. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e85wxv._.js +0 -4
  215. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gfxvb1._.js +0 -3
  216. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1hlrq6y._.js +0 -4
  217. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ihxdo5._.js +0 -4
  218. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1vvfde2._.js +0 -4
  219. package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_1k58rb_.js +0 -3
  220. package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_1n-0xws.js +0 -3
  221. package/.next/standalone/.next/static/chunks/09ueq8s1as8xs.css +0 -2
  222. package/.next/standalone/.next/static/chunks/0qassxjx1ef04.js +0 -1
  223. package/.next/standalone/.next/static/chunks/0qxb5czqxe-vu.js +0 -1
  224. package/.next/standalone/.next/static/chunks/1dh06515j265n.js +0 -41
  225. package/.next/standalone/.next/static/chunks/29gs4efgi3hme.js +0 -6
  226. package/.next/standalone/.next/static/chunks/2mni177pnjx6u.js +0 -1
  227. package/.next/standalone/.next/static/chunks/2so39wg7mjbi7.js +0 -1
  228. package/.next/standalone/.next/static/chunks/3gti1qdk5epqn.js +0 -1
  229. package/.next/standalone/.next/static/chunks/3wycox197ouus.css +0 -1
  230. package/.next/standalone/app/audit/_components/findings-section.tsx +0 -135
  231. package/.next/standalone/app/audit/_components/identity-section.tsx +0 -126
  232. package/.next/standalone/app/audit/_components/policies-section.tsx +0 -194
  233. package/.next/standalone/app/audit/_components/return-section.tsx +0 -416
  234. package/.next/standalone/app/audit/_components/score-section.tsx +0 -179
  235. package/.next/standalone/app/audit/_components/share-dock.tsx +0 -265
  236. package/.next/standalone/app/audit/_components/show-off-cta.tsx +0 -135
  237. /package/.next/standalone/.next/static/{CVv2A0hMd24t0c0x3V-W_ → P_MIRSeoE296wkbE-Icin}/_buildManifest.js +0 -0
  238. /package/.next/standalone/.next/static/{CVv2A0hMd24t0c0x3V-W_ → P_MIRSeoE296wkbE-Icin}/_clientMiddlewareManifest.js +0 -0
  239. /package/.next/standalone/.next/static/{CVv2A0hMd24t0c0x3V-W_ → P_MIRSeoE296wkbE-Icin}/_ssgManifest.js +0 -0
@@ -1,416 +0,0 @@
1
- "use client";
2
-
3
- /**
4
- * Section 06 — NEXT AUDIT / "come back better." Re-audit loop CTA.
5
- *
6
- * Behavior matrix:
7
- * - unknown (probe in flight) → buttons disabled
8
- * - anon (no session) → [ set a reminder ] opens AuthDialog,
9
- * on success we flip to the authed panel
10
- * below and persist the 7-day reminder.
11
- * - authed (any) → consolidated status panel: "signed in as
12
- * …" + either the persisted "next audit in
13
- * X days" line OR a "no reminder set yet"
14
- * line with an inline [ set a reminder ]
15
- * button. The reminder persists across
16
- * reloads via ~/.failproofai/next-audit.json
17
- * — same as the CLI's auth.json.
18
- *
19
- * Also exposes [ re-audit now ] next to [ install policies ] so the user
20
- * can trigger a fresh scan inline without leaving the page. The button
21
- * fires POST /api/audit/run (same backend the empty-state CTA uses).
22
- */
23
- import React, { useCallback, useEffect, useRef, useState } from "react";
24
- import type { AuditResult } from "@/src/audit/types";
25
- import { usePostHog } from "@/contexts/PostHogContext";
26
- import { isAbortError } from "@/lib/fetch-with-timeout";
27
- import { AuthDialog, type AuthedUser } from "./auth-dialog";
28
-
29
- interface Props {
30
- result: AuditResult;
31
- /** Lifted by AuditDashboard so the bottom button and the top-of-page
32
- * re-audit bar share state — only one rerun at a time. */
33
- isRunning: boolean;
34
- /** Lifted handler. Drives the sticky progress strip + soft refresh. */
35
- onRerun: () => void;
36
- }
37
-
38
- const BULK_INSTALL_CMD = "failproofai policies --install";
39
- const DEFAULT_REMINDER_DAYS = 7;
40
-
41
- type AuthStatus =
42
- | { kind: "unknown" }
43
- | { kind: "anon" }
44
- | { kind: "authed"; user: { id: string; email: string } };
45
-
46
- interface Reminder {
47
- next_audit_at: number; // unix seconds
48
- user_email: string;
49
- set_at: number;
50
- }
51
-
52
- function daysUntil(unixSecs: number): number {
53
- const nowSecs = Math.floor(Date.now() / 1000);
54
- return Math.max(0, Math.ceil((unixSecs - nowSecs) / 86400));
55
- }
56
-
57
- function formatNextAudit(unixSecs: number): string {
58
- const d = new Date(unixSecs * 1000);
59
- return d.toLocaleDateString(undefined, {
60
- weekday: "short",
61
- month: "short",
62
- day: "numeric",
63
- });
64
- }
65
-
66
- export function ReturnSection({ result, isRunning, onRerun }: Props) {
67
- const { capture } = usePostHog();
68
- const hasUnenabled = result.results.some(
69
- (r) => r.source === "builtin" && !r.enabledInConfig && r.hits > 0,
70
- );
71
-
72
- const [copied, setCopied] = useState(false);
73
- const [authStatus, setAuthStatus] = useState<AuthStatus>({ kind: "unknown" });
74
- const [reminder, setReminder] = useState<Reminder | null>(null);
75
- const [dialogOpen, setDialogOpen] = useState(false);
76
- const [reminderBusy, setReminderBusy] = useState(false);
77
- const ctaShownRef = useRef(false);
78
- /** Throttle gate for focus/visibility-triggered refreshStatus calls —
79
- * rapid alt-tabbing would otherwise hit /api/auth/status (two disk
80
- * reads each) once per event. */
81
- const lastRefreshAtRef = useRef(0);
82
-
83
- // Probe /api/auth/status on mount — also returns the persisted reminder
84
- // when one exists and belongs to the active session.
85
- const refreshStatus = useCallback(async () => {
86
- lastRefreshAtRef.current = Date.now();
87
- try {
88
- const res = await fetch("/api/auth/status", { cache: "no-store" });
89
- const body = (await res.json()) as {
90
- authenticated?: boolean;
91
- user?: { id: string; email: string };
92
- reminder?: Reminder | null;
93
- };
94
- if (body.authenticated && body.user) {
95
- setAuthStatus({ kind: "authed", user: body.user });
96
- setReminder(body.reminder ?? null);
97
- } else {
98
- setAuthStatus({ kind: "anon" });
99
- setReminder(null);
100
- }
101
- } catch {
102
- setAuthStatus({ kind: "anon" });
103
- setReminder(null);
104
- }
105
- }, []);
106
-
107
- useEffect(() => {
108
- void refreshStatus();
109
- // Re-probe whenever the tab regains focus or visibility — picks up
110
- // CLI `failproofai auth login` / `logout` and api-server restarts
111
- // without the user having to hit reload manually. Throttled to 5s so
112
- // rapid alt-tabbing (a focus + a visibility event for one switch)
113
- // doesn't double-fire.
114
- const REFRESH_MIN_INTERVAL_MS = 5_000;
115
- const maybeRefresh = () => {
116
- if (Date.now() - lastRefreshAtRef.current < REFRESH_MIN_INTERVAL_MS) return;
117
- void refreshStatus();
118
- };
119
- const onFocus = () => maybeRefresh();
120
- const onVisibility = () => {
121
- if (document.visibilityState === "visible") maybeRefresh();
122
- };
123
- window.addEventListener("focus", onFocus);
124
- document.addEventListener("visibilitychange", onVisibility);
125
- return () => {
126
- window.removeEventListener("focus", onFocus);
127
- document.removeEventListener("visibilitychange", onVisibility);
128
- };
129
- }, [refreshStatus]);
130
-
131
- // Fire-once "user saw the reminder CTA" event so we can compute the
132
- // funnel shown → clicked → auth → saved. We wait until the auth probe
133
- // finishes — before that the buttons are disabled and the CTA isn't
134
- // really "shown".
135
- useEffect(() => {
136
- if (ctaShownRef.current) return;
137
- if (authStatus.kind === "unknown") return;
138
- ctaShownRef.current = true;
139
- capture("audit_reminder_cta_shown", {
140
- auth_state: authStatus.kind,
141
- has_existing_reminder: reminder !== null,
142
- source: "return_section",
143
- });
144
- }, [authStatus, capture, reminder]);
145
-
146
- const persistReminder = useCallback(async (): Promise<Reminder | null> => {
147
- // 10s ceiling so a hung route can't permanently disable the CTA.
148
- const controller = new AbortController();
149
- const timer = setTimeout(() => controller.abort(), 10_000);
150
- try {
151
- setReminderBusy(true);
152
- const res = await fetch("/api/auth/reminder", {
153
- method: "POST",
154
- headers: { "content-type": "application/json" },
155
- body: JSON.stringify({ in_days: DEFAULT_REMINDER_DAYS }),
156
- signal: controller.signal,
157
- });
158
- if (!res.ok) {
159
- // A 401 here means our local "authed" view of the world is out of
160
- // date — the server-side session was revoked or expired between the
161
- // status probe and this write. Flip back to anon so the user can
162
- // re-authenticate instead of leaving them on a panel whose actions
163
- // silently no-op.
164
- if (res.status === 401) {
165
- setAuthStatus({ kind: "anon" });
166
- setReminder(null);
167
- }
168
- capture("audit_reminder_saved", {
169
- status: `http_${res.status}`,
170
- source: "return_section",
171
- });
172
- return null;
173
- }
174
- const body = (await res.json()) as { reminder?: Reminder };
175
- capture("audit_reminder_saved", {
176
- status: body.reminder ? "success" : "empty",
177
- source: "return_section",
178
- });
179
- return body.reminder ?? null;
180
- } catch (err) {
181
- const kind = isAbortError(err) ? "timeout" : "error";
182
- capture("audit_reminder_saved", {
183
- status: kind,
184
- source: "return_section",
185
- });
186
- return null;
187
- } finally {
188
- clearTimeout(timer);
189
- setReminderBusy(false);
190
- }
191
- }, [capture]);
192
-
193
- const handleInstall = async () => {
194
- capture("audit_install_policies_clicked", {
195
- source: "return_section",
196
- });
197
- try {
198
- await navigator.clipboard.writeText(BULK_INSTALL_CMD);
199
- setCopied(true);
200
- capture("audit_copy_clicked", {
201
- source: "return_section_install_policies",
202
- item_type: "bulk_install_command",
203
- });
204
- setTimeout(() => setCopied(false), 1500);
205
- } catch {
206
- /* ignore */
207
- }
208
- };
209
-
210
- const handleSetReminder = useCallback(async () => {
211
- if (authStatus.kind === "unknown") return;
212
- capture("audit_reminder_cta_clicked", {
213
- auth_state: authStatus.kind,
214
- has_existing_reminder: reminder !== null,
215
- source: "return_section",
216
- });
217
- if (authStatus.kind === "authed") {
218
- const next = await persistReminder();
219
- if (next) setReminder(next);
220
- return;
221
- }
222
- setDialogOpen(true);
223
- }, [authStatus, capture, persistReminder, reminder]);
224
-
225
- const handleAuthed = useCallback(
226
- async (user: AuthedUser) => {
227
- setAuthStatus({ kind: "authed", user });
228
- capture("audit_auth_completed", {
229
- source: "return_section",
230
- });
231
- // The dialog opened because the user wanted a reminder → persist
232
- // immediately, no second click required.
233
- const next = await persistReminder();
234
- if (next) setReminder(next);
235
- },
236
- [capture, persistReminder],
237
- );
238
-
239
- // Re-audit is now lifted into AuditDashboard so the top-of-page bar and
240
- // this bottom button share state — concurrent clicks are impossible and
241
- // success path soft-refreshes the report rather than reloading the page.
242
- // PostHog `source: "return_section"` capture happens inside the lifted
243
- // handler via the `source` argument it receives.
244
- const handleRerun = useCallback(() => {
245
- if (isRunning) return;
246
- onRerun();
247
- }, [isRunning, onRerun]);
248
-
249
- const authed = authStatus.kind === "authed";
250
- const hasReminder = authed && reminder !== null;
251
- const days = reminder ? daysUntil(reminder.next_audit_at) : 0;
252
- const authedEmail =
253
- authStatus.kind === "authed" ? authStatus.user.email : "";
254
-
255
- return (
256
- <section className="section" data-screen-label="06 Next audit">
257
- <div className="section-mast">
258
- <div className="section-label">
259
- <span className="glyph">━━</span> next audit{" "}
260
- <span style={{ color: "var(--dim)" }}>·</span> improvement
261
- </div>
262
- <div className="section-meta">
263
- <span className="g">●</span> recommended in 7d
264
- </div>
265
- </div>
266
- <h2 className="section-h">come back better.</h2>
267
-
268
- <div className="return-hook">
269
- <div className="label">━━ the loop</div>
270
- <h3>re-audit in 7 days.</h3>
271
- <p>
272
- after the prescribed policies have been live for a week, we&apos;ll show
273
- your before/after score and which detectors went quiet.
274
- </p>
275
- <p style={{ marginTop: 16, color: "var(--dim)" }}>
276
- most agents move from C to B in one session. some make it in a day.
277
- </p>
278
-
279
- {/* Once authed, the section stays in the consolidated status panel —
280
- with the reminder line if one is set, or a "no reminder yet" line
281
- + inline [ set a reminder ] button otherwise. The anonymous CTA
282
- layout only shows for genuinely-unauthed sessions. The action
283
- buttons ([ set a reminder ] / [ re-audit now ] / [ install
284
- policies ]) are identical in both branches — extracted into
285
- <ReturnActions> below to keep them in sync. */}
286
- {authed ? (
287
- <div className="return-status">
288
- {hasReminder && reminder ? (
289
- <div className="rs-row rs-row-primary">
290
- <span className="rs-dot rs-dot-pink" aria-hidden="true" />
291
- <span>
292
- next audit set for{" "}
293
- <span className="rs-strong">{formatNextAudit(reminder.next_audit_at)}</span>
294
- {" "}<span style={{ color: "var(--dim)" }}>·</span>{" "}
295
- <span className="rs-strong">in {days} day{days === 1 ? "" : "s"}</span>
296
- </span>
297
- </div>
298
- ) : (
299
- <div className="rs-row rs-row-primary">
300
- <span className="rs-dot rs-dot-pink" aria-hidden="true" />
301
- <span>
302
- <span className="rs-strong">no reminder set yet</span>
303
- {" "}<span style={{ color: "var(--dim)" }}>·</span>{" "}
304
- recommended in {DEFAULT_REMINDER_DAYS} days
305
- </span>
306
- </div>
307
- )}
308
- <div className="rs-row">
309
- <span className="rs-dot rs-dot-green" aria-hidden="true" />
310
- <span>
311
- signed in as <span className="rs-email">{authedEmail}</span>
312
- </span>
313
- </div>
314
- <ReturnActions
315
- style={{ marginTop: 18 }}
316
- showSetReminder={!hasReminder}
317
- setReminderDisabled={reminderBusy}
318
- reminderBusy={reminderBusy}
319
- rerunBusy={isRunning}
320
- hasUnenabled={hasUnenabled}
321
- copied={copied}
322
- onSetReminder={handleSetReminder}
323
- onRerun={handleRerun}
324
- onInstall={handleInstall}
325
- />
326
- </div>
327
- ) : (
328
- <ReturnActions
329
- showSetReminder
330
- setReminderDisabled={authStatus.kind === "unknown" || reminderBusy}
331
- reminderBusy={reminderBusy}
332
- rerunBusy={isRunning}
333
- hasUnenabled={hasUnenabled}
334
- copied={copied}
335
- onSetReminder={handleSetReminder}
336
- onRerun={handleRerun}
337
- onInstall={handleInstall}
338
- />
339
- )}
340
- </div>
341
-
342
- <AuthDialog
343
- open={dialogOpen}
344
- source="return_section"
345
- headline="oops — you are unknown."
346
- reason="verify yourself to get the re-audit reminder."
347
- onClose={() => setDialogOpen(false)}
348
- onAuthed={(u) => {
349
- setDialogOpen(false);
350
- void handleAuthed(u);
351
- }}
352
- />
353
- </section>
354
- );
355
- }
356
-
357
- interface ReturnActionsProps {
358
- /** Whether to render the `[ set a reminder ]` button. False when the
359
- * user already has a reminder set (the authed-with-reminder case
360
- * shows the panel meta instead). */
361
- showSetReminder: boolean;
362
- setReminderDisabled: boolean;
363
- reminderBusy: boolean;
364
- rerunBusy: boolean;
365
- hasUnenabled: boolean;
366
- copied: boolean;
367
- onSetReminder: () => void;
368
- onRerun: () => void;
369
- onInstall: () => void;
370
- style?: React.CSSProperties;
371
- }
372
-
373
- /** Action button strip shared by the authed and anon branches. Extracted
374
- * to keep the three buttons in sync — the prior inline copies had
375
- * already drifted on margin-top styling. */
376
- function ReturnActions(props: ReturnActionsProps): React.ReactElement {
377
- const {
378
- showSetReminder,
379
- setReminderDisabled,
380
- reminderBusy,
381
- rerunBusy,
382
- hasUnenabled,
383
- copied,
384
- onSetReminder,
385
- onRerun,
386
- onInstall,
387
- style,
388
- } = props;
389
- return (
390
- <div className="return-actions" style={style}>
391
- {showSetReminder && (
392
- <button
393
- type="button"
394
- className="share-btn"
395
- onClick={onSetReminder}
396
- disabled={setReminderDisabled}
397
- >
398
- {reminderBusy ? "[ saving… ]" : "[ set a reminder ]"}
399
- </button>
400
- )}
401
- <button
402
- type="button"
403
- className="share-btn alt"
404
- onClick={onRerun}
405
- disabled={rerunBusy}
406
- >
407
- {rerunBusy ? "[ scanning… ]" : "[ re-audit now ]"}
408
- </button>
409
- {hasUnenabled && (
410
- <button type="button" className="share-btn alt" onClick={onInstall}>
411
- {copied ? "[ ✓ copied — paste in your shell ]" : "[ install policies ]"}
412
- </button>
413
- )}
414
- </div>
415
- );
416
- }
@@ -1,179 +0,0 @@
1
- "use client";
2
-
3
- /**
4
- * Section 03 — SCORE CARD.
5
- *
6
- * Left column only: YOUR AUDIT SCORE (big number, tier badge, progress
7
- * bar, 3 stat boxes, prescribed-policies chip strip).
8
- *
9
- * Share actions have moved to IdentitySection below the archetype sigil.
10
- */
11
- import React, { useMemo } from "react";
12
- import type { AuditResult } from "@/src/audit/types";
13
- import { ARCHETYPES, type ArchetypeKey } from "@/src/audit/archetypes";
14
- import { type Grade } from "@/src/audit/scoring";
15
-
16
- const GRADE_THRESHOLDS: { g: Grade; t: number }[] = [
17
- { g: "S", t: 90 }, { g: "A", t: 80 }, { g: "B", t: 71 },
18
- { g: "C", t: 55 }, { g: "D", t: 40 },
19
- ];
20
-
21
- function pointsToNextFor(score: number): { next: Grade; delta: number } {
22
- for (const { g, t } of GRADE_THRESHOLDS) {
23
- if (score < t) return { next: g, delta: t - score };
24
- }
25
- return { next: "S", delta: 0 };
26
- }
27
-
28
- interface Props {
29
- result: AuditResult;
30
- score: number;
31
- grade: Grade;
32
- archetypeKey: ArchetypeKey;
33
- /** Display name shown in the card header. */
34
- project: string;
35
- }
36
-
37
- export function ScoreSection({ result, score, grade, archetypeKey, project }: Props) {
38
- const archetype = ARCHETYPES[archetypeKey];
39
- // Cheap scan of 5 thresholds — plain computation. React Compiler memoizes
40
- // the surrounding render anyway, and `useMemo` here tripped the
41
- // preserve-manual-memoization rule.
42
- const pointsToNext = pointsToNextFor(score);
43
-
44
- /** Slipping-through builtin policies (the same heuristic ReturnSection uses
45
- * for its [install policies] CTA). Used as the "policies missing" stat. */
46
- const missing = useMemo(
47
- () => result.results.filter((r) => r.source === "builtin" && !r.enabledInConfig && r.hits > 0).length,
48
- [result],
49
- );
50
-
51
- /** Rough "days to fix" — capped 1..14. One day per slipping policy, with a
52
- * baseline of 3d on any non-S grade. */
53
- const daysToFix = useMemo(() => {
54
- if (grade === "S" || missing === 0) return 0;
55
- return Math.max(1, Math.min(14, missing + 1));
56
- }, [grade, missing]);
57
-
58
- /** % of 0–100 bar to fill — simply score/100. */
59
- const progressPct = score;
60
-
61
- /** Top-N slipping policies → chip strip on the left card. Capped at 6. */
62
- const policyChips = useMemo(() => {
63
- const slipping = result.results
64
- .filter((r) => r.source === "builtin" && !r.enabledInConfig && r.hits > 0)
65
- .sort((a, b) => b.hits - a.hits)
66
- .slice(0, 6)
67
- .map((r) => ({ name: shortPolicyLabel(r.name), missing: true as const }));
68
- const enabled = result.results
69
- .filter((r) => r.source === "builtin" && r.enabledInConfig)
70
- .slice(0, Math.max(0, 6 - slipping.length))
71
- .map((r) => ({ name: shortPolicyLabel(r.name), missing: false as const }));
72
- return [...slipping, ...enabled];
73
- }, [result]);
74
-
75
- return (
76
- <section className="section" data-screen-label="03 Score">
77
- <div className="section-mast">
78
- <div className="section-label">
79
- <span className="glyph">━━</span> score
80
- <span style={{ color: "var(--accent-green)", marginLeft: 10, letterSpacing: "0.04em", textTransform: "none", fontSize: 11 }}>· SEE HOW YOUR AGENT IS PERFORMING</span>
81
- </div>
82
- </div>
83
- <h2 className="section-h">your audit score.</h2>
84
-
85
- <div className="panel score-share-card">
86
- <div className="score-card-header">
87
- <span style={{ color: "var(--ink)" }}>{project}</span>
88
- <span style={{ color: "var(--dim)" }}> · </span>
89
- <span style={{ color: "var(--accent-pink)" }}>{archetype.name.toLowerCase()}</span>
90
- </div>
91
-
92
- <div className="ss-score-row">
93
- <span className={"ss-score g-" + grade}>{score}</span>
94
- <span className="ss-score-of">/100</span>
95
- </div>
96
-
97
- <div className="ss-tier-row">
98
- <span className={"ss-tier-badge g-" + grade}>{grade} tier</span>
99
- <span className="ss-arch">{archetype.name.toLowerCase()}</span>
100
- </div>
101
-
102
- <div className="ss-progress-label">
103
- <span style={{ color: "var(--dim)" }}>score</span>
104
- {pointsToNext.delta > 0 ? (
105
- <span style={{ color: "var(--accent-pink)" }}>
106
- +{pointsToNext.delta} pts to {pointsToNext.next} tier
107
- </span>
108
- ) : (
109
- <span style={{ color: "var(--accent-green)" }}>top tier ✓</span>
110
- )}
111
- </div>
112
- <div className="ss-progress-track">
113
- {[40, 55, 71, 80, 90].map((t) => (
114
- <div key={t} className="ss-progress-tick" style={{ left: `${t}%` }} />
115
- ))}
116
- <div
117
- className="ss-progress-fill audit-bar-fill"
118
- style={{ ["--bar-width" as string]: `${progressPct}%` }}
119
- />
120
- </div>
121
- <div className="ss-grade-stops">
122
- {(["D", "C", "B", "A", "S"] as Grade[]).map((g, i) => {
123
- const pos = [40, 55, 71, 80, 90][i];
124
- return (
125
- <span
126
- key={g}
127
- className={"ss-grade-stop" + (grade === g ? " active" : "")}
128
- style={{ left: `${pos}%` }}
129
- >{g}</span>
130
- );
131
- })}
132
- </div>
133
-
134
- <div className="ss-stats">
135
- <div className="ss-stat">
136
- <div className="ss-stat-n" style={{ color: "var(--amber)" }}>{missing}</div>
137
- <div className="ss-stat-l">policies<br />missing</div>
138
- </div>
139
- <div className="ss-stat">
140
- <div className="ss-stat-n" style={{ color: "var(--accent-pink)" }}>
141
- +{pointsToNext.delta}
142
- </div>
143
- <div className="ss-stat-l">pts to<br />{pointsToNext.next} tier</div>
144
- </div>
145
- <div className="ss-stat">
146
- <div className="ss-stat-n" style={{ color: "var(--accent-green)" }}>
147
- {daysToFix === 0 ? "—" : `~${daysToFix}d`}
148
- </div>
149
- <div className="ss-stat-l">est.<br />to fix</div>
150
- </div>
151
- </div>
152
-
153
- {policyChips.length > 0 && (
154
- <>
155
- <div className="ss-policy-label">policy status</div>
156
- <div className="ss-policy-chips">
157
- {policyChips.map((p, i) => (
158
- <span
159
- key={i}
160
- className={"ss-chip" + (p.missing ? " missing" : " enabled")}
161
- >
162
- <span className="dot" aria-hidden="true" />
163
- {p.name}
164
- </span>
165
- ))}
166
- </div>
167
- </>
168
- )}
169
- </div>
170
- </section>
171
- );
172
- }
173
-
174
- /** Drop the "failproofai/" namespace prefix builtin policies carry so chips
175
- * stay compact (`block-sudo` reads better than `failproofai/block-sudo`). */
176
- function shortPolicyLabel(name: string): string {
177
- const slash = name.indexOf("/");
178
- return slash >= 0 ? name.slice(slash + 1) : name;
179
- }