six-degrees 0.4.10 → 0.5.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 (414) 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 +9 -9
  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 +13 -13
  15. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
  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 +1 -1
  27. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/company-scores/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  31. package/.next/standalone/.next/server/app/api/connections/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/reveal/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  38. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  40. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  44. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/outreach/route.js +1 -1
  46. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  48. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
  50. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/settings/route.js +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 +1 -1
  54. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +1 -1
  56. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/social/crm/route/app-paths-manifest.json +3 -0
  60. package/.next/standalone/.next/server/app/api/social/crm/route/build-manifest.json +12 -0
  61. package/.next/standalone/.next/server/app/api/social/crm/route/server-reference-manifest.json +4 -0
  62. package/.next/standalone/.next/server/app/api/social/crm/route.js +10 -0
  63. package/.next/standalone/.next/server/app/api/social/crm/route.js.map +5 -0
  64. package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -0
  65. package/.next/standalone/.next/server/app/api/social/crm/route_client-reference-manifest.js +3 -0
  66. package/.next/standalone/.next/server/app/api/social/messages/route.js +4 -3
  67. package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/api/social/route.js +4 -3
  69. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  71. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  73. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  74. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  76. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/import.html +1 -1
  81. package/.next/standalone/.next/server/app/import.rsc +15 -15
  82. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
  83. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  84. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +14 -14
  85. package/.next/standalone/.next/server/app/index.html +1 -1
  86. package/.next/standalone/.next/server/app/index.rsc +15 -15
  87. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  88. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  89. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  90. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/app/launch.html +2 -2
  93. package/.next/standalone/.next/server/app/launch.rsc +15 -15
  94. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
  95. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  96. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +14 -14
  97. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  100. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  101. package/.next/standalone/.next/server/app/paths.html +1 -1
  102. package/.next/standalone/.next/server/app/paths.rsc +15 -15
  103. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
  104. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  105. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +14 -14
  106. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  107. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  108. package/.next/standalone/.next/server/app/profile.html +1 -1
  109. package/.next/standalone/.next/server/app/profile.rsc +15 -15
  110. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
  111. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  112. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +14 -14
  113. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  115. package/.next/standalone/.next/server/app/queue.html +1 -1
  116. package/.next/standalone/.next/server/app/queue.rsc +15 -15
  117. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
  118. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  119. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +14 -14
  120. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  121. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  122. package/.next/standalone/.next/server/app/scores.html +1 -1
  123. package/.next/standalone/.next/server/app/scores.rsc +15 -15
  124. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
  125. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  126. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +14 -14
  127. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  128. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  129. package/.next/standalone/.next/server/app/settings.html +1 -1
  130. package/.next/standalone/.next/server/app/settings.rsc +15 -15
  131. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
  132. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  133. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +14 -14
  134. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/app/setup.html +1 -1
  137. package/.next/standalone/.next/server/app/setup.rsc +15 -15
  138. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
  139. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  140. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +14 -14
  141. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  143. package/.next/standalone/.next/server/app/social.html +1 -1
  144. package/.next/standalone/.next/server/app/social.rsc +15 -15
  145. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
  146. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
  147. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +14 -14
  148. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  149. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__05_3-22._.js} +2 -2
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  151. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__06mi9k2._.js → [root-of-the-server]__13dx4sd._.js} +7 -2
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
  153. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +8 -0
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
  156. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  157. package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +1 -1
  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/_1iwkh8t._.js +1 -1
  161. package/.next/standalone/.next/server/chunks/_1oqfuh8._.js +3 -0
  162. package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
  163. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_crm_route_actions_0d1aflv.js +3 -0
  164. package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
  165. package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
  166. package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
  167. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
  168. package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +3 -0
  169. package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
  170. package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
  171. package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
  172. package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
  173. package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
  174. package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
  175. package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
  176. package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
  177. package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
  178. package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
  180. package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
  181. package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
  182. package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
  183. package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
  184. package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
  185. package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
  186. package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
  187. package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
  188. package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
  192. package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
  196. package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
  198. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
  199. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  200. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -5
  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 +19 -3
  203. package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
  204. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  205. package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
  206. package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +3 -0
  207. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  208. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  209. package/.next/standalone/.next/server/pages/404.html +1 -1
  210. package/.next/standalone/.next/server/pages/500.html +1 -1
  211. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  212. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  213. package/.next/standalone/.next/static/chunks/03bziijw5srol.js +1 -0
  214. package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
  215. package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
  216. package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
  217. package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
  218. package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
  219. package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
  220. package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
  221. package/.next/{static/chunks/1xptlvtcz6654.css → standalone/.next/static/chunks/1x_ysly42-teq.css} +1 -1
  222. package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
  223. package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
  224. package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
  225. package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
  226. package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
  227. package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
  228. package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
  229. package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
  230. package/.next/standalone/CHANGELOG.md +235 -0
  231. package/.next/standalone/app/api/data/export/route.js +2 -1
  232. package/.next/standalone/app/api/ingest/route.js +40 -1
  233. package/.next/standalone/app/api/scraper/route.js +12 -2
  234. package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
  235. package/.next/standalone/app/api/social/crm/route.js +37 -0
  236. package/.next/standalone/app/api/social/messages/route.js +44 -8
  237. package/.next/standalone/app/api/social/route.js +80 -22
  238. package/.next/standalone/app/components/AppTabs.js +59 -0
  239. package/.next/standalone/app/components/ChainView.js +303 -89
  240. package/.next/standalone/app/components/CirclePanel.js +162 -0
  241. package/.next/standalone/app/components/CompanyScores.js +2 -2
  242. package/.next/standalone/app/components/FieldStep.js +4 -4
  243. package/.next/standalone/app/components/FilterPanel.js +121 -183
  244. package/.next/standalone/app/components/ForceGraph.js +41 -3
  245. package/.next/standalone/app/components/GalaxyLab.js +26 -31
  246. package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
  247. package/.next/standalone/app/components/MapControls.js +154 -0
  248. package/.next/standalone/app/components/NoteDetail.js +118 -0
  249. package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
  250. package/.next/standalone/app/components/PeopleMap.js +308 -0
  251. package/.next/standalone/app/components/ScanRadar.js +129 -0
  252. package/.next/standalone/app/components/ScanStatusBar.js +72 -27
  253. package/.next/standalone/app/components/SeparationView.js +626 -118
  254. package/.next/standalone/app/components/Sidebar.js +28 -2
  255. package/.next/standalone/app/components/settings/DataSection.js +12 -3
  256. package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
  257. package/.next/standalone/app/components/settings/SectorSection.js +2 -2
  258. package/.next/standalone/app/components/settings/TierSection.js +1 -1
  259. package/.next/standalone/app/components/settings/TitleSection.js +149 -0
  260. package/.next/standalone/app/components/views.js +15 -14
  261. package/.next/standalone/app/globals.css +11 -0
  262. package/.next/standalone/app/page.js +89 -156
  263. package/.next/standalone/app/paths/page.js +39 -27
  264. package/.next/standalone/app/profile/page.js +33 -5
  265. package/.next/standalone/app/queue/page.js +56 -34
  266. package/.next/standalone/app/scores/page.js +16 -66
  267. package/.next/standalone/app/settings/page.js +27 -13
  268. package/.next/standalone/app/setup/page.js +298 -205
  269. package/.next/standalone/app/social/Crm.js +683 -0
  270. package/.next/standalone/app/social/SocialHub.js +374 -0
  271. package/.next/standalone/app/social/Thread.js +90 -0
  272. package/.next/standalone/app/social/page.js +10 -351
  273. package/.next/standalone/lib/chain-layout.js +105 -35
  274. package/.next/standalone/lib/data-export.js +10 -4
  275. package/.next/standalone/lib/data-folder.js +9 -0
  276. package/.next/standalone/lib/data-import.js +13 -3
  277. package/.next/standalone/lib/degrees.js +0 -6
  278. package/.next/standalone/lib/dot-rings.js +25 -0
  279. package/.next/standalone/lib/galaxy-lab.js +65 -6
  280. package/.next/standalone/lib/ingest.js +2 -0
  281. package/.next/standalone/lib/island.js +40 -0
  282. package/.next/standalone/lib/linkedin-export.js +267 -46
  283. package/.next/standalone/lib/linkedin-limits.js +6 -2
  284. package/.next/standalone/lib/map-heat.js +20 -0
  285. package/.next/standalone/lib/network-health.js +1 -1
  286. package/.next/standalone/lib/notifications.js +121 -0
  287. package/.next/standalone/lib/people-map.js +82 -0
  288. package/.next/standalone/lib/rarity.js +45 -3
  289. package/.next/standalone/lib/reach.js +7 -6
  290. package/.next/standalone/lib/rpc.js +15 -4
  291. package/.next/standalone/lib/scan-pace.js +46 -0
  292. package/.next/standalone/lib/scoring.js +39 -8
  293. package/.next/standalone/lib/scraper-client.js +19 -6
  294. package/.next/standalone/lib/sector-focus.js +4 -4
  295. package/.next/standalone/lib/separation.js +137 -10
  296. package/.next/standalone/lib/settings-effects.js +20 -11
  297. package/.next/standalone/lib/settings.js +3 -0
  298. package/.next/standalone/lib/social-crm.js +415 -0
  299. package/.next/standalone/lib/social-store.js +13 -6
  300. package/.next/standalone/lib/tier-grid.js +99 -0
  301. package/.next/standalone/lib/title-ranking.js +83 -0
  302. package/.next/standalone/package-lock.json +2 -2
  303. package/.next/standalone/package.json +1 -1
  304. package/.next/standalone/scripts/scrape.py +177 -35
  305. package/.next/static/chunks/03bziijw5srol.js +1 -0
  306. package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
  307. package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
  308. package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
  309. package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
  310. package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
  311. package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
  312. package/.next/static/chunks/1i51yylxdzht1.js +1 -0
  313. package/.next/{standalone/.next/static/chunks/1xptlvtcz6654.css → static/chunks/1x_ysly42-teq.css} +1 -1
  314. package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
  315. package/.next/static/chunks/2rqtpccruavf2.js +1 -0
  316. package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
  317. package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
  318. package/.next/static/chunks/355ryio1b7sm1.js +1 -0
  319. package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
  320. package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
  321. package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
  322. package/CHANGELOG.md +235 -0
  323. package/lib/chain-layout.js +105 -35
  324. package/lib/data-export.js +10 -4
  325. package/lib/data-folder.js +9 -0
  326. package/lib/data-import.js +13 -3
  327. package/lib/degrees.js +0 -6
  328. package/lib/dot-rings.js +25 -0
  329. package/lib/galaxy-lab.js +65 -6
  330. package/lib/ingest.js +2 -0
  331. package/lib/island.js +40 -0
  332. package/lib/linkedin-export.js +267 -46
  333. package/lib/linkedin-limits.js +6 -2
  334. package/lib/map-heat.js +20 -0
  335. package/lib/network-health.js +1 -1
  336. package/lib/notifications.js +121 -0
  337. package/lib/people-map.js +82 -0
  338. package/lib/rarity.js +45 -3
  339. package/lib/reach.js +7 -6
  340. package/lib/rpc.js +15 -4
  341. package/lib/scan-pace.js +46 -0
  342. package/lib/scoring.js +39 -8
  343. package/lib/scraper-client.js +19 -6
  344. package/lib/sector-focus.js +4 -4
  345. package/lib/separation.js +137 -10
  346. package/lib/settings-effects.js +20 -11
  347. package/lib/settings.js +3 -0
  348. package/lib/social-crm.js +415 -0
  349. package/lib/social-store.js +13 -6
  350. package/lib/tier-grid.js +99 -0
  351. package/lib/title-ranking.js +83 -0
  352. package/package.json +1 -1
  353. package/scripts/scrape.py +177 -35
  354. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +0 -8
  355. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
  356. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
  357. package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +0 -8
  358. package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
  359. package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
  360. package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
  361. package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
  362. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
  363. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
  364. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
  365. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
  366. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
  367. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
  368. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
  369. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
  370. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
  371. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
  372. package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
  373. package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +0 -3
  374. package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
  375. package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
  376. package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
  378. package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
  379. package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +0 -3
  380. package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
  381. package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
  382. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
  383. package/.next/standalone/.next/static/chunks/07dyt47mp4s9-.js +0 -1
  384. package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
  385. package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
  386. package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
  387. package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
  388. package/.next/standalone/.next/static/chunks/206rm6j42gl_a.js +0 -1
  389. package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
  390. package/.next/standalone/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
  391. package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
  392. package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +0 -1
  393. package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
  394. package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
  395. package/.next/standalone/app/components/OrbitGraph.js +0 -737
  396. package/.next/standalone/app/social/Conversations.js +0 -246
  397. package/.next/static/chunks/07dyt47mp4s9-.js +0 -1
  398. package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
  399. package/.next/static/chunks/160wjb73ddwqd.js +0 -6
  400. package/.next/static/chunks/1c_n0973t07l0.js +0 -2
  401. package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
  402. package/.next/static/chunks/206rm6j42gl_a.js +0 -1
  403. package/.next/static/chunks/236f0jtp68j_h.js +0 -1
  404. package/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
  405. package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
  406. package/.next/static/chunks/2pw6_osz1n_te.js +0 -1
  407. package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
  408. package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
  409. /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
  410. /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
  411. /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
  412. /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
  413. /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
  414. /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
