six-degrees 0.3.0 → 0.4.0-beta.1

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 (299) 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 +34 -6
  5. package/.next/standalone/.next/routes-manifest.json +6 -0
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.rsc +10 -10
  17. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  18. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +9 -9
  19. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
  37. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/import.html +1 -1
  52. package/.next/standalone/.next/server/app/import.rsc +12 -12
  53. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +12 -12
  54. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  55. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +11 -11
  56. package/.next/standalone/.next/server/app/index.html +1 -1
  57. package/.next/standalone/.next/server/app/index.rsc +12 -12
  58. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +11 -11
  59. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +12 -12
  60. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  61. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/launch.html +2 -2
  64. package/.next/standalone/.next/server/app/launch.rsc +12 -12
  65. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +12 -12
  66. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  67. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +11 -11
  68. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/paths.html +1 -1
  73. package/.next/standalone/.next/server/app/paths.rsc +12 -12
  74. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +12 -12
  75. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  76. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +11 -11
  77. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/profile.html +1 -1
  80. package/.next/standalone/.next/server/app/profile.rsc +12 -12
  81. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +12 -12
  82. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  83. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +11 -11
  84. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  86. package/.next/standalone/.next/server/app/queue.html +1 -1
  87. package/.next/standalone/.next/server/app/queue.rsc +12 -12
  88. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +12 -12
  89. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  90. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +11 -11
  91. package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
  92. package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
  93. package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
  94. package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
  95. package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
  96. package/.next/standalone/.next/server/app/scores/page.js +13 -0
  97. package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
  98. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
  99. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
  100. package/.next/standalone/.next/server/app/scores.html +1 -0
  101. package/.next/standalone/.next/server/app/scores.meta +26 -0
  102. package/.next/standalone/.next/server/app/scores.rsc +24 -0
  103. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
  104. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
  105. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
  106. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  107. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  108. package/.next/standalone/.next/server/app/settings.html +1 -1
  109. package/.next/standalone/.next/server/app/settings.rsc +12 -12
  110. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +12 -12
  111. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  112. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +11 -11
  113. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  115. package/.next/standalone/.next/server/app/setup.html +1 -1
  116. package/.next/standalone/.next/server/app/setup.rsc +12 -12
  117. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +12 -12
  118. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  119. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +11 -11
  120. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  121. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rc27za._.js +8 -0
  122. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
  124. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -6
  127. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +3 -2
  130. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
  131. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/_0223v9s._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/_0bcf13g._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/_0cu6-an._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/_0wxbq78._.js +3 -0
  139. package/.next/standalone/.next/server/chunks/ssr/_0z88zcl._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  145. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +4 -4
  146. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
  147. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  148. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  149. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
  150. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  151. package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
  153. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
  154. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  155. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  156. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  157. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  158. package/.next/standalone/.next/server/pages/404.html +1 -1
  159. package/.next/standalone/.next/server/pages/500.html +1 -1
  160. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  161. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  162. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  163. package/.next/standalone/.next/static/chunks/0_yurn4q91vq8.js +16 -0
  164. package/.next/standalone/.next/static/chunks/0d_mh-06srh2k.js +1 -0
  165. package/.next/standalone/.next/static/chunks/0x3oadfikvb1c.js +1 -0
  166. package/.next/standalone/.next/static/chunks/1d5hl1io798n2.js +1 -0
  167. package/.next/standalone/.next/static/chunks/1qt67cvj9sjwi.js +1 -0
  168. package/.next/{static/chunks/1chp3nx45meg5.js → standalone/.next/static/chunks/1roznk7hkjes1.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/2_bf2n4gstad9.js +33 -0
  170. package/.next/standalone/.next/static/chunks/36gbwek52x_w-.js +2 -0
  171. package/.next/standalone/.next/static/chunks/3a55suosthntc.js +1 -0
  172. package/.next/standalone/.next/static/chunks/3fq1zq-6cfwbc.js +1 -0
  173. package/.next/standalone/.next/static/chunks/3nbnio971zlji.js +2 -0
  174. package/.next/standalone/.next/static/chunks/3s6awp3qq9s7w.js +1 -0
  175. package/.next/standalone/CHANGELOG.md +134 -0
  176. package/.next/standalone/README.md +30 -15
  177. package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
  178. package/.next/standalone/app/api/company-scores/route.js +1 -1
  179. package/.next/standalone/app/api/ingest/route.js +17 -1
  180. package/.next/standalone/app/api/outreach/route.js +107 -44
  181. package/.next/standalone/app/api/scraper/route.js +69 -8
  182. package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
  183. package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
  184. package/.next/standalone/app/components/CompanyScores.js +16 -10
  185. package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
  186. package/.next/standalone/app/components/OrbitGraph.js +30 -0
  187. package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
  188. package/.next/standalone/app/components/SeparationView.js +96 -14
  189. package/.next/standalone/app/components/Sidebar.js +258 -239
  190. package/.next/standalone/app/components/TheirCircle.js +293 -0
  191. package/.next/standalone/app/components/settings/SectorSection.js +5 -5
  192. package/.next/standalone/app/components/settings/TierSection.js +104 -0
  193. package/.next/standalone/app/components/useRequests.js +12 -0
  194. package/.next/standalone/app/components/useScanner.js +13 -0
  195. package/.next/standalone/app/components/views.js +1 -3
  196. package/.next/standalone/app/launch/page.js +4 -1
  197. package/.next/standalone/app/page.js +55 -51
  198. package/.next/standalone/app/paths/page.js +17 -12
  199. package/.next/standalone/app/profile/page.js +3 -3
  200. package/.next/standalone/app/queue/page.js +38 -50
  201. package/.next/standalone/app/scores/page.js +72 -0
  202. package/.next/standalone/app/settings/page.js +15 -3
  203. package/.next/standalone/db/schema.js +1 -0
  204. package/.next/standalone/lib/circle.js +169 -0
  205. package/.next/standalone/lib/companies.js +1 -1
  206. package/.next/standalone/lib/csv.js +17 -4
  207. package/.next/standalone/lib/db-client.js +1 -0
  208. package/.next/standalone/lib/ingest.js +12 -0
  209. package/.next/standalone/lib/legacy-offer.js +7 -6
  210. package/.next/standalone/lib/legacy-scores.js +2 -2
  211. package/.next/standalone/lib/rarity.js +80 -0
  212. package/.next/standalone/lib/requests-client.js +113 -0
  213. package/.next/standalone/lib/rpc.js +26 -15
  214. package/.next/standalone/lib/score-guess.js +60 -0
  215. package/.next/standalone/lib/scoring.js +1002 -61
  216. package/.next/standalone/lib/scraper-client.js +208 -17
  217. package/.next/standalone/lib/sector-directory.js +1 -1
  218. package/.next/standalone/lib/sector-focus.js +4 -3
  219. package/.next/standalone/lib/settings-effects.js +19 -1
  220. package/.next/standalone/lib/settings.js +4 -1
  221. package/.next/standalone/package-lock.json +2 -2
  222. package/.next/standalone/package.json +2 -2
  223. package/.next/standalone/public/demo-data.json +1 -1
  224. package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
  225. package/.next/standalone/scripts/scrape.py +62 -0
  226. package/.next/static/chunks/0_yurn4q91vq8.js +16 -0
  227. package/.next/static/chunks/0d_mh-06srh2k.js +1 -0
  228. package/.next/static/chunks/0x3oadfikvb1c.js +1 -0
  229. package/.next/static/chunks/1d5hl1io798n2.js +1 -0
  230. package/.next/static/chunks/1qt67cvj9sjwi.js +1 -0
  231. package/.next/{standalone/.next/static/chunks/1chp3nx45meg5.js → static/chunks/1roznk7hkjes1.js} +1 -1
  232. package/.next/static/chunks/2_bf2n4gstad9.js +33 -0
  233. package/.next/static/chunks/36gbwek52x_w-.js +2 -0
  234. package/.next/static/chunks/3a55suosthntc.js +1 -0
  235. package/.next/static/chunks/3fq1zq-6cfwbc.js +1 -0
  236. package/.next/static/chunks/3nbnio971zlji.js +2 -0
  237. package/.next/static/chunks/3s6awp3qq9s7w.js +1 -0
  238. package/CHANGELOG.md +134 -0
  239. package/README.md +30 -15
  240. package/db/schema.js +1 -0
  241. package/lib/circle.js +169 -0
  242. package/lib/companies.js +1 -1
  243. package/lib/csv.js +17 -4
  244. package/lib/db-client.js +1 -0
  245. package/lib/ingest.js +12 -0
  246. package/lib/legacy-offer.js +7 -6
  247. package/lib/legacy-scores.js +2 -2
  248. package/lib/rarity.js +80 -0
  249. package/lib/requests-client.js +113 -0
  250. package/lib/rpc.js +26 -15
  251. package/lib/score-guess.js +60 -0
  252. package/lib/scoring.js +1002 -61
  253. package/lib/scraper-client.js +208 -17
  254. package/lib/sector-directory.js +1 -1
  255. package/lib/sector-focus.js +4 -3
  256. package/lib/settings-effects.js +19 -1
  257. package/lib/settings.js +4 -1
  258. package/package.json +2 -2
  259. package/public/demo-data.json +1 -1
  260. package/scripts/scrape.py +62 -0
  261. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
  262. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
  263. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
  264. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
  265. package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
  266. package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
  267. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
  268. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
  269. package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
  270. package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
  271. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  272. package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
  273. package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  274. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
  275. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
  276. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  277. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
  278. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  279. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
  280. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  281. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  282. package/.next/standalone/app/components/BridgeRing.js +0 -753
  283. package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  284. package/.next/static/chunks/08k1crid-05dt.js +0 -1
  285. package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  286. package/.next/static/chunks/0q-_khndfxubv.js +0 -2
  287. package/.next/static/chunks/1c79veqedvubt.js +0 -1
  288. package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  289. package/.next/static/chunks/1uadqylrod5_v.js +0 -1
  290. package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  291. package/.next/static/chunks/289ktj97di6zg.js +0 -1
  292. package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  293. package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  294. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → NnWlxbDF24KhrU1eHiEnv}/_buildManifest.js +0 -0
  295. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → NnWlxbDF24KhrU1eHiEnv}/_clientMiddlewareManifest.js +0 -0
  296. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → NnWlxbDF24KhrU1eHiEnv}/_ssgManifest.js +0 -0
  297. /package/.next/static/{qqqcxvzBUizkECrT4swyK → NnWlxbDF24KhrU1eHiEnv}/_buildManifest.js +0 -0
  298. /package/.next/static/{qqqcxvzBUizkECrT4swyK → NnWlxbDF24KhrU1eHiEnv}/_clientMiddlewareManifest.js +0 -0
  299. /package/.next/static/{qqqcxvzBUizkECrT4swyK → NnWlxbDF24KhrU1eHiEnv}/_ssgManifest.js +0 -0
