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
@@ -9,7 +9,7 @@
9
9
  // Every statement is CREATE ... IF NOT EXISTS, so applying it is both
10
10
  // first-run setup and a no-op on every later boot.
11
11
 
12
- export const SCHEMA_SQL = `-- 6 Degrees — local SQLite schema.
12
+ export const SCHEMA_SQL = `-- Six Degrees — local SQLite schema.
13
13
  --
14
14
  -- Ported from the Postgres schema this project ran on before it went
15
15
  -- local-first. Type mapping: uuid -> TEXT (generated in JS), jsonb and
@@ -60,6 +60,7 @@ CREATE TABLE IF NOT EXISTS linkedin_connections (
60
60
  unlocked_from_bridge_id TEXT,
61
61
  unlocked_from_name TEXT,
62
62
  outreach_status TEXT,
63
+ mutual_count INTEGER, -- LinkedIn's count of mutual connections (2nd degree), when a scan read one
63
64
  scanned_company TEXT,
64
65
  score_why TEXT,
65
66
  user_id TEXT,
@@ -23,9 +23,9 @@ const eslintConfig = defineConfig([
23
23
  // surface in review.
24
24
  "react-hooks/set-state-in-effect": "warn",
25
25
 
26
- // Avatars are user-supplied remote URLs (or local files captured by the
27
- // scraper) with graceful initial-letter fallbacks. next/image would add a
28
- // loader and per-request cost for no benefit here.
26
+ // Avatars are small local files the scanner saved (lib/photos.js), with
27
+ // graceful initial-letter fallbacks. next/image would add a loader and
28
+ // per-request cost for no benefit here.
29
29
  "@next/next/no-img-element": "off",
30
30
  },
31
31
  },
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # Install 6 Degrees on a Mac with one line:
2
+ # Install Six Degrees on a Mac with one line:
3
3
  #
4
4
  # curl -fsSL https://raw.githubusercontent.com/blakeb056/six-degrees/main/install.sh | bash
5
5
  #
@@ -108,7 +108,7 @@ stop_running_copy() {
108
108
  }
109
109
 
110
110
  main() {
111
- printf '\n 6 Degrees — installer\n\n'
111
+ printf '\n Six Degrees — installer\n\n'
112
112
 
113
113
  [ "$(uname -s)" = "Darwin" ] || fail "This installer is for macOS. On Linux, run: npx six-degrees (needs Node 22.13 or later). Windows isn't supported yet."
114
114
 
@@ -182,7 +182,7 @@ main() {
182
182
 
183
183
  if [ -z "${SIX_DEGREES_NO_OPEN:-}" ]; then
184
184
  open "$target"
185
- printf '\n ✓ Opening 6 Degrees. Next time, open it from Applications or Spotlight.\n'
185
+ printf '\n ✓ Opening Six Degrees. Next time, open it from Applications or Spotlight.\n'
186
186
  else
187
187
  printf '\n ✓ Done.\n'
188
188
  fi
@@ -0,0 +1,139 @@
1
+ // Where Bridge Chains puts a circle's dots.
2
+ //
3
+ // Blake, 2026-09-28: "when i scan someone and have a lot of 2nd degree its
4
+ // almost a solid line and needs to expand more". Every dot sat on one ring, so
5
+ // 200 people round one circle were a band and 800 were a solid line. Here a
6
+ // circle fills rings from the inside out instead, as many as its count needs:
7
+ // a small circle is one ring, a big one spreads into more, and neighbours keep
8
+ // their distance. Plain maths, no React; tests/chain-layout.test.mjs.
9
+
10
+ const TAU = Math.PI * 2;
11
+
12
+ /** How many dots fit on a ring of `radius` across `sweep` radians, `spacing` apart. */
13
+ function capacity(radius, spacing, sweep) {
14
+ if (radius <= 0) return 1;
15
+ const arc = sweep * radius;
16
+ // A whole circle closes on itself; an arc has a dot at each end.
17
+ return Math.max(1, sweep >= TAU - 1e-9 ? Math.floor(arc / spacing) : Math.floor(arc / spacing) + 1);
18
+ }
19
+
20
+ /** The rings from `inner` outwards, `spacing` apart, until `n` dots fit. */
21
+ function ringsFor(n, inner, spacing, sweep) {
22
+ const radii = [];
23
+ let room = 0;
24
+ for (let r = inner; room < n; r += spacing) {
25
+ radii.push(r);
26
+ room += capacity(r, spacing, sweep);
27
+ }
28
+ return radii;
29
+ }
30
+
31
+ const outerOf = (n, inner, spacing, sweep) => {
32
+ const radii = ringsFor(n, inner, spacing, sweep);
33
+ return radii[radii.length - 1];
34
+ };
35
+
36
+ /**
37
+ * Places `n` dots in rings round a centre at (0, 0).
38
+ *
39
+ * The first ring sits at `inner`, and each next one `spacing` further out.
40
+ * When that would run past `outer`, the rings first start further in (down to
41
+ * `innerMin`), then close up (down to `minSpacing`); past that they carry on
42
+ * beyond `outer` rather than overlap, and zooming out shows them. The dots on
43
+ * each ring share it evenly, in proportion to its length, so every ring is
44
+ * about as full as the rest; the first `n` of a list sorted best-first land
45
+ * nearest the centre. Every other ring is turned by half a step, so dots on
46
+ * neighbouring rings don't line up in spokes.
47
+ *
48
+ * `start` and `sweep` fill an arc instead of the whole circle, each ring from
49
+ * its middle outwards.
50
+ *
51
+ * @returns {{ spacing: number, inner: number, rings: { radius: number, count: number }[],
52
+ * points: { x: number, y: number, angle: number, ring: number }[] }}
53
+ */
54
+ export function ringLayout(n, {
55
+ inner, outer, spacing, minSpacing = spacing / 3, innerMin = inner,
56
+ start = -Math.PI / 2, sweep = TAU,
57
+ } = {}) {
58
+ const count = Math.max(0, Math.floor(n) || 0);
59
+ if (!count) return { spacing, inner, rings: [], points: [] };
60
+
61
+ let s = spacing;
62
+ let r0 = inner;
63
+ if (outerOf(count, r0, s, sweep) > outer) {
64
+ // Start further in, as far as innerMin, before closing anything up.
65
+ if (outerOf(count, innerMin, s, sweep) <= outer) {
66
+ let lo = innerMin, hi = inner;
67
+ for (let i = 0; i < 24; i++) {
68
+ const mid = (lo + hi) / 2;
69
+ if (outerOf(count, mid, s, sweep) <= outer) lo = mid; else hi = mid;
70
+ }
71
+ r0 = lo;
72
+ } else {
73
+ r0 = innerMin;
74
+ let lo = minSpacing, hi = spacing;
75
+ if (outerOf(count, r0, lo, sweep) <= outer) {
76
+ for (let i = 0; i < 24; i++) {
77
+ const mid = (lo + hi) / 2;
78
+ if (outerOf(count, r0, mid, sweep) <= outer) lo = mid; else hi = mid;
79
+ }
80
+ }
81
+ s = lo;
82
+ }
83
+ }
84
+
85
+ const radii = ringsFor(count, r0, s, sweep);
86
+ const caps = radii.map((r) => capacity(r, s, sweep));
87
+ const room = caps.reduce((a, b) => a + b, 0);
88
+
89
+ // Share the dots in proportion to each ring's room, never over it; what the
90
+ // rounding leaves goes to the rings with the most room left.
91
+ const counts = caps.map((c) => Math.min(c, Math.floor((count * c) / room)));
92
+ let left = count - counts.reduce((a, b) => a + b, 0);
93
+ while (left > 0) {
94
+ let best = -1;
95
+ for (let k = 0; k < caps.length; k++) {
96
+ if (counts[k] < caps[k] && (best < 0 || caps[k] - counts[k] > caps[best] - counts[best])) best = k;
97
+ }
98
+ counts[best]++;
99
+ left--;
100
+ }
101
+
102
+ const whole = sweep >= TAU - 1e-9;
103
+ const points = [];
104
+ const rings = [];
105
+ radii.forEach((radius, k) => {
106
+ const m = counts[k];
107
+ if (!m) return;
108
+ rings.push({ radius, count: m });
109
+ // Round a whole circle evenly; across an arc from one edge to the other, a
110
+ // lone dot in the middle of it. An arc fills from its middle outwards, so
111
+ // the first in the list sit nearest whatever the arc fans out from.
112
+ const step = whole ? TAU / m : m > 1 ? sweep / (m - 1) : 0;
113
+ const from = whole ? start + (k % 2 ? step / 2 : 0) : m > 1 ? start : start + sweep / 2;
114
+ const slots = whole ? null : middleOut(m);
115
+ for (let j = 0; j < m; j++) {
116
+ const angle = from + (slots ? slots[j] : j) * step;
117
+ points.push({ x: radius * Math.cos(angle), y: radius * Math.sin(angle), angle, ring: rings.length - 1 });
118
+ }
119
+ });
120
+ return { spacing: s, inner: r0, rings, points };
121
+ }
122
+
123
+ /** 0 … m-1 from the middle outwards: for 5, 2 3 1 4 0. */
124
+ function middleOut(m) {
125
+ let lo = Math.floor((m - 1) / 2);
126
+ let hi = lo + 1;
127
+ const out = [lo--];
128
+ while (out.length < m) {
129
+ if (hi < m) out.push(hi++);
130
+ if (out.length < m && lo >= 0) out.push(lo--);
131
+ }
132
+ return out;
133
+ }
134
+
135
+ /** A dot's radius for a layout's spacing: bigger for higher tiers, never touching a neighbour. */
136
+ export function dotRadius(spacing, tier) {
137
+ const scale = tier === 'S' ? 1 : tier === 'A' ? 0.86 : tier === 'B' ? 0.72 : 0.6;
138
+ return Math.max(1.4, Math.min(7, spacing * 0.42 * scale));
139
+ }
@@ -0,0 +1,169 @@
1
+ // Someone's circle as rings, for the top of their profile card.
2
+ //
3
+ // Blake, 2026-09-28: "for every profile card we can see their network circle
4
+ // … all of their current connections scraped and graded but then outside of
5
+ // that sits the connections all from d2 that we added and scanned coming from
6
+ // that person as the provider of the cluster … and then for d3,d4,d5,d6".
7
+ //
8
+ // Two facts the app already keeps make the whole chain, with nothing new stored:
9
+ // source_connection_id whose circle a row sits in: a scan of that person found it
10
+ // unlocked_from_bridge_id who introduced someone you connected with, kept
11
+ // after they became your connection (lib/promote.js)
12
+ // So from a person P: P's circle (D2, through P) → the people in it you
13
+ // connected with or asked → their circles (D3) → who you added from those →
14
+ // their circles (D4) … to six degrees. Everyone appears once, in the nearest
15
+ // ring they reach.
16
+
17
+ import { keyFor, score1 } from './separation.js';
18
+
19
+ export const MAX_DEGREE = 6;
20
+
21
+ /** A request is out to them and they haven't accepted (Sidebar's "Connect to Unlock Path"). */
22
+ export const requestedByDefault = (row) => row?.unlock_status === 'pending';
23
+
24
+ /**
25
+ * Rows grouped two ways: `circles`, id → the rows a scan of that person found;
26
+ * `introduced`, id → your connections you met through them.
27
+ */
28
+ export function circleIndex(connections = [], degree2 = []) {
29
+ const circles = new Map();
30
+ for (const row of degree2 || []) {
31
+ const id = row?.source_connection_id;
32
+ if (id == null) continue;
33
+ let list = circles.get(id);
34
+ if (!list) circles.set(id, (list = []));
35
+ list.push(row);
36
+ }
37
+ const introduced = new Map();
38
+ for (const row of connections || []) {
39
+ const id = row?.unlocked_from_bridge_id;
40
+ if (id == null || row.degree !== 1) continue;
41
+ let list = introduced.get(id);
42
+ if (!list) introduced.set(id, (list = []));
43
+ list.push(row);
44
+ }
45
+ return { circles, introduced };
46
+ }
47
+
48
+ const STATE_ORDER = { connected: 0, requested: 1 };
49
+ const byStateThenPower = (a, b) => (STATE_ORDER[a.state] ?? 2) - (STATE_ORDER[b.state] ?? 2)
50
+ || score1(b.row) - score1(a.row)
51
+ || String(a.row.name || '').localeCompare(String(b.row.name || ''));
52
+
53
+ /**
54
+ * The rings around `person`: [{ degree, people: [{ row, key, parentId, state }] }],
55
+ * degree 2 first. `state` is 'connected' (you added them, so their own circle
56
+ * is the next ring out), 'requested' (a request is out: drawn dotted) or null.
57
+ * Also the totals: { people, connected, requested }.
58
+ */
59
+ export function circleRings(person, index, { maxDegree = MAX_DEGREE, isRequested = requestedByDefault } = {}) {
60
+ const rings = [];
61
+ const totals = { people: 0, connected: 0, requested: 0 };
62
+ if (!person || !index) return { rings, totals };
63
+ const seen = new Set([keyFor(person)]);
64
+ let frontier = [person];
65
+ for (let degree = 2; degree <= maxDegree && frontier.length; degree++) {
66
+ const ring = [];
67
+ const next = [];
68
+ for (const parent of frontier) {
69
+ const kids = [];
70
+ // The people you connected with first: they have circles of their own.
71
+ for (const row of index.introduced.get(parent.id) || []) {
72
+ const key = keyFor(row);
73
+ if (seen.has(key)) continue;
74
+ seen.add(key);
75
+ kids.push({ row, key, parentId: parent.id, state: 'connected' });
76
+ next.push(row);
77
+ }
78
+ for (const row of index.circles.get(parent.id) || []) {
79
+ const key = keyFor(row);
80
+ if (seen.has(key)) continue;
81
+ seen.add(key);
82
+ kids.push({ row, key, parentId: parent.id, state: isRequested(row) ? 'requested' : null });
83
+ }
84
+ kids.sort(byStateThenPower);
85
+ ring.push(...kids);
86
+ }
87
+ if (!ring.length) break;
88
+ for (const p of ring) {
89
+ totals.people++;
90
+ if (p.state === 'connected') totals.connected++;
91
+ else if (p.state === 'requested') totals.requested++;
92
+ }
93
+ rings.push({ degree, people: ring });
94
+ frontier = next;
95
+ }
96
+ return { rings, totals };
97
+ }
98
+
99
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
100
+
101
+ /**
102
+ * Where every dot goes, in a square `size` wide: a radial tree. Each person
103
+ * gets a slice of the circle in proportion to how much of the next rings
104
+ * hangs off them, so whoever opened the most reach shows it at a glance, and
105
+ * their circle fans out behind them.
106
+ * Returns { size, center, rings: [{ degree, radius, dotR, items }], spokes }.
107
+ */
108
+ export function circleLayout(rings = [], size = 240) {
109
+ const half = size / 2;
110
+ const R = rings.length;
111
+ const radiusOf = (k) => half * (R === 1 ? 0.78 : 0.4 + 0.52 * (k / (R - 1)));
112
+
113
+ // Each ring's people by who they hang off, in the ring's order.
114
+ const byParent = rings.map((ring) => {
115
+ const m = new Map();
116
+ for (const p of ring.people) {
117
+ if (!m.has(p.parentId)) m.set(p.parentId, []);
118
+ m.get(p.parentId).push(p);
119
+ }
120
+ return m;
121
+ });
122
+ const kidsOf = (k, id) => byParent[k + 1]?.get(id) || [];
123
+
124
+ // Weights, outermost ring first: at least 1, else what hangs off them.
125
+ const weight = new Map();
126
+ for (let k = R - 1; k >= 0; k--) {
127
+ for (const p of rings[k].people) {
128
+ const w = p.state === 'connected' ? kidsOf(k, p.row.id).reduce((s, c) => s + weight.get(c), 0) : 0;
129
+ weight.set(p, Math.max(1, w));
130
+ }
131
+ }
132
+
133
+ const out = [];
134
+ const spokes = [];
135
+ const span = new Map(); // row id → [start, end] angle for its children
136
+ const START = -Math.PI / 2;
137
+ for (let k = 0; k < R; k++) {
138
+ const radius = radiusOf(k);
139
+ const people = rings[k].people;
140
+ const dotR = clamp((2 * Math.PI * radius) / Math.max(1, people.length) * 0.32, 0.8, size / 60);
141
+ const items = [];
142
+ for (const [parentId, group] of byParent[k]) {
143
+ const [a0, a1] = k === 0 ? [START, START + 2 * Math.PI] : span.get(parentId) || [START, START + 2 * Math.PI];
144
+ const total = group.reduce((s, p) => s + weight.get(p), 0);
145
+ let a = a0;
146
+ for (const p of group) {
147
+ const w = ((a1 - a0) * weight.get(p)) / total;
148
+ const angle = a + w / 2;
149
+ const x = half + radius * Math.cos(angle);
150
+ const y = half + radius * Math.sin(angle);
151
+ items.push({ ...p, angle, x, y });
152
+ if (p.state === 'connected') span.set(p.row.id, [a, a + w]);
153
+ a += w;
154
+ }
155
+ }
156
+ // A line from each person you added out to the middle of their own circle.
157
+ if (k + 1 < R) {
158
+ const nextR = radiusOf(k + 1);
159
+ for (const it of items) {
160
+ if (it.state !== 'connected' || !kidsOf(k, it.row.id).length) continue;
161
+ const [s, e] = span.get(it.row.id);
162
+ const mid = (s + e) / 2;
163
+ spokes.push({ x1: it.x, y1: it.y, x2: half + nextR * Math.cos(mid), y2: half + nextR * Math.sin(mid) });
164
+ }
165
+ }
166
+ out.push({ degree: rings[k].degree, radius, dotR, items });
167
+ }
168
+ return { size, center: { x: half, y: half }, rings: out, spokes };
169
+ }
@@ -85,7 +85,7 @@ export const isSenior = (headline) => getSeniority(headline).level >= 4;
85
85
  * A company's industry is the one scoring uses (lib/scoring.js
86
86
  * networkCompanies over the same rows): the curated list's, else the name's,
87
87
  * else what most of its people say. So a company's colour here is the sector
88
- * Settings → Your sector leans on, and each company is the one scoring
88
+ * Scores → Your sector leans on, and each company is the one scoring
89
89
  * scores: both name it with cleanCompany().
90
90
  */
91
91
  export function buildCompanyIndex(rows = []) {
@@ -1,4 +1,5 @@
1
- import { scorePerson } from './scoring.js';
1
+ import { scorePerson, curveCutoffs, curvedTier } from './scoring.js';
2
+ import { toIsoDate } from './ingest.js';
2
3
  // LinkedIn Connections.csv import — parsed ENTIRELY in the browser.
3
4
  //
4
5
  // Nothing is uploaded and nothing is written to the database: an imported
@@ -49,6 +50,20 @@ export function scoreRecord(headline, company) {
49
50
  return { seniority_score: s.title.points, company_prestige_score: s.companyScore, power_score: s.power, tier: s.tier };
50
51
  }
51
52
 
53
+ /**
54
+ * Tiers for a whole import, graded on its own curve (lib/scoring.js CURVE):
55
+ * a CSV import uses the app's defaults, never your settings (no company
56
+ * scores, no sector), and the curve is the default, so the first look at any
57
+ * export has a top. Scores → Tiers says so while one is open. Run on the
58
+ * parse (the import page's counts) and on every load (the tab keeps only
59
+ * what the export says, packConnections), so the two always agree.
60
+ */
61
+ function gradeOnCurve(connections) {
62
+ const cut = curveCutoffs(connections.map((c) => c.power_score));
63
+ for (const c of connections) c.tier = curvedTier(c.power_score, cut);
64
+ return connections;
65
+ }
66
+
52
67
  // Stable id from the profile URL so re-importing keeps node identity.
53
68
  function hashId(input) {
54
69
  let h = 5381;
@@ -122,12 +137,9 @@ export function parseConnectionsCsv(text) {
122
137
  if (seen.has(id)) { skipped++; continue; }
123
138
  seen.add(id);
124
139
 
125
- let connected_date = null;
126
- const raw = cell(iConnected);
127
- if (raw) {
128
- const d = new Date(raw);
129
- if (!isNaN(d.getTime())) connected_date = d.toISOString().split('T')[0];
130
- }
140
+ // "28 Sep 2026", read as the calendar date it is. new Date(raw).toISOString()
141
+ // made it local midnight in UTC, a day early anywhere east of London.
142
+ const connected_date = toIsoDate(cell(iConnected));
131
143
 
132
144
  connections.push(connectionFrom({ id, name, role, company, profileUrl, connected_date }));
133
145
  }
@@ -138,7 +150,7 @@ export function parseConnectionsCsv(text) {
138
150
  );
139
151
  }
140
152
 
141
- connections.sort((a, b) => b.power_score - a.power_score);
153
+ gradeOnCurve(connections).sort((a, b) => b.power_score - a.power_score);
142
154
  return { connections, skipped, total: connections.length + skipped };
143
155
  }
144
156
 
@@ -161,8 +173,7 @@ export function packConnections(connections) {
161
173
  }
162
174
 
163
175
  export function unpackConnections(rows = []) {
164
- return rows
165
- .map(([id, name, role, company, profileUrl, connected_date]) => connectionFrom({ id, name, role, company, profileUrl, connected_date }))
176
+ return gradeOnCurve(rows.map(([id, name, role, company, profileUrl, connected_date]) => connectionFrom({ id, name, role, company, profileUrl, connected_date })))
166
177
  .sort((a, b) => b.power_score - a.power_score);
167
178
  }
168
179
 
@@ -111,7 +111,8 @@ export function buildExport(db, { dir, outFile, includePhotos = true, appVersion
111
111
  // file the other computer doesn't have: some views draw an empty circle
112
112
  // for it. With no photo, they show initials, and the next scan of that
113
113
  // person saves a new one. A photo not saved yet (LinkedIn's own link,
114
- // which expires in a few weeks) is left as it is, as it is here.
114
+ // which expires in a few weeks) is left as it is, as it is here: neither
115
+ // computer shows it, and either saves it once (lib/photos.js).
115
116
  if (!includePhotos) {
116
117
  out.exec("UPDATE linkedin_connections SET profile_image_url = NULL WHERE profile_image_url LIKE '/avatars/%'");
117
118
  }
@@ -11,9 +11,11 @@
11
11
  import { readdirSync, lstatSync, existsSync, rmSync } from 'node:fs';
12
12
  import { spawn } from 'node:child_process';
13
13
  import path from 'node:path';
14
+ import { AVATAR_FILE } from './photos.js';
14
15
 
15
- /** A photo the /avatars route serves: a plain file name, no folders. The route uses this same pattern. */
16
- export const AVATAR_FILE = /^[A-Za-z0-9_-]+\.(webp|jpg|jpeg|png)$/;
16
+ // A photo the /avatars route serves (lib/photos.js). An export carries and an
17
+ // import accepts exactly those names.
18
+ export { AVATAR_FILE };
17
19
 
18
20
  /**
19
21
  * The scanner's notes about the network itself: without them, people whose
@@ -108,6 +108,7 @@ export function applySchema(db) {
108
108
  // exists, so columns added after first release are added here, once.
109
109
  const ADDED_COLUMNS = [
110
110
  ['linkedin_connections', 'score_why', 'TEXT'],
111
+ ['linkedin_connections', 'mutual_count', 'INTEGER'],
111
112
  ];
112
113
 
113
114
  function addMissingColumns(db) {
@@ -0,0 +1,12 @@
1
+ // The Galaxy's view, as numbers, kept apart from the drawing so it can be tested
2
+ // (app/components/ForceGraph.js).
3
+
4
+ /**
5
+ * The same view in a box of a new size. It moves by half the change, at the
6
+ * same zoom, so whatever was in the middle of the box is in the middle still.
7
+ * Opening a 320-pixel panel slides the view 160 pixels left, and closing it
8
+ * slides the view back to exactly where it was.
9
+ */
10
+ export function recentre({ x, y, k }, from, to) {
11
+ return { x: x + (to.width - from.width) / 2, y: y + (to.height - from.height) / 2, k };
12
+ }
@@ -43,22 +43,29 @@ const MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july',
43
43
  'august', 'september', 'october', 'november', 'december'];
44
44
 
45
45
  /**
46
- * "September 22, 2026" (as LinkedIn prints "Connected on …") → "2026-09-22".
46
+ * A date as LinkedIn writes it → "2026-09-22": "September 22, 2026" (a profile's
47
+ * "Connected on …", read by the scanner) or "22 Sep 2026" (the "Connected On"
48
+ * column of the Connections.csv export, read by lib/csv.js).
47
49
  *
48
50
  * Parsed by hand rather than with new Date(text).toISOString(): that reads the
49
51
  * text as local midnight and converts to UTC, which moves the date back a day
50
52
  * anywhere east of Greenwich — and throws outright on text it cannot read.
51
- * Returns null for anything it does not recognise.
53
+ * A calendar date has no time zone, so nothing here uses one.
54
+ * Returns null for anything it does not recognise, or a day the month doesn't have.
52
55
  */
53
56
  export function toIsoDate(text) {
54
57
  const t = String(text || '').trim();
55
58
  if (/^\d{4}-\d{2}-\d{2}$/.test(t)) return t;
56
- const m = t.match(/^([A-Za-z]+)\.?\s+(\d{1,2}),?\s+(\d{4})$/);
57
- if (!m) return null;
58
- const month = MONTHS.findIndex((name) => name.startsWith(m[1].toLowerCase()) && m[1].length >= 3);
59
- const day = Number(m[2]);
60
- if (month < 0 || day < 1 || day > 31) return null;
61
- return `${m[3]}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
59
+ let name, day, year;
60
+ let m = t.match(/^([A-Za-z]+)\.?\s+(\d{1,2}),?\s+(\d{4})$/); // September 22, 2026
61
+ if (m) [, name, day, year] = m;
62
+ else if ((m = t.match(/^(\d{1,2})\s+([A-Za-z]+)\.?\s+(\d{4})$/))) [, day, name, year] = m; // 22 Sep 2026
63
+ else return null;
64
+ const month = MONTHS.findIndex((full) => full.startsWith(name.toLowerCase()) && name.length >= 3);
65
+ day = Number(day);
66
+ // Day 0 of the next month is this month's last, counted in UTC so no zone moves it.
67
+ if (month < 0 || day < 1 || day > new Date(Date.UTC(Number(year), month + 1, 0)).getUTCDate()) return null;
68
+ return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
62
69
  }
63
70
 
64
71
  // Tiers that make a new connection worth a notification of its own.
@@ -100,3 +107,15 @@ export function refreshNotifications({ added = [], checked = 0, tierOf = () => n
100
107
  }
101
108
  return out;
102
109
  }
110
+
111
+ /**
112
+ * LinkedIn's own count of the mutual connections you share with someone, as
113
+ * the scanner read it off their result card (scripts/scrape.py
114
+ * BRIDGE_RESULTS_JS), or null. A whole number from 1 (the bridge is always
115
+ * one) to 30,000 (LinkedIn's cap on connections); anything else is a misread
116
+ * and is dropped, never stored.
117
+ */
118
+ export function mutualCountOf(value) {
119
+ const n = typeof value === 'string' && /^\d[\d,]*$/.test(value.trim()) ? Number(value.replace(/,/g, '')) : value;
120
+ return Number.isInteger(n) && n >= 1 && n <= 30000 ? n : null;
121
+ }
@@ -1,4 +1,4 @@
1
- // Paths → Scores: the one-time offer to keep the curated list's old scores as
1
+ // The Scores tab: the one-time offer to keep the curated list's old scores as
2
2
  // your own, after the list was made neutral (lib/legacy-scores.js has the old
3
3
  // scores and why each changed).
4
4
  //
@@ -10,7 +10,7 @@
10
10
  // scored on the server, so the page doesn't ask while one is open.
11
11
  //
12
12
  // Keeping writes the old score under every name scoring uses for the entry's
13
- // companies, as Paths → Scores sets a score (setCompanyScores), then rescores
13
+ // companies, as the Scores tab sets a score (setCompanyScores), then rescores
14
14
  // everyone once for the whole batch. Keep or No thanks, it is never offered
15
15
  // again, and an offer whose first look finds nothing to offer closes itself.
16
16
 
@@ -62,14 +62,14 @@ export function legacyOfferState(db = getDb()) {
62
62
  }
63
63
 
64
64
  /**
65
- * What Paths → Scores offers, or null: nothing when the offer isn't open
65
+ * What the Scores tab offers, or null: nothing when the offer isn't open
66
66
  * (never was, or was answered) or no company in the network lost an old score
67
67
  * that you haven't replaced with one of your own. That last closes it for
68
68
  * good ('none'): the network the old list scored is the one read here, so
69
- * nothing could turn up later, and every visit to Paths → Scores would
69
+ * nothing could turn up later, and every visit to the Scores tab would
70
70
  * otherwise read the whole network again to find nothing.
71
71
  *
72
- * The companies are the ones Paths → Scores lists, those someone works at now,
72
+ * The companies are the ones the Scores tab lists, those someone works at now,
73
73
  * so a kept score shows there as yours and Auto can hand it back. (A name only
74
74
  * in former roles would be a score nothing lists; its people's former role
75
75
  * counts at 70% and moves them little.) Each is compared by its built-in score
@@ -116,7 +116,8 @@ export function legacyOffer(db = getDb()) {
116
116
  name: o.name,
117
117
  was: o.was,
118
118
  now: main.now.score,
119
- estimated: main.now.source !== 'known',
119
+ // A score from the curated list or the public dataset is a fact, not an estimate.
120
+ estimated: !['known', 'data'].includes(main.now.source),
120
121
  people: o.people.size,
121
122
  names: o.names.map((n) => n.name).sort(),
122
123
  };
@@ -1,5 +1,5 @@
1
1
  // The curated company list's scores from before it was made neutral, kept for
2
- // one thing only: the one-time offer on Paths → Scores to keep them as your
2
+ // one thing only: the one-time offer on the Scores tab to keep them as your
3
3
  // own (lib/legacy-offer.js). Nothing scores with these. lib/scoring.js never
4
4
  // imports this file, and the model must never read it.
5
5
  //
@@ -24,7 +24,7 @@
24
24
 
25
25
  export const LEGACY_SCORES = [
26
26
  // Rescored: scored like its peers Pinterest, Reddit and X.
27
- ['Snap', 9, /^(snap|snapchat)( inc)?$/, 'media'],
27
+ ['Snap', 9, /^(snap|snapchat)( inc)?( (specs|spectacles|(mena|emea|apac|latam|north america)( region)?))?$/, 'media'],
28
28
  // Removed: private companies that are neither household names nor Fortune
29
29
  // 500-sized, and small startups.
30
30
  ['Anduril', 9, /^anduril( industries)?$/, 'defense'],