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
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,292 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-09-28
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **A cap on profile views.** A circle scan opens the person's profile once, and profile
|
|
13
|
+
views are what LinkedIn restricted an account for. They now have a cap of their own: 50
|
|
14
|
+
in any 24 hours by default. The Scan page offers 10, 25, 50 or 100, and there is no "no
|
|
15
|
+
limit".
|
|
16
|
+
- **A minute apart:** at least 60 seconds pass between any two profile opens. It's timed
|
|
17
|
+
from the last one written down, so scans started back to back can't open profiles back
|
|
18
|
+
to back. The log counts down while it waits, and Stop ends the wait.
|
|
19
|
+
- **Checked before the profile opens:** at the cap, a scan opens nothing and records
|
|
20
|
+
nothing about that person. Auto-Bridge stops, as it does at the search budget, and the
|
|
21
|
+
next run starts with the same person.
|
|
22
|
+
- **On the Scan page:** "3 of 50 profile views today", with a bar and the picker.
|
|
23
|
+
- **Re-mapping someone** checks it before deleting their circle. A damaged record counts
|
|
24
|
+
the day's views as used. An import brings the other computer's setting only when this
|
|
25
|
+
one has none, like the search budget.
|
|
26
|
+
- **The Scan page asks what field you're in, before your first scan.** One optional step,
|
|
27
|
+
with the same picks as *Scores → Your sector*: tech, government and defense, dental, and
|
|
28
|
+
the rest. Companies in your field count for more, so your first scores already use it.
|
|
29
|
+
*Skip for now* carries on without one. Both stay at the bottom of the window while you
|
|
30
|
+
look through the list. It's asked once, and never if you already have a network or picked
|
|
31
|
+
a sector. Change it anytime on the Scores tab.
|
|
32
|
+
- **Bridge Chains opens any circle, as far as your scans reach.** Click someone in a
|
|
33
|
+
bridge's circle and their own circle opens in place: the people found in it once you
|
|
34
|
+
connected and scanned it (3rd degree, counted along the chain), and on from there. A
|
|
35
|
+
trail at the top, and Esc, go back. An empty circle says why and what gets it: connect,
|
|
36
|
+
then scan their circle. While one is being scanned, it fills in as the scan saves.
|
|
37
|
+
Someone ready for a scan goes to the Scan page with them picked instead, since their
|
|
38
|
+
circle is empty until then.
|
|
39
|
+
- **A soft glow on people ready for a scan.** In Bridge Chains, someone you added through
|
|
40
|
+
a circle whose own circle isn't scanned yet has a soft breathing halo, and their bridge
|
|
41
|
+
says how many are "ready". A hidden list is greyed with a lock instead. The halo stays
|
|
42
|
+
still with Reduce Motion on. The old rainbow glow is gone: it looked for them inside the
|
|
43
|
+
circle they came from, which accepting takes them out of, so it never showed.
|
|
44
|
+
- **Ready to scan (N), in the Degrees panel.** It replaces *Auto-Bridge Next*, which
|
|
45
|
+
started a scan in one click, by name, with no cost shown, and offered hidden lists again
|
|
46
|
+
and again. The list is everyone you added through a circle whose circle can be scanned,
|
|
47
|
+
strongest first, with the circle you found them in and what a scan costs. Hidden lists
|
|
48
|
+
stay in view, greyed with a lock.
|
|
49
|
+
- **Scan one circle, on the Scan page.** A ready person, from the list or Bridge Chains,
|
|
50
|
+
opens the Scan page with them picked: the cost (a profile view, then a search a page, up
|
|
51
|
+
to 100), what's left of the day's budget, how deep to read, and a button. Nothing starts
|
|
52
|
+
until it's pressed.
|
|
53
|
+
- **A release checks the website's version lines.** Before building anything, the release
|
|
54
|
+
workflow runs `scripts/check-site-version.mjs`: the site's `softwareVersion` and
|
|
55
|
+
`llms.txt` must name the version, and its `dateModified` and sitemap `lastmod` can't be
|
|
56
|
+
older than the release's date in the changelog. A stale site stops the release with a
|
|
57
|
+
line for each thing to change. A pre-release skips it. Anyone can run it before tagging.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
- **A big circle spreads out.** A bridge's circle used to sit on one ring, so a few hundred
|
|
61
|
+
people were almost a solid line. It now fills rings from the inside out, as many as it
|
|
62
|
+
needs, with the highest tiers nearest the middle; drag to move, scroll or +/− to zoom.
|
|
63
|
+
Lots of bridges spread the same way, and so does the preview on hover.
|
|
64
|
+
- **The map picks up a finished scan by itself.** No refresh needed.
|
|
65
|
+
- **One rule for "ready".** Bridge Chains, the Degrees panel, Outlink's new doors and the
|
|
66
|
+
profile page's mapping bar now agree on who can be scanned next, and all leave hidden
|
|
67
|
+
lists out.
|
|
68
|
+
- **Photos stay on your Mac.** The app shows a profile photo only from the copy saved on
|
|
69
|
+
your computer, so looking at your network never contacts LinkedIn. It used to load a
|
|
70
|
+
photo from LinkedIn until a scan had saved it, and a rescan could put LinkedIn's link
|
|
71
|
+
back in place of a saved photo. Every page now also tells the browser to load pictures
|
|
72
|
+
from the app alone, and the scanner fetches photos from LinkedIn's image servers only.
|
|
73
|
+
- **Photos an older version kept as links are saved once.** Until then those people show
|
|
74
|
+
initials. The next scan saves them at its end, or *Save photos* on the Scan page does it
|
|
75
|
+
now, without opening a browser. A link more than a few weeks old has expired: that
|
|
76
|
+
person's photo comes back when they're next scanned. Offline, or with LinkedIn's image
|
|
77
|
+
server busy, nothing is forgotten, and *Save photos* stops and says why.
|
|
78
|
+
- **Government and military titles are read.** A senator, a governor, a mayor and a cabinet
|
|
79
|
+
secretary score like a C-suite; their deputies, state legislators, commissioners,
|
|
80
|
+
ambassadors and judges like a VP; a city councilmember or a sheriff like a director.
|
|
81
|
+
Officers by rank: a 3–4 star general or admiral 10, a 1–2 star 9, a colonel 7.5, a
|
|
82
|
+
lieutenant colonel 6.5, a major 5, and a commander by their unit. "(Ret.)" and
|
|
83
|
+
"Retired" make a role former. They count only where a title is written, and only where
|
|
84
|
+
their own organization is a government's or a service's: a staffer in an Office of the
|
|
85
|
+
Secretary, a veteran's civilian job, a city manager at Uber, a Rotary district governor,
|
|
86
|
+
a team captain or a Kentucky colonel isn't one. A title that doesn't say whose it is
|
|
87
|
+
("Mayor", "Police Chief", "Ambassador") needs its government named, or stays "Title
|
|
88
|
+
unclear": a missed official is a neutral 4, a realtor read as a mayor would be an A.
|
|
89
|
+
Until now all of them were "Title unclear" (a U.S. senator scored 2.9, C).
|
|
90
|
+
- **One name: Six Degrees.** The header, the welcome screen, the loading and launch pages,
|
|
91
|
+
the side panel and the Terminal installer said "6 Degrees". They now say Six Degrees, as
|
|
92
|
+
the window title and the app in Applications already did.
|
|
93
|
+
- **Contributor docs and `.gitignore`.** CONTRIBUTING says npm releases go out through
|
|
94
|
+
trusted publishing, with no token to set, and that `npm run build:desktop` builds the app
|
|
95
|
+
releases ship (`build:app` is the old launcher). `.gitignore` keeps a LinkedIn export, a
|
|
96
|
+
database, a saved copy of a network and the scanner's signed-in browser profile out of a
|
|
97
|
+
commit, wherever they sit in the tree. A local build keeps them out of the app too, and
|
|
98
|
+
its list of uncommitted files inside the app now names ignored ones as well.
|
|
99
|
+
- **0.4.0-beta.1, promoted: this is now the Mac app and npm package everyone gets.** It adds:
|
|
100
|
+
- **Their circle, on every card,** with rarity beside the tier and LinkedIn's own mutual count.
|
|
101
|
+
- **One scan at a time,** and *Resume* beside *Rescan*.
|
|
102
|
+
- **Tiers on your own network's curve,** with unknown companies at a neutral 5.
|
|
103
|
+
- **631 public organizations** the built-in list leaves off, and **scoring 6.**
|
|
104
|
+
- **Scores, a tab of its own.**
|
|
105
|
+
|
|
106
|
+
The details are under 0.4.0-beta.1 below.
|
|
107
|
+
- **The download page** says what's new in 0.4.0.
|
|
108
|
+
- **Degrees' corner toggle says Orbit, which is what it shows.** It said "Galaxy", but
|
|
109
|
+
the Galaxy is a Network Circle view, so Degrees fell back to Orbit. It switches
|
|
110
|
+
between Orbit and Bridge Chains, and it's on whenever Orbit is showing.
|
|
111
|
+
|
|
112
|
+
### Removed
|
|
113
|
+
- **About 630 lines of Galaxy code that never ran:** a Degrees drawing from before Orbit
|
|
114
|
+
and Bridge Chains. Nothing could open it. The Galaxy draws Network Circle only.
|
|
115
|
+
|
|
116
|
+
### Fixed
|
|
117
|
+
- **A one-person scan notes a hidden list.** A card's Scan or Rescan that found someone's
|
|
118
|
+
list hidden said so and noted nothing, so they stayed "not scanned yet" and were offered
|
|
119
|
+
again forever. Only a batch noted it. Now every read from page 1 does.
|
|
120
|
+
- **A scan reads the list of the person you picked.** A card's Scan and Rescan went by
|
|
121
|
+
name, so with two connections of the same name the scanner could read the other one's
|
|
122
|
+
list. Every Scan button now goes by that person's profile, as Resume already did.
|
|
123
|
+
- **A company's name after "at" is no longer read as a title.** "Server at President Hotel"
|
|
124
|
+
scored as a C-suite (7.3, A), and so did an IT specialist in an "Office of the Chief
|
|
125
|
+
Information Officer". "Executive Assistant to the General Manager" scored as a VP.
|
|
126
|
+
- **↻ asks before it scans.** The round button in the header started *Check for new* the
|
|
127
|
+
moment it was clicked. It now says what it will do (open Chrome on your connections list
|
|
128
|
+
and read it until it reaches people already saved) and what it costs (no search budget,
|
|
129
|
+
but it is LinkedIn traffic from your account), and waits for OK.
|
|
130
|
+
- **No Scan buttons on the sample or a CSV import.** Every card there offered a scan of
|
|
131
|
+
that person's circle, and Degrees offered *Auto-Bridge Next*, but both networks live
|
|
132
|
+
only in the window, so the scan could only fail ("Bridge '…' not found in database").
|
|
133
|
+
In their place a line says scanning needs your own network.
|
|
134
|
+
- **A CSV import's dates are the day LinkedIn says.** Anywhere east of London, each "Connected
|
|
135
|
+
On" date landed a day early (28 Sep 2026 became 27 Sep). The import now reads it as the
|
|
136
|
+
calendar date it is, the way a scan already did, and a date it can't read is left blank
|
|
137
|
+
rather than guessed.
|
|
138
|
+
- **Opening a panel no longer rebuilds the Galaxy.** Opening or closing the side panel or
|
|
139
|
+
the Filter panel used to draw the Galaxy again from scratch: the layout started over,
|
|
140
|
+
the selection ring vanished, and a big network stalled the page. Now the view slides
|
|
141
|
+
over at the same zoom, so what was in the middle stays in the middle.
|
|
142
|
+
- **The selection ring stays on the person you picked.** It is tied to their dot, and it
|
|
143
|
+
comes back after a tier filter that still shows them. It used to follow the first dot
|
|
144
|
+
it found near its old spot, which could be someone else, even You. *Back to list*
|
|
145
|
+
clears it.
|
|
146
|
+
- **The selection ring pulses gently, as it was always meant to.** Its animation had only
|
|
147
|
+
ever been written in code that never ran. With Reduce Motion on, it holds still.
|
|
148
|
+
- **Hovering a dot eases it up to 1.5 times its size,** instead of jumping. With Reduce
|
|
149
|
+
Motion on, it still changes at once.
|
|
150
|
+
- **The Galaxy draws faster on a big network.** Colouring its lines searched every person
|
|
151
|
+
for every line: about 1.4 s at 30,000 people, on every draw. It now takes under a
|
|
152
|
+
millisecond.
|
|
153
|
+
- **The main screen no longer stalls when nothing is selected on a big network.** The
|
|
154
|
+
side panel's Power Rankings counted every tier again for each person in the list, even
|
|
155
|
+
with the panel closed. At 30,000 people that took about 20 s each time the screen
|
|
156
|
+
redrew, opening the Filter panel included.
|
|
157
|
+
- **On a laptop, the Galaxy keeps its usual layout with a panel open.** Its fixed phone
|
|
158
|
+
layout used to switch on whenever the graph was under 768 pixels wide, which a tier
|
|
159
|
+
filter picked with a panel open could do. It now follows the window, as the page does.
|
|
160
|
+
|
|
161
|
+
## [0.4.0-beta.1] - 2026-09-28 (beta: a pre-release, never installed automatically)
|
|
162
|
+
|
|
163
|
+
### Added
|
|
164
|
+
- **Their circle, on every card.** Open one of your connections and the top of their card
|
|
165
|
+
is a small map of their circle. Their scanned connections sit round them (D2). Anyone
|
|
166
|
+
you connected with from it gets a green ring, and their own circle fans out behind
|
|
167
|
+
them (D3), on out to D6. A request you've sent and they haven't accepted is a dotted
|
|
168
|
+
dot. Tap the map for the large one, where any dot opens that person. It's built from
|
|
169
|
+
what the app already keeps (whose circle someone was found in, and who introduced
|
|
170
|
+
you), so it needs no extra scanning. Not scanned yet? It says so, and points at the
|
|
171
|
+
scan below.
|
|
172
|
+
- **Rarity, beside the tier.** How many mutual connections lead to someone, in five
|
|
173
|
+
bands: *Only way in* (1), *Rare* (2–3), *Uncommon* (4–10), *Common* (11–30) and
|
|
174
|
+
*Warm* (31+). It's a distinction, never a score: tiers and power are untouched.
|
|
175
|
+
Rarity and the tier filter together, in the circle and in Separation, so "S" and
|
|
176
|
+
"Only way in" is exactly the rare finds, and "S" and "Warm" the easy wins. Until a
|
|
177
|
+
scan saves LinkedIn's own count, it's counted from the circles you've scanned, which
|
|
178
|
+
can only go up as you scan more, and everywhere it shows says so.
|
|
179
|
+
- **Resume beside Rescan on a profile card.** When someone's list was only partly read (a
|
|
180
|
+
page limit, a stop, LinkedIn pushing back), their card offers *Resume from page N* next
|
|
181
|
+
to *Rescan from the start*: the same carry-on as the Scan page's Paused list, for that
|
|
182
|
+
one person. LinkedIn lists other people's connections in its own order, with no dates,
|
|
183
|
+
so anyone new can be on any page: Rescan reads the whole list again from page 1, and
|
|
184
|
+
Resume picks up where the last read stopped. A list read to the end says so instead.
|
|
185
|
+
- **LinkedIn's own mutual count, from circle scans.** Everyone a circle scan finds now
|
|
186
|
+
carries LinkedIn's count of the mutual connections you share with them, read from the
|
|
187
|
+
line under their result card ("Maya Chen and 23 other mutual connections" is 24).
|
|
188
|
+
- **No extra page views:** the line was already on the pages the scanner reads.
|
|
189
|
+
- **One count per person:** every copy of the person, one per bridge that knows them,
|
|
190
|
+
keeps the newest count read. A scan that doesn't see the line leaves the count alone.
|
|
191
|
+
- **Used by rarity** in place of the count from your scans, which could only be a floor.
|
|
192
|
+
- **Not yet tried against live LinkedIn,** and it reads English wording only. The first
|
|
193
|
+
real circle scan is the check. Any other wording reads as no count, never a wrong one.
|
|
194
|
+
- **A public company dataset: 631 organizations the built-in list leaves off,** scored 6
|
|
195
|
+
to 8 from public facts on one published scale, each with its source beside it (an exchange
|
|
196
|
+
listing, a reported valuation, a U.S. News rank, a federal department's size).
|
|
197
|
+
- **The Fortune 500 (2025):** 376 companies, and the brands their staff write as their
|
|
198
|
+
employer (KeyBank, Alaska Airlines, VMware, The Wall Street Journal…).
|
|
199
|
+
- **The U.S. federal government:** 114 entries. The departments and military branches
|
|
200
|
+
are 8; agencies and national labs 7. Each is matched only by forms that say which one
|
|
201
|
+
it is: "US Army", never "Army" alone.
|
|
202
|
+
- **Universities ranked 21–100 by U.S. News:** 77 of them.
|
|
203
|
+
- **64 more, researched one by one,** among them Anduril, CoreWeave, MLB, WWE, UF and UCF.
|
|
204
|
+
|
|
205
|
+
Someone there is scored from those facts, not as an unknown company, and the Scores tab
|
|
206
|
+
labels the score *public data*. The list is built from public sources only, never from
|
|
207
|
+
anyone's scan, and grows each release. The built-in list itself is unchanged: household
|
|
208
|
+
names only, 7 and up.
|
|
209
|
+
- **A card says when a score is a guess, and what would firm it up.** When someone's headline
|
|
210
|
+
gives the app no title to read, or no company it knows, the top of their card says so
|
|
211
|
+
("Their score is a guess. The app couldn't find where they work.") and offers *Scan their
|
|
212
|
+
circle*, which brings the scan button into view. Who you can reach through them is what the
|
|
213
|
+
score can read instead, and a strong circle adds up to +2.
|
|
214
|
+
|
|
215
|
+
### Fixed
|
|
216
|
+
- **A request you send shows everywhere, at once.**
|
|
217
|
+
- **Before:** *Connect to Unlock Path* marked one bridge's copy of the person and told
|
|
218
|
+
nothing else on the page.
|
|
219
|
+
- The card kept offering the link.
|
|
220
|
+
- Separation, Orbit and the circle never showed it, even after a reload.
|
|
221
|
+
- Every click awarded XP again.
|
|
222
|
+
- Undo took back only half of it.
|
|
223
|
+
- **Now:** a request belongs to the person, in one list every view reads, so the
|
|
224
|
+
card, Separation, Orbit, the new circle, the Outlink queue and the Pending badge
|
|
225
|
+
all change the moment you click.
|
|
226
|
+
- **The details:**
|
|
227
|
+
- The bridge whose circle you found them in is kept, so once they accept, their
|
|
228
|
+
card and circle say who introduced you.
|
|
229
|
+
- XP comes once per person.
|
|
230
|
+
- *Didn't send it? Undo* on the card takes all of it back, the XP too.
|
|
231
|
+
- **One scan at a time, and every Scan button knows it.** A scan started on one profile
|
|
232
|
+
card left the Scan button on every other card clickable, and *Auto-Bridge Next*, the ↻
|
|
233
|
+
refresh and Paths' company scan too. Pressing one was refused, and the card then said
|
|
234
|
+
*"Scan failed — connections may be private"* about a scan that had never started. Now
|
|
235
|
+
they all grey out while anything runs and say what does ("Ada Park's circle is being
|
|
236
|
+
scanned. One scan at a time…"), even a scan started on the Scan page or in another
|
|
237
|
+
window. Close a card mid-scan and open it again and its progress is still there; when it
|
|
238
|
+
ends, the card says so. The page asks the scanner once for all of them
|
|
239
|
+
(`/api/scraper?job=1`, which reads only memory), not once per button.
|
|
240
|
+
- **Company names written in styled letters or with a logo are read.** "𝗠𝗶𝗰𝗿𝗼𝘀𝗼𝗳𝘁",
|
|
241
|
+
"Apple " (with Apple's logo) and "Snapchat|ex-L'Oréal" (a full-width bar) used to read
|
|
242
|
+
as unknown companies. So did "Meta Superintelligence Labs" and "Snapchat MENA Region",
|
|
243
|
+
and a class year ("UCF ’26") hid the school.
|
|
244
|
+
- **"President's Club" is a sales award, not a president.** A title's possessive read as
|
|
245
|
+
the title, so "Account Executive at Oracle | 3x President's Club" scored 7.3 (A) as a
|
|
246
|
+
C-suite, and so did "Chairman's Award" and "Chief of Staff, CEO's Office". It's the
|
|
247
|
+
title's own: 3.6 (C), and a chief of staff is a director again (5.4, B).
|
|
248
|
+
|
|
249
|
+
### Changed
|
|
250
|
+
- **Tiers are graded on your own network's curve** (new: *Scores → Tiers*, and the default).
|
|
251
|
+
Your top 3% of connections are S, the next 12% A, the next 25% B and the next 30% C, so
|
|
252
|
+
the first scan of any network has a top, not only a network full of companies the app
|
|
253
|
+
knows. The curve only lifts: nobody drops a tier because their network is strong, and
|
|
254
|
+
nobody under 4 is lifted into S or A. People tied at a line all come in or all stay out.
|
|
255
|
+
Choose *On the fixed scale* for the lines as they were (S ≥ 7.5, A ≥ 5.5, B ≥ 4, C ≥ 2.5).
|
|
256
|
+
A LinkedIn CSV import opened in the app is always graded on its own curve.
|
|
257
|
+
- **A company the app doesn't know is neutral.** It counts as 5, the middle of the scale
|
|
258
|
+
(it was 4, and 3 when no company was found), so the people the built-in list doesn't
|
|
259
|
+
know aren't pushed down a tier for it. A founder at an unknown company scores 7.3 (was
|
|
260
|
+
6.7), a director 5.4 (was 5.0). Claims in their headlines still count half. Your network
|
|
261
|
+
is rescored once, the first time the map loads after updating.
|
|
262
|
+
- **The power score reads more of who someone is.** A review of the score found kinds of
|
|
263
|
+
people it ranked too low:
|
|
264
|
+
- A title's company written without "at" counts: "Global Category President | The
|
|
265
|
+
Coca-Cola Company" scores 9.5 (S), not 7.3 (A), and so does "Corporate VP, Samsung".
|
|
266
|
+
Only a company the app knows, and not after a founder's, an owner's or a CEO's title,
|
|
267
|
+
not a program or a degree ("AWS Community Builder", "Harvard MBA"), and not a school
|
|
268
|
+
after a title that isn't an academic's.
|
|
269
|
+
- "MD @ J.P. Morgan", "MD, Investment Banking", "Head of Country" and "Country Head" are
|
|
270
|
+
senior, like a managing director; "MD, MBA" and "Physician, MD" aren't.
|
|
271
|
+
- Universities have their own ladder: an assistant professor 5, an associate professor
|
|
272
|
+
6.5, a professor 7.5 (was 4, an entry-level job), a dean 9, a provost or a chancellor 10.
|
|
273
|
+
- An audience of their own counts like a title: 100K+ followers like a manager's, 1M+ a
|
|
274
|
+
director's, 10M+ a VP's. A creator with 2.5M followers scores 5.8 (A), not 3.3 (C).
|
|
275
|
+
- A title the app can't read counts as an individual contributor's (4, was 3), so a
|
|
276
|
+
founder whose headline names no title or company isn't put below every job.
|
|
277
|
+
- A strong circle counts by how many strong people are in it, not only by their share:
|
|
278
|
+
+1 for every 25 at A or S, up to +2 (was up to +1). A vague title at a big company with
|
|
279
|
+
72 of them in a circle of 600 scores 5.6 (A), not 3.6 (C).
|
|
280
|
+
|
|
281
|
+
Your network is rescored once, the first time the map loads after updating. The sample
|
|
282
|
+
network's tiers don't change.
|
|
283
|
+
- **Scores has a tab of its own.** How a power score is worked out and the three things you
|
|
284
|
+
can change about it are in one place: your field (*Your sector*), how tiers are graded
|
|
285
|
+
(*Tiers*) and every company's score. Company scores used to be a tab inside Paths, and the
|
|
286
|
+
other two were in Settings. Old links (*Paths → Scores*, *Settings → Your sector*,
|
|
287
|
+
*Settings → Tiers*) forward there, and Settings says where they went.
|
|
288
|
+
- **Separation's map is "who to ask next".** It skips anyone you've already asked or
|
|
289
|
+
already know. Send requests to its ten and the next ten come up. The list below
|
|
290
|
+
still shows everyone, marked *Request sent* or *Connected*, with their rarity.
|
|
291
|
+
|
|
292
|
+
### Removed
|
|
293
|
+
- **The Revolver view.** Degrees keeps Separation, Orbit, Bridge Chains, Pyramid and List.
|
|
294
|
+
|
|
9
295
|
## [0.3.0] - 2026-09-26
|
|
10
296
|
|
|
11
297
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://
|
|
2
|
+
<a href="https://sixdegreesapp.com/"><img src="desktop/icon/icon.svg" width="128" height="128" alt="Six Degrees website"></a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">Six Degrees</h1>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg"><img src="docs/img/download-intel.png" width="346" alt="Download Six Degrees for a Mac with an Intel chip"></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
-
<p align="center"><sub>Free Mac app · macOS 13.5 or later · <a href="https://
|
|
21
|
+
<p align="center"><sub>Free Mac app · macOS 13.5 or later · <a href="https://sixdegreesapp.com/">website</a> · <a href="https://github.com/blakeb056/six-degrees/releases/latest">what's new</a></sub></p>
|
|
22
22
|
|
|
23
23
|
**Which download?** Apple menu → **About This Mac**. If it says "Chip: Apple M…", take
|
|
24
24
|
**Apple Silicon**. If it says "Processor: …Intel…", take **Intel**. It needs **macOS 13.5
|
|
@@ -123,8 +123,7 @@ whose own circles reach the furthest, and the shortest chain from you to a stran
|
|
|
123
123
|
worth meeting.
|
|
124
124
|
|
|
125
125
|
It's a Mac app (or `npx six-degrees` on Linux). It runs on your computer, against your own data, with no account and
|
|
126
|
-
no server.
|
|
127
|
-
Degrees**.)
|
|
126
|
+
no server.
|
|
128
127
|
|
|
129
128
|
## Before you scan LinkedIn
|
|
130
129
|
|
|
@@ -137,8 +136,9 @@ Degrees**.)
|
|
|
137
136
|
> - Search was blocked after results were read too fast (2026-09-24).
|
|
138
137
|
>
|
|
139
138
|
> The scanner now reads slowly and keeps to a search budget (by default 50 a day and 250
|
|
140
|
-
> a month
|
|
141
|
-
>
|
|
139
|
+
> a month) and a cap on profile views (50 a day, at least a minute apart). You can change
|
|
140
|
+
> both on the Scan page. It locks itself during a cooldown, and stops at the first sign of
|
|
141
|
+
> push-back. The risk is still yours.
|
|
142
142
|
>
|
|
143
143
|
> **The CSV import and the sample network carry no LinkedIn risk at all.** Details:
|
|
144
144
|
> [how scanning works and what it risks](docs/SCRAPING.md).
|
|
@@ -147,12 +147,13 @@ Degrees**.)
|
|
|
147
147
|
|
|
148
148
|
The app opens on a welcome screen with three ways in, and asks nothing about you:
|
|
149
149
|
|
|
150
|
-
- **Scan my LinkedIn**: a guided page that ticks each step off as it goes. It
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
- **Scan my LinkedIn**: a guided page that ticks each step off as it goes. It first asks
|
|
151
|
+
one optional question, what field you're in (see *Your sector* below), and *Skip for
|
|
152
|
+
now* is fine. Then it sets up the scanner in one click, you sign into LinkedIn yourself
|
|
153
|
+
in a Chrome window, and then it scans. Needs Google Chrome. The Mac app brings its own
|
|
154
|
+
Python; with `npx six-degrees` the page uses yours, or sets up a private one with a
|
|
155
|
+
click. It checks and says what's missing. The app marks this *Recommended* because
|
|
156
|
+
it's the only way to Degrees and Outlink. Read the warning above first.
|
|
156
157
|
- **Import my LinkedIn CSV**: LinkedIn's official export, read on your machine. On
|
|
157
158
|
LinkedIn: **Settings & Privacy → Data privacy → Get a copy of your data →
|
|
158
159
|
Connections**. LinkedIn emails a link in about ten minutes; unzip it and drop
|
|
@@ -186,8 +187,8 @@ panel on the left.
|
|
|
186
187
|
|
|
187
188
|
**Degrees** *(needs a scan or the sample)*. The people two steps away. **Separation**
|
|
188
189
|
ranks every one of them with every way in: which of your connections knows them, and
|
|
189
|
-
how many do. **Orbit**, **Bridge Chains**, **
|
|
190
|
-
|
|
190
|
+
how many do. **Orbit**, **Bridge Chains**, **Pyramid** and **List** draw the same people
|
|
191
|
+
differently.
|
|
191
192
|
|
|
192
193
|
<img src="docs/img/degrees.png" alt="Degrees → Separation: people two steps away, ranked, each with the connection who can introduce you" width="100%">
|
|
193
194
|
|
|
@@ -197,13 +198,16 @@ InMaps:
|
|
|
197
198
|
where your connections at one know people at another.
|
|
198
199
|
- **Industries**: a card per industry.
|
|
199
200
|
- **Companies**: pick one to see its people level by level, up to the top.
|
|
200
|
-
- **Scores**: every scanned company's score, which you can change.
|
|
201
201
|
|
|
202
202
|
Click a company for its analyzer: who you know there, who you can reach, the warmest way
|
|
203
203
|
in, and its most powerful people.
|
|
204
204
|
|
|
205
205
|
<img src="docs/img/paths.png" alt="Paths → Map with the company analyzer open: companies grouped by industry, and one company's people by level and ways in" width="100%">
|
|
206
206
|
|
|
207
|
+
**Scores.** How a power score is worked out, and the three things you can change about
|
|
208
|
+
it, in one place: your field (the sectors you work in), how tiers are graded (on your
|
|
209
|
+
network's curve or the fixed scale), and every scanned company's score.
|
|
210
|
+
|
|
207
211
|
**Outlink** *(needs a scan)*. Getting introduced, as a game. Each mapped connection's
|
|
208
212
|
circle offers its best five people at a time; mark invites as sent to fill the ring and
|
|
209
213
|
unlock the next five. It also shows the next best moves, levels and points (from
|
|
@@ -216,20 +220,28 @@ lock, and a **Paused** list you can carry on from.
|
|
|
216
220
|
|
|
217
221
|
```
|
|
218
222
|
power = title × (0.45 + 0.055 × company score) + reach bonus + bridge boost
|
|
219
|
-
tiers: S
|
|
223
|
+
tiers, on your network's curve (the default): your top 3% S, then A to 15%, B to 40%, C to 70%
|
|
224
|
+
tiers, on the fixed scale: S ≥ 7.5 A ≥ 5.5 B ≥ 4.0 C ≥ 2.5 D < 2.5
|
|
220
225
|
```
|
|
221
226
|
|
|
222
|
-
- **Title (1–10)** comes from someone's
|
|
223
|
-
|
|
224
|
-
|
|
227
|
+
- **Title (1–10)** comes from someone's roles in their headline, with former roles at
|
|
228
|
+
70%; current students are capped. Academics have their own ladder (an assistant
|
|
229
|
+
professor 5 up to a dean 9), and so do government and the services (a city
|
|
230
|
+
councilmember or a colonel 7.5 up to a senator or a four-star general 10), read only
|
|
231
|
+
where a title is written. An audience of their own counts like a title (100K+
|
|
232
|
+
followers 6.5, 1M+ 7.5, 10M+ 9), and a title it can't read counts as an individual
|
|
233
|
+
contributor's (4).
|
|
234
|
+
- **Company score (1–10)** is the one you set (the Scores tab) if there is one. Otherwise
|
|
225
235
|
it comes from a built-in list of well-known companies, or from how many of your people
|
|
226
236
|
work there. The built-in list is the same for everyone and follows one rule: only
|
|
227
237
|
companies most US professionals would recognise (household names, the largest companies,
|
|
228
238
|
top investment, consulting, law and accounting firms, frontier AI labs, top universities),
|
|
229
|
-
scored 7 to 10. Anything else is estimated from your network
|
|
230
|
-
|
|
239
|
+
scored 7 to 10. Anything else is estimated from your network: a company it doesn't
|
|
240
|
+
know counts as 5, the middle of the scale, a little more when several of your people
|
|
241
|
+
work there. If you scanned with an
|
|
242
|
+
earlier version, the Scores tab offers once to keep its old scores for the companies in
|
|
231
243
|
your network whose score changed.
|
|
232
|
-
- **Your sector (
|
|
244
|
+
- **Your sector (Scores, optional):** pick up to three sectors you work in, and companies
|
|
233
245
|
in them get +1 ("lean") or +2 ("strong") on that score, once, never above 10 and never on
|
|
234
246
|
a score you set. A pick is one of twelve broad industries or a narrower sector from a
|
|
235
247
|
built-in list of about fifty (Dental, Real Estate, Software & SaaS…). A sector matches by a
|
|
@@ -237,19 +249,26 @@ tiers: S ≥ 7.5 A ≥ 5.5 B ≥ 4.0 C ≥ 2.5 D < 2.5
|
|
|
237
249
|
people you know there, where a job every kind of company has (recruiter, accountant,
|
|
238
250
|
software engineer) doesn't count. An industry includes its sectors, and a company's one
|
|
239
251
|
industry when the known list or the company's name gives it, not when only its people's
|
|
240
|
-
job titles do.
|
|
241
|
-
change before you save.
|
|
252
|
+
job titles do. Scores suggests the sectors your network is in and shows what would
|
|
253
|
+
change before you save. The Scan page asks for it once, before your first scan, so your
|
|
254
|
+
first scores already use it; you can skip it and pick later.
|
|
242
255
|
- Multiplying means seniority counts for more at a bigger company. A VP at a company
|
|
243
|
-
scored 10 gets 9.0; a founder at an unknown company,
|
|
256
|
+
scored 10 gets 9.0; a founder at an unknown company, 7.3.
|
|
244
257
|
- **Reach bonus (up to 1.5)**: investor, YC, an audience in the millions, the top award
|
|
245
258
|
of any field (a Nobel, a Pulitzer, an Oscar, an Olympic medal…). It counts half
|
|
246
259
|
at an unknown company, because headlines are self-written (your sector doesn't change
|
|
247
260
|
that).
|
|
248
|
-
- **Bridge boost (up to
|
|
261
|
+
- **Bridge boost (up to 2)**: someone whose mapped circle is unusually strong: a high
|
|
262
|
+
share of it at A or S (up to +1), or many people there (+1 for every 25, up to +2).
|
|
263
|
+
- **Tiers (Scores):** graded on your own network's curve by default, so any network has a
|
|
264
|
+
top: your top 3% of connections are S, the next 12% A, the next 25% B and the next 30% C.
|
|
265
|
+
The curve only lifts, so a network full of well-known companies keeps its tiers, and it
|
|
266
|
+
never lifts anyone under 4 into S or A. People tied at a line all come in or all stay out.
|
|
267
|
+
Choose the fixed scale instead for the same lines as everyone else.
|
|
249
268
|
|
|
250
269
|
Scanned and sample people show the working ("VP / Partner / GM (9) · Adobe (8/10) · +0.7
|
|
251
270
|
strong circle"). A CSV import shows the score only, and uses the built-in company list
|
|
252
|
-
rather than your own scores or sectors; the sample network keeps its own. The model is
|
|
271
|
+
rather than your own scores or sectors, graded on its own curve; the sample network keeps its own. The model is
|
|
253
272
|
[`lib/scoring.js`](lib/scoring.js), explained in
|
|
254
273
|
[`docs/brain/SCORING.md`](docs/brain/SCORING.md).
|
|
255
274
|
|
|
@@ -262,8 +281,10 @@ It estimates **network position**, not what anyone is worth as a person.
|
|
|
262
281
|
- A CSV import is read on your machine and never added to your network. It's held only
|
|
263
282
|
in the app window's own storage, and you import it again next time.
|
|
264
283
|
- The app contacts only these, and only when you act:
|
|
265
|
-
- **LinkedIn**, while you scan.
|
|
266
|
-
|
|
284
|
+
- **LinkedIn**, while you scan. Each profile photo is saved on your computer as the
|
|
285
|
+
scan reads it, and the app shows photos only from there, so looking at your network
|
|
286
|
+
never contacts LinkedIn. (Photos an older version kept as links to LinkedIn show
|
|
287
|
+
initials until the next scan, or *Save photos* on the Scan page, saves them.)
|
|
267
288
|
- Only without the Mac app (which carries the scanner's Python and add-ons inside it),
|
|
268
289
|
once, when you set the scanner up: **PyPI** (the Python package library) for the
|
|
269
290
|
scanner's add-ons, and, if the computer has no Python the scanner can use, **GitHub**
|
package/SECURITY.md
CHANGED
|
@@ -99,6 +99,14 @@ This app is designed to run **on your own machine, against your own network**.
|
|
|
99
99
|
- Values stored in the database are treated as untrusted text and escaped
|
|
100
100
|
before rendering. Do not reintroduce `innerHTML` (including d3's `.html()`)
|
|
101
101
|
for anything data-bearing.
|
|
102
|
+
- **Profile photos come from this computer only.** A page shows a photo only
|
|
103
|
+
from a file saved in the data folder (`/avatars/<name>`, `lib/photos.js`),
|
|
104
|
+
never from a link, and every page carries
|
|
105
|
+
`Content-Security-Policy: img-src 'self' data: blob:`, so the browser won't
|
|
106
|
+
load a picture from anywhere else. The scanner fetches photos only from
|
|
107
|
+
LinkedIn's image servers (`*.licdn.com`, over https): while it scans, and
|
|
108
|
+
once each for links older versions stored, which a crafted import could also
|
|
109
|
+
carry.
|
|
102
110
|
- Do not bind this app to `0.0.0.0` or expose it through a tunnel.
|
|
103
111
|
|
|
104
112
|
## Your data
|
|
@@ -108,6 +116,8 @@ Everything stays local:
|
|
|
108
116
|
- A CSV import is parsed in the browser and held for that tab only. It is never
|
|
109
117
|
uploaded and never written to a database.
|
|
110
118
|
- Scanned data and cached avatars are written to your machine and are gitignored.
|
|
119
|
+
The app shows photos only from those files, so looking at your network never
|
|
120
|
+
contacts LinkedIn.
|
|
111
121
|
- There is no telemetry, no analytics, and no crash reporting.
|
|
112
122
|
|
|
113
123
|
If you run the scanner, `chrome-profile/` in the data directory holds a live
|
package/db/schema.js
CHANGED
|
@@ -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,
|
|
@@ -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
|
+
}
|