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
@@ -19,7 +19,7 @@ import Link from 'next/link';
19
19
  import { Section, Body, LINE } from '../ui';
20
20
  import {
21
21
  AUTO, PEOPLE_PER_SEARCH, DANGER_AT, REPORTED_MONTH, LEVELS, usageLevel, warningParts, estimate,
22
- barMax, pacificText, whenText, untilText, agoText, hoursText,
22
+ barMax, pacificText, whenText, untilText, agoText, hoursText, INVITE_CAPS, REPORTED_WEEKLY_INVITES,
23
23
  } from '../../../lib/usage';
24
24
  import { RESTRICTED_AT, RISKY_DAILY, SAFE_LIMITS } from '../../../lib/search-risk';
25
25
  import { paceOf, searchesPerHour } from '../../../lib/scan-pace';
@@ -100,6 +100,51 @@ function Meter({ title, used, of, max, color, ticks = [], band, children }) {
100
100
  );
101
101
  }
102
102
 
103
+ /**
104
+ * Auto's connection requests (lib/auto-connect.js): the last 24 hours and the
105
+ * last 7 days, each against its cap, one bar under the other. Its own caps,
106
+ * not the search budget: a request is the one thing Six Degrees sends.
107
+ */
108
+ function InviteMeter({ day, week, freesAt, weekFreesAt, now }) {
109
+ const known = Number.isFinite(day) && Number.isFinite(week);
110
+ const bars = [
111
+ { label: 'Last 24 hours', used: day, cap: INVITE_CAPS.day },
112
+ { label: 'Last 7 days', used: week, cap: INVITE_CAPS.week },
113
+ ];
114
+ const left = known ? Math.max(0, Math.min(INVITE_CAPS.day - day, INVITE_CAPS.week - week)) : null;
115
+ return (
116
+ <div style={{ marginTop: 18 }} data-usage="invites">
117
+ <div style={{ fontSize: 13.5, fontWeight: 650, color: 'var(--sd-fg-1, #e8e8ee)' }}>Connection requests (Auto)</div>
118
+ {bars.map((b) => (
119
+ <div key={b.label} style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 8 }}>
120
+ <span style={{ flex: '0 0 88px', fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)' }}>{b.label}</span>
121
+ <div style={{ position: 'relative', flex: 1, height: 10, borderRadius: 5, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.07)' }}>
122
+ <div style={{
123
+ position: 'absolute', top: 0, bottom: 0, left: 0, borderRadius: 5, transition: 'width 0.4s ease',
124
+ width: Number.isFinite(b.used) ? at(b.used, Math.max(b.cap, b.used)) : 0,
125
+ background: b.used >= b.cap ? '#ff6b6b' : 'linear-gradient(90deg, #FFD700, #FF6B35)',
126
+ }} />
127
+ </div>
128
+ <span style={{ flex: '0 0 auto', fontSize: 13, color: 'var(--sd-fg-3, #8b9a9a)', fontVariantNumeric: 'tabular-nums', minWidth: 58, textAlign: 'right' }}>
129
+ <b style={{ fontSize: 15, color: 'var(--sd-fg-1, #fff)' }}>{count(b.used)}</b> of {b.cap}
130
+ </span>
131
+ </div>
132
+ ))}
133
+ <div style={{ fontSize: 12.5, color: 'var(--sd-fg-3, #8b9a9a)', lineHeight: 1.6, marginTop: 6 }}>
134
+ {!known ? null
135
+ : freesAt ? <>Auto&rsquo;s {INVITE_CAPS.day} a day are used. The next one frees {whenAndUntil(freesAt, now)}.</>
136
+ : weekFreesAt ? <>Auto&rsquo;s {INVITE_CAPS.week} a week are used. The next one frees {whenAndUntil(weekFreesAt, now)}.</>
137
+ : <>{plural(left, 'request')} left for Auto now.</>}
138
+ <div style={small}>
139
+ Each press of Auto sends one request from the scanner&rsquo;s Chrome, without a note, and opens their
140
+ profile once (a profile view). It stops at {INVITE_CAPS.day} in any 24 hours and {INVITE_CAPS.week} in any 7 days.
141
+ LinkedIn doesn&rsquo;t publish its invitation limit; people commonly report about {REPORTED_WEEKLY_INVITES} a week.
142
+ </div>
143
+ </div>
144
+ </div>
145
+ );
146
+ }
147
+
103
148
  function Row({ label, children }) {
104
149
  return (
105
150
  <div style={{ display: 'flex', gap: '4px 16px', flexWrap: 'wrap', padding: '10px 0', borderTop: LINE, fontSize: 13 }}>
@@ -260,6 +305,8 @@ export default function UsageSection() {
260
305
  </div>
261
306
  </Meter>
262
307
 
308
+ <InviteMeter day={u.invitesToday} week={u.invitesWeek} freesAt={u.invitesFreeAt} weekFreesAt={u.invitesWeekFreeAt} now={now} />
309
+
263
310
  <div style={{ marginTop: 22 }}>
264
311
  <Row label="Speed">
265
312
  {pace.label}: about {searchesPerHour(limits.pace)} searches an hour at most while a scan runs.
@@ -301,8 +348,8 @@ export default function UsageSection() {
301
348
  </div>
302
349
 
303
350
  <div style={{ ...small, marginTop: 14, paddingTop: 12, borderTop: LINE }}>
304
- Where these numbers come from: Six Degrees writes down every search and profile view it makes on your
305
- LinkedIn account, on this computer, and counts them here. Searches you make yourself on linkedin.com
351
+ Where these numbers come from: Six Degrees writes down every search, profile view and Auto request it makes
352
+ on your LinkedIn account, on this computer, and counts them here. Searches you make yourself on linkedin.com
306
353
  aren&rsquo;t in it, and LinkedIn shows no count of its own. The {RESTRICTED_AT} searches and the 20 to 25
307
354
  profile views are what happened to one real account, not safe limits. Change the budget on the
308
355
  {' '}<Link href="/setup" style={link}>Scan page</Link>, under Fine-tune the scanner. Nothing here is sent anywhere.
@@ -107,13 +107,12 @@ html:has([data-field-question]) {
107
107
  border-radius: 3px;
108
108
  }
109
109
 
110
- /* Where the scan status bar sits: just under the header (app/components/ScanStatusBar.js). */
111
- :root { --scan-bar-top: 94px; }
112
- @media (max-width: 767px) { :root { --scan-bar-top: 66px; } }
113
110
 
114
111
  /* The notch's running dot (app/components/ScanStatusBar.js). */
115
112
  @keyframes notch-pulse { 50% { opacity: 0.35; } }
116
113
  @media (prefers-reduced-motion: no-preference) { .notch-pulse { animation: notch-pulse 1.6s ease-in-out infinite; } }
114
+ @keyframes notch-in { from { opacity: 0; } }
115
+ @media (prefers-reduced-motion: no-preference) { .notch-in { animation: notch-in 0.25s ease-out; } }
117
116
 
118
117
  /* The Social tab's CRM (app/social/Crm.js): the list and the person side by
119
118
  side, stacked on a phone. In Pipeline the board takes the whole width and
@@ -0,0 +1,220 @@
1
+ 'use client';
2
+
3
+ // Insights: your network ranked by power, and the boards that read it
4
+ // (Blake, 2026-10-03: "stats and insights … data and insights based off their
5
+ // circles … almost no moral tie to the person … base it off value like
6
+ // richest person or highest power people … an intro perspective look at the
7
+ // data of what we have"). Mock-up B, "The Power Index", is the page; mock-up
8
+ // C, the scrolling report, is its Report view.
9
+ //
10
+ // Its views sit in the notch under the header, as every page's do
11
+ // (lib/island.js setNotchTabs): People, Kingmakers, Gatekeepers, Companies,
12
+ // Industries and Report. Every number comes from lib/insights-board.js, which
13
+ // says where each came from; nothing here is estimated and nothing is about
14
+ // money.
15
+
16
+ import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
17
+ import { useSearchParams } from 'next/navigation';
18
+ import Link from 'next/link';
19
+ import AppHeader from '../components/AppHeader';
20
+ import OnboardingGate from '../components/OnboardingGate';
21
+ import { useUser } from '../components/UserProvider';
22
+ import useRequests from '../components/useRequests';
23
+ import { setNotchTabs } from '../../lib/island';
24
+ import { loadNetwork } from '../../lib/network';
25
+ import { loadCsvNetwork } from '../../lib/csv';
26
+ import { loadScanNotes, NO_SCAN_NOTES } from '../../lib/scraper-client';
27
+ import { hasRequest } from '../../lib/requests-client';
28
+ import { networkLevel } from '../../lib/level';
29
+ import { IS_DEMO } from '../../lib/demo';
30
+ import {
31
+ boardBase, powerIndex, kingmakers, gatekeepers, hiddenGiants, tierByRarity, untapped,
32
+ companyPower, industries, titleLadder, coverage, howSure, reachSummary,
33
+ } from '../../lib/insights-board';
34
+ import PeopleView from '../components/insights/PeopleView';
35
+ import { KingmakersBoard, GatekeepersBoard, CompaniesBoard, IndustriesBoard } from '../components/insights/Boards';
36
+ import Report from '../components/insights/Report';
37
+ import { fmt, useIsMobile, TITLE } from '../components/insights/parts';
38
+
39
+ const VIEWS = ['people', 'kingmakers', 'gatekeepers', 'companies', 'industries', 'report'];
40
+ const LABELS = { people: 'People', kingmakers: 'Kingmakers', gatekeepers: 'Gatekeepers', companies: 'Companies', industries: 'Industries', report: 'Report' };
41
+
42
+ export default function InsightsPage() {
43
+ // Suspense because the page reads the address's ?view= (useSearchParams),
44
+ // which Next requires to sit inside one for the page to build (TRAPS §41).
45
+ return <OnboardingGate><Suspense><InsightsInner /></Suspense></OnboardingGate>;
46
+ }
47
+
48
+ function InsightsInner() {
49
+ const { userId } = useUser();
50
+ const isMobile = useIsMobile();
51
+ const asked = useSearchParams().get('view');
52
+ const [view, setView] = useState(() => (VIEWS.includes(asked) ? asked : 'people'));
53
+ const [net, setNet] = useState(null); // { degree1, degree2, degree3, notes, source, budget }
54
+ const [failed, setFailed] = useState(null);
55
+
56
+ useEffect(() => {
57
+ if (IS_DEMO || !userId) return;
58
+ let live = true;
59
+ (async () => {
60
+ try {
61
+ // The sample (this window) or a kept CSV import, else your own network:
62
+ // lib/csv.js openNetworkSource, the one rule every page opens on.
63
+ const local = await loadCsvNetwork();
64
+ if (local) {
65
+ if (live) setNet({ degree1: local.degree1 || [], degree2: local.degree2 || [], degree3: [], notes: NO_SCAN_NOTES, source: local.source || 'csv', budget: null });
66
+ return;
67
+ }
68
+ // Your daily search budget and speed, for what scanning the next circles would take:
69
+ // ?usage only reads the scanner's files (no checks, no Python, nothing written).
70
+ const usage = fetch('/api/scraper?usage').then((r) => (r.ok ? r.json() : null)).catch(() => null);
71
+ const [n, notes, used] = await Promise.all([loadNetwork(userId), loadScanNotes(), usage]);
72
+ const limits = used?.limits;
73
+ if (live) setNet({ ...n, notes, source: 'own', budget: limits ? { daily: limits.daily, pace: limits.pace } : null });
74
+ } catch (e) {
75
+ if (live) setFailed(e.message || 'The network couldn’t be loaded.');
76
+ }
77
+ })();
78
+ return () => { live = false; };
79
+ }, [userId]);
80
+
81
+ // The quick part first: the ranking and the boards that read circles.
82
+ const base = useMemo(() => (net ? boardBase(net) : null), [net]);
83
+ const data = useMemo(() => {
84
+ if (!base) return null;
85
+ const index = powerIndex(base);
86
+ return {
87
+ index, reach: reachSummary(base), kings: kingmakers(base), gates: gatekeepers(base), hidden: hiddenGiants(base),
88
+ grid: tierByRarity(base), cover: coverage(base, net.budget || {}), sure: howSure(index),
89
+ };
90
+ }, [base, net]);
91
+ // Companies and titles read every headline (about half a second on 7,000
92
+ // people), so they follow once the table is on screen.
93
+ const [slow, setSlow] = useState(null);
94
+ useEffect(() => {
95
+ if (!base) return undefined;
96
+ const t = setTimeout(() => {
97
+ const companies = companyPower(base);
98
+ setSlow({ base, companies, industries: industries(companies), ladder: titleLadder(base) });
99
+ }, 30);
100
+ return () => clearTimeout(t);
101
+ }, [base]);
102
+ const heavy = slow && slow.base === base ? slow : null;
103
+
104
+ const requests = useRequests();
105
+ const isAsked = useCallback((row) => hasRequest(row, requests), [requests]);
106
+ const open = useMemo(() => (base ? untapped(base, isAsked) : null), [base, isAsked]);
107
+
108
+ const pick = useCallback((v) => {
109
+ setView(v);
110
+ try {
111
+ const u = new URL(window.location.href);
112
+ if (v === 'people') u.searchParams.delete('view'); else u.searchParams.set('view', v);
113
+ window.history.replaceState(null, '', u.pathname + u.search);
114
+ } catch { /* the address stays */ }
115
+ window.scrollTo(0, 0);
116
+ }, []);
117
+
118
+ // The views in the notch, with how many each ranks.
119
+ useEffect(() => {
120
+ if (!data || data.index.total === 0) { setNotchTabs(null); return undefined; }
121
+ const count = {
122
+ people: data.index.total, kingmakers: data.kings.scanned, gatekeepers: data.gates.list.length,
123
+ companies: heavy?.companies.count, industries: heavy?.industries.list.length,
124
+ };
125
+ setNotchTabs({
126
+ items: VIEWS.map((key) => ({
127
+ key,
128
+ label: count[key] ? <>{LABELS[key]}<small style={{ fontSize: 10.5, opacity: 0.65, fontWeight: 600 }}>{fmt(count[key])}</small></> : LABELS[key],
129
+ })),
130
+ current: view, onPick: pick,
131
+ });
132
+ return () => setNotchTabs(null);
133
+ }, [data, heavy, view, pick]);
134
+
135
+ const level = useMemo(() => (net ? networkLevel(net.degree1, net.degree2) : null), [net]);
136
+ const csvMode = Boolean(net && net.source !== 'own');
137
+
138
+ if (IS_DEMO) {
139
+ return (
140
+ <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, color: 'var(--sd-fg-2, #ccc)', fontFamily: 'var(--sd-font)' }}>
141
+ <h1 style={{ fontSize: 20, fontWeight: 700, margin: 0, color: 'var(--sd-fg-1, #fff)' }}>Not part of the demo</h1>
142
+ <p style={{ fontSize: 13, margin: 0 }}>The public demo includes the Network Circle and Degrees views only.</p>
143
+ <Link href="/" style={{ fontSize: 13, color: 'var(--sd-fg-3, #888)', textDecoration: 'none' }}>&larr; Back to the network</Link>
144
+ </div>
145
+ );
146
+ }
147
+
148
+ const shared = { data, heavy, open, net, source: net?.source, isMobile, onView: pick };
149
+ return (
150
+ <div style={{ minHeight: '100vh', background: 'var(--sd-page)', color: 'var(--sd-fg-1, #fff)', fontFamily: 'var(--sd-font)' }}>
151
+ <AppHeader active="insights" csvMode={csvMode} level={level} isMobile={isMobile} />
152
+ {/* Room for the notch, which hangs under the header with the views in it */}
153
+ <main style={{ maxWidth: view === 'report' ? 920 : 1400, margin: '0 auto', padding: isMobile ? '52px 12px 0' : '58px 30px 0' }}>
154
+ {failed ? <Message title="The network couldn’t be loaded" body={failed} />
155
+ : !data ? <Message title="Reading your network…" />
156
+ : data.index.total === 0 ? <Empty source={net.source} />
157
+ : view === 'people' ? <PeopleView {...shared} requests={requests} />
158
+ : view === 'kingmakers' ? <KingmakersBoard {...shared} />
159
+ : view === 'gatekeepers' ? <GatekeepersBoard {...shared} />
160
+ : view === 'companies' ? <CompaniesBoard {...shared} />
161
+ : view === 'industries' ? <IndustriesBoard {...shared} />
162
+ : <Report {...shared} />}
163
+ <Footer net={net} data={data} isMobile={isMobile} />
164
+ </main>
165
+ </div>
166
+ );
167
+ }
168
+
169
+ function Message({ title, body }) {
170
+ return (
171
+ <div style={{ padding: '80px 0', textAlign: 'center' }}>
172
+ <div style={{ fontSize: 17, fontWeight: 700 }}>{title}</div>
173
+ {body && <div style={{ fontSize: 13, color: 'var(--sd-fg-3, #888)', marginTop: 8 }}>{body}</div>}
174
+ </div>
175
+ );
176
+ }
177
+
178
+ /** No one to rank yet: where people come from. */
179
+ function Empty({ source }) {
180
+ return (
181
+ <div style={{ maxWidth: 560, margin: '60px auto', textAlign: 'center' }}>
182
+ <h1 style={TITLE}>The Power Index</h1>
183
+ <p style={{ fontSize: 15, color: 'var(--sd-fg-2, #b8c4c4)', lineHeight: 1.6, margin: '12px 0 22px' }}>
184
+ Nothing to rank yet. Once your connections are in, everyone within two steps of you shows here, ranked by power,
185
+ with who holds the doors and what’s still closed.
186
+ </p>
187
+ <div style={{ display: 'flex', gap: 10, justifyContent: 'center', flexWrap: 'wrap' }}>
188
+ {source === 'own' && (
189
+ <Link href="/setup" style={{ padding: '10px 16px', borderRadius: 8, fontWeight: 700, fontSize: 13.5, textDecoration: 'none', color: '#000', background: 'linear-gradient(135deg, #00ff88, #1abc9c)' }}>
190
+ Scan your connections →
191
+ </Link>
192
+ )}
193
+ <Link href="/import" style={{ padding: '10px 16px', borderRadius: 8, fontWeight: 700, fontSize: 13.5, textDecoration: 'none', color: 'var(--sd-fg-1, #fff)', border: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.2)' }}>
194
+ Import a CSV
195
+ </Link>
196
+ </div>
197
+ <p style={{ fontSize: 12.5, color: 'var(--sd-fg-4, #6f7a88)', marginTop: 18 }}>
198
+ Or look around first: the <Link href="/" style={{ color: 'var(--sd-blue, #3498DB)', textDecoration: 'none' }}>map’s welcome screen</Link> opens a sample network where every person is invented.
199
+ </p>
200
+ </div>
201
+ );
202
+ }
203
+
204
+ /** The rule every screen with a tier says, and which network this is. */
205
+ function Footer({ net, data, isMobile }) {
206
+ const what = !net || !data?.index.total ? null
207
+ : net.source === 'sample' ? 'The sample network: every person in it is invented.'
208
+ : net.source === 'csv' ? `Your CSV import: ${fmt(data?.reach.d1)} connections, no circles.`
209
+ : data ? `Counted from your ${fmt(data.reach.d1)} connections and the ${fmt(data.reach.circles)} circle${data.reach.circles === 1 ? '' : 's'} you’ve scanned.` : null;
210
+ return (
211
+ <footer style={{
212
+ margin: '36px 0 0', padding: '18px 0 34px', borderTop: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.08)',
213
+ display: 'flex', flexDirection: isMobile ? 'column' : 'row', justifyContent: 'space-between', gap: isMobile ? 8 : 20,
214
+ color: 'var(--sd-fg-4, #6f7a88)', fontSize: 11.5, lineHeight: 1.5,
215
+ }}>
216
+ <div><b style={{ color: 'var(--sd-fg-2, #b8c4c4)' }}>Power ranks reachability, not people.</b> A tier is a statement about network position, not human worth.</div>
217
+ {what && <div>{what}</div>}
218
+ </footer>
219
+ );
220
+ }
@@ -4,8 +4,6 @@ import { Suspense, useEffect, useState, useRef, useMemo, useCallback } from 'rea
4
4
  import { useSearchParams } from 'next/navigation';
