six-degrees 0.4.0-beta.1 → 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 (355) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +5 -5
  4. package/.next/standalone/.next/required-server-files.json +6 -1
  5. package/.next/standalone/.next/routes-manifest.json +12 -1
  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.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
  15. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  19. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  23. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  25. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  36. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  38. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  40. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/outreach/route.js +1 -1
  44. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  46. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
  48. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
  53. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  55. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  60. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/import.html +1 -1
  65. package/.next/standalone/.next/server/app/import.rsc +13 -13
  66. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
  67. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  68. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
  69. package/.next/standalone/.next/server/app/index.html +1 -1
  70. package/.next/standalone/.next/server/app/index.rsc +13 -13
  71. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
  72. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
  73. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  74. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/launch.html +2 -2
  77. package/.next/standalone/.next/server/app/launch.rsc +13 -13
  78. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
  79. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  80. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
  81. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/app/paths.html +1 -1
  86. package/.next/standalone/.next/server/app/paths.rsc +13 -13
  87. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
  88. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  89. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
  90. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/app/profile.html +1 -1
  93. package/.next/standalone/.next/server/app/profile.rsc +13 -13
  94. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
  95. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  96. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
  97. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/app/queue.html +1 -1
  100. package/.next/standalone/.next/server/app/queue.rsc +13 -13
  101. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
  102. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  103. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
  104. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  105. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  106. package/.next/standalone/.next/server/app/scores.html +1 -1
  107. package/.next/standalone/.next/server/app/scores.rsc +13 -13
  108. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +13 -13
  109. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  110. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +12 -12
  111. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/standalone/.next/server/app/settings.html +1 -1
  114. package/.next/standalone/.next/server/app/settings.rsc +13 -13
  115. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
  116. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  117. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
  118. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  119. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/app/setup.html +1 -1
  121. package/.next/standalone/.next/server/app/setup.rsc +13 -13
  122. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
  123. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  124. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
  125. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
  128. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0rc27za._.js → [root-of-the-server]__0bat8nx._.js} +2 -2
  130. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  137. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -6
  154. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  156. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  157. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +12 -8
  158. package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
  159. package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
  160. package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
  161. package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
  162. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
  163. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
  164. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
  165. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
  166. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
  167. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
  168. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
  169. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
  170. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
  171. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
  172. package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +1 -1
  173. package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
  174. package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
  175. package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
  176. package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
  177. package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +1 -1
  178. package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
  179. package/.next/standalone/.next/server/chunks/ssr/{_0cu6-an._.js → _1vpfyd-._.js} +2 -2
  180. package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
  181. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
  182. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
  184. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +1 -1
  185. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  186. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  187. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +1 -1
  188. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  189. package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +1 -1
  190. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
  191. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  192. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  193. package/.next/standalone/.next/server/pages/404.html +1 -1
  194. package/.next/standalone/.next/server/pages/500.html +1 -1
  195. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  196. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  197. package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  198. package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  199. package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
  200. package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  201. package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  202. package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
  203. package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  204. package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
  205. package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  206. package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  207. package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  208. package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  209. package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  210. package/.next/standalone/CHANGELOG.md +152 -0
  211. package/.next/standalone/CONTRIBUTING.md +15 -4
  212. package/.next/standalone/README.md +21 -15
  213. package/.next/standalone/SECURITY.md +10 -0
  214. package/.next/standalone/app/api/ingest/route.js +6 -1
  215. package/.next/standalone/app/api/scraper/route.js +79 -11
  216. package/.next/standalone/app/api/update-images/route.js +41 -7
  217. package/.next/standalone/app/avatars/[file]/route.js +6 -1
  218. package/.next/standalone/app/components/Avatar.js +5 -2
  219. package/.next/standalone/app/components/ChainView.js +659 -469
  220. package/.next/standalone/app/components/EmptyState.js +1 -1
  221. package/.next/standalone/app/components/FieldStep.js +94 -0
  222. package/.next/standalone/app/components/ForceGraph.js +183 -768
  223. package/.next/standalone/app/components/GridView.js +9 -7
  224. package/.next/standalone/app/components/LinkedInLimits.js +22 -7
  225. package/.next/standalone/app/components/ListView.js +6 -4
  226. package/.next/standalone/app/components/MappingProgress.js +5 -6
  227. package/.next/standalone/app/components/OnboardingGate.js +1 -1
  228. package/.next/standalone/app/components/OrbitGraph.js +3 -1
  229. package/.next/standalone/app/components/OutlinkQuest.js +5 -4
  230. package/.next/standalone/app/components/RingsView.js +9 -8
  231. package/.next/standalone/app/components/Sidebar.js +172 -112
  232. package/.next/standalone/app/components/TheirCircle.js +18 -11
  233. package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
  234. package/.next/standalone/app/components/settings/SectorSection.js +17 -191
  235. package/.next/standalone/app/components/settings/TierSection.js +7 -12
  236. package/.next/standalone/app/components/views.js +2 -2
  237. package/.next/standalone/app/globals.css +7 -0
  238. package/.next/standalone/app/launch/page.js +2 -2
  239. package/.next/standalone/app/page.js +130 -25
  240. package/.next/standalone/app/paths/page.js +3 -2
  241. package/.next/standalone/app/queue/page.js +18 -10
  242. package/.next/standalone/app/setup/page.js +423 -260
  243. package/.next/standalone/db/schema.js +1 -1
  244. package/.next/standalone/eslint.config.mjs +3 -3
  245. package/.next/standalone/install.sh +3 -3
  246. package/.next/standalone/lib/chain-layout.js +139 -0
  247. package/.next/standalone/lib/csv.js +4 -6
  248. package/.next/standalone/lib/data-export.js +2 -1
  249. package/.next/standalone/lib/data-folder.js +4 -2
  250. package/.next/standalone/lib/galaxy.js +12 -0
  251. package/.next/standalone/lib/ingest.js +15 -8
  252. package/.next/standalone/lib/linkedin-limits.js +30 -8
  253. package/.next/standalone/lib/photos.js +47 -0
  254. package/.next/standalone/lib/quest.js +8 -3
  255. package/.next/standalone/lib/reach.js +113 -0
  256. package/.next/standalone/lib/scan-state.js +1 -0
  257. package/.next/standalone/lib/scanner-setup.js +20 -1
  258. package/.next/standalone/lib/scoring.js +769 -18
  259. package/.next/standalone/lib/scraper-client.js +27 -0
  260. package/.next/standalone/lib/sector-focus.js +13 -0
  261. package/.next/standalone/lib/separation.js +4 -2
  262. package/.next/standalone/lib/settings-client.js +21 -0
  263. package/.next/standalone/lib/settings.js +3 -1
  264. package/.next/standalone/next.config.mjs +25 -3
  265. package/.next/standalone/package-lock.json +2 -2
  266. package/.next/standalone/package.json +1 -1
  267. package/.next/standalone/scripts/build-app.mjs +11 -2
  268. package/.next/standalone/scripts/check-node.mjs +1 -1
  269. package/.next/standalone/scripts/image_store.py +77 -21
  270. package/.next/standalone/scripts/scrape.py +243 -29
  271. package/.next/standalone/server.js +1 -1
  272. package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  273. package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  274. package/.next/static/chunks/0l1568qff0efj.js +1 -0
  275. package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  276. package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  277. package/.next/static/chunks/135n87ydxjrra.js +2 -0
  278. package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  279. package/.next/static/chunks/28bu26ausqzmn.js +1 -0
  280. package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  281. package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  282. package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  283. package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  284. package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  285. package/CHANGELOG.md +152 -0
  286. package/README.md +21 -15
  287. package/SECURITY.md +10 -0
  288. package/db/schema.js +1 -1
  289. package/lib/chain-layout.js +139 -0
  290. package/lib/csv.js +4 -6
  291. package/lib/data-export.js +2 -1
  292. package/lib/data-folder.js +4 -2
  293. package/lib/galaxy.js +12 -0
  294. package/lib/ingest.js +15 -8
  295. package/lib/linkedin-limits.js +30 -8
  296. package/lib/photos.js +47 -0
  297. package/lib/quest.js +8 -3
  298. package/lib/reach.js +113 -0
  299. package/lib/scan-state.js +1 -0
  300. package/lib/scanner-setup.js +20 -1
  301. package/lib/scoring.js +769 -18
  302. package/lib/scraper-client.js +27 -0
  303. package/lib/sector-focus.js +13 -0
  304. package/lib/separation.js +4 -2
  305. package/lib/settings-client.js +21 -0
  306. package/lib/settings.js +3 -1
  307. package/package.json +1 -1
  308. package/scripts/check-node.mjs +1 -1
  309. package/scripts/image_store.py +77 -21
  310. package/scripts/scrape.py +243 -29
  311. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
  312. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
  313. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
  314. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
  315. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
  316. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
  317. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
  318. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
  319. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
  320. package/.next/standalone/.next/server/chunks/ssr/_0223v9s._.js +0 -3
  321. package/.next/standalone/.next/server/chunks/ssr/_0bcf13g._.js +0 -3
  322. package/.next/standalone/.next/server/chunks/ssr/_0wxbq78._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/ssr/_0z88zcl._.js +0 -3
  324. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
  325. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
  326. package/.next/standalone/.next/static/chunks/0_yurn4q91vq8.js +0 -16
  327. package/.next/standalone/.next/static/chunks/0d_mh-06srh2k.js +0 -1
  328. package/.next/standalone/.next/static/chunks/0x3oadfikvb1c.js +0 -1
  329. package/.next/standalone/.next/static/chunks/1d5hl1io798n2.js +0 -1
  330. package/.next/standalone/.next/static/chunks/1qt67cvj9sjwi.js +0 -1
  331. package/.next/standalone/.next/static/chunks/1roznk7hkjes1.js +0 -44
  332. package/.next/standalone/.next/static/chunks/2_bf2n4gstad9.js +0 -33
  333. package/.next/standalone/.next/static/chunks/36gbwek52x_w-.js +0 -2
  334. package/.next/standalone/.next/static/chunks/3a55suosthntc.js +0 -1
  335. package/.next/standalone/.next/static/chunks/3fq1zq-6cfwbc.js +0 -1
  336. package/.next/standalone/.next/static/chunks/3nbnio971zlji.js +0 -2
  337. package/.next/standalone/.next/static/chunks/3s6awp3qq9s7w.js +0 -1
  338. package/.next/static/chunks/0_yurn4q91vq8.js +0 -16
  339. package/.next/static/chunks/0d_mh-06srh2k.js +0 -1
  340. package/.next/static/chunks/0x3oadfikvb1c.js +0 -1
  341. package/.next/static/chunks/1d5hl1io798n2.js +0 -1
  342. package/.next/static/chunks/1qt67cvj9sjwi.js +0 -1
  343. package/.next/static/chunks/1roznk7hkjes1.js +0 -44
  344. package/.next/static/chunks/2_bf2n4gstad9.js +0 -33
  345. package/.next/static/chunks/36gbwek52x_w-.js +0 -2
  346. package/.next/static/chunks/3a55suosthntc.js +0 -1
  347. package/.next/static/chunks/3fq1zq-6cfwbc.js +0 -1
  348. package/.next/static/chunks/3nbnio971zlji.js +0 -2
  349. package/.next/static/chunks/3s6awp3qq9s7w.js +0 -1
  350. /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  351. /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  352. /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
  353. /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  354. /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  355. /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
