six-degrees 0.7.0 → 1.0.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 (360) 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_0yx8ynd.js +3 -0
  174. package/.next/standalone/.next/server/chunks/lib_rpc_12p-o9-.js +3 -0
  175. package/.next/standalone/.next/server/chunks/lib_rpc_1466dnb.js +3 -0
  176. package/.next/standalone/.next/server/chunks/{lib_rpc_0is4qoc.js → lib_rpc_1hjjjr4.js} +1 -1
  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 +4 -4
  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/0hggugbh84m4t.js +21 -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/{static/chunks/44yn0tcd9dfk3.js → standalone/.next/static/chunks/1bkt_hhjzvbh8.js} +1 -1
  218. package/.next/standalone/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  219. package/.next/{static/chunks/1qbjermbf68xj.js → standalone/.next/static/chunks/1d8yv3k_atka7.js} +1 -1
  220. package/.next/standalone/.next/static/chunks/1ods5h86raivz.js +1 -0
  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 +83 -0
  232. package/.next/standalone/README.md +40 -14
  233. package/.next/standalone/SECURITY.md +17 -4
  234. package/.next/standalone/app/api/outreach/route.js +13 -73
  235. package/.next/standalone/app/api/scraper/route.js +106 -5
  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 +71 -13
  258. package/.next/standalone/install.sh +7 -6
  259. package/.next/standalone/lib/auto-connect.js +145 -0
  260. package/.next/standalone/lib/companies.js +1 -1
  261. package/.next/standalone/lib/in-progress.js +46 -0
  262. package/.next/standalone/lib/insights-board.js +613 -0
  263. package/.next/standalone/lib/linkedin-limits.js +36 -11
  264. package/.next/standalone/lib/requests-client.js +10 -0
  265. package/.next/standalone/lib/requests.js +124 -0
  266. package/.next/standalone/lib/scan-state.js +1 -0
  267. package/.next/standalone/lib/scanner-setup.js +82 -3
  268. package/.next/standalone/lib/scraper-client.js +18 -5
  269. package/.next/standalone/lib/settings.js +6 -0
  270. package/.next/standalone/lib/usage.js +5 -0
  271. package/.next/standalone/package-lock.json +2 -2
  272. package/.next/standalone/package.json +1 -1
  273. package/.next/standalone/scripts/build-app.mjs +167 -11
  274. package/.next/standalone/scripts/build-desktop.mjs +3 -2
  275. package/.next/standalone/scripts/ci-mac-signing.sh +128 -0
  276. package/.next/standalone/scripts/entitlements/electron.plist +23 -0
  277. package/.next/standalone/scripts/entitlements/node.plist +27 -0
  278. package/.next/standalone/scripts/mac-sign.mjs +563 -0
  279. package/.next/standalone/scripts/make-dmg-background.mjs +2 -1
  280. package/.next/standalone/scripts/scrape.py +569 -9
  281. package/.next/standalone/scripts/set-signing-secrets.sh +96 -0
  282. package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
  283. package/.next/static/chunks/0hggugbh84m4t.js +21 -0
  284. package/.next/{standalone/.next/static/chunks/2eo9pqq9gay3r.js → static/chunks/0ka4prpk3w9wn.js} +4 -4
  285. package/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
  286. package/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
  287. package/.next/{standalone/.next/static/chunks/44yn0tcd9dfk3.js → static/chunks/1bkt_hhjzvbh8.js} +1 -1
  288. package/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  289. package/.next/{standalone/.next/static/chunks/1qbjermbf68xj.js → static/chunks/1d8yv3k_atka7.js} +1 -1
  290. package/.next/static/chunks/1ods5h86raivz.js +1 -0
  291. package/.next/static/chunks/23hc51oa4vkhc.js +5 -0
  292. package/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
  293. package/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
  294. package/.next/{standalone/.next/static/chunks/2zf4jmz9l3li2.js → static/chunks/2w-y83mjlpnse.js} +1 -1
  295. package/.next/static/chunks/2ws1xf293j-tm.js +5 -0
  296. package/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
  297. package/.next/static/chunks/3opgg1efrx8q6.js +1 -0
  298. package/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
  299. package/.next/{standalone/.next/static/chunks/0an_b6zi1r_qq.js → static/chunks/3y40z6of3r9py.js} +1 -5
  300. package/.next/static/chunks/403dpflln8ah5.js +1 -0
  301. package/CHANGELOG.md +83 -0
  302. package/README.md +40 -14
  303. package/SECURITY.md +17 -4
  304. package/lib/auto-connect.js +145 -0
  305. package/lib/companies.js +1 -1
  306. package/lib/in-progress.js +46 -0
  307. package/lib/insights-board.js +613 -0
  308. package/lib/linkedin-limits.js +36 -11
  309. package/lib/requests-client.js +10 -0
  310. package/lib/requests.js +124 -0
  311. package/lib/scan-state.js +1 -0
  312. package/lib/scanner-setup.js +82 -3
  313. package/lib/scraper-client.js +18 -5
  314. package/lib/settings.js +6 -0
  315. package/lib/usage.js +5 -0
  316. package/package.json +1 -1
  317. package/scripts/scrape.py +569 -9
  318. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_4y8z7._.js +0 -8
  319. package/.next/standalone/.next/server/chunks/_02twlca._.js +0 -10
  320. package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +0 -3
  321. package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +0 -3
  322. package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +0 -3
  323. package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +0 -3
  324. package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +0 -3
  325. package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +0 -3
  326. package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +0 -3
  327. package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +0 -3
  328. package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +0 -7
  330. package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +0 -3
  333. package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +0 -21
  334. package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  335. package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +0 -34
  336. package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  337. package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +0 -57
  338. package/.next/standalone/.next/static/chunks/260bomtr214m8.js +0 -34
  339. package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  340. package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  341. package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +0 -1
  342. package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +0 -3
  343. package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  344. package/.next/static/chunks/0e3g0sil79cus.js +0 -21
  345. package/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  346. package/.next/static/chunks/163b-20vwa06i.js +0 -34
  347. package/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  348. package/.next/static/chunks/23_1z47al92nz.js +0 -57
  349. package/.next/static/chunks/260bomtr214m8.js +0 -34
  350. package/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  351. package/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  352. package/.next/static/chunks/3jarus1_38s2z.js +0 -1
  353. package/.next/static/chunks/3ssu341hav_sq.css +0 -3
  354. package/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  355. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_buildManifest.js +0 -0
  356. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_clientMiddlewareManifest.js +0 -0
  357. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_ssgManifest.js +0 -0
  358. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_buildManifest.js +0 -0
  359. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_clientMiddlewareManifest.js +0 -0
  360. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_ssgManifest.js +0 -0
