six-degrees 0.6.0 → 0.7.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 (557) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +3 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +14 -14
  5. package/.next/standalone/.next/routes-manifest.json +18 -0
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.rsc +26 -18
  16. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +26 -18
  17. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +25 -16
  18. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  19. package/.next/standalone/.next/server/app/api/admin-delete/route.js +2 -2
  20. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-update/route.js +2 -2
  22. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/bridges/route.js +2 -2
  24. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bulk-import/route.js +2 -2
  26. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +3 -3
  28. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -3
  30. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/connections/route.js +2 -2
  32. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/backup/route/app-paths-manifest.json +3 -0
  34. package/.next/standalone/.next/server/app/api/data/backup/route/build-manifest.json +12 -0
  35. package/.next/standalone/.next/server/app/api/data/backup/route/server-reference-manifest.json +4 -0
  36. package/.next/standalone/.next/server/app/api/data/backup/route.js +7 -0
  37. package/.next/standalone/.next/server/app/api/data/backup/route.js.map +5 -0
  38. package/.next/standalone/.next/server/app/api/data/backup/route.js.nft.json +1 -0
  39. package/.next/standalone/.next/server/app/api/data/backup/route_client-reference-manifest.js +3 -0
  40. package/.next/standalone/.next/server/app/api/data/csv/route/app-paths-manifest.json +3 -0
  41. package/.next/standalone/.next/server/app/api/data/csv/route/build-manifest.json +12 -0
  42. package/.next/standalone/.next/server/app/api/data/csv/route/server-reference-manifest.json +4 -0
  43. package/.next/standalone/.next/server/app/api/data/csv/route.js +8 -0
  44. package/.next/standalone/.next/server/app/api/data/csv/route.js.map +5 -0
  45. package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -0
  46. package/.next/standalone/.next/server/app/api/data/csv/route_client-reference-manifest.js +3 -0
  47. package/.next/standalone/.next/server/app/api/data/export/route.js +1 -1
  48. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/data/import/route.js +1 -1
  50. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/data/restart/route.js +1 -1
  52. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/data/restore/route/app-paths-manifest.json +3 -0
  54. package/.next/standalone/.next/server/app/api/data/restore/route/build-manifest.json +12 -0
  55. package/.next/standalone/.next/server/app/api/data/restore/route/server-reference-manifest.json +4 -0
  56. package/.next/standalone/.next/server/app/api/data/restore/route.js +7 -0
  57. package/.next/standalone/.next/server/app/api/data/restore/route.js.map +5 -0
  58. package/.next/standalone/.next/server/app/api/data/restore/route.js.nft.json +1 -0
  59. package/.next/standalone/.next/server/app/api/data/restore/route_client-reference-manifest.js +3 -0
  60. package/.next/standalone/.next/server/app/api/data/reveal/route.js +1 -1
  61. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/data/route.js +1 -1
  63. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +2 -2
  65. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
  67. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/api/network/route.js +2 -2
  69. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/api/notifications/route.js +2 -2
  71. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/api/outreach/route.js +2 -2
  73. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/api/queue/route.js +2 -2
  75. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/api/scraper/route.js +4 -2
  77. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/api/settings/route.js +3 -3
  79. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  80. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +3 -3
  81. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +3 -3
  83. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/setup-profile/route.js +2 -2
  85. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/api/social/crm/route.js +1 -1
  87. package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
  88. package/.next/standalone/.next/server/app/api/social/messages/route.js +1 -1
  89. package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
  90. package/.next/standalone/.next/server/app/api/social/route.js +2 -2
  91. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  92. package/.next/standalone/.next/server/app/api/unlock/route.js +2 -2
  93. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  95. package/.next/standalone/.next/server/app/api/update-images/route.js +2 -2
  96. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/api/users/route.js +2 -2
  98. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  99. package/.next/standalone/.next/server/app/avatars/[file]/route.js +1 -1
  100. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/import/page.js +1 -1
  102. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  103. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  104. package/.next/standalone/.next/server/app/import.html +1 -1
  105. package/.next/standalone/.next/server/app/import.rsc +32 -24
  106. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +32 -24
  107. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  108. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +23 -18
  109. package/.next/standalone/.next/server/app/index.html +1 -1
  110. package/.next/standalone/.next/server/app/index.rsc +29 -22
  111. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +20 -15
  112. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +29 -22
  113. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  114. package/.next/standalone/.next/server/app/launch/page.js +1 -1
  115. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/app/launch.html +2 -2
  118. package/.next/standalone/.next/server/app/launch.rsc +32 -24
  119. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +32 -24
  120. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  121. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +23 -18
  122. package/.next/standalone/.next/server/app/page.js +1 -1
  123. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  125. package/.next/standalone/.next/server/app/paths/page.js +1 -1
  126. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/app/paths.html +1 -1
  129. package/.next/standalone/.next/server/app/paths.rsc +32 -24
  130. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +32 -24
  131. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  132. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +21 -18
  133. package/.next/standalone/.next/server/app/profile/page.js +1 -1
  134. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/app/profile.html +1 -1
  137. package/.next/standalone/.next/server/app/profile.rsc +32 -24
  138. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +32 -24
  139. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  140. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +23 -18
  141. package/.next/standalone/.next/server/app/queue/page.js +1 -1
  142. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  143. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  144. package/.next/standalone/.next/server/app/queue.html +1 -1
  145. package/.next/standalone/.next/server/app/queue.rsc +32 -24
  146. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +32 -24
  147. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  148. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +20 -18
  149. package/.next/standalone/.next/server/app/scores/page.js +1 -1
  150. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  151. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  152. package/.next/standalone/.next/server/app/scores.html +1 -1
  153. package/.next/standalone/.next/server/app/scores.rsc +32 -24
  154. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +32 -24
  155. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  156. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +23 -18
  157. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  158. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  159. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  160. package/.next/standalone/.next/server/app/settings.html +1 -1
  161. package/.next/standalone/.next/server/app/settings.rsc +32 -24
  162. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +32 -24
  163. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  164. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +20 -18
  165. package/.next/standalone/.next/server/app/setup/page.js +1 -1
  166. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  167. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  168. package/.next/standalone/.next/server/app/setup.html +1 -1
  169. package/.next/standalone/.next/server/app/setup.rsc +32 -24
  170. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +32 -24
  171. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  172. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +21 -18
  173. package/.next/standalone/.next/server/app/social/page.js +1 -1
  174. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  175. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  176. package/.next/standalone/.next/server/app/social.html +1 -1
  177. package/.next/standalone/.next/server/app/social.rsc +32 -24
  178. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +32 -24
  179. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
  180. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +23 -18
  181. package/.next/standalone/.next/server/app-paths-manifest.json +3 -0
  182. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  183. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
  184. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__15wc9e-._.js → [root-of-the-server]__063s0x7._.js} +2 -2
  185. package/.next/standalone/.next/server/chunks/[root-of-the-server]__066rab1._.js +7 -0
  186. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  187. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__13dx4sd._.js → [root-of-the-server]__0_4y8z7._.js} +3 -3
  188. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fooke1._.js +7 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  195. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t6y79e._.js +7 -0
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x29muh._.js +7 -0
  198. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a_6x37._.js +8 -0
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an_8pa._.js +7 -0
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +1 -1
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ducpz2._.js +3 -0
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +1 -1
  210. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xhyinn._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +1 -1
  213. package/.next/standalone/.next/server/chunks/_02twlca._.js +1 -1
  214. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  215. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
  216. package/.next/standalone/.next/server/chunks/_0sqqpzb._.js +3 -0
  217. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  218. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
  219. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  220. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  221. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_backup_route_actions_0zoj04p.js +3 -0
  222. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_csv_route_actions_0l6m7lg.js +3 -0
  223. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restore_route_actions_0-b05bv.js +3 -0
  224. package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +4 -0
  225. package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +4 -0
  226. package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +4 -0
  227. package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +174 -0
  228. package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +1 -1
  229. package/.next/standalone/.next/server/chunks/lib_rpc_005abye.js +3 -0
  230. package/.next/standalone/.next/server/chunks/lib_rpc_0b17r6v.js +3 -0
  231. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +7 -0
  232. package/.next/standalone/.next/server/chunks/lib_rpc_0doq0t-.js +3 -0
  233. package/.next/standalone/.next/server/chunks/lib_rpc_0fd2rlw.js +3 -0
  234. package/.next/standalone/.next/server/chunks/lib_rpc_0is4qoc.js +3 -0
  235. package/.next/standalone/.next/server/chunks/lib_rpc_0jnfp2e.js +3 -0
  236. package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +3 -0
  237. package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +3 -0
  238. package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +3 -0
  239. package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +3 -0
  240. package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +3 -0
  241. package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +3 -0
  242. package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +3 -0
  243. package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +3 -0
  244. package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +3 -0
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qd8pzm._.js +3 -0
  246. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qdeuyo._.js → [root-of-the-server]__1-012q1._.js} +2 -2
  247. package/.next/standalone/.next/server/chunks/ssr/_01o3i5y._.js +7 -0
  248. package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +7 -0
  249. package/.next/standalone/.next/server/chunks/ssr/_06rgzfh._.js +3 -0
  250. package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +3 -0
  251. package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +3 -0
  252. package/.next/standalone/.next/server/chunks/ssr/_0gy11ar._.js +3 -0
  253. package/.next/standalone/.next/server/chunks/ssr/_0hb-dhy._.js +3 -0
  254. package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +3 -0
  255. package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +3 -0
  256. package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +1 -1
  257. package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +2 -2
  258. package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +2 -2
  259. package/.next/standalone/.next/server/chunks/ssr/_1k9qfsk._.js +3 -0
  260. package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
  261. package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +1 -1
  262. package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +25 -2
  263. package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +25 -2
  264. package/.next/standalone/.next/server/chunks/ssr/app_components_CompanyScores_1g0mytz.js +1 -1
  265. package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
  266. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  267. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +25 -13
  268. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
  269. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  270. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +11 -11
  271. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  272. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +12 -13
  273. package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
  274. package/.next/standalone/.next/server/chunks/ssr/{lib_1c4ieuw._.js → lib_csv_1ttiu_h.js} +2 -2
  275. package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
  276. package/.next/standalone/.next/server/chunks/ssr/lib_separation_1305zsx.js +3 -0
  277. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +1 -1
  278. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  279. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  280. package/.next/standalone/.next/server/pages/404.html +1 -1
  281. package/.next/standalone/.next/server/pages/500.html +1 -1
  282. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  283. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  284. package/.next/standalone/.next/static/chunks/03-_esd2x_yk6.js +1 -0
  285. package/.next/standalone/.next/static/chunks/03-demj2iy-0p.js +44 -0
  286. package/.next/standalone/.next/static/chunks/0_1r-3csplxnj.js +1 -0
  287. package/.next/standalone/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
  288. package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +21 -0
  289. package/.next/standalone/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
  290. package/.next/standalone/.next/static/chunks/0p3g5tfostsep.js +7 -0
  291. package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
  292. package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +34 -0
  293. package/.next/standalone/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
  294. package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
  295. package/.next/standalone/.next/static/chunks/1qbjermbf68xj.js +1 -0
  296. package/.next/standalone/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
  297. package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +57 -0
  298. package/.next/standalone/.next/static/chunks/260bomtr214m8.js +34 -0
  299. package/.next/standalone/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
  300. package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +34 -0
  301. package/.next/standalone/.next/static/chunks/2z1-q0_vunbql.js +2 -0
  302. package/.next/standalone/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
  303. package/.next/standalone/.next/static/chunks/34g73qhayuevu.js +1 -0
  304. package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +1 -0
  305. package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +1 -0
  306. package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +3 -0
  307. package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
  308. package/.next/standalone/.next/static/chunks/43pg2sursiypz.js +1 -0
  309. package/.next/standalone/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
  310. package/.next/standalone/CHANGELOG.md +143 -0
  311. package/.next/standalone/README.md +71 -49
  312. package/.next/standalone/SECURITY.md +17 -11
  313. package/.next/standalone/app/api/data/backup/route.js +25 -0
  314. package/.next/standalone/app/api/data/csv/route.js +74 -0
  315. package/.next/standalone/app/api/data/export/route.js +1 -1
  316. package/.next/standalone/app/api/data/restore/route.js +48 -0
  317. package/.next/standalone/app/api/data/reveal/route.js +19 -6
  318. package/.next/standalone/app/api/data/route.js +15 -4
  319. package/.next/standalone/app/api/ingest/route.js +1 -1
  320. package/.next/standalone/app/api/queue/route.js +1 -1
  321. package/.next/standalone/app/api/scraper/route.js +120 -32
  322. package/.next/standalone/app/api/social/crm/route.js +2 -1
  323. package/.next/standalone/app/components/AppHeader.js +20 -16
  324. package/.next/standalone/app/components/AppTabs.js +8 -8
  325. package/.next/standalone/app/components/AutoScanButton.js +2 -2
  326. package/.next/standalone/app/components/Avatar.js +1 -1
  327. package/.next/standalone/app/components/ChainView.js +184 -112
  328. package/.next/standalone/app/components/CirclePanel.js +16 -16
  329. package/.next/standalone/app/components/ClusterSpinner.js +64 -0
  330. package/.next/standalone/app/components/CompanyScores.js +20 -20
  331. package/.next/standalone/app/components/EmptyState.js +39 -18
  332. package/.next/standalone/app/components/FieldStep.js +15 -13
  333. package/.next/standalone/app/components/FilterPanel.js +17 -17
  334. package/.next/standalone/app/components/ForceGraph.js +49 -14
  335. package/.next/standalone/app/components/GalaxyLab.js +33 -33
  336. package/.next/standalone/app/components/GridView.js +18 -18
  337. package/.next/standalone/app/components/LegacyScoresCard.js +10 -11
  338. package/.next/standalone/app/components/LinkedInLimits.js +24 -19
  339. package/.next/standalone/app/components/LiquidGlass.js +99 -0
  340. package/.next/standalone/app/components/ListView.js +20 -20
  341. package/.next/standalone/app/components/MapControls.js +5 -5
  342. package/.next/standalone/app/components/MappingProgress.js +16 -16
  343. package/.next/standalone/app/components/NoteDetail.js +13 -13
  344. package/.next/standalone/app/components/OnboardingGate.js +4 -4
  345. package/.next/standalone/app/components/OutlinkQuest.js +25 -25
  346. package/.next/standalone/app/components/PathsAnalyzer.js +37 -36
  347. package/.next/standalone/app/components/PeopleMap.js +21 -21
  348. package/.next/standalone/app/components/RingsView.js +11 -11
  349. package/.next/standalone/app/components/ScanRadar.js +84 -39
  350. package/.next/standalone/app/components/ScanStatusBar.js +22 -18
  351. package/.next/standalone/app/components/ScanTrail.js +8 -5
  352. package/.next/standalone/app/components/SeparationView.js +168 -104
  353. package/.next/standalone/app/components/Sidebar.js +161 -161
  354. package/.next/standalone/app/components/StaleServerBanner.js +4 -4
  355. package/.next/standalone/app/components/TheirCircle.js +26 -26
  356. package/.next/standalone/app/components/ThemeDefs.js +42 -0
  357. package/.next/standalone/app/components/ThemeLoader.js +20 -1
  358. package/.next/standalone/app/components/UpdatePanel.js +22 -22
  359. package/.next/standalone/app/components/UserProvider.js +22 -15
  360. package/.next/standalone/app/components/settings/AppearanceSection.js +47 -24
  361. package/.next/standalone/app/components/settings/DataSection.js +276 -109
  362. package/.next/standalone/app/components/settings/NetworkHealthSection.js +13 -13
  363. package/.next/standalone/app/components/settings/SectorPicker.js +12 -12
  364. package/.next/standalone/app/components/settings/SectorSection.js +13 -14
  365. package/.next/standalone/app/components/settings/TierSection.js +9 -9
  366. package/.next/standalone/app/components/settings/TitleSection.js +11 -11
  367. package/.next/standalone/app/components/settings/UsageSection.js +313 -0
  368. package/.next/standalone/app/components/ui.js +8 -8
  369. package/.next/standalone/app/globals.css +159 -2
  370. package/.next/standalone/app/import/page.js +94 -38
  371. package/.next/standalone/app/launch/page.js +5 -5
  372. package/.next/standalone/app/layout.js +6 -2
  373. package/.next/standalone/app/page.js +53 -37
  374. package/.next/standalone/app/paths/page.js +63 -63
  375. package/.next/standalone/app/profile/page.js +58 -58
  376. package/.next/standalone/app/queue/page.js +54 -54
  377. package/.next/standalone/app/scores/page.js +2 -2
  378. package/.next/standalone/app/settings/page.js +16 -13
  379. package/.next/standalone/app/setup/page.js +221 -125
  380. package/.next/standalone/app/social/Crm.js +63 -62
  381. package/.next/standalone/app/social/SocialHub.js +22 -22
  382. package/.next/standalone/app/social/Thread.js +7 -7
  383. package/.next/standalone/app/social/page.js +2 -2
  384. package/.next/standalone/lib/backups.js +458 -0
  385. package/.next/standalone/lib/chain-layout.js +32 -0
  386. package/.next/standalone/lib/csv-store.js +131 -0
  387. package/.next/standalone/lib/csv.js +137 -39
  388. package/.next/standalone/lib/data-folder.js +19 -3
  389. package/.next/standalone/lib/data-import.js +5 -2
  390. package/.next/standalone/lib/db-client.js +78 -20
  391. package/.next/standalone/lib/demo.js +5 -4
  392. package/.next/standalone/lib/galaxy-export.js +9 -2
  393. package/.next/standalone/lib/galaxy-lab.js +19 -5
  394. package/.next/standalone/lib/galaxy-layouts.js +43 -0
  395. package/.next/standalone/lib/gate.js +10 -4
  396. package/.next/standalone/lib/ingest.js +1 -1
  397. package/.next/standalone/lib/linkedin-limits.js +212 -11
  398. package/.next/standalone/lib/liquid-glass.js +59 -0
  399. package/.next/standalone/lib/notifications.js +13 -0
  400. package/.next/standalone/lib/scan-pace.js +11 -0
  401. package/.next/standalone/lib/scan-progress.js +20 -0
  402. package/.next/standalone/lib/scan-risk.js +44 -0
  403. package/.next/standalone/lib/scanner-setup.js +97 -15
  404. package/.next/standalone/lib/scraper-client.js +17 -3
  405. package/.next/standalone/lib/search-risk.js +7 -3
  406. package/.next/standalone/lib/settings-client.js +5 -3
  407. package/.next/standalone/lib/settings.js +10 -0
  408. package/.next/standalone/lib/social-store.js +3 -2
  409. package/.next/standalone/lib/synced-setting.js +46 -0
  410. package/.next/standalone/lib/theme-store.js +42 -10
  411. package/.next/standalone/lib/themes.js +111 -21
  412. package/.next/standalone/lib/usage.js +195 -0
  413. package/.next/standalone/package-lock.json +2 -2
  414. package/.next/standalone/package.json +1 -1
  415. package/.next/standalone/scripts/scrape.py +26 -2
  416. package/.next/static/chunks/03-_esd2x_yk6.js +1 -0
  417. package/.next/static/chunks/03-demj2iy-0p.js +44 -0
  418. package/.next/static/chunks/0_1r-3csplxnj.js +1 -0
  419. package/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
  420. package/.next/static/chunks/0e3g0sil79cus.js +21 -0
  421. package/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
  422. package/.next/static/chunks/0p3g5tfostsep.js +7 -0
  423. package/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
  424. package/.next/static/chunks/163b-20vwa06i.js +34 -0
  425. package/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
  426. package/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
  427. package/.next/static/chunks/1qbjermbf68xj.js +1 -0
  428. package/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
  429. package/.next/static/chunks/23_1z47al92nz.js +57 -0
  430. package/.next/static/chunks/260bomtr214m8.js +34 -0
  431. package/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
  432. package/.next/static/chunks/2xk2ra4piiuod.js +34 -0
  433. package/.next/static/chunks/2z1-q0_vunbql.js +2 -0
  434. package/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
  435. package/.next/static/chunks/34g73qhayuevu.js +1 -0
  436. package/.next/static/chunks/3b0-14cxqsprt.js +1 -0
  437. package/.next/static/chunks/3jarus1_38s2z.js +1 -0
  438. package/.next/static/chunks/3ssu341hav_sq.css +3 -0
  439. package/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
  440. package/.next/static/chunks/43pg2sursiypz.js +1 -0
  441. package/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
  442. package/CHANGELOG.md +143 -0
  443. package/README.md +71 -49
  444. package/SECURITY.md +17 -11
  445. package/lib/backups.js +458 -0
  446. package/lib/chain-layout.js +32 -0
  447. package/lib/csv-store.js +131 -0
  448. package/lib/csv.js +137 -39
  449. package/lib/data-folder.js +19 -3
  450. package/lib/data-import.js +5 -2
  451. package/lib/db-client.js +78 -20
  452. package/lib/demo.js +5 -4
  453. package/lib/galaxy-export.js +9 -2
  454. package/lib/galaxy-lab.js +19 -5
  455. package/lib/galaxy-layouts.js +43 -0
  456. package/lib/gate.js +10 -4
  457. package/lib/ingest.js +1 -1
  458. package/lib/linkedin-limits.js +212 -11
  459. package/lib/liquid-glass.js +59 -0
  460. package/lib/notifications.js +13 -0
  461. package/lib/scan-pace.js +11 -0
  462. package/lib/scan-progress.js +20 -0
  463. package/lib/scan-risk.js +44 -0
  464. package/lib/scanner-setup.js +97 -15
  465. package/lib/scraper-client.js +17 -3
  466. package/lib/search-risk.js +7 -3
  467. package/lib/settings-client.js +5 -3
  468. package/lib/settings.js +10 -0
  469. package/lib/social-store.js +3 -2
  470. package/lib/synced-setting.js +46 -0
  471. package/lib/theme-store.js +42 -10
  472. package/lib/themes.js +111 -21
  473. package/lib/usage.js +195 -0
  474. package/package.json +1 -1
  475. package/scripts/scrape.py +26 -2
  476. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +0 -3
  477. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +0 -3
  478. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +0 -3
  479. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +0 -3
  480. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +0 -3
  481. package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +0 -8
  482. package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +0 -8
  483. package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +0 -8
  484. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +0 -174
  485. package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +0 -3
  486. package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +0 -3
  487. package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +0 -3
  488. package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +0 -3
  489. package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +0 -3
  490. package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +0 -3
  491. package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +0 -3
  492. package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +0 -3
  493. package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +0 -3
  494. package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +0 -3
  495. package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +0 -3
  496. package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +0 -3
  497. package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +0 -3
  498. package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +0 -3
  499. package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +0 -3
  500. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +0 -3
  501. package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +0 -3
  502. package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +0 -3
  503. package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +0 -3
  504. package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +0 -3
  505. package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +0 -3
  506. package/.next/standalone/.next/server/chunks/ssr/_1-658fq._.js +0 -3
  507. package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +0 -3
  508. package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +0 -3
  509. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +0 -3
  510. package/.next/standalone/.next/static/chunks/02pq76u7y4v9-.js +0 -22
  511. package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
  512. package/.next/standalone/.next/static/chunks/0i02yxppzds1o.js +0 -1
  513. package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
  514. package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +0 -1
  515. package/.next/standalone/.next/static/chunks/0vo-l8hxneba6.css +0 -3
  516. package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
  517. package/.next/standalone/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
  518. package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +0 -34
  519. package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
  520. package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
  521. package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
  522. package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +0 -45
  523. package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +0 -1
  524. package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +0 -11
  525. package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
  526. package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +0 -1
  527. package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
  528. package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +0 -1
  529. package/.next/standalone/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
  530. package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +0 -11
  531. package/.next/static/chunks/02pq76u7y4v9-.js +0 -22
  532. package/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
  533. package/.next/static/chunks/0i02yxppzds1o.js +0 -1
  534. package/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
  535. package/.next/static/chunks/0oyslj62jkubd.js +0 -1
  536. package/.next/static/chunks/0vo-l8hxneba6.css +0 -3
  537. package/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
  538. package/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
  539. package/.next/static/chunks/172e10mi-tpo_.js +0 -34
  540. package/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
  541. package/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
  542. package/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
  543. package/.next/static/chunks/1m3017gp790pw.js +0 -45
  544. package/.next/static/chunks/1t20e8u0st7ep.js +0 -1
  545. package/.next/static/chunks/2-ch9rm27eril.js +0 -11
  546. package/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
  547. package/.next/static/chunks/3bp284gjf7dyq.js +0 -1
  548. package/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
  549. package/.next/static/chunks/3l4t8je6iguzc.js +0 -1
  550. package/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
  551. package/.next/static/chunks/42m0bqr8pbovz.js +0 -11
  552. /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
  553. /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
  554. /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
  555. /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
  556. /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
  557. /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