@@ -1,12 +1,13 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
4
- import { scraperStatus, beginScrape, notReadyMessage, busyReason } 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';
5
6
  import useScanner from './components/useScanner';
6
7
  import useRequests from './components/useRequests';
7
8
  import { requestCount } from '../lib/requests-client';
8
9
  import { loadNetwork } from '../lib/network';
9
- import { resolveView } from './components/views';
10
+ import { VIEWS, resolveView } from './components/views';
10
11
  import Sidebar from './components/Sidebar';
11
12
  import FilterPanel from './components/FilterPanel';
12
13
  import OnboardingGate from './components/OnboardingGate';
@@ -19,6 +20,18 @@ import Link from 'next/link';
19
20
  // One shared empty list, so "no 2nd-degree data" is the same value every render.
20
21
  const NO_DEGREE2 = [];
21
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
+
22
35
  const TIER_COLORS = {
23
36
  S: '#FFD700',
24
37
  A: '#9B59B6',
@@ -28,9 +41,13 @@ const TIER_COLORS = {
28
41
  };
29
42
 
30
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.
31
46
  return (
32
47
  <OnboardingGate>
33
- <HomeInner />
48
+ <Suspense>
49
+ <HomeInner />
50
+ </Suspense>
34
51
  </OnboardingGate>
35
52
  );
36
53
  }