@@ -1,80 +1,143 @@
1
1
  import { db as supabase } from '../../../lib/db';
2
+ import { getDb, decodeRow } from '../../../lib/db-client';
3
+ import { resolveProfile } from '../../../lib/profile';
4
+
5
+ // Requests you've sent, kept per PERSON.
6
+ //
7
+ // A person has one row per bridge whose circle holds them (the table's key is
8
+ // profile + bridge + user), and a request used to mark only the copy that was
9
+ // clicked. The other copies still offered "Add", the Pending count counted
10
+ // copies, and every click awarded XP again. And the card's link wrote a second
11
+ // column (unlock_status) through another route, which Undo never took back.
12
+ // Now a request marks every copy of the person, in both columns, the bridge you
13
+ // asked through is kept (so once they accept, their card and circle say who
14
+ // introduced you: lib/promote.js keeps it), XP comes once per person, and Undo
15
+ // takes back all of it. lib/requests-client.js is the one way the pages call this.
2
16
 
3
17
  // XP rewards by tier
4
18
  const XP_SEND = { S: 25, A: 15, B: 10, C: 5, D: 2 };
5
19
  const XP_ACCEPT = { S: 100, A: 60, B: 35, C: 15, D: 5 };
20
+ const TIER_ORDER = ['S', 'A', 'B', 'C', 'D'];
6
21
 
