six-degrees 0.3.0 → 0.4.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 (427) 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 +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +35 -7
  5. package/.next/standalone/.next/required-server-files.json +6 -1
  6. package/.next/standalone/.next/routes-manifest.json +18 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  22. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  24. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  26. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  28. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  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 +1 -1
  32. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  43. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  45. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
  47. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
  51. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
  56. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  61. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  63. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/import.html +1 -1
  68. package/.next/standalone/.next/server/app/import.rsc +13 -13
  69. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
  70. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  71. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
  72. package/.next/standalone/.next/server/app/index.html +1 -1
  73. package/.next/standalone/.next/server/app/index.rsc +13 -13
  74. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
  75. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
  76. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  77. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/launch.html +2 -2
  80. package/.next/standalone/.next/server/app/launch.rsc +13 -13
  81. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
  82. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  83. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
  84. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  86. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/paths.html +1 -1
  89. package/.next/standalone/.next/server/app/paths.rsc +13 -13
  90. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
  91. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  92. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
  93. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app/profile.html +1 -1
  96. package/.next/standalone/.next/server/app/profile.rsc +13 -13
  97. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
  98. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  99. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
  100. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  102. package/.next/standalone/.next/server/app/queue.html +1 -1
  103. package/.next/standalone/.next/server/app/queue.rsc +13 -13
  104. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
  105. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  106. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
  107. package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
  108. package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
  109. package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
  110. package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
  111. package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
  112. package/.next/standalone/.next/server/app/scores/page.js +13 -0
  113. package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
  114. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
  115. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
  116. package/.next/standalone/.next/server/app/scores.html +1 -0
  117. package/.next/standalone/.next/server/app/scores.meta +26 -0
  118. package/.next/standalone/.next/server/app/scores.rsc +24 -0
  119. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
  120. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
  121. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
  122. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  124. package/.next/standalone/.next/server/app/settings.html +1 -1
  125. package/.next/standalone/.next/server/app/settings.rsc +13 -13
  126. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
  127. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  128. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
  129. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  131. package/.next/standalone/.next/server/app/setup.html +1 -1
  132. package/.next/standalone/.next/server/app/setup.rsc +13 -13
  133. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
  134. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  135. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
  136. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  137. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +8 -0
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
  157. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  158. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
  159. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  161. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  163. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
  164. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  165. package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
  166. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  167. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
  168. package/.next/standalone/.next/server/chunks/_1_fynia._.js +8 -6
  169. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
  170. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  171. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  172. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +13 -8
  173. package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
  174. package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
  175. package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
  176. package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
  177. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
  178. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
  180. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
  181. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
  182. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
  183. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
  184. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
  185. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
  186. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
  187. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
  188. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
  190. package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
  201. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
  202. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  203. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
  204. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
  205. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  206. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  207. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
  208. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  209. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  210. package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
  212. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
  213. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
  214. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  215. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  216. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  217. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  218. package/.next/standalone/.next/server/pages/404.html +1 -1
  219. package/.next/standalone/.next/server/pages/500.html +1 -1
  220. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  221. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  222. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  223. package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  224. package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  225. package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
  226. package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  227. package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  228. package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
  229. package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  230. package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
  231. package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  232. package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  233. package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  234. package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  235. package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  236. package/.next/standalone/CHANGELOG.md +286 -0
  237. package/.next/standalone/CONTRIBUTING.md +15 -4
  238. package/.next/standalone/README.md +50 -29
  239. package/.next/standalone/SECURITY.md +10 -0
  240. package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
  241. package/.next/standalone/app/api/company-scores/route.js +1 -1
  242. package/.next/standalone/app/api/ingest/route.js +23 -2
  243. package/.next/standalone/app/api/outreach/route.js +107 -44
  244. package/.next/standalone/app/api/scraper/route.js +146 -17
  245. package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
  246. package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
  247. package/.next/standalone/app/api/update-images/route.js +41 -7
  248. package/.next/standalone/app/avatars/[file]/route.js +6 -1
  249. package/.next/standalone/app/components/Avatar.js +5 -2
  250. package/.next/standalone/app/components/ChainView.js +659 -469
  251. package/.next/standalone/app/components/CompanyScores.js +16 -10
  252. package/.next/standalone/app/components/EmptyState.js +1 -1
  253. package/.next/standalone/app/components/FieldStep.js +94 -0
  254. package/.next/standalone/app/components/ForceGraph.js +183 -768
  255. package/.next/standalone/app/components/GridView.js +9 -7
  256. package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
  257. package/.next/standalone/app/components/LinkedInLimits.js +22 -7
  258. package/.next/standalone/app/components/ListView.js +6 -4
  259. package/.next/standalone/app/components/MappingProgress.js +5 -6
  260. package/.next/standalone/app/components/OnboardingGate.js +1 -1
  261. package/.next/standalone/app/components/OrbitGraph.js +33 -1
  262. package/.next/standalone/app/components/OutlinkQuest.js +5 -4
  263. package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
  264. package/.next/standalone/app/components/RingsView.js +9 -8
  265. package/.next/standalone/app/components/SeparationView.js +96 -14
  266. package/.next/standalone/app/components/Sidebar.js +366 -287
  267. package/.next/standalone/app/components/TheirCircle.js +300 -0
  268. package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
  269. package/.next/standalone/app/components/settings/SectorSection.js +21 -195
  270. package/.next/standalone/app/components/settings/TierSection.js +99 -0
  271. package/.next/standalone/app/components/useRequests.js +12 -0
  272. package/.next/standalone/app/components/useScanner.js +13 -0
  273. package/.next/standalone/app/components/views.js +3 -5
  274. package/.next/standalone/app/globals.css +7 -0
  275. package/.next/standalone/app/launch/page.js +6 -3
  276. package/.next/standalone/app/page.js +183 -74
  277. package/.next/standalone/app/paths/page.js +20 -14
  278. package/.next/standalone/app/profile/page.js +3 -3
  279. package/.next/standalone/app/queue/page.js +56 -60
  280. package/.next/standalone/app/scores/page.js +72 -0
  281. package/.next/standalone/app/settings/page.js +15 -3
  282. package/.next/standalone/app/setup/page.js +423 -260
  283. package/.next/standalone/db/schema.js +2 -1
  284. package/.next/standalone/eslint.config.mjs +3 -3
  285. package/.next/standalone/install.sh +3 -3
  286. package/.next/standalone/lib/chain-layout.js +139 -0
  287. package/.next/standalone/lib/circle.js +169 -0
  288. package/.next/standalone/lib/companies.js +1 -1
  289. package/.next/standalone/lib/csv.js +21 -10
  290. package/.next/standalone/lib/data-export.js +2 -1
  291. package/.next/standalone/lib/data-folder.js +4 -2
  292. package/.next/standalone/lib/db-client.js +1 -0
  293. package/.next/standalone/lib/galaxy.js +12 -0
  294. package/.next/standalone/lib/ingest.js +27 -8
  295. package/.next/standalone/lib/legacy-offer.js +7 -6
  296. package/.next/standalone/lib/legacy-scores.js +2 -2
  297. package/.next/standalone/lib/linkedin-limits.js +30 -8
  298. package/.next/standalone/lib/photos.js +47 -0
  299. package/.next/standalone/lib/quest.js +8 -3
  300. package/.next/standalone/lib/rarity.js +80 -0
  301. package/.next/standalone/lib/reach.js +113 -0
  302. package/.next/standalone/lib/requests-client.js +113 -0
  303. package/.next/standalone/lib/rpc.js +26 -15
  304. package/.next/standalone/lib/scan-state.js +1 -0
  305. package/.next/standalone/lib/scanner-setup.js +20 -1
  306. package/.next/standalone/lib/score-guess.js +60 -0
  307. package/.next/standalone/lib/scoring.js +1761 -69
  308. package/.next/standalone/lib/scraper-client.js +235 -17
  309. package/.next/standalone/lib/sector-directory.js +1 -1
  310. package/.next/standalone/lib/sector-focus.js +17 -3
  311. package/.next/standalone/lib/separation.js +4 -2
  312. package/.next/standalone/lib/settings-client.js +21 -0
  313. package/.next/standalone/lib/settings-effects.js +19 -1
  314. package/.next/standalone/lib/settings.js +7 -2
  315. package/.next/standalone/next.config.mjs +25 -3
  316. package/.next/standalone/package-lock.json +2 -2
  317. package/.next/standalone/package.json +2 -2
  318. package/.next/standalone/public/demo-data.json +1 -1
  319. package/.next/standalone/scripts/build-app.mjs +11 -2
  320. package/.next/standalone/scripts/check-node.mjs +1 -1
  321. package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
  322. package/.next/standalone/scripts/image_store.py +77 -21
  323. package/.next/standalone/scripts/scrape.py +305 -29
  324. package/.next/standalone/server.js +1 -1
  325. package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  326. package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  327. package/.next/static/chunks/0l1568qff0efj.js +1 -0
  328. package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  329. package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  330. package/.next/static/chunks/135n87ydxjrra.js +2 -0
  331. package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  332. package/.next/static/chunks/28bu26ausqzmn.js +1 -0
  333. package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  334. package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  335. package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  336. package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  337. package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  338. package/CHANGELOG.md +286 -0
  339. package/README.md +50 -29
  340. package/SECURITY.md +10 -0
  341. package/db/schema.js +2 -1
  342. package/lib/chain-layout.js +139 -0
  343. package/lib/circle.js +169 -0
  344. package/lib/companies.js +1 -1
  345. package/lib/csv.js +21 -10
  346. package/lib/data-export.js +2 -1
  347. package/lib/data-folder.js +4 -2
  348. package/lib/db-client.js +1 -0
  349. package/lib/galaxy.js +12 -0
  350. package/lib/ingest.js +27 -8
  351. package/lib/legacy-offer.js +7 -6
  352. package/lib/legacy-scores.js +2 -2
  353. package/lib/linkedin-limits.js +30 -8
  354. package/lib/photos.js +47 -0
  355. package/lib/quest.js +8 -3
  356. package/lib/rarity.js +80 -0
  357. package/lib/reach.js +113 -0
  358. package/lib/requests-client.js +113 -0
  359. package/lib/rpc.js +26 -15
  360. package/lib/scan-state.js +1 -0
  361. package/lib/scanner-setup.js +20 -1
  362. package/lib/score-guess.js +60 -0
  363. package/lib/scoring.js +1761 -69
  364. package/lib/scraper-client.js +235 -17
  365. package/lib/sector-directory.js +1 -1
  366. package/lib/sector-focus.js +17 -3
  367. package/lib/separation.js +4 -2
  368. package/lib/settings-client.js +21 -0
  369. package/lib/settings-effects.js +19 -1
  370. package/lib/settings.js +7 -2
  371. package/package.json +2 -2
  372. package/public/demo-data.json +1 -1
  373. package/scripts/check-node.mjs +1 -1
  374. package/scripts/image_store.py +77 -21
  375. package/scripts/scrape.py +305 -29
  376. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
  378. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
  379. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
  380. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
  381. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
  382. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
  383. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
  384. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
  385. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
  386. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
  387. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
  388. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
  389. package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
  390. package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
  391. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
  392. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
  393. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
  394. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
  395. package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
  396. package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
  397. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  398. package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
  399. package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  400. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
  401. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
  402. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +0 -44
  403. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  404. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
  405. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  406. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
  407. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  408. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  409. package/.next/standalone/app/components/BridgeRing.js +0 -753
  410. package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  411. package/.next/static/chunks/08k1crid-05dt.js +0 -1
  412. package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  413. package/.next/static/chunks/0q-_khndfxubv.js +0 -2
  414. package/.next/static/chunks/1c79veqedvubt.js +0 -1
  415. package/.next/static/chunks/1chp3nx45meg5.js +0 -44
  416. package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  417. package/.next/static/chunks/1uadqylrod5_v.js +0 -1
  418. package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  419. package/.next/static/chunks/289ktj97di6zg.js +0 -1
  420. package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  421. package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  422. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  423. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  424. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
  425. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  426. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  427. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