@@ -54,16 +71,26 @@ function HomeInner() {
54
71
  const [selected, setSelected] = useState(null);
55
72
  const focusNodeRef = useRef(null);
56
73
  const [filter, setFilter] = useState('all');
57
- 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'));
58
81
  const [loading, setLoading] = useState(true);
59
82
  const [stats, setStats] = useState(null);
60
83
  const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
61
84
  const [filterPanelCollapsed, setFilterPanelCollapsed] = useState(true);
62
- const [visualMode, setVisualMode] = useState('galaxy');
85
+ const [visualMode, setVisualMode] = useState(() => (chainOpen ? 'chain' : 'galaxy'));
63
86
  const [notifications, setNotifications] = useState([]);
64
87
  const [showNotifs, setShowNotifs] = useState(false);
65
88
  const [csvMode, setCsvMode] = useState(false);
66
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('');
67
94
  // Everyone with a request out, shared with every view (lib/requests-client.js).
68
95
  const pendingCount = requestCount(useRequests());
69
96
 
@@ -85,21 +112,43 @@ function HomeInner() {
85
112
 
86
113
  setDegree1(d1);
87
114
  setDegree2(d2);
88
-
89
- const tierCounts = {};
90
- d1.forEach(c => { tierCounts[c.tier] = (tierCounts[c.tier] || 0) + 1; });
91
- const d2TierCounts = {};
92
- d2.forEach(c => { d2TierCounts[c.tier] = (d2TierCounts[c.tier] || 0) + 1; });
93
- setStats({ total: d1.length, tiers: tierCounts, d2Total: d2.length, d2Tiers: d2TierCounts });
115
+ shapeRef.current = networkShape(d1, d2);
116
+ setStats(statsFor(d1, d2));
94
117
  setLoading(false);
95
118
  }
96
119
  load();
97
120
  if (!IS_DEMO && !hasCsvNetwork()) {
121
+ loadScanNotes().then(setScanNotes);
98
122
  // Fetch notifications
99
123
  fetch(`/api/notifications?userId=${userId}`).then(r => r.json()).then(d => setNotifications(d.notifications || [])).catch(() => {});
100
124
  }
101
125
  }, [userId]);
