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
@@ -5,28 +5,37 @@
5
5
 
6
6
  import { rescoreAll } from './rpc.js';
7
7
  import { sameFocus, NO_FOCUS, tierMoves } from './sector-focus.js';
8
+ import { rankingFingerprint } from './title-ranking.js';
8
9
 
9
10
  // Each entry: `changed(before, after)` says whether a change matters (by
10
11
  // default, any change to the stored value), and `run(db, after, before)` does
11
12
  // the work and returns what to tell the page.
13
+ // Rescore everyone and count who changed tier, from the stored tiers before and after.
14
+ function rescoreCountingMoves(db) {
15
+ const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
16
+ try {
17
+ const before = new Map(rows().map((r) => [r.id, r.tier]));
18
+ const { scored } = rescoreAll();
19
+ const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
20
+ return { scored, people, moved: up + down, up, down };
21
+ } catch (err) {
22
+ throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
23
+ }
24
+ }
25
+
12
26
  const EFFECTS = {
27
+ // Title points are part of everyone's score, so a new ranking rescores everyone, as a new tier scale does.
28
+ titleRanking: {
29
+ changed: (before, after) => rankingFingerprint(before) !== rankingFingerprint(after),
30
+ run: (db) => rescoreCountingMoves(db),
31
+ },
13
32
  tierScale: {
14
33
  // Tiers are graded when everyone is scored, so a new scale rescores
15
34
  // everyone. The answer counts who changed tier, from the stored tiers
16
35
  // before and after, with the sector focus's own counter (tierMoves): a
17
36
  // person reachable through two bridges is two rows but one person, counted
18
37
  // once at their closest degree.
19
- run(db) {
20
- const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
21
- try {
22
- const before = new Map(rows().map((r) => [r.id, r.tier]));
23
- const { scored } = rescoreAll();
24
- const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
25
- return { scored, people, moved: up + down, up, down };
26
- } catch (err) {
27
- throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
28
- }
29
- },
38
+ run: (db) => rescoreCountingMoves(db),
30
39
  },
31
40
  sectorFocus: {
32
41
  // Only a change to what scoring uses: a strength with no sectors picked
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { SECTOR_FOCUS_SETTING, FIELD_ASKED_SETTING } from './sector-focus.js';
14
14
  import { TIER_SCALE_SETTING } from './scoring.js';
15
+ import { TITLE_RANKING_SETTING } from './title-ranking.js';
15
16
 
16
17
  export const SETTINGS = {
17
18
  // Scores → Your sector: industries whose companies count for more (lib/sector-focus.js).
@@ -20,6 +21,8 @@ export const SETTINGS = {
20
21
  fieldAsked: FIELD_ASKED_SETTING,
21
22
  // Scores → Tiers: graded on your network's curve, or on the fixed scale (lib/scoring.js).
22
23
  tierScale: TIER_SCALE_SETTING,
24
+ // Scores → Titles: how much each kind of title counts, and roles you're looking for (lib/title-ranking.js).
25
+ titleRanking: TITLE_RANKING_SETTING,
23
26
  };
24
27
 
25
28
  const KEY = 'settings';
@@ -0,0 +1,415 @@
1
+ // The Social tab's CRM: one place for everyone you've been in touch with on
2
+ // LinkedIn, and your own notes on them.
3
+ //
4
+ // Blake, 2026-09-29: "it doesn't show all the conversations and sent messages;
5
+ // this needs to be a full CRM with past conversations, current ones, sent out,
6
+ // etc." The Conversations list showed only connections, and nothing you'd
7
+ // sent that wasn't a message. Here every person you've messaged, asked to
8
+ // connect, been asked by, or tracked a request to in the app is one contact,
9
+ // connection or not, with:
10
+ //
11
+ // what LinkedIn says their conversations (export and live sync), requests
12
+ // both ways (Invitations.csv), the app's own tracked
13
+ // requests (the Outlink queue's "Mark sent")
14
+ // what you say a stage, tags, notes and a next follow-up date: yours,
15
+ // kept per profile in crm-<profile>.json
16
+ // (app/api/social/crm), always, whatever the Keep my
17
+ // messages switch says, because you wrote them
18
+ //
19
+ // and the views: Inbox (waiting on you), Awaiting reply (you wrote last, no
20
+ // answer for N days), Follow-ups due, Pipeline (by stage), Sent, Received, All.
21
+ //
22
+ // Plain functions, no React; tests/social-crm.test.mjs.
23
+
24
+ import { keyFor, routeIndex, routesFor } from './separation.js';
25
+ import { nameKey } from './linkedin-export.js';
26
+
27
+ const DAY = 86400000;
28
+
29
+ export const STAGES = Object.freeze([
30
+ { id: 'new', label: 'New' },
31
+ { id: 'contacted', label: 'Contacted' },
32
+ { id: 'replied', label: 'Replied' },
33
+ { id: 'meeting', label: 'Meeting' },
34
+ { id: 'won', label: 'Won/Partner' },
35
+ { id: 'not-now', label: 'Not now' },
36
+ ]);
37
+ const STAGE_IDS = new Set(STAGES.map((s) => s.id));
38
+ export const stageLabel = (id) => STAGES.find((s) => s.id === id)?.label || '';
39
+
40
+ /** Awaiting reply: you wrote last, and nothing came back for this many days. */
41
+ export const AWAIT_DAYS = 7;
42
+ const MAX_NOTES = 20000;
43
+ const MAX_TAGS = 30;
44
+ const MAX_TAG = 40;
45
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
46
+ const KEY_OK = (k) => typeof k === 'string' && k.length > 0 && k.length < 300;
47
+
48
+ // ── The store: crm-<profile>.json ─────────────────────────────────────────
49
+
50
+ export function emptyCrm() {
51
+ return { version: 1, settings: { awaitDays: AWAIT_DAYS }, people: {} };
52
+ }
53
+
54
+ /** Tags as typed, without commas or control characters, once each whatever the case. */
55
+ export function cleanTags(list) {
56
+ const raw = Array.isArray(list) ? list : typeof list === 'string' ? list.split(',') : [];
57
+ const seen = new Set();
58
+ const out = [];
59
+ for (const t of raw) {
60
+ const tag = String(t ?? '').replace(/[\u0000-\u001f,]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, MAX_TAG);
61
+ if (!tag || seen.has(tag.toLowerCase())) continue;
62
+ seen.add(tag.toLowerCase());
63
+ out.push(tag);
64
+ if (out.length >= MAX_TAGS) break;
65
+ }
66
+ return out;
67
+ }
68
+
69
+ /** A real calendar day as YYYY-MM-DD, or null. */
70
+ function cleanDay(s) {
71
+ if (typeof s !== 'string' || !DATE.test(s)) return null;
72
+ const t = Date.parse(`${s}T00:00:00Z`);
73
+ return Number.isNaN(t) || new Date(t).toISOString().slice(0, 10) !== s ? null : s;
74
+ }
75
+
76
+ /** One person's CRM entry with only what it may hold, or null when there's nothing in it. */
77
+ export function cleanEntry(e) {
78
+ if (!e || typeof e !== 'object') return null;
79
+ const out = {};
80
+ if (STAGE_IDS.has(e.stage)) out.stage = e.stage;
81
+ const tags = cleanTags(e.tags);
82
+ if (tags.length) out.tags = tags;
83
+ if (typeof e.notes === 'string' && e.notes.trim()) out.notes = e.notes.slice(0, MAX_NOTES);
84
+ const followUp = cleanDay(e.followUp);
85
+ if (followUp) out.followUp = followUp;
86
+ if (!Object.keys(out).length) return null;
87
+ if (typeof e.updatedAt === 'string' && !Number.isNaN(Date.parse(e.updatedAt))) out.updatedAt = e.updatedAt;
88
+ return out;
89
+ }
90
+
91
+ const cleanDays = (n) => {
92
+ const d = Math.round(Number(n));
93
+ return Number.isFinite(d) && d >= 1 && d <= 365 ? d : AWAIT_DAYS;
94
+ };
95
+
96
+ /** The whole file, read back: anything that isn't a CRM entry is dropped. */
97
+ export function cleanCrm(raw) {
98
+ const out = emptyCrm();
99
+ if (!raw || typeof raw !== 'object') return out;
100
+ out.settings.awaitDays = cleanDays(raw.settings?.awaitDays);
101
+ for (const [key, e] of Object.entries(raw.people && typeof raw.people === 'object' ? raw.people : {})) {
102
+ if (!KEY_OK(key)) continue;
103
+ const entry = cleanEntry(e);
104
+ if (entry) out.people[key] = entry;
105
+ }
106
+ return out;
107
+ }
108
+
109
+ /**
110
+ * One change to the store: { key, set } changes that person's fields (only the
111
+ * ones sent; null or '' clears one), { settings } the settings. A person left
112
+ * with nothing is taken out. Returns the new store; the old one isn't touched.
113
+ */
114
+ export function patchCrm(store, { key, set, settings } = {}, now = Date.now()) {
115
+ const out = cleanCrm(store);
116
+ if (settings && typeof settings === 'object' && 'awaitDays' in settings) out.settings.awaitDays = cleanDays(settings.awaitDays);
117
+ if (KEY_OK(key) && set && typeof set === 'object') {
118
+ const merged = { ...(out.people[key] || {}) };
119
+ for (const f of ['stage', 'tags', 'notes', 'followUp']) {
120
+ if (!(f in set)) continue;
121
+ if (set[f] == null || set[f] === '' || (Array.isArray(set[f]) && !set[f].length)) delete merged[f];
122
+ else merged[f] = set[f];
123
+ }
124
+ delete merged.updatedAt;
125
+ const entry = cleanEntry(merged);
126
+ if (entry) out.people[key] = { ...entry, updatedAt: new Date(now).toISOString() };
127
+ else delete out.people[key];
128
+ }
129
+ return out;
130
+ }
131
+
132
+ // ── Contacts: everything known about each person ─────────────────────────
133
+
134
+ /** Today on this computer's calendar, as YYYY-MM-DD (a follow-up date is a day, not a moment). */
135
+ export function localDay(d = new Date()) {
136
+ const p = (n) => String(n).padStart(2, '0');
137
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
138
+ }
139
+
140
+ const PROFILE = /^https:\/\/(www\.)?linkedin\.com\/in\/[^/?#\s]+$/;
141
+ const slug = (u) => String(u || '').replace(/\/+$/, '').split('/').pop();
142
+
143
+ /** Who a key is, for showing: their name in the network, else as the export or the sync spelt it, else the end of their link. */
144
+ function nameFor(key, row, names) {
145
+ if (row?.name) return row.name;
146
+ if (names[key]) return names[key];
147
+ if (key.startsWith('name:')) return key.slice(5).replace(/\b\w/g, (c) => c.toUpperCase());
148
+ return slug(key);
149
+ }
150
+
151
+ const TIER_ORDER = { S: 0, A: 1, B: 2, C: 3, D: 4 };
152
+ const betterRow = (a, b) => (a.degree ?? 9) < (b.degree ?? 9)
153
+ || ((a.degree ?? 9) === (b.degree ?? 9) && (TIER_ORDER[a.tier] ?? 9) < (TIER_ORDER[b.tier] ?? 9));
154
+
155
+ /**
156
+ * Everyone you've been in touch with, one contact each, and the group
157
+ * conversations beside them:
158
+ *
159
+ * conversations the Conversations list (conversationList): 1:1 ones make
160
+ * contacts, groups are returned as they are
161
+ * invitations buildInvitations' records
162
+ * network { degree1, degree2, degree3 } rows, as /api/network sends them
163
+ * crm the store (cleanCrm)
164
+ * names names by key, from the export and the live sync
165
+ * also keys to make a contact for even with nothing on file yet
166
+ * (someone found by search, to add a note to)
167
+ *
168
+ * A person the export knows only by name (no link) is joined to the network's
169
+ * person of that name when only one has it. A contact:
170
+ *
171
+ * { key, name, row, connection, degree, profileUrl, introducers,
172
+ * conversations, groups, last, lastFromThem, unread, threadUrl, count,
173
+ * theyWrote, kind, invitesIn, invitesOut, request, crm, lastContact }
174
+ */
175
+ export function buildContacts({ conversations = [], invitations = [], network = {}, crm = emptyCrm(), names = {}, also = [] } = {}) {
176
+ const degree1 = network.degree1 || [];
177
+ const degree2 = network.degree2 || [];
178
+ const rows = [...degree1, ...degree2, ...(network.degree3 || [])];
179
+ const byKey = new Map();
180
+ const byName = new Map();
181
+ for (const r of rows) {
182
+ const k = keyFor(r);
183
+ const had = byKey.get(k);
184
+ if (!had || betterRow(r, had)) byKey.set(k, r);
185
+ }
186
+ for (const [k, r] of byKey) {
187
+ const n = nameKey(r.name);
188
+ if (n) byName.set(n, byName.has(n) && byName.get(n) !== k ? null : k);
189
+ }
190
+ const connected = new Set(degree1.map(keyFor));
191
+ const routes = routeIndex(degree2);
192
+ const bridgeById = new Map(degree1.map((r) => [r.id, r]));
193
+ // A name-only key becomes the network's person of that name, when there's one.
194
+ const resolve = (k) => (k.startsWith('name:') ? byName.get(k.slice(5)) || k : k);
195
+
196
+ const contacts = new Map();
197
+ const contact = (rawKey) => {
198
+ const key = resolve(rawKey);
199
+ let c = contacts.get(key);
200
+ if (c) return c;
201
+ const row = byKey.get(key) || null;
202
+ c = {
203
+ key,
204
+ name: nameFor(key, row, names),
205
+ row,
206
+ connection: connected.has(key),
207
+ degree: row?.degree ?? null,
208
+ profileUrl: PROFILE.test(key) ? key : (row?.profile_url && PROFILE.test(keyFor(row)) ? keyFor(row) : null),
209
+ introducers: row && row.degree === 2
210
+ ? [...new Set(routesFor(row, routes, bridgeById).map((x) => x.bridge?.name).filter(Boolean))]
211
+ : [],
212
+ conversations: [],
213
+ groups: [],
214
+ last: null,
215
+ lastFromThem: null,
216
+ unread: null,
217
+ threadUrl: null,
218
+ count: 0,
219
+ theyWrote: false,
220
+ kind: null,
221
+ invitesIn: [],
222
+ invitesOut: [],
223
+ request: null,
224
+ crm: crm?.people?.[key] || null,
225
+ lastContact: null,
226
+ };
227
+ contacts.set(key, c);
228
+ return c;
229
+ };
230
+
231
+ const groups = [];
232
+ for (const conv of conversations) {
233
+ if (!conv || !Array.isArray(conv.people)) continue;
234
+ if (conv.group) {
235
+ groups.push({ ...conv, people: conv.people.map(resolve) });
236
+ continue;
237
+ }
238
+ if (!conv.people.length) {
239
+ // Only you wrote, and the export doesn't say to whom: kept as a group-like entry of its own.
240
+ groups.push({ ...conv, unknown: true });
241
+ continue;
242
+ }
243
+ const c = contact(conv.people[0]);
244
+ c.conversations.push(conv);
245
+ c.count += conv.count || 0;
246
+ if (conv.lastFromThem === true || (conv.count != null && conv.mine != null && conv.count > conv.mine)) c.theyWrote = true;
247
+ if (conv.last && (!c.last || conv.last >= c.last)) {
248
+ c.last = conv.last;
249
+ c.lastFromThem = conv.lastFromThem;
250
+ c.unread = conv.unread;
251
+ c.threadUrl = conv.threadUrl || c.threadUrl;
252
+ } else if (!c.last && !conv.last) {
253
+ c.threadUrl = conv.threadUrl || c.threadUrl;
254
+ }
255
+ }
256
+ for (const c of contacts.values()) {
257
+ c.conversations.sort((a, b) => (b.last || 0) - (a.last || 0));
258
+ // An advert or an InMail only when every conversation with them is one.
259
+ const kinds = new Set(c.conversations.map((x) => x.kind || null));
260
+ c.kind = kinds.size === 1 ? [...kinds][0] : null;
261
+ }
262
+ for (const g of groups) for (const k of g.people) contacts.get(k)?.groups.push(g.id);
263
+
264
+ for (const inv of invitations) {
265
+ if (!inv?.key) continue;
266
+ const c = contact(inv.key);
267
+ if (inv.name && !c.row && !names[c.key]) c.name = inv.name;
268
+ (inv.dir === 'in' ? c.invitesIn : c.invitesOut).push(inv);
269
+ }
270
+
271
+ // Requests the app tracks: the Outlink queue's "Mark sent", once per person.
272
+ for (const r of rows) {
273
+ const sent = r.outreach_status === 'sent' || r.outreach_status === 'accepted' || r.unlock_status === 'pending';
274
+ if (!sent) continue;
275
+ const c = contact(keyFor(r));
276
+ const status = c.connection || r.outreach_status === 'accepted' ? 'accepted' : 'pending';
277
+ if (!c.request || status === 'accepted') c.request = { status };
278
+ }
279
+
280
+ for (const key of Object.keys(crm?.people || {})) contact(key);
281
+ for (const key of also) if (KEY_OK(key)) contact(key);
282
+
283
+ for (const c of contacts.values()) {
284
+ const times = [c.last, ...c.invitesIn.map((i) => i.t), ...c.invitesOut.map((i) => i.t)].filter(Boolean);
285
+ c.lastContact = times.length ? Math.max(...times) : null;
286
+ }
287
+ return { contacts, groups };
288
+ }
289
+
290
+ /** Where a contact seems to be, from what happened, for when you haven't set a stage. */
291
+ export function suggestedStage(c) {
292
+ if (c.theyWrote) return 'replied';
293
+ if (c.conversations.length || c.invitesOut.length || c.request) return 'contacted';
294
+ return 'new';
295
+ }
296
+
297
+ const isAdvert = (c) => c.kind === 'sponsored';
298
+
299
+ /** Waiting on you: they wrote last, or there's something unread. Unread first, then the newest. */
300
+ export function inboxOf(contacts) {
301
+ return [...contacts].filter((c) => !isAdvert(c) && (c.lastFromThem === true || c.unread > 0))
302
+ .sort((a, b) => (b.unread > 0) - (a.unread > 0) || (b.last || 0) - (a.last || 0));
303
+ }
304
+
305
+ /**
306
+ * Awaiting reply: you wrote last, in a 1:1 conversation, and `days` or more
307
+ * have gone by with nothing back, as of `asOf` (the newest thing the data
308
+ * knows, so an older export reads as it was). Each with `waited` (days).
309
+ */
310
+ export function awaitingOf(contacts, asOf, days = AWAIT_DAYS) {
311
+ const out = [];
312
+ for (const c of contacts) {
313
+ if (isAdvert(c) || c.lastFromThem !== false || !c.last) continue;
314
+ const waited = Math.floor((asOf - c.last) / DAY);
315
+ if (waited >= days) out.push({ contact: c, waited });
316
+ }
317
+ return out.sort((a, b) => a.waited - b.waited);
318
+ }
319
+
320
+ /** Follow-ups due: a next follow-up date of today or before. Oldest first. */
321
+ export function followUpsDue(contacts, today = localDay()) {
322
+ return [...contacts].filter((c) => c.crm?.followUp && c.crm.followUp <= today)
323
+ .sort((a, b) => a.crm.followUp.localeCompare(b.crm.followUp));
324
+ }
325
+
326
+ /** The pipeline: people you've given a stage, by stage, most recently in touch first. */
327
+ export function pipelineOf(contacts) {
328
+ const out = Object.fromEntries(STAGES.map((s) => [s.id, []]));
329
+ for (const c of contacts) if (c.crm?.stage) out[c.crm.stage].push(c);
330
+ for (const list of Object.values(out)) list.sort((a, b) => (b.lastContact || 0) - (a.lastContact || 0));
331
+ return out;
332
+ }
333
+
334
+ /**
335
+ * Sent: what went out from you, per person, newest first.
336
+ * invite a connection request (Invitations.csv, OUTGOING): accepted once
337
+ * they're a connection, pending until then
338
+ * request one the app tracked (Mark sent): the same
339
+ * awaiting a message with no reply for `days` or more (awaitingOf)
340
+ * Each: { contact, t, items: [{ type, t?, status?, waited?, id? }] }.
341
+ */
342
+ export function sentOf(contacts, asOf, days = AWAIT_DAYS) {
343
+ const list = [...contacts];
344
+ const waiting = new Map(awaitingOf(list, asOf, days).map((a) => [a.contact.key, a.waited]));
345
+ const out = [];
346
+ for (const c of list) {
347
+ const items = [];
348
+ for (const inv of c.invitesOut) items.push({ type: 'invite', id: inv.id, t: inv.t, status: c.connection ? 'accepted' : 'pending' });
349
+ if (c.request) items.push({ type: 'request', status: c.request.status });
350
+ if (waiting.has(c.key)) items.push({ type: 'awaiting', t: c.last, waited: waiting.get(c.key) });
351
+ if (!items.length) continue;
352
+ out.push({ contact: c, items, t: Math.max(0, ...items.map((i) => i.t || 0)) });
353
+ }
354
+ return out.sort((a, b) => b.t - a.t);
355
+ }
356
+
357
+ /** Received: requests to you from people who aren't connections yet (so you haven't accepted them). Newest first. */
358
+ export function receivedOf(contacts) {
359
+ const out = [];
360
+ for (const c of contacts) {
361
+ if (c.connection || !c.invitesIn.length) continue;
362
+ out.push({ contact: c, t: Math.max(0, ...c.invitesIn.map((i) => i.t || 0)) });
363
+ }
364
+ return out.sort((a, b) => b.t - a.t);
365
+ }
366
+
367
+ /**
368
+ * Search across names, companies, roles, tags and notes, and what was said
369
+ * when the messages are kept: `said` is the set of conversation ids whose
370
+ * kept messages match (GET /api/social/messages?q=).
371
+ */
372
+ export function matchesContact(c, q, said = null) {
373
+ const words = String(q || '').trim().toLowerCase();
374
+ if (!words) return true;
375
+ const hay = [c.name, c.row?.company, c.row?.role, c.row?.headline, ...(c.crm?.tags || []), c.crm?.notes]
376
+ .filter(Boolean).join('\n').toLowerCase();
377
+ return hay.includes(words) || (!!said && c.conversations.some((conv) => said.has(conv.id)));
378
+ }
379
+
380
+ // ── The CSV export ───────────────────────────────────────────────────────
381
+
382
+ /**
383
+ * One cell. Quoted when it holds a comma, a quote or a line break; and a
384
+ * leading =, +, - or @ gets a ' in front, so a spreadsheet shows it as text
385
+ * instead of running it as a formula (a note or a name is written by someone).
386
+ */
387
+ export function csvCell(v) {
388
+ let s = v == null ? '' : String(v);
389
+ if (/^[=+\-@\t\r]/.test(s)) s = `'${s}`;
390
+ return /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
391
+ }
392
+
393
+ const isoDay = (t) => (t ? new Date(t).toISOString().slice(0, 10) : '');
394
+
395
+ export const CSV_COLUMNS = ['Name', 'Company', 'Profile', 'Connection', 'Stage', 'Tags', 'Last contact', 'Who wrote last', 'Next follow-up', 'Notes'];
396
+
397
+ /** The CRM table as a CSV file's text: one row per contact, as Excel and Numbers read it (a BOM, CRLF). */
398
+ export function crmCsv(contacts) {
399
+ const lines = [CSV_COLUMNS.map(csvCell).join(',')];
400
+ for (const c of contacts) {
401
+ lines.push([
402
+ c.name,
403
+ c.row?.company || '',
404
+ c.profileUrl || '',
405
+ c.connection ? 'Yes' : 'No',
406
+ stageLabel(c.crm?.stage),
407
+ (c.crm?.tags || []).join('; '),
408
+ isoDay(c.lastContact),
409
+ c.lastFromThem == null ? '' : c.lastFromThem ? 'Them' : 'You',
410
+ c.crm?.followUp || '',
411
+ c.crm?.notes || '',
412
+ ].map(csvCell).join(','));
413
+ }
414
+ return `${lines.join('\r\n')}\r\n`;
415
+ }
@@ -4,13 +4,18 @@
4
4
  // messaged, when, who wrote last, the list of
5
5
  // conversations. Never any words.
6
6
  // social-messages-<profile>.json the messages themselves, only while Keep my
7
- // messages is on (app/api/social/messages).
7
+ // messages is on (app/api/social/messages),
8
+ // and the notes sent with requests, likewise.
9
+ // crm-<profile>.json your own CRM: each person's stage, tags,
10
+ // notes and next follow-up (app/api/social/crm,
11
+ // lib/social-crm.js). Always kept: you wrote
12
+ // it. Forget it offers to delete it, asking
13
+ // separately, so notes aren't lost by accident.
8
14
  //
9
15
  // Two files so that turning Keep my messages off, or Forget it, deletes the
10
16
  // words by deleting one file, and so the everyday file the Galaxy and the
11
- // Social tab read stays small however many messages there are. Neither is in
12
- // "Save a copy of my network": that carries an allow-list of files
13
- // (lib/data-folder.js TRAVELLING_FILES) and these aren't on it.
17
+ // Social tab read stays small however many messages there are. "Save a copy of my network" carries
18
+ // them when its Social box is ticked (lib/data-folder.js SOCIAL_FILE).
14
19
 
15
20
  import { readFileSync, writeFileSync, renameSync, rmSync, statSync } from 'node:fs';
16
21
  import path from 'node:path';
@@ -30,6 +35,7 @@ export function socialFiles() {
30
35
  profileId: me.id,
31
36
  social: path.join(dir, `social-${me.id}.json`),
32
37
  messages,
38
+ crm: path.join(dir, `crm-${me.id}.json`),
33
39
  // An import's messages arrive in parts (each request stays under the 10 MB
34
40
  // middleware reads); they gather here and replace `messages` at the end.
35
41
  incoming: `${messages}.incoming`,
@@ -48,17 +54,18 @@ export function writeJsonAtomic(file, value) {
48
54
  if (cache.file === file) cache.file = null;
49
55
  }
50
56
 
51
- /** The kept messages and their live-sync previews, or empty ones. */
57
+ /** The kept messages, their live-sync previews and the requests' notes, or empty ones. */
52
58
  export function readMessages(file) {
53
59
  // Kept in memory while the file is unchanged: a long history is read once,
54
60
  // not on every page of a thread or every letter typed in the search box.
55
61
  let st;
56
- try { st = statSync(file); } catch { return { threads: {}, previews: {} }; }
62
+ try { st = statSync(file); } catch { return { threads: {}, previews: {}, invites: {} }; }
57
63
  if (cache.file === file && cache.mtimeMs === st.mtimeMs && cache.size === st.size) return cache.data;
58
64
  const raw = readJson(file, {});
59
65
  const data = {
60
66
  threads: raw?.threads && typeof raw.threads === 'object' ? raw.threads : {},
61
67
  previews: raw?.previews && typeof raw.previews === 'object' ? raw.previews : {},
68
+ invites: raw?.invites && typeof raw.invites === 'object' ? raw.invites : {},
62
69
  };
63
70
  Object.assign(cache, { file, mtimeMs: st.mtimeMs, size: st.size, data });
64
71
  return data;
@@ -0,0 +1,99 @@
1
+ // The Filters panel's grid: five tiers down, six degrees across, the same in
2
+ // Network Circle and in Degrees.
3
+ //
4
+ // Blake, 2026-10-02: "keeping the tiers as is but enlarging them and having 6
5
+ // dots to let the user tap on to toggle or not if they want specific degrees
6
+ // showing for whatever tier … it will be universal for both".
7
+ //
8
+ // A grid is { hidden: [tiers switched off], degrees: { S: [1, 2], … } }: a tier
9
+ // row switches on and off as it always did, and remembers which degrees it
10
+ // shows while it's off. Counts are { S: { 1: n, 2: n, … }, … }: how many people
11
+ // there are at each tier and degree, so a dot with nobody behind it can't be
12
+ // tapped, and the last people on screen can't be switched off.
13
+
14
+ export const TIERS = ['S', 'A', 'B', 'C', 'D'];
15
+ export const GRID_DEGREES = [1, 2, 3, 4, 5, 6];
16
+
17
+ const tierOf = (tier) => (TIERS.includes(tier) ? tier : 'D'); // no tier: with D, as the views draw it
18
+
19
+ export const makeGrid = (degrees = GRID_DEGREES) => ({
20
+ hidden: [],
21
+ degrees: Object.fromEntries(TIERS.map((t) => [t, degrees.slice()])),
22
+ });
23
+
24
+ /** Network Circle starts with your own connections; you pick the rest. */
25
+ export const NETWORK_GRID = Object.freeze(makeGrid([1]));
26
+ /** Degrees starts with everything it has. */
27
+ export const DEGREES_GRID = Object.freeze(makeGrid());
28
+
29
+ /** Is someone of this tier, at this degree, on screen? */
30
+ export function shows(grid, tier, degree) {
31
+ const t = tierOf(tier);
32
+ return !grid.hidden.includes(t) && grid.degrees[t].includes(degree);
33
+ }
34
+
35
+ const countAt = (counts, tier, degree) => counts?.[tier]?.[degree] || 0;
36
+
37
+ /** How many people one tier is showing. */
38
+ export function showingIn(grid, counts, tier) {
39
+ if (grid.hidden.includes(tier)) return 0;
40
+ return grid.degrees[tier].reduce((n, d) => n + countAt(counts, tier, d), 0);
41
+ }
42
+
43
+ /** How many people the whole grid is showing. */
44
+ export const showing = (grid, counts) => TIERS.reduce((n, t) => n + showingIn(grid, counts, t), 0);
45
+
46
+ // A change that would leave nobody on screen isn't made.
47
+ const keep = (grid, next, counts) => (counts && showing(next, counts) === 0 ? grid : next);
48
+
49
+ /** One dot: that degree, for that tier. Tapping a dot in a tier that's off switches the tier on with it. */
50
+ export function toggleCell(grid, tier, degree, counts) {
51
+ const has = grid.degrees[tier].includes(degree);
52
+ const off = grid.hidden.includes(tier);
53
+ const degrees = off || !has
54
+ ? [...new Set([...grid.degrees[tier], degree])].sort((a, b) => a - b)
55
+ : grid.degrees[tier].filter((d) => d !== degree);
56
+ return keep(grid, {
57
+ hidden: off ? grid.hidden.filter((t) => t !== tier) : grid.hidden,
58
+ degrees: { ...grid.degrees, [tier]: degrees },
59
+ }, counts);
60
+ }
61
+
62
+ /** A whole tier, off or back on with the degrees it had. */
63
+ export function toggleTier(grid, tier, counts) {
64
+ if (!grid.hidden.includes(tier)) return keep(grid, { ...grid, hidden: [...grid.hidden, tier] }, counts);
65
+ // Back on with nothing to show would look broken: give it what the others show.
66
+ const own = grid.degrees[tier];
67
+ const others = [...new Set(TIERS.flatMap((t) => (t === tier ? [] : grid.degrees[t])))].sort((a, b) => a - b);
68
+ return {
69
+ hidden: grid.hidden.filter((t) => t !== tier),
70
+ degrees: own.length ? grid.degrees : { ...grid.degrees, [tier]: others.length ? others : [1] },
71
+ };
72
+ }
73
+
74
+ /** A whole degree: off if every tier with people there is showing it, otherwise on for all of them. */
75
+ export function toggleDegree(grid, degree, counts) {
76
+ const tiers = TIERS.filter((t) => !grid.hidden.includes(t) && (!counts || countAt(counts, t, degree) > 0));
77
+ if (!tiers.length) return grid;
78
+ const allOn = tiers.every((t) => grid.degrees[t].includes(degree));
79
+ const degrees = { ...grid.degrees };
80
+ for (const t of tiers) {
81
+ degrees[t] = allOn ? degrees[t].filter((d) => d !== degree) : [...new Set([...degrees[t], degree])].sort((a, b) => a - b);
82
+ }
83
+ return keep(grid, { ...grid, degrees }, counts);
84
+ }
85
+
86
+ /** Every tier back on, each with the degrees it had. */
87
+ export const showAllTiers = (grid) => (grid.hidden.length ? { ...grid, hidden: [] } : grid);
88
+
89
+ /** People by tier and degree, for the dots: `byDegree` is { 1: rows, 2: rows, … }. */
90
+ export function gridCounts(byDegree = {}) {
91
+ const counts = Object.fromEntries(TIERS.map((t) => [t, {}]));
92
+ for (const [degree, rows] of Object.entries(byDegree)) {
93
+ for (const row of rows || []) {
94
+ const t = tierOf(row?.tier);
95
+ counts[t][degree] = (counts[t][degree] || 0) + 1;
96
+ }
97
+ }
98
+ return counts;
99
+ }