@@ -89,7 +89,7 @@ export function refreshNotifications({ added = [], checked = 0, tierOf = () => n
89
89
  if (!added.length) {
90
90
  return [{
91
91
  user_id, type: 'refresh_summary', title: 'Network up to date',
92
- message: `Checked ${checked} connections — no new additions`, icon: '✓',
92
+ message: `Checked ${checked} connections: no new additions`, icon: '✓',
93
93
  }];
94
94
  }
95
95
  const out = [{
@@ -4,6 +4,10 @@
4
4
  // reads them with the same rules, and writes only the two things a person
5
5
  // changes here — the budget, and lifting a cooldown.
6
6
  //
7
+ // Settings → LinkedIn usage reads the same record more closely (linkedinUsage):
8
+ // the last hour and the last 7 days, when the next search frees up, and the
9
+ // last time LinkedIn pushed back. lib/usage.js says what the numbers mean.
10
+ //
7
11
  // The budget is searches (a day and a month) and profile views (a day). Profile
8
12
  // views got an account restricted (TRAPS §16), so their cap has no "no limit",
9
13
  // and the scanner also keeps any two a minute apart. Searches did too, at 373 in
@@ -11,10 +15,13 @@
11
15
  //
12
16
  // They belong to the LinkedIn account, not to a profile in the app. TRAPS §35.
13
17
 
14
- import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, lstatSync } from 'node:fs';
18
+ import {
19
+ readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, lstatSync, readdirSync, openSync, readSync, closeSync,
20
+ } from 'node:fs';
15
21
  import path from 'node:path';
16
22
  import { durable } from './durable.js';
17
23
  import { PACES, DEFAULT_PACE } from './scan-pace.js';
24
+ import { countAfter } from './usage.js';
18
25
 
19
26
  export const DEFAULT_LIMITS = { daily: 50, monthly: 250, profiles: 50, pace: DEFAULT_PACE };
20
27
  export const DAILY_CHOICES = [25, 50, 100, 200, 500];
@@ -94,6 +101,20 @@ export function writeLimits(dir, { daily, monthly, profiles, pace } = {}) {
94
101
  return next;
95
102
  }
96
103
 
104
+ /**
105
+ * The scanner's record as two lists of times (epoch seconds), or null when it
106
+ * can't be read. No record at all is nothing done yet.
107
+ */
108
+ function readActivity(dir) {
109
+ const file = path.join(dir, 'linkedin-activity.json');
110
+ const data = existsSync(file) ? readJson(file) : { searches: [], profiles: [] };
111
+ if (!data || typeof data !== 'object') return null;
112
+ return {
113
+ searches: (Array.isArray(data.searches) ? data.searches : []).map(Number),
114
+ profiles: (Array.isArray(data.profiles) ? data.profiles : []).map(Number),
115
+ };
116
+ }
117
+
97
118
  /**
98
119
  * Searches in the last 24 hours and since LinkedIn's month began, and profile
99
120
  * views in the last 24 hours. An unreadable record reads as the day used up,
@@ -101,11 +122,9 @@ export function writeLimits(dir, { daily, monthly, profiles, pace } = {}) {
101
122
  * would say nothing had been searched.
102
123
  */
103
124
  export function usage(dir, now = Date.now()) {
104
- const file = path.join(dir, 'linkedin-activity.json');
105
- const data = existsSync(file) ? readJson(file) : { searches: [], profiles: [] };
106
- if (!data || typeof data !== 'object') return { searchesToday: Infinity, searchesMonth: 0, profilesToday: Infinity, unreadable: true };
107
- const s = (Array.isArray(data.searches) ? data.searches : []).map(Number);
108
- const p = (Array.isArray(data.profiles) ? data.profiles : []).map(Number);
125
+ const rec = readActivity(dir);
126
+ if (!rec) return { searchesToday: Infinity, searchesMonth: 0, profilesToday: Infinity, unreadable: true };
127
+ const { searches: s, profiles: p } = rec;
109
128
  const dayAgo = now / 1000 - DAY;
110
129
  const month0 = monthStartPacific(now) / 1000;
111
130
  return {
@@ -115,17 +134,42 @@ export function usage(dir, now = Date.now()) {
115
134
  };
116
135
  }
117
136
 
118
- /** The active cooldown { until (ms), reason, setAt }, or null. */
137
+ /** The active cooldown { until (ms), reason, setAt }, or null. A lifted one (liftCooldown) is none. */
119
138
  export function readCooldown(dir, now = Date.now()) {
120
139
  const raw = readJson(path.join(dir, 'linkedin-cooldown.json'));
121
140
  const until = Number(raw?.until) * 1000;
122
- if (!raw || !Number.isFinite(until) || until <= now) return null;
141
+ if (!raw || raw.lifted_at !== undefined || !Number.isFinite(until) || until <= now) return null;
123
142
  return { until, reason: String(raw.reason || 'LinkedIn pushed back'), setAt: Number(raw.set_at) * 1000 || null };
124
143
  }
125
144
 
126
- /** Lift it: the person says LinkedIn works for them again. */
127
- export function liftCooldown(dir) {
128
- try { unlinkSync(path.join(dir, 'linkedin-cooldown.json')); } catch { /* none to lift */ }
145
+ /**
146
+ * Lift it: the person says LinkedIn works for them again. The record is kept,
147
+ * marked lifted, so Settings → LinkedIn usage can still say when LinkedIn last
148
+ * pushed back; deleting it forgot that. Its `until` becomes the moment it was
149
+ * lifted and the old end moves to `was_until`, so everything that reads the
150
+ * file sees a pause that has simply ended, with no change of its own: here
151
+ * (readCooldown, the import's merge) and in the scanner (read_cooldown and
152
+ * set_cooldown in scripts/scrape.py ask only whether `until` is still ahead),
153
+ * so the scanner's pacing is exactly what it was. A pause already over is
154
+ * left alone; a record that can't be read is removed, as before.
155
+ */
156
+ export function liftCooldown(dir, now = Date.now()) {
157
+ const file = path.join(dir, COOLDOWN);
158
+ const raw = readJson(file);
159
+ const until = Number(raw?.until);
160
+ if (!isObject(raw) || !Number.isFinite(until)) {
161
+ try { unlinkSync(file); } catch { /* none to lift */ }
162
+ return;
163
+ }
164
+ const at = now / 1000;
165
+ if (until <= at) return;
166
+ writeJsonAtomic(file, {
167
+ until: at,
168
+ reason: typeof raw.reason === 'string' ? raw.reason : 'LinkedIn pushed back',
169
+ set_at: isTime(raw.set_at) ? raw.set_at : null,
170
+ lifted_at: at,
171
+ was_until: until,
172
+ });
129
173
  }
130
174
 
131
175
  /** Everything the Scan page shows, in one object. */
@@ -144,6 +188,155 @@ export function linkedinState(dir, now = Date.now()) {
144
188
  };
145
189
  }
146
190
 
191
+ // ── Settings → LinkedIn usage ────────────────────────────────────────────────
192
+ // The same record, read more closely. Windows count a time strictly inside
193
+ // them (t > now - window), as usage() and the scanner's linkedin_usage do, so a
194
+ // search exactly 24 hours old has stopped counting.
195
+
196
+ /** How many of `times` (epoch s) fall in the last `seconds` before `now` (ms). */
197
+ export function countWithin(times, now, seconds) {
198
+ const from = now / 1000 - seconds;
199
+ return times.filter((t) => t > from).length;
200
+ }
201
+
202
+ /**
203
+ * When the count in the window next drops under `cap`, in ms: the moment a
204
+ * search (or profile view) is free again. null when one is free now, or with
205
+ * no cap. The scanner says the same for profile views (scrape.py
206
+ * budget_message: the one that stops counting soonest frees the next).
207
+ */
208
+ export function freesAt(times, now, cap, seconds = DAY) {
209
+ if (!cap) return null;
210
+ const live = times.filter((t) => t > now / 1000 - seconds).sort((a, b) => a - b);
211
+ if (live.length < cap) return null;
212
+ return (live[live.length - cap] + seconds) * 1000;
213
+ }
214
+
215
+ /** When the window holds nothing at all again (the newest time turns `seconds` old), in ms; null if it's empty. */
216
+ export function clearAt(times, now, seconds = DAY) {
217
+ const live = times.filter((t) => t > now / 1000 - seconds);
218
+ return live.length ? (Math.max(...live) + seconds) * 1000 : null;
219
+ }
220
+
221
+ // The scanner keeps what the page showed when LinkedIn pushed back as
222
+ // pushback/<local date>_<time>.txt: "reason: …", then "url: …", then the page's
223
+ // text (scrape.py _keep_pushback_evidence). Only the name's time and the first
224
+ // line are read here: the page's address and its text are LinkedIn's page about
225
+ // this account and stay in the file.
226
+ const PUSHBACK_FILE = /^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})(\d{2})\.txt$/;
227
+ const FIRST_LINE_BYTES = 256;
228
+
229
+ /** The reason on a pushback file's first line, or null. Never more of the file. */
230
+ function pushbackReason(file) {
231
+ let fd;
232
+ try {
233
+ fd = openSync(file, 'r');
234
+ const buf = Buffer.alloc(FIRST_LINE_BYTES);
235
+ const n = readSync(fd, buf, 0, FIRST_LINE_BYTES, 0);
236
+ const line = buf.subarray(0, n).toString('utf8').split(/\r?\n/)[0];
237
+ const m = line.match(/^reason: (.+)$/);
238
+ const reason = m && m[1].replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 120);
239
+ // The scanner's reasons are its own short phrases; anything shaped like an address isn't one.
240
+ return reason && !/:\/\/|www\.|linkedin\.com|\/in\//i.test(reason) ? reason : null;
241
+ } catch {
242
+ return null;
243
+ } finally {
244
+ if (fd !== undefined) closeSync(fd);
245
+ }
246
+ }
247
+
248
+ /** The newest pushback file: { at (ms, from its name), reason }, or null. */
249
+ export function newestPushback(dir) {
250
+ let names;
251
+ try {
252
+ // Plain files only: a link or a folder by that name is not the scanner's.
253
+ names = readdirSync(path.join(dir, 'pushback'), { withFileTypes: true })
254
+ .filter((e) => e.isFile() && PUSHBACK_FILE.test(e.name)).map((e) => e.name);
255
+ } catch {
256
+ return null;
257
+ }
258
+ // Newest first by name, which is by time; a name that isn't a real time is passed over.
259
+ for (const name of names.sort().reverse()) {
260
+ const [, y, mo, d, h, mi, s] = name.match(PUSHBACK_FILE).map(Number);
261
+ // The scanner names it by this computer's clock (datetime.now()), as this reads it.
262
+ const when = new Date(y, mo - 1, d, h, mi, s);
263
+ if (when.getMonth() !== mo - 1 || when.getDate() !== d || h > 23 || mi > 59 || s > 59) continue;
264
+ return { at: when.getTime(), reason: pushbackReason(path.join(dir, 'pushback', name)) };
265
+ }
266
+ return null;
267
+ }
268
+
269
+ // One pushback usually leaves both: the page kept, then the pause set, moments
270
+ // (at most the rest of that person's read) apart.
271
+ const SAME_EVENT_MS = 10 * 60 * 1000;
272
+
273
+ /**
274
+ * The last time LinkedIn pushed back, from the cooldown file (active, ended or
275
+ * lifted) and the newest pushback file: { at (ms or null), reason, pausedUntil,
276
+ * liftedAt, active }, or null when there's no sign of one. Times and the
277
+ * reason only.
278
+ */
279
+ export function lastPushback(dir, now = Date.now()) {
280
+ const file = newestPushback(dir);
281
+ const raw = readJson(path.join(dir, COOLDOWN));
282
+ const until = Number(raw?.until);
283
+ const pause = isObject(raw) && Number.isFinite(until) ? {
284
+ at: isTime(raw.set_at) ? raw.set_at * 1000 : null,
285
+ reason: typeof raw.reason === 'string' && raw.reason.trim() ? raw.reason.trim().slice(0, 160) : 'LinkedIn pushed back',
286
+ pausedUntil: (isTime(raw.was_until) ? raw.was_until : until) * 1000,
287
+ liftedAt: isTime(raw.lifted_at) ? raw.lifted_at * 1000 : null,
288
+ } : null;
289
+ const fromFile = file && {
290
+ at: file.at,
291
+ reason: file.reason ? `LinkedIn pushed back: ${file.reason}` : 'LinkedIn pushed back',
292
+ pausedUntil: null,
293
+ liftedAt: null,
294
+ };
295
+ let last;
296
+ if (pause && fromFile && pause.at != null && Math.abs(pause.at - fromFile.at) <= SAME_EVENT_MS) {
297
+ last = { ...pause, at: Math.min(pause.at, fromFile.at) };
298
+ } else if (pause && fromFile) {
299
+ last = (pause.at ?? -Infinity) >= fromFile.at ? pause : fromFile;
300
+ } else {
301
+ last = pause || fromFile;
302
+ }
303
+ if (!last) return null;
304
+ return { ...last, active: !last.liftedAt && last.pausedUntil != null && last.pausedUntil > now };
305
+ }
306
+
307
+ /**
308
+ * Everything Settings → LinkedIn usage shows (GET /api/scraper?usage): the
309
+ * Scan page's state, plus
310
+ * searchesLastHour, searchesWeek counted like searchesToday
311
+ * dayFreesAt when the daily budget has a search free again (null: one is free now)
312
+ * dayClearAt when the last 24 hours hold no searches at all
313
+ * profilesFreeAt when a profile view is free again
314
+ * lastPushback lastPushback() above
315
+ * searchesSincePushback searches made after it (lib/usage.js countAfter)
316
+ * An unreadable record gives nulls, never zeros: it isn't "nothing searched".
317
+ */
318
+ export function linkedinUsage(dir, now = Date.now()) {
319
+ const state = linkedinState(dir, now);
320
+ const rec = readActivity(dir);
321
+ const pushback = lastPushback(dir, now);
322
+ const known = (fn) => (rec ? fn(rec) : null);
323
+ return {
324
+ ...state,
325
+ now,
326
+ searchesToday: rec ? state.searchesToday : null,
327
+ profilesToday: rec ? state.profilesToday : null,
328
+ searchesMonth: rec ? state.searchesMonth : null,
329
+ leftMonth: rec ? state.leftMonth : null,
330
+ searchesLastHour: known((r) => countWithin(r.searches, now, 3600)),
331
+ searchesWeek: known((r) => countWithin(r.searches, now, 7 * DAY)),
332
+ dayFreesAt: known((r) => freesAt(r.searches, now, state.limits.daily)),
333
+ dayClearAt: known((r) => clearAt(r.searches, now)),
334
+ profilesFreeAt: known((r) => freesAt(r.profiles, now, state.limits.profiles)),
335
+ lastPushback: pushback,
336
+ searchesSincePushback: known((r) => countAfter(r.searches, pushback?.at)),
337
+ };
338
+ }
339
+
147
340
  // ── another computer's budget (Settings → Your data → Import) ───────────────
148
341
  // An import carries these three files from the other computer. They describe
149
342
  // the same LinkedIn account, so they are merged with the ones here, never put
@@ -175,6 +368,10 @@ export function budgetFileProblem(name, data) {
175
368
  if (!isTime(data.until)) return 'its pause has no end time';
176
369
  if (data.reason !== undefined && typeof data.reason !== 'string') return 'its pause has a reason that is not text';
177
370
  if (data.set_at !== undefined && !isTime(data.set_at)) return 'its pause has a start time that is not a time';
371
+ // A pause lifted by hand (liftCooldown) says when, and when it would have ended.
372
+ for (const key of ['lifted_at', 'was_until']) {
373
+ if (data[key] !== undefined && !isTime(data[key])) return 'its lifted pause has a time that is not a time';
374
+ }
178
375
  } else if (name === LIMITS) {
179
376
  if (data.daily !== undefined && !DAILY_CHOICES.includes(data.daily)) {
180
377
  return `its daily limit (${String(data.daily).slice(0, 20)}) is not one Six Degrees offers`;
@@ -252,11 +449,15 @@ export function mergeBudgetFiles({ dir, from }) {
252
449
  if (cooldown) {
253
450
  const mine = readJson(here(COOLDOWN));
254
451
  const mineEnds = Number.isFinite(Number(mine?.until)) ? Number(mine.until) : -Infinity;
452
+ // A pause lifted here ends when it was lifted (liftCooldown), so one still
453
+ // on in the copy comes back, as it did when lifting deleted the file.
255
454
  if (cooldown.until > mineEnds) {
256
455
  durable.writeJsonDurably(here(COOLDOWN), {
257
456
  until: cooldown.until,
258
457
  reason: cooldown.reason ?? 'LinkedIn pushed back',
259
458
  set_at: cooldown.set_at ?? null,
459
+ // Lifted in the copy: still lifted here, and still the last pushback on record.
460
+ ...(cooldown.lifted_at !== undefined && { lifted_at: cooldown.lifted_at, was_until: cooldown.was_until ?? cooldown.until }),
260
461
  });
261
462
  }
262
463
  }
@@ -0,0 +1,59 @@
1
+ // Liquid Glass's lensing (Apple, WWDC25 "Meet Liquid Glass"; HIG → Materials):
2
+ // the edge of a pane of glass bends what's behind it, where frosted glass only
3
+ // blurs it. Done the way the good web versions do it in Chromium: a
4
+ // displacement map, drawn here for a panel's exact size, used by an SVG
5
+ // feDisplacementMap that the panel's backdrop-filter runs
6
+ // (app/components/LiquidGlass.js).
7
+ //
8
+ // The map's red channel shifts a pixel across, green up and down; 128 is no
9
+ // shift. Only a band along the edge (the bezel) moves, and it always pulls
10
+ // inward, since a backdrop is clipped to the panel and anything pulled from
11
+ // outside it would come back empty.
12
+
13
+ /**
14
+ * How far, and which way, each pixel of a rounded rectangle w×h (corner r)
15
+ * bends: [dx, dy] in -1…1 for (x, y), strongest at the edge and fading to
16
+ * nothing `bezel` pixels in.
17
+ */
18
+ export function lensAt(x, y, w, h, r, bezel) {
19
+ const px = x + 0.5 - w / 2;
20
+ const py = y + 0.5 - h / 2;
21
+ const qx = Math.abs(px) - (w / 2 - r);
22
+ const qy = Math.abs(py) - (h / 2 - r);
23
+ const ox = Math.max(qx, 0);
24
+ const oy = Math.max(qy, 0);
25
+ const o = Math.hypot(ox, oy);
26
+ const dist = r - (o + Math.min(Math.max(qx, qy), 0)); // pixels in from the edge
27
+ let nx = 0;
28
+ let ny = 0;
29
+ if (o > 0) { nx = ox / o; ny = oy / o; } else if (qx > qy) nx = 1; else ny = 1;
30
+ nx *= -Math.sign(px) || 0;
31
+ ny *= -Math.sign(py) || 0;
32
+ const t = Math.max(0, 1 - dist / bezel);
33
+ const m = t * t;
34
+ return [nx * m, ny * m];
35
+ }
36
+
37
+ /** The displacement map for a w×h panel, as a PNG data URL (needs a canvas: the browser only). */
38
+ export function lensMap(w, h, r, bezel) {
39
+ const c = document.createElement('canvas');
40
+ c.width = w;
41
+ c.height = h;
42
+ const ctx = c.getContext('2d');
43
+ const img = ctx.createImageData(w, h);
44
+ const d = img.data;
45
+ for (let y = 0; y < h; y++) {
46
+ for (let x = 0; x < w; x++) {
47
+ const i = (y * w + x) * 4;
48
+ // Most of a big panel is its middle, which never moves.
49
+ const nearEdge = x < bezel + r || y < bezel + r || x >= w - bezel - r || y >= h - bezel - r;
50
+ const [dx, dy] = nearEdge ? lensAt(x, y, w, h, r, bezel) : [0, 0];
51
+ d[i] = 128 + 127 * dx;
52
+ d[i + 1] = 128 + 127 * dy;
53
+ d[i + 2] = 128;
54
+ d[i + 3] = 255;
55
+ }
56
+ }
57
+ ctx.putImageData(img, 0, 0);
58
+ return c.toDataURL();
59
+ }
@@ -94,6 +94,19 @@ const NAME_IN_TITLE = [
94
94
  * `via` is who you met them through: the notification's own, else the person's
95
95
  * recorded introducer. `people` are everyone it names (a top-company list).
96
96
  */
97
+ /**
98
+ * Which circle a notification is about, as a connection's id (a string), or
99
+ * null: whoever someone came through for "added you back" and "top companies",
100
+ * and whose circle it was for "Scan done". Read from what the note saved, so it
101
+ * needs nothing loaded.
102
+ */
103
+ export function noteCircle(note) {
104
+ const data = typeof note?.data === 'string' ? safeJson(note.data) : note?.data || {};
105
+ if (data.viaId != null) return String(data.viaId);
106
+ if (data.action && data.personId != null) return String(data.personId);
107
+ return null;
108
+ }
109
+
97
110
  export function noteSubject(note, connections = [], degree2 = []) {
98
111
  const all = [...(connections || []), ...(degree2 || [])];
99
112
  const byId = new Map(all.map((r) => [r.id, r]));
@@ -29,6 +29,17 @@ export function paceSeconds(name, searches) {
29
29
  return n * (WORK_SECONDS + p.pagePause) + Math.floor(n / EVERY) * p.chunkCooldown;
30
30
  }
31
31
 
32
+ /**
33
+ * About how long a circle scan takes to show its first people on the map. The
34
+ * scanner saves after its first ten pages (scrape.py SAVE_EVERY_PAGES), so it's
35
+ * those ten at this speed; the longer rest counted after them stands in for
36
+ * opening Chrome and the person's profile first. About 5 minutes at Fast: the
37
+ * Scan page's honest "first circle" (the rest of a network takes days).
38
+ */
39
+ export function firstCircleSeconds(name) {
40
+ return paceSeconds(name, EVERY);
41
+ }
42
+
32
43
  /** About how many searches an hour a speed makes. */
33
44
  export function searchesPerHour(name) {
34
45
  const p = paceOf(name);
@@ -70,6 +70,26 @@ export function scanProgress(log, action) {
70
70
  return null;
71
71
  }
72
72
 
73
+ // "[3/10] Ada Park (A-tier, score 6.2)", with ", carrying on from page 11"
74
+ // after it when a read goes on: the name is everything before the last
75
+ // "(…-tier, score …)".
76
+ const BRIDGE_PERSON = /^\[\d+\/\d+\] (.+) \([^()]*-tier, score [^()]*\)/;
77
+
78
+ /**
79
+ * Whose circle a batch (Map 2nd degree, every paused list) is reading now,
80
+ * by name, from the line it printed as that person started; null for any
81
+ * other job, or before the first. The Scan page's "Watch it fill in" link
82
+ * (app/api/scraper/route.js turns the name into one of your connections).
83
+ */
84
+ export function mappingNow(log, action) {
85
+ if (!Array.isArray(log) || !action || !(action.startsWith('auto-bridge') || action === 'resume-all')) return null;
86
+ for (let i = log.length - 1; i >= 0; i--) {
87
+ const m = String(log[i]).match(BRIDGE_PERSON);
88
+ if (m) return m[1];
89
+ }
90
+ return null;
91
+ }
92
+
73
93
  function walk(done, total) {
74
94
  if (!total) return null;
75
95
  return { done: Math.min(done, total), total, kind: 'walk' };
@@ -0,0 +1,44 @@
1
+ // Saying yes to the scanner's risk, once, before it first touches LinkedIn.
2
+ //
3
+ // The Scan page used to carry the risk as a grey line at the bottom. Now the
4
+ // first scan waits for an explicit "I understand" to three plain points, kept
5
+ // as a setting (app_meta, lib/settings.js) so it travels with a copy of the
6
+ // data. The server checks it too, so no button anywhere (the header's Check for
7
+ // new, a Scan button in a panel) can start a first scan without it.
8
+ //
9
+ // Someone who scanned before this existed has seen the risk and chosen it: the
10
+ // scanner's own Chrome profile in the data folder is the sign, since only the
11
+ // scanner makes one, and it never travels in a copy. They aren't asked again.
12
+ //
13
+ // No Node imports: the Scan page reads the points from here too.
14
+
15
+ export const RISK_POINTS = [
16
+ 'It runs your own LinkedIn account automatically, in a Chrome window on this computer.',
17
+ 'LinkedIn’s User Agreement doesn’t allow automated tools, and LinkedIn may restrict accounts that use them. The safe limits, 50 searches a day and 250 a month, stay on unless you change them.',
18
+ 'Nothing leaves your computer. There is no account with us and nothing is uploaded.',
19
+ ];
20
+
21
+ export const RISK_REFUSAL = 'Before the first scan, open the Scan page and confirm you understand what scanning risks.';
22
+
23
+ // The scanner's actions that never open LinkedIn: installing its Python and
24
+ // packages, and saving photos already linked from it (no browser, no search).
25
+ const OFF_LINKEDIN = new Set(['install', 'setup', 'photos']);
26
+
27
+ export const touchesLinkedIn = (action) => !OFF_LINKEDIN.has(String(action));
28
+
29
+ /** When the risk was accepted (ISO time), or null; set from the Scan page. */
30
+ export const SCAN_RISK_SETTING = {
31
+ default: null,
32
+ parse(value) {
33
+ if (value === null) return null;
34
+ if (typeof value !== 'string' || Number.isNaN(Date.parse(value))) {
35
+ throw new Error('When the scanning risk was accepted is a date and time.');
36
+ }
37
+ return new Date(value).toISOString();
38
+ },
39
+ };
40
+
41
+ /** @param {{ acceptedAt?: string | null, scannedBefore?: boolean }} state */
42
+ export function riskAccepted({ acceptedAt = null, scannedBefore = false } = {}) {
43
+ return Boolean(acceptedAt) || scannedBefore;
44
+ }
@@ -1,12 +1,71 @@
1
1
  // What the Scan page's first step says and offers, from the checks GET
2
- // /api/scraper reports (app/api/scraper/route.js, lib/scanner-python.js), and
3
- // whether it asks for your field before that (askForField).
2
+ // /api/scraper reports (app/api/scraper/route.js, lib/scanner-python.js),
3
+ // whether it asks for your field once your connections are in (askForField),
4
+ // and two answers the server gives that step: is Google Chrome here
5
+ // (chromeInstalled), and are you signed in to LinkedIn (signedInFrom).
4
6
  // Plain data in, plain data out: the page draws it, and
5
7
  // tests/scanner-setup.test.mjs checks every state without a browser. No Node
6
8
  // imports here; the page runs in the browser.
7
9
 
8
10
  const MB = 1024 * 1024;
9
11
 
12
+ // The scanner drives Google Chrome (scripts/scrape.py, channel "chrome"), so a
13
+ // computer without it can set up the scanner's Python and still never scan.
14
+ // It used to be a red box above the steps while step 1 said Ready; now step 1
15
+ // isn't done without it, and the server refuses anything that opens LinkedIn
16
+ // with the same words (app/api/scraper/route.js).
17
+ export const CHROME_DOWNLOAD = 'https://www.google.com/chrome/';
18
+ export const CHROME_MISSING = 'The scanner works in Google Chrome, and Chrome isn’t on this computer.';
19
+ export const CHROME_BUTTON = 'Install Google Chrome, then come back';
20
+ export const CHROME_REFUSAL = `${CHROME_MISSING} ${CHROME_BUTTON}.`;
21
+
22
+ /**
23
+ * Is Google Chrome where Playwright's "chrome" channel looks for it (Chromium
24
+ * doesn't count)? `exists` is the file check (existsSync on the server), so
25
+ * this runs in a test with no Chrome at all.
26
+ *
27
+ * SIX_DEGREES_TEST_CHROME=found or =missing answers for the computer, for
28
+ * tests and for checking the Scan page's states by eye: the route's tests then
29
+ * don't depend on whether the machine running them has Chrome. Nothing in the
30
+ * app sets it; any other value is ignored.
31
+ */
32
+ export function chromeInstalled({ platform, env = {}, exists }) {
33
+ const forced = env.SIX_DEGREES_TEST_CHROME;
34
+ if (forced === 'found') return true;
35
+ if (forced === 'missing') return false;
36
+ if (platform === 'darwin') return exists('/Applications/Google Chrome.app');
37
+ if (platform === 'linux') return exists('/opt/google/chrome/chrome');
38
+ // On Windows, for this user, then for everyone.
39
+ if (platform === 'win32') {
40
+ return [env.LOCALAPPDATA, env.PROGRAMFILES, env['PROGRAMFILES(X86)']]
41
+ .filter(Boolean)
42
+ .some((root) => exists(`${String(root).replace(/[\\/]+$/, '')}\\Google\\Chrome\\Application\\chrome.exe`));
43
+ }
44
+ return true; // elsewhere Playwright resolves the channel itself
45
+ }
46
+
47
+ // The scanner's note that LinkedIn's session was confirmed (scripts/scrape.py
48
+ // SIGNED_IN_FILE): { signedIn, at }, written when the li_at cookie is seen past
49
+ // any sign-in wall, and set to false while it waits for you to sign in.
50
+ export const SIGNED_IN_FILE = 'signed-in.json';
51
+
52
+ /**
53
+ * Step 2's "Signed in". It used to be "Chrome's cookie file exists", which is
54
+ * true the moment the scanner's window first opens, so opening LinkedIn and
55
+ * closing the window without signing in ticked the step. Now the scanner's own
56
+ * note says it (`note`, the parsed SIGNED_IN_FILE, or null when there is none
57
+ * or it can't be read), and only while its Chrome profile is still there
58
+ * (`profile`): deleting that signs you out.
59
+ *
60
+ * A data folder from before the note existed has none, and there the old
61
+ * check stands (`cookies`), so nobody already signed in is told they're signed
62
+ * out; the next scan writes the note.
63
+ */
64
+ export function signedInFrom({ note, profile, cookies }) {
65
+ if (note && typeof note === 'object' && typeof note.signedIn === 'boolean') return note.signedIn && Boolean(profile);
66
+ return Boolean(cookies);
67
+ }
68
+
10
69
  /** "Python 3.9.6 is too old…": why a Python this computer has won't do, or ''. */
11
70
  function whyNotThisPython(found) {
12
71
  if (!found?.version) return 'This computer has no Python the scanner can use.';
@@ -40,10 +99,11 @@ function ownPythonNote(own, { done, button }) {
40
99
  }
41
100
 
42
101
  /**
43
- * Step 1, "Set up the scanner": { done, text, button, note }, where button is
44
- * { action: 'install'|'setup', label } or null (nothing to press), and note is
45
- * a line to show above the step when the Python inside the app didn't work
46
- * (ownPythonNote), or null.
102
+ * Step 1, "Set up the scanner": { done, text, button, note, chrome }, where
103
+ * button is { action: 'install'|'setup', label } or null (nothing to press),
104
+ * note is a line to show above the step when the Python inside the app didn't
105
+ * work (ownPythonNote), or null, and chrome is { label, href } when Google
106
+ * Chrome isn't on this computer (the way to get it), or null.
47
107
  *
48
108
  * ready the app's own Python (nothing to install), one named by
49
109
  * SIX_DEGREES_PYTHON, or the scanner's environment / a Python
@@ -53,10 +113,24 @@ function ownPythonNote(own, { done, button }) {
53
113
  * setup none, but a standalone Python for this computer is pinned:
54
114
  * "Set up the scanner" downloads it, then installs
55
115
  * neither no Python and nothing to download for this computer
116
+ *
117
+ * Any of them without Chrome isn't done: the Python's part is said first if
118
+ * there is one, then Chrome's. Only a definite "no Chrome" counts; a status
119
+ * that couldn't say (none yet, or an older server's) leaves it out.
56
120
  */
57
121
  export function setupStep(status) {
58
122
  const step = stepOnly(status);
59
- return { ...step, note: ownPythonNote(status?.checks?.ownPython, step) };
123
+ const note = ownPythonNote(status?.checks?.ownPython, step);
124
+ if (status?.checks?.chrome !== false) return { ...step, note, chrome: null };
125
+ return {
126
+ ...step,
127
+ done: false,
128
+ // "Ready" or "Installed." would read as finished: with the Python done,
129
+ // Chrome is all the step says.
130
+ text: step.done ? CHROME_MISSING : `${step.text} ${CHROME_MISSING}`,
131
+ note,
132
+ chrome: { label: CHROME_BUTTON, href: CHROME_DOWNLOAD },
133
+ };
60
134
  }
61
135
 
62
136
  function stepOnly(status) {
@@ -105,19 +179,27 @@ function stepOnly(status) {
105
179
  }
106
180
 
107
181
  /**
108
- * Whether the Scan page asks for your field first (app/components/FieldStep.js):
109
- * once, before the first scan. Never to someone who already has a network, has
110
- * picked a sector (Scores → Your sector), or answered or skipped the question
111
- * before (`fieldAsked`). `status` is GET /api/scraper's answer (null while it
112
- * loads), `settings` GET /api/settings' `settings` (undefined while it loads,
113
- * null when it couldn't be read). null means not known yet: the page waits,
114
- * rather than show the steps and then swap them for the question.
182
+ * Whether the Scan page asks for your field (app/components/FieldStep.js):
183
+ * once, when your own connections are in (step 3) and before who they know is
184
+ * mapped, under "Your galaxy is ready". It used to stand in front of step 1,
185
+ * one more thing between someone new and their first scan; asked here, it
186
+ * rescores people they can already see.
187
+ *
188
+ * Never before step 3, never once a 2nd-degree or company scan is in (that
189
+ * network's owner is past onboarding), never while a scan runs, and never to
190
+ * someone who has picked a sector (Scores → Your sector) or answered or
191
+ * skipped it before (`fieldAsked`, saved either way). So whoever has only
192
+ * their 1st degree mapped, someone from before this moved included, is asked
193
+ * once. `status` is GET /api/scraper's answer (null while it loads), `settings`
194
+ * GET /api/settings' `settings` (undefined while it loads, null when it
195
+ * couldn't be read). null means not known yet: the page leaves the question
196
+ * out until it is, rather than show it and take it away.
115
197
  */
116
198
  export function askForField(status, settings) {
117
199
  if (!status) return null;
118
200
  const n = status.network;
119
201
  // No counts means the app couldn't be asked; a scan running is past asking.
120
- if (!n || n.first || n.second || n.third || status.running) return false;
202
+ if (!n || !n.first || n.second || n.third || status.running) return false;
121
203
  if (settings === undefined) return null;
122
204
  return Boolean(settings && !settings.fieldAsked && !settings.sectorFocus?.sectors?.length);
123
205
  }