102
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
+
103
152
  const isDegreesMode = mode === 'degrees';
104
153
  const connections = degree1;
105
154
 
@@ -138,22 +187,24 @@ function HomeInner() {
138
187
  if (loading) {
139
188
  return (
140
189
  <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: '#0a0a1a', color: '#fff' }}>
141
- <div style={{ fontSize: 24, fontWeight: 700 }}>Loading 6 Degrees of Separation...</div>
190
+ <div style={{ fontSize: 24, fontWeight: 700 }}>Loading Six Degrees…</div>
142
191
  <div style={{ fontSize: 14, color: '#888', marginTop: 8 }}>Mapping your LinkedIn network</div>
143
192
  </div>
144
193
  );
145
194
  }
146
195
 
147
- // 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
148
197
  // toggle, which hides over Separation.
149
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;
150
201
 
151
202
  return (
152
203
  <div data-map style={{ height: '100vh', overflow: 'hidden', background: '#0a0a1a', color: '#fff', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }}>
153
204
  <header style={{ padding: isMobile ? '10px 12px' : '20px 30px', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
154
205
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: isMobile ? 4 : 8, flexWrap: isMobile ? 'wrap' : 'nowrap', gap: isMobile ? 6 : 0 }}>
155
206
  <h1 style={{ fontSize: isMobile ? 16 : 28, fontWeight: 700, margin: 0, background: 'linear-gradient(135deg, #FFD700, #9B59B6, #3498DB)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
156
- 6 Degrees
207
+ Six Degrees
157
208
  </h1>
158
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' }}>
159
210
  <button
@@ -327,7 +378,7 @@ function HomeInner() {
327
378
  </div>
328
379
  )}
329
380
  </div>}
330
- {/* Refresh button — checks connections + notifies */}
381
+ {/* Refresh button — Check for new, once you say yes; notifies */}
331
382
  {!IS_DEMO && !csvMode && <RefreshButton isMobile={isMobile} />}
332
383
  {/* Profile icon — top right */}
333
384
  <a href={IS_DEMO ? '/launch' : csvMode ? '/import' : '/profile'} style={{
@@ -423,6 +474,10 @@ function HomeInner() {
423
474
  // all your scanned circles, and "already connected" needs everyone.
424
475
  fullDegree1: degree1,
425
476
  fullDegree2: degree2,
477
+ scanNotes,
478
+ canScan,
479
+ chainOpen,
480
+ onChainOpened: chainOpened,
426
481
  };
427
482
  return <View {...viewProps} />;
428
483
  })()}
