six-degrees 0.4.10 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +9 -9
- package/.next/standalone/.next/routes-manifest.json +6 -0
- 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 +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- 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 +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 +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 +1 -1
- 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 +3 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +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 +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 +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/social/crm/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js +10 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/crm/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/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 +15 -15
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- 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 +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- 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 +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
- 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 +14 -14
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__05_3-22._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__06mi9k2._.js → [root-of-the-server]__13dx4sd._.js} +7 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- 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/_1oqfuh8._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_crm_route_actions_0d1aflv.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
- package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- 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 +6 -5
- 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 +19 -3
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.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/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- 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/03bziijw5srol.js +1 -0
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/{static/chunks/1xptlvtcz6654.css → standalone/.next/static/chunks/1x_ysly42-teq.css} +1 -1
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/.next/standalone/CHANGELOG.md +235 -0
- package/.next/standalone/app/api/data/export/route.js +2 -1
- package/.next/standalone/app/api/ingest/route.js +40 -1
- package/.next/standalone/app/api/scraper/route.js +12 -2
- package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
- package/.next/standalone/app/api/social/crm/route.js +37 -0
- package/.next/standalone/app/api/social/messages/route.js +44 -8
- package/.next/standalone/app/api/social/route.js +80 -22
- package/.next/standalone/app/components/AppTabs.js +59 -0
- package/.next/standalone/app/components/ChainView.js +303 -89
- package/.next/standalone/app/components/CirclePanel.js +162 -0
- package/.next/standalone/app/components/CompanyScores.js +2 -2
- package/.next/standalone/app/components/FieldStep.js +4 -4
- package/.next/standalone/app/components/FilterPanel.js +121 -183
- package/.next/standalone/app/components/ForceGraph.js +41 -3
- package/.next/standalone/app/components/GalaxyLab.js +26 -31
- package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
- package/.next/standalone/app/components/MapControls.js +154 -0
- package/.next/standalone/app/components/NoteDetail.js +118 -0
- package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
- package/.next/standalone/app/components/PeopleMap.js +308 -0
- package/.next/standalone/app/components/ScanRadar.js +129 -0
- package/.next/standalone/app/components/ScanStatusBar.js +72 -27
- package/.next/standalone/app/components/SeparationView.js +626 -118
- package/.next/standalone/app/components/Sidebar.js +28 -2
- package/.next/standalone/app/components/settings/DataSection.js +12 -3
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
- package/.next/standalone/app/components/settings/SectorSection.js +2 -2
- package/.next/standalone/app/components/settings/TierSection.js +1 -1
- package/.next/standalone/app/components/settings/TitleSection.js +149 -0
- package/.next/standalone/app/components/views.js +15 -14
- package/.next/standalone/app/globals.css +11 -0
- package/.next/standalone/app/page.js +89 -156
- package/.next/standalone/app/paths/page.js +39 -27
- package/.next/standalone/app/profile/page.js +33 -5
- package/.next/standalone/app/queue/page.js +56 -34
- package/.next/standalone/app/scores/page.js +16 -66
- package/.next/standalone/app/settings/page.js +27 -13
- package/.next/standalone/app/setup/page.js +298 -205
- package/.next/standalone/app/social/Crm.js +683 -0
- package/.next/standalone/app/social/SocialHub.js +374 -0
- package/.next/standalone/app/social/Thread.js +90 -0
- package/.next/standalone/app/social/page.js +10 -351
- package/.next/standalone/lib/chain-layout.js +105 -35
- package/.next/standalone/lib/data-export.js +10 -4
- package/.next/standalone/lib/data-folder.js +9 -0
- package/.next/standalone/lib/data-import.js +13 -3
- package/.next/standalone/lib/degrees.js +0 -6
- package/.next/standalone/lib/dot-rings.js +25 -0
- package/.next/standalone/lib/galaxy-lab.js +65 -6
- package/.next/standalone/lib/ingest.js +2 -0
- package/.next/standalone/lib/island.js +40 -0
- package/.next/standalone/lib/linkedin-export.js +267 -46
- package/.next/standalone/lib/linkedin-limits.js +6 -2
- package/.next/standalone/lib/map-heat.js +20 -0
- package/.next/standalone/lib/network-health.js +1 -1
- package/.next/standalone/lib/notifications.js +121 -0
- package/.next/standalone/lib/people-map.js +82 -0
- package/.next/standalone/lib/rarity.js +45 -3
- package/.next/standalone/lib/reach.js +7 -6
- package/.next/standalone/lib/rpc.js +15 -4
- package/.next/standalone/lib/scan-pace.js +46 -0
- package/.next/standalone/lib/scoring.js +39 -8
- package/.next/standalone/lib/scraper-client.js +19 -6
- package/.next/standalone/lib/sector-focus.js +4 -4
- package/.next/standalone/lib/separation.js +137 -10
- package/.next/standalone/lib/settings-effects.js +20 -11
- package/.next/standalone/lib/settings.js +3 -0
- package/.next/standalone/lib/social-crm.js +415 -0
- package/.next/standalone/lib/social-store.js +13 -6
- package/.next/standalone/lib/tier-grid.js +99 -0
- package/.next/standalone/lib/title-ranking.js +83 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +177 -35
- package/.next/static/chunks/03bziijw5srol.js +1 -0
- package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/{standalone/.next/static/chunks/1xptlvtcz6654.css → static/chunks/1x_ysly42-teq.css} +1 -1
- package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/CHANGELOG.md +235 -0
- package/lib/chain-layout.js +105 -35
- package/lib/data-export.js +10 -4
- package/lib/data-folder.js +9 -0
- package/lib/data-import.js +13 -3
- package/lib/degrees.js +0 -6
- package/lib/dot-rings.js +25 -0
- package/lib/galaxy-lab.js +65 -6
- package/lib/ingest.js +2 -0
- package/lib/island.js +40 -0
- package/lib/linkedin-export.js +267 -46
- package/lib/linkedin-limits.js +6 -2
- package/lib/map-heat.js +20 -0
- package/lib/network-health.js +1 -1
- package/lib/notifications.js +121 -0
- package/lib/people-map.js +82 -0
- package/lib/rarity.js +45 -3
- package/lib/reach.js +7 -6
- package/lib/rpc.js +15 -4
- package/lib/scan-pace.js +46 -0
- package/lib/scoring.js +39 -8
- package/lib/scraper-client.js +19 -6
- package/lib/sector-focus.js +4 -4
- package/lib/separation.js +137 -10
- package/lib/settings-effects.js +20 -11
- package/lib/settings.js +3 -0
- package/lib/social-crm.js +415 -0
- package/lib/social-store.js +13 -6
- package/lib/tier-grid.js +99 -0
- package/lib/title-ranking.js +83 -0
- package/package.json +1 -1
- package/scripts/scrape.py +177 -35
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
- package/.next/standalone/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/standalone/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/standalone/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- package/.next/standalone/app/components/OrbitGraph.js +0 -737
- package/.next/standalone/app/social/Conversations.js +0 -246
- package/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,241 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-10-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Rank titles your way** (Settings → Scores → Titles). Set how much each kind of title counts, out
|
|
13
|
+
of 10, or start from a preset: Founders first, Investors, or A role I'm looking for. Add the roles
|
|
14
|
+
you're after ("account executive", "head of growth", "recruiter") with their own points, and
|
|
15
|
+
everyone whose title has those words rises, whatever their level: for outbound to one role, or
|
|
16
|
+
hiring for it. Saving rescores everyone and says how many people changed tier, and a card's score
|
|
17
|
+
says "your ranking" where it moved a title. Nothing set scores exactly as before.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **The Scan page is a launch, not a manual.** It opens with what the scanner is and what it never does
|
|
21
|
+
(never posts or messages anyone, never sees your password, nothing leaves this Mac, stops when you
|
|
22
|
+
say), then the four steps on a rail: Get ready, Sign in, Who you know, Who they know. The one
|
|
23
|
+
you're on pulses, with one big button that does it and how long it takes; any other opens with a
|
|
24
|
+
tap. While it runs, "Your scanner is working" and Stop sit right under the step. How many people
|
|
25
|
+
this round and who first are beside the radar; the daily budget, how much of each list, the hidden
|
|
26
|
+
window, retrying hidden lists and paused lists are in Fine-tune, closed until you want them. The
|
|
27
|
+
log is one tap away, and opens by itself when a run stops badly. Scan has the same header as every
|
|
28
|
+
page now.
|
|
29
|
+
- **Paths reads like Network Circle.** Its notch is now Map and Companies: companies count for more
|
|
30
|
+
than industries, and an industry opens from a company's card (its industry is a link there). Whether
|
|
31
|
+
the map's bubbles are companies or your connections (what was the People tab) is a switch at the
|
|
32
|
+
top of the Filters panel. Filters use the same tiers × degrees grid as Network Circle. Colour is by
|
|
33
|
+
sector or by **Heat**: for companies, how strong your people there are (the average power of the
|
|
34
|
+
five strongest); for people, how strong their cluster is, person for person; size still says how
|
|
35
|
+
many. And **Physics**: switch it to Live to drag bubbles (the ones joined to them follow) and change
|
|
36
|
+
Spread, Pull to their group and Pull along lines. Still, as before, is the default. Old links to
|
|
37
|
+
the People or Industries tab still land on the map.
|
|
38
|
+
- **Heat: power as a thermal map.** A new Colour by in Network Circle's Physics: every dot coloured
|
|
39
|
+
from cold violet through red and orange to white hot by where their power score ranks among the
|
|
40
|
+
people showing, with a soft glow behind the hotter ones that pools where powerful people cluster,
|
|
41
|
+
like a thermal camera. Works with every layout, the replay and the filter grid.
|
|
42
|
+
- **Separation all the way right counts every mutual, scanned or not.** It aims at whoever you share
|
|
43
|
+
the most mutual connections with, even when most of them are in circles you haven't scanned: an
|
|
44
|
+
unscanned mutual is still a likely introduction, so it no longer holds someone back. The ones the
|
|
45
|
+
app can draw are lines as before; the rest are grey dots, each with a faint line in, with "+37 more
|
|
46
|
+
mutuals, in circles not scanned yet". The person glows less the less of it is scanned. The Next
|
|
47
|
+
names are in the same order and say their mutual counts. Narrow the tiers (S only, say) first to
|
|
48
|
+
aim at the strongest.
|
|
49
|
+
- **The right panel follows the circle you're in.** Open someone's circle in Bridge Chains and the
|
|
50
|
+
panel is about them: how many people are in their circle, tier chips to see just one tier, a
|
|
51
|
+
search inside it, the people you added from it (with Scan… for the ones ready), then everyone
|
|
52
|
+
else in it, strongest first. Click anyone for their card.
|
|
53
|
+
- **Orbit is a layout of the Galaxy now**, in Physics next to Rings and Clusters, not a separate
|
|
54
|
+
view: every tier held on its own orbit, S nearest you, and each connection's circle tucked in
|
|
55
|
+
behind them. It's still the Galaxy, so you can drag dots and move every slider on it.
|
|
56
|
+
- **Light up a branch on hover starts off.** It dimmed and relit thousands of dots on every hover,
|
|
57
|
+
which flickered; it's still in Physics if you want it, and moving from one dot to the next no
|
|
58
|
+
longer flashes the whole Galaxy in between.
|
|
59
|
+
- **Social is part of Outlink.** Outlink has the same header as every page, and its views sit in
|
|
60
|
+
the notch: Circles, To add, Pending, and Messages & follow-ups, which is the whole Social tab (your
|
|
61
|
+
LinkedIn export, the live messages sync, the CRM with its inbox, follow-ups, pipeline, sent and
|
|
62
|
+
received). Adding people and keeping track of them are now one place. The Social tab is gone from
|
|
63
|
+
the header, and old links to it land on Messages & follow-ups.
|
|
64
|
+
- **Notifications open.** Click one and it takes over the right panel with the detail: who it is
|
|
65
|
+
about, their tier and score, who you met them through, and, for an S or A, that they're a
|
|
66
|
+
valuable person to know; with buttons to open their card, their circle in Bridge Chains, or
|
|
67
|
+
them in Separation. A notification naming several people lists them all.
|
|
68
|
+
- **New notifications:** someone you met through a bridge added you back ("Ana added you back,
|
|
69
|
+
through Yuki"); a circle scan found people at top companies (a company score of 8 or more), named
|
|
70
|
+
in one notification; and a scan finished ("Scan done: Yuki's circle", "Check for new: done").
|
|
71
|
+
Accepted requests and high-value new connections now say who they are about, and older ones are
|
|
72
|
+
matched by the name in their title.
|
|
73
|
+
- **Paths has the same header as the map.** The app's tabs stay at the top, with Paths lit, and Map,
|
|
74
|
+
People, Industries and Companies sit in the notch under them, as the map page's views do; a tab
|
|
75
|
+
for Network Circle, Degrees or Separation takes you straight to it. Paths' filters moved into a
|
|
76
|
+
Filters panel on the left, like the map page's, with new ways to read the map: what a bubble's
|
|
77
|
+
size means (your people there, the S and A among them, or the ones you already know; on People,
|
|
78
|
+
cluster value, size or S tier) and which names show (the biggest, all, or none).
|
|
79
|
+
- **All the way to easy, Separation aims at whoever the most of your connections lead to**, with
|
|
80
|
+
every one of those lines drawn, the strongest first among equals; the "Next" names are the
|
|
81
|
+
runners-up by the same measure. It used to aim at the top of the list, which could be someone
|
|
82
|
+
with many mutual connections by LinkedIn's count but only one the app could draw.
|
|
83
|
+
- **The Scan page has a radar, and a speed: Slow, Medium or Fast.** Mapping the 2nd degree starts
|
|
84
|
+
from a round Scan button in the middle of a radar: its sweep turns while a scan runs, and the
|
|
85
|
+
ring round it fills with today's searches against your budget. Beside it, three speeds. Fast is
|
|
86
|
+
how the scanner has always run, and nothing is faster; Medium waits 45 seconds before each page
|
|
87
|
+
of results and 3 minutes after every 10, Slow 90 seconds and 5 minutes, and both keep profiles
|
|
88
|
+
further apart (90 and 120 seconds). Each says about how many searches an hour it makes and how
|
|
89
|
+
long the rest of today's budget would take. The daily budget stays the cap at every speed:
|
|
90
|
+
slower spreads it out, it doesn't shrink it. The speed is saved with the budget and the scanner
|
|
91
|
+
says which it is using at the start of every run; the Scan page's time estimates follow it.
|
|
92
|
+
- **Scores moved into Settings.** Your field, how tiers are graded and every company's score are a
|
|
93
|
+
Scores section in Settings (the gear), and the Scores tab is gone from the header. Old links to
|
|
94
|
+
the Scores tab land in the same place in Settings.
|
|
95
|
+
- **Profile has Insights.** An Insights button beside Profile shows network health: how much of
|
|
96
|
+
your 2nd degree you reach two or more ways, the effective reach of your connections, and the
|
|
97
|
+
connections who reach the most people no one else does. It was at the top of the Scores tab.
|
|
98
|
+
- **Paths → People.** A new view beside the company map, drawn the same way but with your
|
|
99
|
+
connections as the bubbles: each is sized by the value of the cluster behind them (S tier counts
|
|
100
|
+
3, A tier 2, everyone else 1; or by cluster size, or by S tier inside), coloured by their sector
|
|
101
|
+
and grouped like the companies. A white centre is the share of their cluster you can only reach
|
|
102
|
+
through them, a gold ring means S-tier people inside, and a line joins two connections whose
|
|
103
|
+
clusters share people. Point at someone for their numbers and how their cluster compares to the
|
|
104
|
+
rest you've scanned; click to open it in Bridge Chains. Shows the connections you've scanned,
|
|
105
|
+
or all of them, with the ones not scanned yet as small grey dots.
|
|
106
|
+
- **Separation's map draws connections as pills and people as cards** on a computer (a phone keeps
|
|
107
|
+
the dots). Each connection shows their initials, tier and what they are to the people on the
|
|
108
|
+
map: the only door to someone, the best way in for some, or only another way in; the ones with
|
|
109
|
+
a solid line are lit. Each person is a card with rank, role, score and how many ways in. With
|
|
110
|
+
the slider all the way to easy, the one person becomes a large card with LinkedIn's mutual
|
|
111
|
+
count and how many of those are drawn. Everything still glides as the slider moves.
|
|
112
|
+
- **Separation's list is a ledger.** A title bar says how many are shown and that the order
|
|
113
|
+
follows the slider. The way in shows the connection's face with "only way in" or how many more
|
|
114
|
+
ways there are under it, a new column shows how rare the way in is as a bar (full and cyan for
|
|
115
|
+
one door, short and orange for the warmest), and the power score sits beside its bar.
|
|
116
|
+
- **Separation is a tab of its own**, beside Degrees in the header, instead of a view inside
|
|
117
|
+
Degrees. It uses the same Filters grid as Degrees. Degrees keeps Bridge Chains, Pyramid and List.
|
|
118
|
+
- **Separation has a slider.** One slider reorders everyone as you drag it: the rarest ways in
|
|
119
|
+
at one end (strong people only one of your connections knows), the easiest at the other
|
|
120
|
+
(strong people you share the most mutual connections with), and each person's own score alone
|
|
121
|
+
in the middle, as the list has always been. A line under it says in words what it is
|
|
122
|
+
surfacing. It only reorders: scores, tiers and ranks stay as they are, and it claims no odds.
|
|
123
|
+
Double-click the slider, or tap 50, to go back to the middle. It replaces the Power / Ways in
|
|
124
|
+
switch. The tier chips and the rarity chips ("Only show") sit in the same card.
|
|
125
|
+
- **Separation's map follows the slider, and glides.** From the rare end to the middle it fans out
|
|
126
|
+
to ten people, each with the one or two doors that lead to them. Towards the easy end it
|
|
127
|
+
closes in on fewer people, and at the end on one: the person you're aiming at, with rings
|
|
128
|
+
round them and every connection of yours who leads to them drawn in, the top-scored one solid.
|
|
129
|
+
As the slider moves, people, connections and lines slide to their new places and newcomers
|
|
130
|
+
fade in (still, with Reduce Motion on). Pick anyone in the list, or one of the "Next" names
|
|
131
|
+
under the map, to aim at them instead. Beside each name it says how many mutual connections
|
|
132
|
+
lead to them: "only way in", "3 ways in", or, where LinkedIn's own count is higher than the
|
|
133
|
+
connections the app can draw, "37 mutuals · 6 mapped".
|
|
134
|
+
- **Path, in Separation.** A Path button beside the tier chips narrows everyone to one sector or
|
|
135
|
+
one company. The picker opens under the button with the sectors (inferred, as in Paths), the
|
|
136
|
+
biggest companies and a box to type in, and goes away once you've picked; the button then
|
|
137
|
+
shows your pick with a × to clear it.
|
|
138
|
+
- **Separation's heading no longer sits under the notch.**
|
|
139
|
+
- **One filter for tiers and degrees, the same in Network Circle and in Degrees.** The Filters
|
|
140
|
+
panel shows the five tiers as bigger rows, each with six dots, one for each degree. Tap a tier
|
|
141
|
+
to hide or show it, as before. Tap a dot to show or hide that degree for that tier alone:
|
|
142
|
+
S tier at 2nd degree without everyone else's, say. Tap a number at the top to switch a degree
|
|
143
|
+
for every tier at once. A dot with nobody behind it is faint and can't be tapped, each row
|
|
144
|
+
says how many people it is showing, and the last people on screen can't be switched off.
|
|
145
|
+
Network Circle starts with your own connections; Degrees starts with everything. In Degrees
|
|
146
|
+
the rows used to pick one bridge tier at a time; now 1st degree is your bridges and 2nd is
|
|
147
|
+
the people in their circles, each by their own tier.
|
|
148
|
+
- **The Galaxy's physics is the main thing in Network Circle's Filters panel.** The sliders,
|
|
149
|
+
layouts, colours, find and replay sit straight in the panel under the tiers, always on, with
|
|
150
|
+
no switch to turn on first and no "experimental" box. With no slider moved the Galaxy is laid
|
|
151
|
+
out as before; pointing at a dot now lights up its branch unless you turn that off.
|
|
152
|
+
- **Pyramid and List left Network Circle.** Its views are the Galaxy and Orbit. Pyramid and List
|
|
153
|
+
stay in Degrees.
|
|
154
|
+
- **An opened circle shows the chains that lead on from it.** Anyone you added from that circle
|
|
155
|
+
and then scanned keeps their dot in the circle, and the cluster their scan formed sits outside
|
|
156
|
+
it, joined to that dot by a line, so you can see where each path came from. Inside a cluster,
|
|
157
|
+
anyone you added and scanned in turn has their own dot marked and a line on to their cluster,
|
|
158
|
+
for as far as your scans reach. Every dot in a cluster is joined to its middle by a faint line.
|
|
159
|
+
Each cluster says whose circle it is, how many are in it and which degree that is. Point at
|
|
160
|
+
one and the way back to the middle lights up; click and it opens with the whole trail. The
|
|
161
|
+
people you added sit together on the side the chains lead off to, their names taking turns
|
|
162
|
+
above and below so they don't overlap, and the view zooms to fit when it opens.
|
|
163
|
+
- **Bridge Chains shows the bridges you connected with yourself.** Someone you met through a
|
|
164
|
+
bridge's circle is a link in that bridge's chain, not a bridge of their own: they're inside its
|
|
165
|
+
circle.
|
|
166
|
+
- **A bridge's ring now counts the people you added through it.** The ring sits tight on the
|
|
167
|
+
dot, with one bar for each person: orange once they've been scanned and have a cluster of
|
|
168
|
+
their own, green while they're ready for a scan. A small green number at the top right says
|
|
169
|
+
how many are ready, in place of the "ready" label. How much of a bridge's own list is scanned
|
|
170
|
+
is in its tooltip.
|
|
171
|
+
- **A crowded Bridge Chains is easier to read.** When your bridges need more than one ring, the
|
|
172
|
+
rings sit further apart, each with its own guide line, and the counts under each name wait
|
|
173
|
+
until you point at the bridge.
|
|
174
|
+
- **Orbit moved to Network Circle.** It shows the size of every circle at once, which is a
|
|
175
|
+
picture of the network, not a way to follow a chain. The corner switch in Degrees is gone.
|
|
176
|
+
- **Views are in the notch, under the tabs.** Galaxy, Orbit, Pyramid and List in Network Circle,
|
|
177
|
+
and Bridge Chains, Separation, Pyramid and List in Degrees, switch from the notch that hangs
|
|
178
|
+
under the top tabs, instead of a list at the top of the Filters panel. A scan's progress
|
|
179
|
+
shows in the same notch, beside them. The Filters panel now only filters.
|
|
180
|
+
|
|
181
|
+
### Fixed
|
|
182
|
+
- The scanner's log, shown on the Scan page, says "scanning" throughout; a few lines still said the
|
|
183
|
+
old word.
|
|
184
|
+
- The "Scanning…" dot in the right panel pulses again; the animation it named was never defined.
|
|
185
|
+
- **Separation's map lines now meet every box.** Each connection sits level with the people it
|
|
186
|
+
leads to, so a connection with one person is on that person's row and the line between them runs
|
|
187
|
+
straight; lines start and end exactly on the pill and the card, with a small dot where they plug in.
|
|
188
|
+
- **"Hide the Chrome window while scanning" did nothing for scans started on the Scan page.**
|
|
189
|
+
Its own buttons (Scan, Check for new, Auto scan and the rest) sent their request without the
|
|
190
|
+
setting, so Chrome opened anyway; only scans started from a person's card or Resume were hidden.
|
|
191
|
+
Every scan start now goes through one place that adds it.
|
|
192
|
+
- **"Only way in" could show beside someone with two ways in drawn.** Rarity trusted the mutual
|
|
193
|
+
count a scan saved even when the app had since found them through more of your connections
|
|
194
|
+
than that. It now takes whichever is larger.
|
|
195
|
+
|
|
196
|
+
## [0.4.11] - 2026-09-29
|
|
197
|
+
|
|
198
|
+
### Added
|
|
199
|
+
- **The Social tab is a personal CRM.** Everyone you've been in touch with on LinkedIn is one contact,
|
|
200
|
+
connection or not: a list on the left with views and a search box, the person on the right (stacked
|
|
201
|
+
on a phone). The views:
|
|
202
|
+
- **Inbox:** they wrote last, or there's something unread. Unread first.
|
|
203
|
+
- **Awaiting reply:** you wrote last and nothing came back for 7 days or more (you can change the 7).
|
|
204
|
+
- **Follow-ups due:** a follow-up date of today or before.
|
|
205
|
+
- **Pipeline:** a board of the people you've given a stage.
|
|
206
|
+
- **Sent:** connection requests you sent (from your export's Invitations.csv, with the date, and the
|
|
207
|
+
note you wrote if Keep my messages is on), requests the app tracked (*Mark sent*), each accepted
|
|
208
|
+
once they're a connection and pending until then, and messages awaiting a reply.
|
|
209
|
+
- **Received:** requests to you from people who aren't connections yet.
|
|
210
|
+
- **All:** everyone and every group conversation, newest first.
|
|
211
|
+
|
|
212
|
+
Each person's panel shows their tier, company and how you're connected (and who can introduce you,
|
|
213
|
+
for 2nd degree), their conversations (the messages themselves if you keep them), requests both ways,
|
|
214
|
+
and **your own stage** (New, Contacted, Replied, Meeting, Won/Partner, Not now; it suggests one),
|
|
215
|
+
**tags**, **notes** and **next follow-up date**. Those are yours, so they're always kept, in a file of
|
|
216
|
+
their own per profile in the app's data folder, and never sent anywhere. Search covers names,
|
|
217
|
+
companies, tags and notes, and what was said while the messages are kept; in All it finds anyone in
|
|
218
|
+
your network, so you can add a note before you've written to them. **Export CSV** saves the table
|
|
219
|
+
(name, company, profile, connection, stage, tags, last contact, who wrote last, next follow-up,
|
|
220
|
+
notes) as a spreadsheet file. The list shows 100 at a time, so thousands of conversations stay quick.
|
|
221
|
+
- **Every conversation in your export, not just your connections'.** People who aren't connections
|
|
222
|
+
are listed by the name the export gives them, marked *Not a connection*, with their profile link when
|
|
223
|
+
it has one. Past (archived), current and ones only you wrote in are all there, from every folder;
|
|
224
|
+
group conversations are listed and marked; Sponsored Messages and InMails are marked, and adverts are
|
|
225
|
+
kept out of the Inbox. Unsent drafts are left out and said so.
|
|
226
|
+
- **Read my whole history**, beside *Sync messages now*: the live messages sync keeps scrolling your
|
|
227
|
+
messages list until no new conversations load (at most 60 scrolls, about 1,000 conversations), at the
|
|
228
|
+
same slow, fixed pace, with no random waits. The daily sync still reads just the top. The sync now
|
|
229
|
+
also brings in people who aren't your connections (with their name) and group conversations (without
|
|
230
|
+
any words), and each sync adds to what earlier ones found instead of replacing it.
|
|
231
|
+
- **Delete my CRM notes**, and *Forget it* asks, separately, whether to delete them too, so notes
|
|
232
|
+
aren't lost by accident.
|
|
233
|
+
|
|
234
|
+
### Changed
|
|
235
|
+
- **"Save a copy of my network" carries the Social tab too.** A new box, ticked by default,
|
|
236
|
+
adds its findings, your CRM stages, tags, notes and follow-ups, and any messages you keep,
|
|
237
|
+
so moving to a new computer brings everything. Importing the copy puts them in place and
|
|
238
|
+
keeps this computer's beside the old network. Untick it to leave them behind.
|
|
239
|
+
- **Waiting on you has become the CRM's Inbox**, and Conversations is part of the CRM.
|
|
240
|
+
- **The Social tab keeps the names of people who aren't your connections**, from your export and the
|
|
241
|
+
sync, since nothing else in the app says who they are. A connection's name still isn't copied: the
|
|
242
|
+
app has it already.
|
|
243
|
+
|
|
9
244
|
## [0.4.10] - 2026-09-29
|
|
10
245
|
|
|
11
246
|
### Changed
|
package/lib/chain-layout.js
CHANGED
|
@@ -17,19 +17,19 @@ function capacity(radius, spacing, sweep) {
|
|
|
17
17
|
return Math.max(1, sweep >= TAU - 1e-9 ? Math.floor(arc / spacing) : Math.floor(arc / spacing) + 1);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** The rings from `inner` outwards, `
|
|
21
|
-
function ringsFor(n, inner, spacing, sweep) {
|
|
20
|
+
/** The rings from `inner` outwards, `gap` apart (at least `spacing`), until `n` dots fit. */
|
|
21
|
+
function ringsFor(n, inner, spacing, sweep, gap = spacing) {
|
|
22
22
|
const radii = [];
|
|
23
23
|
let room = 0;
|
|
24
|
-
for (let r = inner; room < n; r += spacing) {
|
|
24
|
+
for (let r = inner; room < n; r += Math.max(spacing, gap)) {
|
|
25
25
|
radii.push(r);
|
|
26
26
|
room += capacity(r, spacing, sweep);
|
|
27
27
|
}
|
|
28
28
|
return radii;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const outerOf = (n, inner, spacing, sweep) => {
|
|
32
|
-
const radii = ringsFor(n, inner, spacing, sweep);
|
|
31
|
+
const outerOf = (n, inner, spacing, sweep, gap) => {
|
|
32
|
+
const radii = ringsFor(n, inner, spacing, sweep, gap);
|
|
33
33
|
return radii[radii.length - 1];
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -46,43 +46,44 @@ const outerOf = (n, inner, spacing, sweep) => {
|
|
|
46
46
|
* neighbouring rings don't line up in spokes.
|
|
47
47
|
*
|
|
48
48
|
* `start` and `sweep` fill an arc instead of the whole circle, each ring from
|
|
49
|
-
* its middle outwards.
|
|
49
|
+
* its middle outwards. `ringGap` keeps the rings at least that far apart, for
|
|
50
|
+
* dots that carry a name under them and need the room.
|
|
50
51
|
*
|
|
51
52
|
* @returns {{ spacing: number, inner: number, rings: { radius: number, count: number }[],
|
|
52
53
|
* points: { x: number, y: number, angle: number, ring: number }[] }}
|
|
53
54
|
*/
|
|
54
55
|
export function ringLayout(n, {
|
|
55
56
|
inner, outer, spacing, minSpacing = spacing / 3, innerMin = inner,
|
|
56
|
-
start = -Math.PI / 2, sweep = TAU,
|
|
57
|
+
start = -Math.PI / 2, sweep = TAU, ringGap = 0,
|
|
57
58
|
} = {}) {
|
|
58
59
|
const count = Math.max(0, Math.floor(n) || 0);
|
|
59
60
|
if (!count) return { spacing, inner, rings: [], points: [] };
|
|
60
61
|
|
|
61
62
|
let s = spacing;
|
|
62
63
|
let r0 = inner;
|
|
63
|
-
if (outerOf(count, r0, s, sweep) > outer) {
|
|
64
|
+
if (outerOf(count, r0, s, sweep, ringGap) > outer) {
|
|
64
65
|
// Start further in, as far as innerMin, before closing anything up.
|
|
65
|
-
if (outerOf(count, innerMin, s, sweep) <= outer) {
|
|
66
|
+
if (outerOf(count, innerMin, s, sweep, ringGap) <= outer) {
|
|
66
67
|
let lo = innerMin, hi = inner;
|
|
67
68
|
for (let i = 0; i < 24; i++) {
|
|
68
69
|
const mid = (lo + hi) / 2;
|
|
69
|
-
if (outerOf(count, mid, s, sweep) <= outer) lo = mid; else hi = mid;
|
|
70
|
+
if (outerOf(count, mid, s, sweep, ringGap) <= outer) lo = mid; else hi = mid;
|
|
70
71
|
}
|
|
71
72
|
r0 = lo;
|
|
72
73
|
} else {
|
|
73
74
|
r0 = innerMin;
|
|
74
75
|
let lo = minSpacing, hi = spacing;
|
|
75
|
-
if (outerOf(count, r0, lo, sweep) <= outer) {
|
|
76
|
+
if (outerOf(count, r0, lo, sweep, ringGap) <= outer) {
|
|
76
77
|
for (let i = 0; i < 24; i++) {
|
|
77
78
|
const mid = (lo + hi) / 2;
|
|
78
|
-
if (outerOf(count, r0, mid, sweep) <= outer) lo = mid; else hi = mid;
|
|
79
|
+
if (outerOf(count, r0, mid, sweep, ringGap) <= outer) lo = mid; else hi = mid;
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
s = lo;
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
const radii = ringsFor(count, r0, s, sweep);
|
|
86
|
+
const radii = ringsFor(count, r0, s, sweep, ringGap);
|
|
86
87
|
const caps = radii.map((r) => capacity(r, s, sweep));
|
|
87
88
|
const room = caps.reduce((a, b) => a + b, 0);
|
|
88
89
|
|
|
@@ -162,8 +163,9 @@ const TIER_BANDS = ['S', 'A', 'B', 'C', 'D'];
|
|
|
162
163
|
*
|
|
163
164
|
* @returns {{ spacing: number, edge: number, bands: { tier: string, inner: number, outer: number }[],
|
|
164
165
|
* points: { x: number, y: number, angle: number, slot: number }[] }}
|
|
166
|
+
* `start` is the angle each ring's first dot sits at (the top, unless told).
|
|
165
167
|
*/
|
|
166
|
-
export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spacing / 3, gap = spacing }) {
|
|
168
|
+
export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spacing / 3, gap = spacing, start = -Math.PI / 2 }) {
|
|
167
169
|
const groups = TIER_BANDS.map((tier) => ({ tier, idx: [] }));
|
|
168
170
|
rows.forEach((row, i) => {
|
|
169
171
|
const t = TIER_BANDS.indexOf(row?.tier);
|
|
@@ -175,7 +177,7 @@ export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spaci
|
|
|
175
177
|
const bands = [];
|
|
176
178
|
let r = inner;
|
|
177
179
|
for (const g of used) {
|
|
178
|
-
const l = ringLayout(g.idx.length, { inner: r, outer: Infinity, spacing: s, minSpacing: s });
|
|
180
|
+
const l = ringLayout(g.idx.length, { inner: r, outer: Infinity, spacing: s, minSpacing: s, start });
|
|
179
181
|
g.idx.forEach((rowIndex, j) => {
|
|
180
182
|
const p = l.points[j];
|
|
181
183
|
points[rowIndex] = { x: p.x, y: p.y, angle: p.angle, slot: TAU / l.rings[p.ring].count };
|
|
@@ -201,28 +203,96 @@ export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spaci
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
/**
|
|
204
|
-
* The
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* behind its own person. At most `cap` dots a fan and about `total` in all, so
|
|
208
|
-
* a big network stays quick; `more` is how many weren't drawn.
|
|
206
|
+
* The chains that lead on from an opened circle (Blake, 2026-10-02: "once they
|
|
207
|
+
* are clicked on and can view the cluster i want any scanned d2,3,4 or whatever
|
|
208
|
+
* to be show inside with lines out of the d1 leading to the d2s").
|
|
209
209
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
210
|
+
* `parents[i]` is the index of the link that introduced link i, or -1 when the
|
|
211
|
+
* person in the middle did. The links one step on sit on a ring at `from`, each
|
|
212
|
+
* as near its own angle in `angles` as it can get (where that person's dot is
|
|
213
|
+
* in the circle), or fanned about `toward` without one; each later step sits `hop` further out,
|
|
214
|
+
* as near the angle of the link it came from as it can get without touching a
|
|
215
|
+
* neighbour on its own ring (`spacing` apart). So a chain reads outwards from
|
|
216
|
+
* the middle: the bridge, someone met through them, someone met through *them*.
|
|
217
|
+
*
|
|
218
|
+
* @param {number[]} parents
|
|
219
|
+
* @returns {{ rings: { radius: number, count: number, depth: number }[],
|
|
220
|
+
* points: { x: number, y: number, angle: number, radius: number, depth: number, parent: number }[] }}
|
|
212
221
|
*/
|
|
213
|
-
export function
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
export function chainTree(parents = [], { from = 0, hop = 60, spacing = 46, toward = 0, angles = [] } = {}) {
|
|
223
|
+
const n = parents.length;
|
|
224
|
+
const parent = parents.map((p, i) => (Number.isInteger(p) && p >= 0 && p < n && p !== i ? p : -1));
|
|
225
|
+
|
|
226
|
+
// How many steps out each one is. A loop (it shouldn't happen) is cut where
|
|
227
|
+
// it closes, so everyone still gets a place.
|
|
228
|
+
const depth = new Array(n).fill(0);
|
|
229
|
+
for (let i = 0; i < n; i++) {
|
|
230
|
+
const trail = [];
|
|
231
|
+
let k = i;
|
|
232
|
+
while (k >= 0 && !depth[k] && !trail.includes(k)) { trail.push(k); k = parent[k]; }
|
|
233
|
+
if (k >= 0 && !depth[k]) { parent[trail[trail.length - 1]] = -1; k = -1; }
|
|
234
|
+
let d = k >= 0 ? depth[k] + 1 : 1;
|
|
235
|
+
for (let j = trail.length - 1; j >= 0; j--) depth[trail[j]] = d++;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const points = new Array(n);
|
|
239
|
+
const rings = [];
|
|
240
|
+
for (let d = 1; d <= Math.max(0, ...depth); d++) {
|
|
241
|
+
const here = [];
|
|
242
|
+
for (let i = 0; i < n; i++) if (depth[i] === d) here.push(i);
|
|
243
|
+
const radius = from + (d - 1) * hop;
|
|
244
|
+
const placed = spread(here.map((i) => (parent[i] < 0 ? (Number.isFinite(angles[i]) ? angles[i] : toward) : points[parent[i]].angle)),
|
|
245
|
+
Math.min(spacing / Math.max(1, radius), TAU / here.length));
|
|
246
|
+
here.forEach((i, j) => {
|
|
247
|
+
const angle = placed[j];
|
|
248
|
+
points[i] = { x: radius * Math.cos(angle), y: radius * Math.sin(angle), angle, radius, depth: d, parent: parent[i] };
|
|
225
249
|
});
|
|
226
|
-
|
|
250
|
+
rings.push({ radius, count: here.length, depth: d });
|
|
251
|
+
}
|
|
252
|
+
return { rings, points };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Angles as near the ones asked for as they can be while staying `sep` apart:
|
|
257
|
+
* neighbours that would touch move apart by the same amount each way, so two
|
|
258
|
+
* people met through the same person sit either side of them. Comes back in
|
|
259
|
+
* the order given.
|
|
260
|
+
*/
|
|
261
|
+
function spread(wanted, sep) {
|
|
262
|
+
const m = wanted.length;
|
|
263
|
+
if (m < 2) return wanted.slice();
|
|
264
|
+
const turn = (a) => ((a % TAU) + TAU) % TAU;
|
|
265
|
+
const order = wanted.map((a, i) => i).sort((i, j) => turn(wanted[i]) - turn(wanted[j]) || i - j);
|
|
266
|
+
// Start counting just after the widest empty stretch, so no group straddles the join.
|
|
267
|
+
let cut = 0;
|
|
268
|
+
let widest = -1;
|
|
269
|
+
order.forEach((i, k) => {
|
|
270
|
+
const next = order[(k + 1) % m];
|
|
271
|
+
const gap = turn(turn(wanted[next]) - turn(wanted[i])) || (m > 1 && k === m - 1 ? TAU : 0);
|
|
272
|
+
if (gap > widest) { widest = gap; cut = (k + 1) % m; }
|
|
227
273
|
});
|
|
274
|
+
const seq = order.slice(cut).concat(order.slice(0, cut));
|
|
275
|
+
const first = turn(wanted[seq[0]]);
|
|
276
|
+
const at = seq.map((i) => first + turn(turn(wanted[i]) - first));
|
|
277
|
+
|
|
278
|
+
// Least movement that keeps them `sep` apart: take the spacing out, pool any
|
|
279
|
+
// neighbours left out of order to their average, put the spacing back.
|
|
280
|
+
const blocks = [];
|
|
281
|
+
at.forEach((a, k) => {
|
|
282
|
+
blocks.push({ sum: a - k * sep, count: 1 });
|
|
283
|
+
while (blocks.length > 1 && blocks[blocks.length - 2].sum / blocks[blocks.length - 2].count > blocks[blocks.length - 1].sum / blocks[blocks.length - 1].count) {
|
|
284
|
+
const last = blocks.pop();
|
|
285
|
+
blocks[blocks.length - 1].sum += last.sum;
|
|
286
|
+
blocks[blocks.length - 1].count += last.count;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
let placed = blocks.flatMap((b) => new Array(b.count).fill(b.sum / b.count)).map((q, k) => q + k * sep);
|
|
290
|
+
// Round the join too: if the last has come back round onto the first, share the ring evenly.
|
|
291
|
+
if (placed[m - 1] - placed[0] > TAU - sep) {
|
|
292
|
+
const mid = (placed[0] + placed[m - 1]) / 2;
|
|
293
|
+
placed = placed.map((_, k) => mid + (k - (m - 1) / 2) * (TAU / m));
|
|
294
|
+
}
|
|
295
|
+
const out = new Array(m);
|
|
296
|
+
seq.forEach((i, k) => { out[i] = placed[k]; });
|
|
297
|
+
return out;
|
|
228
298
|
}
|
package/lib/data-export.js
CHANGED
|
@@ -14,7 +14,7 @@ import { DatabaseSync } from 'node:sqlite';
|
|
|
14
14
|
import { readFileSync, lstatSync, statSync } from 'node:fs';
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
-
import { AVATAR_FILE, TRAVELLING_FILES, namesInFolder } from './data-folder.js';
|
|
17
|
+
import { AVATAR_FILE, TRAVELLING_FILES, SOCIAL_FILE, namesInFolder } from './data-folder.js';
|
|
18
18
|
|
|
19
19
|
/** Bump when the file's layout changes in a way an older import can't read. */
|
|
20
20
|
export const EXPORT_FORMAT = 1;
|
|
@@ -72,7 +72,7 @@ export function referencedPhotos(db) {
|
|
|
72
72
|
* plain files are taken, never a link, and avatars/ is read only if it is a
|
|
73
73
|
* real folder, so nothing outside the data folder can be pulled in through one.
|
|
74
74
|
*/
|
|
75
|
-
export function travellingFiles(dir, { includePhotos = true, photos = null } = {}) {
|
|
75
|
+
export function travellingFiles(dir, { includePhotos = true, photos = null, includeSocial = true } = {}) {
|
|
76
76
|
const plain = (p) => {
|
|
77
77
|
try { return lstatSync(p).isFile(); } catch { return false; }
|
|
78
78
|
};
|
|
@@ -81,6 +81,12 @@ export function travellingFiles(dir, { includePhotos = true, photos = null } = {
|
|
|
81
81
|
const abs = path.join(dir, name);
|
|
82
82
|
if (plain(abs)) out.push({ rel: name, abs });
|
|
83
83
|
}
|
|
84
|
+
if (includeSocial) {
|
|
85
|
+
for (const name of namesInFolder(dir).sort()) {
|
|
86
|
+
const abs = path.join(dir, name);
|
|
87
|
+
if (SOCIAL_FILE.test(name) && plain(abs)) out.push({ rel: name, abs });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
84
90
|
if (includePhotos) {
|
|
85
91
|
for (const name of namesInFolder(path.join(dir, 'avatars')).sort()) {
|
|
86
92
|
if (!AVATAR_FILE.test(name) || (photos && !photos.has(name))) continue;
|
|
@@ -96,7 +102,7 @@ export function travellingFiles(dir, { includePhotos = true, photos = null } = {
|
|
|
96
102
|
* `outFile`, which must not exist yet.
|
|
97
103
|
* @returns {{ bytes, people, photos, files, counts }}
|
|
98
104
|
*/
|
|
99
|
-
export function buildExport(db, { dir, outFile, includePhotos = true, appVersion, schemaSql, now = new Date() }) {
|
|
105
|
+
export function buildExport(db, { dir, outFile, includePhotos = true, includeSocial = true, appVersion, schemaSql, now = new Date() }) {
|
|
100
106
|
db.exec(`VACUUM INTO ${sqlString(outFile)}`);
|
|
101
107
|
|
|
102
108
|
const out = new DatabaseSync(outFile);
|
|
@@ -127,7 +133,7 @@ export function buildExport(db, { dir, outFile, includePhotos = true, appVersion
|
|
|
127
133
|
// photo of someone whose circle was deleted since; the copy has no row
|
|
128
134
|
// for them, so their face has no reason to travel.
|
|
129
135
|
const inNetwork = includePhotos ? referencedPhotos(out) : null;
|
|
130
|
-
for (const f of travellingFiles(dir, { includePhotos, photos: inNetwork })) {
|
|
136
|
+
for (const f of travellingFiles(dir, { includePhotos, photos: inNetwork, includeSocial })) {
|
|
131
137
|
let bytes;
|
|
132
138
|
try { bytes = readFileSync(f.abs); } catch { continue; } // replaced since the listing
|
|
133
139
|
put.run(f.rel, bytes, sha256(bytes));
|
package/lib/data-folder.js
CHANGED
|
@@ -44,6 +44,15 @@ export const BUDGET_FILES = [
|
|
|
44
44
|
'linkedin-cooldown.json',
|
|
45
45
|
];
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The Social tab's files, one set per profile: its findings, the messages kept
|
|
49
|
+
* with "Keep my messages on this computer", and the CRM's stages, tags and
|
|
50
|
+
* notes (lib/social-store.js). A copy carries them when asked (Blake,
|
|
51
|
+
* 2026-09-29: everything goes with the network), and an import puts them back
|
|
52
|
+
* in place of this computer's, which are kept beside the old network.
|
|
53
|
+
*/
|
|
54
|
+
export const SOCIAL_FILE = /^(?:social|social-messages|crm)-[\w-]+\.json$/;
|
|
55
|
+
|
|
47
56
|
/** Everything of the scanner's that an export carries. */
|
|
48
57
|
export const TRAVELLING_FILES = [...NETWORK_FILES, ...BUDGET_FILES];
|
|
49
58
|
|
package/lib/data-import.js
CHANGED
|
@@ -31,7 +31,7 @@ import { runningNow } from './scan-state.js';
|
|
|
31
31
|
import { durable } from './durable.js';
|
|
32
32
|
import { budgetFileProblem, mergeBudgetFiles } from './linkedin-limits.js';
|
|
33
33
|
import {
|
|
34
|
-
AVATAR_FILE, TRAVELLING_FILES, NETWORK_FILES, BUDGET_FILES, PENDING_DIR, IMPORT_BACKUP_PREFIX,
|
|
34
|
+
AVATAR_FILE, TRAVELLING_FILES, NETWORK_FILES, BUDGET_FILES, SOCIAL_FILE, PENDING_DIR, IMPORT_BACKUP_PREFIX,
|
|
35
35
|
sweepLeftovers, namesInFolder,
|
|
36
36
|
} from './data-folder.js';
|
|
37
37
|
import {
|
|
@@ -158,10 +158,11 @@ export async function receiveUpload(body, file, { declared }) {
|
|
|
158
158
|
return received;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/** 'photo', 'file' (one of the scanner's travelling files), or null for anything an export never carries. */
|
|
161
|
+
/** 'photo', 'file' (one of the scanner's travelling files), 'social', or null for anything an export never carries. */
|
|
162
162
|
export function exportedFileKind(rel) {
|
|
163
163
|
if (typeof rel !== 'string') return null;
|
|
164
164
|
if (TRAVELLING_FILES.includes(rel)) return 'file';
|
|
165
|
+
if (SOCIAL_FILE.test(rel)) return 'social';
|
|
165
166
|
// The same pattern the /avatars route serves: no folders, no dots but the
|
|
166
167
|
// extension, so no path can climb out of avatars/.
|
|
167
168
|
if (rel.startsWith('avatars/') && AVATAR_FILE.test(rel.slice('avatars/'.length))) return 'photo';
|
|
@@ -421,7 +422,7 @@ export function stageImport(upload, {
|
|
|
421
422
|
throw new ImportError(`“${shortName(rel)}” inside this file is damaged, so nothing was imported.`);
|
|
422
423
|
}
|
|
423
424
|
writeFileSync(path.join(filesDir, rel), row.bytes, { flag: 'wx' });
|
|
424
|
-
if (kind === 'file') scannerFiles.push(path.join(filesDir, rel));
|
|
425
|
+
if (kind === 'file' || kind === 'social') scannerFiles.push(path.join(filesDir, rel));
|
|
425
426
|
}
|
|
426
427
|
} finally {
|
|
427
428
|
db.close();
|
|
@@ -731,6 +732,14 @@ export function applyPendingImport({ dir, dbFile, now = new Date(), copyDatabase
|
|
|
731
732
|
keptFolder();
|
|
732
733
|
move(from, path.join(keptFiles, name));
|
|
733
734
|
}
|
|
735
|
+
// This computer's Social files step aside only when the copy brings its
|
|
736
|
+
// own; a copy made without them leaves them where they are.
|
|
737
|
+
if (namesInFolder(path.join(pending, 'files')).some((n) => SOCIAL_FILE.test(n))) {
|
|
738
|
+
for (const name of namesInFolder(dir).filter((n) => SOCIAL_FILE.test(n))) {
|
|
739
|
+
keptFolder();
|
|
740
|
+
move(path.join(dir, name), path.join(keptFiles, name));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
734
743
|
for (const name of BUDGET_FILES) {
|
|
735
744
|
const from = path.join(dir, name);
|
|
736
745
|
if (!exists(from) || !lstatSync(from).isFile()) continue;
|
|
@@ -786,6 +795,7 @@ export function applyPendingImport({ dir, dbFile, now = new Date(), copyDatabase
|
|
|
786
795
|
const from = path.join(files, name);
|
|
787
796
|
if (exists(from)) move(from, path.join(dir, name));
|
|
788
797
|
}
|
|
798
|
+
for (const name of namesInFolder(files).filter((n) => SOCIAL_FILE.test(n))) move(path.join(files, name), path.join(dir, name));
|
|
789
799
|
mergeBudgetFiles({ dir, from: files });
|
|
790
800
|
durable.syncFolder(dir);
|
|
791
801
|
step('done');
|
package/lib/degrees.js
CHANGED
|
@@ -22,9 +22,3 @@ export function peopleByDegree(degree1 = [], degree2 = [], degree3 = []) {
|
|
|
22
22
|
};
|
|
23
23
|
return { 1: take(degree1, 1), 2: take(degree2, 2), 3: take(degree3, 3) };
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
export function tierCountsOf(rows = []) {
|
|
27
|
-
const counts = {};
|
|
28
|
-
for (const row of rows) counts[row.tier] = (counts[row.tier] || 0) + 1;
|
|
29
|
-
return counts;
|
|
30
|
-
}
|
package/lib/dot-rings.js
CHANGED
|
@@ -18,4 +18,29 @@ export function ringSegments(r) {
|
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The ring round a bridge in Bridge Chains (Blake, 2026-10-02: "the bars on the
|
|
23
|
+
* ring should signify how many dots in the clusters are ready to be scanned
|
|
24
|
+
* and new clusters formed"). One bar for each person you added through that
|
|
25
|
+
* circle: the ones scanned since, with a cluster of their own (`formed`),
|
|
26
|
+
* then the ones waiting for a scan (`ready`). Past `max` bars the two kinds
|
|
27
|
+
* share them in proportion, each keeping at least one.
|
|
28
|
+
*
|
|
29
|
+
* @returns {{ d: string, kind: 'formed' | 'ready' }[]}
|
|
30
|
+
*/
|
|
31
|
+
export function reachSegments(r, { formed = 0, ready = 0 } = {}, max = 12) {
|
|
32
|
+
const total = Math.max(0, formed) + Math.max(0, ready);
|
|
33
|
+
if (!total) return [];
|
|
34
|
+
const n = Math.min(total, max);
|
|
35
|
+
let nFormed = formed > 0 ? Math.max(1, Math.round((n * formed) / total)) : 0;
|
|
36
|
+
if (ready > 0 && nFormed >= n) nFormed = n - 1;
|
|
37
|
+
const gap = n === 1 ? 0.5 : Math.min(GAP, ((2 * Math.PI) / n) * 0.35);
|
|
38
|
+
const span = (2 * Math.PI) / n - gap;
|
|
39
|
+
const p = (a) => `${(r * Math.cos(a)).toFixed(2)} ${(r * Math.sin(a)).toFixed(2)}`;
|
|
40
|
+
return Array.from({ length: n }, (_, i) => {
|
|
41
|
+
const a0 = -Math.PI / 2 + gap / 2 + i * (span + gap);
|
|
42
|
+
return { kind: i < nFormed ? 'formed' : 'ready', d: `M${p(a0)} A${r} ${r} 0 ${span > Math.PI ? 1 : 0} 1 ${p(a0 + span)}` };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
export const RING = { filled: '#1abc9c', empty: 'rgba(255,255,255,0.16)', badge: '#7F77DD' };
|