six-degrees 0.4.3 → 0.4.4
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 +3 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +41 -13
- package/.next/standalone/.next/routes-manifest.json +12 -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 +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 +18 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +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.nft.json +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.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.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.nft.json +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.nft.json +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.nft.json +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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/route.js +8 -0
- package/.next/standalone/.next/server/app/api/social/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/route_client-reference-manifest.js +3 -0
- 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.nft.json +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 +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 +22 -21
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +20 -19
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +20 -19
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch/page.js +1 -1
- 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 +22 -21
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/page.js +1 -1
- 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 +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 +22 -21
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/profile/page.js +1 -1
- 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 +22 -21
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/queue/page.js +1 -1
- 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 +22 -21
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/scores/page.js +1 -1
- 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 +22 -21
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- 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 +22 -21
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/setup/page.js +1 -1
- 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 +22 -21
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/social/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/social/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/social/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/social/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/social/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/social/page.js +13 -0
- package/.next/standalone/.next/server/app/social/page.js.map +5 -0
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/social.html +1 -0
- package/.next/standalone/.next/server/app/social.meta +26 -0
- package/.next/standalone/.next/server/app/social.rsc +26 -0
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +26 -0
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +30 -0
- package/.next/standalone/.next/server/app-paths-manifest.json +3 -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]__17kn1mk._.js +8 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0uag-k5._.js → [root-of-the-server]__1dmc7-t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_route_actions_1_3c6qn.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16hs8kw._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1feicqq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jeztd-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_005q9n1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08_03wh._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08cfj2b._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0hw_qnj._.js → _0wxbq78._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1cyxocj._.js → _1ap1tu_._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1mj739k._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_social_page_actions_176i1kl.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +3 -0
- 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/04a3zockahetx.js +1 -0
- package/.next/standalone/.next/static/chunks/{2wwmljnf7yn_5.js → 0a5iadnmt9u9k.js} +1 -1
- package/.next/standalone/.next/static/chunks/0g_p4p7oci18u.js +1 -0
- package/.next/{static/chunks/0f4ugd6v3ph0-.js → standalone/.next/static/chunks/1ra4cwcp75tfa.js} +1 -1
- package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +1 -0
- package/.next/standalone/.next/static/chunks/{1zwjy1mmpkdrg.js → 24m745q_85u34.js} +2 -2
- package/.next/standalone/.next/static/chunks/2hvcevue9aro4.js +1 -0
- package/.next/standalone/.next/static/chunks/2uo9i03_05rqi.js +18 -0
- package/.next/standalone/.next/static/chunks/3p_pwu5gx2i9h.js +1 -0
- package/.next/standalone/.next/static/chunks/3yc3rzp4dxvyo.js +1 -0
- package/.next/standalone/.next/static/chunks/{0_3ubivay59bq.js → 3zw8bv7-mc4n6.js} +1 -1
- package/.next/standalone/CHANGELOG.md +36 -0
- package/.next/standalone/app/api/network/route.js +11 -0
- package/.next/standalone/app/api/scraper/route.js +2 -0
- package/.next/standalone/app/api/social/route.js +96 -0
- package/.next/standalone/app/components/AutoScanButton.js +41 -0
- package/.next/standalone/app/components/ChainView.js +7 -0
- package/.next/standalone/app/components/ScanStatusBar.js +22 -8
- package/.next/standalone/app/components/SeparationView.js +9 -1
- package/.next/standalone/app/components/Sidebar.js +88 -26
- package/.next/standalone/app/components/SocialAutoSync.js +34 -0
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +85 -0
- package/.next/standalone/app/layout.js +3 -0
- package/.next/standalone/app/page.js +29 -0
- package/.next/standalone/app/scores/page.js +3 -0
- package/.next/standalone/app/setup/page.js +1 -1
- package/.next/standalone/app/social/page.js +293 -0
- package/.next/standalone/lib/experimental-client.js +38 -0
- package/.next/standalone/lib/insights.js +54 -0
- package/.next/standalone/lib/linkedin-export.js +156 -0
- package/.next/standalone/lib/network-health.js +42 -0
- package/.next/standalone/lib/network.js +1 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +118 -2
- package/.next/static/chunks/04a3zockahetx.js +1 -0
- package/.next/static/chunks/{2wwmljnf7yn_5.js → 0a5iadnmt9u9k.js} +1 -1
- package/.next/static/chunks/0g_p4p7oci18u.js +1 -0
- package/.next/{standalone/.next/static/chunks/0f4ugd6v3ph0-.js → static/chunks/1ra4cwcp75tfa.js} +1 -1
- package/.next/static/chunks/236f0jtp68j_h.js +1 -0
- package/.next/static/chunks/{1zwjy1mmpkdrg.js → 24m745q_85u34.js} +2 -2
- package/.next/static/chunks/2hvcevue9aro4.js +1 -0
- package/.next/static/chunks/2uo9i03_05rqi.js +18 -0
- package/.next/static/chunks/3p_pwu5gx2i9h.js +1 -0
- package/.next/static/chunks/3yc3rzp4dxvyo.js +1 -0
- package/.next/static/chunks/{0_3ubivay59bq.js → 3zw8bv7-mc4n6.js} +1 -1
- package/CHANGELOG.md +36 -0
- package/lib/experimental-client.js +38 -0
- package/lib/insights.js +54 -0
- package/lib/linkedin-export.js +156 -0
- package/lib/network-health.js +42 -0
- package/lib/network.js +1 -0
- package/package.json +1 -1
- package/scripts/scrape.py +118 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d3vjsq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0m4vk0b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_012mop3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1163x49._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1av11ws._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1ig7em7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +0 -3
- package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +0 -1
- package/.next/standalone/.next/static/chunks/10ogurbcuf3nq.js +0 -1
- package/.next/standalone/.next/static/chunks/1489t7jrjojk_.js +0 -1
- package/.next/standalone/.next/static/chunks/1zunq4tk-u3ng.js +0 -1
- package/.next/standalone/.next/static/chunks/2pyo4j84prgp6.js +0 -18
- package/.next/standalone/.next/static/chunks/3kllnkb3hof1s.js +0 -1
- package/.next/static/chunks/0cuuo2jwc9h94.js +0 -1
- package/.next/static/chunks/10ogurbcuf3nq.js +0 -1
- package/.next/static/chunks/1489t7jrjojk_.js +0 -1
- package/.next/static/chunks/1zunq4tk-u3ng.js +0 -1
- package/.next/static/chunks/2pyo4j84prgp6.js +0 -18
- package/.next/static/chunks/3kllnkb3hof1s.js +0 -1
- /package/.next/standalone/.next/static/{814ehZBeFO2rUKle7mzX- → VE8LeJXfD2H0_OgJxuJcm}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{814ehZBeFO2rUKle7mzX- → VE8LeJXfD2H0_OgJxuJcm}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{814ehZBeFO2rUKle7mzX- → VE8LeJXfD2H0_OgJxuJcm}/_ssgManifest.js +0 -0
- /package/.next/static/{814ehZBeFO2rUKle7mzX- → VE8LeJXfD2H0_OgJxuJcm}/_buildManifest.js +0 -0
- /package/.next/static/{814ehZBeFO2rUKle7mzX- → VE8LeJXfD2H0_OgJxuJcm}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{814ehZBeFO2rUKle7mzX- → VE8LeJXfD2H0_OgJxuJcm}/_ssgManifest.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,2776,e=>{"use strict";var t=e.i(43476),n=e.i(15812);e.s(["default",0,function({children:e}){let{userId:r,ready:o}=(0,n.useUser)();return o&&r?e:(0,t.jsx)("div",{style:{minHeight:"100vh",display:"flex",alignItems:"center",justifyContent:"center",background:"#0a0a1a",color:"#fff",padding:24,textAlign:"center",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'},children:o?(0,t.jsxs)("div",{style:{maxWidth:420},children:[(0,t.jsx)("div",{style:{fontSize:18,fontWeight:700,marginBottom:8},children:"Can’t reach Six Degrees’ local server"}),(0,t.jsx)("div",{style:{color:"#888",fontSize:14,lineHeight:1.6},children:"The page loaded but the app behind it is not answering. If you started it from a terminal, check that window for an error, then reload this page."})]}):(0,t.jsx)("div",{style:{fontSize:18,fontWeight:600},children:"Loading…"})})}])},28867,86106,e=>{"use strict";var t=e.i(71645),n=e.i(7214);let r=Object.freeze({known:!1,sent:new Set,mine:new Map,version:0}),o=new Set,l=r,i=null,a=new Map;function s(e=l.sent,t=l.known){for(let n of(l=Object.freeze({known:t,sent:e,mine:new Map(a),version:l.version+1}),[...o]))n()}function d(e){return o.add(e),i||l.known||(i=fetch("/api/outreach").then(e=>e.ok?e.json():null).then(e=>{e&&s(new Set((e.pending||[]).map(n.keyFor)),!0)}).catch(()=>{}).finally(()=>{i=null})),()=>o.delete(e)}function u(){return l}async function c(e){let t=await fetch("/api/outreach",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),n=await t.json().catch(()=>({}));if(!t.ok)throw Error(n.error||"Could not save that.");return n}async function f(e,{bridgeId:t}={}){let r=(0,n.keyFor)(e),o=a.has(r)?a.get(r):void 0;a.set(r,!0),s();try{return await c({action:"mark-sent",connectionId:e.id,profileUrl:e.profile_url,bridgeId:t})}catch(e){throw void 0===o?a.delete(r):a.set(r,o),s(),e}}async function h(e){let t=(0,n.keyFor)(e),r=a.has(t)?a.get(t):void 0;a.set(t,!1),s();try{return await c({action:"undo",connectionId:e.id,profileUrl:e.profile_url})}catch(e){throw void 0===r?a.delete(t):a.set(t,r),s(),e}}e.s(["REQUESTS_UNKNOWN",0,r,"hasRequest",0,function(e,t=l){if(!e)return!1;let r=(0,n.keyFor)(e);return t.mine.has(r)?t.mine.get(r):t.known?t.sent.has(r):"pending"===e.unlock_status||"sent"===e.outreach_status},"markRequested",0,f,"requestCount",0,function(e=l){let t=new Set(e.known?e.sent:[]);for(let[n,r]of e.mine)r?t.add(n):t.delete(n);return t.size},"requestsNow",0,u,"undoRequest",0,h,"watchRequests",0,d],86106),e.s(["default",0,function(){return(0,t.useSyncExternalStore)(d,u,()=>r)}],28867)},64666,e=>{"use strict";var t=e.i(7214);function n(e=[],r=[]){let o=new Set((r||[]).map(t.keyFor)),l=new Map;for(let n of e||[]){let e=n?.source_connection_id;if(null==e)continue;let r=(0,t.keyFor)(n);if(o.has(r))continue;let i=l.get(r);i||l.set(r,i=new Set),i.add(e)}let i=new Map;for(let e of l.values())for(let t of e){let n=i.get(t)||{reach:0,only:0,total:0};n.reach+=1/e.size,n.total+=1,1===e.size&&(n.only+=1),i.set(t,n)}return i}e.s(["bridgeOverlap",0,function(e=[],r=[]){let o=n(e,r),l=new Set((r||[]).map(t.keyFor)),i=new Map;for(let n of e||[]){let e=n?.source_connection_id;if(null==e)continue;let r=(0,t.keyFor)(n);if(l.has(r))continue;let o=i.get(r);o||i.set(r,o=new Set),o.add(e)}let a=new Map;for(let e of i.values()){if(e.size<2)continue;let t=[...e];for(let e=0;e<t.length;e++)for(let n=e+1;n<t.length;n++){let r=t[e]<t[n]?`${t[e]}|${t[n]}`:`${t[n]}|${t[e]}`;a.set(r,(a.get(r)||0)+1)}}let s=new Map;for(let[e,t]of a){let[n,r]=e.split("|"),l=o.get(n).total+o.get(r).total-t,i=l?t/l:0;for(let[e,o]of[[n,r],[r,n]]){let n=s.get(e);(!n||i>n.share)&&s.set(e,{with:o,share:i,shared:t})}}return s},"exclusiveReach",0,n,"redundancy",0,function(e=[],t=[]){let r=n(e,t),o=0,l=0;for(let e of r.values())o+=e.reach,l+=e.only;return o?1-l/o:0}])},19821,e=>{"use strict";async function t(e){let t=e?`/api/network?userId=${encodeURIComponent(e)}`:"/api/network",n=await fetch(t);if(!n.ok)throw Error(`Failed to load network (${n.status})`);let r=await n.json();return{degree1:r.degree1||[],degree2:r.degree2||[],degree3:r.degree3||[],ties:Number(r.ties)||0}}e.s(["loadNetwork",0,t])},36774,e=>{"use strict";let t=/^[A-Za-z0-9_-]+\.(webp|jpg|jpeg|png)$/,n="/avatars/";e.s(["localPhoto",0,function(e){return"string"==typeof e&&e.startsWith(n)&&t.test(e.slice(n.length))?e:null}])},48235,e=>{"use strict";var t=e.i(7214);function n(e,n){return e?n.scanned.has(e.id)||e.profile_url&&n.read?.has((0,t.keyFor)(e))?"scanned":e.profile_url&&n.hidden.has((0,t.keyFor)(e))?"hidden":"todo":"todo"}function r(e,t){if(!e||1!==e.degree||null==e.unlocked_from_bridge_id)return null;let r=n(e,t);return"todo"===r?"ready":r}e.s(["circleScanCost",0,function(e=100){let t=Math.max(1,Math.min(100,Math.round(e)||100));return{profileViews:1,searches:t,minutes:t>=100?55:Math.max(5,Math.round(.55*t))}},"circleState",0,n,"reachIndex",0,function(e=[],n=[],r={}){let o=new Set;for(let e of n||[])e?.source_connection_id!=null&&o.add(e.source_connection_id);let l=new Set;for(let e of r?.skips||[])e?.profileUrl&&l.add((0,t.keyFor)({profile_url:e.profileUrl}));let i=new Set;for(let e of r?.read||[])e&&i.add((0,t.keyFor)({profile_url:e}));let a=new Map;for(let[e,n]of Object.entries(r?.lists||{}))e&&n&&a.set((0,t.keyFor)({profile_url:e}),n);let s=new Map;for(let t of e||[]){let e=t?.unlocked_from_bridge_id;if(null==e||1!==t.degree)continue;let n=s.get(e);n||s.set(e,n=[]),n.push(t)}return{scanned:o,read:i,hidden:l,reachedFrom:s,lists:a}},"reachState",0,r,"readyByCircle",0,function(e){let t=new Map;for(let[n,o]of e.reachedFrom){let l=o.filter(t=>"ready"===r(t,e)).length;l&&t.set(n,l)}return t},"readyToScan",0,function(e,n){return(e||[]).filter(e=>"ready"===r(e,n)).sort(t.compareBridges)},"scanBars",0,function(e,r){let o=n(e,r),l=e?.profile_url?r.lists?.get((0,t.keyFor)(e)):null;return"hidden"===o?null:l?l.more?l.total?Math.max(1,Math.min(4,Math.round(5*Math.min(1,10*l.pages/l.total)))):2:5:"scanned"===o?2:null}])},7214,e=>{"use strict";var t=e.i(36774);let n={S:0,A:1,B:2,C:3,D:4};function r(e){let t=parseFloat(e?.power_score);return Number.isFinite(t)?t:0}function o(e){return Math.round(10*r(e))/10}function l(e){return String(e?.profile_url||"").split(/[?#]/)[0].replace(/\/+$/,"")||`id:${e?.id}`}let i=(e,t)=>String(e??"").localeCompare(String(t??""),void 0,{numeric:!0});function a(e,t){return(n[e.tier]??9)-(n[t.tier]??9)||r(t)-r(e)||String(e.name||"").localeCompare(String(t.name||""))||i(e.id,t.id)}function s(e,t){return e.bridge&&t.bridge?a(e.bridge,t.bridge):e.bridge?-1:t.bridge?1:i(e.id,t.id)}function d(e,t){return t.score-e.score||t.waysIn-e.waysIn||s(e.routes[0]||u,t.routes[0]||u)||String(e.person.name||"").localeCompare(String(t.person.name||""))||(e.key<t.key?-1:+(e.key>t.key))}let u={id:null,bridge:null};function c(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return t.length<2?t[0]||"":`${t[0]} ${t[t.length-1].charAt(0)}.`}function f(e,t){let n=String(e||"");return t<=1?n?"…":"":n.length>t?`${n.slice(0,t-1)}…`:n}e.s(["TIERS",0,["S","A","B","C","D"],"compareBridges",0,a,"compareWaysIn",0,function(e,t){return t.waysIn-e.waysIn||d(e,t)},"keyFor",0,l,"routeIndex",0,function(e=[]){let t=new Map;for(let n of e||[]){let e=l(n),r=t.get(e);r||(r=new Set,t.set(e,r)),r.add(n.source_connection_id??null)}return t},"routesFor",0,function(e,t,n){return e?[...t?.get(l(e))??(null!=e.source_connection_id?new Set([e.source_connection_id]):new Set)].map(e=>({id:e,bridge:n?.get(e)??null})).sort(s):[]},"score",0,r,"score1",0,o,"separationPeople",0,function(e=[],n=[]){let r=new Map;for(let e of n||[])r.set(e.id,e);let a=new Map,u=new Map;for(let n of e||[]){let e=l(n);u.set(n.id,e);let r=a.get(e);r?function(e,n){let r=o(e)-o(n);if(r)return r>0;let l=!!(0,t.localPhoto)(e.profile_image_url);return!!(0,t.localPhoto)(n.profile_image_url)!==l?l:0>i(e.id,n.id)}(n,r.person)&&(r.person=n):(r={key:e,person:n,rowIds:[],routeIds:new Set},a.set(e,r)),r.rowIds.push(n.id),r.routeIds.add(n.source_connection_id??null)}let c={people:a.size,byTier:{S:0,A:0,B:0,C:0,D:0},multi:0,bridges:0,unresolvedIds:0,peopleOnlyUnresolved:0,onlyUnresolvedVia:0},f=new Set,h=new Set,g=new Set,p=[];for(let e of a.values()){let t=[...e.routeIds].map(e=>({id:e,bridge:r.get(e)??null}));t.sort(s);let n=t.filter(e=>e.bridge);for(let e of t)(e.bridge?f:h).add(e.id);if(!n.length)for(let e of(c.peopleOnlyUnresolved++,t))g.add(e.id);let{person:l}=e,i=l.tier;void 0!==c.byTier[i]&&c.byTier[i]++,t.length>1&&c.multi++,p.push({key:e.key,person:l,score:o(l),tier:i,routes:t,waysIn:t.length,rowIds:e.rowIds,haystack:[l.name,l.headline,l.company,l.role,...n.map(e=>e.bridge.name)].filter(Boolean).join("\n").toLowerCase(),rank:0,tied:!1})}return c.bridges=f.size,c.unresolvedIds=h.size,c.onlyUnresolvedVia=g.size,p.sort(d),function(e){for(let t=0;t<e.length;t++){let n=e[t],r=e[t-1],o=r&&r.score===n.score&&r.waysIn===n.waysIn;n.rank=o?r.rank:t+1,o&&(n.tied=!0,r.tied=!0)}}(p),{people:p,rowToKey:u,summary:c}},"shortName",0,c,"summitLayout",0,function(e=[],t=800,n=!1){let r=n?36:26,o=Math.round(.42*t),l=Math.round(.58*t),i=e.map((e,n)=>{var o;return{key:e.key,p:e,x:l,y:16+n*r,r:(o=e.waysIn)>=4?8.5:3===o?7:2===o?5.5:4,label:f(`#${e.rank}${e.tied?"=":""} ${e.person.name||""}`,Math.floor((t-l-60)/6.2))}}),s=new Map,d="\0unresolved";for(let e of i)for(let t of e.p.routes){let n=t.bridge?t.id:d,r=s.get(n);r||(r={id:t.bridge?t.id:null,bridge:t.bridge,unresolved:!t.bridge,ys:[],count:0},s.set(n,r)),r.count++,r.ys.push(e.y)}let u=[...s.values()].map(e=>({...e,bary:e.ys.reduce((e,t)=>e+t,0)/e.ys.length}));u.sort((e,t)=>e.bary-t.bary||e.unresolved-t.unresolved||(e.bridge&&t.bridge?a(e.bridge,t.bridge):0));let h=i.length?i[i.length-1].y:16,g=u.length>1?Math.max(18,(h-16)/(u.length-1)):0,p=Math.max(4,Math.floor((o-10-42)/6));u.forEach((e,t)=>{if(e.x=o,e.y=1===u.length?(16+h)/2:16+t*g,e.r=5,e.unresolved)e.label=f(`can't be named (${e.count})`,p);else{let t=e.bridge.name||"";e.label=t.length<=p?t:f(c(t),p)}delete e.ys});let y=new Map(u.map(e=>[e.unresolved?d:e.id,e])),w=[];for(let e of i){let t=new Set;e.p.routes.forEach((n,r)=>{let o=n.bridge?n.id:d;if(t.has(o))return;t.add(o);let l=y.get(o);w.push({key:e.key,bridgeId:l.id,primary:0===r&&!l.unresolved,unresolved:l.unresolved,tier:l.bridge?.tier??null,x1:l.x,y1:l.y,x2:e.x,y2:e.y})})}let m=Math.max(h,u.length?u[u.length-1].y:0),S=i.length?Math.ceil(m+16):0,b={x:20,y:i.length?(16+Math.max(h,m))/2:16,r:14},k=u.map(e=>({id:e.id,x1:b.x,y1:b.y,x2:e.x,y2:e.y}));return{width:t,height:S,you:b,people:i,bridges:u,links:w,spokes:k}}])},5258,e=>{"use strict";e.s(["TIER_COLORS",0,{S:"#f6c344",A:"#a78bfa",B:"#60a5fa",C:"#94a3b8",D:"#6b7280"},"TIER_COLORS_CLASSIC",0,{S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"},"TIER_ORDER",0,["S","A","B","C","D"],"TIER_RING_RADIUS",0,{S:150,A:230,B:310,C:390,D:470},"initialsFor",0,function(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===t.length?"?":(t[0].charAt(0)+(t.length>1?t[t.length-1].charAt(0):"")).toUpperCase()}])},18566,(e,t,n)=>{t.exports=e.r(76562)}]);
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
font-weight:700;font-size:13px;cursor:pointer;border:none;margin-bottom:16px}</style></head>
|
|
31
31
|
<body><h3 style="color:${_[e]}">${e}-Tier (${n.length})</h3>
|
|
32
32
|
<button class="oa" onclick="document.querySelectorAll('.btn').forEach((a,i)=>setTimeout(()=>a.click(),i*300))">
|
|
33
|
-
Open All</button>${n.map(e=>'<a class="btn" href="'+e.profile_url+'" target="_blank">'+e.name+" · "+parseFloat(e.power_score||0).toFixed(1)+"</a>").join("")}</body></html>`;window.open(URL.createObjectURL(new Blob([t],{type:"text/html"})),"_blank")},style:{marginLeft:"auto",padding:"2px 8px",borderRadius:3,border:"none",cursor:"pointer",background:"#0077B5",color:"#fff",fontSize:8,fontWeight:700},children:["Open All ",e]})]}),n.map(e=>(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"5px 8px 5px 16px",borderBottom:"1px solid rgba(255,255,255,0.02)",background:b.has(e.id)?"rgba(255,215,0,0.05)":"transparent",cursor:"pointer"},onMouseEnter:t=>{b.has(e.id)||(t.currentTarget.style.background="rgba(255,255,255,0.04)")},onMouseLeave:t=>{b.has(e.id)||(t.currentTarget.style.background="transparent")},children:[(0,t.jsx)("input",{type:"checkbox",checked:b.has(e.id),onChange:()=>X(e.id),onClick:e=>e.stopPropagation(),style:{cursor:"pointer",flexShrink:0}}),(0,c.localPhoto)(e.profile_image_url)?(0,t.jsx)("img",{src:(0,c.localPhoto)(e.profile_image_url),alt:"",style:{width:28,height:28,borderRadius:"50%",objectFit:"cover",flexShrink:0,border:`1.5px solid ${_[e.tier]||"#555"}`},onError:e=>{e.target.style.display="none"}}):(0,t.jsx)("div",{style:{width:28,height:28,borderRadius:"50%",flexShrink:0,fontSize:10,fontWeight:700,background:_[e.tier]||"#555",color:"S"===e.tier?"#000":"#fff",display:"flex",alignItems:"center",justifyContent:"center"},children:e.name?.charAt(0)}),(0,t.jsxs)("div",{style:{flex:1,minWidth:0},onClick:()=>X(e.id),children:[(0,t.jsx)("div",{style:{fontSize:11,fontWeight:600,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name}),(0,t.jsx)("div",{style:{fontSize:8,color:"#666",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.headline?.substring(0,45)})]}),(0,t.jsx)("span",{style:{fontSize:9,fontWeight:700,color:_[e.tier],flexShrink:0},children:parseFloat(e.power_score||0).toFixed(1)}),q.has(e.id)?(0,t.jsx)("span",{style:{padding:"3px 6px",borderRadius:3,fontSize:8,fontWeight:700,background:"rgba(255,107,53,0.15)",color:"#FF6B35",flexShrink:0},children:"Sent"}):(0,t.jsx)("a",{href:e.profile_url,target:"_blank",rel:"noopener noreferrer",onClick:t=>{t.stopPropagation(),(0,j.markRequested)(e,{bridgeId:e.source_connection_id}).catch(()=>{})},style:{padding:"3px 6px",borderRadius:3,fontSize:8,fontWeight:700,background:"#0077B5",color:"#fff",textDecoration:"none",flexShrink:0},children:"Add"})]},e.id))]},e)}):r.map(e=>(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"6px 8px",borderBottom:"1px solid rgba(255,255,255,0.03)",background:b.has(e.id)?"rgba(255,215,0,0.05)":"transparent",cursor:"pointer"},onMouseEnter:t=>{b.has(e.id)||(t.currentTarget.style.background="rgba(255,255,255,0.04)")},onMouseLeave:t=>{b.has(e.id)||(t.currentTarget.style.background="transparent")},children:[(0,t.jsx)("input",{type:"checkbox",checked:b.has(e.id),onChange:()=>X(e.id),onClick:e=>e.stopPropagation(),style:{cursor:"pointer",flexShrink:0}}),(0,c.localPhoto)(e.profile_image_url)?(0,t.jsx)("img",{src:(0,c.localPhoto)(e.profile_image_url),alt:"",style:{width:32,height:32,borderRadius:"50%",objectFit:"cover",flexShrink:0,border:`1.5px solid ${_[e.tier]||"#555"}`},onError:e=>{e.target.style.display="none",e.target.nextSibling.style.display="flex"}}):null,(0,t.jsx)("div",{style:{width:32,height:32,borderRadius:"50%",flexShrink:0,fontSize:12,fontWeight:700,background:_[e.tier]||"#555",color:"S"===e.tier?"#000":"#fff",display:(0,c.localPhoto)(e.profile_image_url)?"none":"flex",alignItems:"center",justifyContent:"center"},children:e.name?.charAt(0)}),(0,t.jsxs)("div",{style:{flex:1,minWidth:0},onClick:()=>X(e.id),children:[(0,t.jsx)("div",{style:{fontSize:12,fontWeight:600,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name}),(0,t.jsx)("div",{style:{fontSize:9,color:"#666",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.headline?.substring(0,50)})]}),e.bridge&&(0,t.jsxs)("span",{style:{fontSize:8,color:"#555",flexShrink:0,maxWidth:60,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:["via ",e.bridge.name?.split(" ")[0]]}),(0,t.jsx)("div",{style:{textAlign:"right",flexShrink:0,minWidth:32},children:(0,t.jsx)("div",{style:{fontSize:10,fontWeight:700,color:_[e.tier]},children:parseFloat(e.power_score).toFixed(1)})}),q.has(e.id)?(0,t.jsx)("span",{style:{padding:"4px 8px",borderRadius:4,fontSize:9,fontWeight:700,background:"rgba(255,107,53,0.15)",color:"#FF6B35",flexShrink:0},children:"⏳ Sent"}):(0,t.jsx)("a",{href:e.profile_url,target:"_blank",rel:"noopener noreferrer",onClick:t=>{t.stopPropagation(),(0,j.markRequested)(e,{bridgeId:e.source_connection_id}).catch(()=>{})},style:{padding:"4px 8px",borderRadius:4,fontSize:9,fontWeight:700,background:"#0077B5",color:"#fff",textDecoration:"none",flexShrink:0},children:"Add"})]},e.id))]},e))]})]})})]})}e.s(["default",0,function(){return(0,t.jsx)(y.default,{children:(0,t.jsx)(F,{})})}],77305)},19821,e=>{"use strict";async function t(e){let t=e?`/api/network?userId=${encodeURIComponent(e)}`:"/api/network",r=await fetch(t);if(!r.ok)throw Error(`Failed to load network (${r.status})`);let n=await r.json();return{degree1:n.degree1||[],degree2:n.degree2||[],degree3:n.degree3||[]}}e.s(["loadNetwork",0,t])},36774,e=>{"use strict";let t=/^[A-Za-z0-9_-]+\.(webp|jpg|jpeg|png)$/,r="/avatars/";e.s(["localPhoto",0,function(e){return"string"==typeof e&&e.startsWith(r)&&t.test(e.slice(r.length))?e:null}])},48235,7214,e=>{"use strict";var t=e.i(36774);let r={S:0,A:1,B:2,C:3,D:4};function n(e){let t=parseFloat(e?.power_score);return Number.isFinite(t)?t:0}function i(e){return Math.round(10*n(e))/10}function o(e){return String(e?.profile_url||"").split(/[?#]/)[0].replace(/\/+$/,"")||`id:${e?.id}`}let l=(e,t)=>String(e??"").localeCompare(String(t??""),void 0,{numeric:!0});function s(e,t){return(r[e.tier]??9)-(r[t.tier]??9)||n(t)-n(e)||String(e.name||"").localeCompare(String(t.name||""))||l(e.id,t.id)}function d(e,t){return e.bridge&&t.bridge?s(e.bridge,t.bridge):e.bridge?-1:t.bridge?1:l(e.id,t.id)}function a(e,t){return t.score-e.score||t.waysIn-e.waysIn||d(e.routes[0]||c,t.routes[0]||c)||String(e.person.name||"").localeCompare(String(t.person.name||""))||(e.key<t.key?-1:+(e.key>t.key))}let c={id:null,bridge:null};function g(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return t.length<2?t[0]||"":`${t[0]} ${t[t.length-1].charAt(0)}.`}function p(e,t){let r=String(e||"");return t<=1?r?"…":"":r.length>t?`${r.slice(0,t-1)}…`:r}function h(e,t){return e?t.scanned.has(e.id)||e.profile_url&&t.read?.has(o(e))?"scanned":e.profile_url&&t.hidden.has(o(e))?"hidden":"todo":"todo"}function f(e,t){if(!e||1!==e.degree||null==e.unlocked_from_bridge_id)return null;let r=h(e,t);return"todo"===r?"ready":r}e.s(["TIERS",0,["S","A","B","C","D"],"compareBridges",0,s,"compareWaysIn",0,function(e,t){return t.waysIn-e.waysIn||a(e,t)},"keyFor",0,o,"routeIndex",0,function(e=[]){let t=new Map;for(let r of e||[]){let e=o(r),n=t.get(e);n||(n=new Set,t.set(e,n)),n.add(r.source_connection_id??null)}return t},"routesFor",0,function(e,t,r){return e?[...t?.get(o(e))??(null!=e.source_connection_id?new Set([e.source_connection_id]):new Set)].map(e=>({id:e,bridge:r?.get(e)??null})).sort(d):[]},"score",0,n,"score1",0,i,"separationPeople",0,function(e=[],r=[]){let n=new Map;for(let e of r||[])n.set(e.id,e);let s=new Map,c=new Map;for(let r of e||[]){let e=o(r);c.set(r.id,e);let n=s.get(e);n?function(e,r){let n=i(e)-i(r);if(n)return n>0;let o=!!(0,t.localPhoto)(e.profile_image_url);return!!(0,t.localPhoto)(r.profile_image_url)!==o?o:0>l(e.id,r.id)}(r,n.person)&&(n.person=r):(n={key:e,person:r,rowIds:[],routeIds:new Set},s.set(e,n)),n.rowIds.push(r.id),n.routeIds.add(r.source_connection_id??null)}let g={people:s.size,byTier:{S:0,A:0,B:0,C:0,D:0},multi:0,bridges:0,unresolvedIds:0,peopleOnlyUnresolved:0,onlyUnresolvedVia:0},p=new Set,h=new Set,f=new Set,u=[];for(let e of s.values()){let t=[...e.routeIds].map(e=>({id:e,bridge:n.get(e)??null}));t.sort(d);let r=t.filter(e=>e.bridge);for(let e of t)(e.bridge?p:h).add(e.id);if(!r.length)for(let e of(g.peopleOnlyUnresolved++,t))f.add(e.id);let{person:o}=e,l=o.tier;void 0!==g.byTier[l]&&g.byTier[l]++,t.length>1&&g.multi++,u.push({key:e.key,person:o,score:i(o),tier:l,routes:t,waysIn:t.length,rowIds:e.rowIds,haystack:[o.name,o.headline,o.company,o.role,...r.map(e=>e.bridge.name)].filter(Boolean).join("\n").toLowerCase(),rank:0,tied:!1})}return g.bridges=p.size,g.unresolvedIds=h.size,g.onlyUnresolvedVia=f.size,u.sort(a),function(e){for(let t=0;t<e.length;t++){let r=e[t],n=e[t-1],i=n&&n.score===r.score&&n.waysIn===r.waysIn;r.rank=i?n.rank:t+1,i&&(r.tied=!0,n.tied=!0)}}(u),{people:u,rowToKey:c,summary:g}},"shortName",0,g,"summitLayout",0,function(e=[],t=800,r=!1){let n=r?36:26,i=Math.round(.42*t),o=Math.round(.58*t),l=e.map((e,r)=>{var i;return{key:e.key,p:e,x:o,y:16+r*n,r:(i=e.waysIn)>=4?8.5:3===i?7:2===i?5.5:4,label:p(`#${e.rank}${e.tied?"=":""} ${e.person.name||""}`,Math.floor((t-o-60)/6.2))}}),d=new Map,a="\0unresolved";for(let e of l)for(let t of e.p.routes){let r=t.bridge?t.id:a,n=d.get(r);n||(n={id:t.bridge?t.id:null,bridge:t.bridge,unresolved:!t.bridge,ys:[],count:0},d.set(r,n)),n.count++,n.ys.push(e.y)}let c=[...d.values()].map(e=>({...e,bary:e.ys.reduce((e,t)=>e+t,0)/e.ys.length}));c.sort((e,t)=>e.bary-t.bary||e.unresolved-t.unresolved||(e.bridge&&t.bridge?s(e.bridge,t.bridge):0));let h=l.length?l[l.length-1].y:16,f=c.length>1?Math.max(18,(h-16)/(c.length-1)):0,u=Math.max(4,Math.floor((i-10-42)/6));c.forEach((e,t)=>{if(e.x=i,e.y=1===c.length?(16+h)/2:16+t*f,e.r=5,e.unresolved)e.label=p(`can't be named (${e.count})`,u);else{let t=e.bridge.name||"";e.label=t.length<=u?t:p(g(t),u)}delete e.ys});let x=new Map(c.map(e=>[e.unresolved?a:e.id,e])),b=[];for(let e of l){let t=new Set;e.p.routes.forEach((r,n)=>{let i=r.bridge?r.id:a;if(t.has(i))return;t.add(i);let o=x.get(i);b.push({key:e.key,bridgeId:o.id,primary:0===n&&!o.unresolved,unresolved:o.unresolved,tier:o.bridge?.tier??null,x1:o.x,y1:o.y,x2:e.x,y2:e.y})})}let y=Math.max(h,c.length?c[c.length-1].y:0),m=l.length?Math.ceil(y+16):0,S={x:20,y:l.length?(16+Math.max(h,y))/2:16,r:14},k=c.map(e=>({id:e.id,x1:S.x,y1:S.y,x2:e.x,y2:e.y}));return{width:t,height:m,you:S,people:l,bridges:c,links:b,spokes:k}}],7214),e.s(["circleScanCost",0,function(e=100){let t=Math.max(1,Math.min(100,Math.round(e)||100));return{profileViews:1,searches:t,minutes:t>=100?55:Math.max(5,Math.round(.55*t))}},"circleState",0,h,"reachIndex",0,function(e=[],t=[],r={}){let n=new Set;for(let e of t||[])e?.source_connection_id!=null&&n.add(e.source_connection_id);let i=new Set;for(let e of r?.skips||[])e?.profileUrl&&i.add(o({profile_url:e.profileUrl}));let l=new Set;for(let e of r?.read||[])e&&l.add(o({profile_url:e}));let s=new Map;for(let[e,t]of Object.entries(r?.lists||{}))e&&t&&s.set(o({profile_url:e}),t);let d=new Map;for(let t of e||[]){let e=t?.unlocked_from_bridge_id;if(null==e||1!==t.degree)continue;let r=d.get(e);r||d.set(e,r=[]),r.push(t)}return{scanned:n,read:l,hidden:i,reachedFrom:d,lists:s}},"reachState",0,f,"readyByCircle",0,function(e){let t=new Map;for(let[r,n]of e.reachedFrom){let i=n.filter(t=>"ready"===f(t,e)).length;i&&t.set(r,i)}return t},"readyToScan",0,function(e,t){return(e||[]).filter(e=>"ready"===f(e,t)).sort(s)},"scanBars",0,function(e,t){let r=h(e,t),n=e?.profile_url?t.lists?.get(o(e)):null;return"hidden"===r?null:n?n.more?n.total?Math.max(1,Math.min(4,Math.round(5*Math.min(1,10*n.pages/n.total)))):2:5:"scanned"===r?2:null}],48235)},5258,e=>{"use strict";e.s(["TIER_COLORS",0,{S:"#f6c344",A:"#a78bfa",B:"#60a5fa",C:"#94a3b8",D:"#6b7280"},"TIER_COLORS_CLASSIC",0,{S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"},"TIER_ORDER",0,["S","A","B","C","D"],"TIER_RING_RADIUS",0,{S:150,A:230,B:310,C:390,D:470},"initialsFor",0,function(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===t.length?"?":(t[0].charAt(0)+(t.length>1?t[t.length-1].charAt(0):"")).toUpperCase()}])}]);
|
|
33
|
+
Open All</button>${n.map(e=>'<a class="btn" href="'+e.profile_url+'" target="_blank">'+e.name+" · "+parseFloat(e.power_score||0).toFixed(1)+"</a>").join("")}</body></html>`;window.open(URL.createObjectURL(new Blob([t],{type:"text/html"})),"_blank")},style:{marginLeft:"auto",padding:"2px 8px",borderRadius:3,border:"none",cursor:"pointer",background:"#0077B5",color:"#fff",fontSize:8,fontWeight:700},children:["Open All ",e]})]}),n.map(e=>(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"5px 8px 5px 16px",borderBottom:"1px solid rgba(255,255,255,0.02)",background:b.has(e.id)?"rgba(255,215,0,0.05)":"transparent",cursor:"pointer"},onMouseEnter:t=>{b.has(e.id)||(t.currentTarget.style.background="rgba(255,255,255,0.04)")},onMouseLeave:t=>{b.has(e.id)||(t.currentTarget.style.background="transparent")},children:[(0,t.jsx)("input",{type:"checkbox",checked:b.has(e.id),onChange:()=>X(e.id),onClick:e=>e.stopPropagation(),style:{cursor:"pointer",flexShrink:0}}),(0,c.localPhoto)(e.profile_image_url)?(0,t.jsx)("img",{src:(0,c.localPhoto)(e.profile_image_url),alt:"",style:{width:28,height:28,borderRadius:"50%",objectFit:"cover",flexShrink:0,border:`1.5px solid ${_[e.tier]||"#555"}`},onError:e=>{e.target.style.display="none"}}):(0,t.jsx)("div",{style:{width:28,height:28,borderRadius:"50%",flexShrink:0,fontSize:10,fontWeight:700,background:_[e.tier]||"#555",color:"S"===e.tier?"#000":"#fff",display:"flex",alignItems:"center",justifyContent:"center"},children:e.name?.charAt(0)}),(0,t.jsxs)("div",{style:{flex:1,minWidth:0},onClick:()=>X(e.id),children:[(0,t.jsx)("div",{style:{fontSize:11,fontWeight:600,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name}),(0,t.jsx)("div",{style:{fontSize:8,color:"#666",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.headline?.substring(0,45)})]}),(0,t.jsx)("span",{style:{fontSize:9,fontWeight:700,color:_[e.tier],flexShrink:0},children:parseFloat(e.power_score||0).toFixed(1)}),q.has(e.id)?(0,t.jsx)("span",{style:{padding:"3px 6px",borderRadius:3,fontSize:8,fontWeight:700,background:"rgba(255,107,53,0.15)",color:"#FF6B35",flexShrink:0},children:"Sent"}):(0,t.jsx)("a",{href:e.profile_url,target:"_blank",rel:"noopener noreferrer",onClick:t=>{t.stopPropagation(),(0,j.markRequested)(e,{bridgeId:e.source_connection_id}).catch(()=>{})},style:{padding:"3px 6px",borderRadius:3,fontSize:8,fontWeight:700,background:"#0077B5",color:"#fff",textDecoration:"none",flexShrink:0},children:"Add"})]},e.id))]},e)}):r.map(e=>(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8,padding:"6px 8px",borderBottom:"1px solid rgba(255,255,255,0.03)",background:b.has(e.id)?"rgba(255,215,0,0.05)":"transparent",cursor:"pointer"},onMouseEnter:t=>{b.has(e.id)||(t.currentTarget.style.background="rgba(255,255,255,0.04)")},onMouseLeave:t=>{b.has(e.id)||(t.currentTarget.style.background="transparent")},children:[(0,t.jsx)("input",{type:"checkbox",checked:b.has(e.id),onChange:()=>X(e.id),onClick:e=>e.stopPropagation(),style:{cursor:"pointer",flexShrink:0}}),(0,c.localPhoto)(e.profile_image_url)?(0,t.jsx)("img",{src:(0,c.localPhoto)(e.profile_image_url),alt:"",style:{width:32,height:32,borderRadius:"50%",objectFit:"cover",flexShrink:0,border:`1.5px solid ${_[e.tier]||"#555"}`},onError:e=>{e.target.style.display="none",e.target.nextSibling.style.display="flex"}}):null,(0,t.jsx)("div",{style:{width:32,height:32,borderRadius:"50%",flexShrink:0,fontSize:12,fontWeight:700,background:_[e.tier]||"#555",color:"S"===e.tier?"#000":"#fff",display:(0,c.localPhoto)(e.profile_image_url)?"none":"flex",alignItems:"center",justifyContent:"center"},children:e.name?.charAt(0)}),(0,t.jsxs)("div",{style:{flex:1,minWidth:0},onClick:()=>X(e.id),children:[(0,t.jsx)("div",{style:{fontSize:12,fontWeight:600,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name}),(0,t.jsx)("div",{style:{fontSize:9,color:"#666",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.headline?.substring(0,50)})]}),e.bridge&&(0,t.jsxs)("span",{style:{fontSize:8,color:"#555",flexShrink:0,maxWidth:60,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:["via ",e.bridge.name?.split(" ")[0]]}),(0,t.jsx)("div",{style:{textAlign:"right",flexShrink:0,minWidth:32},children:(0,t.jsx)("div",{style:{fontSize:10,fontWeight:700,color:_[e.tier]},children:parseFloat(e.power_score).toFixed(1)})}),q.has(e.id)?(0,t.jsx)("span",{style:{padding:"4px 8px",borderRadius:4,fontSize:9,fontWeight:700,background:"rgba(255,107,53,0.15)",color:"#FF6B35",flexShrink:0},children:"⏳ Sent"}):(0,t.jsx)("a",{href:e.profile_url,target:"_blank",rel:"noopener noreferrer",onClick:t=>{t.stopPropagation(),(0,j.markRequested)(e,{bridgeId:e.source_connection_id}).catch(()=>{})},style:{padding:"4px 8px",borderRadius:4,fontSize:9,fontWeight:700,background:"#0077B5",color:"#fff",textDecoration:"none",flexShrink:0},children:"Add"})]},e.id))]},e))]})]})})]})}e.s(["default",0,function(){return(0,t.jsx)(y.default,{children:(0,t.jsx)(F,{})})}],77305)},19821,e=>{"use strict";async function t(e){let t=e?`/api/network?userId=${encodeURIComponent(e)}`:"/api/network",r=await fetch(t);if(!r.ok)throw Error(`Failed to load network (${r.status})`);let n=await r.json();return{degree1:n.degree1||[],degree2:n.degree2||[],degree3:n.degree3||[],ties:Number(n.ties)||0}}e.s(["loadNetwork",0,t])},36774,e=>{"use strict";let t=/^[A-Za-z0-9_-]+\.(webp|jpg|jpeg|png)$/,r="/avatars/";e.s(["localPhoto",0,function(e){return"string"==typeof e&&e.startsWith(r)&&t.test(e.slice(r.length))?e:null}])},48235,e=>{"use strict";var t=e.i(7214);function r(e,r){return e?r.scanned.has(e.id)||e.profile_url&&r.read?.has((0,t.keyFor)(e))?"scanned":e.profile_url&&r.hidden.has((0,t.keyFor)(e))?"hidden":"todo":"todo"}function n(e,t){if(!e||1!==e.degree||null==e.unlocked_from_bridge_id)return null;let n=r(e,t);return"todo"===n?"ready":n}e.s(["circleScanCost",0,function(e=100){let t=Math.max(1,Math.min(100,Math.round(e)||100));return{profileViews:1,searches:t,minutes:t>=100?55:Math.max(5,Math.round(.55*t))}},"circleState",0,r,"reachIndex",0,function(e=[],r=[],n={}){let i=new Set;for(let e of r||[])e?.source_connection_id!=null&&i.add(e.source_connection_id);let o=new Set;for(let e of n?.skips||[])e?.profileUrl&&o.add((0,t.keyFor)({profile_url:e.profileUrl}));let l=new Set;for(let e of n?.read||[])e&&l.add((0,t.keyFor)({profile_url:e}));let s=new Map;for(let[e,r]of Object.entries(n?.lists||{}))e&&r&&s.set((0,t.keyFor)({profile_url:e}),r);let d=new Map;for(let t of e||[]){let e=t?.unlocked_from_bridge_id;if(null==e||1!==t.degree)continue;let r=d.get(e);r||d.set(e,r=[]),r.push(t)}return{scanned:i,read:l,hidden:o,reachedFrom:d,lists:s}},"reachState",0,n,"readyByCircle",0,function(e){let t=new Map;for(let[r,i]of e.reachedFrom){let o=i.filter(t=>"ready"===n(t,e)).length;o&&t.set(r,o)}return t},"readyToScan",0,function(e,r){return(e||[]).filter(e=>"ready"===n(e,r)).sort(t.compareBridges)},"scanBars",0,function(e,n){let i=r(e,n),o=e?.profile_url?n.lists?.get((0,t.keyFor)(e)):null;return"hidden"===i?null:o?o.more?o.total?Math.max(1,Math.min(4,Math.round(5*Math.min(1,10*o.pages/o.total)))):2:5:"scanned"===i?2:null}])},7214,e=>{"use strict";var t=e.i(36774);let r={S:0,A:1,B:2,C:3,D:4};function n(e){let t=parseFloat(e?.power_score);return Number.isFinite(t)?t:0}function i(e){return Math.round(10*n(e))/10}function o(e){return String(e?.profile_url||"").split(/[?#]/)[0].replace(/\/+$/,"")||`id:${e?.id}`}let l=(e,t)=>String(e??"").localeCompare(String(t??""),void 0,{numeric:!0});function s(e,t){return(r[e.tier]??9)-(r[t.tier]??9)||n(t)-n(e)||String(e.name||"").localeCompare(String(t.name||""))||l(e.id,t.id)}function d(e,t){return e.bridge&&t.bridge?s(e.bridge,t.bridge):e.bridge?-1:t.bridge?1:l(e.id,t.id)}function a(e,t){return t.score-e.score||t.waysIn-e.waysIn||d(e.routes[0]||c,t.routes[0]||c)||String(e.person.name||"").localeCompare(String(t.person.name||""))||(e.key<t.key?-1:+(e.key>t.key))}let c={id:null,bridge:null};function g(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return t.length<2?t[0]||"":`${t[0]} ${t[t.length-1].charAt(0)}.`}function p(e,t){let r=String(e||"");return t<=1?r?"…":"":r.length>t?`${r.slice(0,t-1)}…`:r}e.s(["TIERS",0,["S","A","B","C","D"],"compareBridges",0,s,"compareWaysIn",0,function(e,t){return t.waysIn-e.waysIn||a(e,t)},"keyFor",0,o,"routeIndex",0,function(e=[]){let t=new Map;for(let r of e||[]){let e=o(r),n=t.get(e);n||(n=new Set,t.set(e,n)),n.add(r.source_connection_id??null)}return t},"routesFor",0,function(e,t,r){return e?[...t?.get(o(e))??(null!=e.source_connection_id?new Set([e.source_connection_id]):new Set)].map(e=>({id:e,bridge:r?.get(e)??null})).sort(d):[]},"score",0,n,"score1",0,i,"separationPeople",0,function(e=[],r=[]){let n=new Map;for(let e of r||[])n.set(e.id,e);let s=new Map,c=new Map;for(let r of e||[]){let e=o(r);c.set(r.id,e);let n=s.get(e);n?function(e,r){let n=i(e)-i(r);if(n)return n>0;let o=!!(0,t.localPhoto)(e.profile_image_url);return!!(0,t.localPhoto)(r.profile_image_url)!==o?o:0>l(e.id,r.id)}(r,n.person)&&(n.person=r):(n={key:e,person:r,rowIds:[],routeIds:new Set},s.set(e,n)),n.rowIds.push(r.id),n.routeIds.add(r.source_connection_id??null)}let g={people:s.size,byTier:{S:0,A:0,B:0,C:0,D:0},multi:0,bridges:0,unresolvedIds:0,peopleOnlyUnresolved:0,onlyUnresolvedVia:0},p=new Set,h=new Set,f=new Set,u=[];for(let e of s.values()){let t=[...e.routeIds].map(e=>({id:e,bridge:n.get(e)??null}));t.sort(d);let r=t.filter(e=>e.bridge);for(let e of t)(e.bridge?p:h).add(e.id);if(!r.length)for(let e of(g.peopleOnlyUnresolved++,t))f.add(e.id);let{person:o}=e,l=o.tier;void 0!==g.byTier[l]&&g.byTier[l]++,t.length>1&&g.multi++,u.push({key:e.key,person:o,score:i(o),tier:l,routes:t,waysIn:t.length,rowIds:e.rowIds,haystack:[o.name,o.headline,o.company,o.role,...r.map(e=>e.bridge.name)].filter(Boolean).join("\n").toLowerCase(),rank:0,tied:!1})}return g.bridges=p.size,g.unresolvedIds=h.size,g.onlyUnresolvedVia=f.size,u.sort(a),function(e){for(let t=0;t<e.length;t++){let r=e[t],n=e[t-1],i=n&&n.score===r.score&&n.waysIn===r.waysIn;r.rank=i?n.rank:t+1,i&&(r.tied=!0,n.tied=!0)}}(u),{people:u,rowToKey:c,summary:g}},"shortName",0,g,"summitLayout",0,function(e=[],t=800,r=!1){let n=r?36:26,i=Math.round(.42*t),o=Math.round(.58*t),l=e.map((e,r)=>{var i;return{key:e.key,p:e,x:o,y:16+r*n,r:(i=e.waysIn)>=4?8.5:3===i?7:2===i?5.5:4,label:p(`#${e.rank}${e.tied?"=":""} ${e.person.name||""}`,Math.floor((t-o-60)/6.2))}}),d=new Map,a="\0unresolved";for(let e of l)for(let t of e.p.routes){let r=t.bridge?t.id:a,n=d.get(r);n||(n={id:t.bridge?t.id:null,bridge:t.bridge,unresolved:!t.bridge,ys:[],count:0},d.set(r,n)),n.count++,n.ys.push(e.y)}let c=[...d.values()].map(e=>({...e,bary:e.ys.reduce((e,t)=>e+t,0)/e.ys.length}));c.sort((e,t)=>e.bary-t.bary||e.unresolved-t.unresolved||(e.bridge&&t.bridge?s(e.bridge,t.bridge):0));let h=l.length?l[l.length-1].y:16,f=c.length>1?Math.max(18,(h-16)/(c.length-1)):0,u=Math.max(4,Math.floor((i-10-42)/6));c.forEach((e,t)=>{if(e.x=i,e.y=1===c.length?(16+h)/2:16+t*f,e.r=5,e.unresolved)e.label=p(`can't be named (${e.count})`,u);else{let t=e.bridge.name||"";e.label=t.length<=u?t:p(g(t),u)}delete e.ys});let x=new Map(c.map(e=>[e.unresolved?a:e.id,e])),b=[];for(let e of l){let t=new Set;e.p.routes.forEach((r,n)=>{let i=r.bridge?r.id:a;if(t.has(i))return;t.add(i);let o=x.get(i);b.push({key:e.key,bridgeId:o.id,primary:0===n&&!o.unresolved,unresolved:o.unresolved,tier:o.bridge?.tier??null,x1:o.x,y1:o.y,x2:e.x,y2:e.y})})}let y=Math.max(h,c.length?c[c.length-1].y:0),m=l.length?Math.ceil(y+16):0,S={x:20,y:l.length?(16+Math.max(h,y))/2:16,r:14},k=c.map(e=>({id:e.id,x1:S.x,y1:S.y,x2:e.x,y2:e.y}));return{width:t,height:m,you:S,people:l,bridges:c,links:b,spokes:k}}])},5258,e=>{"use strict";e.s(["TIER_COLORS",0,{S:"#f6c344",A:"#a78bfa",B:"#60a5fa",C:"#94a3b8",D:"#6b7280"},"TIER_COLORS_CLASSIC",0,{S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"},"TIER_ORDER",0,["S","A","B","C","D"],"TIER_RING_RADIUS",0,{S:150,A:230,B:310,C:390,D:470},"initialsFor",0,function(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===t.length?"?":(t[0].charAt(0)+(t.length>1?t[t.length-1].charAt(0):"")).toUpperCase()}])}]);
|
|
@@ -6,6 +6,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.4] - 2026-09-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Social (experimental): a tab for your relationships.** It works from your own LinkedIn data:
|
|
13
|
+
- **Your export:** the folder LinkedIn emails you from *Settings → Data privacy → Get a copy
|
|
14
|
+
of your data*, read in the app window with no traffic to LinkedIn.
|
|
15
|
+
- **A live messages sync:** reads your messages list once in your own Chrome, by hand or once
|
|
16
|
+
a day in the daytime, using no search budget.
|
|
17
|
+
|
|
18
|
+
It shows:
|
|
19
|
+
- how many of your connections are warm, cool, dormant or never messaged;
|
|
20
|
+
- who's waiting on a reply (S and A tier first), or has unread messages;
|
|
21
|
+
- your career chapters, with the connections made in each and how many you're still in
|
|
22
|
+
touch with;
|
|
23
|
+
- whether posting works (new connections the week after a post against a usual week);
|
|
24
|
+
- how many connection requests came to you and how many you sent.
|
|
25
|
+
|
|
26
|
+
Only dates, who wrote last and counts are kept, one file per profile in the data folder, and
|
|
27
|
+
*Forget it* removes it. Message text is never kept. Email addresses are kept only if you tick
|
|
28
|
+
the box. The live sync hasn't been tried on a live account yet.
|
|
29
|
+
- **Auto scan.** With the Scan page's experimental all-day box ticked, an *Auto scan* button sits
|
|
30
|
+
beside Scan and starts the all-day Auto-Bridge with your Scan page choices. The status bar sits
|
|
31
|
+
under the tabs, dimmed while it's ready, and lights up once it runs.
|
|
32
|
+
- **Insights on a connection's card.** A collapsible panel with their circle's size (*Explore →*
|
|
33
|
+
opens it in Bridge Chains), how many only they reach, whose circle overlaps theirs most
|
|
34
|
+
(*Open →*), the tier mix of their circle, where most of it works, how much is scanned (*Scan →*
|
|
35
|
+
or *Finish →*), when you connected and how many requests you have out through them, and their
|
|
36
|
+
rank by who only they reach. *Show them →* opens Separation filtered to the people only they
|
|
37
|
+
reach, and *S only →* to the S-tier people in their circle. All from what the app already
|
|
38
|
+
keeps. It replaces the *Only through …* box.
|
|
39
|
+
- **Network health on the Scores tab.** How much of your 2nd degree you reach two or more ways,
|
|
40
|
+
the effective reach of your own network (Burt's effective size, from the ties between your
|
|
41
|
+
connections that circle scans keep), and your five connections who reach the most people no
|
|
42
|
+
one else does, each with *Explore →*. With fewer than five circles scanned it says it's an
|
|
43
|
+
early estimate. Only your own numbers: never a percentile against other people.
|
|
44
|
+
|
|
9
45
|
## [0.4.3] - 2026-09-29
|
|
10
46
|
|
|
11
47
|
### Added
|
|
@@ -33,7 +33,18 @@ export async function GET(request) {
|
|
|
33
33
|
return Response.json({ error: firstError.message }, { status: 500 });
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// How many ties between your own connections circle scans have kept (lib/ties.js),
|
|
37
|
+
// for network health on the Scores tab.
|
|
38
|
+
let ties = 0;
|
|
39
|
+
try {
|
|
40
|
+
let tq = supabase.from('connection_ties').select('a_url');
|
|
41
|
+
if (userId) tq = tq.eq('user_id', userId);
|
|
42
|
+
const { data } = await tq;
|
|
43
|
+
ties = data?.length || 0;
|
|
44
|
+
} catch { /* an older database: none yet */ }
|
|
45
|
+
|
|
36
46
|
return Response.json({
|
|
47
|
+
ties,
|
|
37
48
|
degree1: d1.data || [],
|
|
38
49
|
degree2: d2.data || [],
|
|
39
50
|
degree3: d3.data || [],
|
|
@@ -422,6 +422,8 @@ const ACTIONS = {
|
|
|
422
422
|
company: { flag: '--company', needsName: true, label: 'Scanning' },
|
|
423
423
|
// Photos an older version kept as links to LinkedIn: saved here, once each.
|
|
424
424
|
// No browser, no search; every scan does the same at its end.
|
|
425
|
+
// Experimental, for the Social tab: your messages list, once (who, when, unread; never text).
|
|
426
|
+
messages: { flag: '--messages', label: 'Reading your messages list' },
|
|
425
427
|
photos: { flag: '--save-photos', label: 'Saving profile photos to this computer' },
|
|
426
428
|
};
|
|
427
429
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, renameSync, rmSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { dataDir } from '../../../lib/db-client';
|
|
4
|
+
import { resolveProfile } from '../../../lib/profile';
|
|
5
|
+
|
|
6
|
+
// The Social tab's saved findings (lib/linkedin-export.js): numbers and dates
|
|
7
|
+
// from your own LinkedIn export, one file per profile in the data folder. No
|
|
8
|
+
// message text ever reaches here. GET reads it, POST replaces it, DELETE
|
|
9
|
+
// forgets it.
|
|
10
|
+
|
|
11
|
+
function fileFor() {
|
|
12
|
+
const me = resolveProfile({ create: false });
|
|
13
|
+
if (!me?.id || !/^[\w-]+$/.test(String(me.id))) return null;
|
|
14
|
+
return path.join(dataDir(), `social-${me.id}.json`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function GET() {
|
|
18
|
+
const file = fileFor();
|
|
19
|
+
if (!file) return Response.json({ social: null });
|
|
20
|
+
try { return Response.json({ social: JSON.parse(readFileSync(file, 'utf8')) }); } catch { return Response.json({ social: null }); }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function POST(request) {
|
|
24
|
+
const file = fileFor();
|
|
25
|
+
if (!file) return Response.json({ error: 'Pick a profile first.' }, { status: 400 });
|
|
26
|
+
let body;
|
|
27
|
+
try { body = await request.json(); } catch { return Response.json({ error: 'That isn’t readable.' }, { status: 400 }); }
|
|
28
|
+
// Only the known fields, so nothing else rides along into the file.
|
|
29
|
+
let before = {};
|
|
30
|
+
try { before = JSON.parse(readFileSync(file, 'utf8')) || {}; } catch { /* none yet */ }
|
|
31
|
+
const clean = {
|
|
32
|
+
live: before.live || {},
|
|
33
|
+
liveAt: before.liveAt || null,
|
|
34
|
+
autoSync: before.autoSync === true,
|
|
35
|
+
importedAt: new Date().toISOString(),
|
|
36
|
+
asOf: Number(body?.asOf) || null,
|
|
37
|
+
people: body?.people && typeof body.people === 'object' ? body.people : {},
|
|
38
|
+
chapters: Array.isArray(body?.chapters) ? body.chapters : [],
|
|
39
|
+
posts: body?.posts && typeof body.posts === 'object' ? body.posts : null,
|
|
40
|
+
invites: body?.invites && typeof body.invites === 'object' ? body.invites : null,
|
|
41
|
+
emails: body?.emails === true,
|
|
42
|
+
};
|
|
43
|
+
for (const p of Object.values(clean.people)) {
|
|
44
|
+
if (p && typeof p === 'object') {
|
|
45
|
+
for (const k of Object.keys(p)) if (!['last', 'lastFromThem', 'total', 'recent', 'email'].includes(k)) delete p[k];
|
|
46
|
+
if (!clean.emails) delete p.email;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
50
|
+
writeFileSync(tmp, JSON.stringify(clean));
|
|
51
|
+
renameSync(tmp, file);
|
|
52
|
+
return Response.json({ ok: true, people: Object.keys(clean.people).length });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// From the scanner's experimental messages sync (scripts/scrape.py sync_messages):
|
|
56
|
+
// per person, when your 1:1 conversation was last active and unread count.
|
|
57
|
+
// Merged in beside what the export gave, kept under `live`.
|
|
58
|
+
export async function PUT(request) {
|
|
59
|
+
const file = fileFor();
|
|
60
|
+
if (!file) return Response.json({ error: 'Pick a profile first.' }, { status: 400 });
|
|
61
|
+
let body;
|
|
62
|
+
try { body = await request.json(); } catch { return Response.json({ error: 'That isn’t readable.' }, { status: 400 }); }
|
|
63
|
+
let saved = {};
|
|
64
|
+
try { saved = JSON.parse(readFileSync(file, 'utf8')) || {}; } catch { /* first sync */ }
|
|
65
|
+
const live = {};
|
|
66
|
+
for (const [url, v] of Object.entries(body?.live || {})) {
|
|
67
|
+
if (typeof url !== 'string' || !url.includes('/in/') || !v || typeof v !== 'object') continue;
|
|
68
|
+
live[url] = { last: Number(v.last) || null, unread: Number.isFinite(Number(v.unread)) ? Number(v.unread) : null };
|
|
69
|
+
}
|
|
70
|
+
const next = { ...saved, live, liveAt: new Date().toISOString() };
|
|
71
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
72
|
+
writeFileSync(tmp, JSON.stringify(next));
|
|
73
|
+
renameSync(tmp, file);
|
|
74
|
+
return Response.json({ ok: true, people: Object.keys(live).length });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The Social tab's "once a day" switch for the live messages sync.
|
|
78
|
+
export async function PATCH(request) {
|
|
79
|
+
const file = fileFor();
|
|
80
|
+
if (!file) return Response.json({ error: 'Pick a profile first.' }, { status: 400 });
|
|
81
|
+
let body = {};
|
|
82
|
+
try { body = await request.json(); } catch { /* nothing to change */ }
|
|
83
|
+
let saved = {};
|
|
84
|
+
try { saved = JSON.parse(readFileSync(file, 'utf8')) || {}; } catch { /* none yet */ }
|
|
85
|
+
const next = { ...saved, autoSync: body?.autoSync === true };
|
|
86
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
87
|
+
writeFileSync(tmp, JSON.stringify(next));
|
|
88
|
+
renameSync(tmp, file);
|
|
89
|
+
return Response.json({ ok: true, autoSync: next.autoSync });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function DELETE() {
|
|
93
|
+
const file = fileFor();
|
|
94
|
+
if (file) rmSync(file, { force: true });
|
|
95
|
+
return Response.json({ ok: true });
|
|
96
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// "Auto scan" in the header, beside Scan, while the Scan page's experimental
|
|
4
|
+
// all-day switch is on: starts Auto-Bridge in all-day mode (lib/experimental-client.js).
|
|
5
|
+
// Unlit until it runs; lit while it does, and then it opens the Scan page.
|
|
6
|
+
|
|
7
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
8
|
+
import Link from 'next/link';
|
|
9
|
+
import { watchScanner, scannerNow } from '../../lib/scraper-client';
|
|
10
|
+
import { watchAllDay, allDayNow, startAutoScan } from '../../lib/experimental-client';
|
|
11
|
+
|
|
12
|
+
export default function AutoScanButton({ isMobile = false }) {
|
|
13
|
+
const on = useSyncExternalStore(watchAllDay, allDayNow, () => false);
|
|
14
|
+
const [job, setJob] = useState(() => scannerNow());
|
|
15
|
+
const [starting, setStarting] = useState(false);
|
|
16
|
+
useEffect(() => watchScanner(() => { setJob(scannerNow()); setStarting(false); }), []);
|
|
17
|
+
if (!on) return null;
|
|
18
|
+
const mine = job?.running && String(job.action || '').startsWith('auto-bridge');
|
|
19
|
+
const style = {
|
|
20
|
+
padding: isMobile ? '8px 12px' : '8px 16px', borderRadius: 6, fontSize: 13, fontWeight: 600, textDecoration: 'none',
|
|
21
|
+
display: 'flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap',
|
|
22
|
+
};
|
|
23
|
+
if (mine) {
|
|
24
|
+
return (
|
|
25
|
+
<Link href="/setup" title="Auto scan is running: see it on the Scan page" style={{ ...style, background: 'rgba(0,255,136,0.15)', color: '#00ff88', border: '1px solid rgba(0,255,136,0.45)' }}>
|
|
26
|
+
<span style={{ width: 7, height: 7, borderRadius: '50%', background: '#00ff88' }} />Auto scan
|
|
27
|
+
</Link>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const busy = job?.running || starting;
|
|
31
|
+
return (
|
|
32
|
+
<button
|
|
33
|
+
onClick={() => { setStarting(true); startAutoScan().catch(() => setStarting(false)); }}
|
|
34
|
+
disabled={busy}
|
|
35
|
+
title={busy ? 'The scanner is busy: one scan at a time' : 'Start the all-day Auto-Bridge (experimental)'}
|
|
36
|
+
style={{ ...style, cursor: busy ? 'default' : 'pointer', background: 'rgba(255,255,255,0.04)', color: busy ? '#555' : '#8b9a9a', border: '1px solid rgba(255,215,0,0.25)' }}
|
|
37
|
+
>
|
|
38
|
+
<span style={{ width: 7, height: 7, borderRadius: '50%', background: '#556' }} />Auto scan
|
|
39
|
+
</button>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -110,6 +110,13 @@ export default function ChainView({ connections, degree2 = [], onSelect, userNam
|
|
|
110
110
|
// The circles opened, outermost last: [bridge id, person id, …]; [] is the overview.
|
|
111
111
|
const [path, setPath] = useState(() => trailTo(chainOpen, all1));
|
|
112
112
|
// A link is followed once: coming back to this view later starts from the overview.
|
|
113
|
+
// One asked for while this view is already showing (a card's Insights) opens too.
|
|
114
|
+
const [openedFor, setOpenedFor] = useState(chainOpen);
|
|
115
|
+
if (chainOpen && chainOpen !== openedFor) {
|
|
116
|
+
setOpenedFor(chainOpen);
|
|
117
|
+
const trail = trailTo(chainOpen, all1);
|
|
118
|
+
if (trail.length) setPath(trail);
|
|
119
|
+
}
|
|
113
120
|
useEffect(() => {
|
|
114
121
|
if (chainOpen) onChainOpened?.();
|
|
115
122
|
}, [chainOpen, onChainOpened]);
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
// page, the Scan page too (Blake, 2026-09-29). It only reports: the
|
|
8
8
|
// pacing and the caps live in the scanner (scripts/scrape.py, lib/linkedin-limits.js).
|
|
9
9
|
|
|
10
|
-
import { useEffect, useState } from 'react';
|
|
10
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
11
11
|
import Link from 'next/link';
|
|
12
12
|
import { watchScanner, scannerNow, stopScrape } from '../../lib/scraper-client';
|
|
13
|
+
import { watchAllDay, allDayNow } from '../../lib/experimental-client';
|
|
13
14
|
|
|
14
15
|
const WHAT = {
|
|
15
16
|
full: 'Scanning your network',
|
|
@@ -24,6 +25,7 @@ const WHAT = {
|
|
|
24
25
|
setup: 'Setting up the scanner',
|
|
25
26
|
company: 'Scanning a company',
|
|
26
27
|
photos: 'Saving photos',
|
|
28
|
+
messages: 'Reading your messages list',
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
// Green while there's plenty left, amber past 60%, red past 90%.
|
|
@@ -32,12 +34,29 @@ const tone = (used, cap) => (!cap ? '#8b9a9a' : used / cap >= 0.9 ? '#ff6b6b' :
|
|
|
32
34
|
export default function ScanStatusBar() {
|
|
33
35
|
const [job, setJob] = useState(() => scannerNow());
|
|
34
36
|
const [stopping, setStopping] = useState(false);
|
|
37
|
+
// The Scan page's experimental all-day Auto-Bridge switch: with it on, the bar
|
|
38
|
+
// stays under the tabs even while nothing runs, so you can see the mode is on.
|
|
39
|
+
const allDay = useSyncExternalStore(watchAllDay, allDayNow, () => false);
|
|
35
40
|
useEffect(() => watchScanner(() => {
|
|
36
41
|
const now = scannerNow();
|
|
37
42
|
setJob(now);
|
|
38
43
|
if (!now.running) setStopping(false);
|
|
39
44
|
}), []);
|
|
40
|
-
|
|
45
|
+
const place = {
|
|
46
|
+
position: 'fixed', left: '50%', top: 'var(--scan-bar-top, 94px)', transform: 'translateX(-50%)', zIndex: 60,
|
|
47
|
+
maxWidth: 'calc(100vw - 32px)', display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'nowrap', whiteSpace: 'nowrap',
|
|
48
|
+
padding: '8px 12px', borderRadius: 12, fontSize: 12, color: '#cfd8d8', background: 'rgba(14,16,32,0.94)',
|
|
49
|
+
};
|
|
50
|
+
if (!job?.running) {
|
|
51
|
+
if (!allDay) return null;
|
|
52
|
+
return (
|
|
53
|
+
<div role="status" style={{ ...place, opacity: 0.55, border: '1px solid rgba(255,255,255,0.1)' }}>
|
|
54
|
+
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#556', flexShrink: 0 }} />
|
|
55
|
+
<b style={{ color: '#b8c4c4' }}>Auto scan</b>
|
|
56
|
+
<span style={{ color: '#8b9a9a' }}>ready · press Auto scan beside Scan to start</span>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
41
60
|
|
|
42
61
|
const p = job.progress;
|
|
43
62
|
const step = p?.kind === 'batch' && p.total ? `${p.current || p.done || 0} of ${p.total}`
|
|
@@ -47,12 +66,7 @@ export default function ScanStatusBar() {
|
|
|
47
66
|
const last = [...(job.log || [])].reverse().find((l) => l && l.trim())?.trim();
|
|
48
67
|
|
|
49
68
|
return (
|
|
50
|
-
<div role="status" aria-live="polite" style={{
|
|
51
|
-
position: 'fixed', left: '50%', top: 'var(--scan-bar-top, 94px)', transform: 'translateX(-50%)', zIndex: 60,
|
|
52
|
-
maxWidth: 'calc(100vw - 32px)', display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'nowrap', whiteSpace: 'nowrap',
|
|
53
|
-
padding: '8px 12px', borderRadius: 12, fontSize: 12, color: '#cfd8d8',
|
|
54
|
-
background: 'rgba(14,16,32,0.94)', border: '1px solid rgba(0,255,136,0.25)',
|
|
55
|
-
}}>
|
|
69
|
+
<div role="status" aria-live="polite" style={{ ...place, border: '1px solid rgba(0,255,136,0.25)' }}>
|
|
56
70
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#00ff88', flexShrink: 0 }} />
|
|
57
71
|
<b style={{ color: '#fff' }}>{WHAT[job.action] || 'Scanning'}</b>
|
|
58
72
|
{job.target?.name && <span>{job.target.name}</span>}
|
|
@@ -66,7 +66,7 @@ function subline(person) {
|
|
|
66
66
|
return person.headline || person.role || person.company || '';
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export default function SeparationView({ connections = [], degree2 = [], fullDegree1 = connections, fullDegree2 = degree2, onSelect, userName, selectedId, tierColors = CLASSIC }) {
|
|
69
|
+
export default function SeparationView({ connections = [], degree2 = [], fullDegree1 = connections, fullDegree2 = degree2, onSelect, userName, selectedId, tierColors = CLASSIC, preset = null }) {
|
|
70
70
|
const isMobile = useIsMobile();
|
|
71
71
|
const ROW_H = isMobile ? 68 : 56;
|
|
72
72
|
const K = isMobile ? 5 : 10;
|
|
@@ -78,6 +78,14 @@ export default function SeparationView({ connections = [], degree2 = [], fullDeg
|
|
|
78
78
|
const [tier, setTier] = useState('all');
|
|
79
79
|
// Rarity beside the tier (lib/rarity.js): any bands switched on; none is everyone.
|
|
80
80
|
const [rarities, setRarities] = useState(() => new Set());
|
|
81
|
+
// Filters a card's Insights asked for ("Show them", "S only"): applied once each.
|
|
82
|
+
const [presetFor, setPresetFor] = useState(null);
|
|
83
|
+
if (preset && preset.id !== presetFor) {
|
|
84
|
+
setPresetFor(preset.id);
|
|
85
|
+
setQuery(preset.query || '');
|
|
86
|
+
setTier(preset.tier || 'all');
|
|
87
|
+
setRarities(new Set(preset.rarity ? [preset.rarity] : []));
|
|
88
|
+
}
|
|
81
89
|
const requests = useRequests();
|
|
82
90
|
const [scrollY, setScrollY] = useState(0);
|
|
83
91
|
const [box, setBox] = useState({ w: 0, h: 800 });
|
|
@@ -12,10 +12,11 @@ import { routeIndex, routesFor } from '../../lib/separation';
|
|
|
12
12
|
import { topCompanies } from '../../lib/scoring';
|
|
13
13
|
import { reachIndex, reachState, circleState, readyToScan, circleScanCost } from '../../lib/reach';
|
|
14
14
|
import { exclusiveReach, bridgeOverlap } from '../../lib/brokerage';
|
|
15
|
+
import { profileInsights } from '../../lib/insights';
|
|
15
16
|
import Avatar from './Avatar';
|
|
16
17
|
import { localPhoto } from '../../lib/photos';
|
|
17
18
|
|
|
18
|
-
export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending, scanNotes, csvSource = null }) {
|
|
19
|
+
export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending, scanNotes, csvSource = null, onOpenCircle, onShowInSeparation }) {
|
|
19
20
|
const isDegreesMode = mode === 'degrees';
|
|
20
21
|
// 'sample' or 'csv' while the sample or a CSV import is open in this window,
|
|
21
22
|
// null for your own network. Only your own can be scanned: the scanner looks
|
|
@@ -43,6 +44,9 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
43
44
|
// Who only each connection reaches (lib/brokerage.js), for their card.
|
|
44
45
|
const exclusive = useMemo(() => exclusiveReach(degree2, connections), [degree2, connections]);
|
|
45
46
|
const overlap = useMemo(() => bridgeOverlap(degree2, connections), [degree2, connections]);
|
|
47
|
+
const insightReach = useMemo(() => reachIndex(connections, degree2, scanNotes), [connections, degree2, scanNotes]);
|
|
48
|
+
// The Insights panel on a connection's card: open or closed, kept as you move between cards.
|
|
49
|
+
const [showInsights, setShowInsights] = useState(false);
|
|
46
50
|
const bridgeById = useMemo(() => new Map(connections.map(c => [c.id, c])), [connections]);
|
|
47
51
|
// Who you've asked, shared with every view: a request sent here shows in
|
|
48
52
|
// Separation, the circle and the Outlink queue at once (lib/requests-client.js).
|
|
@@ -143,33 +147,91 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
143
147
|
/>
|
|
144
148
|
)}
|
|
145
149
|
|
|
146
|
-
{selected.degree !== 2 && (
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
// A twin only when it's real: at least 15% of either's people, and 3 of them.
|
|
150
|
-
const twin = ov && ov.share >= 0.15 && ov.shared >= 3 && connections.find((c) => c.id === ov.with);
|
|
150
|
+
{selected.degree !== 2 && (() => {
|
|
151
|
+
const i = profileInsights(selected, { degree2, exclusive, overlap, reach: insightReach });
|
|
152
|
+
if (!i) return null;
|
|
151
153
|
const first = selected.name?.split(' ')[0] || 'them';
|
|
154
|
+
const twin = i.twin && connections.find((c) => c.id === i.twin.id);
|
|
155
|
+
const row = { display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10, padding: '7px 0', borderTop: '1px solid rgba(255,255,255,0.05)' };
|
|
156
|
+
const label = { fontSize: 10.5, color: '#8b9a9a', minWidth: 92 };
|
|
157
|
+
const act = { background: 'none', border: 'none', padding: 0, color: '#3498DB', cursor: 'pointer', fontSize: 11.5, whiteSpace: 'nowrap' };
|
|
158
|
+
const scanned = i.bars == null ? 'Not scanned yet' : i.bars === 5 ? 'Their whole list' : `About ${i.bars * 20}% of their list`;
|
|
152
159
|
return (
|
|
153
|
-
<div style={{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
<div style={{ border: '1px solid rgba(52,152,219,0.25)', borderRadius: 8, marginBottom: 16, background: 'rgba(52,152,219,0.04)' }}>
|
|
161
|
+
<button onClick={() => setShowInsights((v) => !v)} aria-expanded={showInsights}
|
|
162
|
+
style={{ width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '9px 12px', background: 'none', border: 'none', cursor: 'pointer', color: '#cfe6f7' }}>
|
|
163
|
+
<span style={{ fontSize: 11, fontWeight: 700, letterSpacing: 1 }}>INSIGHTS</span>
|
|
164
|
+
<span style={{ fontSize: 11, color: '#8b9a9a' }}>
|
|
165
|
+
{!showInsights && i.circle > 0 && `${i.circle.toLocaleString()} in their circle${i.only ? ` · ${i.only.toLocaleString()} only through ${first}` : ''} `}
|
|
166
|
+
{showInsights ? '▲' : '▼'}
|
|
167
|
+
</span>
|
|
168
|
+
</button>
|
|
169
|
+
{showInsights && (
|
|
170
|
+
<div style={{ padding: '0 12px 8px', fontSize: 12, color: '#ccc' }}>
|
|
171
|
+
<div style={row}>
|
|
172
|
+
<span style={label}>Their circle</span>
|
|
173
|
+
<span style={{ flex: 1 }}>{i.circle ? `${i.circle.toLocaleString()} people` : 'None scanned yet'}</span>
|
|
174
|
+
{i.circle > 0 && onOpenCircle && <button style={act} onClick={() => onOpenCircle(selected.id)}>Explore →</button>}
|
|
175
|
+
</div>
|
|
176
|
+
<div style={row}>
|
|
177
|
+
<span style={label}>Only through {first}</span>
|
|
178
|
+
<span style={{ flex: 1 }}>
|
|
179
|
+
{i.circle ? <><b style={{ color: '#00ff88' }}>{i.only.toLocaleString()}</b> ({Math.round(i.onlyShare * 100)}%): none of your other connections reach them</> : '—'}
|
|
180
|
+
</span>
|
|
181
|
+
{i.only > 0 && onShowInSeparation && (
|
|
182
|
+
<button style={act} onClick={() => onShowInSeparation({ query: selected.name, rarity: 'only' })}>Show them →</button>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
<div style={row}>
|
|
186
|
+
<span style={label}>Closest overlap</span>
|
|
187
|
+
<span style={{ flex: 1 }}>
|
|
188
|
+
{twin ? <>Opens the same doors as <b style={{ color: '#fff' }}>{twin.name}</b> ({Math.round(i.twin.share * 100)}%, {i.twin.shared.toLocaleString()} people)</> : 'No one opens the same doors'}
|
|
189
|
+
</span>
|
|
190
|
+
{twin && <button style={act} onClick={() => onSelect?.(twin)}>Open →</button>}
|
|
191
|
+
</div>
|
|
192
|
+
<div style={row}>
|
|
193
|
+
<span style={label}>Tier mix</span>
|
|
194
|
+
<span style={{ flex: 1, display: 'flex', height: 8, borderRadius: 4, overflow: 'hidden', background: 'rgba(255,255,255,0.05)', alignSelf: 'center' }}>
|
|
195
|
+
{i.circle > 0 && ['S', 'A', 'B', 'C', 'D'].map((t) => i.mix[t] > 0 && (
|
|
196
|
+
<span key={t} title={`${t}: ${i.mix[t]}`} style={{ width: `${(100 * i.mix[t]) / i.circle}%`, background: tierColors[t] }} />
|
|
197
|
+
))}
|
|
198
|
+
</span>
|
|
199
|
+
<span style={{ fontSize: 10.5, color: '#8b9a9a' }}>{i.circle ? `${i.mix.S} S · ${i.mix.A} A` : ''}</span>
|
|
200
|
+
{i.mix.S > 0 && onShowInSeparation && (
|
|
201
|
+
<button style={act} onClick={() => onShowInSeparation({ query: selected.name, tier: 'S' })}>S only →</button>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
{i.companies.length > 0 && (
|
|
205
|
+
<div style={row}>
|
|
206
|
+
<span style={label}>Where they work</span>
|
|
207
|
+
<span style={{ flex: 1 }}>{i.companies.map((c) => `${c.count} at ${c.name}`).join(' · ')}</span>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
<div style={row}>
|
|
211
|
+
<span style={label}>Scanned</span>
|
|
212
|
+
<span style={{ flex: 1 }}>{scanned}</span>
|
|
213
|
+
{i.bars !== 5 && canScan && (
|
|
214
|
+
<a href={`/setup?scan=${encodeURIComponent(selected.id)}`} style={act}>{i.bars == null ? 'Scan →' : 'Finish →'}</a>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
{(() => {
|
|
218
|
+
const since = selected.connected_date ? new Date(selected.connected_date) : null;
|
|
219
|
+
const asked = (bridgeMap[selected.id] || []).filter((p) => hasRequest(p, requests)).length;
|
|
220
|
+
if (!(since && !Number.isNaN(since.getTime())) && !asked) return null;
|
|
221
|
+
return (
|
|
222
|
+
<div style={row}>
|
|
223
|
+
<span style={label}>You and {first}</span>
|
|
224
|
+
<span style={{ flex: 1 }}>
|
|
225
|
+
{since && !Number.isNaN(since.getTime()) ? `Connected ${since.toLocaleDateString(undefined, { month: 'short', year: 'numeric' })}` : ''}
|
|
226
|
+
{asked ? `${since ? ' · ' : ''}${asked} request${asked === 1 ? '' : 's'} out through them` : ''}
|
|
227
|
+
</span>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
})()}
|
|
231
|
+
<div style={row}>
|
|
232
|
+
<span style={label}>Rank</span>
|
|
233
|
+
<span style={{ flex: 1 }}>{i.rank ? `#${i.rank} of ${i.of} by who only they reach` : 'Ranked once their circle is scanned'}</span>
|
|
234
|
+
</div>
|
|
173
235
|
</div>
|
|
174
236
|
)}
|
|
175
237
|
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// The Social tab's "once a day" live messages sync (experimental): once per
|
|
4
|
+
// app session, if it's switched on, the last sync is over a day old, it's
|
|
5
|
+
// daytime and the scanner is free, read the messages list (scripts/scrape.py
|
|
6
|
+
// --messages). Nothing else starts it.
|
|
7
|
+
|
|
8
|
+
import { useEffect } from 'react';
|
|
9
|
+
import { watchScanner, scannerNow, beginScrape } from '../../lib/scraper-client';
|
|
10
|
+
import { IS_DEMO } from '../../lib/demo';
|
|
11
|
+
|
|
12
|
+
let tried = false;
|
|
13
|
+
|
|
14
|
+
export default function SocialAutoSync() {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (IS_DEMO || tried) return undefined;
|
|
17
|
+
let stop = () => {};
|
|
18
|
+
fetch('/api/social').then((r) => r.json()).then(({ social }) => {
|
|
19
|
+
if (!social?.autoSync || tried) return;
|
|
20
|
+
const last = social.liveAt ? Date.parse(social.liveAt) : 0;
|
|
21
|
+
const hour = new Date().getHours();
|
|
22
|
+
if (Date.now() - last < 24 * 3600 * 1000 || hour < 9 || hour >= 19) return;
|
|
23
|
+
stop = watchScanner(() => {
|
|
24
|
+
const now = scannerNow();
|
|
25
|
+
if (!now.known || tried) return;
|
|
26
|
+
tried = true;
|
|
27
|
+
stop();
|
|
28
|
+
if (!now.running) beginScrape('messages').catch(() => {});
|
|
29
|
+
});
|
|
30
|
+
}).catch(() => {});
|
|
31
|
+
return () => stop();
|
|
32
|
+
}, []);
|
|
33
|
+
return null;
|
|
34
|
+
}
|