5
5
  import { fillsCircles, loadScanNotes, NO_SCAN_NOTES } from '../lib/scraper-client';
6
6
  import useScanner from './components/useScanner';
7
- import useRequests from './components/useRequests';
8
- import { requestCount } from '../lib/requests-client';
9
7
  import { loadNetwork } from '../lib/network';
10
8
  import { resolveView } from './components/views';
11
9
  import { peopleByDegree } from '../lib/degrees';
@@ -105,7 +103,6 @@ function HomeInner() {
105
103
  const [scanNotes, setScanNotes] = useState(NO_SCAN_NOTES);
106
104
  const shapeRef = useRef('');
107
105
  // Everyone with a request out, shared with every view (lib/requests-client.js).
108
- const pendingCount = requestCount(useRequests());
109
106
 
110
107
  useEffect(() => {
111
108
  if (!userId) return;
@@ -305,20 +302,7 @@ function HomeInner() {
305
302
  </button>
306
303
  </div>
307
304
  )}
308
- >
309
- {/* Pending count badge — in header: everyone with a request out, once each (lib/requests-client.js) */}
310
- {isDegreesMode && !csvMode && pendingCount > 0 && (
311
- <Link href="/queue" style={{
312
- padding: '5px 12px', borderRadius: 16, fontSize: 12, fontWeight: 600, textDecoration: 'none',
313
- background: 'rgba(255,107,53,0.12)', color: 'var(--sd-orange, #FF6B35)',
314
- border: '1px solid rgba(255,107,53,0.25)',
315
- display: 'inline-flex', alignItems: 'center', gap: 5,
316
- }}>
317
- <span style={{ fontSize: 10 }}>⏳</span>
318
- {pendingCount} Pending
319
- </Link>
320
- )}
321
- </AppHeader>
305
+ />
322
306
 
323
307
  <div style={{ display: 'flex', height: isMobile ? 'calc(100vh - 70px)' : 'calc(100vh - 130px)', position: 'relative' }}>
324
308
  <FilterPanel
@@ -19,6 +19,10 @@ import { localPhoto } from '../../lib/photos';
19
19
  import { reachIndex } from '../../lib/reach';
20
20
  import { loadScanNotes } from '../../lib/scraper-client';
21
21
  import { TIER_COLORS } from '../../lib/themes';
22
+ import ConnectChoice, { secondaryLook } from '../components/AutoConnect';
23
+
24
+ // Add, standing second to Auto: LinkedIn's blue, as an outline.
25
+ const BLUE_OUTLINE = { color: '#5aa9e6', line: 'rgba(0,119,181,0.75)' };
22
26
 
23
27
 
24
28
  // Who to reach first: power, +1 when the company their score is built on is a
@@ -342,6 +346,7 @@ function QueueInner() {
342
346
  reach={reach}
343
347
  onSend={(r) => markRequested(r, { bridgeId: r.source_connection_id }).catch(() => {})}
344
348
  onUndo={(r) => undoRequest(r).catch(() => {})}
349
+ canAuto={!IS_DEMO}
345
350
  />
346
351
  </div>
347
352
  )}
@@ -600,12 +605,16 @@ function QueueInner() {
600
605
  {sentIds.has(r.id) ? (
601
606
  <span style={{ padding: '3px 6px', borderRadius: 3, fontSize: 8, fontWeight: 700, background: 'rgba(255,107,53,0.15)', color: 'var(--sd-orange, #FF6B35)', flexShrink: 0 }}>Sent</span>
602
607
  ) : (
603
- <a href={r.profile_url} target="_blank" rel="noopener noreferrer"
604
- onClick={(e) => {
605
- e.stopPropagation();
606
- markRequested(r, { bridgeId: r.source_connection_id }).catch(() => {});
607
- }}
608
- style={{ padding: '3px 6px', borderRadius: 3, fontSize: 8, fontWeight: 700, background: '#0077B5', color: '#fff', textDecoration: 'none', flexShrink: 0 }}>Add</a>
608
+ // Auto first, then Add as an outline (components/AutoConnect.js).
609
+ <ConnectChoice person={r} canAuto={!IS_DEMO} size="row" connect={(second) => (
610
+ <a href={r.profile_url} target="_blank" rel="noopener noreferrer"
611
+ onClick={(e) => {
612
+ e.stopPropagation();
613
+ markRequested(r, { bridgeId: r.source_connection_id }).catch(() => {});
614
+ }}
615
+ style={second ? { ...secondaryLook('row', BLUE_OUTLINE), padding: '3px 6px', fontSize: 8 }
616
+ : { padding: '3px 6px', borderRadius: 3, fontSize: 8, fontWeight: 700, background: '#0077B5', color: '#fff', textDecoration: 'none', flexShrink: 0 }}>Add</a>
617
+ )} />
609
618
  )}
610
619
  </div>
611
620
  ))}