7
22
  async function awardXP(userId, amount) {
8
23
  if (!userId || !amount) return;
9
- // Upsert: create if not exists, increment if exists
24
+ // Upsert: create if not exists, change if exists. Never below zero: an Undo
25
+ // takes back what a send gave, including sends from before sends gave any.
10
26
  const { data: existing } = await supabase.from('user_stats').select('xp').eq('id', userId).single();
11
27
  if (existing) {
12
- await supabase.from('user_stats').update({ xp: (existing.xp || 0) + amount }).eq('id', userId);
13
- } else {
28
+ await supabase.from('user_stats').update({ xp: Math.max(0, (existing.xp || 0) + amount) }).eq('id', userId);
29
+ } else if (amount > 0) {
14
30
  await supabase.from('user_stats').insert([{ id: userId, xp: amount, level: 1 }]).catch(() => {});
15
31
  }
16
32
  }
17
33
 
34
+ /** The profile a request belongs to: the one sent, else this machine's. */
35
+ function whose(userId) {
36
+ if (userId) return String(userId);
37
+ try { return resolveProfile({ create: false })?.id || null; } catch { return null; }
38
+ }
39
+
40
+ /** Every copy of one person: by the clicked row's profile URL, or the URL given. */
41
+ function copiesOf(db, { connectionId, profileUrl, userId }) {
42
+ let url = profileUrl ? String(profileUrl) : null;
43
+ let user = userId ? String(userId) : null;
44
+ let clicked = null;
45
+ if (connectionId != null) {
46
+ clicked = db.prepare('SELECT id, profile_url, user_id FROM linkedin_connections WHERE id = ?').get(String(connectionId)) || null;
47
+ if (clicked) {
48
+ url = clicked.profile_url || url;
49
+ user = user || clicked.user_id || null;
50
+ }
51
+ }
52
+ const cols = 'id, degree, tier, outreach_status, unlock_status, unlocked_from_bridge_id';
53
+ let rows = [];
54
+ if (url) {
55
+ rows = user
56
+ ? db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ? AND user_id = ?`).all(url, user)
57
+ : db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ?`).all(url);
58
+ } else if (clicked) {
59
+ rows = db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE id = ?`).all(clicked.id);
60
+ }
61
+ return { url, user, rows };
62
+ }
63
+
64
+ const inList = (ids) => ids.map(() => '?').join(', ');
65
+ const bestTier = (rows) => TIER_ORDER.find((t) => rows.some((r) => r.tier === t)) || 'D';
66
+
18
67
  export async function POST(request) {
19
68
  try {
20
69
  const body = await request.json();
21
- const { action, connectionId, profileUrl, userId } = body;
70
+ const { action, connectionId, profileUrl, userId, bridgeId } = body;
71
+ if (!['mark-sent', 'mark-accepted', 'undo'].includes(action)) {
72
+ return Response.json({ error: 'Unknown action' }, { status: 400 });
73
+ }
74
+ const db = getDb();
75
+ const { user, rows } = copiesOf(db, { connectionId, profileUrl, userId });
76
+ // Nobody on file (the sample network lives in the browser): nothing to keep.
77
+ if (!rows.length) return Response.json({ success: true, saved: 0, xp: 0 });
78
+ const ids = rows.map((r) => r.id);
79
+ const now = new Date().toISOString();
22
80
 
23
81
  if (action === 'mark-sent') {
24
- // Get the person's tier for XP calculation
25
- let tierQuery = supabase.from('linkedin_connections').select('tier');
26
- tierQuery = connectionId ? tierQuery.eq('id', connectionId) : tierQuery.eq('profile_url', profileUrl);
27
- const { data: person } = await tierQuery.single();
28
- const tier = person?.tier || 'D';
29
-
30
- let query = supabase.from('linkedin_connections').update({ outreach_status: 'sent' });
31
- query = connectionId ? query.eq('id', connectionId) : query.eq('profile_url', profileUrl);
32
- if (userId) query = query.eq('user_id', userId);
33
- const { error } = await query;
34
- if (error) return Response.json({ error: error.message }, { status: 500 });
35
-
36
- // Award XP for sending request
37
- await awardXP(userId, XP_SEND[tier] || 2);
38
-
39
- return Response.json({ success: true, xp: XP_SEND[tier] || 2 });
82
+ const already = rows.some((r) => r.outreach_status === 'sent' || r.outreach_status === 'accepted');
83
+ db.prepare(`UPDATE linkedin_connections SET outreach_status = 'sent', updated_at = ?
84
+ WHERE id IN (${inList(ids)}) AND (outreach_status IS NULL OR outreach_status NOT IN ('sent', 'accepted'))`).run(now, ...ids);
85
+ db.prepare(`UPDATE linkedin_connections SET unlock_status = 'pending', updated_at = ?
86
+ WHERE id IN (${inList(ids)}) AND (unlock_status IS NULL OR unlock_status = 'locked')`).run(now, ...ids);
87
+ // Who you asked through: one of your own connections, or nothing.
88
+ if (bridgeId != null) {
89
+ const bridge = db.prepare(
90
+ `SELECT id, name FROM linkedin_connections WHERE id = ? AND degree = 1${user ? ' AND user_id = ?' : ''}`,
91
+ ).get(...[String(bridgeId), ...(user ? [user] : [])]);
92
+ if (bridge) {
93
+ db.prepare(`UPDATE linkedin_connections SET unlocked_from_bridge_id = ?, unlocked_from_name = ?, updated_at = ?
94
+ WHERE id IN (${inList(ids)}) AND degree = 2 AND unlocked_from_bridge_id IS NULL`).run(bridge.id, bridge.name, now, ...ids);
95
+ }
96
+ }
97
+ const xp = already ? 0 : XP_SEND[bestTier(rows)] || 2;
98
+ await awardXP(whose(user), xp);
99
+ return Response.json({ success: true, saved: ids.length, xp, already });
40
100
  }
41
101
 
42
102
  if (action === 'mark-accepted') {
43
- let query = supabase.from('linkedin_connections').update({ outreach_status: 'accepted' });
44
- query = connectionId ? query.eq('id', connectionId) : query.eq('profile_url', profileUrl);
45
- if (userId) query = query.eq('user_id', userId);
46
- const { error } = await query;
47
- if (error) return Response.json({ error: error.message }, { status: 500 });
48
- return Response.json({ success: true });
103
+ db.prepare(`UPDATE linkedin_connections SET outreach_status = 'accepted', updated_at = ? WHERE id IN (${inList(ids)})`).run(now, ...ids);
104
+ return Response.json({ success: true, saved: ids.length, xp: 0, accepts: XP_ACCEPT[bestTier(rows)] || 5 });
49
105
  }
50
106
 
51
- if (action === 'undo') {
52
- let query = supabase.from('linkedin_connections').update({ outreach_status: null });
53
- query = connectionId ? query.eq('id', connectionId) : query.eq('profile_url', profileUrl);
54
- if (userId) query = query.eq('user_id', userId);
55
- const { error } = await query;
56
- if (error) return Response.json({ error: error.message }, { status: 500 });
57
- return Response.json({ success: true });
58
- }
59
-
60
- return Response.json({ error: 'Unknown action' }, { status: 400 });
107
+ // undo
108
+ const wasSent = rows.some((r) => r.outreach_status === 'sent');
109
+ db.prepare(`UPDATE linkedin_connections SET outreach_status = NULL, updated_at = ?
110
+ WHERE id IN (${inList(ids)}) AND outreach_status = 'sent'`).run(now, ...ids);
111
+ db.prepare(`UPDATE linkedin_connections SET unlock_status = 'locked', updated_at = ?
112
+ WHERE id IN (${inList(ids)}) AND unlock_status = 'pending'`).run(now, ...ids);
113
+ const xp = wasSent ? -(XP_SEND[bestTier(rows)] || 2) : 0;
114
+ await awardXP(whose(user), xp);
115
+ return Response.json({ success: true, saved: ids.length, xp });
61
116
  } catch (err) {
62
117
  console.error('Outreach error:', err);
63
118
  return Response.json({ error: err.message }, { status: 500 });
64
119
  }
65
120
  }
66
121
 
122
+ /** Everyone with a request out, once each (the newest copy), newest first. */
67
123
  export async function GET(request) {
68
124
  try {
69
125
  const { searchParams } = new URL(request.url);
70
- const userId = searchParams.get('userId');
71
-
72
- let query = supabase.from('linkedin_connections').select('*').eq('outreach_status', 'sent').order('created_at', { ascending: false });
73
- if (userId) query = query.eq('user_id', userId);
74
-
75
- const { data, error } = await query;
76
- if (error) return Response.json({ error: error.message }, { status: 500 });
77
- return Response.json({ pending: data || [] });
126
+ const user = whose(searchParams.get('userId'));
127
+ const rows = getDb().prepare(
128
+ `SELECT * FROM linkedin_connections
129
+ WHERE (outreach_status = 'sent' OR unlock_status = 'pending')${user ? ' AND user_id = ?' : ''}
130
+ ORDER BY created_at DESC`,
131
+ ).all(...(user ? [user] : []));
132
+ const seen = new Set();
133
+ const pending = [];
134
+ for (const row of rows) {
135
+ const key = row.profile_url || `id:${row.id}`;
136
+ if (seen.has(key)) continue;
137
+ seen.add(key);
138
+ pending.push(decodeRow('linkedin_connections', row));
139
+ }
140
+ return Response.json({ pending });
78
141
  } catch (err) {
79
142
  return Response.json({ error: err.message }, { status: 500 });
80
143
  }
@@ -32,6 +32,8 @@ const MAX_LOG = 500;
32
32
  const state = registerScanState({
33
33
  running: false,
34
34
  action: null,
35
+ target: null, // who a scan is of: { id, name } (either may be null), or null for scans of no one
36
+ recent: [], // the last few finished jobs, newest first: how each one ended
35
37
  log: [],
36
38
  startedAt: null,
37
39
  exitCode: null,
@@ -251,20 +253,66 @@ async function status() {
251
253
  signedIn: m.signedIn,
252
254
  },
253
255
  network,
256
+ ...job(),
257
+ skips: bridgeSkips(),
258
+ linkedin: linkedinState(dataDir()),
259
+ paused: paused(me?.id),
260
+ };
261
+ }
262
+
263
+ /**
264
+ * The running job alone, from memory: no checks, no database. Every Scan
265
+ * button in the app asks this (lib/scraper-client.js watchScanner), every few
266
+ * seconds, so it must cost nothing.
267
+ *
268
+ * `recent` is how the last few jobs ended. A page following one job can then
269
+ * learn how it ended even when another job started before it looked again;
270
+ * with only the latest job's exit code, that one would never seem to finish.
271
+ */
272
+ function job() {
273
+ return {
254
274
  running: state.running,
255
275
  action: state.action,
276
+ target: state.target,
256
277
  startedAt: state.startedAt,
257
278
  exitCode: state.exitCode,
258
279
  failure: state.running ? null : state.failure,
259
280
  progress: state.running ? scanProgress(state.log, state.action) : null,
260
281
  log: state.log.slice(-120),
261
- skips: bridgeSkips(),
262
- linkedin: linkedinState(dataDir()),
263
- paused: paused(me?.id),
282
+ recent: state.recent,
264
283
  };
265
284
  }
266
285
 
267
- export async function GET() {
286
+ /**
287
+ * Where Resume would carry on with one of your connections, for their profile
288
+ * card: { nextPage, pagesRead, legacy }, or null when there is nothing to carry
289
+ * on with (never mapped, read to the end, hidden). The Paused list's own rules
290
+ * (lib/paused.js), so the card and the Scan page can't disagree.
291
+ */
292
+ function resumePoint(id) {
293
+ try {
294
+ const me = resolveProfile({ create: false });
295
+ if (!me) return null;
296
+ const db = getDb();
297
+ const row = db.prepare(
298
+ `SELECT id, name, tier, profile_url, connected_date, created_at
299
+ FROM linkedin_connections WHERE id = ? AND user_id = ? AND degree = 1`,
300
+ ).get(String(id ?? ''), me.id);
301
+ if (!row) return null;
302
+ const mapped = db.prepare(
303
+ 'SELECT 1 FROM linkedin_connections WHERE user_id = ? AND degree = 2 AND source_connection_id = ? LIMIT 1',
304
+ ).get(me.id, row.id);
305
+ const [p] = pausedList([row], new Set(mapped ? [row.id] : []), readProgress(dataDir(), me.id), readUnclear(dataDir()));
306
+ return p ? { nextPage: p.nextPage, pagesRead: p.pagesRead, legacy: p.legacy } : null;
307
+ } catch {
308
+ return null;
309
+ }
310
+ }
311
+
312
+ export async function GET(request) {
313
+ const q = new URL(request?.url || 'http://127.0.0.1/api/scraper').searchParams;
314
+ if (q.has('job')) return Response.json(job());
315
+ if (q.has('resume')) return Response.json({ resume: resumePoint(q.get('resume')) });
268
316
  return Response.json(await status());
269
317
  }
270
318
 
@@ -439,16 +487,23 @@ export async function POST(request) {
439
487
  // Resume sends the connection's id; the URL is looked up here, for this
440
488
  // profile, so nothing from the request itself reaches the command line.
441
489
  let profileUrl = null;
490
+ let person = null;
442
491
  if (spec.needsId) {
443
492
  try {
444
493
  const me = resolveProfile({ create: false });
445
- const row = me && getDb().prepare(
446
- 'SELECT profile_url FROM linkedin_connections WHERE id = ? AND user_id = ? AND degree = 1',
494
+ person = me && getDb().prepare(
495
+ 'SELECT name, profile_url FROM linkedin_connections WHERE id = ? AND user_id = ? AND degree = 1',
447
496
  ).get(String(body.id ?? ''), me.id);
448
- profileUrl = row?.profile_url && cleanProfileUrl(row.profile_url);
497
+ profileUrl = person?.profile_url && cleanProfileUrl(person.profile_url);
449
498
  } catch { profileUrl = null; }
450
499
  if (!profileUrl) return Response.json({ error: 'That connection could not be found.' }, { status: 400 });
451
500
  }
501
+ // Who the job is of, so every Scan button can say whose scan is running and a
502
+ // profile card can show its own person's progress. Only ever shown: the id
503
+ // sent alongside a name never reaches the command line (the name does, as
504
+ // always), and anything not shaped like one of our ids is dropped.
505
+ const hintId = typeof body.id === 'string' && /^[\w-]{1,64}$/.test(body.id) ? body.id : null;
506
+ const target = name || profileUrl ? { id: hintId, name: name || person?.name || null } : null;
452
507
  // Nothing that searches LinkedIn starts during a cooldown; the scanner checks
453
508
  // too, this just says so before a process is spawned.
454
509
  // The 1st-degree scans aren't searches, but they open LinkedIn with automation too.
@@ -517,6 +572,7 @@ export async function POST(request) {
517
572
  state.running = true;
518
573
  state.stopping = false;
519
574
  state.action = action;
575
+ state.target = target;
520
576
  state.exitCode = null;
521
577
  state.startedAt = Date.now();
522
578
  state.log = [spec.label + (name ? ` ${name}…` : '…')];
@@ -560,6 +616,9 @@ export async function POST(request) {
560
616
  state.child = null;
561
617
  state.abort = null;
562
618
  state.exitCode = code;
619
+ state.recent = [{
620
+ action: state.action, target: state.target, startedAt: state.startedAt, exitCode: code, failure: state.failure,
621
+ }, ...state.recent].slice(0, 5);
563
622
  // A setup's private folder (the download and what was unpacked from it).
564
623
  if (cleanup) {
565
624
  try { cleanup(); } catch { /* swept at the next setup (sweepSetupLeftovers) */ }
@@ -625,5 +684,7 @@ export async function POST(request) {
625
684
 
626
685
  runStep(0);
627
686
 
628
- return Response.json({ ok: true, action });
687
+ // Which job this is, so the page that started it can tell its end from the
688
+ // end of whatever runs next (lib/scraper-client.js runScrape).
689
+ return Response.json({ ok: true, action, startedAt: state.startedAt });
629
690
  }
@@ -1,8 +1,8 @@
1
1
  import { getDb } from '../../../../lib/db-client';
2
- import { companyOverrides, readForScoring, scoringRows, sectorFocusOf } from '../../../../lib/rpc';
2
+ import { companyOverrides, readForScoring, scoringRows, sectorFocusOf, tierScaleOf } from '../../../../lib/rpc';
3
3
  import { parseSectorFocus, previewSectorFocus } from '../../../../lib/sector-focus';
4
4
 
5
- // Settings → Your sector, before you save: what a sector focus would change,
5
+ // Scores → Your sector, before you save: what a sector focus would change,
6
6
  // against the one saved now. It reads the network once and scores it twice in
7
7
  // memory, with the inputs rescoreAll() uses (readForScoring: each company's
8
8
  // industry and its sectors from the directory), and writes nothing. A save
@@ -31,6 +31,7 @@ export async function POST(request) {
31
31
  read: readForScoring(rows),
32
32
  from: sectorFocusOf(db),
33
33
  to,
34
+ tierScale: tierScaleOf(db),
34
35
  }));
35
36
  } catch (err) {
36
37
  return Response.json({ error: err.message }, { status: 500 });
@@ -2,7 +2,7 @@ import { getDb } from '../../../../lib/db-client';
2
2
  import { readForScoring, scoringRows } from '../../../../lib/rpc';
3
3
  import { suggestSectors } from '../../../../lib/sector-directory';
4
4
 
5
- // Settings → Your sector: "Suggested from your network". The directory's
5
+ // Scores → Your sector: "Suggested from your network". The directory's
6
6
  // sectors most of your scanned people work in, counted here from the rows
7
7
  // with the same read rescoring uses, so a suggestion's companies are the ones
8
8
  // picking it would lean. Its own read-only GET, not part of GET
@@ -1,17 +1,17 @@
1
1
  'use client';
2
2
 
3
- // Paths → Scores: every company in your network, the score it carries into
3
+ // The Scores tab's company scores (app/scores): every company in your network, the score it carries into
4
4
  // people's power, where that score comes from, and a control to set your own.
5
5
  // Setting a score rescores everyone (app/api/company-scores → lib/scoring.js).
6
6
 
7
7
  import { useCallback, useEffect, useMemo, useState } from 'react';
8
- import Link from 'next/link';
9
8
  import LegacyScoresCard from './LegacyScoresCard';
10
9
 
11
10
  const LINE = '1px solid rgba(255,255,255,0.1)';
12
11
  const SOURCE = {
13
12
  yours: { label: 'your score', color: '#00ff88' },
14
13
  known: { label: 'known list', color: '#3498DB' },
14
+ data: { label: 'public data', color: '#1abc9c' },
15
15
  network: { label: 'estimated: many of your people', color: '#FF6B35' },
16
16
  default: { label: 'unknown company', color: '#778' },
17
17
  };
@@ -59,8 +59,14 @@ export function ScorePicker({ company, onSet, compact }) {
59
59
  );
60
60
  }
61
61
 
62
- export default function CompanyScores({ onRescored }) {
62
+ export default function CompanyScores({ onRescored, version = 0 }) {
63
63
  const { companies, error, setScore, reload } = useCompanyScores();
64
+ // A save beside the list (your sector or tiers, on the Scores tab) rescores
65
+ // everyone: `version` changes, and the list is read again, keeping what you
66
+ // searched and filtered.
67
+ useEffect(() => {
68
+ if (version) reload().catch(() => {});
69
+ }, [version, reload]);
64
70
  const [query, setQuery] = useState('');
65
71
  const [only, setOnly] = useState('all');
66
72
  const [limit, setLimit] = useState(150);
@@ -103,22 +109,22 @@ export default function CompanyScores({ onRescored }) {
103
109
  <LegacyScoresCard onAnswered={legacyAnswered} />
104
110
  <div style={{ padding: 14, borderRadius: 10, border: LINE, background: 'rgba(255,255,255,0.03)', fontSize: 12.5, color: '#cfd8d8', lineHeight: 1.6 }}>
105
111
  <b style={{ color: '#fff' }}>How a power score works.</b> power = <b>title</b> × <b>company weight</b> + bonus.
106
- The title (student 1 … founder or C-suite 10) comes from someone&rsquo;s current role, with former roles at 70%.
107
- The company weight runs from 0.615 (no company found) to 1.0 (a company scored 10), so seniority counts for more at a bigger company.
112
+ The title (student 1 … founder or C-suite 10) comes from someone&rsquo;s roles, with former roles at 70%; an audience of their own counts like a title (1M+ followers like a director).
113
+ The company weight runs from 0.725 for a company we don’t know (or none found), the middle of the scale, to 1.0 (a company scored 10), so seniority counts for more at a bigger company.
108
114
  The bonus is at most +1.5, for investor, YC, an audience in the millions or the top award of any field, and counts half at an unknown company.
109
- A 1st-degree person whose circle is unusually strong gets up to +1 more.
110
- S ≥ 7.5 · A ≥ 5.5 · B ≥ 4 · C ≥ 2.5.
115
+ A 1st-degree person whose circle is unusually strong gets up to +2 more: up to +1 for a high share of it at A or S, or +1 for every 25 people there.
116
+ Tiers are graded on your network’s curve unless you choose the fixed scale (<a href="#tiers" style={{ color: '#3498DB' }}>Tiers</a>, above): your top 3% are S, the next 12% A, the next 25% B (it only lifts, and never lifts anyone under 4 into S or A). On the fixed scale, S ≥ 7.5 · A ≥ 5.5 · B ≥ 4 · C ≥ 2.5.
111
117
  <div style={{ marginTop: 6, color: '#8b9a9a' }}>
112
- So a VP at a 10 scores 9.0, a founder at an unknown company 6.7, a director at an unknown company 5.0, and an intern at a 10 scores 2.0.
118
+ So a VP at a 10 scores 9.0, a founder at an unknown company 7.3, a director at an unknown company 5.4, and an intern at a 10 scores 2.0.
113
119
  Set a company&rsquo;s score below and everyone there is rescored.
114
- Companies in the sectors you pick in <Link href="/settings#sector" style={{ color: '#3498DB' }}>Settings</Link> get +1 or +2; a score you set is never changed.
120
+ Companies in the sectors you pick (<a href="#sector" style={{ color: '#3498DB' }}>Your sector</a>, above) get +1 or +2; a score you set is never changed.
115
121
  </div>
116
122
  </div>
117
123
 
118
124
  <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', margin: '14px 0 10px' }}>
119
125
  <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Find a company"
120
126
  style={{ padding: '6px 10px', borderRadius: 7, border: LINE, background: 'rgba(255,255,255,0.05)', color: '#fff', fontSize: 13, minWidth: 180 }} />
121
- {[['all', `All ${companies.length}`], ['known', `Known list ${count('known')}`], ['yours', `Yours ${count('yours')}`], ['network', `Estimated ${count('network')}`], ['default', `Unknown ${count('default')}`]].map(([k, l]) => (
127
+ {[['all', `All ${companies.length}`], ['known', `Known list ${count('known')}`], ['data', `Public data ${count('data')}`], ['yours', `Yours ${count('yours')}`], ['network', `Estimated ${count('network')}`], ['default', `Unknown ${count('default')}`]].map(([k, l]) => (
122
128
  <button key={k} onClick={() => setOnly(k)} style={{
123
129
  padding: '5px 10px', borderRadius: 7, border: LINE, cursor: 'pointer', fontSize: 11.5, fontWeight: 600,
124
130
  background: only === k ? 'rgba(52,152,219,0.25)' : 'transparent', color: only === k ? '#cfe6f7' : '#8b9a9a',
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- // Paths → Scores: the one-time offer to keep the curated list's old scores as
3
+ // The Scores tab: the one-time offer to keep the curated list's old scores as
4
4
  // your own, after the list was made neutral (lib/legacy-offer.js decides who
5
5
  // sees it and what "keep" writes). It sits at the top of the Scores tab until
6
6
  // it's answered, then never shows again. Not while a CSV import or the sample
@@ -17,6 +17,8 @@
17
17
  */
18
18
 
19
19
  import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
20
+ import useRequests from './useRequests';
21
+ import { hasRequest } from '../../lib/requests-client';
20
22
  import {
21
23
  drag,
22
24
  forceCollide,
@@ -71,6 +73,7 @@ const OrbitGraph = forwardRef(function OrbitGraph(
71
73
  const zoomKRef = useRef(1);
72
74
  const savedTransformRef = useRef(null);
73
75
  const applySelectionRef = useRef(() => {});
76
+ const applyRequestsRef = useRef(() => {});
74
77
  const focusNodeRef = useRef(() => {});
75
78
  const [size, setSize] = useState({ w: 0, h: 0 });
76
79
  const isMobile = useIsMobile();
@@ -122,6 +125,15 @@ const OrbitGraph = forwardRef(function OrbitGraph(
122
125
  applySelectionRef.current(selectedId);
123
126
  }, [selectedId]);
124
127
 
128
+ // So do requests you've sent (lib/requests-client.js): a request sent from
129
+ // any view rings that person's dots here at once.
130
+ const requests = useRequests();
131
+ const requestsRef = useRef(requests);
132
+ useEffect(() => {
133
+ requestsRef.current = requests;
134
+ applyRequestsRef.current(requests);
135
+ }, [requests]);
136
+
125
137
  useEffect(() => {
126
138
  const svgEl = svgRef.current;
127
139
  if (!svgEl || size.w < 40 || size.h < 40) return undefined;
@@ -545,6 +557,23 @@ const OrbitGraph = forwardRef(function OrbitGraph(
545
557
  };
546
558
  applySelectionRef.current(selectedIdRef.current);
547
559
 
560
+ // ---- requests you've sent ---------------------------------------------
561
+ // A dashed gold ring on every 2nd-degree dot you've asked, in step with
562
+ // the shared list, without rebuilding the scene.
563
+ applyRequestsRef.current = (state) => {
564
+ nodesLayer.selectAll('circle.og-asked').remove();
565
+ node.filter((d) => d.kind === 'd2' && hasRequest(d.connection, state))
566
+ .append('circle')
567
+ .attr('class', 'og-asked')
568
+ .attr('r', (d) => d.r + 2.2)
569
+ .attr('fill', 'none')
570
+ .attr('stroke', '#FFD700')
571
+ .attr('stroke-width', 1)
572
+ .attr('stroke-dasharray', '2 1.6')
573
+ .style('pointer-events', 'none');
574
+ };
575
+ applyRequestsRef.current(requestsRef.current);
576
+
548
577
  // ---- fly-to ----------------------------------------------------------
549
578
  const nodeById = new Map(renderNodes.map((n) => [n.id, n]));
550
579
  focusNodeRef.current = (id) => {
@@ -591,6 +620,7 @@ const OrbitGraph = forwardRef(function OrbitGraph(
591
620
  return () => {
592
621
  sim?.stop();
593
622
  applySelectionRef.current = () => {};
623
+ applyRequestsRef.current = () => {};
594
624
  focusNodeRef.current = () => {};
595
625
  svg.on('.zoom', null);
596
626
  svg.on('click', null);
@@ -505,7 +505,7 @@ function Section({ title, children }) {
505
505
  }
506
506
 
507
507
  // The company's score in people's power, where it comes from, and a control
508
- // to set it (Paths → Scores has every company).
508
+ // to set it (the Scores tab has every company).
509
509
  function CompanyScoreLine({ name }) {
510
510
  const { companies, setScore } = useCompanyScores();
511
511
  const c = companies?.find((x) => x.name === name);