@@ -35,6 +35,7 @@ import { useRouter } from 'next/navigation';
35
35
  import { circleIndex, MAX_DEGREE } from '../../lib/circle';
36
36
  import { localPhoto } from '../../lib/photos';
37
37
  import { reachIndex, reachState, circleState, readyByCircle, circleScanCost, scanBars } from '../../lib/reach';
38
+ import { stoppedLine } from '../../lib/in-progress';
38
39
  import { reachSegments, RING } from '../../lib/dot-rings';
39
40
  import { ringLayout, chainTree, dotRadius, previewBand, tierBandLayout, circleActivity } from '../../lib/chain-layout';
40
41
  import { noteCircle } from '../../lib/notifications';
@@ -42,7 +43,7 @@ import { useUser } from './UserProvider';
42
43
  import { redundancy } from '../../lib/brokerage';
43
44
  import { keyFor, score } from '../../lib/separation';
44
45
  import { hasRequest } from '../../lib/requests-client';
45
- import { watchScanner, scannerNow, isCircleScan } from '../../lib/scraper-client';
46
+ import { watchScanner, scannerNow, isCircleScan, beginScrape, scraperStatus, notReadyMessage, busyReason, resumePoint } from '../../lib/scraper-client';
46
47
  import useRequests from './useRequests';
47
48
  import { TIER_COLORS } from '../../lib/themes';
48
49
 
