failproofai 1.0.0 → 1.0.1-beta.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 (238) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +0 -1
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +5 -5
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +0 -6
  7. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page.js +4 -3
  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 +6 -6
  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/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js +4 -3
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/standalone/.next/server/app/api/audit/invite/route.js +2 -2
  26. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -3
  28. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/status/route.js +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +4 -3
  32. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  36. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -3
  38. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  41. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  42. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/index.html +1 -1
  45. package/.next/standalone/.next/server/app/index.rsc +15 -15
  46. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  47. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  48. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  49. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/page.js +4 -3
  51. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  54. package/.next/standalone/.next/server/app/policies/page.js +6 -5
  55. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/page.js +5 -4
  59. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  67. package/.next/standalone/.next/server/app/projects/page.js +5 -4
  68. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  71. package/.next/standalone/.next/server/app/settings/page.js +6 -4
  72. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  73. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  74. package/.next/standalone/.next/server/app-paths-manifest.json +0 -1
  75. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nne229._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ny9q5d._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hstmft._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p8poxd._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0jx6cp6._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0f-x2b0._.js} +1 -1
  90. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1f290jp._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0hwvl7e._.js} +1 -1
  91. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1g2ji1t._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1ctjdzl._.js} +1 -1
  92. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03eykvd._.js +5 -0
  95. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07175vb._.js → [root-of-the-server]__07oh8b5._.js} +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cxe_2_._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g2rg69._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0m98kzy._.js +4 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0sjzdjg._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0v5_91z._.js +4 -0
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-a3135._.js +4 -0
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1hvwiw8._.js → [root-of-the-server]__11z_299._.js} +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apc90t._.js → [root-of-the-server]__15c_z97._.js} +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1wmno1q._.js → [root-of-the-server]__17d_ffl._.js} +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +4 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1e5g_ur._.js +4 -0
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1hmqefd._.js +4 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +6 -0
  113. package/.next/standalone/.next/server/chunks/ssr/_04j57fb._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/_05enntk._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/ssr/_0hom1s1._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/_1nq-35m._.js +23 -0
  118. package/.next/standalone/.next/server/chunks/ssr/_1q7j05x._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  121. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +3 -0
  128. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  129. package/.next/standalone/.next/server/pages/404.html +1 -1
  130. package/.next/standalone/.next/server/pages/500.html +1 -1
  131. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  132. package/.next/standalone/.next/server/server-reference-manifest.json +15 -15
  133. package/.next/standalone/.next/static/chunks/00q6cfzuzpc85.js +1 -0
  134. package/.next/standalone/.next/static/chunks/{0zm3_8jpqitxy.js → 054eg7y_cjyf9.js} +22 -22
  135. package/.next/standalone/.next/static/chunks/{2ouq44jv_0x04.js → 0mjufa_0xxz75.js} +1 -1
  136. package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
  137. package/.next/standalone/.next/static/chunks/{0zi8zjav5a4rq.js → 1-oz4co31nvc5.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/13--2yvqek_s1.js +1 -0
  139. package/.next/standalone/.next/static/chunks/136ldjmzxhush.js +1 -0
  140. package/.next/standalone/.next/static/chunks/18fqy3doo2ial.js +6 -0
  141. package/.next/standalone/.next/static/chunks/1eyl7fzkmnkf_.js +1 -0
  142. package/.next/standalone/.next/static/chunks/{31k3yxpy57_fy.js → 1o5sxp_mdaoci.js} +1 -1
  143. package/.next/standalone/.next/static/chunks/29-iqnp9v_axt.css +1 -0
  144. package/.next/standalone/.next/static/chunks/{2676v-lyzgo91.js → 2es4z1ei90vsv.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
  146. package/.next/standalone/.next/static/chunks/3peix2vxzj7eh.js +1 -0
  147. package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
  148. package/.next/standalone/.next/static/chunks/{0km-jn9o5xyqs.css → 3wq9x0c-7ld5d.css} +1 -1
  149. package/.next/standalone/app/actions/get-scheduled-audit.ts +65 -6
  150. package/.next/standalone/app/actions/update-scheduled-audit.ts +90 -17
  151. package/.next/standalone/app/api/auth/status/route.ts +7 -17
  152. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +2 -6
  153. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +101 -281
  154. package/.next/standalone/app/audit/_components/empty-state.tsx +2 -2
  155. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  156. package/.next/standalone/app/audit/audit-styles.css +150 -26
  157. package/.next/standalone/app/globals.css +39 -1
  158. package/.next/standalone/app/settings/page.tsx +38 -21
  159. package/.next/standalone/app/settings/settings-client.tsx +583 -392
  160. package/.next/standalone/app/settings/settings.css +354 -0
  161. package/.next/standalone/components/navbar.tsx +33 -4
  162. package/.next/standalone/lib/auth/api-server-client.ts +51 -29
  163. package/.next/standalone/lib/auth/auth-store.ts +44 -59
  164. package/.next/standalone/package.json +12 -10
  165. package/.next/standalone/server.js +1 -1
  166. package/bin/failproofai.mjs +6 -0
  167. package/bin/failproofaid-shim.mjs +0 -0
  168. package/dist/cli.mjs +1889 -426
  169. package/dist/worker.mjs +27 -5
  170. package/lib/auth/api-server-client.ts +51 -29
  171. package/lib/auth/auth-store.ts +44 -59
  172. package/package.json +12 -10
  173. package/scripts/docs-audit.ts +493 -0
  174. package/scripts/translate-docs/cli.ts +16 -2
  175. package/scripts/translate-docs/mdx-translator.ts +6 -1
  176. package/scripts/translate-docs/readme-translator.ts +4 -2
  177. package/src/audit/cli-login.ts +330 -0
  178. package/src/audit/cli.ts +124 -6
  179. package/src/audit/dashboard-cache.ts +44 -8
  180. package/src/audit/harm-report.ts +260 -0
  181. package/src/audit/machine-store.ts +120 -0
  182. package/src/audit/redact-example.ts +312 -0
  183. package/src/audit/report-harm.ts +167 -0
  184. package/src/audit/schedule-cli.ts +352 -0
  185. package/src/hooks/builtin-policies.ts +26 -0
  186. package/src/hooks/daemon-service.ts +76 -0
  187. package/src/hooks/fp-config.ts +96 -5
  188. package/src/hooks/fp-home.ts +118 -8
  189. package/src/hooks/fp-reset.ts +79 -9
  190. package/src/hooks/migrations.ts +241 -4
  191. package/src/hooks/tui.ts +61 -3
  192. package/.next/standalone/.next/server/app/api/auth/reminder/route/app-paths-manifest.json +0 -3
  193. package/.next/standalone/.next/server/app/api/auth/reminder/route/build-manifest.json +0 -12
  194. package/.next/standalone/.next/server/app/api/auth/reminder/route/server-reference-manifest.json +0 -4
  195. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +0 -9
  196. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.map +0 -5
  197. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +0 -1
  198. package/.next/standalone/.next/server/app/api/auth/reminder/route_client-reference-manifest.js +0 -3
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-xggru._.js +0 -3
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00ds05_._.js +0 -3
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +0 -3
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1dxagv_._.js +0 -3
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fgqj1_._.js +0 -3
  204. package/.next/standalone/.next/server/chunks/_084toou._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/_0g4t8c9._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/_0km521a._.js +0 -3
  207. package/.next/standalone/.next/server/chunks/_0t8kefc._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/_16dwkfn._.js +0 -3
  209. package/.next/standalone/.next/server/chunks/_1phm9ui._.js +0 -3
  210. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_auth_reminder_route_actions_1kjgxf8.js +0 -3
  211. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dk8r_l._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kviq2m._.js +0 -3
  213. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ogyhu0._.js +0 -3
  214. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-rfu32._.js +0 -5
  215. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mmefsr._.js +0 -5
  216. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1t62w37._.js +0 -3
  217. package/.next/standalone/.next/server/chunks/ssr/_032wo7w._.js +0 -3
  218. package/.next/standalone/.next/server/chunks/ssr/_08840em._.js +0 -3
  219. package/.next/standalone/.next/server/chunks/ssr/_0gl2ujp._.js +0 -3
  220. package/.next/standalone/.next/server/chunks/ssr/_0j0ggkc._.js +0 -3
  221. package/.next/standalone/.next/server/chunks/ssr/_0kbv5-q._.js +0 -3
  222. package/.next/standalone/.next/server/chunks/ssr/_0r1jgvp._.js +0 -3
  223. package/.next/standalone/.next/server/chunks/ssr/_0t5xq1b._.js +0 -3
  224. package/.next/standalone/.next/server/chunks/ssr/_0w_be0g._.js +0 -3
  225. package/.next/standalone/.next/server/chunks/ssr/_15gba46._.js +0 -5
  226. package/.next/standalone/.next/server/chunks/ssr/_1qyhb9r._.js +0 -23
  227. package/.next/standalone/.next/server/chunks/ssr/_1vel2ey._.js +0 -3
  228. package/.next/standalone/.next/static/chunks/0wd503tv801gy.js +0 -1
  229. package/.next/standalone/.next/static/chunks/1hfocb0sdl85c.js +0 -1
  230. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +0 -1
  231. package/.next/standalone/.next/static/chunks/2kopjq_or743g.js +0 -6
  232. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +0 -2
  233. package/.next/standalone/.next/static/chunks/2ynl0-6aqng_b.js +0 -1
  234. package/.next/standalone/.next/static/chunks/3xvtgz6arztp_.js +0 -1
  235. package/.next/standalone/app/api/auth/reminder/route.ts +0 -213
  236. /package/.next/standalone/.next/static/{Aulh3DRWwRfwh2POCvZR_ → jSKB7CQQSNTX-uSbY2bUs}/_buildManifest.js +0 -0
  237. /package/.next/standalone/.next/static/{Aulh3DRWwRfwh2POCvZR_ → jSKB7CQQSNTX-uSbY2bUs}/_clientMiddlewareManifest.js +0 -0
  238. /package/.next/standalone/.next/static/{Aulh3DRWwRfwh2POCvZR_ → jSKB7CQQSNTX-uSbY2bUs}/_ssgManifest.js +0 -0
@@ -1,29 +1,104 @@
1
1
  "use server";
2
2
 
3
3
  /**
4
- * Write side of the /settings "Scheduled audit" section. Every write goes
5
- * through `updateConfig` — never a raw file write — so the layout-2 config
6
- * helpers stay the single writer of `config.toml` and the dashboard can never
7
- * disagree with what the CLI reads.
4
+ * Write side of the /settings scheduled-audit panel. Every write goes through
5
+ * `updateConfig` — never a raw file write — so `fp-config` stays the single
6
+ * writer of `config.json` and the dashboard can never disagree with what the
7
+ * CLI reads.
8
8
  *
9
9
  * ## CLI ⟷ dashboard parity
10
- * - `setAutoAuditAction(enabled)` ⟷ `[audit] auto` (updateConfig)
11
- * - `setAuditIntervalAction(days)` ⟷ `[audit] interval_days` (updateConfig)
12
- * Both keys are exactly what the `failproofai config` wizard writes, so a value
13
- * set here is indistinguishable from one set on the CLI.
10
+ * - `setAutoAuditAction(enabled)` ⟷ `[audit] auto`
11
+ * - `setAuditIntervalAction(days)` ⟷ `[audit] interval_days`
12
+ *
13
+ * Both go through the same `updateConfig` the CLI uses — `failproofai audit
14
+ * --schedule` / `--no-schedule` call it too — so a value set on either side is
15
+ * byte-identical. That is the whole mechanism behind "the two
16
+ * surfaces are always in sync": there is one file, one writer function, and no
17
+ * second copy of the state to drift.
14
18
  */
15
19
 
16
20
  import { readConfig, updateConfig } from "@/src/hooks/fp-config";
21
+ import { readAuth, whoAmI } from "@/lib/auth/auth-store";
22
+
23
+ /**
24
+ * The outcome of trying to turn scheduling on.
25
+ *
26
+ * "Signed out" is RETURNED, not thrown, and that is the whole point of this
27
+ * type. Next masks a server action's thrown error before the browser sees it —
28
+ * the client gets an opaque digest, never the message — so a caller matching on
29
+ * the text works in development and silently degrades to a generic failure in
30
+ * production, which is exactly what happened: the page showed an address it had
31
+ * read from the local session file, the toggle took the signed-in path, and the
32
+ * user got "could not turn that on." with no way forward from that click.
33
+ *
34
+ * A returned discriminant survives the boundary, so the caller can open the
35
+ * sign-in dialog for the one failure that has an obvious next step.
36
+ */
37
+ export type SetAutoAuditResult =
38
+ | { ok: true; auto: boolean }
39
+ | { ok: false; reason: "signed-out" }
40
+ /**
41
+ * The session is intact locally and the api-server could not be reached.
42
+ *
43
+ * Separated from `signed-out` because `whoAmI()` collapses them: it returns
44
+ * null for a 401 AND for every transport failure, so an offline machine, a
45
+ * proxy that blocks the host, and a genuinely expired token were one answer.
46
+ * The user was told "that sign-in expired" and handed a code prompt that
47
+ * cannot succeed either — the failure it names is not the failure they have,
48
+ * and following its advice costs them a real, working session.
49
+ */
50
+ | { ok: false; reason: "unreachable" };
17
51
 
18
52
  /**
19
53
  * Turn the scheduled scan on or off.
20
54
  *
55
+ * Turning it ON is refused without a session. Scheduling and mailing are ONE
56
+ * decision — the reason to put a scan on a timer is to be told what it found —
57
+ * so a machine with the timer set and nobody to tell is a switch that reads as
58
+ * on and produces nothing, discoverable only by noticing that no digest ever
59
+ * arrives. The caller signs the user in first and retries.
60
+ *
61
+ * `whoAmI()` asks the SERVER, so this refuses in a case the page cannot see: a
62
+ * session file that exists locally but whose refresh token the api-server has
63
+ * rejected. The local file is what the page reads to show "reports go to …", so
64
+ * the two disagree exactly when a session has expired or was minted against a
65
+ * different server — and that disagreement is the common case, not an edge one.
66
+ *
67
+ * Turning it OFF never checks. An expired session must not be able to trap
68
+ * somebody into keeping a feature they are trying to disable.
69
+ *
70
+ * Note this gates SETTING UP the timer, not the machine's ongoing work: a
71
+ * session that later expires leaves the timer running and the local scan
72
+ * working, and only the digest stops. See `report-harm.ts`.
73
+ *
21
74
  * Returns the value actually stored (re-read), so an optimistic UI can confirm
22
75
  * against the source of truth rather than assume its own guess landed.
23
76
  */
24
- export async function setAutoAuditAction(enabled: boolean): Promise<{ auto: boolean }> {
25
- const next = updateConfig({ audit: { auto: enabled } });
26
- return { auto: next.audit.auto };
77
+ export async function setAutoAuditAction(enabled: boolean): Promise<SetAutoAuditResult> {
78
+ if (enabled) {
79
+ const who = await whoAmI();
80
+ if (!who) {
81
+ // `whoAmI()` deletes the session on a 401 and leaves it alone on a
82
+ // transport failure, so what is left ON DISK is the one thing that tells
83
+ // the two apart — no extra request, and nothing new that can fail.
84
+ const local = readAuth();
85
+ const stillWithinWindow = local && local.refresh_expires_at * 1000 > Date.now();
86
+ return { ok: false, reason: stillWithinWindow ? "unreachable" : "signed-out" };
87
+ }
88
+ }
89
+ // Enabling stamps consent in the same write, because the `whoAmI()` above is
90
+ // exactly what makes this a consent record rather than a guess: a person was
91
+ // present, signed in, and looking at the panel that enumerates what gets
92
+ // sent. `reportHarm` gates sending on the stamp, not on `auto`, so a machine
93
+ // that inherited `auto` from a release where it meant "scan locally on a
94
+ // timer" mails nothing until somebody passes through here or the CLI.
95
+ //
96
+ // Disabling leaves the stamp alone. It is a record of something that did
97
+ // happen, and it grants nothing on its own — sending needs `auto` too.
98
+ const next = updateConfig({
99
+ audit: enabled ? { auto: true, reportsConsentedAt: Date.now() } : { auto: false },
100
+ });
101
+ return { ok: true, auto: next.audit.auto };
27
102
  }
28
103
 
29
104
  /**
@@ -31,14 +106,12 @@ export async function setAutoAuditAction(enabled: boolean): Promise<{ auto: bool
31
106
  *
32
107
  * The clamp lives in `fp-config.readIntervalDays` (1..90, with 0/negatives/
33
108
  * fractions falling back to the default) and is DELIBERATELY not reimplemented
34
- * here: we write the raw value and then RE-READ, so what we return to the UI is
35
- * exactly what the config decided to keep. Reflecting the re-read value is how a
36
- * hand-typed 3650 shows up in the dashboard as the 90 the config actually
37
- * enforces, with no second copy of the bounds to drift.
109
+ * here: we write the raw value and then RE-READ, so what comes back is exactly
110
+ * what the config decided to keep. Reflecting the re-read value is how a
111
+ * hand-typed 3650 shows up as the 90 the config actually enforces, with no
112
+ * second copy of the bounds to drift.
38
113
  */
39
114
  export async function setAuditIntervalAction(days: number): Promise<{ intervalDays: number }> {
40
115
  updateConfig({ audit: { intervalDays: days } });
41
- // Re-read through readConfig so the returned value carries the config's own
42
- // clamp, not the raw input.
43
116
  return { intervalDays: readConfig().audit.intervalDays };
44
117
  }
@@ -2,40 +2,30 @@
2
2
  * GET /api/auth/status
3
3
  *
4
4
  * Returns the currently signed-in identity by reading the local
5
- * `~/.failproofai/auth.json` cache. No round-trip to the api-server — the
5
+ * `~/.failproofai/audit/session.json` cache. No round-trip to the api-server — the
6
6
  * file is the source of truth for who is signed in on this machine.
7
7
  * This keeps the dashboard UI and the CLI consistent regardless of whether
8
8
  * the api-server is reachable.
9
9
  *
10
- * Also returns the user's persisted re-audit reminder (if any). The reminder
11
- * lives in ~/.failproofai/next-audit.json and is only surfaced when its
12
- * `user_email` matches the active session so swapping accounts via CLI
13
- * does not leak a previous user's reminder into the dashboard.
10
+ * Reminders are gone: the machine now audits itself on a timer and mails a
11
+ * digest when it finds harm, so there is nothing to nudge anyone about. The
12
+ * scheduled-scan state lives in `getScheduledAuditAction`, which reads it from
13
+ * the config and the daemon rather than from here.
14
14
  */
15
15
  import { NextResponse } from "next/server";
16
- import { readAuth, readReminder } from "@/lib/auth/auth-store";
16
+ import { readAuth } from "@/lib/auth/auth-store";
17
17
 
18
18
  export const dynamic = "force-dynamic";
19
19
 
20
20
  export async function GET(): Promise<NextResponse> {
21
21
  const auth = readAuth();
22
22
  if (!auth) {
23
- return NextResponse.json({ authenticated: false, reminder: null }, { status: 200 });
23
+ return NextResponse.json({ authenticated: false }, { status: 200 });
24
24
  }
25
- const reminderRaw = readReminder();
26
- const reminder =
27
- reminderRaw && reminderRaw.user_email === auth.user.email
28
- ? {
29
- next_audit_at: reminderRaw.next_audit_at,
30
- user_email: reminderRaw.user_email,
31
- set_at: reminderRaw.set_at,
32
- }
33
- : null;
34
25
  return NextResponse.json(
35
26
  {
36
27
  authenticated: true,
37
28
  user: { id: auth.user.id, email: auth.user.email },
38
- reminder,
39
29
  },
40
30
  { status: 200 },
41
31
  );
@@ -10,7 +10,7 @@
10
10
  * 02 StrengthsSection — what it's great at
11
11
  * 03 QuirksSection — what slipped through
12
12
  * 04 HowToImproveSection — install / configure
13
- * 05 ComeBackBetterSection — reminder + perks
13
+ * 05 ComeBackBetterSection — spread the audit (invite)
14
14
  *
15
15
  * Empty / running states fall back to EmptyState and RunProgress.
16
16
  */
@@ -355,11 +355,7 @@ function MainReport({
355
355
  projected={projected}
356
356
  projectedGrade={projectedGrade}
357
357
  />
358
- <ComeBackBetterSection
359
- isRunning={isRunning}
360
- onRerun={() => onRerun("return_section")}
361
- score={score}
362
- />
358
+ <ComeBackBetterSection score={score} />
363
359
  </div>
364
360
  <ReportFooter cachedAt={cachedAt} />
365
361
  </div>
@@ -1,335 +1,155 @@
1
1
  "use client";
2
2
 
3
3
  /**
4
- * Section 05 — COME BACK BETTER. "build the habit."
4
+ * Section 05 — SPREAD THE AUDIT.
5
5
  *
6
- * Two side-by-side cards:
6
+ * One job: get someone else to run this on their own machine. The scheduled-
7
+ * audit controls used to live here too and have moved to `/settings`, reachable
8
+ * from the gear in the header — they are machine configuration, and this is the
9
+ * end of a report. Mixing "here is what your agent did" with "here is how to
10
+ * configure a background service" made the last thing you read before leaving
11
+ * the page a settings form.
7
12
  *
8
- * Reminder set a reminder cadence (3d / 7d / 14d / 30d). The cadence
9
- * selection persists through /api/auth/reminder. Anon users get the
10
- * AuthDialog first; authed-with-existing-reminder users see the next
11
- * audit date and can reset.
12
- *
13
- * • Unlock perks — share with N friends to unlock pro features for a
14
- * month. UI only — invite tracking + entitlement is a follow-up; the
15
- * button opens the same X share intent the poster uses.
16
- *
17
- * Re-audit moves out of this section: a small inline "or re-audit now"
18
- * link sits under the reminder card so the affordance survives without
19
- * dominating the layout.
13
+ * The AuthDialog is still here because inviting needs a sender identity to Cc.
14
+ * It is now the ONLY thing on this section that opens it, which is what makes
15
+ * the resume unambiguous the bug this section used to have was a shared
16
+ * dialog whose success handler assumed which control had opened it.
20
17
  */
21
18
  import { useCallback, useEffect, useRef, useState } from "react";
22
19
  import { usePostHog } from "@/contexts/PostHogContext";
23
- import { isAbortError } from "@/lib/fetch-with-timeout";
24
20
  import { AuthDialog, type AuthedUser } from "./auth-dialog";
25
21
  import { InviteDialog } from "./invite-dialog";
26
22
 
27
23
  interface Props {
28
- isRunning: boolean;
29
- onRerun: () => void;
30
24
  /** Current audit score (0–100), forwarded into the invite email body. */
31
25
  score?: number;
32
26
  }
33
27
 
34
- const DEFAULT_REMINDER_DAYS = 7;
35
- const REMINDER_OPTIONS = [3, 7, 14, 30] as const;
36
- type Cadence = typeof REMINDER_OPTIONS[number];
37
-
38
28
  const PERKS_PERK = "wanna know how your friends' agents score?";
39
29
 
40
- // The AuthDialog is shared by the reminder and invite CTAs. The reminder path
41
- // keeps the dialog's default copy; the invite path swaps in login-required
42
- // copy. Content only — the auth flow is identical for both.
43
30
  const INVITE_AUTH_COPY = {
44
31
  headline: "Oops! Login required",
45
32
  subhead: "What's your email?",
46
33
  } as const;
47
34
 
48
- type AuthStatus =
49
- | { kind: "unknown" }
50
- | { kind: "anon" }
51
- | { kind: "authed"; user: { id: string; email: string } };
52
-
53
- interface Reminder {
54
- next_audit_at: number;
55
- user_email: string;
56
- set_at: number;
57
- }
58
-
59
- function daysUntil(unixSecs: number): number {
60
- const nowSecs = Math.floor(Date.now() / 1000);
61
- return Math.max(0, Math.ceil((unixSecs - nowSecs) / 86400));
62
- }
63
-
64
- function formatNextAudit(unixSecs: number): string {
65
- const d = new Date(unixSecs * 1000);
66
- return d.toLocaleDateString(undefined, {
67
- weekday: "short",
68
- month: "short",
69
- day: "numeric",
70
- });
71
- }
72
-
73
- export function ComeBackBetterSection({ isRunning, onRerun, score }: Props) {
35
+ export function ComeBackBetterSection({ score }: Props) {
74
36
  const { capture } = usePostHog();
75
- const [authStatus, setAuthStatus] = useState<AuthStatus>({ kind: "unknown" });
76
- const [reminder, setReminder] = useState<Reminder | null>(null);
77
- const [cadence, setCadence] = useState<Cadence>(DEFAULT_REMINDER_DAYS);
78
- const [dialogOpen, setDialogOpen] = useState(false);
79
- const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
80
- const [reminderBusy, setReminderBusy] = useState(false);
81
- // Copy for the shared AuthDialog: {} keeps the reminder defaults,
82
- // INVITE_AUTH_COPY shows the invite variant. Set by whichever CTA opens the
83
- // dialog content selection only, no effect on the auth flow.
84
- const [authCopy, setAuthCopy] = useState<{ headline?: string; subhead?: string }>({});
85
- const ctaShownRef = useRef(false);
86
- const lastRefreshAtRef = useRef(0);
87
-
88
- const refreshStatus = useCallback(async () => {
89
- lastRefreshAtRef.current = Date.now();
90
- // Preserve current UI state on transient failures (5xx, network blips).
91
- // Downgrading to anon on every error would clear a valid reminder mid-
92
- // session on a single failed poll, forcing an unnecessary auth prompt.
93
- // Only fall through to anon on the very first probe (still "unknown")
94
- // so the cadence buttons unlock even if the server is unreachable.
95
- const fallbackToAnonOnError = () => {
96
- setAuthStatus((prev) => (prev.kind === "unknown" ? { kind: "anon" } : prev));
97
- };
98
- try {
99
- const res = await fetch("/api/auth/status", { cache: "no-store" });
100
- if (!res.ok) {
101
- fallbackToAnonOnError();
102
- return;
103
- }
104
- const body = (await res.json()) as {
105
- authenticated?: boolean;
106
- user?: { id: string; email: string };
107
- reminder?: Reminder | null;
108
- };
109
- if (body.authenticated && body.user) {
110
- setAuthStatus({ kind: "authed", user: body.user });
111
- setReminder(body.reminder ?? null);
112
- } else {
113
- setAuthStatus({ kind: "anon" });
114
- setReminder(null);
115
- }
116
- } catch {
117
- fallbackToAnonOnError();
118
- }
119
- }, []);
37
+ const [signedIn, setSignedIn] = useState<{ id: string; email: string } | null>(null);
38
+ /**
39
+ * Whether the sign-in probe below has come back yet.
40
+ *
41
+ * `signedIn` starts null and null also means "signed out", so on its own it
42
+ * cannot say whether the answer has arrived — and the impression event fires
43
+ * on the first commit, which is always before the fetch resolves. It
44
+ * therefore reported `signed_in: false` for every view ever recorded,
45
+ * including a signed-in one. A separate flag restores the tri-state the
46
+ * previous version of this section carried for the same reason.
47
+ */
48
+ const [probed, setProbed] = useState(false);
49
+ const [authOpen, setAuthOpen] = useState(false);
50
+ const [inviteOpen, setInviteOpen] = useState(false);
51
+ const shownRef = useRef(false);
120
52
 
121
53
  useEffect(() => {
122
- void refreshStatus();
123
- const REFRESH_MIN_INTERVAL_MS = 5_000;
124
- const maybeRefresh = () => {
125
- if (Date.now() - lastRefreshAtRef.current < REFRESH_MIN_INTERVAL_MS) return;
126
- void refreshStatus();
127
- };
128
- const onFocus = () => maybeRefresh();
129
- const onVisibility = () => {
130
- if (document.visibilityState === "visible") maybeRefresh();
131
- };
132
- window.addEventListener("focus", onFocus);
133
- document.addEventListener("visibilitychange", onVisibility);
54
+ // Cancellation guard rather than a bare fire-and-forget: the probe outlives
55
+ // a fast unmount otherwise, and setting state on a gone component is the
56
+ // kind of warning people learn to scroll past.
57
+ let cancelled = false;
58
+ (async () => {
59
+ try {
60
+ const res = await fetch("/api/auth/status", { cache: "no-store" });
61
+ if (!res.ok || cancelled) return;
62
+ const body = (await res.json()) as {
63
+ authenticated?: boolean;
64
+ user?: { id: string; email: string };
65
+ };
66
+ if (!cancelled) setSignedIn(body.authenticated && body.user ? body.user : null);
67
+ } catch {
68
+ // Leave whatever we last knew. A failed probe is not evidence of a
69
+ // signed-out user, and downgrading on one would prompt for a login the
70
+ // person already completed.
71
+ } finally {
72
+ // In `finally`, so a route that 404s or a fetch that throws still
73
+ // releases the impression event. A failed probe genuinely does not know
74
+ // whether anyone is signed in, and never reporting the view at all is a
75
+ // worse answer than reporting the one it has.
76
+ if (!cancelled) setProbed(true);
77
+ }
78
+ })();
134
79
  return () => {
135
- window.removeEventListener("focus", onFocus);
136
- document.removeEventListener("visibilitychange", onVisibility);
80
+ cancelled = true;
137
81
  };
138
- }, [refreshStatus]);
82
+ }, []);
139
83
 
140
84
  useEffect(() => {
141
- if (ctaShownRef.current) return;
142
- if (authStatus.kind === "unknown") return;
143
- ctaShownRef.current = true;
144
- capture("audit_reminder_cta_shown", {
145
- auth_state: authStatus.kind,
146
- has_existing_reminder: reminder !== null,
147
- source: "come_back_better_section",
148
- });
149
- }, [authStatus, capture, reminder]);
150
-
151
- const persistReminder = useCallback(
152
- async (inDays: number): Promise<Reminder | null> => {
153
- const controller = new AbortController();
154
- const timer = setTimeout(() => controller.abort(), 10_000);
155
- try {
156
- setReminderBusy(true);
157
- const res = await fetch("/api/auth/reminder", {
158
- method: "POST",
159
- headers: { "content-type": "application/json" },
160
- body: JSON.stringify({ in_days: inDays }),
161
- signal: controller.signal,
162
- });
163
- if (!res.ok) {
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: "come_back_better_section",
171
- cadence_days: inDays,
172
- });
173
- return null;
174
- }
175
- const body = (await res.json()) as { reminder?: Reminder };
176
- capture("audit_reminder_saved", {
177
- status: body.reminder ? "success" : "empty",
178
- source: "come_back_better_section",
179
- cadence_days: inDays,
180
- });
181
- return body.reminder ?? null;
182
- } catch (err) {
183
- const kind = isAbortError(err) ? "timeout" : "error";
184
- capture("audit_reminder_saved", {
185
- status: kind,
186
- source: "come_back_better_section",
187
- cadence_days: inDays,
188
- });
189
- return null;
190
- } finally {
191
- clearTimeout(timer);
192
- setReminderBusy(false);
193
- }
194
- },
195
- [capture],
196
- );
85
+ if (!probed || shownRef.current) return;
86
+ shownRef.current = true;
87
+ capture("audit_share_section_shown", { signed_in: signedIn !== null });
88
+ }, [capture, probed, signedIn]);
197
89
 
198
- const handleCadenceClick = useCallback(
199
- async (next: Cadence) => {
200
- setCadence(next);
201
- capture("audit_reminder_cta_clicked", {
202
- auth_state: authStatus.kind,
203
- has_existing_reminder: reminder !== null,
204
- cadence_days: next,
205
- source: "come_back_better_section",
206
- });
207
- if (authStatus.kind === "authed") {
208
- const saved = await persistReminder(next);
209
- if (saved) setReminder(saved);
210
- return;
211
- }
212
- if (authStatus.kind === "anon") {
213
- setAuthCopy({}); // reminder context → keep the dialog's default copy
214
- setDialogOpen(true);
215
- }
216
- },
217
- [authStatus, capture, persistReminder, reminder],
218
- );
90
+ const handleInvite = useCallback(() => {
91
+ capture("audit_perks_invite_clicked", { signed_in: signedIn !== null });
92
+ // Unauthed users sign in first, so the invite has a sender to Cc.
93
+ if (!signedIn) {
94
+ setAuthOpen(true);
95
+ return;
96
+ }
97
+ setInviteOpen(true);
98
+ }, [capture, signedIn]);
219
99
 
220
100
  const handleAuthed = useCallback(
221
101
  async (user: AuthedUser) => {
222
- setAuthStatus({ kind: "authed", user });
223
- capture("audit_auth_completed", {
224
- source: "come_back_better_section",
225
- });
226
- const saved = await persistReminder(cadence);
227
- if (saved) setReminder(saved);
102
+ setSignedIn(user);
103
+ setAuthOpen(false);
104
+ capture("audit_auth_completed", { source: "share_section" });
105
+ // Resume the one thing that could have opened the dialog.
106
+ setInviteOpen(true);
228
107
  },
229
- [cadence, capture, persistReminder],
108
+ [capture],
230
109
  );
231
110
 
232
- const handleInvite = useCallback(() => {
233
- capture("audit_perks_invite_clicked", {
234
- source: "come_back_better_section",
235
- auth_state: authStatus.kind,
236
- });
237
- // Unauthed users go through the AuthDialog first so we have a sender
238
- // identity to Cc on the invite email.
239
- if (authStatus.kind !== "authed") {
240
- setAuthCopy(INVITE_AUTH_COPY); // invite context → "Oops! Login required"
241
- setDialogOpen(true);
242
- return;
243
- }
244
- setInviteDialogOpen(true);
245
- }, [authStatus.kind, capture]);
246
-
247
- const handleRerunInline = useCallback(() => {
248
- if (isRunning) return;
249
- onRerun();
250
- }, [isRunning, onRerun]);
251
-
252
- const days = reminder ? daysUntil(reminder.next_audit_at) : 0;
253
-
254
111
  return (
255
- <section className="audit-sec" data-screen-label="05 Come back better">
112
+ <section className="audit-sec" data-screen-label="05 Spread the audit">
256
113
  <div className="audit-sec-head">
257
- <span className="audit-sec-eyebrow">
258
- <span className="ix">05</span>{"// come back better"}
259
- </span>
260
- </div>
261
- <h2 className="audit-sec-title">build the habit</h2>
262
-
263
- <div className="cbb-grid">
264
- {/* Reminder card */}
265
- <div className="cbb-card">
266
- <div className="cbb-card-title">set a reminder</div>
267
- <div className="cbb-card-sub">
268
- {reminder
269
- ? `next audit set for ${formatNextAudit(reminder.next_audit_at)} · in ${days} day${days === 1 ? "" : "s"}.`
270
- : "we'll nudge you when your next audit is due. pick the cadence:"}
271
- </div>
272
- <div className="cadence-row">
273
- {REMINDER_OPTIONS.map((d) => (
274
- <button
275
- key={d}
276
- type="button"
277
- className={`cadence-btn${cadence === d ? " on" : ""}`}
278
- disabled={reminderBusy || authStatus.kind === "unknown"}
279
- onClick={() => void handleCadenceClick(d)}
280
- >
281
- {d}d
282
- </button>
283
- ))}
284
- </div>
285
- <button
286
- type="button"
287
- className="cbb-link"
288
- disabled={isRunning}
289
- onClick={handleRerunInline}
290
- >
291
- {isRunning ? "scanning…" : "or re-audit now →"}
292
- </button>
114
+ <div className="audit-sec-eyebrow">
115
+ <span className="ix">05</span> share
293
116
  </div>
294
-
295
- {/* Perks card */}
296
- <div className="cbb-card">
297
- <div className="cbb-card-title">Share with friends</div>
298
- <div className="cbb-card-sub">{PERKS_PERK}</div>
299
- <button type="button" className="invite-btn" onClick={handleInvite}>
300
- invite a friend
301
- </button>
302
- <div className="cbb-foot">
303
- {"// invites are sent from failproof.ai, Cc'd to you, with a link to run their own audit."}
304
- </div>
117
+ </div>
118
+ <h2 className="audit-sec-title">spread the audit</h2>
119
+
120
+ <div className="cbb-card share-card">
121
+ <div className="cbb-card-title">Share with friends</div>
122
+ <div className="cbb-card-sub">{PERKS_PERK}</div>
123
+ <button type="button" className="invite-btn" onClick={handleInvite}>
124
+ invite a friend
125
+ </button>
126
+ <div className="cbb-foot">
127
+ {"// invites are sent from failproof.ai, Cc'd to you, with a link to run their own audit."}
305
128
  </div>
306
129
  </div>
307
130
 
308
131
  <InviteDialog
309
- open={inviteDialogOpen}
132
+ open={inviteOpen}
310
133
  source="come_back_better_section"
311
134
  score={score}
312
- onClose={() => setInviteDialogOpen(false)}
135
+ onClose={() => setInviteOpen(false)}
313
136
  onUnauthorized={() => {
314
- // Session expired between probe and submit flip back to anon
315
- // and bounce through the AuthDialog so the user re-auths.
316
- setAuthStatus({ kind: "anon" });
317
- setReminder(null);
318
- setAuthCopy(INVITE_AUTH_COPY); // still the invite context
319
- setDialogOpen(true);
137
+ // Session expired between the probe and the submit. Bounce through
138
+ // the dialog; success reopens the invite, since that is the only
139
+ // thing it can be resuming.
140
+ setInviteOpen(false);
141
+ setSignedIn(null);
142
+ setAuthOpen(true);
320
143
  }}
321
144
  />
322
145
 
323
146
  <AuthDialog
324
- open={dialogOpen}
325
- source="return_section"
326
- headline={authCopy.headline}
327
- subhead={authCopy.subhead}
328
- onClose={() => setDialogOpen(false)}
329
- onAuthed={(u) => {
330
- setDialogOpen(false);
331
- void handleAuthed(u);
332
- }}
147
+ open={authOpen}
148
+ source="share_section"
149
+ headline={INVITE_AUTH_COPY.headline}
150
+ subhead={INVITE_AUTH_COPY.subhead}
151
+ onClose={() => setAuthOpen(false)}
152
+ onAuthed={(u) => void handleAuthed(u)}
333
153
  />
334
154
  </section>
335
155
  );