@@ -670,18 +679,21 @@ function QueueInner() {
670
679
  background: 'rgba(255,107,53,0.15)', color: 'var(--sd-orange, #FF6B35)', flexShrink: 0,
671
680
  }}>⏳ Sent</span>
672
681
  ) : (
673
- <a href={r.profile_url} target="_blank" rel="noopener noreferrer"
674
- onClick={(e) => {
675
- e.stopPropagation();
676
- // Opens LinkedIn, and marks the request sent everywhere.
677
- markRequested(r, { bridgeId: r.source_connection_id }).catch(() => {});
678
- }}
679
- style={{
680
- padding: '4px 8px', borderRadius: 4, fontSize: 9, fontWeight: 700,
681
- background: '#0077B5', color: '#fff', textDecoration: 'none', flexShrink: 0,
682
- }}>
683
- Add
684
- </a>
682
+ // Auto first, the better pick; Add beside it as an outline (components/AutoConnect.js).
683
+ <ConnectChoice person={r} canAuto={!IS_DEMO} size="row" connect={(second) => (
684
+ <a href={r.profile_url} target="_blank" rel="noopener noreferrer"
685
+ onClick={(e) => {
686
+ e.stopPropagation();
687
+ // Opens LinkedIn, and marks the request sent everywhere.
688
+ markRequested(r, { bridgeId: r.source_connection_id }).catch(() => {});
689
+ }}
690
+ style={second ? secondaryLook('row', BLUE_OUTLINE) : {
691
+ padding: '4px 8px', borderRadius: 4, fontSize: 9, fontWeight: 700,
692
+ background: '#0077B5', color: '#fff', textDecoration: 'none', flexShrink: 0,
693
+ }}>
694
+ Add
695
+ </a>
696
+ )} />
685
697
  )}
