six-degrees 0.3.0 → 0.4.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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +35 -7
- package/.next/standalone/.next/required-server-files.json +6 -1
- package/.next/standalone/.next/routes-manifest.json +18 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +13 -13
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/scores/page.js +13 -0
- package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/scores.html +1 -0
- package/.next/standalone/.next/server/app/scores.meta +26 -0
- package/.next/standalone/.next/server/app/scores.rsc +24 -0
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +13 -13
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +8 -6
- package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +13 -8
- package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +4 -0
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/prefetch-hints.json +14 -0
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
- package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/.next/standalone/CHANGELOG.md +286 -0
- package/.next/standalone/CONTRIBUTING.md +15 -4
- package/.next/standalone/README.md +50 -29
- package/.next/standalone/SECURITY.md +10 -0
- package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
- package/.next/standalone/app/api/company-scores/route.js +1 -1
- package/.next/standalone/app/api/ingest/route.js +23 -2
- package/.next/standalone/app/api/outreach/route.js +107 -44
- package/.next/standalone/app/api/scraper/route.js +146 -17
- package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
- package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
- package/.next/standalone/app/api/update-images/route.js +41 -7
- package/.next/standalone/app/avatars/[file]/route.js +6 -1
- package/.next/standalone/app/components/Avatar.js +5 -2
- package/.next/standalone/app/components/ChainView.js +659 -469
- package/.next/standalone/app/components/CompanyScores.js +16 -10
- package/.next/standalone/app/components/EmptyState.js +1 -1
- package/.next/standalone/app/components/FieldStep.js +94 -0
- package/.next/standalone/app/components/ForceGraph.js +183 -768
- package/.next/standalone/app/components/GridView.js +9 -7
- package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
- package/.next/standalone/app/components/LinkedInLimits.js +22 -7
- package/.next/standalone/app/components/ListView.js +6 -4
- package/.next/standalone/app/components/MappingProgress.js +5 -6
- package/.next/standalone/app/components/OnboardingGate.js +1 -1
- package/.next/standalone/app/components/OrbitGraph.js +33 -1
- package/.next/standalone/app/components/OutlinkQuest.js +5 -4
- package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
- package/.next/standalone/app/components/RingsView.js +9 -8
- package/.next/standalone/app/components/SeparationView.js +96 -14
- package/.next/standalone/app/components/Sidebar.js +366 -287
- package/.next/standalone/app/components/TheirCircle.js +300 -0
- package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
- package/.next/standalone/app/components/settings/SectorSection.js +21 -195
- package/.next/standalone/app/components/settings/TierSection.js +99 -0
- package/.next/standalone/app/components/useRequests.js +12 -0
- package/.next/standalone/app/components/useScanner.js +13 -0
- package/.next/standalone/app/components/views.js +3 -5
- package/.next/standalone/app/globals.css +7 -0
- package/.next/standalone/app/launch/page.js +6 -3
- package/.next/standalone/app/page.js +183 -74
- package/.next/standalone/app/paths/page.js +20 -14
- package/.next/standalone/app/profile/page.js +3 -3
- package/.next/standalone/app/queue/page.js +56 -60
- package/.next/standalone/app/scores/page.js +72 -0
- package/.next/standalone/app/settings/page.js +15 -3
- package/.next/standalone/app/setup/page.js +423 -260
- package/.next/standalone/db/schema.js +2 -1
- package/.next/standalone/eslint.config.mjs +3 -3
- package/.next/standalone/install.sh +3 -3
- package/.next/standalone/lib/chain-layout.js +139 -0
- package/.next/standalone/lib/circle.js +169 -0
- package/.next/standalone/lib/companies.js +1 -1
- package/.next/standalone/lib/csv.js +21 -10
- package/.next/standalone/lib/data-export.js +2 -1
- package/.next/standalone/lib/data-folder.js +4 -2
- package/.next/standalone/lib/db-client.js +1 -0
- package/.next/standalone/lib/galaxy.js +12 -0
- package/.next/standalone/lib/ingest.js +27 -8
- package/.next/standalone/lib/legacy-offer.js +7 -6
- package/.next/standalone/lib/legacy-scores.js +2 -2
- package/.next/standalone/lib/linkedin-limits.js +30 -8
- package/.next/standalone/lib/photos.js +47 -0
- package/.next/standalone/lib/quest.js +8 -3
- package/.next/standalone/lib/rarity.js +80 -0
- package/.next/standalone/lib/reach.js +113 -0
- package/.next/standalone/lib/requests-client.js +113 -0
- package/.next/standalone/lib/rpc.js +26 -15
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scanner-setup.js +20 -1
- package/.next/standalone/lib/score-guess.js +60 -0
- package/.next/standalone/lib/scoring.js +1761 -69
- package/.next/standalone/lib/scraper-client.js +235 -17
- package/.next/standalone/lib/sector-directory.js +1 -1
- package/.next/standalone/lib/sector-focus.js +17 -3
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/settings-client.js +21 -0
- package/.next/standalone/lib/settings-effects.js +19 -1
- package/.next/standalone/lib/settings.js +7 -2
- package/.next/standalone/next.config.mjs +25 -3
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/public/demo-data.json +1 -1
- package/.next/standalone/scripts/build-app.mjs +11 -2
- package/.next/standalone/scripts/check-node.mjs +1 -1
- package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
- package/.next/standalone/scripts/image_store.py +77 -21
- package/.next/standalone/scripts/scrape.py +305 -29
- package/.next/standalone/server.js +1 -1
- package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
- package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/CHANGELOG.md +286 -0
- package/README.md +50 -29
- package/SECURITY.md +10 -0
- package/db/schema.js +2 -1
- package/lib/chain-layout.js +139 -0
- package/lib/circle.js +169 -0
- package/lib/companies.js +1 -1
- package/lib/csv.js +21 -10
- package/lib/data-export.js +2 -1
- package/lib/data-folder.js +4 -2
- package/lib/db-client.js +1 -0
- package/lib/galaxy.js +12 -0
- package/lib/ingest.js +27 -8
- package/lib/legacy-offer.js +7 -6
- package/lib/legacy-scores.js +2 -2
- package/lib/linkedin-limits.js +30 -8
- package/lib/photos.js +47 -0
- package/lib/quest.js +8 -3
- package/lib/rarity.js +80 -0
- package/lib/reach.js +113 -0
- package/lib/requests-client.js +113 -0
- package/lib/rpc.js +26 -15
- package/lib/scan-state.js +1 -0
- package/lib/scanner-setup.js +20 -1
- package/lib/score-guess.js +60 -0
- package/lib/scoring.js +1761 -69
- package/lib/scraper-client.js +235 -17
- package/lib/sector-directory.js +1 -1
- package/lib/sector-focus.js +17 -3
- package/lib/separation.js +4 -2
- package/lib/settings-client.js +21 -0
- package/lib/settings-effects.js +19 -1
- package/lib/settings.js +7 -2
- package/package.json +2 -2
- package/public/demo-data.json +1 -1
- package/scripts/check-node.mjs +1 -1
- package/scripts/image_store.py +77 -21
- package/scripts/scrape.py +305 -29
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
- package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
- package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
- package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
- package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
- package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
- package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
- package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +0 -44
- package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
- package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
- package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
- package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
- package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
- package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
- package/.next/standalone/app/components/BridgeRing.js +0 -753
- package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
- package/.next/static/chunks/08k1crid-05dt.js +0 -1
- package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
- package/.next/static/chunks/0q-_khndfxubv.js +0 -2
- package/.next/static/chunks/1c79veqedvubt.js +0 -1
- package/.next/static/chunks/1chp3nx45meg5.js +0 -44
- package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
- package/.next/static/chunks/1uadqylrod5_v.js +0 -1
- package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
- package/.next/static/chunks/289ktj97di6zg.js +0 -1
- package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
- package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Every statement is CREATE ... IF NOT EXISTS, so applying it is both
|
|
10
10
|
// first-run setup and a no-op on every later boot.
|
|
11
11
|
|
|
12
|
-
export const SCHEMA_SQL = `--
|
|
12
|
+
export const SCHEMA_SQL = `-- Six Degrees — local SQLite schema.
|
|
13
13
|
--
|
|
14
14
|
-- Ported from the Postgres schema this project ran on before it went
|
|
15
15
|
-- local-first. Type mapping: uuid -> TEXT (generated in JS), jsonb and
|
|
@@ -60,6 +60,7 @@ CREATE TABLE IF NOT EXISTS linkedin_connections (
|
|
|
60
60
|
unlocked_from_bridge_id TEXT,
|
|
61
61
|
unlocked_from_name TEXT,
|
|
62
62
|
outreach_status TEXT,
|
|
63
|
+
mutual_count INTEGER, -- LinkedIn's count of mutual connections (2nd degree), when a scan read one
|
|
63
64
|
scanned_company TEXT,
|
|
64
65
|
score_why TEXT,
|
|
65
66
|
user_id TEXT,
|
|
@@ -23,9 +23,9 @@ const eslintConfig = defineConfig([
|
|
|
23
23
|
// surface in review.
|
|
24
24
|
"react-hooks/set-state-in-effect": "warn",
|
|
25
25
|
|
|
26
|
-
// Avatars are
|
|
27
|
-
//
|
|
28
|
-
//
|
|
26
|
+
// Avatars are small local files the scanner saved (lib/photos.js), with
|
|
27
|
+
// graceful initial-letter fallbacks. next/image would add a loader and
|
|
28
|
+
// per-request cost for no benefit here.
|
|
29
29
|
"@next/next/no-img-element": "off",
|
|
30
30
|
},
|
|
31
31
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Install
|
|
2
|
+
# Install Six Degrees on a Mac with one line:
|
|
3
3
|
#
|
|
4
4
|
# curl -fsSL https://raw.githubusercontent.com/blakeb056/six-degrees/main/install.sh | bash
|
|
5
5
|
#
|
|
@@ -108,7 +108,7 @@ stop_running_copy() {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
main() {
|
|
111
|
-
printf '\n
|
|
111
|
+
printf '\n Six Degrees — installer\n\n'
|
|
112
112
|
|
|
113
113
|
[ "$(uname -s)" = "Darwin" ] || fail "This installer is for macOS. On Linux, run: npx six-degrees (needs Node 22.13 or later). Windows isn't supported yet."
|
|
114
114
|
|
|
@@ -182,7 +182,7 @@ main() {
|
|
|
182
182
|
|
|
183
183
|
if [ -z "${SIX_DEGREES_NO_OPEN:-}" ]; then
|
|
184
184
|
open "$target"
|
|
185
|
-
printf '\n ✓ Opening
|
|
185
|
+
printf '\n ✓ Opening Six Degrees. Next time, open it from Applications or Spotlight.\n'
|
|
186
186
|
else
|
|
187
187
|
printf '\n ✓ Done.\n'
|
|
188
188
|
fi
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Where Bridge Chains puts a circle's dots.
|
|
2
|
+
//
|
|
3
|
+
// Blake, 2026-09-28: "when i scan someone and have a lot of 2nd degree its
|
|
4
|
+
// almost a solid line and needs to expand more". Every dot sat on one ring, so
|
|
5
|
+
// 200 people round one circle were a band and 800 were a solid line. Here a
|
|
6
|
+
// circle fills rings from the inside out instead, as many as its count needs:
|
|
7
|
+
// a small circle is one ring, a big one spreads into more, and neighbours keep
|
|
8
|
+
// their distance. Plain maths, no React; tests/chain-layout.test.mjs.
|
|
9
|
+
|
|
10
|
+
const TAU = Math.PI * 2;
|
|
11
|
+
|
|
12
|
+
/** How many dots fit on a ring of `radius` across `sweep` radians, `spacing` apart. */
|
|
13
|
+
function capacity(radius, spacing, sweep) {
|
|
14
|
+
if (radius <= 0) return 1;
|
|
15
|
+
const arc = sweep * radius;
|
|
16
|
+
// A whole circle closes on itself; an arc has a dot at each end.
|
|
17
|
+
return Math.max(1, sweep >= TAU - 1e-9 ? Math.floor(arc / spacing) : Math.floor(arc / spacing) + 1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The rings from `inner` outwards, `spacing` apart, until `n` dots fit. */
|
|
21
|
+
function ringsFor(n, inner, spacing, sweep) {
|
|
22
|
+
const radii = [];
|
|
23
|
+
let room = 0;
|
|
24
|
+
for (let r = inner; room < n; r += spacing) {
|
|
25
|
+
radii.push(r);
|
|
26
|
+
room += capacity(r, spacing, sweep);
|
|
27
|
+
}
|
|
28
|
+
return radii;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const outerOf = (n, inner, spacing, sweep) => {
|
|
32
|
+
const radii = ringsFor(n, inner, spacing, sweep);
|
|
33
|
+
return radii[radii.length - 1];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Places `n` dots in rings round a centre at (0, 0).
|
|
38
|
+
*
|
|
39
|
+
* The first ring sits at `inner`, and each next one `spacing` further out.
|
|
40
|
+
* When that would run past `outer`, the rings first start further in (down to
|
|
41
|
+
* `innerMin`), then close up (down to `minSpacing`); past that they carry on
|
|
42
|
+
* beyond `outer` rather than overlap, and zooming out shows them. The dots on
|
|
43
|
+
* each ring share it evenly, in proportion to its length, so every ring is
|
|
44
|
+
* about as full as the rest; the first `n` of a list sorted best-first land
|
|
45
|
+
* nearest the centre. Every other ring is turned by half a step, so dots on
|
|
46
|
+
* neighbouring rings don't line up in spokes.
|
|
47
|
+
*
|
|
48
|
+
* `start` and `sweep` fill an arc instead of the whole circle, each ring from
|
|
49
|
+
* its middle outwards.
|
|
50
|
+
*
|
|
51
|
+
* @returns {{ spacing: number, inner: number, rings: { radius: number, count: number }[],
|
|
52
|
+
* points: { x: number, y: number, angle: number, ring: number }[] }}
|
|
53
|
+
*/
|
|
54
|
+
export function ringLayout(n, {
|
|
55
|
+
inner, outer, spacing, minSpacing = spacing / 3, innerMin = inner,
|
|
56
|
+
start = -Math.PI / 2, sweep = TAU,
|
|
57
|
+
} = {}) {
|
|
58
|
+
const count = Math.max(0, Math.floor(n) || 0);
|
|
59
|
+
if (!count) return { spacing, inner, rings: [], points: [] };
|
|
60
|
+
|
|
61
|
+
let s = spacing;
|
|
62
|
+
let r0 = inner;
|
|
63
|
+
if (outerOf(count, r0, s, sweep) > outer) {
|
|
64
|
+
// Start further in, as far as innerMin, before closing anything up.
|
|
65
|
+
if (outerOf(count, innerMin, s, sweep) <= outer) {
|
|
66
|
+
let lo = innerMin, hi = inner;
|
|
67
|
+
for (let i = 0; i < 24; i++) {
|
|
68
|
+
const mid = (lo + hi) / 2;
|
|
69
|
+
if (outerOf(count, mid, s, sweep) <= outer) lo = mid; else hi = mid;
|
|
70
|
+
}
|
|
71
|
+
r0 = lo;
|
|
72
|
+
} else {
|
|
73
|
+
r0 = innerMin;
|
|
74
|
+
let lo = minSpacing, hi = spacing;
|
|
75
|
+
if (outerOf(count, r0, lo, sweep) <= outer) {
|
|
76
|
+
for (let i = 0; i < 24; i++) {
|
|
77
|
+
const mid = (lo + hi) / 2;
|
|
78
|
+
if (outerOf(count, r0, mid, sweep) <= outer) lo = mid; else hi = mid;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
s = lo;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const radii = ringsFor(count, r0, s, sweep);
|
|
86
|
+
const caps = radii.map((r) => capacity(r, s, sweep));
|
|
87
|
+
const room = caps.reduce((a, b) => a + b, 0);
|
|
88
|
+
|
|
89
|
+
// Share the dots in proportion to each ring's room, never over it; what the
|
|
90
|
+
// rounding leaves goes to the rings with the most room left.
|
|
91
|
+
const counts = caps.map((c) => Math.min(c, Math.floor((count * c) / room)));
|
|
92
|
+
let left = count - counts.reduce((a, b) => a + b, 0);
|
|
93
|
+
while (left > 0) {
|
|
94
|
+
let best = -1;
|
|
95
|
+
for (let k = 0; k < caps.length; k++) {
|
|
96
|
+
if (counts[k] < caps[k] && (best < 0 || caps[k] - counts[k] > caps[best] - counts[best])) best = k;
|
|
97
|
+
}
|
|
98
|
+
counts[best]++;
|
|
99
|
+
left--;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const whole = sweep >= TAU - 1e-9;
|
|
103
|
+
const points = [];
|
|
104
|
+
const rings = [];
|
|
105
|
+
radii.forEach((radius, k) => {
|
|
106
|
+
const m = counts[k];
|
|
107
|
+
if (!m) return;
|
|
108
|
+
rings.push({ radius, count: m });
|
|
109
|
+
// Round a whole circle evenly; across an arc from one edge to the other, a
|
|
110
|
+
// lone dot in the middle of it. An arc fills from its middle outwards, so
|
|
111
|
+
// the first in the list sit nearest whatever the arc fans out from.
|
|
112
|
+
const step = whole ? TAU / m : m > 1 ? sweep / (m - 1) : 0;
|
|
113
|
+
const from = whole ? start + (k % 2 ? step / 2 : 0) : m > 1 ? start : start + sweep / 2;
|
|
114
|
+
const slots = whole ? null : middleOut(m);
|
|
115
|
+
for (let j = 0; j < m; j++) {
|
|
116
|
+
const angle = from + (slots ? slots[j] : j) * step;
|
|
117
|
+
points.push({ x: radius * Math.cos(angle), y: radius * Math.sin(angle), angle, ring: rings.length - 1 });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return { spacing: s, inner: r0, rings, points };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 0 … m-1 from the middle outwards: for 5, 2 3 1 4 0. */
|
|
124
|
+
function middleOut(m) {
|
|
125
|
+
let lo = Math.floor((m - 1) / 2);
|
|
126
|
+
let hi = lo + 1;
|
|
127
|
+
const out = [lo--];
|
|
128
|
+
while (out.length < m) {
|
|
129
|
+
if (hi < m) out.push(hi++);
|
|
130
|
+
if (out.length < m && lo >= 0) out.push(lo--);
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A dot's radius for a layout's spacing: bigger for higher tiers, never touching a neighbour. */
|
|
136
|
+
export function dotRadius(spacing, tier) {
|
|
137
|
+
const scale = tier === 'S' ? 1 : tier === 'A' ? 0.86 : tier === 'B' ? 0.72 : 0.6;
|
|
138
|
+
return Math.max(1.4, Math.min(7, spacing * 0.42 * scale));
|
|
139
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Someone's circle as rings, for the top of their profile card.
|
|
2
|
+
//
|
|
3
|
+
// Blake, 2026-09-28: "for every profile card we can see their network circle
|
|
4
|
+
// … all of their current connections scraped and graded but then outside of
|
|
5
|
+
// that sits the connections all from d2 that we added and scanned coming from
|
|
6
|
+
// that person as the provider of the cluster … and then for d3,d4,d5,d6".
|
|
7
|
+
//
|
|
8
|
+
// Two facts the app already keeps make the whole chain, with nothing new stored:
|
|
9
|
+
// source_connection_id whose circle a row sits in: a scan of that person found it
|
|
10
|
+
// unlocked_from_bridge_id who introduced someone you connected with, kept
|
|
11
|
+
// after they became your connection (lib/promote.js)
|
|
12
|
+
// So from a person P: P's circle (D2, through P) → the people in it you
|
|
13
|
+
// connected with or asked → their circles (D3) → who you added from those →
|
|
14
|
+
// their circles (D4) … to six degrees. Everyone appears once, in the nearest
|
|
15
|
+
// ring they reach.
|
|
16
|
+
|
|
17
|
+
import { keyFor, score1 } from './separation.js';
|
|
18
|
+
|
|
19
|
+
export const MAX_DEGREE = 6;
|
|
20
|
+
|
|
21
|
+
/** A request is out to them and they haven't accepted (Sidebar's "Connect to Unlock Path"). */
|
|
22
|
+
export const requestedByDefault = (row) => row?.unlock_status === 'pending';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Rows grouped two ways: `circles`, id → the rows a scan of that person found;
|
|
26
|
+
* `introduced`, id → your connections you met through them.
|
|
27
|
+
*/
|
|
28
|
+
export function circleIndex(connections = [], degree2 = []) {
|
|
29
|
+
const circles = new Map();
|
|
30
|
+
for (const row of degree2 || []) {
|
|
31
|
+
const id = row?.source_connection_id;
|
|
32
|
+
if (id == null) continue;
|
|
33
|
+
let list = circles.get(id);
|
|
34
|
+
if (!list) circles.set(id, (list = []));
|
|
35
|
+
list.push(row);
|
|
36
|
+
}
|
|
37
|
+
const introduced = new Map();
|
|
38
|
+
for (const row of connections || []) {
|
|
39
|
+
const id = row?.unlocked_from_bridge_id;
|
|
40
|
+
if (id == null || row.degree !== 1) continue;
|
|
41
|
+
let list = introduced.get(id);
|
|
42
|
+
if (!list) introduced.set(id, (list = []));
|
|
43
|
+
list.push(row);
|
|
44
|
+
}
|
|
45
|
+
return { circles, introduced };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const STATE_ORDER = { connected: 0, requested: 1 };
|
|
49
|
+
const byStateThenPower = (a, b) => (STATE_ORDER[a.state] ?? 2) - (STATE_ORDER[b.state] ?? 2)
|
|
50
|
+
|| score1(b.row) - score1(a.row)
|
|
51
|
+
|| String(a.row.name || '').localeCompare(String(b.row.name || ''));
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The rings around `person`: [{ degree, people: [{ row, key, parentId, state }] }],
|
|
55
|
+
* degree 2 first. `state` is 'connected' (you added them, so their own circle
|
|
56
|
+
* is the next ring out), 'requested' (a request is out: drawn dotted) or null.
|
|
57
|
+
* Also the totals: { people, connected, requested }.
|
|
58
|
+
*/
|
|
59
|
+
export function circleRings(person, index, { maxDegree = MAX_DEGREE, isRequested = requestedByDefault } = {}) {
|
|
60
|
+
const rings = [];
|
|
61
|
+
const totals = { people: 0, connected: 0, requested: 0 };
|
|
62
|
+
if (!person || !index) return { rings, totals };
|
|
63
|
+
const seen = new Set([keyFor(person)]);
|
|
64
|
+
let frontier = [person];
|
|
65
|
+
for (let degree = 2; degree <= maxDegree && frontier.length; degree++) {
|
|
66
|
+
const ring = [];
|
|
67
|
+
const next = [];
|
|
68
|
+
for (const parent of frontier) {
|
|
69
|
+
const kids = [];
|
|
70
|
+
// The people you connected with first: they have circles of their own.
|
|
71
|
+
for (const row of index.introduced.get(parent.id) || []) {
|
|
72
|
+
const key = keyFor(row);
|
|
73
|
+
if (seen.has(key)) continue;
|
|
74
|
+
seen.add(key);
|
|
75
|
+
kids.push({ row, key, parentId: parent.id, state: 'connected' });
|
|
76
|
+
next.push(row);
|
|
77
|
+
}
|
|
78
|
+
for (const row of index.circles.get(parent.id) || []) {
|
|
79
|
+
const key = keyFor(row);
|
|
80
|
+
if (seen.has(key)) continue;
|
|
81
|
+
seen.add(key);
|
|
82
|
+
kids.push({ row, key, parentId: parent.id, state: isRequested(row) ? 'requested' : null });
|
|
83
|
+
}
|
|
84
|
+
kids.sort(byStateThenPower);
|
|
85
|
+
ring.push(...kids);
|
|
86
|
+
}
|
|
87
|
+
if (!ring.length) break;
|
|
88
|
+
for (const p of ring) {
|
|
89
|
+
totals.people++;
|
|
90
|
+
if (p.state === 'connected') totals.connected++;
|
|
91
|
+
else if (p.state === 'requested') totals.requested++;
|
|
92
|
+
}
|
|
93
|
+
rings.push({ degree, people: ring });
|
|
94
|
+
frontier = next;
|
|
95
|
+
}
|
|
96
|
+
return { rings, totals };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Where every dot goes, in a square `size` wide: a radial tree. Each person
|
|
103
|
+
* gets a slice of the circle in proportion to how much of the next rings
|
|
104
|
+
* hangs off them, so whoever opened the most reach shows it at a glance, and
|
|
105
|
+
* their circle fans out behind them.
|
|
106
|
+
* Returns { size, center, rings: [{ degree, radius, dotR, items }], spokes }.
|
|
107
|
+
*/
|
|
108
|
+
export function circleLayout(rings = [], size = 240) {
|
|
109
|
+
const half = size / 2;
|
|
110
|
+
const R = rings.length;
|
|
111
|
+
const radiusOf = (k) => half * (R === 1 ? 0.78 : 0.4 + 0.52 * (k / (R - 1)));
|
|
112
|
+
|
|
113
|
+
// Each ring's people by who they hang off, in the ring's order.
|
|
114
|
+
const byParent = rings.map((ring) => {
|
|
115
|
+
const m = new Map();
|
|
116
|
+
for (const p of ring.people) {
|
|
117
|
+
if (!m.has(p.parentId)) m.set(p.parentId, []);
|
|
118
|
+
m.get(p.parentId).push(p);
|
|
119
|
+
}
|
|
120
|
+
return m;
|
|
121
|
+
});
|
|
122
|
+
const kidsOf = (k, id) => byParent[k + 1]?.get(id) || [];
|
|
123
|
+
|
|
124
|
+
// Weights, outermost ring first: at least 1, else what hangs off them.
|
|
125
|
+
const weight = new Map();
|
|
126
|
+
for (let k = R - 1; k >= 0; k--) {
|
|
127
|
+
for (const p of rings[k].people) {
|
|
128
|
+
const w = p.state === 'connected' ? kidsOf(k, p.row.id).reduce((s, c) => s + weight.get(c), 0) : 0;
|
|
129
|
+
weight.set(p, Math.max(1, w));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const out = [];
|
|
134
|
+
const spokes = [];
|
|
135
|
+
const span = new Map(); // row id → [start, end] angle for its children
|
|
136
|
+
const START = -Math.PI / 2;
|
|
137
|
+
for (let k = 0; k < R; k++) {
|
|
138
|
+
const radius = radiusOf(k);
|
|
139
|
+
const people = rings[k].people;
|
|
140
|
+
const dotR = clamp((2 * Math.PI * radius) / Math.max(1, people.length) * 0.32, 0.8, size / 60);
|
|
141
|
+
const items = [];
|
|
142
|
+
for (const [parentId, group] of byParent[k]) {
|
|
143
|
+
const [a0, a1] = k === 0 ? [START, START + 2 * Math.PI] : span.get(parentId) || [START, START + 2 * Math.PI];
|
|
144
|
+
const total = group.reduce((s, p) => s + weight.get(p), 0);
|
|
145
|
+
let a = a0;
|
|
146
|
+
for (const p of group) {
|
|
147
|
+
const w = ((a1 - a0) * weight.get(p)) / total;
|
|
148
|
+
const angle = a + w / 2;
|
|
149
|
+
const x = half + radius * Math.cos(angle);
|
|
150
|
+
const y = half + radius * Math.sin(angle);
|
|
151
|
+
items.push({ ...p, angle, x, y });
|
|
152
|
+
if (p.state === 'connected') span.set(p.row.id, [a, a + w]);
|
|
153
|
+
a += w;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// A line from each person you added out to the middle of their own circle.
|
|
157
|
+
if (k + 1 < R) {
|
|
158
|
+
const nextR = radiusOf(k + 1);
|
|
159
|
+
for (const it of items) {
|
|
160
|
+
if (it.state !== 'connected' || !kidsOf(k, it.row.id).length) continue;
|
|
161
|
+
const [s, e] = span.get(it.row.id);
|
|
162
|
+
const mid = (s + e) / 2;
|
|
163
|
+
spokes.push({ x1: it.x, y1: it.y, x2: half + nextR * Math.cos(mid), y2: half + nextR * Math.sin(mid) });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
out.push({ degree: rings[k].degree, radius, dotR, items });
|
|
167
|
+
}
|
|
168
|
+
return { size, center: { x: half, y: half }, rings: out, spokes };
|
|
169
|
+
}
|
|
@@ -85,7 +85,7 @@ export const isSenior = (headline) => getSeniority(headline).level >= 4;
|
|
|
85
85
|
* A company's industry is the one scoring uses (lib/scoring.js
|
|
86
86
|
* networkCompanies over the same rows): the curated list's, else the name's,
|
|
87
87
|
* else what most of its people say. So a company's colour here is the sector
|
|
88
|
-
*
|
|
88
|
+
* Scores → Your sector leans on, and each company is the one scoring
|
|
89
89
|
* scores: both name it with cleanCompany().
|
|
90
90
|
*/
|
|
91
91
|
export function buildCompanyIndex(rows = []) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { scorePerson } from './scoring.js';
|
|
1
|
+
import { scorePerson, curveCutoffs, curvedTier } from './scoring.js';
|
|
2
|
+
import { toIsoDate } from './ingest.js';
|
|
2
3
|
// LinkedIn Connections.csv import — parsed ENTIRELY in the browser.
|
|
3
4
|
//
|
|
4
5
|
// Nothing is uploaded and nothing is written to the database: an imported
|
|
@@ -49,6 +50,20 @@ export function scoreRecord(headline, company) {
|
|
|
49
50
|
return { seniority_score: s.title.points, company_prestige_score: s.companyScore, power_score: s.power, tier: s.tier };
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Tiers for a whole import, graded on its own curve (lib/scoring.js CURVE):
|
|
55
|
+
* a CSV import uses the app's defaults, never your settings (no company
|
|
56
|
+
* scores, no sector), and the curve is the default, so the first look at any
|
|
57
|
+
* export has a top. Scores → Tiers says so while one is open. Run on the
|
|
58
|
+
* parse (the import page's counts) and on every load (the tab keeps only
|
|
59
|
+
* what the export says, packConnections), so the two always agree.
|
|
60
|
+
*/
|
|
61
|
+
function gradeOnCurve(connections) {
|
|
62
|
+
const cut = curveCutoffs(connections.map((c) => c.power_score));
|
|
63
|
+
for (const c of connections) c.tier = curvedTier(c.power_score, cut);
|
|
64
|
+
return connections;
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
// Stable id from the profile URL so re-importing keeps node identity.
|
|
53
68
|
function hashId(input) {
|
|
54
69
|
let h = 5381;
|
|
@@ -122,12 +137,9 @@ export function parseConnectionsCsv(text) {
|
|
|
122
137
|
if (seen.has(id)) { skipped++; continue; }
|
|
123
138
|
seen.add(id);
|
|
124
139
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const d = new Date(raw);
|
|
129
|
-
if (!isNaN(d.getTime())) connected_date = d.toISOString().split('T')[0];
|
|
130
|
-
}
|
|
140
|
+
// "28 Sep 2026", read as the calendar date it is. new Date(raw).toISOString()
|
|
141
|
+
// made it local midnight in UTC, a day early anywhere east of London.
|
|
142
|
+
const connected_date = toIsoDate(cell(iConnected));
|
|
131
143
|
|
|
132
144
|
connections.push(connectionFrom({ id, name, role, company, profileUrl, connected_date }));
|
|
133
145
|
}
|
|
@@ -138,7 +150,7 @@ export function parseConnectionsCsv(text) {
|
|
|
138
150
|
);
|
|
139
151
|
}
|
|
140
152
|
|
|
141
|
-
connections.sort((a, b) => b.power_score - a.power_score);
|
|
153
|
+
gradeOnCurve(connections).sort((a, b) => b.power_score - a.power_score);
|
|
142
154
|
return { connections, skipped, total: connections.length + skipped };
|
|
143
155
|
}
|
|
144
156
|
|
|
@@ -161,8 +173,7 @@ export function packConnections(connections) {
|
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
export function unpackConnections(rows = []) {
|
|
164
|
-
return rows
|
|
165
|
-
.map(([id, name, role, company, profileUrl, connected_date]) => connectionFrom({ id, name, role, company, profileUrl, connected_date }))
|
|
176
|
+
return gradeOnCurve(rows.map(([id, name, role, company, profileUrl, connected_date]) => connectionFrom({ id, name, role, company, profileUrl, connected_date })))
|
|
166
177
|
.sort((a, b) => b.power_score - a.power_score);
|
|
167
178
|
}
|
|
168
179
|
|
|
@@ -111,7 +111,8 @@ export function buildExport(db, { dir, outFile, includePhotos = true, appVersion
|
|
|
111
111
|
// file the other computer doesn't have: some views draw an empty circle
|
|
112
112
|
// for it. With no photo, they show initials, and the next scan of that
|
|
113
113
|
// person saves a new one. A photo not saved yet (LinkedIn's own link,
|
|
114
|
-
// which expires in a few weeks) is left as it is, as it is here
|
|
114
|
+
// which expires in a few weeks) is left as it is, as it is here: neither
|
|
115
|
+
// computer shows it, and either saves it once (lib/photos.js).
|
|
115
116
|
if (!includePhotos) {
|
|
116
117
|
out.exec("UPDATE linkedin_connections SET profile_image_url = NULL WHERE profile_image_url LIKE '/avatars/%'");
|
|
117
118
|
}
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
import { readdirSync, lstatSync, existsSync, rmSync } from 'node:fs';
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
+
import { AVATAR_FILE } from './photos.js';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
// A photo the /avatars route serves (lib/photos.js). An export carries and an
|
|
17
|
+
// import accepts exactly those names.
|
|
18
|
+
export { AVATAR_FILE };
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* The scanner's notes about the network itself: without them, people whose
|
|
@@ -108,6 +108,7 @@ export function applySchema(db) {
|
|
|
108
108
|
// exists, so columns added after first release are added here, once.
|
|
109
109
|
const ADDED_COLUMNS = [
|
|
110
110
|
['linkedin_connections', 'score_why', 'TEXT'],
|
|
111
|
+
['linkedin_connections', 'mutual_count', 'INTEGER'],
|
|
111
112
|
];
|
|
112
113
|
|
|
113
114
|
function addMissingColumns(db) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The Galaxy's view, as numbers, kept apart from the drawing so it can be tested
|
|
2
|
+
// (app/components/ForceGraph.js).
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The same view in a box of a new size. It moves by half the change, at the
|
|
6
|
+
* same zoom, so whatever was in the middle of the box is in the middle still.
|
|
7
|
+
* Opening a 320-pixel panel slides the view 160 pixels left, and closing it
|
|
8
|
+
* slides the view back to exactly where it was.
|
|
9
|
+
*/
|
|
10
|
+
export function recentre({ x, y, k }, from, to) {
|
|
11
|
+
return { x: x + (to.width - from.width) / 2, y: y + (to.height - from.height) / 2, k };
|
|
12
|
+
}
|
|
@@ -43,22 +43,29 @@ const MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july',
|
|
|
43
43
|
'august', 'september', 'october', 'november', 'december'];
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* A date as LinkedIn writes it → "2026-09-22": "September 22, 2026" (a profile's
|
|
47
|
+
* "Connected on …", read by the scanner) or "22 Sep 2026" (the "Connected On"
|
|
48
|
+
* column of the Connections.csv export, read by lib/csv.js).
|
|
47
49
|
*
|
|
48
50
|
* Parsed by hand rather than with new Date(text).toISOString(): that reads the
|
|
49
51
|
* text as local midnight and converts to UTC, which moves the date back a day
|
|
50
52
|
* anywhere east of Greenwich — and throws outright on text it cannot read.
|
|
51
|
-
*
|
|
53
|
+
* A calendar date has no time zone, so nothing here uses one.
|
|
54
|
+
* Returns null for anything it does not recognise, or a day the month doesn't have.
|
|
52
55
|
*/
|
|
53
56
|
export function toIsoDate(text) {
|
|
54
57
|
const t = String(text || '').trim();
|
|
55
58
|
if (/^\d{4}-\d{2}-\d{2}$/.test(t)) return t;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
let name, day, year;
|
|
60
|
+
let m = t.match(/^([A-Za-z]+)\.?\s+(\d{1,2}),?\s+(\d{4})$/); // September 22, 2026
|
|
61
|
+
if (m) [, name, day, year] = m;
|
|
62
|
+
else if ((m = t.match(/^(\d{1,2})\s+([A-Za-z]+)\.?\s+(\d{4})$/))) [, day, name, year] = m; // 22 Sep 2026
|
|
63
|
+
else return null;
|
|
64
|
+
const month = MONTHS.findIndex((full) => full.startsWith(name.toLowerCase()) && name.length >= 3);
|
|
65
|
+
day = Number(day);
|
|
66
|
+
// Day 0 of the next month is this month's last, counted in UTC so no zone moves it.
|
|
67
|
+
if (month < 0 || day < 1 || day > new Date(Date.UTC(Number(year), month + 1, 0)).getUTCDate()) return null;
|
|
68
|
+
return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
// Tiers that make a new connection worth a notification of its own.
|
|
@@ -100,3 +107,15 @@ export function refreshNotifications({ added = [], checked = 0, tierOf = () => n
|
|
|
100
107
|
}
|
|
101
108
|
return out;
|
|
102
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* LinkedIn's own count of the mutual connections you share with someone, as
|
|
113
|
+
* the scanner read it off their result card (scripts/scrape.py
|
|
114
|
+
* BRIDGE_RESULTS_JS), or null. A whole number from 1 (the bridge is always
|
|
115
|
+
* one) to 30,000 (LinkedIn's cap on connections); anything else is a misread
|
|
116
|
+
* and is dropped, never stored.
|
|
117
|
+
*/
|
|
118
|
+
export function mutualCountOf(value) {
|
|
119
|
+
const n = typeof value === 'string' && /^\d[\d,]*$/.test(value.trim()) ? Number(value.replace(/,/g, '')) : value;
|
|
120
|
+
return Number.isInteger(n) && n >= 1 && n <= 30000 ? n : null;
|
|
121
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// The Scores tab: the one-time offer to keep the curated list's old scores as
|
|
2
2
|
// your own, after the list was made neutral (lib/legacy-scores.js has the old
|
|
3
3
|
// scores and why each changed).
|
|
4
4
|
//
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// scored on the server, so the page doesn't ask while one is open.
|
|
11
11
|
//
|
|
12
12
|
// Keeping writes the old score under every name scoring uses for the entry's
|
|
13
|
-
// companies, as
|
|
13
|
+
// companies, as the Scores tab sets a score (setCompanyScores), then rescores
|
|
14
14
|
// everyone once for the whole batch. Keep or No thanks, it is never offered
|
|
15
15
|
// again, and an offer whose first look finds nothing to offer closes itself.
|
|
16
16
|
|
|
@@ -62,14 +62,14 @@ export function legacyOfferState(db = getDb()) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
* What
|
|
65
|
+
* What the Scores tab offers, or null: nothing when the offer isn't open
|
|
66
66
|
* (never was, or was answered) or no company in the network lost an old score
|
|
67
67
|
* that you haven't replaced with one of your own. That last closes it for
|
|
68
68
|
* good ('none'): the network the old list scored is the one read here, so
|
|
69
|
-
* nothing could turn up later, and every visit to
|
|
69
|
+
* nothing could turn up later, and every visit to the Scores tab would
|
|
70
70
|
* otherwise read the whole network again to find nothing.
|
|
71
71
|
*
|
|
72
|
-
* The companies are the ones
|
|
72
|
+
* The companies are the ones the Scores tab lists, those someone works at now,
|
|
73
73
|
* so a kept score shows there as yours and Auto can hand it back. (A name only
|
|
74
74
|
* in former roles would be a score nothing lists; its people's former role
|
|
75
75
|
* counts at 70% and moves them little.) Each is compared by its built-in score
|
|
@@ -116,7 +116,8 @@ export function legacyOffer(db = getDb()) {
|
|
|
116
116
|
name: o.name,
|
|
117
117
|
was: o.was,
|
|
118
118
|
now: main.now.score,
|
|
119
|
-
|
|
119
|
+
// A score from the curated list or the public dataset is a fact, not an estimate.
|
|
120
|
+
estimated: !['known', 'data'].includes(main.now.source),
|
|
120
121
|
people: o.people.size,
|
|
121
122
|
names: o.names.map((n) => n.name).sort(),
|
|
122
123
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The curated company list's scores from before it was made neutral, kept for
|
|
2
|
-
// one thing only: the one-time offer on
|
|
2
|
+
// one thing only: the one-time offer on the Scores tab to keep them as your
|
|
3
3
|
// own (lib/legacy-offer.js). Nothing scores with these. lib/scoring.js never
|
|
4
4
|
// imports this file, and the model must never read it.
|
|
5
5
|
//
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
export const LEGACY_SCORES = [
|
|
26
26
|
// Rescored: scored like its peers Pinterest, Reddit and X.
|
|
27
|
-
['Snap', 9, /^(snap|snapchat)( inc)?$/, 'media'],
|
|
27
|
+
['Snap', 9, /^(snap|snapchat)( inc)?( (specs|spectacles|(mena|emea|apac|latam|north america)( region)?))?$/, 'media'],
|
|
28
28
|
// Removed: private companies that are neither household names nor Fortune
|
|
29
29
|
// 500-sized, and small startups.
|
|
30
30
|
['Anduril', 9, /^anduril( industries)?$/, 'defense'],
|