failproofai 0.0.11-beta.7 → 0.0.11-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/.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/[root-of-the-server]__1_mqemn._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1r1h8v9._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uatkiv._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1y6gxxb._.js +3 -0
  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 +8 -8
  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]__0808sha._.js +4 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1cd25c7._.js → [root-of-the-server]__0e4-6d8._.js} +3 -3
  107. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08gyb62._.js → [root-of-the-server]__0e446gb._.js} +2 -2
  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} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +4 -0
  113. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08v7sn2._.js → [root-of-the-server]__0wprfyc._.js} +2 -2
  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/_11_p9y8._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +21 -21
  124. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0y4a-0q.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  129. package/.next/standalone/.next/server/functions-config-manifest.json +0 -3
  130. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  131. package/.next/standalone/.next/server/pages/404.html +1 -1
  132. package/.next/standalone/.next/server/pages/500.html +1 -1
  133. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  134. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  135. package/.next/standalone/.next/static/chunks/{0hmxjseydp6as.js → 07_d165p5h5ys.js} +1 -1
  136. package/.next/standalone/.next/static/chunks/0f7d7hnbh4djs.js +1 -0
  137. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +1 -0
  138. package/.next/standalone/.next/static/chunks/168k-8z6k7e8z.css +1 -0
  139. package/.next/standalone/.next/static/chunks/{3n28elq6kwpy3.js → 1kvadxkgnapyj.js} +1 -1
  140. package/.next/standalone/.next/static/chunks/{0fpw1jzeg66_x.js → 277oc363p56n6.js} +2 -2
  141. package/.next/standalone/.next/static/chunks/28mkxkl_d91-l.js +1 -0
  142. package/.next/standalone/.next/static/chunks/28x7jvo3kxd3u.js +41 -0
  143. package/.next/standalone/.next/static/chunks/29nrs5xs9c4hx.css +2 -0
  144. package/.next/standalone/.next/static/chunks/{41vwoxpkauj1w.js → 29tg7deqmq32l.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/{3w8d8k_dca5rp.js → 2h0dkzyy0vocp.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/{1eq7f419t6ln3.js → 2z42u62k-8-_q.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/{0avgb78edu0i-.js → 3nj6g3xu9uy78.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/{0xkxbaa-oo180.js → 3ty6dhcuogout.js} +1 -1
  149. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
  150. package/.next/standalone/.next/static/chunks/{turbopack-00qy7zfa7m--m.js → turbopack-3lrm4f20fz89b.js} +1 -1
  151. package/.next/standalone/app/api/audit/_state.ts +40 -33
  152. package/.next/standalone/app/api/audit/invite/route.ts +183 -0
  153. package/.next/standalone/app/api/audit/run/route.ts +30 -16
  154. package/.next/standalone/app/api/audit/status/route.ts +1 -0
  155. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +33 -65
  156. package/.next/standalone/app/audit/_components/audit-poster.tsx +322 -0
  157. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  158. package/.next/standalone/app/audit/_components/auth-dialog.tsx +21 -49
  159. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +316 -0
  160. package/.next/standalone/app/audit/_components/empty-state.tsx +8 -2
  161. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +187 -0
  162. package/.next/standalone/app/audit/_components/invite-dialog.tsx +227 -0
  163. package/.next/standalone/app/audit/_components/quirks-section.tsx +75 -0
  164. package/.next/standalone/app/audit/_components/rerun-button.tsx +43 -14
  165. package/.next/standalone/app/audit/_components/run-progress.tsx +2 -2
  166. package/.next/standalone/app/audit/_components/share-templates.ts +23 -22
  167. package/.next/standalone/app/audit/_components/sigil.tsx +9 -66
  168. package/.next/standalone/app/audit/_components/strengths-section.tsx +20 -32
  169. package/.next/standalone/app/audit/audit-styles.css +781 -1784
  170. package/.next/standalone/app/components/sessions-list.tsx +77 -80
  171. package/.next/standalone/app/globals.css +214 -32
  172. package/.next/standalone/app/layout.tsx +1 -10
  173. package/.next/standalone/app/policies/hooks-client.tsx +12 -4
  174. package/.next/standalone/app/project/[name]/page.tsx +23 -79
  175. package/.next/standalone/app/projects/page.tsx +14 -23
  176. package/.next/standalone/assets/audit/poster-styles.css +1 -1
  177. package/.next/standalone/assets/audit/styles.css +11 -11
  178. package/.next/standalone/components/navbar.tsx +2 -37
  179. package/.next/standalone/lib/auth/api-server-client.ts +25 -0
  180. package/.next/standalone/node_modules/@next/env/package.json +2 -2
  181. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  184. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  185. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  186. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  187. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  188. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  189. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  190. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  191. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  192. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  193. package/.next/standalone/node_modules/next/package.json +15 -15
  194. package/.next/standalone/package.json +18 -15
  195. package/.next/standalone/server.js +1 -1
  196. package/dist/cli.mjs +2 -2
  197. package/lib/auth/api-server-client.ts +25 -0
  198. package/package.json +18 -15
  199. package/src/audit/dashboard-cache.ts +13 -6
  200. package/src/audit/social-proof.ts +34 -0
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07tgnzi._.js +0 -3
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oeun7z._.js +0 -3
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12pit4m._.js +0 -3
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13ra2jq._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1b9z5-i._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ixjiy8._.js +0 -3
  207. package/.next/standalone/.next/server/chunks/_1-1804f._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00jkjmt._.js +0 -4
  209. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013du6r._.js +0 -4
  210. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e85wxv._.js +0 -4
  211. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gfxvb1._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1hlrq6y._.js +0 -4
  213. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ihxdo5._.js +0 -4
  214. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1vvfde2._.js +0 -4
  215. package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_0q4m0_h.js +0 -3
  216. package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_1n-0xws.js +0 -3
  217. package/.next/standalone/.next/static/chunks/09ueq8s1as8xs.css +0 -2
  218. package/.next/standalone/.next/static/chunks/0qassxjx1ef04.js +0 -1
  219. package/.next/standalone/.next/static/chunks/2452blhu4bcdr.js +0 -41
  220. package/.next/standalone/.next/static/chunks/2so39wg7mjbi7.js +0 -1
  221. package/.next/standalone/.next/static/chunks/3gti1qdk5epqn.js +0 -1
  222. package/.next/standalone/.next/static/chunks/3osmt-t2hsrng.js +0 -1
  223. package/.next/standalone/.next/static/chunks/3wycox197ouus.css +0 -1
  224. package/.next/standalone/app/audit/_components/findings-section.tsx +0 -135
  225. package/.next/standalone/app/audit/_components/identity-section.tsx +0 -126
  226. package/.next/standalone/app/audit/_components/policies-section.tsx +0 -194
  227. package/.next/standalone/app/audit/_components/return-section.tsx +0 -416
  228. package/.next/standalone/app/audit/_components/score-section.tsx +0 -179
  229. package/.next/standalone/app/audit/_components/share-dock.tsx +0 -265
  230. package/.next/standalone/app/audit/_components/show-off-cta.tsx +0 -135
  231. /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_buildManifest.js +0 -0
  232. /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_clientMiddlewareManifest.js +0 -0
  233. /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_ssgManifest.js +0 -0
@@ -2,14 +2,15 @@
2
2
 
3
3
  /**
4
4
  * Auth dialog — modal overlay shown when an unauthenticated user clicks
5
- * "[ set a reminder ]". Two-step flow:
5
+ * a cadence button to set a reminder. Two-step flow:
6
6
  *
7
7
  * 1. Email entry → POST /api/auth/login-request
8
8
  * 2. OTP entry → POST /api/auth/login-verify
9
9
  *
10
- * Styled to match the rest of the /audit page: pixel brackets, sharp pink
11
- * accent, terminal-style frame. The dialog never sees the refresh token —
12
- * the dashboard's API route writes it to ~/.failproofai/auth.json.
10
+ * Calm chrome to match the rest of /audit: 1px borders, plain
11
+ * lowercase copy, no corner crosshair frame. The dialog never sees
12
+ * the refresh token — the dashboard's API route writes it to
13
+ * ~/.failproofai/auth.json.
13
14
  */
14
15
 
15
16
  import React, { useCallback, useEffect, useRef, useState } from "react";
@@ -23,15 +24,12 @@ export interface AuthedUser {
23
24
 
24
25
  interface Props {
25
26
  open: boolean;
26
- /** Copy shown above the title, e.g. "oops you are unknown." */
27
+ /** Optional title. Defaults to "set a reminder". */
27
28
  headline?: string;
28
- /** Copy under the title explaining why we need auth right now. */
29
- reason?: string;
30
29
  onClose: () => void;
31
30
  /** Fired after successful verify. Caller decides what to do next. */
32
31
  onAuthed: (user: AuthedUser) => void;
33
- /** Telemetry tag identifying which CTA opened the dialog. Defaults to
34
- * "unknown" so existing call sites continue to compile. */
32
+ /** Telemetry tag identifying which CTA opened the dialog. */
35
33
  source?: string;
36
34
  }
37
35
 
@@ -52,8 +50,7 @@ function describeFetchError(err: unknown): string {
52
50
 
53
51
  export function AuthDialog({
54
52
  open,
55
- headline = "oops you are unknown.",
56
- reason = "verify yourself to continue.",
53
+ headline = "where to route the reminder?",
57
54
  onClose,
58
55
  onAuthed,
59
56
  source = "unknown",
@@ -65,8 +62,6 @@ export function AuthDialog({
65
62
  const emailInputRef = useRef<HTMLInputElement | null>(null);
66
63
  const codeInputRef = useRef<HTMLInputElement | null>(null);
67
64
 
68
- // Reset internal state every time the dialog opens. Also fire the
69
- // funnel-opened event so we can measure dismissal vs verification rates.
70
65
  useEffect(() => {
71
66
  if (open) {
72
67
  setStep({ kind: "email", error: null });
@@ -76,9 +71,6 @@ export function AuthDialog({
76
71
  }
77
72
  }, [capture, open, source]);
78
73
 
79
- // Fire dismissed when the dialog closes WITHOUT a successful verify.
80
- // We piggyback on `open` flipping false instead of intercepting every
81
- // close path so resend / step transitions don't double-count.
82
74
  const wasOpenRef = useRef(false);
83
75
  useEffect(() => {
84
76
  if (open) {
@@ -94,7 +86,6 @@ export function AuthDialog({
94
86
  wasOpenRef.current = false;
95
87
  }, [capture, open, source, step.kind]);
96
88
 
97
- // Autofocus the right input as the step changes.
98
89
  useEffect(() => {
99
90
  if (!open) return;
100
91
  const t = setTimeout(() => {
@@ -104,7 +95,6 @@ export function AuthDialog({
104
95
  return () => clearTimeout(t);
105
96
  }, [open, step.kind]);
106
97
 
107
- // ESC to close.
108
98
  useEffect(() => {
109
99
  if (!open) return;
110
100
  const onKey = (e: KeyboardEvent): void => {
@@ -114,7 +104,6 @@ export function AuthDialog({
114
104
  return () => window.removeEventListener("keydown", onKey);
115
105
  }, [open, busy, onClose]);
116
106
 
117
- // Resend countdown ticker.
118
107
  const resendActive = step.kind === "code" && step.resendIn > 0;
119
108
  useEffect(() => {
120
109
  if (!resendActive) return;
@@ -129,9 +118,6 @@ export function AuthDialog({
129
118
  const requestCode = useCallback(
130
119
  async (email: string, opts: { isResend?: boolean } = {}): Promise<void> => {
131
120
  const { isResend = false } = opts;
132
- // Show resend errors inline on the OTP step — the previously sent
133
- // code is still usable. Only the first-send error path bounces back
134
- // to the email step.
135
121
  const setError = (msg: string) => {
136
122
  if (isResend) {
137
123
  setStep((s) => (s.kind === "code" ? { ...s, error: msg } : s));
@@ -268,11 +254,6 @@ export function AuthDialog({
268
254
  }}
269
255
  >
270
256
  <div className="auth-dialog">
271
- <span className="corner tl">┌</span>
272
- <span className="corner tr">┐</span>
273
- <span className="corner bl">└</span>
274
- <span className="corner br">┘</span>
275
-
276
257
  <button
277
258
  type="button"
278
259
  className="auth-close"
@@ -280,21 +261,19 @@ export function AuthDialog({
280
261
  disabled={busy}
281
262
  aria-label="close"
282
263
  >
283
- [ x ]
264
+ ×
284
265
  </button>
285
266
 
286
- <div className="auth-label">━━ identity check</div>
287
267
  <h2 id="auth-dialog-title" className="auth-headline">
288
268
  {headline}
289
269
  </h2>
290
270
 
291
271
  {step.kind === "email" && (
292
272
  <>
293
- <p className="auth-sub">{reason}</p>
273
+ <p className="auth-sub">
274
+ we&apos;ll send a one-time code to confirm.
275
+ </p>
294
276
  <form onSubmit={onEmailSubmit} className="auth-form">
295
- <label className="auth-field-label" htmlFor="auth-dialog-email">
296
- email
297
- </label>
298
277
  <input
299
278
  ref={emailInputRef}
300
279
  id="auth-dialog-email"
@@ -311,7 +290,7 @@ export function AuthDialog({
311
290
  {step.error && <div className="auth-error">{step.error}</div>}
312
291
  <div className="auth-actions">
313
292
  <button type="submit" className="auth-btn primary" disabled={busy}>
314
- {busy ? "[ sending… ]" : "[ send code ]"}
293
+ {busy ? "sending…" : "send code"}
315
294
  </button>
316
295
  <button
317
296
  type="button"
@@ -319,7 +298,7 @@ export function AuthDialog({
319
298
  onClick={onClose}
320
299
  disabled={busy}
321
300
  >
322
- [ cancel ]
301
+ cancel
323
302
  </button>
324
303
  </div>
325
304
  </form>
@@ -329,14 +308,10 @@ export function AuthDialog({
329
308
  {step.kind === "code" && (
330
309
  <>
331
310
  <p className="auth-sub">
332
- we sent a code to <span className="auth-email">{step.email}</span>.
333
- <br />
334
- check your inbox — it expires in {Math.ceil(step.expiresIn / 60)} min.
311
+ code sent to <span className="auth-email">{step.email}</span>.
312
+ expires in {Math.ceil(step.expiresIn / 60)} min.
335
313
  </p>
336
314
  <form onSubmit={onCodeSubmit} className="auth-form">
337
- <label className="auth-field-label" htmlFor="auth-dialog-code">
338
- one-time code
339
- </label>
340
315
  <input
341
316
  ref={codeInputRef}
342
317
  id="auth-dialog-code"
@@ -354,7 +329,7 @@ export function AuthDialog({
354
329
  {step.error && <div className="auth-error">{step.error}</div>}
355
330
  <div className="auth-actions">
356
331
  <button type="submit" className="auth-btn primary" disabled={busy}>
357
- {busy ? "[ verifying… ]" : "[ verify ]"}
332
+ {busy ? "verifying…" : "verify"}
358
333
  </button>
359
334
  <button
360
335
  type="button"
@@ -363,8 +338,8 @@ export function AuthDialog({
363
338
  disabled={busy || step.resendIn > 0}
364
339
  >
365
340
  {step.resendIn > 0
366
- ? `[ resend in ${step.resendIn}s ]`
367
- : "[ resend code ]"}
341
+ ? `resend in ${step.resendIn}s`
342
+ : "resend code"}
368
343
  </button>
369
344
  </div>
370
345
  <button
@@ -382,15 +357,12 @@ export function AuthDialog({
382
357
  {step.kind === "done" && (
383
358
  <>
384
359
  <p className="auth-sub">
385
- <span className="auth-ok">✓</span> you are{" "}
360
+ <span className="auth-ok">✓</span> signed in as{" "}
386
361
  <span className="auth-email">{step.user.email}</span>.
387
362
  </p>
388
- <p className="auth-sub" style={{ marginTop: 8 }}>
389
- session saved locally.
390
- </p>
391
363
  <div className="auth-actions">
392
364
  <button type="button" className="auth-btn primary" onClick={onClose}>
393
- [ continue ]
365
+ continue
394
366
  </button>
395
367
  </div>
396
368
  </>
@@ -0,0 +1,316 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Section 05 — COME BACK BETTER. "build the habit."
5
+ *
6
+ * Two side-by-side cards:
7
+ *
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.
20
+ */
21
+ import React, { useCallback, useEffect, useRef, useState } from "react";
22
+ import { usePostHog } from "@/contexts/PostHogContext";
23
+ import { isAbortError } from "@/lib/fetch-with-timeout";
24
+ import { AuthDialog, type AuthedUser } from "./auth-dialog";
25
+ import { InviteDialog } from "./invite-dialog";
26
+
27
+ interface Props {
28
+ isRunning: boolean;
29
+ onRerun: () => void;
30
+ }
31
+
32
+ const DEFAULT_REMINDER_DAYS = 7;
33
+ const REMINDER_OPTIONS = [3, 7, 14, 30] as const;
34
+ type Cadence = typeof REMINDER_OPTIONS[number];
35
+
36
+ const PERKS_PERK = "share with 3 friends → unlock pro features for a month.";
37
+
38
+ type AuthStatus =
39
+ | { kind: "unknown" }
40
+ | { kind: "anon" }
41
+ | { kind: "authed"; user: { id: string; email: string } };
42
+
43
+ interface Reminder {
44
+ next_audit_at: number;
45
+ user_email: string;
46
+ set_at: number;
47
+ }
48
+
49
+ function daysUntil(unixSecs: number): number {
50
+ const nowSecs = Math.floor(Date.now() / 1000);
51
+ return Math.max(0, Math.ceil((unixSecs - nowSecs) / 86400));
52
+ }
53
+
54
+ function formatNextAudit(unixSecs: number): string {
55
+ const d = new Date(unixSecs * 1000);
56
+ return d.toLocaleDateString(undefined, {
57
+ weekday: "short",
58
+ month: "short",
59
+ day: "numeric",
60
+ });
61
+ }
62
+
63
+ export function ComeBackBetterSection({ isRunning, onRerun }: Props) {
64
+ const { capture } = usePostHog();
65
+ const [authStatus, setAuthStatus] = useState<AuthStatus>({ kind: "unknown" });
66
+ const [reminder, setReminder] = useState<Reminder | null>(null);
67
+ const [cadence, setCadence] = useState<Cadence>(DEFAULT_REMINDER_DAYS);
68
+ const [dialogOpen, setDialogOpen] = useState(false);
69
+ const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
70
+ const [reminderBusy, setReminderBusy] = useState(false);
71
+ const ctaShownRef = useRef(false);
72
+ const lastRefreshAtRef = useRef(0);
73
+
74
+ const refreshStatus = useCallback(async () => {
75
+ lastRefreshAtRef.current = Date.now();
76
+ // Preserve current UI state on transient failures (5xx, network blips).
77
+ // Downgrading to anon on every error would clear a valid reminder mid-
78
+ // session on a single failed poll, forcing an unnecessary auth prompt.
79
+ // Only fall through to anon on the very first probe (still "unknown")
80
+ // so the cadence buttons unlock even if the server is unreachable.
81
+ const fallbackToAnonOnError = () => {
82
+ setAuthStatus((prev) => (prev.kind === "unknown" ? { kind: "anon" } : prev));
83
+ };
84
+ try {
85
+ const res = await fetch("/api/auth/status", { cache: "no-store" });
86
+ if (!res.ok) {
87
+ fallbackToAnonOnError();
88
+ return;
89
+ }
90
+ const body = (await res.json()) as {
91
+ authenticated?: boolean;
92
+ user?: { id: string; email: string };
93
+ reminder?: Reminder | null;
94
+ };
95
+ if (body.authenticated && body.user) {
96
+ setAuthStatus({ kind: "authed", user: body.user });
97
+ setReminder(body.reminder ?? null);
98
+ } else {
99
+ setAuthStatus({ kind: "anon" });
100
+ setReminder(null);
101
+ }
102
+ } catch {
103
+ fallbackToAnonOnError();
104
+ }
105
+ }, []);
106
+
107
+ useEffect(() => {
108
+ void refreshStatus();
109
+ const REFRESH_MIN_INTERVAL_MS = 5_000;
110
+ const maybeRefresh = () => {
111
+ if (Date.now() - lastRefreshAtRef.current < REFRESH_MIN_INTERVAL_MS) return;
112
+ void refreshStatus();
113
+ };
114
+ const onFocus = () => maybeRefresh();
115
+ const onVisibility = () => {
116
+ if (document.visibilityState === "visible") maybeRefresh();
117
+ };
118
+ window.addEventListener("focus", onFocus);
119
+ document.addEventListener("visibilitychange", onVisibility);
120
+ return () => {
121
+ window.removeEventListener("focus", onFocus);
122
+ document.removeEventListener("visibilitychange", onVisibility);
123
+ };
124
+ }, [refreshStatus]);
125
+
126
+ useEffect(() => {
127
+ if (ctaShownRef.current) return;
128
+ if (authStatus.kind === "unknown") return;
129
+ ctaShownRef.current = true;
130
+ capture("audit_reminder_cta_shown", {
131
+ auth_state: authStatus.kind,
132
+ has_existing_reminder: reminder !== null,
133
+ source: "come_back_better_section",
134
+ });
135
+ }, [authStatus, capture, reminder]);
136
+
137
+ const persistReminder = useCallback(
138
+ async (inDays: number): Promise<Reminder | null> => {
139
+ const controller = new AbortController();
140
+ const timer = setTimeout(() => controller.abort(), 10_000);
141
+ try {
142
+ setReminderBusy(true);
143
+ const res = await fetch("/api/auth/reminder", {
144
+ method: "POST",
145
+ headers: { "content-type": "application/json" },
146
+ body: JSON.stringify({ in_days: inDays }),
147
+ signal: controller.signal,
148
+ });
149
+ if (!res.ok) {
150
+ if (res.status === 401) {
151
+ setAuthStatus({ kind: "anon" });
152
+ setReminder(null);
153
+ }
154
+ capture("audit_reminder_saved", {
155
+ status: `http_${res.status}`,
156
+ source: "come_back_better_section",
157
+ cadence_days: inDays,
158
+ });
159
+ return null;
160
+ }
161
+ const body = (await res.json()) as { reminder?: Reminder };
162
+ capture("audit_reminder_saved", {
163
+ status: body.reminder ? "success" : "empty",
164
+ source: "come_back_better_section",
165
+ cadence_days: inDays,
166
+ });
167
+ return body.reminder ?? null;
168
+ } catch (err) {
169
+ const kind = isAbortError(err) ? "timeout" : "error";
170
+ capture("audit_reminder_saved", {
171
+ status: kind,
172
+ source: "come_back_better_section",
173
+ cadence_days: inDays,
174
+ });
175
+ return null;
176
+ } finally {
177
+ clearTimeout(timer);
178
+ setReminderBusy(false);
179
+ }
180
+ },
181
+ [capture],
182
+ );
183
+
184
+ const handleCadenceClick = useCallback(
185
+ async (next: Cadence) => {
186
+ setCadence(next);
187
+ capture("audit_reminder_cta_clicked", {
188
+ auth_state: authStatus.kind,
189
+ has_existing_reminder: reminder !== null,
190
+ cadence_days: next,
191
+ source: "come_back_better_section",
192
+ });
193
+ if (authStatus.kind === "authed") {
194
+ const saved = await persistReminder(next);
195
+ if (saved) setReminder(saved);
196
+ return;
197
+ }
198
+ if (authStatus.kind === "anon") {
199
+ setDialogOpen(true);
200
+ }
201
+ },
202
+ [authStatus, capture, persistReminder, reminder],
203
+ );
204
+
205
+ const handleAuthed = useCallback(
206
+ async (user: AuthedUser) => {
207
+ setAuthStatus({ kind: "authed", user });
208
+ capture("audit_auth_completed", {
209
+ source: "come_back_better_section",
210
+ });
211
+ const saved = await persistReminder(cadence);
212
+ if (saved) setReminder(saved);
213
+ },
214
+ [cadence, capture, persistReminder],
215
+ );
216
+
217
+ const handleInvite = useCallback(() => {
218
+ capture("audit_perks_invite_clicked", {
219
+ source: "come_back_better_section",
220
+ auth_state: authStatus.kind,
221
+ });
222
+ // Unauthed users go through the AuthDialog first so we have a sender
223
+ // identity to Cc on the invite email.
224
+ if (authStatus.kind !== "authed") {
225
+ setDialogOpen(true);
226
+ return;
227
+ }
228
+ setInviteDialogOpen(true);
229
+ }, [authStatus.kind, capture]);
230
+
231
+ const handleRerunInline = useCallback(() => {
232
+ if (isRunning) return;
233
+ onRerun();
234
+ }, [isRunning, onRerun]);
235
+
236
+ const days = reminder ? daysUntil(reminder.next_audit_at) : 0;
237
+
238
+ return (
239
+ <section className="audit-sec" data-screen-label="05 Come back better">
240
+ <div className="audit-sec-head">
241
+ <span className="audit-sec-eyebrow">
242
+ <span className="ix">05</span>{"// come back better"}
243
+ </span>
244
+ </div>
245
+ <h2 className="audit-sec-title">build the habit</h2>
246
+
247
+ <div className="cbb-grid">
248
+ {/* Reminder card */}
249
+ <div className="cbb-card">
250
+ <div className="cbb-card-title">set a reminder</div>
251
+ <div className="cbb-card-sub">
252
+ {reminder
253
+ ? `next audit set for ${formatNextAudit(reminder.next_audit_at)} · in ${days} day${days === 1 ? "" : "s"}.`
254
+ : "we'll nudge you when your next audit is due. pick the cadence:"}
255
+ </div>
256
+ <div className="cadence-row">
257
+ {REMINDER_OPTIONS.map((d) => (
258
+ <button
259
+ key={d}
260
+ type="button"
261
+ className={`cadence-btn${cadence === d ? " on" : ""}`}
262
+ disabled={reminderBusy || authStatus.kind === "unknown"}
263
+ onClick={() => void handleCadenceClick(d)}
264
+ >
265
+ {d}d
266
+ </button>
267
+ ))}
268
+ </div>
269
+ <button
270
+ type="button"
271
+ className="cbb-link"
272
+ disabled={isRunning}
273
+ onClick={handleRerunInline}
274
+ >
275
+ {isRunning ? "scanning…" : "or re-audit now →"}
276
+ </button>
277
+ </div>
278
+
279
+ {/* Perks card */}
280
+ <div className="cbb-card">
281
+ <div className="cbb-card-title">unlock failproof perks</div>
282
+ <div className="cbb-card-sub">{PERKS_PERK}</div>
283
+ <button type="button" className="invite-btn" onClick={handleInvite}>
284
+ invite a friend
285
+ </button>
286
+ <div className="cbb-foot">
287
+ {"// invites are sent from failproof.ai, Cc'd to you, with a link to run their own audit."}
288
+ </div>
289
+ </div>
290
+ </div>
291
+
292
+ <InviteDialog
293
+ open={inviteDialogOpen}
294
+ source="come_back_better_section"
295
+ onClose={() => setInviteDialogOpen(false)}
296
+ onUnauthorized={() => {
297
+ // Session expired between probe and submit — flip back to anon
298
+ // and bounce through the AuthDialog so the user re-auths.
299
+ setAuthStatus({ kind: "anon" });
300
+ setReminder(null);
301
+ setDialogOpen(true);
302
+ }}
303
+ />
304
+
305
+ <AuthDialog
306
+ open={dialogOpen}
307
+ source="return_section"
308
+ onClose={() => setDialogOpen(false)}
309
+ onAuthed={(u) => {
310
+ setDialogOpen(false);
311
+ void handleAuthed(u);
312
+ }}
313
+ />
314
+ </section>
315
+ );
316
+ }
@@ -32,7 +32,13 @@ export function EmptyState({ mode, running, onStarted, onCompleted }: Props) {
32
32
  });
33
33
  onStarted();
34
34
  try {
35
- await triggerRun({ cli: [], since: "30d" });
35
+ // since:"all" — scan the user's entire session history, not a window.
36
+ await triggerRun({ cli: [], since: "all" });
37
+ } catch (err) {
38
+ // A failed first run falls back to the empty state via onCompleted's
39
+ // refetch (no cache was written). Swallow here so it doesn't surface as
40
+ // an unhandled promise rejection on the click handler.
41
+ console.error("audit first run failed:", err);
36
42
  } finally {
37
43
  await onCompleted();
38
44
  }
@@ -82,7 +88,7 @@ export function EmptyState({ mode, running, onStarted, onCompleted }: Props) {
82
88
  {running ? "[ scanning… ]" : "[ run audit ]"}
83
89
  </button>
84
90
  <span className="empty-meta">
85
- scans the last 30 days · all installed CLIs · 10–30s
91
+ scans all sessions · all installed CLIs · may take a while
86
92
  </span>
87
93
  </div>
88
94
  </div>