686
698
  </div>
687
699
  ))}
@@ -5,7 +5,8 @@ import { useSearchParams } from 'next/navigation';
5
5
  import OnboardingGate from '../components/OnboardingGate';
6
6
  import Link from 'next/link';
7
7
  import { stopScrape, pickedPerson, scanRequest, HIDE_CHROME_KEY } from '../../lib/scraper-client';
8
- import { setupStep, askForField } from '../../lib/scanner-setup';
8
+ import { setupStep, askForField, appManagementStep } from '../../lib/scanner-setup';
9
+ import { saveSettings } from '../../lib/settings-client';
9
10
  import { RISK_POINTS } from '../../lib/scan-risk';
10
11
  import { IS_DEMO } from '../../lib/demo';
11
12
  import { circleScanCost } from '../../lib/reach';
@@ -35,6 +36,7 @@ const ACTION_LABELS = {
35
36
  'auto-bridge-retry': 'Mapping 2nd degree, hidden ones included',
36
37
  resume: 'Carrying on with one paused list',
37
38
  'resume-all': 'Carrying on with every paused list',
39
+ connect: 'Sending a connection request (Auto)',
38
40
  };
39
41
 
40
42
  export default function SetupPage() {
@@ -81,6 +83,8 @@ function SetupInner() {
81
83
  // loads, null when it couldn't be read. And the answer given here, if any.
82
84
  const [settings, setSettings] = useState(undefined);
83
85
  const [field, setField] = useState(null);
86
+ // Step 1's App Management item, answered here ('done' or 'skipped'), if it was.
87
+ const [appAnswer, setAppAnswer] = useState(null);
84
88
  // Someone sent here to have their circle scanned (Bridge Chains, the Degrees
85
89
  // panel's Ready to scan): /setup?scan=<id>. Nothing starts until it's confirmed.
86
90
  // The router's search params rather than window.location: every way here is a
@@ -142,6 +146,15 @@ function SetupInner() {
142
146
  window.scrollTo(0, 0);
143
147
  }
144
148
 
149
+ // Done or Skip on the App Management item: it goes at once, and the answer is
150
+ // kept so it isn't offered again. If that can't be saved it still goes for
151
+ // now and comes back next time, which is all it costs (as Skip for now does,
152
+ // app/components/FieldStep.js).
153
+ async function answerAppManagement(answer) {
154
+ setAppAnswer(answer);
155
+ await saveSettings({ appManagement: answer });
156
+ }
157
+
145
158
  // "I understand": when, kept with your settings so it travels with a copy.
146
159
  async function acceptRisk() {
147
160
  setError(null);
@@ -211,6 +224,10 @@ function SetupInner() {
211
224
  // Your field, asked once when your connections are in, before who they know
212
225
  // (lib/scanner-setup.js askForField); left out while that isn't known (null).
213
226
  const askField = IS_DEMO || field ? false : askForField(s, settings);
227
+ // On a Mac, macOS may ask about App Management when the scanner starts Chrome
228
+ // (lib/scanner-setup.js appManagementStep): offered once, under step 1, and
229
+ // never part of whether step 1 is done.
230
+ const appMgmt = IS_DEMO || appAnswer ? false : appManagementStep(s, settings);
214
231
  // Step 4, honestly: the first circle shows in minutes, the rest takes days.
215
232
  const pace = li?.limits?.pace;
216
233
  const firstCircle = durationText(firstCircleSeconds(pace));
@@ -256,7 +273,7 @@ function SetupInner() {
256
273
  You launch it; it does the reading; you watch it go.
257
274
  </div>
258
275
  <div style={{ display: 'flex', gap: '8px 18px', flexWrap: 'wrap', marginTop: 16 }}>
259
- {['Never posts or messages anyone', 'Never sees your password', 'Nothing leaves this Mac', 'Stops the moment you say'].map((t) => (
276
+ {['Never posts or messages anyone; sends a request only when you press Auto', 'Never sees your password', 'Nothing leaves this Mac', 'Stops the moment you say'].map((t) => (
260
277
  <span key={t} style={{ fontSize: 12.5, color: 'var(--sd-fg-1, #cfe8dc)', display: 'flex', alignItems: 'center', gap: 6 }}>
261
278
  <span style={{ color: 'var(--sd-green, #00ff88)', fontWeight: 900 }}>✓</span>{t}
262
279
  </span>
@@ -374,6 +391,10 @@ function SetupInner() {
374
391
  more={s && step1.chrome && (
375
392
  <Launch href={step1.chrome.href} quiet={Boolean(step1.button)}>{step1.chrome.label}</Launch>
376
393
  )} />
394
+ {/* Blake, 2026-10-04: "shouldnt we have in the onboarding for scan … a button where the
395
+ user is basically brought to the app management and enables it like Flow does".
396
+ Optional, so it sits under the step and never holds it up. */}
397
+ {appMgmt && <AppManagementItem item={appMgmt} onAnswer={answerAppManagement} />}
377
398
  </StepRow>
378
399
  <StepRow n={2} label="Sign in" state={stateOf(2, !!c.signedIn)}>
379
400
  <Mission title="Sign in to LinkedIn, once" time="You do this part" done={!!c.signedIn}
@@ -523,11 +544,23 @@ function SetupInner() {
523
544
  </div>
524
545
  )}
525
546
 
547
+ {/* ---- In progress: everyone whose scan stopped partway, with Resume (Blake, 2026-10-03:
548
+ "some sort of in progress ones in the scanner as well to see all the ones they stopped
549
+ and would like to resume"). Right after step 4, where someone who just stopped a scan
550
+ looks; it was folded away in Fine-tune, and nobody found it. Under Stop while a scan
551
+ runs, so Stop stays right under the step. Only there while someone is. ---- */}
552
+ <PausedList
553
+ paused={s?.paused || []}
554
+ disabled={!canSearch}
555
+ onResume={(p) => run('resume', { id: p.id })}
556
+ onResumeAll={() => run('resume-all', { maxBridges: batch })}
557
+ />
558
+
526
559
  {/* Everything you might want to change, out of the way until you do */}
527
560
  <details style={{ margin: '18px 0 4px', borderRadius: 10, border: LINE, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.02)' }}>
528
561
  <summary style={{ padding: '12px 16px', cursor: 'pointer', fontSize: 13.5, fontWeight: 650, color: 'var(--sd-fg-2, #cfd8d8)' }}>
529
562
  Fine-tune the scanner
530
- <span style={{ fontWeight: 500, color: 'var(--sd-fg-4, #778)', marginLeft: 8 }}>daily budget, how much of each list, the hidden window, paused lists</span>
563
+ <span style={{ fontWeight: 500, color: 'var(--sd-fg-4, #778)', marginLeft: 8 }}>daily budget, how much of each list, the hidden window</span>
531
564
  </summary>
532
565
  <div style={{ padding: '4px 16px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}>
533
566
  <BudgetBox li={li} disabled={busy} onSetLimits={(l) => run('set-limits', l)} />
@@ -586,12 +619,6 @@ function SetupInner() {
586
619
  </Btn>
587
620
  <span style={{ fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)', marginLeft: 10 }}>People whose list was hidden last time, in case they&rsquo;ve opened it.</span>
588
621
  </div>
589
- <PausedList
590
- paused={s?.paused || []}
591
- disabled={!canSearch}
592
- onResume={(p) => run('resume', { id: p.id })}
593
- onResumeAll={() => run('resume-all', { maxBridges: batch })}
594
- />
595
622
  </div>
596
623
  </details>
597
624
 
@@ -664,7 +691,7 @@ function ScanOne({ pick, pages, setPages, li, running, s, canSearch, busy, onSta
664
691
  {circle === 'scanned' && (
665
692
  <div style={{ color: 'var(--sd-gold, #FFD700)', fontSize: 12.5, marginTop: 6 }}>
666
693
  Their circle is already scanned. This reads their whole list again from page 1; to carry on
667
- from where a read stopped, use Resume on their card or in the Paused list below.
694
+ from where a read stopped, use Resume on their card or under In progress below.
668
695
  </div>
669
696
  )}
670
697
  {circle === 'hidden' && (
@@ -792,15 +819,46 @@ function Mission({ title, time, body, launch, more, done = false, children }) {
792
819
  );
793
820
  }
794
821
 
822
+ /**
823
+ * Step 1's optional item on a Mac (lib/scanner-setup.js appManagementStep):
824
+ * why macOS may ask about App Management while scanning, the button that opens
825
+ * that pane, and Done or Skip, either of which puts it away for good.
826
+ */
827
+ function AppManagementItem({ item, onAnswer }) {
828
+ return (
829
+ <section aria-labelledby="app-management-title" data-app-management style={{
830
+ marginTop: 10, padding: '14px 18px', borderRadius: 14,
831
+ background: 'rgba(var(--sd-ink, 255, 255, 255), 0.025)',
832
+ border: '1px dashed rgba(var(--sd-ink, 255, 255, 255), 0.16)',
833
+ }}>
834
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
835
+ <span id="app-management-title" style={{ fontSize: 16, fontWeight: 800 }}>{item.title}</span>
836
+ <span style={{ fontSize: 12, color: 'var(--sd-fg-2, #8fd9b6)', marginLeft: 'auto' }}>Optional, once</span>
837
+ </div>
838
+ <div style={{ fontSize: 13.5, color: 'var(--sd-fg-3, #9fb0bb)', lineHeight: 1.6, margin: '6px 0 12px', maxWidth: 600 }}>{item.text}</div>
839
+ <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
840
+ <Launch href={item.href} quiet>Open App Management</Launch>
841
+ <Btn onClick={() => onAnswer('done')}>Done</Btn>
842
+ <Btn onClick={() => onAnswer('skipped')}>Skip</Btn>
843
+ </div>
844
+ <div style={{ fontSize: 12, color: 'var(--sd-fg-4, #778)', marginTop: 10 }}>It&rsquo;s in {item.where}.</div>
845
+ </section>
846
+ );
847
+ }
848
+
795
849
  /**
796
850
  * The big go button. `quiet` once that step is done, so the next thing to do
797
- * stands out. With `href`, a link that looks the same, opened outside the app
798
- * (the desktop app sends it to the browser): Chrome's download.
851
+ * stands out. With `href`, a link that looks the same: a web address (Chrome's
852
+ * download) opens outside the app, which the desktop app sends to the browser.
853
+ * A System Settings address (App Management) opens in place: a browser then
854
+ * asks to open System Settings without leaving an empty tab behind, and the
855
+ * desktop app opens System Settings itself (desktop/main.mjs).
799
856
  */
800
857
  function Launch({ children, onClick, disabled, quiet = false, href }) {
801
858
  if (href) {
859
+ const web = /^https?:/i.test(href);
802
860
  return (
803
- <a className="launch-go" href={href} target="_blank" rel="noreferrer" style={{
861
+ <a className="launch-go" href={href} {...(web ? { target: '_blank', rel: 'noreferrer' } : {})} style={{
804
862
  ...launchLook(false, quiet), textDecoration: 'none',
805
863
  }}>
806
864
  <span aria-hidden="true" style={{ fontSize: quiet ? 11 : 13 }}>▶</span>{children}
@@ -9,12 +9,13 @@
9
9
  # 3. copies "Six Degrees.app" into /Applications (or ~/Applications)
10
10
  # 4. opens it
11
11
  #
12
- # Why a script and not just the .dmg: the app is not signed with a paid Apple
13
- # developer certificate, so a .dmg downloaded in a browser makes macOS refuse the
14
- # first launch until you approve it in System Settings. A file fetched with curl
15
- # is not marked as "downloaded from the internet", so installed this way it
16
- # simply opens. The .dmg on the Releases page still works; it just needs that
17
- # one approval.
12
+ # Why a script as well as the .dmg: one line that picks the right build for this
13
+ # Mac, checks it against the published checksums, installs it and opens it, and
14
+ # running it again updates. Until 0.8.0 the app wasn't signed, and this was also
15
+ # the way around macOS's first-launch block (a file fetched with curl isn't
16
+ # marked as downloaded from the internet). Since then the app is signed by Blake
17
+ # Burford and notarized by Apple (docs/brain/DESKTOP.md D4), so the .dmg opens
18
+ # like any Mac app too.
18
19
  #
19
20
  # Your data is never touched: it lives in ~/.six-degrees, not in the app, so
20
21
  # running this again later is also how you update.