@@ -1111,6 +1112,17 @@ function EmptyCircle({ person, depth, reach, requests, scanning, canScan, onSele
1111
1112
  const next = ordinal(depth + 1);
1112
1113
  const yours = person.degree === 1;
1113
1114
  const state = yours ? circleState(person, reach) : null;
1115
+ // Read partway with nothing saved yet (everyone on those pages was already
1116
+ // yours): that isn't a list that has been read, so it says it stopped, with
1117
+ // the card's Resume (Blake, 2026-10-03: "the ones they stopped and would like
1118
+ // to resume"). Where it stopped is the card's own answer, asked of the server
1119
+ // (GET /api/scraper?resume=<id>): the map's scan notes don't say how far a
1120
+ // list got. Undefined while it's asked.
1121
+ const stopped = useStoppedAt(state === 'scanned' && !scanning ? person.id : null);
1122
+ // Why Resume didn't start, kept here: a start greys this to "Scanning…" for a
1123
+ // moment even when it's refused, and the button's own state would go with it.
1124
+ const [problem, setProblem] = useState(null);
1125
+ if (state === 'scanned' && !scanning && stopped === undefined) return null; // never "has been read" by mistake
1114
1126
  const cost = circleScanCost();
1115
1127
  let title;
1116
1128
  let body;
@@ -1118,6 +1130,10 @@ function EmptyCircle({ person, depth, reach, requests, scanning, canScan, onSele
1118
1130
  if (scanning) {
1119
1131
  title = `Scanning ${first}’s circle now`;
1120
1132
  body = 'Their people appear here as the scan saves them, every 10 pages.';
1133
+ } else if (stopped) {
1134
+ title = `${first}’s scan stopped partway`;
1135
+ body = `${stoppedLine(stopped)}. Everyone on those pages was already one of your connections, so nobody new is here yet. Resume carries on from page ${stopped.nextPage}.`;
1136
+ if (canScan) action = <ResumeHere person={person} stopped={stopped} problem={problem} setProblem={setProblem} />;
1121
1137
  } else if (state === 'hidden') {
1122
1138
  title = `${first} keeps their connections hidden`;
1123
1139
  body = 'LinkedIn doesn’t show their list, so there is no circle to scan.';
@@ -1149,7 +1165,7 @@ function EmptyCircle({ person, depth, reach, requests, scanning, canScan, onSele
1149
1165
  return (
1150
1166
  <div role="status" style={{
1151
1167
  position: 'absolute', left: '50%', top, transform: 'translateX(-50%)', width: 'min(360px, calc(100% - 32px))',
1152
- background: 'color-mix(in srgb, var(--sd-bg) 94%, transparent)', border: `1px solid ${state === 'hidden' ? 'rgba(var(--sd-ink, 255, 255, 255), 0.12)' : 'rgba(0,255,136,0.25)'}`,
1168
+ background: 'color-mix(in srgb, var(--sd-bg) 94%, transparent)', border: `1px solid ${state === 'hidden' ? 'rgba(var(--sd-ink, 255, 255, 255), 0.12)' : stopped && !scanning ? 'rgba(255,215,0,0.4)' : 'rgba(0,255,136,0.25)'}`,
1153
1169
  borderRadius: 12, padding: '14px 16px', textAlign: 'center',
1154
1170
  }}>
1155
1171
  <div style={{ fontSize: 13, fontWeight: 800, color: state === 'hidden' ? 'var(--sd-fg-3, #aaa)' : 'var(--sd-fg-1, #fff)', marginBottom: 6 }}>
@@ -1161,6 +1177,63 @@ function EmptyCircle({ person, depth, reach, requests, scanning, canScan, onSele
1161
1177
  );
1162
1178
  }
1163
1179
 
1180
+ /**
1181
+ * Where Resume would carry on with one of your connections ({ nextPage,
1182
+ * pagesRead }), null when there's nothing to carry on with, undefined while
1183
+ * it's asked; and never asked for null. Asked again whenever `id` comes back,
1184
+ * as it does when a scan of theirs ends.
1185
+ */
1186
+ function useStoppedAt(id) {
1187
+ const [got, setGot] = useState({ id: null, resume: undefined });
1188
+ useEffect(() => {
1189
+ if (!id) return undefined;
1190
+ let live = true;
1191
+ resumePoint(id).then((r) => { if (live) setGot({ id, resume: r }); }, () => { if (live) setGot({ id, resume: null }); });
1192
+ return () => { live = false; };
1193
+ }, [id]);
1194
+ if (!id) return null;
1195
+ return got.id === id ? got.resume : undefined;
1196
+ }
1197
+
1198
+ /**
1199
+ * Resume for a circle that stopped partway, from the map: what the card's
1200
+ * Resume does (Sidebar.js CreateClusterCard), the same check first, carrying on
1201
+ * from the page it stopped at, never page 1 again. Once it starts, the empty
1202
+ * circle says it's being scanned. Greyed out, saying why, while another scan runs.
1203
+ */
1204
+ function ResumeHere({ person, stopped, problem, setProblem }) {
1205
+ const busy = useSyncExternalStore(watchScanner, () => busyReason(scannerNow()), () => null);
1206
+ const [checking, setChecking] = useState(false);
1207
+ async function go() {
1208
+ setProblem(null);
1209
+ setChecking(true);
1210
+ const blocked = notReadyMessage(await scraperStatus().catch(() => null));
1211
+ setChecking(false);
1212
+ if (blocked) {
1213
+ setProblem(`${blocked} Open the Scan page to finish setting the scanner up.`);
1214
+ return;
1215
+ }
1216
+ try {
1217
+ await beginScrape('resume', { id: person.id });
1218
+ } catch (e) {
1219
+ setProblem(e.message || 'Could not start the scan.');
1220
+ }
1221
+ }
1222
+ const off = Boolean(busy) || checking;
1223
+ return (
1224
+ <>
1225
+ <button type="button" onClick={go} disabled={off} style={{
1226
+ ...primary, border: 'none', cursor: off ? 'not-allowed' : 'pointer', opacity: off ? 0.45 : 1,
1227
+ }}>Resume from page {stopped.nextPage}</button>
1228
+ {(busy || problem) && (
1229
+ <div style={{ fontSize: 10.5, color: problem ? '#ff8080' : 'var(--sd-fg-3, #888)', marginTop: 8 }}>
1230
+ {problem || `${busy}. One scan at a time: this one can start when it finishes.`}
1231
+ </div>
1232
+ )}
1233
+ </>
1234
+ );
1235
+ }
1236
+
1164
1237
  const crumb = { background: 'none', border: 'none', color: '#8fb8d6', fontSize: 11, fontWeight: 600, cursor: 'pointer', padding: 0 };
1165
1238
  const note = {
1166
1239
  fontSize: 11, padding: '6px 10px', borderRadius: 8, border: '1px solid', background: 'rgba(var(--sd-shade, 0, 0, 0), 0.6)', pointerEvents: 'auto',
@@ -34,9 +34,16 @@ export function TierGrid({ grid, counts, onChange, mode, isMobile }) {
34
34
  const what = (d) => DEGREE_IS[mode]?.[d];
35
35
  // Separation: your connections are the ways in, and all of them stay, whatever their tier.
36
36
  const waysIn = mode === 'separation';
37
+ // Degrees and Separation draw no one past the 2nd degree: the 3rd, the people
38
+ // company scans find, is on Network Circle. Its dot says so instead of "no one
39
+ // yet", which read as locked (Blake, 2026-10-03: "the 3rd one is locked even
40
+ // though … we have 3rd degrees unlocked by now").
41
+ const elsewhere = (d) => d === 3 && (mode === 'degrees' || mode === 'separation');
37
42
  const emptyTitle = (d, t) => (waysIn && d === 1
38
43
  ? 'Every connection stays a way in here, whatever their tier: the tiers choose who is ranked'
39
- : `No ${t ? `${t}-Tier ` : 'one '}at ${DEGREE_NAMES[d]} degree yet`);
44
+ : elsewhere(d)
45
+ ? `${t ? `${t}-Tier at 3rd degree` : '3rd degree'}, the people company scans find, isn't drawn here. Network Circle shows ${t ? 'them' : 'it'}.`
46
+ : `No ${t ? `${t}-Tier ` : 'one '}at ${DEGREE_NAMES[d]} degree yet`);
40
47
  return (
41
48
  <div style={{ marginBottom: 18 }}>
42
49
  <div style={{ display: 'flex', alignItems: 'center', marginBottom: 6 }}>
@@ -10,6 +10,7 @@ import { useState } from 'react';
10
10
  import Link from 'next/link';
11
11
  import { limitQuestion, limitNote, SAFE_LIMITS } from '../../lib/search-risk';
12
12
  import { TIER_COLORS as THEME_TIERS } from '../../lib/themes';
13
+ import { inProgressSummary } from '../../lib/in-progress';
13
14
 
14
15
  const LINE = '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.1)';
15
16
  const TIER = THEME_TIERS; // the theme's dot colours (lib/themes.js)
@@ -125,33 +126,57 @@ export function BudgetBox({ li, onSetLimits, disabled }) {
125
126
  );
126
127
  }
127
128
 
129
+ /**
130
+ * Everyone whose list was only partly read: the Scan page's In progress card,
131
+ * with Resume for each and Resume all. Blake, 2026-10-03: "we need to have some
132
+ * sort of in progress ones in the scanner as well to see all the ones they
133
+ * stopped and would like to resume". It used to be folded away in Fine-tune the
134
+ * scanner, where nobody who had just stopped a scan would look; now it stands
135
+ * on its own right after step 4, and only while someone is stopped partway.
136
+ * Each name opens their circle on the map (/?chain=<id>, the link Watch it fill
137
+ * in uses), to see how far it got.
138
+ */
128
139
  export function PausedList({ paused = [], onResume, onResumeAll, disabled }) {
129
140
  const [showAll, setShowAll] = useState(false);
130
141
  if (!paused.length) return null;
131
- const legacy = paused.filter((p) => p.legacy).length;
132
142
  const shown = showAll ? paused : paused.slice(0, 8);
133
143
  return (
134
- <div style={{ borderRadius: 8, border: LINE, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.03)' }}>
135
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', borderBottom: LINE, flexWrap: 'wrap' }}>
136
- <div style={{ flex: 1, minWidth: 200 }}>
137
- <b style={{ fontSize: 13.5 }}>Paused: {paused.length} {paused.length === 1 ? 'list' : 'lists'} with more to read</b>
138
- <div style={{ fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)', marginTop: 2 }}>
139
- Each carries on from the page it stopped at{legacy ? `; ${legacy} were read to page 10 before whole lists were read` : ''}.
144
+ <section aria-labelledby="in-progress-title" style={{
145
+ margin: '20px 0 4px', borderRadius: 14, overflow: 'hidden',
146
+ border: '1px solid rgba(255,215,0,0.3)', background: 'rgba(255,215,0,0.035)',
147
+ }}>
148
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '16px 18px 14px', borderBottom: LINE, flexWrap: 'wrap' }}>
149
+ <div style={{ flex: 1, minWidth: 220 }}>
150
+ <h2 id="in-progress-title" style={{ fontSize: 17, fontWeight: 800, margin: 0, display: 'flex', alignItems: 'center', gap: 9 }}>
151
+ {/* The pause sign the person card shows by a name that stopped partway (Sidebar.js StoppedPartway). */}
152
+ <span aria-hidden="true" style={{ display: 'inline-flex', gap: 2 }}>
153
+ <span style={{ width: 3, height: 12, borderRadius: 1, background: 'var(--sd-gold, #FFD700)' }} />
154
+ <span style={{ width: 3, height: 12, borderRadius: 1, background: 'var(--sd-gold, #FFD700)' }} />
155
+ </span>
156
+ In progress
157
+ </h2>
158
+ <div style={{ fontSize: 13, color: 'var(--sd-fg-3, #8b9a9a)', marginTop: 4, lineHeight: 1.5 }}>
159
+ {inProgressSummary(paused)}
140
160
  </div>
141
161
  </div>
142
- <button onClick={onResumeAll} disabled={disabled} style={{ ...btn, opacity: disabled ? 0.4 : 1, cursor: disabled ? 'not-allowed' : 'pointer', background: disabled ? 'rgba(var(--sd-ink, 255, 255, 255), 0.06)' : 'linear-gradient(135deg, #3498DB, #9B59B6)' }}>
162
+ <button onClick={onResumeAll} disabled={disabled} style={{ ...btn, opacity: disabled ? 0.4 : 1, cursor: disabled ? 'not-allowed' : 'pointer', background: disabled ? 'rgba(var(--sd-ink, 255, 255, 255), 0.06)' : 'linear-gradient(135deg, #3498DB, #9B59B6)', ...(disabled ? {} : { color: '#fff' }) }}>
143
163
  Resume all
144
164
  </button>
145
165
  </div>
146
166
  <div>
147
167
  {shown.map((p) => (
148
- <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 14px', borderBottom: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.04)' }}>
168
+ <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 18px', borderBottom: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.04)' }}>
149
169
  <span style={{ width: 8, height: 8, borderRadius: '50%', background: TIER[p.tier] || '#667', flexShrink: 0 }} />
150
170
  <div style={{ flex: 1, minWidth: 0 }}>
151
- <div style={{ fontSize: 13, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</div>
171
+ <div style={{ fontSize: 13, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
172
+ <Link href={`/?chain=${encodeURIComponent(p.id)}`} title={`See ${p.name}’s circle on the map`} style={{
173
+ color: 'var(--sd-fg-1, #fff)', textDecoration: 'underline', textDecorationColor: 'rgba(var(--sd-ink, 255, 255, 255), 0.35)', textUnderlineOffset: 3,
174
+ }}>{p.name}</Link>
175
+ </div>
152
176
  <div style={{ fontSize: 11.5, color: 'var(--sd-fg-3, #8b9a9a)' }}>
153
177
  Read to page {p.pagesRead} · carries on at {p.nextPage}
154
- {p.at ? ` · ${day(p.at)}` : ''}
178
+ {/* Kept whole: on a phone "Oct" and "3" landed on two lines. */}
179
+ {p.at ? <> <span style={{ whiteSpace: 'nowrap' }}>· {day(p.at)}</span></> : ''}
155
180
  {p.unclear >= 2 ? ' · came back unclear twice, so it waits at the back of Resume all' : ''}
156
181
  </div>
157
182
  </div>
@@ -162,11 +187,11 @@ export function PausedList({ paused = [], onResume, onResumeAll, disabled }) {
162
187
  ))}
163
188
  </div>
164
189
  {paused.length > 8 && (
165
- <button onClick={() => setShowAll(!showAll)} style={{ ...linkBtn, padding: '10px 14px' }}>
190
+ <button onClick={() => setShowAll(!showAll)} style={{ ...linkBtn, padding: '10px 18px' }}>
166
191
  {showAll ? 'Show fewer' : `Show all ${paused.length}`}
167
192
  </button>
168
193
  )}
169
- </div>
194
+ </section>
170
195
  );
171
196
  }
172
197
 
@@ -13,6 +13,7 @@ import { buildQuest, XP_SEND, STAGE_SIZE } from '../../lib/quest';
13
13
  import { initialsFor } from '../../lib/tiers';
14
14
  import { localPhoto } from '../../lib/photos';
15
15
  import { TIER_COLORS as THEME_TIERS } from '../../lib/themes';
16
+ import ConnectChoice from './AutoConnect';
16
17
 
17
18
  const TIER = THEME_TIERS; // the theme's dot colours (lib/themes.js)
18
19
  const LINE = '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.1)';
@@ -49,7 +50,8 @@ function Ring({ progress, size = 86, color = '#FF6B35', children }) {
49
50
  );
50
51
  }
51
52
 
52
- export default function OutlinkQuest({ recs, sentIds, added, mappedIds, reach, onSend, onUndo }) {
53
+ // canAuto: Auto beside every invite (components/AutoConnect.js; Blake, 2026-10-03), on your own network.
54
+ export default function OutlinkQuest({ recs, sentIds, added, mappedIds, reach, onSend, onUndo, canAuto = false }) {
53
55
  const quest = useMemo(() => buildQuest({ recs, sentIds, added, mappedIds, reach }), [recs, sentIds, added, mappedIds, reach]);
54
56
  const [open, setOpen] = useState(null); // bridge id of the expanded cluster
55
57
  const [toast, setToast] = useState(null);
@@ -57,6 +59,11 @@ export default function OutlinkQuest({ recs, sentIds, added, mappedIds, reach, o
57
59
 
58
60
  async function send(person, cluster) {
59
61
  await onSend(person);
62
+ cheer(person, cluster);
63
+ }
64
+
65
+ // The points and the circle's progress, for an invite sent by hand or by Auto.
66
+ function cheer(person, cluster) {
60
67
  const done = cluster.done + 1;
61
68
  const first = String(cluster.bridge.name || '').split(/[\s,]+/)[0];
62
69
  setToast(done >= cluster.targets.length
@@ -119,12 +126,17 @@ export default function OutlinkQuest({ recs, sentIds, added, mappedIds, reach, o
119
126
  <div style={{ fontSize: 11.5, color: 'var(--sd-fg-2, #aab7b7)', margin: '10px 0' }}>
120
127
  via <b style={{ color: TIER[cluster.bridge.tier] }}>{cluster.bridge.name}</b> · their circle {cluster.done}/{cluster.targets.length} this stage
121
128
  </div>
122
- <div style={{ display: 'flex', gap: 8 }}>
123
- {person.profile_url && (
124
- <a href={person.profile_url} target="_blank" rel="noopener noreferrer" style={btnGhost}>Open on LinkedIn</a>
125
- )}
126
- <button onClick={() => send(person, cluster)} style={btnHot}>I sent an invite · +{XP_SEND[person.tier] || 2}</button>
127
- </div>
129
+ <ConnectChoice person={person} canAuto={canAuto} size="quest" onSent={(p) => cheer(p, cluster)} connect={(second) => {
130
+ const by = (
131
+ <>
132
+ {person.profile_url && (
133
+ <a href={person.profile_url} target="_blank" rel="noopener noreferrer" style={btnGhost}>Open on LinkedIn</a>
134
+ )}
135
+ <button onClick={() => send(person, cluster)} style={second ? btnGhost : btnHot}>I sent an invite · +{XP_SEND[person.tier] || 2}</button>
136
+ </>
137
+ );
138
+ return second ? by : <div style={{ display: 'flex', gap: 8 }}>{by}</div>;
139
+ }} />
128
140
  </div>
129
141
  ))}
130
142
  </div>
@@ -186,10 +198,19 @@ export default function OutlinkQuest({ recs, sentIds, added, mappedIds, reach, o
186
198
  <div style={{ fontWeight: 600, fontSize: 13 }}>{p.name} <span style={{ color: TIER[p.tier], fontSize: 11 }}>{p.tier}</span></div>
187
199
  <div style={{ fontSize: 11.5, color: 'var(--sd-fg-3, #8b9a9a)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.headline}</div>
188
200
  </div>
189
- {p.profile_url && <a href={p.profile_url} target="_blank" rel="noopener noreferrer" style={btnGhost}>LinkedIn</a>}
190
- {sent
191
- ? <button onClick={() => onUndo(p)} style={{ ...btnGhost, color: 'var(--sd-green, #00ff88)' }}>Sent ✓ (undo)</button>
192
- : <button onClick={() => send(p, expanded)} style={btnHot}>Sent · +{XP_SEND[p.tier] || 2}</button>}
201
+ {sent ? (
202
+ <>
203
+ {p.profile_url && <a href={p.profile_url} target="_blank" rel="noopener noreferrer" style={btnGhost}>LinkedIn</a>}
204
+ <button onClick={() => onUndo(p)} style={{ ...btnGhost, color: 'var(--sd-green, #00ff88)' }}>Sent ✓ (undo)</button>
205
+ </>
206
+ ) : (
207
+ <ConnectChoice person={p} canAuto={canAuto} size="quest" onSent={(x) => cheer(x, expanded)} connect={(second) => (
208
+ <>
209
+ {p.profile_url && <a href={p.profile_url} target="_blank" rel="noopener noreferrer" style={btnGhost}>LinkedIn</a>}
210
+ <button onClick={() => send(p, expanded)} style={second ? btnGhost : btnHot}>Sent · +{XP_SEND[p.tier] || 2}</button>
211
+ </>
212
+ )} />
213
+ )}
193
214
  </div>
194
215
  );
195
216
  })}
@@ -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