@@ -439,12 +494,16 @@ function HomeInner() {
439
494
  onSelect={(node) => { setSelected(node || null); }}
440
495
  onSwitchMode={(newMode) => { setMode(newMode); setFilter('all'); }}
441
496
  onFocusNode={(nodeId) => { if (focusNodeRef.current) focusNodeRef.current(nodeId); }}
497
+ csvSource={IS_DEMO ? 'sample' : csvMode ? csvSource : null}
498
+ scanNotes={scanNotes}
499
+ canScan={canScan}
442
500
  />
501
+ {canScan && <NetworkRefresh onChange={reload} live={view.key === 'chain'} />}
443
502
 
444
- {/* Galaxy experimental toggle — bottom right, Degrees mode only.
445
- Hidden over Separation: Galaxy isn't a Degrees view, so from there
446
- the toggle could only fall back to Separation itself, and on a
447
- 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. */}
448
507
  {isDegreesMode && view.key !== 'separation' && (
449
508
  <div style={{
450
509
  position: 'absolute', bottom: 20, right: 20, zIndex: 20,
@@ -452,19 +511,19 @@ function HomeInner() {
452
511
  background: 'rgba(0,0,0,0.7)', borderRadius: 20, padding: '6px 14px',
453
512
  backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.08)',
454
513
  }}>
455
- <span style={{ fontSize: 10, color: '#666' }}>🧪 Galaxy</span>
514
+ <span style={{ fontSize: 10, color: '#666' }}>{VIEWS.orbit.icon} {VIEWS.orbit.label}</span>
456
515
  <div
457
- onClick={() => setVisualMode(visualMode === 'galaxy' ? 'chain' : 'galaxy')}
516
+ onClick={() => setVisualMode(view.key === 'orbit' ? 'chain' : 'orbit')}
458
517
  style={{
459
518
  width: 32, height: 18, borderRadius: 9, cursor: 'pointer',
460
- background: visualMode === 'galaxy' ? '#FF6B35' : 'rgba(255,255,255,0.15)',
519
+ background: view.key === 'orbit' ? '#FF6B35' : 'rgba(255,255,255,0.15)',
461
520
  position: 'relative', transition: 'background 0.2s',
462
521
  }}
463
522
  >
464
523
  <div style={{
465
524
  width: 14, height: 14, borderRadius: '50%', background: '#fff',
466
525
  position: 'absolute', top: 2,
467
- left: visualMode === 'galaxy' ? 16 : 2,
526
+ left: view.key === 'orbit' ? 16 : 2,
468
527
  transition: 'left 0.2s',
469
528
  }} />
470
529
  </div>
@@ -475,8 +534,47 @@ function HomeInner() {
475
534
  );
476
535
  }
477
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
+
478
572
  // Its own component so the scanner's answer, which changes every second or two
479
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.
480
578
  function RefreshButton({ isMobile }) {
481
579
  const scan = useScanner();
482
580
  const busy = busyReason(scan);
@@ -486,6 +584,13 @@ function RefreshButton({ isMobile }) {
486
584
  try {
487
585
  const blocked = notReadyMessage(await scraperStatus());
488
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;
489
594
  await beginScrape('refresh');
490
595
  alert('Checking for new connections — watch it on the Scan page.');
491
596
  } catch (e) {
@@ -493,7 +598,7 @@ function RefreshButton({ isMobile }) {
493
598
  }
494
599
  }}
495
600
  disabled={Boolean(busy)}
496
- title={busy ? `${busy}. One scan at a time.` : 'Refresh connections + bridges'}
601
+ title={busy ? `${busy}. One scan at a time.` : 'Check for new connections'}
497
602
  style={{
498
603
  width: isMobile ? 28 : 32, height: isMobile ? 28 : 32, borderRadius: '50%', border: 'none',
499
604
  cursor: busy ? 'not-allowed' : 'pointer', opacity: busy ? 0.4 : 1,
@@ -12,6 +12,7 @@ import { useUser } from '../components/UserProvider';
12
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
18
  const TABS = [['map', 'Map'], ['industries', 'Industries'], ['companies', 'Companies']];
@@ -466,8 +467,8 @@ function PathPersonRow({ p, level, companyPeople, d1Data, selectedCompany }) {
466
467
  }}>{p.connected ? '✓' : '🔒'}</div>
467
468
 
468
469
  {/* Photo */}
469
- {(p.profile_image_url || p.imageUrl) ? (
470
- <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={{
471
472
  width: 26, height: 26, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
472
473
  }} onError={e => e.target.style.display = 'none'} />
473
474
  ) : (
@@ -11,6 +11,9 @@ import Link from 'next/link';
11
11
  import useRequests from '../components/useRequests';
12
12
  import { hasRequest, markRequested, undoRequest } from '../../lib/requests-client';
13
13
  import { keyFor } from '../../lib/separation';
14
+ import { localPhoto } from '../../lib/photos';
15
+ import { reachIndex } from '../../lib/reach';
16
+ import { loadScanNotes } from '../../lib/scraper-client';
14
17
 
15
18
  const TIER_COLORS = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
16
19
 
@@ -51,6 +54,8 @@ function QueueInner() {
51
54
  const [view, setView] = useState('quest'); // quest | recs | pending
52
55
  const [added, setAdded] = useState([]);
53
56
  const [mappedIds, setMappedIds] = useState(() => new Set());
57
+ // Whose circle can be scanned next, hidden lists known (lib/reach.js).
58
+ const [reach, setReach] = useState(null);
54
59
  const [bridgeFilter, setBridgeFilter] = useState(null);
55
60
 
56
61
  // Default to bridge view — shows D1 person → their D2 people
@@ -75,14 +80,16 @@ function QueueInner() {
75
80
  if (IS_DEMO) return;
76
81
  if (!userId) return;
77
82
  async function load() {
78
- const [net, pendingRes] = await Promise.all([
83
+ const [net, pendingRes, notes] = await Promise.all([
79
84
  loadNetwork(userId),
80
85
  fetch(`/api/outreach?userId=${userId}`).then(r => r.json()).catch(() => ({ pending: [] })),
86
+ loadScanNotes(),
81
87
  ]);
82
88
  const d1 = net.degree1;
83
89
  const d2 = net.degree2;
84
90
  setAdded(d1.filter(c => c.unlocked_from_bridge_id));
85
91
  setMappedIds(new Set(d2.map(c => c.source_connection_id).filter(Boolean)));
92
+ setReach(reachIndex(d1, d2, notes));
86
93
  const d1Urls = new Set(d1.map(c => c.profile_url));
87
94
  const bridgeById = {};
88
95
  d1.forEach(c => { bridgeById[c.id] = c; });
@@ -323,6 +330,7 @@ function QueueInner() {
323
330
  sentIds={sentIds}
324
331
  added={added}
325
332
  mappedIds={mappedIds}
333
+ reach={reach}
326
334
  onSend={(r) => markRequested(r, { bridgeId: r.source_connection_id }).catch(() => {})}
327
335
  onUndo={(r) => undoRequest(r).catch(() => {})}
328
336
  />
@@ -355,8 +363,8 @@ function QueueInner() {
355
363
  border: '1px solid rgba(255,107,53,0.1)',
356
364
  }}>
357
365
  {/* Photo */}
358
- {p.profile_image_url ? (
359
- <img src={p.profile_image_url} alt="" style={{
366
+ {localPhoto(p.profile_image_url) ? (
367
+ <img src={localPhoto(p.profile_image_url)} alt="" style={{
360
368
  width: 36, height: 36, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
361
369
  border: `2px solid ${TIER_COLORS[p.tier] || '#555'}`,
362
370
  }} onError={e => { e.target.style.display = 'none'; }} />
@@ -436,8 +444,8 @@ function QueueInner() {
436
444
  border: `1px solid ${TIER_COLORS[bridge.tier] || '#555'}20`,
437
445
  }}>
438
446
  <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
439
- {bridge.profile_image_url ? (
440
- <img src={bridge.profile_image_url} alt="" style={{
447
+ {localPhoto(bridge.profile_image_url) ? (
448
+ <img src={localPhoto(bridge.profile_image_url)} alt="" style={{
441
449
  width: 36, height: 36, borderRadius: '50%', objectFit: 'cover',
442
450
  border: `2px solid ${TIER_COLORS[bridge.tier]}`,
443
451
  }} onError={e => { e.target.style.display = 'none'; }} />
@@ -563,8 +571,8 @@ function QueueInner() {
563
571
  <input type="checkbox" checked={selected.has(r.id)}
564
572
  onChange={() => toggleSelect(r.id)} onClick={e => e.stopPropagation()}
565
573
  style={{ cursor: 'pointer', flexShrink: 0 }} />
566
- {r.profile_image_url ? (
567
- <img src={r.profile_image_url} alt="" style={{
574
+ {localPhoto(r.profile_image_url) ? (
575
+ <img src={localPhoto(r.profile_image_url)} alt="" style={{
568
576
  width: 28, height: 28, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
569
577
  border: `1.5px solid ${TIER_COLORS[r.tier] || '#555'}`,
570
578
  }} onError={e => { e.target.style.display = 'none'; }} />
@@ -610,8 +618,8 @@ function QueueInner() {
610
618
  style={{ cursor: 'pointer', flexShrink: 0 }} />
611
619
 
612
620
  {/* Photo */}
613
- {r.profile_image_url ? (
614
- <img src={r.profile_image_url} alt="" style={{
621
+ {localPhoto(r.profile_image_url) ? (
622
+ <img src={localPhoto(r.profile_image_url)} alt="" style={{
615
623
  width: 32, height: 32, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
616
624
  border: `1.5px solid ${TIER_COLORS[r.tier] || '#555'}`,
617
625
  }} onError={e => { e.target.style.display = 'none'; e.target.nextSibling.style.display = 'flex'; }} />
@@ -619,7 +627,7 @@ function QueueInner() {
619
627
  <div style={{
620
628
  width: 32, height: 32, borderRadius: '50%', flexShrink: 0, fontSize: 12, fontWeight: 700,
621
629
  background: TIER_COLORS[r.tier] || '#555', color: r.tier === 'S' ? '#000' : '#fff',
622
- display: r.profile_image_url ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center',
630
+ display: localPhoto(r.profile_image_url) ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center',
623
631
  }}>{r.name?.charAt(0)}</div>
624
632
 
625
633
  {/* Info */}