six-degrees 0.7.0 → 0.8.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 (349) 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 +32 -4
  5. package/.next/standalone/.next/routes-manifest.json +6 -0
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.rsc +16 -16
  15. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  19. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  23. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  25. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/data/backup/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/data/restore/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 +4 -4
  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/social/crm/route.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/social/route.js +1 -1
  60. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  62. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  65. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  67. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/import.html +1 -1
  72. package/.next/standalone/.next/server/app/import.rsc +18 -18
  73. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +18 -18
  74. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  75. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +16 -16
  76. package/.next/standalone/.next/server/app/index.html +1 -1
  77. package/.next/standalone/.next/server/app/index.rsc +19 -19
  78. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -16
  79. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +19 -19
  80. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  81. package/.next/standalone/.next/server/app/insights/page/app-paths-manifest.json +3 -0
  82. package/.next/standalone/.next/server/app/insights/page/build-manifest.json +20 -0
  83. package/.next/standalone/.next/server/app/insights/page/next-font-manifest.json +11 -0
  84. package/.next/standalone/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
  85. package/.next/standalone/.next/server/app/insights/page/server-reference-manifest.json +4 -0
  86. package/.next/standalone/.next/server/app/insights/page.js +13 -0
  87. package/.next/standalone/.next/server/app/insights/page.js.map +5 -0
  88. package/.next/standalone/.next/server/app/insights/page.js.nft.json +1 -0
  89. package/.next/standalone/.next/server/app/insights/page_client-reference-manifest.js +3 -0
  90. package/.next/standalone/.next/server/app/insights.html +1 -0
  91. package/.next/standalone/.next/server/app/insights.meta +26 -0
  92. package/.next/standalone/.next/server/app/insights.rsc +35 -0
  93. package/.next/standalone/.next/server/app/insights.segments/_full.segment.rsc +35 -0
  94. package/.next/standalone/.next/server/app/insights.segments/_tree.segment.rsc +4 -0
  95. package/.next/standalone/.next/server/app/insights.segments/insights/__PAGE__.segment.rsc +33 -0
  96. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  98. package/.next/standalone/.next/server/app/launch.html +2 -2
  99. package/.next/standalone/.next/server/app/launch.rsc +18 -18
  100. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +18 -18
  101. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  102. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +16 -16
  103. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  104. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  105. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  106. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  107. package/.next/standalone/.next/server/app/paths.html +1 -1
  108. package/.next/standalone/.next/server/app/paths.rsc +19 -19
  109. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +19 -19
  110. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  111. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +16 -16
  112. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  114. package/.next/standalone/.next/server/app/profile.html +1 -1
  115. package/.next/standalone/.next/server/app/profile.rsc +19 -19
  116. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +19 -19
  117. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  118. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +16 -16
  119. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  121. package/.next/standalone/.next/server/app/queue.html +1 -1
  122. package/.next/standalone/.next/server/app/queue.rsc +19 -19
  123. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +19 -19
  124. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  125. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +21 -20
  126. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/app/scores.html +1 -1
  129. package/.next/standalone/.next/server/app/scores.rsc +18 -18
  130. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +18 -18
  131. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  132. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +16 -16
  133. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/app/settings.html +1 -1
  136. package/.next/standalone/.next/server/app/settings.rsc +19 -19
  137. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +19 -19
  138. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  139. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -16
  140. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  141. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  142. package/.next/standalone/.next/server/app/setup.html +1 -1
  143. package/.next/standalone/.next/server/app/setup.rsc +19 -19
  144. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +19 -19
  145. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  146. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +16 -16
  147. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  149. package/.next/standalone/.next/server/app/social.html +1 -1
  150. package/.next/standalone/.next/server/app/social.rsc +18 -18
  151. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +18 -18
  152. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
  153. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +16 -16
  154. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  155. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__063s0x7._.js → [root-of-the-server]__0r-0t-9._.js} +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ra6xyg._.js +15 -0
  157. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20rwfdw._.js → [root-of-the-server]__0rcpjad._.js} +5 -2
  158. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  159. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +2 -2
  161. package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +2 -2
  162. package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +2 -2
  163. package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +9 -9
  164. package/.next/standalone/.next/server/chunks/{lib_rpc_0jnfp2e.js → lib_rpc_00nndai.js} +1 -1
  165. package/.next/standalone/.next/server/chunks/{lib_rpc_0b17r6v.js → lib_rpc_03ibb-x.js} +1 -1
  166. package/.next/standalone/.next/server/chunks/{lib_rpc_0doq0t-.js → lib_rpc_03ri3ji.js} +1 -1
  167. package/.next/standalone/.next/server/chunks/{lib_rpc_0fd2rlw.js → lib_rpc_078ce48.js} +1 -1
  168. package/.next/standalone/.next/server/chunks/lib_rpc_0a69mgr.js +3 -0
  169. package/.next/standalone/.next/server/chunks/{lib_rpc_005abye.js → lib_rpc_0cej3t8.js} +1 -1
  170. package/.next/standalone/.next/server/chunks/lib_rpc_0cmh4g6.js +3 -0
  171. package/.next/standalone/.next/server/chunks/lib_rpc_0nx2_pc.js +3 -0
  172. package/.next/standalone/.next/server/chunks/lib_rpc_0r79-w1.js +3 -0
  173. package/.next/standalone/.next/server/chunks/{lib_rpc_0is4qoc.js → lib_rpc_0t552z6.js} +1 -1
  174. package/.next/standalone/.next/server/chunks/lib_rpc_0yx8ynd.js +3 -0
  175. package/.next/standalone/.next/server/chunks/lib_rpc_12p-o9-.js +3 -0
  176. package/.next/standalone/.next/server/chunks/lib_rpc_1466dnb.js +3 -0
  177. package/.next/standalone/.next/server/chunks/lib_rpc_1sryxv6.js +3 -0
  178. package/.next/standalone/.next/server/chunks/lib_rpc_1trn-x5.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_1ygu_mv.js +3 -0
  180. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egamyq._.js +3 -0
  181. package/.next/standalone/.next/server/chunks/ssr/_04am_h8._.js +3 -0
  182. package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +1 -1
  184. package/.next/standalone/.next/server/chunks/ssr/_0gds903._.js +3 -0
  185. package/.next/standalone/.next/server/chunks/ssr/_0klqt9z._.js +3 -0
  186. package/.next/standalone/.next/server/chunks/ssr/{_1k9qfsk._.js → _14jak5s._.js} +6 -2
  187. package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +1 -1
  188. package/.next/standalone/.next/server/chunks/ssr/_1cj2yog._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +1 -1
  190. package/.next/standalone/.next/server/chunks/ssr/_1hdqw0-._.js +7 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +1 -1
  192. package/.next/standalone/.next/server/chunks/ssr/{_0gy11ar._.js → _1r62six._.js} +6 -2
  193. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_insights_page_actions_0-0tf75.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +2 -2
  195. package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +2 -2
  196. package/.next/standalone/.next/server/chunks/ssr/{_01o3i5y._.js → app_components_settings_SectorPicker_1c-29qw.js} +2 -6
  197. package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
  198. package/.next/standalone/.next/server/chunks/ssr/app_insights_page_1d_chja.js +3 -0
  199. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -14
  200. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +3 -3
  201. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  202. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +3 -3
  203. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  204. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  205. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  206. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  207. package/.next/standalone/.next/server/pages/404.html +1 -1
  208. package/.next/standalone/.next/server/pages/500.html +1 -1
  209. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  210. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  211. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  212. package/.next/standalone/.next/static/chunks/02wnf8dkxegxg.js +34 -0
  213. package/.next/standalone/.next/static/chunks/0838_s6w-i_ik.js +1 -0
  214. package/.next/{static/chunks/2eo9pqq9gay3r.js → standalone/.next/static/chunks/0ka4prpk3w9wn.js} +4 -4
  215. package/.next/standalone/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
  216. package/.next/standalone/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
  217. package/.next/standalone/.next/static/chunks/112mv-com-mqa.js +21 -0
  218. package/.next/{static/chunks/44yn0tcd9dfk3.js → standalone/.next/static/chunks/1bkt_hhjzvbh8.js} +1 -1
  219. package/.next/standalone/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  220. package/.next/{static/chunks/1qbjermbf68xj.js → standalone/.next/static/chunks/1d8yv3k_atka7.js} +1 -1
  221. package/.next/standalone/.next/static/chunks/23hc51oa4vkhc.js +5 -0
  222. package/.next/standalone/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
  223. package/.next/standalone/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
  224. package/.next/{static/chunks/2zf4jmz9l3li2.js → standalone/.next/static/chunks/2w-y83mjlpnse.js} +1 -1
  225. package/.next/standalone/.next/static/chunks/2ws1xf293j-tm.js +5 -0
  226. package/.next/standalone/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
  227. package/.next/standalone/.next/static/chunks/3opgg1efrx8q6.js +1 -0
  228. package/.next/standalone/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
  229. package/.next/{static/chunks/0an_b6zi1r_qq.js → standalone/.next/static/chunks/3y40z6of3r9py.js} +1 -5
  230. package/.next/standalone/.next/static/chunks/403dpflln8ah5.js +1 -0
  231. package/.next/standalone/CHANGELOG.md +60 -0
  232. package/.next/standalone/README.md +25 -1
  233. package/.next/standalone/SECURITY.md +8 -0
  234. package/.next/standalone/app/api/outreach/route.js +13 -73
  235. package/.next/standalone/app/api/scraper/route.js +97 -4
  236. package/.next/standalone/app/components/AppHeader.js +22 -6
  237. package/.next/standalone/app/components/AppTabs.js +4 -0
  238. package/.next/standalone/app/components/AutoConnect.js +286 -0
  239. package/.next/standalone/app/components/ChainView.js +75 -2
  240. package/.next/standalone/app/components/FilterPanel.js +8 -1
  241. package/.next/standalone/app/components/LinkedInLimits.js +38 -13
  242. package/.next/standalone/app/components/OutlinkQuest.js +32 -11
  243. package/.next/standalone/app/components/ScanStatusBar.js +18 -3
  244. package/.next/standalone/app/components/ScanTrail.js +13 -3
  245. package/.next/standalone/app/components/SeparationView.js +6 -22
  246. package/.next/standalone/app/components/Sidebar.js +147 -38
  247. package/.next/standalone/app/components/insights/Boards.js +275 -0
  248. package/.next/standalone/app/components/insights/PeopleView.js +266 -0
  249. package/.next/standalone/app/components/insights/Rail.js +156 -0
  250. package/.next/standalone/app/components/insights/Report.js +382 -0
  251. package/.next/standalone/app/components/insights/parts.js +239 -0
  252. package/.next/standalone/app/components/settings/UsageSection.js +50 -3
  253. package/.next/standalone/app/globals.css +2 -3
  254. package/.next/standalone/app/insights/page.js +220 -0
  255. package/.next/standalone/app/page.js +1 -17
  256. package/.next/standalone/app/queue/page.js +30 -18
  257. package/.next/standalone/app/setup/page.js +16 -9
  258. package/.next/standalone/lib/auto-connect.js +145 -0
  259. package/.next/standalone/lib/companies.js +1 -1
  260. package/.next/standalone/lib/in-progress.js +46 -0
  261. package/.next/standalone/lib/insights-board.js +613 -0
  262. package/.next/standalone/lib/linkedin-limits.js +36 -11
  263. package/.next/standalone/lib/requests-client.js +10 -0
  264. package/.next/standalone/lib/requests.js +124 -0
  265. package/.next/standalone/lib/scan-state.js +1 -0
  266. package/.next/standalone/lib/scraper-client.js +18 -5
  267. package/.next/standalone/lib/settings.js +3 -0
  268. package/.next/standalone/lib/usage.js +5 -0
  269. package/.next/standalone/package-lock.json +2 -2
  270. package/.next/standalone/package.json +1 -1
  271. package/.next/standalone/scripts/scrape.py +569 -9
  272. package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
  273. package/.next/static/chunks/0838_s6w-i_ik.js +1 -0
  274. package/.next/{standalone/.next/static/chunks/2eo9pqq9gay3r.js → static/chunks/0ka4prpk3w9wn.js} +4 -4
  275. package/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
  276. package/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
  277. package/.next/static/chunks/112mv-com-mqa.js +21 -0
  278. package/.next/{standalone/.next/static/chunks/44yn0tcd9dfk3.js → static/chunks/1bkt_hhjzvbh8.js} +1 -1
  279. package/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  280. package/.next/{standalone/.next/static/chunks/1qbjermbf68xj.js → static/chunks/1d8yv3k_atka7.js} +1 -1
  281. package/.next/static/chunks/23hc51oa4vkhc.js +5 -0
  282. package/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
  283. package/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
  284. package/.next/{standalone/.next/static/chunks/2zf4jmz9l3li2.js → static/chunks/2w-y83mjlpnse.js} +1 -1
  285. package/.next/static/chunks/2ws1xf293j-tm.js +5 -0
  286. package/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
  287. package/.next/static/chunks/3opgg1efrx8q6.js +1 -0
  288. package/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
  289. package/.next/{standalone/.next/static/chunks/0an_b6zi1r_qq.js → static/chunks/3y40z6of3r9py.js} +1 -5
  290. package/.next/static/chunks/403dpflln8ah5.js +1 -0
  291. package/CHANGELOG.md +60 -0
  292. package/README.md +25 -1
  293. package/SECURITY.md +8 -0
  294. package/lib/auto-connect.js +145 -0
  295. package/lib/companies.js +1 -1
  296. package/lib/in-progress.js +46 -0
  297. package/lib/insights-board.js +613 -0
  298. package/lib/linkedin-limits.js +36 -11
  299. package/lib/requests-client.js +10 -0
  300. package/lib/requests.js +124 -0
  301. package/lib/scan-state.js +1 -0
  302. package/lib/scraper-client.js +18 -5
  303. package/lib/settings.js +3 -0
  304. package/lib/usage.js +5 -0
  305. package/package.json +1 -1
  306. package/scripts/scrape.py +569 -9
  307. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_4y8z7._.js +0 -8
  308. package/.next/standalone/.next/server/chunks/_02twlca._.js +0 -10
  309. package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +0 -3
  310. package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +0 -3
  311. package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +0 -3
  312. package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +0 -3
  313. package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +0 -3
  314. package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +0 -3
  315. package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +0 -3
  316. package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +0 -3
  317. package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +0 -3
  318. package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +0 -7
  319. package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +0 -3
  320. package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +0 -3
  321. package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +0 -3
  322. package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +0 -21
  323. package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  324. package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +0 -34
  325. package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  326. package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +0 -57
  327. package/.next/standalone/.next/static/chunks/260bomtr214m8.js +0 -34
  328. package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  329. package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  330. package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +0 -1
  331. package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +0 -3
  332. package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  333. package/.next/static/chunks/0e3g0sil79cus.js +0 -21
  334. package/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  335. package/.next/static/chunks/163b-20vwa06i.js +0 -34
  336. package/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  337. package/.next/static/chunks/23_1z47al92nz.js +0 -57
  338. package/.next/static/chunks/260bomtr214m8.js +0 -34
  339. package/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  340. package/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  341. package/.next/static/chunks/3jarus1_38s2z.js +0 -1
  342. package/.next/static/chunks/3ssu341hav_sq.css +0 -3
  343. package/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  344. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
  345. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
  346. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
  347. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
  348. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
  349. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