package/lib/galaxy-lab.js CHANGED
@@ -7,7 +7,7 @@
7
7
  // which isn't. Both follow useSyncExternalStore's rules: a snapshot only changes
8
8
  // when something in it does.
9
9
 
10
- import { keyFor } from './separation.js';
10
+ import { keyFor, score } from './separation.js';
11
11
  import { warmthOf } from './linkedin-export.js';
12
12
 
13
13
  const KEY = 'six-degrees-galaxy-lab';
@@ -15,7 +15,7 @@ const LAYOUTS_KEY = 'six-degrees-galaxy-layouts';
15
15
 
16
16
  /** The layout as it has always been. Every slider is a multiple of today's value, or 0 for off. */
17
17
  export const LAB_DEFAULTS = Object.freeze({
18
- on: false, // the lab is on: without it, the Galaxy ignores everything below
18
+ on: true, // always: the sliders are the Galaxy's own controls now (it was an experimental switch)
19
19
  gravity: 0, // a pull on every dot towards you, in the middle
20
20
  rings: 1, // how hard each tier (or degree) holds its ring; 0 lets it find its own shape
21
21
  push: 15, // how hard dots push each other apart
@@ -25,11 +25,12 @@ export const LAB_DEFAULTS = Object.freeze({
25
25
  lines: 1,
26
26
  sizeBy: 'power', // 'power' (power score) or 'reach' (how many hang off them)
27
27
  names: 'key', // 'key' (you, S tier, catalysts) or 'all' (all your connections)
28
- branch: true, // hovering a dot lights up its branch and dims the rest
29
- colourBy: 'tier', // 'tier', 'degree', 'company' or 'warmth' (from the Social tab)
28
+ branch: false, // hovering a dot lights up its branch and dims the rest: off to start (Blake, 2026-10-02: it glitched)
29
+ colourBy: 'tier', // 'tier', 'heat' (power as a thermal map), 'degree', 'company' or 'warmth' (from the Social tab)
30
30
  speed: 15, // a replay's length, in seconds
31
31
  loop: false, // start the replay again when it ends
32
32
  labels: true, // names on the Galaxy at all: the Filter panel's switch, honoured with the lab off too
33
+ v: 2, // which defaults a saved setting was made under (2: the branch starts off)
33
34
  });
34
35
 
35
36
  /** The slider settings a saved layout keeps (not whether the lab is on, nor the names switch). */
@@ -38,13 +39,33 @@ const LAYOUT_KEYS = ['gravity', 'rings', 'push', 'pull', 'distance', 'dotSize',
38
39
  /** Let the layout find its own shape: no rings, each connection's circle bunched round them. */
39
40
  export const CLUSTERS = Object.freeze({ gravity: 0.25, rings: 0, push: 25, pull: 2.5, distance: 0.35, sizeBy: 'reach' });
40
41
 
42
+ /**
43
+ * Orbit, the old view, as a layout of the Galaxy (Blake, 2026-10-02: "add that as a
44
+ * visual option instead so the person can interact with it"): every tier held hard
45
+ * on its orbit, and each connection's circle drawn in close behind them. It's still
46
+ * the Galaxy, so dragging and every slider work on it.
47
+ */
48
+ export const ORBIT = Object.freeze({ gravity: 0, rings: 3, push: 6, pull: 4, distance: 0.22, sizeBy: 'power' });
49
+
50
+ /** Which layout the sliders are set to: 'rings', 'clusters', 'orbit', or null for your own. */
51
+ export function layoutOf(lab) {
52
+ const is = (preset) => Object.entries(preset).every(([k, v]) => lab[k] === v);
53
+ if (['gravity', 'rings', 'push', 'pull', 'distance'].every((k) => lab[k] === LAB_DEFAULTS[k])) return 'rings';
54
+ if (is(CLUSTERS)) return 'clusters';
55
+ if (is(ORBIT)) return 'orbit';
56
+ return null;
57
+ }
58
+
41
59
  const listeners = new Set();
42
60
  let cache = null;
43
61
 
44
62
  function read() {
45
63
  try {
46
64
  const raw = JSON.parse(localStorage.getItem(KEY) || 'null');
47
- return raw && typeof raw === 'object' ? { ...LAB_DEFAULTS, ...raw } : LAB_DEFAULTS;
65
+ // `on` was a switch once; a setting saved back then mustn't turn the sliders off.
66
+ // Saved before v2, the branch light-up was on by default; it starts off now.
67
+ if (!raw || typeof raw !== 'object') return LAB_DEFAULTS;
68
+ return { ...LAB_DEFAULTS, ...raw, ...(raw.v === LAB_DEFAULTS.v ? {} : { branch: false }), v: LAB_DEFAULTS.v, on: true };
48
69
  } catch { return LAB_DEFAULTS; }
49
70
  }
50
71
 
@@ -54,7 +75,7 @@ export function labNow() {
54
75
  }
55
76
 
56
77
  export function setLab(patch) {
57
- cache = patch === null ? { ...LAB_DEFAULTS, on: labNow().on, labels: labNow().labels } : { ...labNow(), ...patch };
78
+ cache = patch === null ? { ...LAB_DEFAULTS, labels: labNow().labels } : { ...labNow(), ...patch, on: true };
58
79
  try { localStorage.setItem(KEY, JSON.stringify(cache)); } catch {}
59
80
  listeners.forEach((f) => f());
60
81
  }
@@ -249,6 +270,36 @@ const DEGREE_COLOURS = { 1: '#4FC3F7', 2: '#BA68C8', 3: '#FFB74D' };
249
270
  const COMPANY_COLOURS = ['#FF6B6B', '#4ECDC4', '#FFD93D', '#A29BFE', '#00B894', '#FD79A8', '#74B9FF', '#E17055'];
250
271
  const WARMTH_COLOURS = { warm: '#FF7043', cool: '#4FC3F7', dormant: '#8D6E63', never: '#546E7A' };
251
272
  const OTHER = '#4a5568';
273
+
274
+ // Heat (Blake, 2026-10-02: "a thermal visual of power based on the dots"): a
275
+ // thermal camera's colours, cold violet through red and orange to white hot.
276
+ const HEAT_STOPS = ['#2b1b6b', '#6a1b9a', '#c2185b', '#f4511e', '#ffb300', '#fff3b0'];
277
+
278
+ /** The thermal colour at `t` (0 cold … 1 hottest). */
279
+ export function heatColour(t) {
280
+ const x = Math.max(0, Math.min(1, Number(t) || 0)) * (HEAT_STOPS.length - 1);
281
+ const i = Math.min(HEAT_STOPS.length - 2, Math.floor(x));
282
+ const f = x - i;
283
+ const a = HEAT_STOPS[i], b = HEAT_STOPS[i + 1];
284
+ const mix = (k) => Math.round(parseInt(a.slice(k, k + 2), 16) * (1 - f) + parseInt(b.slice(k, k + 2), 16) * f);
285
+ return `#${[1, 3, 5].map((k) => mix(k).toString(16).padStart(2, '0')).join('')}`;
286
+ }
287
+
288
+ /**
289
+ * Everyone's heat, 0 to 1: where their power score ranks among the people shown,
290
+ * so the colours spread across whoever is on screen (a network of mostly B tier
291
+ * still has its hot spots). Equal scores share a heat.
292
+ */
293
+ export function heatOf(people = []) {
294
+ return heatBy(people, score);
295
+ }
296
+
297
+ /** The same for anything with a value: each one's heat, 0 to 1, from where its value ranks. */
298
+ export function heatBy(items = [], valueOf = () => 0) {
299
+ const values = [...new Set(items.map(valueOf))].sort((a, b) => a - b);
300
+ const at = new Map(values.map((v, i) => [v, values.length > 1 ? i / (values.length - 1) : 1]));
301
+ return (d) => at.get(valueOf(d)) ?? 0;
302
+ }
252
303
  const companyKey = (c) => String(c || '').trim().toLowerCase();
253
304
 
254
305
  /**
@@ -280,6 +331,14 @@ export function colourScheme(mode, people, tierColors, social) {
280
331
  legend: [...top.map(([, c], i) => [c.name, COMPANY_COLOURS[i]]), ['Other', OTHER]],
281
332
  };
282
333
  }
334
+ if (mode === 'heat') {
335
+ const heat = heatOf(people);
336
+ return {
337
+ of: (d) => heatColour(heat(d)),
338
+ heat,
339
+ legend: [['Cold', heatColour(0.1)], ['Warm', heatColour(0.45)], ['Hot', heatColour(0.75)], ['Hottest', heatColour(1)]],
340
+ };
341
+ }
283
342
  if (mode === 'warmth' && social) {
284
343
  return {
285
344
  of: (d) => ((d.degree || 1) === 1 ? WARMTH_COLOURS[warmthOf(social.merged.get(keyFor(d)), social.asOf)] : '#2d3748'),
package/lib/ingest.js CHANGED
@@ -103,6 +103,8 @@ export function refreshNotifications({ added = [], checked = 0, tierOf = () => n
103
103
  out.push({
104
104
  user_id, type: 'new_elite_connection', title: `High-value connection: ${r.name}`,
105
105
  message: r.headline?.substring(0, 60), icon: '👑',
106
+ // Who it is, so the notification can open them (lib/notifications.js noteSubject).
107
+ data: { profileUrl: r.profile_url },
106
108
  });
107
109
  }
108
110
  return out;
package/lib/island.js CHANGED
@@ -34,3 +34,43 @@ export function clearActivity(id) {
34
34
  activities = activities.filter((x) => x.id !== id);
35
35
  listeners.forEach((f) => f());
36
36
  }
37
+
38
+ // The notch's buttons: the views of the page you're on (Bridge Chains,
39
+ // Separation…), or any page's own sub-tabs. A page sets them while it's on
40
+ // screen and clears them when it leaves; the notch draws them under the header's
41
+ // tabs (Blake, 2026-10-02: "the notch… under the ui buttons at top so then we
42
+ // can use that for button selection too").
43
+ //
44
+ // setNotchTabs({ items: [{ key, label, icon?, title? }], current, onPick })
45
+ // setNotchTabs(null)
46
+ const tabListeners = new Set();
47
+ let notchTabs = null;
48
+
49
+ export const notchTabsNow = () => notchTabs;
50
+ export const noNotchTabs = () => null;
51
+
52
+ export function watchNotchTabs(f) {
53
+ tabListeners.add(f);
54
+ return () => tabListeners.delete(f);
55
+ }
56
+
57
+ // Whether the notch is on screen at all (tabs, a scan, or another job), so a
58
+ // page with a heading at the top can leave room for it only when it's there.
59
+ const shownListeners = new Set();
60
+ let notchShown = false;
61
+ export const notchShownNow = () => notchShown;
62
+ export const noNotch = () => false;
63
+ export function watchNotchShown(f) {
64
+ shownListeners.add(f);
65
+ return () => shownListeners.delete(f);
66
+ }
67
+ export function setNotchShown(shown) {
68
+ if (notchShown === Boolean(shown)) return;
69
+ notchShown = Boolean(shown);
70
+ shownListeners.forEach((f) => f());
71
+ }
72
+
73
+ export function setNotchTabs(tabs) {
74
+ notchTabs = tabs && Array.isArray(tabs.items) && tabs.items.length > 1 ? tabs : null;
75
+ tabListeners.forEach((f) => f());
76
+ }
@@ -12,7 +12,14 @@
12
12
  // (app/api/social/messages).
13
13
  // chapters your jobs (Positions.csv) and the connections made in each
14
14
  // posts your posts (Shares.csv) and the connections that came the week after
15
- // invites requests to and from you (Invitations.csv), when the export has them
15
+ // invites requests to and from you (Invitations.csv), when the export has
16
+ // them: counts, and one record per request (who, which way, when).
17
+ // The note sent with a request only with Keep my messages.
18
+ // names the names the export gives people, so someone who isn't a
19
+ // connection (and so isn't in the app's network) can be listed
20
+ //
21
+ // Every conversation is kept, whoever it's with and whatever folder it's in;
22
+ // Sponsored Messages and InMails are marked, not dropped (buildConversations).
16
23
  //
17
24
  // Plain functions, no React; tests/linkedin-export.test.mjs.
18
25
 
@@ -111,29 +118,86 @@ export function messageStats(rows = []) {
111
118
  return { self, asOf: asOf || null, people };
112
119
  }
113
120
 
121
+ /** A person with no profile link in the export, by their name: `name:ada quill`. */
122
+ export const nameOnlyKey = (name) => {
123
+ const n = nameKey(name);
124
+ return n ? `name:${n}` : null;
125
+ };
126
+
127
+ const SPONSORED = /sponsored/i;
128
+ const INMAIL = /in\s?mail/i;
129
+ // A company's or a showcase page's link as the sender: an advert, not a person.
130
+ const PAGE_SENDER = /linkedin\.com\/(company|showcase|school)\//i;
131
+ const yes = (v) => /^(yes|true|1)$/i.test(String(v || '').trim());
132
+
133
+ /**
134
+ * What LinkedIn's rows say a conversation is: 'sponsored' (a Sponsored
135
+ * Message, an advert), 'inmail' (an InMail), or null for an ordinary one. The
136
+ * export marks neither in a column of its own, so it's read from the folder,
137
+ * the conversation's title, and a sender that's a company page, not a person.
138
+ */
139
+ function kindOfRow(r) {
140
+ const folder = String(r.folder || '');
141
+ const title = String(r['conversation title'] || '');
142
+ if (SPONSORED.test(folder) || SPONSORED.test(title) || PAGE_SENDER.test(String(r['sender profile url'] || ''))) return 'sponsored';
143
+ if (INMAIL.test(folder) || INMAIL.test(title)) return 'inmail';
144
+ return null;
145
+ }
146
+
114
147
  /**
115
- * Every conversation in messages.csv you're part of, one record each:
116
- * { id, title, people, group, messages: [{ t, fromMe, text }] }, messages oldest
117
- * first. `people` is everyone but you, by profile key; more than one makes it a
118
- * group. You are found the way messageStats finds you. The words (the subject,
148
+ * Every conversation in messages.csv, one record each:
149
+ * { id, title, people, group, kind, folder, messages: [{ t, fromMe, text }] },
150
+ * messages oldest first. `people` is everyone but you, by profile key, or by
151
+ * name (nameOnlyKey) when the export has no link for them; more than one makes
152
+ * it a group, none (only you wrote, and to whom isn't said) leaves it empty.
153
+ * Nothing is left out for being in a folder: INBOX, SENT, ARCHIVE, SPAM and
154
+ * anything else LinkedIn adds all count; `folder` is the newest message's, in
155
+ * lower case. `kind` is 'sponsored', 'inmail' or null (kindOfRow). Drafts
156
+ * weren't sent, so they aren't messages; they're counted in `drafts`, and rows
157
+ * with no conversation or no readable date in `skipped`.
158
+ *
159
+ * You are found the way messageStats finds you, and by your name on rows with
160
+ * no link. `names` is everyone's name as the export spells it, by key, so a
161
+ * person who isn't a connection can still be shown. The words (the subject,
119
162
  * when there is one, then the message) are read only with keepText: without it
120
163
  * a message is a date and who sent it, and the title (a group's name) is left
121
164
  * out too.
122
165
  */
123
166
  export function buildConversations(rows = [], { keepText = false } = {}) {
124
167
  const self = selfOf(rows);
168
+ // Your name as the export spells it, for rows that have no link for you.
169
+ const selfNames = new Map();
170
+ for (const r of rows) {
171
+ if (self && urlKey(r['sender profile url']) === self && r.from) selfNames.set(r.from, (selfNames.get(r.from) || 0) + 1);
172
+ }
173
+ const selfName = nameKey([...selfNames].sort((a, b) => b[1] - a[1])[0]?.[0] || '');
174
+ const isMe = (k) => !!k && (k === self || (!!selfName && k === `name:${selfName}`));
175
+ const names = {};
125
176
  const convos = new Map();
177
+ let drafts = 0;
178
+ let skipped = 0;
126
179
  for (const r of rows) {
127
- const from = urlKey(r['sender profile url']);
128
180
  const id = r['conversation id'];
129
- if (!id || !from) continue;
130
181
  const t = exportDate(r.date);
131
- if (t == null) continue;
132
- if (!convos.has(id)) convos.set(id, { id, title: '', everyone: new Set(), messages: [] });
182
+ if (!id || t == null) { skipped += 1; continue; }
183
+ if (yes(r['is message draft'])) { drafts += 1; continue; }
184
+ const fromUrl = urlKey(r['sender profile url']);
185
+ const from = fromUrl || nameOnlyKey(r.from);
186
+ if (from && r.from && !names[from]) names[from] = String(r.from).trim();
187
+ const toUrls = String(r['recipient profile urls'] || '').split(/[,;\s]+/).filter(Boolean).map(urlKey);
188
+ const toNames = String(r.to || '').split(',').map((s) => s.trim()).filter(Boolean);
189
+ // Names line up with links only when there are as many of each (a name can
190
+ // hold a comma: "Ben Ode, MBA"); without links, the names are all there is.
191
+ const to = toUrls.length ? toUrls : toNames.map(nameOnlyKey).filter(Boolean);
192
+ if (toUrls.length && toUrls.length === toNames.length) toUrls.forEach((u, i) => { if (!names[u]) names[u] = toNames[i]; });
193
+ if (!toUrls.length) toNames.forEach((n) => { const k = nameOnlyKey(n); if (k && !names[k]) names[k] = n; });
194
+ if (!convos.has(id)) convos.set(id, { id, title: '', everyone: new Set(), messages: [], kind: null, folder: '', newest: -Infinity });
133
195
  const c = convos.get(id);
134
- c.everyone.add(from);
135
- String(r['recipient profile urls'] || '').split(/[,;\s]+/).filter(Boolean).forEach((u) => c.everyone.add(urlKey(u)));
136
- const m = { t, fromMe: from === self };
196
+ if (from) c.everyone.add(from);
197
+ to.forEach((p) => c.everyone.add(p));
198
+ c.kind = c.kind || kindOfRow(r);
199
+ if (t >= c.newest) { c.newest = t; c.folder = String(r.folder || '').trim().toLowerCase().slice(0, 30); }
200
+ const m = { t, fromMe: isMe(from) };
137
201
  if (keepText) {
138
202
  m.text = [r.subject, r.content].map((x) => String(x || '').trim()).filter(Boolean).join('\n\n');
139
203
  if (!c.title && r['conversation title']) c.title = String(r['conversation title']).trim();
@@ -142,37 +206,62 @@ export function buildConversations(rows = [], { keepText = false } = {}) {
142
206
  }
143
207
  const conversations = [];
144
208
  for (const c of convos.values()) {
145
- if (!c.everyone.has(self)) continue;
146
- const people = [...c.everyone].filter((p) => p && p !== self).sort();
147
- if (!people.length) continue;
209
+ const people = [...c.everyone].filter((p) => p && !isMe(p)).sort();
148
210
  c.messages.sort((a, b) => a.t - b.t);
149
- const out = { id: c.id, people, group: people.length > 1, messages: c.messages };
211
+ const out = { id: c.id, people, group: people.length > 1, kind: c.kind, folder: c.folder, messages: c.messages };
150
212
  if (keepText) out.title = c.title;
151
213
  conversations.push(out);
152
214
  }
153
- return { self, conversations };
215
+ for (const k of Object.keys(names)) if (isMe(k)) delete names[k];
216
+ return { self, conversations, names, drafts, skipped };
154
217
  }
155
218
 
156
- /** What the Conversations list keeps of one conversation, never any words: { id, people, group, last, lastFromThem, count }. */
219
+ /**
220
+ * What the Conversations list keeps of one conversation, never any words:
221
+ * { id, people, group, kind, folder, last, lastFromThem, count, mine }. `mine`
222
+ * is how many of the messages you wrote; all of them means no one has answered.
223
+ */
157
224
  export function conversationSummary(c) {
158
225
  const last = c.messages[c.messages.length - 1];
159
226
  return {
160
227
  id: c.id,
161
228
  people: c.people,
162
229
  group: c.group,
230
+ kind: c.kind || null,
231
+ folder: c.folder || '',
163
232
  last: last ? last.t : null,
164
233
  lastFromThem: last ? !last.fromMe : null,
165
234
  count: c.messages.length,
235
+ mine: c.messages.filter((m) => m.fromMe).length,
166
236
  };
167
237
  }
168
238
 
239
+ /** The same people, by their names: a set's signature, or null when a name is missing. */
240
+ function nameSignature(keys, nameOf) {
241
+ const out = [];
242
+ for (const k of keys) {
243
+ const n = nameKey(nameOf(k));
244
+ if (!n) return null;
245
+ out.push(n);
246
+ }
247
+ return out.sort().join('|');
248
+ }
249
+
169
250
  /**
170
251
  * The Conversations list: the export's conversations with what the live sync
171
252
  * adds (unread, the thread's link, and a newer last message), plus a
172
- * conversation for each person the sync found and the export doesn't have yet
173
- * (id `live:<key>`, count unknown).
253
+ * conversation for each person or group the sync found and the export doesn't
254
+ * have yet (id `live:<key>`, count unknown).
255
+ *
256
+ * live your connections, by their key (matchLive)
257
+ * others people who aren't your connections, by the link messaging gives
258
+ * them, with their name: matched to the export's conversation with
259
+ * someone of that name when only one has it
260
+ * groups group conversations: { threadUrl, people, names, last, unread },
261
+ * matched to the export's group with the same people by name
262
+ * names the export's names, by key
174
263
  */
175
- export function conversationList(list = [], live = {}) {
264
+ export function conversationList(list = [], live = {}, { others = {}, groups = [], names = {} } = {}) {
176
265
  const out = (Array.isArray(list) ? list : []).map((c) => ({ ...c, unread: null, threadUrl: null }));
177
266
  const oneToOne = new Map();
178
267
  for (const c of out) {
@@ -180,20 +269,59 @@ export function conversationList(list = [], live = {}) {
180
269
  const had = oneToOne.get(c.people[0]);
181
270
  if (!had || (c.last || 0) > (had.last || 0)) oneToOne.set(c.people[0], c);
182
271
  }
272
+ const addLive = (c, v) => {
273
+ if (v.last && (!c.last || v.last > c.last)) {
274
+ c.last = v.last;
275
+ if (typeof v.lastFromThem === 'boolean') c.lastFromThem = v.lastFromThem;
276
+ }
277
+ c.unread = Number.isFinite(v.unread) ? v.unread : c.unread;
278
+ c.threadUrl = v.threadUrl || c.threadUrl;
279
+ };
280
+ const fresh = (id, people, group) => ({ id, people, group, kind: null, folder: '', last: null, lastFromThem: null, count: null, mine: null, unread: null, threadUrl: null });
183
281
  for (const [key, v] of Object.entries(live || {})) {
184
282
  if (!v || typeof v !== 'object') continue;
185
283
  let c = oneToOne.get(key);
186
284
  if (!c) {
187
- c = { id: `live:${key}`, people: [key], group: false, last: null, lastFromThem: null, count: null, unread: null, threadUrl: null };
285
+ c = fresh(`live:${key}`, [key], false);
188
286
  out.push(c);
189
287
  oneToOne.set(key, c);
190
288
  }
191
- if (v.last && (!c.last || v.last > c.last)) {
192
- c.last = v.last;
193
- if (typeof v.lastFromThem === 'boolean') c.lastFromThem = v.lastFromThem;
289
+ addLive(c, v);
290
+ }
291
+ // Someone who isn't a connection: the export knows them by their /in/name
292
+ // link, messaging by /in/ACoA…, so only their name can join the two.
293
+ const byName = new Map();
294
+ for (const [key, c] of oneToOne) {
295
+ const n = nameKey(names[key]);
296
+ if (n) byName.set(n, byName.has(n) ? null : c);
297
+ }
298
+ for (const [link, v] of Object.entries(others || {})) {
299
+ if (!v || typeof v !== 'object') continue;
300
+ let c = oneToOne.get(link) || byName.get(nameKey(v.name)) || null;
301
+ if (!c) {
302
+ c = fresh(`live:${link}`, [link], false);
303
+ out.push(c);
304
+ oneToOne.set(link, c);
194
305
  }
195
- c.unread = Number.isFinite(v.unread) ? v.unread : c.unread;
196
- c.threadUrl = v.threadUrl || c.threadUrl;
306
+ c.liveKey = link;
307
+ addLive(c, v);
308
+ }
309
+ const groupsByNames = new Map();
310
+ for (const c of out) {
311
+ if (!c.group) continue;
312
+ const sig = nameSignature(c.people, (k) => names[k]);
313
+ if (sig) groupsByNames.set(sig, groupsByNames.has(sig) ? null : c);
314
+ }
315
+ for (const g of Array.isArray(groups) ? groups : []) {
316
+ if (!g || !Array.isArray(g.people) || !g.people.length) continue;
317
+ const sig = nameSignature(g.people, (k) => g.names?.[k] || names[k]);
318
+ let c = (sig && groupsByNames.get(sig)) || null;
319
+ if (!c) {
320
+ c = fresh(`live:${g.threadUrl || g.people.join(',')}`, g.people, true);
321
+ out.push(c);
322
+ if (sig) groupsByNames.set(sig, c);
323
+ }
324
+ addLive(c, g);
197
325
  }
198
326
  return out;
199
327
  }
@@ -268,7 +396,32 @@ export function invitationSplit(rows = []) {
268
396
  return { incoming, outgoing, total: incoming + outgoing };
269
397
  }
270
398
 
271
- const nameKey = (s) => String(s || '').split(',')[0].normalize('NFD').replace(/[̀-ͯ]/g, '')
399
+ /**
400
+ * Every request in Invitations.csv, one record each, newest first:
401
+ * { id, dir: 'in' | 'out', key, name, t, note? }. `key` is the other person's
402
+ * profile link (inviterProfileUrl or inviteeProfileUrl, in newer exports), or
403
+ * their name (nameOnlyKey) when there's no link. The note sent with a request
404
+ * is written by a person, like a message, so it's read only with keepText.
405
+ */
406
+ export function buildInvitations(rows = [], { keepText = false } = {}) {
407
+ const out = [];
408
+ for (const r of rows) {
409
+ const d = String(r.direction || '').toUpperCase();
410
+ if (d !== 'INCOMING' && d !== 'OUTGOING') continue;
411
+ const dir = d === 'INCOMING' ? 'in' : 'out';
412
+ const name = String((dir === 'in' ? r.from : r.to) || '').trim();
413
+ const url = urlKey(dir === 'in' ? r.inviterprofileurl : r.inviteeprofileurl);
414
+ const key = (url && /\/in\//.test(url) ? url : null) || nameOnlyKey(name);
415
+ if (!key) continue;
416
+ const t = exportDate(r['sent at']);
417
+ const inv = { id: `${dir}|${key}|${t ?? ''}`, dir, key, name: name.slice(0, 120), t };
418
+ if (keepText && String(r.message || '').trim()) inv.note = String(r.message).trim().slice(0, 2000);
419
+ out.push(inv);
420
+ }
421
+ return out.sort((a, b) => (b.t || 0) - (a.t || 0));
422
+ }
423
+
424
+ export const nameKey = (s) => String(s || '').split(',')[0].normalize('NFD').replace(/[̀-ͯ]/g, '')
272
425
  .toLowerCase().replace(/[^a-z0-9 ]+/g, ' ').replace(/\s+/g, ' ').trim();
273
426
 
274
427
  const THREAD_LINK = /^https:\/\/www\.linkedin\.com\/messaging\/[\w\-/%=.]*$/;
@@ -288,16 +441,8 @@ function previewOf(p) {
288
441
  return { t, fromMe: p.fromMe === true, text };
289
442
  }
290
443
 
291
- /**
292
- * The live messages sync's people, matched to your connections. LinkedIn's
293
- * messaging gives a member link (/in/ACoA…) your connections list doesn't have,
294
- * so a person is matched by their link when it's the same, otherwise by their
295
- * name when exactly one connection has it. Only the match is kept, never the
296
- * name. Returns { live: { connectionKey: { last, unread, threadUrl,
297
- * lastFromThem, preview } }, unmatched }; the caller decides whether the
298
- * preview's words are kept.
299
- */
300
- export function matchLive(incoming = {}, connections = []) {
444
+ /** (link, name) → your connection's key: by link when it's the same, else by a name only one connection has; null otherwise. */
445
+ function connectionMatcher(connections = []) {
301
446
  const byUrl = new Map();
302
447
  const byName = new Map();
303
448
  for (const c of connections) {
@@ -306,21 +451,97 @@ export function matchLive(incoming = {}, connections = []) {
306
451
  const n = nameKey(c.name);
307
452
  if (n) byName.set(n, byName.has(n) ? null : key); // null: two people share it
308
453
  }
454
+ return (url, name) => byUrl.get(keyFor({ profile_url: url })) || byName.get(nameKey(name)) || null;
455
+ }
456
+
457
+ const PROFILE = /^https:\/\/www\.linkedin\.com\/in\/[^/?#\s]+\/?$/;
458
+ const cleanName = (s) => (typeof s === 'string' ? s.replace(/[\u0000-\u001f]/g, ' ').trim().slice(0, 120) : '');
459
+
460
+ /**
461
+ * The live messages sync's people, matched to your connections. LinkedIn's
462
+ * messaging gives a member link (/in/ACoA…) your connections list doesn't have,
463
+ * so a person is matched by their link when it's the same, otherwise by their
464
+ * name when exactly one connection has it. For a connection only the match is
465
+ * kept, never the name: the app has it already. Returns
466
+ *
467
+ * live { connectionKey: { last, unread, threadUrl, lastFromThem, preview } }
468
+ * others { link: { name, ...the same } }: people who aren't your
469
+ * connections. Their name is kept (and nothing else says who they
470
+ * are), so the Social tab can list them.
471
+ * unmatched how many are in `others`
472
+ *
473
+ * The caller decides whether the previews' words are kept.
474
+ */
475
+ export function matchLive(incoming = {}, connections = []) {
476
+ const match = connectionMatcher(connections);
309
477
  const live = {};
310
- let unmatched = 0;
478
+ const others = {};
311
479
  for (const [url, v] of Object.entries(incoming || {})) {
312
480
  if (!v || typeof v !== 'object') continue;
313
- const key = byUrl.get(keyFor({ profile_url: url })) || byName.get(nameKey(v.name)) || null;
314
- if (!key) { unmatched += 1; continue; }
315
- const last = Number(v.last) || null;
316
- if (live[key] && (live[key].last || 0) >= (last || 0)) continue;
317
- live[key] = {
318
- last,
481
+ const key = match(url, v.name);
482
+ const entry = {
483
+ last: Number(v.last) || null,
319
484
  unread: Number.isFinite(Number(v.unread)) && v.unread !== null ? Number(v.unread) : null,
320
485
  threadUrl: threadLink(v.threadUrl),
321
486
  lastFromThem: typeof v.lastFromThem === 'boolean' ? v.lastFromThem : null,
322
487
  preview: previewOf(v.preview),
323
488
  };
489
+ if (!key) {
490
+ const link = keyFor({ profile_url: url });
491
+ if (PROFILE.test(url) && !others[link]) others[link] = { name: cleanName(v.name), ...entry };
492
+ continue;
493
+ }
494
+ if (live[key] && (live[key].last || 0) >= (entry.last || 0)) continue;
495
+ live[key] = entry;
496
+ }
497
+ return { live, others, unmatched: Object.keys(others).length };
498
+ }
499
+
500
+ /**
501
+ * The live sync's group conversations, each member matched to your connections
502
+ * as matchLive does: { threadUrl, people, names, last, unread }. `people` are
503
+ * connection keys where matched, else the link messaging gave; `names` keeps
504
+ * the names of the ones who aren't connections only. No words: a group's
505
+ * newest message isn't read.
506
+ */
507
+ export function matchLiveGroups(groups = [], connections = []) {
508
+ const match = connectionMatcher(connections);
509
+ const out = [];
510
+ for (const g of Array.isArray(groups) ? groups.slice(0, 5000) : []) {
511
+ if (!g || typeof g !== 'object' || !Array.isArray(g.people)) continue;
512
+ const people = [];
513
+ const names = {};
514
+ for (const url of g.people.slice(0, 200)) {
515
+ if (typeof url !== 'string' || !PROFILE.test(url)) continue;
516
+ const name = cleanName(g.names?.[url]);
517
+ const key = match(url, name);
518
+ if (key) { people.push(key); continue; }
519
+ const link = keyFor({ profile_url: url });
520
+ people.push(link);
521
+ if (name) names[link] = name;
522
+ }
523
+ if (people.length < 2) continue;
524
+ out.push({
525
+ threadUrl: threadLink(g.threadUrl),
526
+ people: [...new Set(people)].sort(),
527
+ names,
528
+ last: Number(g.last) || null,
529
+ unread: Number.isFinite(Number(g.unread)) && g.unread !== null ? Number(g.unread) : null,
530
+ lastFromThem: typeof g.lastFromThem === 'boolean' ? g.lastFromThem : null,
531
+ });
532
+ }
533
+ return out;
534
+ }
535
+
536
+ /**
537
+ * A new live sync over what earlier ones found: per person (or group), the
538
+ * newer of the two. A quick sync reads only the top of the list, so what a
539
+ * full-history sync found further down stays until something newer replaces it.
540
+ */
541
+ export function mergeLive(before = {}, after = {}) {
542
+ const out = { ...(before && typeof before === 'object' ? before : {}) };
543
+ for (const [k, v] of Object.entries(after || {})) {
544
+ if (!out[k] || (v.last || 0) >= (out[k].last || 0)) out[k] = v;
324
545
  }
325
- return { live, unmatched };
546
+ return out;
326
547
  }
@@ -14,8 +14,9 @@
14
14
  import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, lstatSync } from 'node:fs';
15
15
  import path from 'node:path';
16
16
  import { durable } from './durable.js';
17
+ import { PACES, DEFAULT_PACE } from './scan-pace.js';
17
18
 
18
- export const DEFAULT_LIMITS = { daily: 50, monthly: 250, profiles: 50 };
19
+ export const DEFAULT_LIMITS = { daily: 50, monthly: 250, profiles: 50, pace: DEFAULT_PACE };
19
20
  export const DAILY_CHOICES = [25, 50, 100, 200, 500];
20
21
  export const MONTHLY_CHOICES = [100, 250, 500, 1000, 0]; // 0 = no monthly cap (Premium)
21
22
  export const PROFILE_CHOICES = [10, 25, 50, 100]; // profile views a day; never unlimited
@@ -71,6 +72,8 @@ export function readLimits(dir) {
71
72
  // default. A number typed in by hand counts as the largest choice under it,
72
73
  // as the scanner reads it (scrape.py search_limits).
73
74
  profiles: profileChoice(num(raw.profiles, 0) || DEFAULT_LIMITS.profiles),
75
+ // The Scan page's speed (lib/scan-pace.js): how fast a budget is used, never how big it is.
76
+ pace: PACES[raw.pace] ? raw.pace : DEFAULT_PACE,
74
77
  };
75
78
  }
76
79
 
@@ -79,12 +82,13 @@ export function readLimits(dir) {
79
82
  * scanner. A value not given keeps what is saved: every key is written back,
80
83
  * or saving one would drop the others to their defaults.
81
84
  */
82
- export function writeLimits(dir, { daily, monthly, profiles } = {}) {
85
+ export function writeLimits(dir, { daily, monthly, profiles, pace } = {}) {
83
86
  const cur = readLimits(dir);
84
87
  const next = {
85
88
  daily: DAILY_CHOICES.includes(daily) ? daily : cur.daily,
86
89
  monthly: MONTHLY_CHOICES.includes(monthly) ? monthly : cur.monthly,
87
90
  profiles: PROFILE_CHOICES.includes(profiles) ? profiles : cur.profiles,
91
+ pace: PACES[pace] ? pace : cur.pace,
88
92
  };
89
93
  writeJsonAtomic(path.join(dir, 'scan-limits.json'), next);
90
94
  return next;
@@ -0,0 +1,20 @@
1
+ // What "hot" means on Paths' two maps (Blake, 2026-10-02: "change color heat
2
+ // mapping" on Paths, as on Network Circle). A bubble's size already says how
3
+ // many; heat says how strong, so the two never repeat each other.
4
+
5
+ import { score } from './separation.js';
6
+
7
+ /** A company's strength: the average power score of its five strongest people shown. */
8
+ export function companyStrength(people = []) {
9
+ const top = people.map(score).sort((a, b) => b - a).slice(0, 5);
10
+ return top.length ? top.reduce((s, v) => s + v, 0) / top.length : 0;
11
+ }
12
+
13
+ /**
14
+ * How strong a connection's cluster is, person for person: S counts 3, A 2,
15
+ * everyone else 1, over the cluster's size (1 to 3). Not scanned: -1, the coldest.
16
+ */
17
+ export function clusterStrength({ size = 0, S = 0, A = 0 } = {}) {
18
+ if (!size) return -1;
19
+ return (3 * S + 2 * A + Math.max(0, size - S - A)) / size;
20
+ }
@@ -1,4 +1,4 @@
1
- // Network health for the Scores tab (item 41, Graph Study §8.5/§10.2), from
1
+ // Network health for Profile → Insights (item 41, Graph Study §8.5/§10.2), from
2
2
  // what the app already keeps. No percentiles against other people: there is no
3
3
  // data to compare with, so every number is about your own network.
4
4
  //