@@ -1,9 +1,13 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
4
- import { scraperStatus, startScrape, notReadyMessage } from '../lib/scraper-client';
3
+ import { Suspense, useEffect, useState, useRef, useMemo, useCallback } from 'react';
4
+ import { useSearchParams } from 'next/navigation';
5
+ import { scraperStatus, beginScrape, notReadyMessage, busyReason, isCircleScan, loadScanNotes, NO_SCAN_NOTES } from '../lib/scraper-client';
6
+ import useScanner from './components/useScanner';
7
+ import useRequests from './components/useRequests';
8
+ import { requestCount } from '../lib/requests-client';
5
9
  import { loadNetwork } from '../lib/network';
6
- import { resolveView } from './components/views';
10
+ import { VIEWS, resolveView } from './components/views';
7
11
  import Sidebar from './components/Sidebar';
8
12
  import FilterPanel from './components/FilterPanel';
9
13
  import OnboardingGate from './components/OnboardingGate';
@@ -16,6 +20,18 @@ import Link from 'next/link';
16
20
  // One shared empty list, so "no 2nd-degree data" is the same value every render.
17
21
  const NO_DEGREE2 = [];
18
22
 
23
+ // A job that changes the network. Setting the scanner up and signing in don't.
24
+ const CHANGES_NETWORK = (job) => Boolean(job) && !['install', 'setup', 'login'].includes(job.action);
25
+
26
+ /** Counts for the header and panels, from the two lists. */
27
+ function statsFor(d1, d2) {
28
+ const tierCounts = {};
29
+ d1.forEach(c => { tierCounts[c.tier] = (tierCounts[c.tier] || 0) + 1; });
30
+ const d2TierCounts = {};
31
+ d2.forEach(c => { d2TierCounts[c.tier] = (d2TierCounts[c.tier] || 0) + 1; });
32
+ return { total: d1.length, tiers: tierCounts, d2Total: d2.length, d2Tiers: d2TierCounts };
33
+ }
34
+
19
35
  const TIER_COLORS = {
20
36
  S: '#FFD700',
21
37
  A: '#9B59B6',
@@ -25,9 +41,13 @@ const TIER_COLORS = {
25
41
  };
26
42
 
27
43
  export default function Home() {
44
+ // Suspense because HomeInner reads the address's ?chain= (useSearchParams),
45
+ // which Next requires to sit inside one for the page to build.
28
46
  return (
29
47
  <OnboardingGate>
30
- <HomeInner />
48
+ <Suspense>
49
+ <HomeInner />
50
+ </Suspense>
31
51
  </OnboardingGate>
32
52
  );
33
53
  }
@@ -51,17 +71,28 @@ function HomeInner() {
51
71
  const [selected, setSelected] = useState(null);
52
72
  const focusNodeRef = useRef(null);
53
73
  const [filter, setFilter] = useState('all');
54
- const [mode, setMode] = useState('network');
74
+ // A link to someone's circle in Bridge Chains, /?chain=<id> (the Scan page's
75
+ // "watch it fill in"), opens Degrees on it. The router's search params rather
76
+ // than window.location: on a click from another page the address bar only
77
+ // changes after this page has rendered, and the map opened on the Galaxy.
78
+ const linkedChain = useSearchParams().get('chain');
79
+ const [chainOpen, setChainOpen] = useState(linkedChain);
80
+ const [mode, setMode] = useState(() => (chainOpen ? 'degrees' : 'network'));
55
81
  const [loading, setLoading] = useState(true);
56
82
  const [stats, setStats] = useState(null);
57
83
  const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
58
84
  const [filterPanelCollapsed, setFilterPanelCollapsed] = useState(true);
59
- const [visualMode, setVisualMode] = useState('galaxy');
85
+ const [visualMode, setVisualMode] = useState(() => (chainOpen ? 'chain' : 'galaxy'));
60
86
  const [notifications, setNotifications] = useState([]);
61
87
  const [showNotifs, setShowNotifs] = useState(false);
62
- const [pending, setPending] = useState([]);
63
88
  const [csvMode, setCsvMode] = useState(false);
64
89
  const [csvSource, setCsvSource] = useState('csv');
90
+ // What the scanner noted: hidden lists, and lists read with nothing new in
91
+ // them. With the network, they say who is ready for a circle scan (lib/reach.js).
92
+ const [scanNotes, setScanNotes] = useState(NO_SCAN_NOTES);
93
+ const shapeRef = useRef('');
94
+ // Everyone with a request out, shared with every view (lib/requests-client.js).
95
+ const pendingCount = requestCount(useRequests());
65
96
 
66
97
  useEffect(() => {
67
98
  if (!userId) return;
@@ -81,23 +112,43 @@ function HomeInner() {
81
112
 
82
113
  setDegree1(d1);
83
114
  setDegree2(d2);
84
-
85
- const tierCounts = {};
86
- d1.forEach(c => { tierCounts[c.tier] = (tierCounts[c.tier] || 0) + 1; });
87
- const d2TierCounts = {};
88
- d2.forEach(c => { d2TierCounts[c.tier] = (d2TierCounts[c.tier] || 0) + 1; });
89
- setStats({ total: d1.length, tiers: tierCounts, d2Total: d2.length, d2Tiers: d2TierCounts });
115
+ shapeRef.current = networkShape(d1, d2);
116
+ setStats(statsFor(d1, d2));
90
117
  setLoading(false);
91
118
  }
92
119
  load();
93
120
  if (!IS_DEMO && !hasCsvNetwork()) {
121
+ loadScanNotes().then(setScanNotes);
94
122
  // Fetch notifications
95
123
  fetch(`/api/notifications?userId=${userId}`).then(r => r.json()).then(d => setNotifications(d.notifications || [])).catch(() => {});
96
- // Fetch pending outreach
97
- fetch(`/api/outreach?userId=${userId}`).then(r => r.json()).then(d => setPending(d.pending || [])).catch(() => {});
98
124
  }
99
125
  }, [userId]);
100
126
 
127
+ // The network again, after a scan: circles fill in while they're scanned
128
+ // (Blake, 2026-09-28: "the d3 dots as it builds more in"). Only a real change
129
+ // is published, because the graph views rebuild their scene whenever these
130
+ // lists change identity (TRAPS §29).
131
+ const reload = useCallback(async () => {
132
+ if (IS_DEMO || hasCsvNetwork() || !userId) return;
133
+ try {
134
+ const [{ degree1: d1, degree2: d2 }, notes] = await Promise.all([loadNetwork(userId), loadScanNotes()]);
135
+ setScanNotes((prev) => (JSON.stringify(prev) === JSON.stringify(notes) ? prev : notes));
136
+ const shape = networkShape(d1, d2);
137
+ if (shape === shapeRef.current) return;
138
+ shapeRef.current = shape;
139
+ setDegree1(d1);
140
+ setDegree2(d2);
141
+ setStats(statsFor(d1, d2));
142
+ } catch { /* the app restarting, say: keep what's on screen */ }
143
+ }, [userId]);
144
+
145
+ // Followed once: Bridge Chains has opened it, so leaving that view and coming
146
+ // back, or reloading, starts from the overview.
147
+ const chainOpened = useCallback(() => {
148
+ setChainOpen(null);
149
+ try { window.history.replaceState(null, '', window.location.pathname); } catch { /* the link stays */ }
150
+ }, []);
151
+
101
152
  const isDegreesMode = mode === 'degrees';
102
153
  const connections = degree1;
103
154
 
@@ -136,22 +187,24 @@ function HomeInner() {
136
187
  if (loading) {
137
188
  return (
138
189
  <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: '#0a0a1a', color: '#fff' }}>
139
- <div style={{ fontSize: 24, fontWeight: 700 }}>Loading 6 Degrees of Separation...</div>
190
+ <div style={{ fontSize: 24, fontWeight: 700 }}>Loading Six Degrees…</div>
140
191
  <div style={{ fontSize: 14, color: '#888', marginTop: 8 }}>Mapping your LinkedIn network</div>
141
192
  </div>
142
193
  );
143
194
  }
144
195
 
145
- // Resolved once, because two places care: the renderer below, and the Galaxy
196
+ // Resolved once, because two places care: the renderer below, and the Orbit
146
197
  // toggle, which hides over Separation.
147
198
  const view = resolveView(visualMode, mode);
199
+ // Scanning and the Scan page belong to your own network, not the sample or a CSV.
200
+ const canScan = !IS_DEMO && !csvMode;
148
201
 
149
202
  return (
150
203
  <div data-map style={{ height: '100vh', overflow: 'hidden', background: '#0a0a1a', color: '#fff', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
151
204
  <header style={{ padding: isMobile ? '10px 12px' : '20px 30px', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
152
205
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: isMobile ? 4 : 8, flexWrap: isMobile ? 'wrap' : 'nowrap', gap: isMobile ? 6 : 0 }}>
153
206
  <h1 style={{ fontSize: isMobile ? 16 : 28, fontWeight: 700, margin: 0, background: 'linear-gradient(135deg, #FFD700, #9B59B6, #3498DB)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
154
- 6 Degrees
207
+ Six Degrees
155
208
  </h1>
156
209
  <div style={{ display: 'flex', gap: isMobile ? 2 : 4, background: 'rgba(255,255,255,0.08)', borderRadius: 8, padding: isMobile ? 2 : 3, flexWrap: isMobile ? 'wrap' : 'nowrap' }}>
157
210
  <button
@@ -215,6 +268,16 @@ function HomeInner() {
215
268
  >
216
269
  Paths
217
270
  </Link>}
271
+ {!IS_DEMO && !csvMode && <Link
272
+ href="/scores"
273
+ style={{
274
+ padding: isMobile ? '6px 10px' : '8px 16px', borderRadius: 6, border: 'none', fontSize: isMobile ? 11 : 13, fontWeight: 600,
275
+ background: 'rgba(255,255,255,0.06)', color: '#FFD700', textDecoration: 'none',
276
+ display: 'flex', alignItems: 'center', gap: 4,
277
+ }}
278
+ >
279
+ Scores
280
+ </Link>}
218
281
  {!IS_DEMO && !csvMode && <Link
219
282
  href="/queue"
220
283
  style={{
@@ -315,29 +378,8 @@ function HomeInner() {
315
378
  </div>
316
379
  )}
317
380
  </div>}
318
- {/* Refresh button — checks connections + notifies */}
319
- {!IS_DEMO && !csvMode && <button
320
- onClick={async () => {
321
- try {
322
- const status = await scraperStatus();
323
- const blocked = notReadyMessage(status);
324
- if (blocked) { alert(blocked); return; }
325
- if (status.running) { alert('The scanner is already busy.'); return; }
326
- await startScrape('refresh');
327
- alert('Checking for new connections — watch it on the Scan page.');
328
- } catch (e) {
329
- alert(e.message);
330
- }
331
- }}
332
- title="Refresh connections + bridges"
333
- style={{
334
- width: isMobile ? 28 : 32, height: isMobile ? 28 : 32, borderRadius: '50%', border: 'none', cursor: 'pointer',
335
- background: 'rgba(255,255,255,0.06)', color: '#888', fontSize: isMobile ? 12 : 14,
336
- display: isMobile ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center', marginLeft: 8,
337
- }}
338
- >
339
- ↻
340
- </button>}
381
+ {/* Refresh button — Check for new, once you say yes; notifies */}
382
+ {!IS_DEMO && !csvMode && <RefreshButton isMobile={isMobile} />}
341
383
  {/* Profile icon — top right */}
342
384
  <a href={IS_DEMO ? '/launch' : csvMode ? '/import' : '/profile'} style={{
343
385
  width: isMobile ? 30 : 36, height: isMobile ? 30 : 36, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
@@ -356,8 +398,8 @@ function HomeInner() {
356
398
  </a>
357
399
  </div>
358
400
 
359
- {/* Pending count badge — in header */}
360
- {isDegreesMode && pending.length > 0 && (
401
+ {/* Pending count badge — in header: everyone with a request out, once each (lib/requests-client.js) */}
402
+ {isDegreesMode && !csvMode && pendingCount > 0 && (
361
403
  <Link href="/queue" style={{
362
404
  padding: '5px 12px', borderRadius: 16, fontSize: 12, fontWeight: 600, textDecoration: 'none',
363
405
  background: 'rgba(255,107,53,0.12)', color: '#FF6B35',
@@ -365,7 +407,7 @@ function HomeInner() {
365
407
  display: 'inline-flex', alignItems: 'center', gap: 5,
366
408
  }}>
367
409
  <span style={{ fontSize: 10 }}>⏳</span>
368
- {pending.length} Pending
410
+ {pendingCount} Pending
369
411
  </Link>
370
412
  )}
371
413
  </header>
@@ -428,6 +470,14 @@ function HomeInner() {
428
470
  selectedId: selected?.id ?? null,
429
471
  tierColors: TIER_COLORS,
430
472
  focusNodeRef,
473
+ // Every row, whatever the filters: rarity counts the ways in across
474
+ // all your scanned circles, and "already connected" needs everyone.
475
+ fullDegree1: degree1,
476
+ fullDegree2: degree2,
477
+ scanNotes,
478
+ canScan,
479
+ chainOpen,
480
+ onChainOpened: chainOpened,
431
481
  };
432
482
  return <View {...viewProps} />;
433
483
  })()}
@@ -440,38 +490,20 @@ function HomeInner() {
440
490
  mode={mode}
441
491
  collapsed={sidebarCollapsed}
442
492
  filter={filter}
443
- pending={pending}
444
493
  onToggle={() => setSidebarCollapsed(!sidebarCollapsed)}
445
494
  onSelect={(node) => { setSelected(node || null); }}
446
495
  onSwitchMode={(newMode) => { setMode(newMode); setFilter('all'); }}
447
496
  onFocusNode={(nodeId) => { if (focusNodeRef.current) focusNodeRef.current(nodeId); }}
448
- onMarkSent={async (person) => {
449
- if (IS_DEMO) return;
450
- const id = person.id;
451
- const url = person.profile_url;
452
- await fetch('/api/outreach', {
453
- method: 'POST',
454
- headers: { 'Content-Type': 'application/json' },
455
- body: JSON.stringify({ action: 'mark-sent', connectionId: id, profileUrl: url }),
456
- });
457
- setPending(prev => [...prev, person]);
458
- }}
459
- onUndoPending={async (person) => {
460
- if (IS_DEMO) return;
461
- const id = person.id;
462
- await fetch('/api/outreach', {
463
- method: 'POST',
464
- headers: { 'Content-Type': 'application/json' },
465
- body: JSON.stringify({ action: 'undo', connectionId: id }),
466
- });
467
- setPending(prev => prev.filter(p => p.id !== id));
468
- }}
497
+ csvSource={IS_DEMO ? 'sample' : csvMode ? csvSource : null}
498
+ scanNotes={scanNotes}
499
+ canScan={canScan}
469
500
  />
501
+ {canScan && <NetworkRefresh onChange={reload} live={view.key === 'chain'} />}
470
502
 
471
- {/* Galaxy experimental toggle — bottom right, Degrees mode only.
472
- Hidden over Separation: Galaxy isn't a Degrees view, so from there
473
- the toggle could only fall back to Separation itself, and on a
474
- phone it sat on top of the score column. */}
503
+ {/* Orbit, one tap from Bridge Chains — bottom right, Degrees mode only.
504
+ It used to say "Galaxy" and show Orbit: the Galaxy is a Network
505
+ Circle view, so asking for it here fell back to Orbit. Hidden over
506
+ Separation, where on a phone it sat on top of the score column. */}
475
507
  {isDegreesMode && view.key !== 'separation' && (
476
508
  <div style={{
477
509
  position: 'absolute', bottom: 20, right: 20, zIndex: 20,
@@ -479,19 +511,19 @@ function HomeInner() {
479
511
  background: 'rgba(0,0,0,0.7)', borderRadius: 20, padding: '6px 14px',
480
512
  backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.08)',
481
513
  }}>
482
- <span style={{ fontSize: 10, color: '#666' }}>🧪 Galaxy</span>
514
+ <span style={{ fontSize: 10, color: '#666' }}>{VIEWS.orbit.icon} {VIEWS.orbit.label}</span>
483
515
  <div
484
- onClick={() => setVisualMode(visualMode === 'galaxy' ? 'chain' : 'galaxy')}
516
+ onClick={() => setVisualMode(view.key === 'orbit' ? 'chain' : 'orbit')}
485
517
  style={{
486
518
  width: 32, height: 18, borderRadius: 9, cursor: 'pointer',
487
- background: visualMode === 'galaxy' ? '#FF6B35' : 'rgba(255,255,255,0.15)',
519
+ background: view.key === 'orbit' ? '#FF6B35' : 'rgba(255,255,255,0.15)',
488
520
  position: 'relative', transition: 'background 0.2s',
489
521
  }}
490
522
  >
491
523
  <div style={{
492
524
  width: 14, height: 14, borderRadius: '50%', background: '#fff',
493
525
  position: 'absolute', top: 2,
494
- left: visualMode === 'galaxy' ? 16 : 2,
526
+ left: view.key === 'orbit' ? 16 : 2,
495
527
  transition: 'left 0.2s',
496
528
  }} />
497
529
  </div>
@@ -501,3 +533,80 @@ function HomeInner() {
501
533
  </div>
502
534
  );
503
535
  }
536
+
537
+ /**
538
+ * A cheap fingerprint of the network: who is in it, at which degree and in
539
+ * whose circle, and how they're marked. Equal fingerprints mean nothing a view
540
+ * draws has changed.
541
+ */
542
+ function networkShape(d1, d2) {
543
+ let h = 0x811c9dc5;
544
+ const mix = (text) => {
545
+ for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 0x01000193);
546
+ };
547
+ for (const r of [...d1, ...d2]) {
548
+ mix(`${r.id}|${r.degree}|${r.source_connection_id ?? ''}|${r.unlocked_from_bridge_id ?? ''}|${r.tier}|${r.power_score}|${r.unlock_status ?? ''}|${r.profile_image_url ?? ''};`);
549
+ }
550
+ return `${d1.length}:${d2.length}:${h >>> 0}`;
551
+ }
552
+
553
+ // Looks at the network again when a scan has changed it: once when any scan
554
+ // ends, and every 20 seconds while a circle is being scanned with Bridge Chains
555
+ // open, since the scanner saves every 10 pages and the circle fills in as it
556
+ // does. Its own component for the same reason as RefreshButton below.
557
+ function NetworkRefresh({ onChange, live }) {
558
+ const scan = useScanner();
559
+ const ended = scan.finished.find(CHANGES_NETWORK)?.startedAt ?? null;
560
+ const filling = live && scan.running && isCircleScan(scan);
561
+ useEffect(() => {
562
+ if (ended != null) onChange();
563
+ }, [ended, onChange]);
564
+ useEffect(() => {
565
+ if (!filling) return undefined;
566
+ const timer = setInterval(onChange, 20000);
567
+ return () => clearInterval(timer);
568
+ }, [filling, onChange]);
569
+ return null;
570
+ }
571
+
572
+ // Its own component so the scanner's answer, which changes every second or two
573
+ // while a scan runs, re-renders this button and not the whole map.
574
+ //
575
+ // It asks first, as a company scan does, and says what it will do and what it
576
+ // costs: one click on a small icon used to open a Chrome window on LinkedIn
577
+ // with no word of what it was about to do.
578
+ function RefreshButton({ isMobile }) {
579
+ const scan = useScanner();
580
+ const busy = busyReason(scan);
581
+ return (
582
+ <button
583
+ onClick={async () => {
584
+ try {
585
+ const blocked = notReadyMessage(await scraperStatus());
586
+ if (blocked) { alert(blocked); return; }
587
+ const ok = window.confirm(
588
+ 'Check for new connections?\n\nThis opens a Chrome window on your LinkedIn connections ' +
589
+ 'list and reads it from the newest, stopping once it reaches people already saved. It ' +
590
+ 'usually takes under a minute. It reads your own list, not a search, so it doesn’t ' +
591
+ 'use your search budget, but like any scan it is LinkedIn traffic from your account.',
592
+ );
593
+ if (!ok) return;
594
+ await beginScrape('refresh');
595
+ alert('Checking for new connections — watch it on the Scan page.');
596
+ } catch (e) {
597
+ alert(e.message);
598
+ }
599
+ }}
600
+ disabled={Boolean(busy)}
601
+ title={busy ? `${busy}. One scan at a time.` : 'Check for new connections'}
602
+ style={{
603
+ width: isMobile ? 28 : 32, height: isMobile ? 28 : 32, borderRadius: '50%', border: 'none',
604
+ cursor: busy ? 'not-allowed' : 'pointer', opacity: busy ? 0.4 : 1,
605
+ background: 'rgba(255,255,255,0.06)', color: '#888', fontSize: isMobile ? 12 : 14,
606
+ display: isMobile ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center', marginLeft: 8,
607
+ }}
608
+ >
609
+ ↻
610
+ </button>
611
+ );
612
+ }
@@ -1,20 +1,21 @@
1
1
  'use client';
2
2
 
3
3
  import { Suspense, useEffect, useState } from 'react';
4
- import { useSearchParams } from 'next/navigation';
5
- import { runScrape, scraperStatus, notReadyMessage } from '../../lib/scraper-client';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ import { runScrape, scraperStatus, notReadyMessage, busyReason } from '../../lib/scraper-client';
6
6
  import { loadNetwork } from '../../lib/network';
7
7
  import { IS_DEMO } from '../../lib/demo';
8
8
  import { hasCsvNetwork, loadCsvNetwork } from '../../lib/csv';
9
9
  import OnboardingGate from '../components/OnboardingGate';
10
10
  import PathsAnalyzer from '../components/PathsAnalyzer';
11
- import CompanyScores from '../components/CompanyScores';
12
11
  import { useUser } from '../components/UserProvider';
12
+ import useScanner from '../components/useScanner';
13
13
  import Link from 'next/link';
14
14
  import { companyOf, getSeniority } from '../../lib/companies';
15
+ import { localPhoto } from '../../lib/photos';
15
16
 
16
17
  const TIER_COLORS = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
17
- const TABS = [['map', 'Map'], ['industries', 'Industries'], ['companies', 'Companies'], ['scores', 'Scores']];
18
+ const TABS = [['map', 'Map'], ['industries', 'Industries'], ['companies', 'Companies']];
18
19
 
19
20
  export default function PathsPage() {
20
21
  // Suspense because PathsInner reads the address's ?tab= (useSearchParams),
@@ -32,11 +33,14 @@ function PathsInner() {
32
33
  const [d2Data, setD2Data] = useState([]);
33
34
  const [d3Data, setD3Data] = useState([]);
34
35
  // Map and Industries are the analyzer; Companies is the list it grew from.
35
- // A link can open a tab (/paths?tab=scores, from Settings → Your sector).
36
+ // A link can open a tab (/paths?tab=companies). Company scores have a tab of
37
+ // their own now (app/scores): an old link to /paths?tab=scores goes there.
36
38
  // The router's search params rather than window.location: on a click from
37
39
  // another page the address bar only changes after this page has rendered.
38
40
  const asked = useSearchParams().get('tab');
39
41
  const [tab, setTab] = useState(() => (TABS.some(([k]) => k === asked) ? asked : 'map'));
42
+ const router = useRouter();
43
+ useEffect(() => { if (asked === 'scores') router.replace('/scores#companies'); }, [asked, router]);
40
44
  // Bumped when company scores change, so the network reloads with new tiers.
41
45
  const [reloadKey, setReloadKey] = useState(0);
42
46
 
@@ -92,6 +96,9 @@ function PathsInner() {
92
96
 
93
97
  const [scanning, setScanning] = useState(false);
94
98
  const [scanLog, setScanLog] = useState([]);
99
+ // Anything else the scanner runs (a profile card's scan, say) greys this out too.
100
+ const scan = useScanner();
101
+ const busy = !scanning && scan.running ? busyReason(scan) : null;
95
102
 
96
103
  function enrichPeople(people) {
97
104
  const d1Urls = new Set(d1Data.map(c => c.profile_url));
@@ -255,13 +262,14 @@ function PathsInner() {
255
262
  <span style={{ fontSize: 11, color: '#888' }}>
256
263
  {connectedCount}/{companyPeople.length} connected
257
264
  </span>
258
- <button onClick={scanFullCompany} disabled={scanning}
265
+ <button onClick={scanFullCompany} disabled={scanning || Boolean(busy)}
266
+ title={busy ? `${busy}. One scan at a time.` : undefined}
259
267
  style={{
260
- marginLeft: 'auto', padding: '5px 12px', borderRadius: 6, border: 'none', cursor: scanning ? 'not-allowed' : 'pointer',
261
- background: scanning ? '#333' : 'rgba(0,255,136,0.15)', color: scanning ? '#555' : '#00ff88',
268
+ marginLeft: 'auto', padding: '5px 12px', borderRadius: 6, border: 'none', cursor: scanning || busy ? 'not-allowed' : 'pointer',
269
+ background: scanning || busy ? '#333' : 'rgba(0,255,136,0.15)', color: scanning || busy ? '#555' : '#00ff88',
262
270
  fontSize: 11, fontWeight: 600,
263
271
  }}>
264
- {scanning ? 'Scanning...' : '+ Scan Full Company'}
272
+ {scanning ? 'Scanning...' : busy ? 'Scanner busy' : '+ Scan Full Company'}
265
273
  </button>
266
274
  <span
267
275
  title="Not yet tested against live LinkedIn. Results may be incomplete."
@@ -275,9 +283,7 @@ function PathsInner() {
275
283
  </div>
276
284
  </header>
277
285
 
278
- {!selectedCompany && tab === 'scores' ? (
279
- <CompanyScores onRescored={() => setReloadKey((k) => k + 1)} />
280
- ) : !selectedCompany && tab !== 'companies' ? (
286
+ {!selectedCompany && tab !== 'companies' ? (
281
287
  <PathsAnalyzer d1={d1Data} d2={d2Data} d3={d3Data} tab={tab}
282
288
  onOpenCompany={(co) => selectCompany({ ...co, sCount: co.S, aCount: co.A })} />
283
289
  ) : (
@@ -461,8 +467,8 @@ function PathPersonRow({ p, level, companyPeople, d1Data, selectedCompany }) {
461
467
  }}>{p.connected ? '✓' : '🔒'}</div>
462
468
 
463
469
  {/* Photo */}
464
- {(p.profile_image_url || p.imageUrl) ? (
465
- <img src={p.profile_image_url || p.imageUrl} alt="" style={{
470
+ {localPhoto(p.profile_image_url || p.imageUrl) ? (
471
+ <img src={localPhoto(p.profile_image_url || p.imageUrl)} alt="" style={{
466
472
  width: 26, height: 26, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
467
473
  }} onError={e => e.target.style.display = 'none'} />
468
474
  ) : (
@@ -33,7 +33,7 @@ function ProfileInner() {
33
33
  const [notifications, setNotifications] = useState([]);
34
34
  const [queueStats, setQueueStats] = useState({ total: 0, sent: 0, accepted: 0 });
35
35
  const [mapping, setMapping] = useState({ degree1: [], degree2: [], skips: [] });
36
- // "Your Sectors" is what you picked in Settings → Your sector. (users.sectors,
36
+ // "Your Sectors" is what you picked in Scores → Your sector. (users.sectors,
37
37
  // which this card used to show, is never written by anything; the Sidebar
38
38
  // and Outlink still read it as free text.) null while it loads; a load that
39
39
  // fails says so rather than showing "none picked" (TRAPS §7).
@@ -376,8 +376,8 @@ function ProfileInner() {
376
376
  }}>
377
377
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
378
378
  <h3 style={{ fontSize: 14, fontWeight: 700, margin: 0 }}>Your Sectors</h3>
379
- <Link href="/settings#sector" style={{ fontSize: 11, color: '#3498DB', textDecoration: 'none', fontWeight: 600 }}>
380
- {sectorFocus?.sectors?.length ? 'Change' : sectorFocus && !sectorFocus.failed ? 'Pick in Settings' : 'Settings'} &rarr;
379
+ <Link href="/scores#sector" style={{ fontSize: 11, color: '#3498DB', textDecoration: 'none', fontWeight: 600 }}>
380
+ {sectorFocus?.sectors?.length ? 'Change' : sectorFocus && !sectorFocus.failed ? 'Pick on Scores' : 'Scores'} &rarr;
381
381
  </Link>
382
382
  </div>
383
383
  {!sectorFocus ? (