@@ -13,7 +13,7 @@
13
13
  // (lib/island.js). It only reports: the pacing and the caps live in the scanner
14
14
  // (scripts/scrape.py, lib/linkedin-limits.js).
15
15
 
16
- import { useEffect, useState, useSyncExternalStore } from 'react';
16
+ import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
17
17
  import { usePathname } from 'next/navigation';
18
18
  import Link from 'next/link';
19
19
  import { watchScanner, scannerNow, stopScrape } from '../../lib/scraper-client';
@@ -35,6 +35,7 @@ const WHAT = {
35
35
  photos: 'Saving photos',
36
36
  messages: 'Reading your messages list',
37
37
  'messages-full': 'Reading your whole messages history',
38
+ connect: 'Sending a connection request',
38
39
  };
39
40
 
40
41
  // Green while there's plenty left, amber past 60%, red past 90%.
@@ -59,6 +60,7 @@ export default function ScanStatusBar() {
59
60
  // be swapped for a new one, or wrap to two rows.
60
61
  const pathname = usePathname();
61
62
  const [top, setTop] = useState(null);
63
+ const barRef = useRef(null);
62
64
  const [centre, setCentre] = useState(null);
63
65
  useEffect(() => {
64
66
  let header = null;
@@ -82,7 +84,13 @@ export default function ScanStatusBar() {
82
84
  };
83
85
  const later = () => { if (!queued) queued = requestAnimationFrame(measure); };
84
86
  later();
85
- const mo = new MutationObserver(later);
87
+ // Measuring waits for the next frame, which drew the notch once over the
88
+ // blank page: hidden here, before that frame is painted, the moment the
89
+ // header goes (and shown again the moment it's back).
90
+ const mo = new MutationObserver(() => {
91
+ if (barRef.current) barRef.current.style.visibility = document.querySelector('header') ? '' : 'hidden';
92
+ later();
93
+ });
86
94
  mo.observe(document.body, { childList: true, subtree: true });
87
95
  window.addEventListener('resize', later);
88
96
  return () => { cancelAnimationFrame(queued); ro?.disconnect(); mo.disconnect(); window.removeEventListener('resize', later); };
@@ -98,6 +106,11 @@ export default function ScanStatusBar() {
98
106
  return () => setNotchShown(false);
99
107
  }, [showing]);
100
108
  if (!status && !tabs) return null;
109
+ // Only with a page under it: between tabs the next page has no header until
110
+ // it has loaded, and the notch would hang over a blank screen (Blake,
111
+ // 2026-10-03: "the notch is seen the whole time as the screen blanks out
112
+ // loading to the next … have it appear with the section"). It fades in with it.
113
+ if (top == null) return null;
101
114
 
102
115
  const p = job?.progress;
103
116
  const step = p?.kind === 'batch' && p.total ? `${p.current || p.done || 0} of ${p.total}`
@@ -118,10 +131,12 @@ export default function ScanStatusBar() {
118
131
 
119
132
  return (
120
133
  <div
134
+ ref={barRef}
121
135
  data-glass-panel="bar"
136
+ className="notch-in"
122
137
  onMouseLeave={() => setOpen(false)}
123
138
  style={{
124
- position: 'fixed', left, top: top ?? 'var(--scan-bar-top, 94px)', transform: 'translateX(-50%)', zIndex: 60,
139
+ position: 'fixed', left, top, transform: 'translateX(-50%)', zIndex: 60,
125
140
  maxWidth: 'calc(100vw - 16px)',
126
141
  padding: expanded && status ? '4px 6px 10px' : '4px 6px 5px',
127
142
  borderRadius: '0 0 14px 14px', border: `1px solid ${running ? 'rgba(0,255,136,0.25)' : 'rgba(var(--sd-ink, 255, 255, 255), 0.1)'}`, borderTop: 'none',
@@ -57,9 +57,17 @@ export default function ScanTrail({ inset }) {
57
57
  useEffect(() => {
58
58
  const el = boxRef.current;
59
59
  if (!el) return undefined;
60
- const ro = new ResizeObserver(() => setRoom(Math.max(8, Math.floor(el.clientWidth / STEP) - 4)));
60
+ // On a computer the tabs sit on this same line (AppHeader): the dots stop short of them.
61
+ const fit = () => {
62
+ const box = el.getBoundingClientRect();
63
+ const tabs = document.getElementById('main-tabs')?.getBoundingClientRect();
64
+ const width = tabs && tabs.left > box.left ? Math.min(box.width, tabs.left - box.left - 12) : box.width;
65
+ setRoom(Math.max(8, Math.floor(width / STEP) - 4));
66
+ };
67
+ const ro = new ResizeObserver(fit);
61
68
  ro.observe(el);
62
- return () => ro.disconnect();
69
+ window.addEventListener('resize', fit);
70
+ return () => { ro.disconnect(); window.removeEventListener('resize', fit); };
63
71
  }, []);
64
72
 
65
73
  const { pages } = shown;
@@ -69,7 +77,9 @@ export default function ScanTrail({ inset }) {
69
77
  for (let i = first; i < pages; i++) dots.push(i);
70
78
  const title = `${shown.label}: ${pages === 0 ? 'starting' : `${pages.toLocaleString('en-US')} page${pages === 1 ? '' : 's'} read`}. Open the Scan page`;
71
79
  return (
72
- <div ref={boxRef} aria-hidden={!shown.on} style={{ position: 'absolute', left: inset, right: inset, bottom: -4, height: 7, pointerEvents: 'none' }}>
80
+ // Above the page under the header: the dots straddle its line, and a page
81
+ // drawn after the header (Paths' panel) covered their lower half.
82
+ <div ref={boxRef} aria-hidden={!shown.on} style={{ position: 'absolute', left: inset, right: inset, bottom: -4, height: 7, pointerEvents: 'none', zIndex: 5 }}>
73
83
  <style>{CSS}</style>
74
84
  <Link href="/setup" title={title} tabIndex={shown.on ? 0 : -1} className="scan-trail" style={{
75
85
  position: 'absolute', left: 0, top: 0, height: 7, display: 'flex', alignItems: 'center', gap: STEP - 5,
@@ -380,28 +380,13 @@ export default function SeparationView({ connections = [], degree2 = [], fullDeg
380
380
  flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column',
381
381
  background: 'var(--sd-page)', color: 'var(--sd-fg-1, #fff)', overflow: 'hidden',
382
382
  }}>
383
- {/* ── Header: what this is, how many, and what it can't say ── */}
383
+ {/* ── Top: the search box, and on a phone what it ranks ── */}
384
384
  <div style={{ padding: `${notch ? 48 : 14}px ${SIDE}px 8px`, flexShrink: 0 }}>
385
- <div style={{ display: 'flex', alignItems: isMobile ? 'baseline' : 'center', gap: 10, flexWrap: isMobile ? 'wrap' : 'nowrap' }}>
386
- <h2 title={RANK_NOTE} style={{
387
- margin: 0, fontSize: 18, fontWeight: 800,
388
- background: 'linear-gradient(135deg, #FFD700, #FF6B35)',
389
- WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
390
- }}>Separation</h2>
391
- <span style={{ fontSize: 12, color: 'var(--sd-fg-3, #aaa)', ...(isMobile ? {} : { flex: 1, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }) }}>
392
- {isMobile
393
- ? `${plural(summary.people, 'person', 'people')} · ${fmt(summary.byTier.S)} S · ${fmt(summary.multi)} with 2+ ways`
394
- : `${plural(summary.people, 'person', 'people')} two steps away · ${fmt(summary.byTier.S)} S-tier · ${fmt(summary.multi)} reachable 2+ ways · through ${fmt(summary.bridges)} of your connections`}
395
- </span>
396
- {!isMobile && searchBox}
397
- </div>
398
- {/* The whole population at a glance: one segment per tier, to scale. */}
399
- <div aria-hidden="true" style={{ display: 'flex', gap: 1, height: 6, borderRadius: 3, overflow: 'hidden', margin: '8px 0 6px', background: 'rgba(var(--sd-ink, 255, 255, 255), 0.04)' }}>
400
- {TIERS.filter((t) => summary.byTier[t] > 0).map((t) => (
401
- <div key={t} style={{ flex: summary.byTier[t], background: tierColors[t] || '#555' }} />
402
- ))}
403
- </div>
404
- {/* On a computer the line says it on its own; what it ranks is the title's tooltip (RANK_NOTE). */}
385
+ {/* Blake, 2026-10-03: no title, count line or tier strip up here ("remove this
386
+ line of text … and the actual line of visual of the tiers"); the map and the
387
+ slider say it. The search box keeps the row. */}
388
+ {!isMobile && <div style={{ display: 'flex', justifyContent: 'flex-end' }}>{searchBox}</div>}
389
+ {/* What it ranks, on a phone, where there's room under the search box. */}
405
390
  {isMobile && (
406
391
  <div style={{ fontSize: 11, color: 'var(--sd-fg-4, #777)', lineHeight: 1.4 }}>Ranks reachability, not people · lists scanned so far</div>
407
392
  )}
@@ -710,7 +695,6 @@ const MAP_CSS = `
710
695
  .sepmap .in { animation: none; }
711
696
  }
712
697
  `;
713
- const RANK_NOTE = 'Ranked by each person’s own score (title, company, headline). It ranks reachability, not people. From the lists scanned so far.';
714
698
  const HALO = { paintOrder: 'stroke', stroke: 'var(--sd-bg)', strokeWidth: 3, strokeLinejoin: 'round' };
715
699
  const at = (x, y) => ({ transform: `translate(${x}px, ${y}px)` });
716
700
 
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useMemo, useCallback } from 'react';
3
+ import { useState, useEffect, useMemo, useCallback, useSyncExternalStore } from 'react';
4
4
  import Link from 'next/link';
5
- import { beginScrape, scraperStatus, notReadyMessage, resumePoint, scansCircleOf, isCircleScan, busyReason } from '../../lib/scraper-client';
5
+ import { beginScrape, scraperStatus, notReadyMessage, resumePoint, scansCircleOf, isCircleScan, busyReason, watchScanner, scannerNow } from '../../lib/scraper-client';
6
+ import { scanBoxTitle, stoppedLine } from '../../lib/in-progress';
6
7
  import useScanner from './useScanner';
7
8
  import useRequests from './useRequests';
8
9
  import TheirCircle from './TheirCircle';
@@ -17,6 +18,7 @@ import Avatar from './Avatar';
17
18
  import NoteDetail from './NoteDetail';
18
19
  import CirclePanel from './CirclePanel';
19
20
  import { localPhoto } from '../../lib/photos';
21
+ import ConnectChoice, { secondaryLook } from './AutoConnect';
20
22
 
21
23
  export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending, scanNotes, csvSource = null, onOpenCircle, onShowInSeparation, note = null, onCloseNote, circle = null }) {
22
24
  const isDegreesMode = mode === 'degrees';
@@ -54,6 +56,12 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
54
56
  // Separation, the circle and the Outlink queue at once (lib/requests-client.js).
55
57
  const requests = useRequests();
56
58
  const isRequested = useCallback((row) => hasRequest(row, requests), [requests]);
59
+ // Where Resume would carry on with the person open, asked once for the whole
60
+ // card: the line by their name and the scan box below both show it. Only for
61
+ // whoever gets a scan box (the same test as below). Above the early returns,
62
+ // so the hook order never changes.
63
+ const scansHere = Boolean(selected) && canScan && (selected.degree === 1 || selected.outreach_status === 'accepted');
64
+ const { resume, scanning: scanningThem } = useResume(scansHere ? selected : null);
57
65
 
58
66
  // A notification someone clicked takes the panel over until they go back (app/components/NoteDetail.js).
59
67
  if (note) {
@@ -151,6 +159,9 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
151
159
  </div>
152
160
  </div>
153
161
 
162
+ {/* Their scan stopped partway: said by their name, where it's seen without scrolling. */}
163
+ {scansHere && resume && <StoppedPartway resume={resume} scanning={scanningThem} />}
164
+
154
165
  {/* Their circle, out to six degrees: who they know, who you added from
155
166
  it, and who those people know (TheirCircle.js). Your connections only:
156
167
  a 2nd-degree person's circle isn't yours to scan until they accept. */}
@@ -224,9 +235,15 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
224
235
  <div style={row}>
225
236
  <span style={label}>Scanned</span>
226
237
  <span style={{ flex: 1 }}>{scanned}</span>
227
- {i.bars !== 5 && canScan && (
238
+ {/* Stopped partway: carry on from that page, the line by their name's own
239
+ Resume. The Scan page's Scan one circle reads the list again from page 1,
240
+ which spends the searches already spent; that stays for anyone else. */}
241
+ {i.bars !== 5 && canScan && (scansHere && resume ? (
242
+ <button type="button" style={act} title={`Resume from page ${resume.nextPage}`}
243
+ onClick={() => resumeInScanBox(scanningThem)}>Resume →</button>
244
+ ) : (resume === null || i.bars == null || !scansHere) && (
228
245
  <a href={`/setup?scan=${encodeURIComponent(selected.id)}`} style={act}>{i.bars == null ? 'Scan →' : 'Finish →'}</a>
229
- )}
246
+ ))}
230
247
  </div>
231
248
  {(() => {
232
249
  const since = selected.connected_date ? new Date(selected.connected_date) : null;
@@ -468,21 +485,27 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
468
485
  Didn&apos;t send it? Undo
469
486
  </button>
470
487
  ) : (
471
- <a href={selected.profile_url} target="_blank" rel="noopener noreferrer"
472
- style={{
473
- display: 'block', padding: '10px 16px',
474
- background: 'linear-gradient(135deg, #FFD700, #FF6B35)',
475
- color: '#000', borderRadius: 8, textDecoration: 'none',
476
- fontWeight: 700, fontSize: 13,
477
- }}
478
- onClick={() => {
479
- // Opens their profile to connect, and marks the request
480
- // sent, through the bridge whose circle you found them in.
481
- markRequested(selected, { bridgeId: selected.source_connection_id }).catch(() => {});
482
- }}
483
- >
484
- Connect to Unlock Path
485
- </a>
488
+ // Auto first, the better pick; Connect beside it as an outline
489
+ // (AutoConnect.js; Blake, 2026-10-03). Your own network only.
490
+ <ConnectChoice person={selected} canAuto={canScan} size="card" connect={(second) => (
491
+ <a href={selected.profile_url} target="_blank" rel="noopener noreferrer"
492
+ title={second ? 'Opens their LinkedIn profile, to connect yourself' : undefined}
493
+ style={second ? { ...secondaryLook('card'), flex: 1 } : {
494
+ display: 'block', padding: '10px 16px',
495
+ background: 'linear-gradient(135deg, #FFD700, #FF6B35)',
496
+ color: '#000', borderRadius: 8, textDecoration: 'none',
497
+ fontWeight: 700, fontSize: 13,
498
+ }}
499
+ onClick={() => {
500
+ // Opens their profile to connect, and marks the request
501
+ // sent, through the bridge whose circle you found them in.
502
+ markRequested(selected, { bridgeId: selected.source_connection_id }).catch(() => {});
503
+ }}
504
+ >
505
+ {/* Beside Auto there's room for one word (a 320 px panel). */}
506
+ {second ? 'Connect' : 'Connect to Unlock Path'}
507
+ </a>
508
+ )} />
486
509
  )}
487
510
  </div>
488
511
  );
@@ -491,7 +514,7 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
491
514
  {/* Create Cluster — for D1 connections OR accepted D2 (promoted, ready to bridge for D3) */}
492
515
  {(selected.degree === 1 || selected.outreach_status === 'accepted') && (
493
516
  canScan
494
- ? <CreateClusterCard key={selected.id} selected={selected} degree2={degree2} />
517
+ ? <CreateClusterCard key={selected.id} selected={selected} degree2={degree2} resume={resume} />
495
518
  : <ScansNeedYourNetwork csvSource={csvSource} style={{ marginTop: 16 }} />
496
519
  )}
497
520
 
@@ -1075,6 +1098,87 @@ function PathBox({ routes, selected, tierColors, onSelect }) {
1075
1098
  );
1076
1099
  }
1077
1100
 
1101
+ const noWatch = () => () => {};
1102
+
1103
+ /**
1104
+ * Where Resume would carry on with this person ({ nextPage, pagesRead, legacy };
1105
+ * null when there is nothing to carry on with; undefined until known), and
1106
+ * whether their circle is being scanned right now. One question to the server
1107
+ * for the whole card (GET /api/scraper?resume=<id>), asked again whenever a
1108
+ * scan ends, since every read moves it. Only those two facts are taken from the
1109
+ * scanner, never its whole answer: that changes every time a running scan's log
1110
+ * grows, and this panel would redraw with it.
1111
+ */
1112
+ function useResume(person) {
1113
+ const id = person?.id ?? null;
1114
+ const subscribe = id ? watchScanner : noWatch;
1115
+ const lastEnded = useSyncExternalStore(subscribe, () => scannerNow().finished[0]?.startedAt ?? null, () => null);
1116
+ const scanning = useSyncExternalStore(subscribe, () => {
1117
+ const now = scannerNow();
1118
+ return Boolean(now.running && person && scansCircleOf(now, person));
1119
+ }, () => false);
1120
+ const [got, setGot] = useState({ id: null, resume: undefined });
1121
+ useEffect(() => {
1122
+ if (!id) return undefined;
1123
+ let live = true;
1124
+ resumePoint(id).then((r) => { if (live) setGot({ id, resume: r }); }, () => { if (live) setGot({ id, resume: null }); });
1125
+ return () => { live = false; };
1126
+ }, [id, lastEnded]);
1127
+ // Another person's answer never shows on this one's card while theirs is asked.
1128
+ return { resume: id && got.id === id ? got.resume : undefined, scanning: Boolean(id) && scanning };
1129
+ }
1130
+
1131
+ /**
1132
+ * Resume, from anywhere on the card: bring the scan box into view and press its
1133
+ * own Resume, so the same checks run and the scan's log or any problem shows
1134
+ * where it's seen. While their scan runs (or Resume is greyed out) it only
1135
+ * brings the box into view, which says why.
1136
+ */
1137
+ function resumeInScanBox(scanning = false) {
1138
+ const box = document.getElementById('scan-box');
1139
+ if (!box) return;
1140
+ box.scrollIntoView({ behavior: 'smooth', block: 'center' });
1141
+ box.animate?.([{ boxShadow: '0 0 0 3px rgba(255,215,0,0.75)' }, { boxShadow: '0 0 0 0 rgba(255,215,0,0)' }], { duration: 1600 });
1142
+ if (!scanning) box.querySelector('button[data-resume]:not([disabled])')?.click();
1143
+ }
1144
+
1145
+ /**
1146
+ * The line by their name when the read of their list stopped partway (Blake,
1147
+ * 2026-10-03: "if someone was scanned but not finished it needs to show that in
1148
+ * their profile card"). The scan box is far down the card, so this says it where
1149
+ * it's seen, and Resume here presses the box's own Resume: the same checks, and
1150
+ * the box, brought into view, is where the scan's log and any problem show.
1151
+ * While their scan runs it says so, and only takes you to the box.
1152
+ */
1153
+ function StoppedPartway({ resume, scanning }) {
1154
+ const gold = 'var(--sd-gold, #FFD700)';
1155
+ return (
1156
+ <button type="button" onClick={() => resumeInScanBox(scanning)}
1157
+ aria-label={scanning ? 'Scanning their circle now: go to the scan' : `${stoppedLine(resume)}. Resume from page ${resume.nextPage}`}
1158
+ style={{
1159
+ display: 'flex', alignItems: 'center', gap: 9, width: '100%', boxSizing: 'border-box', margin: '-6px 0 16px',
1160
+ padding: '8px 11px', borderRadius: 8, cursor: 'pointer', textAlign: 'left', font: 'inherit',
1161
+ background: 'rgba(255,215,0,0.07)', border: '1px solid rgba(255,215,0,0.32)', color: 'var(--sd-fg-2, #ddd)',
1162
+ }}>
1163
+ {/* A pause sign while stopped; a pulsing dot while it runs. */}
1164
+ {scanning ? (
1165
+ // ScanLog's keyframes: the box below shows its log while their scan runs.
1166
+ <span aria-hidden="true" className="scanlog-dot" style={{ width: 8, height: 8, borderRadius: '50%', background: gold, flexShrink: 0, animation: 'scanlogPulse 1s ease-in-out infinite' }} />
1167
+ ) : (
1168
+ <span aria-hidden="true" style={{ display: 'flex', gap: 2, flexShrink: 0 }}>
1169
+ <span style={{ width: 3, height: 10, borderRadius: 1, background: gold }} />
1170
+ <span style={{ width: 3, height: 10, borderRadius: 1, background: gold }} />
1171
+ </span>
1172
+ )}
1173
+ <span style={{ flex: 1, minWidth: 0, fontSize: 11.5, lineHeight: 1.35 }}>
1174
+ <b style={{ color: gold }}>{scanning ? 'Scanning their circle now' : `${stoppedLine(resume)}.`}</b>
1175
+ {!scanning && <span style={{ color: 'var(--sd-fg-3, #999)' }}> Their list isn&apos;t finished.</span>}
1176
+ </span>
1177
+ <span style={{ fontSize: 11.5, fontWeight: 800, color: gold, whiteSpace: 'nowrap' }}>{scanning ? 'See it ↓' : 'Resume ↓'}</span>
1178
+ </button>
1179
+ );
1180
+ }
1181
+
1078
1182
  // Scan one person's circle, or carry on with it.
1079
1183
  //
1080
1184
  // LinkedIn lists someone else's connections in its own order, with no dates,
@@ -1085,11 +1189,18 @@ function PathBox({ routes, selected, tierColors, onSelect }) {
1085
1189
  // Whether a scan is running comes from the scanner (useScanner), not from this
1086
1190
  // card: close the card mid-scan and open it again and the scan is still here,
1087
1191
  // and every other card greys its buttons out until it ends.
1088
- function CreateClusterCard({ selected, degree2 }) {
1192
+ //
1193
+ // `resume` is where Resume carries on (useResume, asked once for the card),
1194
+ // null when there is nothing to resume, undefined until it's known. A read that
1195
+ // stopped partway is the first thing the box says: SCAN IN PROGRESS, in gold,
1196
+ // with Resume as the main button (Blake, 2026-10-03: "if someone was scanned but
1197
+ // not finished it needs to show that in their profile card ... showing resume
1198
+ // scanner in that actual card"). It used to sit under CLUSTER ACTIVE, which
1199
+ // reads as done.
1200
+ function CreateClusterCard({ selected, degree2, resume }) {
1089
1201
  const scan = useScanner();
1090
1202
  const [checking, setChecking] = useState(false);
1091
1203
  const [problem, setProblem] = useState(null); // why a scan didn't start: { text, offline }
1092
- const [resume, setResume] = useState(undefined); // where Resume carries on; null: nothing to resume
1093
1204
  const clusterCount = degree2?.filter(d => d.source_connection_id === selected.id).length || 0;
1094
1205
  const hasCluster = clusterCount > 0;
1095
1206
 
@@ -1097,14 +1208,6 @@ function CreateClusterCard({ selected, degree2 }) {
1097
1208
  const busy = scan.running && !mine ? busyReason(scan) : null;
1098
1209
  const last = scan.finished.find((j) => scansCircleOf(j, selected));
1099
1210
 
1100
- // Every read moves where Resume would carry on, so ask again whenever one ends.
1101
- const lastEnded = scan.finished[0]?.startedAt ?? null;
1102
- useEffect(() => {
1103
- let live = true;
1104
- resumePoint(selected.id).then((r) => { if (live) setResume(r); }, () => { if (live) setResume(null); });
1105
- return () => { live = false; };
1106
- }, [selected.id, lastEnded]);
1107
-
1108
1211
  async function start(action) {
1109
1212
  setProblem(null);
1110
1213
  setChecking(true);
@@ -1122,27 +1225,32 @@ function CreateClusterCard({ selected, degree2 }) {
1122
1225
  }
1123
1226
 
1124
1227
  const read = hasCluster || Boolean(resume);
1125
- const accent = hasCluster ? '#00ff88' : '#9B59B6';
1228
+ // Gold while a read is stopped partway, green once a circle is in, purple before.
1229
+ const accent = resume ? '#FFD700' : hasCluster ? '#00ff88' : '#9B59B6';
1230
+ // Gold words vanish on a light look; the theme's own gold reads on either.
1231
+ const ink = resume ? 'var(--sd-gold, #FFD700)' : accent;
1126
1232
  const off = Boolean(busy) || checking;
1127
1233
  const button = (primary) => ({
1128
1234
  flex: 1, padding: '11px 8px', borderRadius: 8, fontWeight: 700, fontSize: primary ? 13 : 12,
1129
1235
  cursor: off ? 'not-allowed' : 'pointer', opacity: off ? 0.45 : 1,
1130
1236
  ...(primary
1131
1237
  ? { border: 'none', background: 'linear-gradient(135deg, #9B59B6, #3498DB)', color: '#fff' }
1132
- : { border: `1px solid ${accent}55`, background: `${accent}14`, color: accent }),
1238
+ : { border: `1px solid ${accent}55`, background: `${accent}14`, color: ink }),
1133
1239
  });
1134
1240
 
1135
1241
  return (
1136
1242
  <div id="scan-box" style={{
1137
- background: hasCluster ? 'rgba(0,255,136,0.06)' : 'rgba(155,89,182,0.08)',
1138
- border: `1px solid ${hasCluster ? 'rgba(0,255,136,0.2)' : 'rgba(155,89,182,0.3)'}`,
1243
+ background: resume ? 'rgba(255,215,0,0.06)' : hasCluster ? 'rgba(0,255,136,0.06)' : 'rgba(155,89,182,0.08)',
1244
+ border: `1px solid ${resume ? 'rgba(255,215,0,0.32)' : hasCluster ? 'rgba(0,255,136,0.2)' : 'rgba(155,89,182,0.3)'}`,
1139
1245
  borderRadius: 10, padding: 16, marginTop: 16,
1140
1246
  }}>
1141
- <div style={{ fontSize: 10, color: accent, fontWeight: 700, letterSpacing: 1, marginBottom: 4, textAlign: 'center' }}>
1142
- {hasCluster ? 'CLUSTER ACTIVE' : 'CREATE CLUSTER'}
1247
+ <div style={{ fontSize: 10, color: ink, fontWeight: 700, letterSpacing: 1, marginBottom: 4, textAlign: 'center' }}>
1248
+ {scanBoxTitle({ resume, hasCluster })}
1143
1249
  </div>
1144
1250
  <div style={{ fontSize: 12, color: 'var(--sd-fg-3, #aaa)', marginBottom: 12, textAlign: 'center' }}>
1145
- {hasCluster
1251
+ {resume
1252
+ ? (hasCluster ? `${clusterCount} connections mapped so far` : 'Their list is only partly read')
1253
+ : hasCluster
1146
1254
  ? `${clusterCount} connections mapped in Six Degrees`
1147
1255
  : <>Scan {selected.name}&apos;s connections to map their network</>}
1148
1256
  </div>
@@ -1169,7 +1277,8 @@ function CreateClusterCard({ selected, degree2 }) {
1169
1277
  {last && <LastRead job={last} />}
1170
1278
  <div style={{ display: 'flex', gap: 8 }}>
1171
1279
  {resume && (
1172
- <button onClick={() => start('resume')} disabled={off} style={button(true)}>
1280
+ // data-resume: the line by their name presses this one (StoppedPartway).
1281
+ <button data-resume onClick={() => start('resume')} disabled={off} style={button(true)}>
1173
1282
  Resume from page {resume.nextPage}
1174
1283
  </button>
1175
1284
  )}