@sleep2agi/agent-network-dashboard 0.6.3-preview.2 → 0.6.3-preview.3
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/BUILD_COMMIT +1 -1
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +1 -0
- package/.next/build/chunks/{0emd_0_wlp72._.js → 0.c6_10r6n.u._.js} +167 -167
- package/.next/build/chunks/0.c6_10r6n.u._.js.map +47 -0
- package/.next/build/chunks/{[root-of-the-server]__0cyvgtf._.js → [root-of-the-server]__08pixqk._.js} +7 -7
- package/.next/build/chunks/{[root-of-the-server]__0cyvgtf._.js.map → [root-of-the-server]__08pixqk._.js.map} +2 -2
- package/.next/build/chunks/{[root-of-the-server]__0z.4hvh._.js → [root-of-the-server]__0fwl53y._.js} +3 -3
- package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_0hycypy._.js +13 -0
- package/.next/build/postcss.js +4 -4
- package/.next/build-manifest.json +8 -8
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/diagnostics/route-bundle-stats.json +168 -168
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +2 -2
- package/.next/required-server-files.json +2 -2
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_global-error/page/build-manifest.json +5 -5
- package/.next/server/app/_global-error/page.js +9 -9
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +8 -8
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_global-error.segments/_full.segment.rsc +8 -8
- package/.next/server/app/_global-error.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/build-manifest.json +5 -5
- package/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
- package/.next/server/app/_not-found/page.js +12 -12
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +13 -13
- package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -8
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page/build-manifest.json +5 -5
- package/.next/server/app/admin/page/next-font-manifest.json +1 -1
- package/.next/server/app/admin/page.js +13 -13
- package/.next/server/app/admin/page.js.nft.json +1 -1
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +15 -15
- package/.next/server/app/admin.segments/_full.segment.rsc +15 -15
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +8 -8
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/api/anet/config/route.js +6 -6
- package/.next/server/app/api/anet/config/route.js.nft.json +1 -1
- package/.next/server/app/api/anet/host-supervisors/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/anet/host-supervisors/route/build-manifest.json +9 -0
- package/.next/server/app/api/anet/host-supervisors/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/anet/host-supervisors/route.js +7 -0
- package/.next/server/app/api/anet/host-supervisors/route.js.map +5 -0
- package/.next/server/app/api/anet/host-supervisors/route.js.nft.json +1 -0
- package/.next/server/app/api/anet/host-supervisors/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/anet/node-config/route.js +6 -6
- package/.next/server/app/api/anet/node-config/route.js.nft.json +1 -1
- package/.next/server/app/api/anet/node-create/route.js +6 -6
- package/.next/server/app/api/anet/node-create/route.js.nft.json +1 -1
- package/.next/server/app/api/anet/node-lifecycle/route.js +6 -6
- package/.next/server/app/api/anet/node-lifecycle/route.js.nft.json +1 -1
- package/.next/server/app/api/auth/login/route.js +7 -7
- package/.next/server/app/api/auth/login/route.js.nft.json +1 -1
- package/.next/server/app/api/auth/logout/route.js +7 -7
- package/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/server/app/api/auth/v3/route.js +7 -7
- package/.next/server/app/api/auth/v3/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/audit-log/route.js +6 -6
- package/.next/server/app/api/hub/audit-log/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/auth/route.js +7 -7
- package/.next/server/app/api/hub/auth/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/broadcast/route.js +6 -6
- package/.next/server/app/api/hub/broadcast/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/events/route.js +6 -6
- package/.next/server/app/api/hub/events/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/health/route.js +6 -6
- package/.next/server/app/api/hub/health/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/inbox/route.js +6 -6
- package/.next/server/app/api/hub/inbox/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/messages/route.js +6 -6
- package/.next/server/app/api/hub/messages/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/networks/route.js +6 -6
- package/.next/server/app/api/hub/networks/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/nodes/route.js +6 -6
- package/.next/server/app/api/hub/nodes/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/register/route.js +6 -6
- package/.next/server/app/api/hub/register/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/send/route.js +6 -6
- package/.next/server/app/api/hub/send/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js +6 -6
- package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/server/[hostname]/health/route.js +6 -6
- package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/server-logs/route.js +6 -6
- package/.next/server/app/api/hub/server-logs/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/servers/route.js +6 -6
- package/.next/server/app/api/hub/servers/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/session/route.js +6 -6
- package/.next/server/app/api/hub/session/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/stats/route.js +6 -6
- package/.next/server/app/api/hub/stats/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/status/route.js +6 -6
- package/.next/server/app/api/hub/status/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/task-events/route.js +6 -6
- package/.next/server/app/api/hub/task-events/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/tasks/route.js +6 -6
- package/.next/server/app/api/hub/tasks/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/tmux/route.js +6 -6
- package/.next/server/app/api/hub/tmux/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/tokens/route.js +6 -6
- package/.next/server/app/api/hub/tokens/route.js.nft.json +1 -1
- package/.next/server/app/api/hub/upload/route.js +6 -6
- package/.next/server/app/api/hub/upload/route.js.nft.json +1 -1
- package/.next/server/app/favicon.ico/route.js +6 -6
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +15 -15
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +8 -8
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page/build-manifest.json +5 -5
- package/.next/server/app/login/page/next-font-manifest.json +1 -1
- package/.next/server/app/login/page.js +13 -13
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +15 -15
- package/.next/server/app/login.segments/_full.segment.rsc +15 -15
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +8 -8
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- package/.next/server/app/logs/page/build-manifest.json +5 -5
- package/.next/server/app/logs/page/next-font-manifest.json +1 -1
- package/.next/server/app/logs/page.js +13 -13
- package/.next/server/app/logs/page.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +15 -15
- package/.next/server/app/logs.segments/_full.segment.rsc +15 -15
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +8 -8
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/manifest.webmanifest/route.js +6 -6
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next/server/app/messages/page/build-manifest.json +5 -5
- package/.next/server/app/messages/page/next-font-manifest.json +1 -1
- package/.next/server/app/messages/page.js +13 -13
- package/.next/server/app/messages/page.js.nft.json +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +15 -15
- package/.next/server/app/messages.segments/_full.segment.rsc +15 -15
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +8 -8
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page/build-manifest.json +5 -5
- package/.next/server/app/node/page/next-font-manifest.json +1 -1
- package/.next/server/app/node/page.js +13 -13
- package/.next/server/app/node/page.js.nft.json +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +15 -15
- package/.next/server/app/node.segments/_full.segment.rsc +15 -15
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +8 -8
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page/build-manifest.json +5 -5
- package/.next/server/app/nodes/page/next-font-manifest.json +1 -1
- package/.next/server/app/nodes/page.js +13 -13
- package/.next/server/app/nodes/page.js.nft.json +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +15 -15
- package/.next/server/app/nodes.segments/_full.segment.rsc +15 -15
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +8 -8
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page/build-manifest.json +5 -5
- package/.next/server/app/page/next-font-manifest.json +1 -1
- package/.next/server/app/page.js +13 -13
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page/build-manifest.json +5 -5
- package/.next/server/app/server-logs/page/next-font-manifest.json +1 -1
- package/.next/server/app/server-logs/page.js +13 -13
- package/.next/server/app/server-logs/page.js.nft.json +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +15 -15
- package/.next/server/app/server-logs.segments/_full.segment.rsc +15 -15
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +8 -8
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/servers/page/build-manifest.json +5 -5
- package/.next/server/app/servers/page/next-font-manifest.json +1 -1
- package/.next/server/app/servers/page.js +13 -13
- package/.next/server/app/servers/page.js.nft.json +1 -1
- package/.next/server/app/servers/page_client-reference-manifest.js +1 -1
- package/.next/server/app/servers.html +2 -2
- package/.next/server/app/servers.rsc +15 -15
- package/.next/server/app/servers.segments/_full.segment.rsc +15 -15
- package/.next/server/app/servers.segments/_head.segment.rsc +4 -4
- package/.next/server/app/servers.segments/_index.segment.rsc +8 -8
- package/.next/server/app/servers.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/servers.segments/servers/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/servers.segments/servers.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page/build-manifest.json +5 -5
- package/.next/server/app/settings/networks/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/networks/page.js +13 -13
- package/.next/server/app/settings/networks/page.js.nft.json +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +15 -15
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +8 -8
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page/build-manifest.json +5 -5
- package/.next/server/app/settings/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/page.js +13 -13
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page/build-manifest.json +5 -5
- package/.next/server/app/settings/tokens/page/next-font-manifest.json +1 -1
- package/.next/server/app/settings/tokens/page.js +13 -13
- package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +15 -15
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +8 -8
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +15 -15
- package/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +8 -8
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page/build-manifest.json +5 -5
- package/.next/server/app/tasks/[id]/page/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/[id]/page.js +13 -13
- package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page/build-manifest.json +5 -5
- package/.next/server/app/tasks/page/next-font-manifest.json +1 -1
- package/.next/server/app/tasks/page.js +13 -13
- package/.next/server/app/tasks/page.js.nft.json +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +15 -15
- package/.next/server/app/tasks.segments/_full.segment.rsc +15 -15
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +8 -8
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/0.c6_next_0pcz2.g._.js +10 -0
- package/.next/server/chunks/0.c6_next_0pcz2.g._.js.map +1 -0
- package/.next/server/chunks/0.c6_next_dist_09~ar_8._.js +18 -0
- package/.next/server/chunks/0.c6_next_dist_09~ar_8._.js.map +1 -0
- package/.next/server/chunks/0.c6_next_dist_esm_build_templates_app-route_06oih...js +3 -0
- package/.next/server/chunks/0.c6_next_dist_esm_build_templates_app-route_06oih...js.map +1 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_anet_config_route_actions_0nj9wq2.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_anet_host-supervisors_route_actions_0fh4up~.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_anet_node-config_route_actions_0kw1wz2.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_anet_node-create_route_actions_0kq6-2f.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_anet_node-lifecycle_route_actions_09r4wn9.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_auth_logout_route_actions_0_cwww~.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_audit-log_route_actions_0exsmg8.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_broadcast_route_actions_01-4p_i.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_messages_route_actions_01jyu9z.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_networks_route_actions_0h7r4md.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_register_route_actions_0ov~dl8.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_server-logs_route_actions_0_h9yne.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_servers_route_actions_0.ey2qn.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_session_route_actions_0wtqww3.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_api_hub_task-events_route_actions_0l8.nc5.js +3 -0
- package/.next/server/chunks/0_pe__next-internal_server_app_manifest_webmanifest_route_actions_0d3r.pv.js +3 -0
- package/.next/server/chunks/0i9l_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_07itbtq.js +3 -0
- package/.next/server/chunks/0i9l_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0-8c~3o.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0-7r2dj._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0-7r2dj._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__028hv1n._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0go6s0b._.js.map → [root-of-the-server]__028hv1n._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__03ys178._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__03ys178._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__04a.eo.._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__04a.eo.._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__068os9q._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__00gb0kv._.js.map → [root-of-the-server]__068os9q._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__07zi.ek._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__04a200m._.js.map → [root-of-the-server]__07zi.ek._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__093ci4f._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0osz.5l._.js.map → [root-of-the-server]__093ci4f._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0a15lr7._.js +6 -0
- package/.next/server/chunks/[root-of-the-server]__0a15lr7._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0b3di-h._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0b3di-h._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0btxlsj._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0btxlsj._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0cj7ayx._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0cj7ayx._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0d79s0l._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0d79s0l._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0e6-5c6._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0e6-5c6._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0ew4z8.._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ew4z8.._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0heyxhl._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0heyxhl._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0j5b~v_._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0j5b~v_._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0jg663w._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0jg663w._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0kmgvoi._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0f5-nhl._.js.map → [root-of-the-server]__0kmgvoi._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0k~3_lg._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0k~3_lg._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0m0vttp._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0m0vttp._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0n80ki-._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0n80ki-._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0o2hqfd._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0o2hqfd._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0q-7xgc._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0q-7xgc._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0sl8a3k._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0ixnvi-._.js.map → [root-of-the-server]__0sl8a3k._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tthh2f._.js +20 -0
- package/.next/server/chunks/[root-of-the-server]__0tthh2f._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0vlfolz._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0vlfolz._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0wwcfb7._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0wwcfb7._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__10hux.s._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__10hux.s._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__10ods8b._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__10ods8b._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__1154y41._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1154y41._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__11d9kku._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__11d9kku._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__11frds1._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__11frds1._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__137.u64._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0._v9vf._.js.map → [root-of-the-server]__137.u64._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__13bjk~b._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__13bjk~b._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__13vqkd8._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__13vqkd8._.js.map +1 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_auth_login_route_actions_08dnh4t.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_auth_v3_route_actions_11yqn8e.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_auth_route_actions_0-r8e._.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_events_route_actions_08-zory.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_health_route_actions_0csmyxs.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_inbox_route_actions_0orjrs_.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_nodes_route_actions_03gqvjo.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_send_route_actions_019lm67.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_stats_route_actions_0bsh3.q.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_status_route_actions_0qkboxw.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_tasks_route_actions_0syaced.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_tmux_route_actions_09-s91e.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_tokens_route_actions_0vwlbn4.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_api_hub_upload_route_actions_04ex2ru.js +3 -0
- package/.next/server/chunks/dashboard-pr4-338__next-internal_server_app_favicon_ico_route_actions_103m2zz.js +3 -0
- package/.next/server/chunks/ssr/0.c6_04u3otz._.js +3 -0
- package/.next/server/chunks/ssr/0.c6_04u3otz._.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_0h_jbfb._.js +3 -0
- package/.next/server/chunks/ssr/0.c6_0h_jbfb._.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_047jqdj._.js → 0.c6_0p_fgv9._.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_0p_fgv9._.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_1382_4c._.js +3 -0
- package/.next/server/chunks/ssr/0.c6_1382_4c._.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_0bnhxgq._.js → 0.c6_next_01qd0pt._.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_01qd0pt._.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_11v9-0y._.js +6 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_11v9-0y._.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_0zbfvho._.js +3 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_0zbfvho._.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_builtin_forbidden_0og_6kz.js +3 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_builtin_forbidden_0og_6kz.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_builtin_global-error_0caj1ti.js +3 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_builtin_global-error_0caj1ti.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_builtin_unauthorized_11zohg7.js +3 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_client_components_builtin_unauthorized_11zohg7.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0.3fpy_.js → 0.c6_next_dist_esm_build_templates_app-page_0.rd_n2.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0.rd_n2.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0c0fklb.js → 0.c6_next_dist_esm_build_templates_app-page_02_g1y5.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_02_g1y5.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0j5dp2i.js → 0.c6_next_dist_esm_build_templates_app-page_03-r5hy.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_03-r5hy.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_06x82hl.js → 0.c6_next_dist_esm_build_templates_app-page_038imr6.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_038imr6.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_10jwiid.js → 0.c6_next_dist_esm_build_templates_app-page_04cxi9v.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_04cxi9v.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0ospxq4.js → 0.c6_next_dist_esm_build_templates_app-page_092bhdz.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_092bhdz.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0oz02hy.js → 0.c6_next_dist_esm_build_templates_app-page_0c~grfv.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0c~grfv.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0g6qhwo.js +4 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0g6qhwo.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0r4~r~z.js → 0.c6_next_dist_esm_build_templates_app-page_0gi1tz6.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0gi1tz6.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0v3so27.js → 0.c6_next_dist_esm_build_templates_app-page_0h1azo~.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0h1azo~.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0rjp8.q.js → 0.c6_next_dist_esm_build_templates_app-page_0pc9ccx.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0pc9ccx.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_133cpo2.js → 0.c6_next_dist_esm_build_templates_app-page_0sei4rb.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0sei4rb.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_0u.fjk1.js → 0.c6_next_dist_esm_build_templates_app-page_0tby~iu.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0tby~iu.js.map +1 -0
- package/.next/server/chunks/ssr/{0emd_next_dist_esm_build_templates_app-page_016_j7..js → 0.c6_next_dist_esm_build_templates_app-page_0xgw-4u.js} +3 -3
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0xgw-4u.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0z7j9o3.js +4 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_0z7j9o3.js.map +1 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_12rjvbo.js +4 -0
- package/.next/server/chunks/ssr/0.c6_next_dist_esm_build_templates_app-page_12rjvbo.js.map +1 -0
- package/.next/server/chunks/ssr/0_pe__next-internal_server_app_settings_networks_page_actions_0kglov-.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0-n_oet._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0-n_oet._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03oij6l._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03oij6l._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__04ckonz._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__04ckonz._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__079442f._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__079442f._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__08pwqsr._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__08pwqsr._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_2atr0._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_2atr0._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_o_gb.._.js +19 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0_o_gb.._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0cuz-m.._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0cuz-m.._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0epg4j8._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0epg4j8._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0i.i6qc._.js +8 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0i.i6qc._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0jgx8j4._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0jgx8j4._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0l.6ig6._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0l.6ig6._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0mazm28._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0mazm28._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0nlid7-._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0nlid7-._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0qzyg15._.js +6 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0yacr~n._.js.map → [root-of-the-server]__0qzyg15._.js.map} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0rncvti._.js +33 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0rncvti._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0rz6yi6._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0rz6yi6._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0tetp~6._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0tetp~6._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0tkjwre._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0tkjwre._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0w0iso1._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0w0iso1._.js.map +1 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0utp0~w._.js → [root-of-the-server]__0~_y~l~._.js} +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0~_y~l~._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0~udac-._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0~udac-._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10nb6z0._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10nb6z0._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__12rcf73._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__12rcf73._.js.map +1 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__132lybf._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__132lybf._.js.map +1 -0
- package/.next/server/chunks/ssr/{dash-main-prep_04jom44._.js → dashboard-pr4-338_0chwajz._.js} +3 -3
- package/.next/server/chunks/ssr/dashboard-pr4-338_0chwajz._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app__global-error_page_actions_02gx6o5.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app__not-found_page_actions_12fmww3.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_admin_page_actions_01fk2aw.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_login_page_actions_05yexf9.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_logs_page_actions_0-p3l45.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_messages_page_actions_0phjj48.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_node_page_actions_0a5k6xf.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_nodes_page_actions_0fstwl_.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_page_actions_0yw2h8..js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_server-logs_page_actions_0fu1.t-.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_servers_page_actions_02z5_.n.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_settings_page_actions_0fi5o6~.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_settings_tokens_page_actions_0crncl9.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_tasks_[id]_page_actions_11owcxr.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_tasks_page_actions_0s6kojk.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338__next-internal_server_app_tasks_page_actions_0s6kojk.js.map +1 -0
- package/.next/server/chunks/ssr/{dash-main-prep_app_105mjhx._.js → dashboard-pr4-338_app_0.2ehyt._.js} +2 -2
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0.2ehyt._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_01m0vjp._.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_01m0vjp._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_05klhv.._.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_05klhv.._.js.map +1 -0
- package/.next/server/chunks/ssr/{dash-main-prep_app_0-ei3ea._.js → dashboard-pr4-338_app_0_vh2hn._.js} +2 -2
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0_vh2hn._.js.map +1 -0
- package/.next/server/chunks/ssr/{dash-main-prep_app_0vx7mgv._.js → dashboard-pr4-338_app_0a352ge._.js} +2 -2
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0a352ge._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0k0.-6z._.js +9 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0k0.-6z._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0l07md.._.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0l07md.._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0s-yvtp._.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0s-yvtp._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0tkwn_n._.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0tkwn_n._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0~ttnqo._.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_0~ttnqo._.js.map +1 -0
- package/.next/server/chunks/ssr/{dash-main-prep_app_components_0p9bpx_._.js → dashboard-pr4-338_app_components_05m_zo2._.js} +3 -3
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_components_05m_zo2._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_nodes_page_tsx_0.1qp86._.js +4 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_nodes_page_tsx_0.1qp86._.js.map +1 -0
- package/.next/server/chunks/ssr/{dash-main-prep_app_servers_page_tsx_0zrm2-z._.js → dashboard-pr4-338_app_servers_page_tsx_0ey5.ky._.js} +2 -2
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_servers_page_tsx_0ey5.ky._.js.map +1 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_tasks_page_tsx_03istgm._.js +3 -0
- package/.next/server/chunks/ssr/dashboard-pr4-338_app_tasks_page_tsx_03istgm._.js.map +1 -0
- package/.next/server/middleware-build-manifest.js +8 -8
- package/.next/server/middleware.js +3 -3
- package/.next/server/middleware.js.nft.json +1 -1
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +15 -15
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/0096w.cu1g6rl.js +1 -0
- package/.next/static/chunks/01ybxwpv04i86.js +1 -0
- package/.next/static/chunks/07vtb2z31qs~9.js +1 -0
- package/.next/static/chunks/{0fval~_yhzi56.js → 0afh7zu_obnss.js} +1 -1
- package/.next/static/chunks/{0_q1lcvifkup1.js → 0dybhs7oad.rq.js} +2 -2
- package/.next/static/chunks/0dzyz0tl5mmyh.js +1 -0
- package/.next/static/chunks/0ejar-4yts1fh.js +1 -0
- package/.next/static/chunks/0kn_928z6zi9y.js +1 -0
- package/.next/static/chunks/{0_eh6-xnv.vas.js → 0ofd00o67o6ht.js} +1 -1
- package/.next/static/chunks/0okyajjiuijbh.css +1 -0
- package/.next/static/chunks/0qbm384wxdx7z.js +1 -0
- package/.next/static/chunks/0r4f9a-1h0l8n.js +7 -0
- package/.next/static/chunks/{0r5q0e.lci5bj.js → 0rxx2o22fr4zs.js} +2 -2
- package/.next/static/chunks/0w7j11ubgyq01.js +1 -0
- package/.next/static/chunks/{05n6g8whg1rnw.js → 0y5af7sl4m2wq.js} +2 -2
- package/.next/static/chunks/0z7gm7mwxvgm7.js +1 -0
- package/.next/static/chunks/0zezb.fc41oyb.js +1 -0
- package/.next/static/chunks/0zta~z4xpak8b.js +2 -0
- package/.next/static/chunks/{0uroeke.37_qr.js → 10r68rewywvsz.js} +1 -1
- package/.next/static/chunks/10rif2p3a6hdl.js +1 -0
- package/.next/static/chunks/11d7__keaen8o.js +1 -0
- package/.next/static/chunks/1238uy.r8ebu0.js +1 -0
- package/.next/static/chunks/124rj3g69riz4.js +4 -0
- package/.next/static/chunks/12xixbh.66y8a.js +31 -0
- package/.next/static/chunks/{074qn1p5esp6v.js → 140-m.i_n29hk.js} +1 -1
- package/.next/static/chunks/14z1ozqztujxj.js +2 -0
- package/.next/static/chunks/turbopack-11~h6e3rogmsb.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/app/api/anet/host-supervisors/route.ts +86 -0
- package/app/components/CreateNodeWizard.tsx +55 -21
- package/app/components/HostSupervisorPicker.tsx +280 -0
- package/package.json +1 -1
- package/.next/build/chunks/0emd_0_wlp72._.js.map +0 -47
- package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_0ji7sn2._.js +0 -13
- package/.next/server/chunks/0emd_next_0zjoz0g._.js +0 -10
- package/.next/server/chunks/0emd_next_0zjoz0g._.js.map +0 -1
- package/.next/server/chunks/0emd_next_dist_07_njzq._.js +0 -18
- package/.next/server/chunks/0emd_next_dist_07_njzq._.js.map +0 -1
- package/.next/server/chunks/0emd_next_dist_esm_build_templates_app-route_0ze.4~8.js +0 -3
- package/.next/server/chunks/0emd_next_dist_esm_build_templates_app-route_0ze.4~8.js.map +0 -1
- package/.next/server/chunks/0eys__next-internal_server_app_api_anet_node-config_route_actions_0vpbqmt.js +0 -3
- package/.next/server/chunks/0eys__next-internal_server_app_api_anet_node-create_route_actions_0ack5_x.js +0 -3
- package/.next/server/chunks/0eys__next-internal_server_app_api_anet_node-lifecycle_route_actions_13z6pk9.js +0 -3
- package/.next/server/chunks/0eys__next-internal_server_app_api_hub_server-logs_route_actions_13k0lzq.js +0 -3
- package/.next/server/chunks/0eys__next-internal_server_app_api_hub_task-events_route_actions_0eayccx.js +0 -3
- package/.next/server/chunks/0eys__next-internal_server_app_manifest_webmanifest_route_actions_03s5neg.js +0 -3
- package/.next/server/chunks/0raa_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0q9dcjz.js +0 -3
- package/.next/server/chunks/0raa_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0s8k6m..js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0-jphjp._.js +0 -6
- package/.next/server/chunks/[root-of-the-server]__0-jphjp._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0-po71t._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0-po71t._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0.5kg~w._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0.5kg~w._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0._v9vf._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__00gb0kv._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__013ptvb._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__013ptvb._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__03c6cit._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__03c6cit._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__04a200m._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__05494tj._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__05494tj._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__065pywy._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__065pywy._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__07kt6aj._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__07kt6aj._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__07x377i._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__07x377i._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0anx8ak._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0anx8ak._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0dqs1t5._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0dqs1t5._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0efa-e~._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0efa-e~._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0eonvri._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0eonvri._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0f5-nhl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0f~mkgn._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0f~mkgn._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0go6s0b._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0ixnvi-._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0jlzmrm._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0jlzmrm._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0k-0~jy._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0k-0~jy._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0kalz0~._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0kalz0~._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0ksm0bm._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0ksm0bm._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0lcdg4r._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0lcdg4r._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0lud1_2._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0lud1_2._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0n__sw5._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0n__sw5._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0ocd-7~._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0ocd-7~._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0osz.5l._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0uc.~eb._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0uc.~eb._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0z~3-vx._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0z~3-vx._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0z~50bw._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0z~50bw._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__10z4o-.._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__10z4o-.._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__11swhm2._.js +0 -20
- package/.next/server/chunks/[root-of-the-server]__11swhm2._.js.map +0 -1
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_anet_config_route_actions_0eb-2a9.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_auth_login_route_actions_0_0zk8~.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_auth_logout_route_actions_0yy6u4n.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_auth_v3_route_actions_09dg3rk.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_audit-log_route_actions_03wia4o.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_auth_route_actions_0k.l0a-.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_broadcast_route_actions_02wotps.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_events_route_actions_0i1zyq~.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_health_route_actions_0fe6lsz.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_inbox_route_actions_0iup.xe.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_messages_route_actions_0syf65m.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_networks_route_actions_04dtp_m.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_nodes_route_actions_0aghuq9.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_register_route_actions_04g3.gx.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_send_route_actions_01m4__p.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_servers_route_actions_10lz59w.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_session_route_actions_03ce57x.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_stats_route_actions_0~xcq1-.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_status_route_actions_0slss8r.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_tasks_route_actions_0yvhaa_.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_tmux_route_actions_0_f8h.h.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_tokens_route_actions_0ke~8y8.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_api_hub_upload_route_actions_02jnx8r.js +0 -3
- package/.next/server/chunks/dash-main-prep__next-internal_server_app_favicon_ico_route_actions_09bgfok.js +0 -3
- package/.next/server/chunks/ssr/0emd_047jqdj._.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_06kedq-._.js +0 -3
- package/.next/server/chunks/ssr/0emd_06kedq-._.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_0_26t76._.js +0 -3
- package/.next/server/chunks/ssr/0emd_0_26t76._.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_13t5_lo._.js +0 -3
- package/.next/server/chunks/ssr/0emd_13t5_lo._.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_0bnhxgq._.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_00jgl6k._.js +0 -6
- package/.next/server/chunks/ssr/0emd_next_dist_00jgl6k._.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_00j7vwk._.js +0 -3
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_00j7vwk._.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_builtin_forbidden_1087x.v.js +0 -3
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_builtin_forbidden_1087x.v.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_builtin_global-error_0qrbd.p.js +0 -3
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_builtin_global-error_0qrbd.p.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_builtin_unauthorized_0~qxw.r.js +0 -3
- package/.next/server/chunks/ssr/0emd_next_dist_client_components_builtin_unauthorized_0~qxw.r.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0.3fpy_.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_016_j7..js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_06x82hl.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0c0fklb.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0j5dp2i.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0kgge41.js +0 -4
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0kgge41.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0ospxq4.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0oz02hy.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0r4~r~z.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0rjp8.q.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0tqydx2.js +0 -4
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0tqydx2.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0u.fjk1.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0v3so27.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0xikgs1.js +0 -4
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_0xikgs1.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_10jwiid.js.map +0 -1
- package/.next/server/chunks/ssr/0emd_next_dist_esm_build_templates_app-page_133cpo2.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__00c-m~h._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__00c-m~h._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__00t8yko._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__00t8yko._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__02e9x~u._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__02e9x~u._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0337u7~._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0337u7~._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0342o.i._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0342o.i._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__03mvbey._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__03mvbey._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0_nj.h9._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0_nj.h9._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0a..bh5._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0a..bh5._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0bhrs1j._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0bhrs1j._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0c1te6d._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0c1te6d._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0cbfccb._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0cbfccb._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0dog1cm._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0dog1cm._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0ei826c._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0ei826c._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0es3uv3._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0es3uv3._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0j5c4hr._.js +0 -8
- package/.next/server/chunks/ssr/[root-of-the-server]__0j5c4hr._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0otzd58._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0otzd58._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0ppvi5_._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0ppvi5_._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0p~jybs._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0p~jybs._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0utp0~w._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0x7j2o1._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0x7j2o1._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0yacr~n._.js +0 -6
- package/.next/server/chunks/ssr/[root-of-the-server]__0~jfwze._.js +0 -19
- package/.next/server/chunks/ssr/[root-of-the-server]__0~jfwze._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0~zzavb._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0~zzavb._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__12oz5~2._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__12oz5~2._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__13ti3mc._.js +0 -33
- package/.next/server/chunks/ssr/[root-of-the-server]__13ti3mc._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_04jom44._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app__global-error_page_actions_07kk3nm.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app__not-found_page_actions_0cb6n8e.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_admin_page_actions_0whq~om.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_login_page_actions_0lfb5vh.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_logs_page_actions_0a9g2gh.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_messages_page_actions_0_0a~oj.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_node_page_actions_0nt83da.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_nodes_page_actions_001898j.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_page_actions_10t6_cb.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_server-logs_page_actions_10i2y0v.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_servers_page_actions_0yfsnrz.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_settings_networks_page_actions_0.6z6e9.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_settings_page_actions_0t_etc1.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_settings_tokens_page_actions_0g~ilh~.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_tasks_[id]_page_actions_0jc~43n.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep__next-internal_server_app_tasks_page_actions_0k0da_-.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_0-ei3ea._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_00xe5ww._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_00xe5ww._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_02jbzgm._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_02jbzgm._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_0_8.j.9._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_0_8.j.9._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_0em9eg-._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_0em9eg-._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_0l5pr3q._.js +0 -9
- package/.next/server/chunks/ssr/dash-main-prep_app_0l5pr3q._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_0vx7mgv._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_0z7jy81._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_0z7jy81._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_0~~c86p._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_0~~c86p._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_105mjhx._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_components_0p9bpx_._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_nodes_page_tsx_0k20g4a._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_nodes_page_tsx_0k20g4a._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_servers_page_tsx_0zrm2-z._.js.map +0 -1
- package/.next/server/chunks/ssr/dash-main-prep_app_tasks_page_tsx_013t47c._.js +0 -3
- package/.next/server/chunks/ssr/dash-main-prep_app_tasks_page_tsx_013t47c._.js.map +0 -1
- package/.next/static/chunks/0..6bvayc~2v_.js +0 -31
- package/.next/static/chunks/0.4it~9-lgmdc.js +0 -7
- package/.next/static/chunks/008f8y0vjzx6q.js +0 -1
- package/.next/static/chunks/026_lvbjd5ehm.js +0 -1
- package/.next/static/chunks/038w1~wz8c.b_.js +0 -1
- package/.next/static/chunks/04ab~m7-c0hma.js +0 -1
- package/.next/static/chunks/0cj3regqtbzgi.js +0 -1
- package/.next/static/chunks/0e.5fp53g~gjx.js +0 -1
- package/.next/static/chunks/0g76.rws7jb6p.js +0 -4
- package/.next/static/chunks/0lfg~2g8_-82b.js +0 -1
- package/.next/static/chunks/0n5d~z6jskv75.js +0 -1
- package/.next/static/chunks/0nvbpp15hjh1k.js +0 -1
- package/.next/static/chunks/0p..-4gjx~ua~.css +0 -1
- package/.next/static/chunks/0u5kv-8r.53y_.js +0 -1
- package/.next/static/chunks/0w66-7icvft37.js +0 -2
- package/.next/static/chunks/0w8djdzyh1ar0.js +0 -1
- package/.next/static/chunks/0wikbewneplt6.js +0 -1
- package/.next/static/chunks/0zozgpnx9_ex_.js +0 -1
- package/.next/static/chunks/135hvx8t2m~5~.js +0 -1
- package/.next/static/chunks/turbopack-1071etkj.k-n~.js +0 -1
- /package/.next/build/chunks/{[root-of-the-server]__0z.4hvh._.js.map → [root-of-the-server]__0fwl53y._.js.map} +0 -0
- /package/.next/build/chunks/{[turbopack-node]_transforms_postcss_ts_0ji7sn2._.js.map → [turbopack-node]_transforms_postcss_ts_0hycypy._.js.map} +0 -0
- /package/.next/server/chunks/{0eys__next-internal_server_app_api_anet_node-config_route_actions_0vpbqmt.js.map → 0_pe__next-internal_server_app_api_anet_config_route_actions_0nj9wq2.js.map} +0 -0
- /package/.next/server/chunks/{0eys__next-internal_server_app_api_anet_node-create_route_actions_0ack5_x.js.map → 0_pe__next-internal_server_app_api_anet_host-supervisors_route_actions_0fh4up~.js.map} +0 -0
- /package/.next/server/chunks/{0eys__next-internal_server_app_api_anet_node-lifecycle_route_actions_13z6pk9.js.map → 0_pe__next-internal_server_app_api_anet_node-config_route_actions_0kw1wz2.js.map} +0 -0
- /package/.next/server/chunks/{0eys__next-internal_server_app_api_hub_server-logs_route_actions_13k0lzq.js.map → 0_pe__next-internal_server_app_api_anet_node-create_route_actions_0kq6-2f.js.map} +0 -0
- /package/.next/server/chunks/{0eys__next-internal_server_app_api_hub_task-events_route_actions_0eayccx.js.map → 0_pe__next-internal_server_app_api_anet_node-lifecycle_route_actions_09r4wn9.js.map} +0 -0
- /package/.next/server/chunks/{0eys__next-internal_server_app_manifest_webmanifest_route_actions_03s5neg.js.map → 0_pe__next-internal_server_app_api_auth_logout_route_actions_0_cwww~.js.map} +0 -0
- /package/.next/server/chunks/{0raa_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0q9dcjz.js.map → 0_pe__next-internal_server_app_api_hub_audit-log_route_actions_0exsmg8.js.map} +0 -0
- /package/.next/server/chunks/{0raa_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0s8k6m..js.map → 0_pe__next-internal_server_app_api_hub_broadcast_route_actions_01-4p_i.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_anet_config_route_actions_0eb-2a9.js.map → 0_pe__next-internal_server_app_api_hub_messages_route_actions_01jyu9z.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_auth_login_route_actions_0_0zk8~.js.map → 0_pe__next-internal_server_app_api_hub_networks_route_actions_0h7r4md.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_auth_logout_route_actions_0yy6u4n.js.map → 0_pe__next-internal_server_app_api_hub_register_route_actions_0ov~dl8.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_auth_v3_route_actions_09dg3rk.js.map → 0_pe__next-internal_server_app_api_hub_server-logs_route_actions_0_h9yne.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_audit-log_route_actions_03wia4o.js.map → 0_pe__next-internal_server_app_api_hub_servers_route_actions_0.ey2qn.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_auth_route_actions_0k.l0a-.js.map → 0_pe__next-internal_server_app_api_hub_session_route_actions_0wtqww3.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_broadcast_route_actions_02wotps.js.map → 0_pe__next-internal_server_app_api_hub_task-events_route_actions_0l8.nc5.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_events_route_actions_0i1zyq~.js.map → 0_pe__next-internal_server_app_manifest_webmanifest_route_actions_0d3r.pv.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_health_route_actions_0fe6lsz.js.map → 0i9l_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_07itbtq.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_inbox_route_actions_0iup.xe.js.map → 0i9l_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0-8c~3o.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_messages_route_actions_0syf65m.js.map → dashboard-pr4-338__next-internal_server_app_api_auth_login_route_actions_08dnh4t.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_networks_route_actions_04dtp_m.js.map → dashboard-pr4-338__next-internal_server_app_api_auth_v3_route_actions_11yqn8e.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_nodes_route_actions_0aghuq9.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_auth_route_actions_0-r8e._.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_register_route_actions_04g3.gx.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_events_route_actions_08-zory.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_send_route_actions_01m4__p.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_health_route_actions_0csmyxs.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_servers_route_actions_10lz59w.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_inbox_route_actions_0orjrs_.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_session_route_actions_03ce57x.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_nodes_route_actions_03gqvjo.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_stats_route_actions_0~xcq1-.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_send_route_actions_019lm67.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_status_route_actions_0slss8r.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_stats_route_actions_0bsh3.q.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_tasks_route_actions_0yvhaa_.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_status_route_actions_0qkboxw.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_tmux_route_actions_0_f8h.h.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_tasks_route_actions_0syaced.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_tokens_route_actions_0ke~8y8.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_tmux_route_actions_09-s91e.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_api_hub_upload_route_actions_02jnx8r.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_tokens_route_actions_0vwlbn4.js.map} +0 -0
- /package/.next/server/chunks/{dash-main-prep__next-internal_server_app_favicon_ico_route_actions_09bgfok.js.map → dashboard-pr4-338__next-internal_server_app_api_hub_upload_route_actions_04ex2ru.js.map} +0 -0
- /package/.next/server/chunks/{ssr/dash-main-prep__next-internal_server_app__global-error_page_actions_07kk3nm.js.map → dashboard-pr4-338__next-internal_server_app_favicon_ico_route_actions_103m2zz.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app__not-found_page_actions_0cb6n8e.js.map → 0_pe__next-internal_server_app_settings_networks_page_actions_0kglov-.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_admin_page_actions_0whq~om.js.map → dashboard-pr4-338__next-internal_server_app__global-error_page_actions_02gx6o5.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_login_page_actions_0lfb5vh.js.map → dashboard-pr4-338__next-internal_server_app__not-found_page_actions_12fmww3.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_logs_page_actions_0a9g2gh.js.map → dashboard-pr4-338__next-internal_server_app_admin_page_actions_01fk2aw.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_messages_page_actions_0_0a~oj.js.map → dashboard-pr4-338__next-internal_server_app_login_page_actions_05yexf9.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_node_page_actions_0nt83da.js.map → dashboard-pr4-338__next-internal_server_app_logs_page_actions_0-p3l45.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_nodes_page_actions_001898j.js.map → dashboard-pr4-338__next-internal_server_app_messages_page_actions_0phjj48.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_page_actions_10t6_cb.js.map → dashboard-pr4-338__next-internal_server_app_node_page_actions_0a5k6xf.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_server-logs_page_actions_10i2y0v.js.map → dashboard-pr4-338__next-internal_server_app_nodes_page_actions_0fstwl_.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_servers_page_actions_0yfsnrz.js.map → dashboard-pr4-338__next-internal_server_app_page_actions_0yw2h8..js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_settings_networks_page_actions_0.6z6e9.js.map → dashboard-pr4-338__next-internal_server_app_server-logs_page_actions_0fu1.t-.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_settings_page_actions_0t_etc1.js.map → dashboard-pr4-338__next-internal_server_app_servers_page_actions_02z5_.n.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_settings_tokens_page_actions_0g~ilh~.js.map → dashboard-pr4-338__next-internal_server_app_settings_page_actions_0fi5o6~.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_tasks_[id]_page_actions_0jc~43n.js.map → dashboard-pr4-338__next-internal_server_app_settings_tokens_page_actions_0crncl9.js.map} +0 -0
- /package/.next/server/chunks/ssr/{dash-main-prep__next-internal_server_app_tasks_page_actions_0k0da_-.js.map → dashboard-pr4-338__next-internal_server_app_tasks_[id]_page_actions_11owcxr.js.map} +0 -0
- /package/.next/static/{aIXvzZuknEQ-phwB09c_j → S9Q_U9UD2ZJQPBZjZgrZy}/_buildManifest.js +0 -0
- /package/.next/static/{aIXvzZuknEQ-phwB09c_j → S9Q_U9UD2ZJQPBZjZgrZy}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{aIXvzZuknEQ-phwB09c_j → S9Q_U9UD2ZJQPBZjZgrZy}/_ssgManifest.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../dashboard-pr4-338/node_modules/styled-jsx/dist/index/index.js","../../../../../dashboard-pr4-338/node_modules/styled-jsx/style.js","../../../../../dashboard-pr4-338/app/page.tsx","../../../../../dashboard-pr4-338/app/components/StatsBar.tsx","../../../../../dashboard-pr4-338/app/components/TopoGraph.tsx","../../../../../dashboard-pr4-338/app/components/ChatPopover.tsx","../../../../../dashboard-pr4-338/app/lib/vendorIdentity.ts","../../../../../dashboard-pr4-338/app/components/AgentCard.tsx","../../../../../dashboard-pr4-338/app/components/NodeSettingsPanel.tsx","../../../../../dashboard-pr4-338/app/components/LoadingSkeleton.tsx","../../../../../dashboard-pr4-338/app/components/CommandCenter.tsx"],"sourcesContent":["require('client-only');\nvar React = require('react');\n\nfunction _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }\n\nvar React__default = /*#__PURE__*/_interopDefaultLegacy(React);\n\n/*\nBased on Glamor's sheet\nhttps://github.com/threepointone/glamor/blob/667b480d31b3721a905021b26e1290ce92ca2879/src/sheet.js\n*/ function _defineProperties(target, props) {\n for(var i = 0; i < props.length; i++){\n var descriptor = props[i];\n descriptor.enumerable = descriptor.enumerable || false;\n descriptor.configurable = true;\n if (\"value\" in descriptor) descriptor.writable = true;\n Object.defineProperty(target, descriptor.key, descriptor);\n }\n}\nfunction _createClass(Constructor, protoProps, staticProps) {\n if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n if (staticProps) _defineProperties(Constructor, staticProps);\n return Constructor;\n}\nvar isProd = typeof process !== \"undefined\" && process.env && process.env.NODE_ENV === \"production\";\nvar isString = function(o) {\n return Object.prototype.toString.call(o) === \"[object String]\";\n};\nvar StyleSheet = /*#__PURE__*/ function() {\n function StyleSheet(param) {\n var ref = param === void 0 ? {} : param, _name = ref.name, name = _name === void 0 ? \"stylesheet\" : _name, _optimizeForSpeed = ref.optimizeForSpeed, optimizeForSpeed = _optimizeForSpeed === void 0 ? isProd : _optimizeForSpeed;\n invariant$1(isString(name), \"`name` must be a string\");\n this._name = name;\n this._deletedRulePlaceholder = \"#\" + name + \"-deleted-rule____{}\";\n invariant$1(typeof optimizeForSpeed === \"boolean\", \"`optimizeForSpeed` must be a boolean\");\n this._optimizeForSpeed = optimizeForSpeed;\n this._serverSheet = undefined;\n this._tags = [];\n this._injected = false;\n this._rulesCount = 0;\n var node = typeof window !== \"undefined\" && document.querySelector('meta[property=\"csp-nonce\"]');\n this._nonce = node ? node.getAttribute(\"content\") : null;\n }\n var _proto = StyleSheet.prototype;\n _proto.setOptimizeForSpeed = function setOptimizeForSpeed(bool) {\n invariant$1(typeof bool === \"boolean\", \"`setOptimizeForSpeed` accepts a boolean\");\n invariant$1(this._rulesCount === 0, \"optimizeForSpeed cannot be when rules have already been inserted\");\n this.flush();\n this._optimizeForSpeed = bool;\n this.inject();\n };\n _proto.isOptimizeForSpeed = function isOptimizeForSpeed() {\n return this._optimizeForSpeed;\n };\n _proto.inject = function inject() {\n var _this = this;\n invariant$1(!this._injected, \"sheet already injected\");\n this._injected = true;\n if (typeof window !== \"undefined\" && this._optimizeForSpeed) {\n this._tags[0] = this.makeStyleTag(this._name);\n this._optimizeForSpeed = \"insertRule\" in this.getSheet();\n if (!this._optimizeForSpeed) {\n if (!isProd) {\n console.warn(\"StyleSheet: optimizeForSpeed mode not supported falling back to standard mode.\");\n }\n this.flush();\n this._injected = true;\n }\n return;\n }\n this._serverSheet = {\n cssRules: [],\n insertRule: function(rule, index) {\n if (typeof index === \"number\") {\n _this._serverSheet.cssRules[index] = {\n cssText: rule\n };\n } else {\n _this._serverSheet.cssRules.push({\n cssText: rule\n });\n }\n return index;\n },\n deleteRule: function(index) {\n _this._serverSheet.cssRules[index] = null;\n }\n };\n };\n _proto.getSheetForTag = function getSheetForTag(tag) {\n if (tag.sheet) {\n return tag.sheet;\n }\n // this weirdness brought to you by firefox\n for(var i = 0; i < document.styleSheets.length; i++){\n if (document.styleSheets[i].ownerNode === tag) {\n return document.styleSheets[i];\n }\n }\n };\n _proto.getSheet = function getSheet() {\n return this.getSheetForTag(this._tags[this._tags.length - 1]);\n };\n _proto.insertRule = function insertRule(rule, index) {\n invariant$1(isString(rule), \"`insertRule` accepts only strings\");\n if (typeof window === \"undefined\") {\n if (typeof index !== \"number\") {\n index = this._serverSheet.cssRules.length;\n }\n this._serverSheet.insertRule(rule, index);\n return this._rulesCount++;\n }\n if (this._optimizeForSpeed) {\n var sheet = this.getSheet();\n if (typeof index !== \"number\") {\n index = sheet.cssRules.length;\n }\n // this weirdness for perf, and chrome's weird bug\n // https://stackoverflow.com/questions/20007992/chrome-suddenly-stopped-accepting-insertrule\n try {\n sheet.insertRule(rule, index);\n } catch (error) {\n if (!isProd) {\n console.warn(\"StyleSheet: illegal rule: \\n\\n\" + rule + \"\\n\\nSee https://stackoverflow.com/q/20007992 for more info\");\n }\n return -1;\n }\n } else {\n var insertionPoint = this._tags[index];\n this._tags.push(this.makeStyleTag(this._name, rule, insertionPoint));\n }\n return this._rulesCount++;\n };\n _proto.replaceRule = function replaceRule(index, rule) {\n if (this._optimizeForSpeed || typeof window === \"undefined\") {\n var sheet = typeof window !== \"undefined\" ? this.getSheet() : this._serverSheet;\n if (!rule.trim()) {\n rule = this._deletedRulePlaceholder;\n }\n if (!sheet.cssRules[index]) {\n // @TBD Should we throw an error?\n return index;\n }\n sheet.deleteRule(index);\n try {\n sheet.insertRule(rule, index);\n } catch (error) {\n if (!isProd) {\n console.warn(\"StyleSheet: illegal rule: \\n\\n\" + rule + \"\\n\\nSee https://stackoverflow.com/q/20007992 for more info\");\n }\n // In order to preserve the indices we insert a deleteRulePlaceholder\n sheet.insertRule(this._deletedRulePlaceholder, index);\n }\n } else {\n var tag = this._tags[index];\n invariant$1(tag, \"old rule at index `\" + index + \"` not found\");\n tag.textContent = rule;\n }\n return index;\n };\n _proto.deleteRule = function deleteRule(index) {\n if (typeof window === \"undefined\") {\n this._serverSheet.deleteRule(index);\n return;\n }\n if (this._optimizeForSpeed) {\n this.replaceRule(index, \"\");\n } else {\n var tag = this._tags[index];\n invariant$1(tag, \"rule at index `\" + index + \"` not found\");\n tag.parentNode.removeChild(tag);\n this._tags[index] = null;\n }\n };\n _proto.flush = function flush() {\n this._injected = false;\n this._rulesCount = 0;\n if (typeof window !== \"undefined\") {\n this._tags.forEach(function(tag) {\n return tag && tag.parentNode.removeChild(tag);\n });\n this._tags = [];\n } else {\n // simpler on server\n this._serverSheet.cssRules = [];\n }\n };\n _proto.cssRules = function cssRules() {\n var _this = this;\n if (typeof window === \"undefined\") {\n return this._serverSheet.cssRules;\n }\n return this._tags.reduce(function(rules, tag) {\n if (tag) {\n rules = rules.concat(Array.prototype.map.call(_this.getSheetForTag(tag).cssRules, function(rule) {\n return rule.cssText === _this._deletedRulePlaceholder ? null : rule;\n }));\n } else {\n rules.push(null);\n }\n return rules;\n }, []);\n };\n _proto.makeStyleTag = function makeStyleTag(name, cssString, relativeToTag) {\n if (cssString) {\n invariant$1(isString(cssString), \"makeStyleTag accepts only strings as second parameter\");\n }\n var tag = document.createElement(\"style\");\n if (this._nonce) tag.setAttribute(\"nonce\", this._nonce);\n tag.type = \"text/css\";\n tag.setAttribute(\"data-\" + name, \"\");\n if (cssString) {\n tag.appendChild(document.createTextNode(cssString));\n }\n var head = document.head || document.getElementsByTagName(\"head\")[0];\n if (relativeToTag) {\n head.insertBefore(tag, relativeToTag);\n } else {\n head.appendChild(tag);\n }\n return tag;\n };\n _createClass(StyleSheet, [\n {\n key: \"length\",\n get: function get() {\n return this._rulesCount;\n }\n }\n ]);\n return StyleSheet;\n}();\nfunction invariant$1(condition, message) {\n if (!condition) {\n throw new Error(\"StyleSheet: \" + message + \".\");\n }\n}\n\nfunction hash(str) {\n var _$hash = 5381, i = str.length;\n while(i){\n _$hash = _$hash * 33 ^ str.charCodeAt(--i);\n }\n /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed\n * integers. Since we want the results to be always positive, convert the\n * signed int to an unsigned by doing an unsigned bitshift. */ return _$hash >>> 0;\n}\nvar stringHash = hash;\n\nvar sanitize = function(rule) {\n return rule.replace(/\\/style/gi, \"\\\\/style\");\n};\nvar cache = {};\n/**\n * computeId\n *\n * Compute and memoize a jsx id from a basedId and optionally props.\n */ function computeId(baseId, props) {\n if (!props) {\n return \"jsx-\" + baseId;\n }\n var propsToString = String(props);\n var key = baseId + propsToString;\n if (!cache[key]) {\n cache[key] = \"jsx-\" + stringHash(baseId + \"-\" + propsToString);\n }\n return cache[key];\n}\n/**\n * computeSelector\n *\n * Compute and memoize dynamic selectors.\n */ function computeSelector(id, css) {\n var selectoPlaceholderRegexp = /__jsx-style-dynamic-selector/g;\n // Sanitize SSR-ed CSS.\n // Client side code doesn't need to be sanitized since we use\n // document.createTextNode (dev) and the CSSOM api sheet.insertRule (prod).\n if (typeof window === \"undefined\") {\n css = sanitize(css);\n }\n var idcss = id + css;\n if (!cache[idcss]) {\n cache[idcss] = css.replace(selectoPlaceholderRegexp, id);\n }\n return cache[idcss];\n}\n\nfunction mapRulesToStyle(cssRules, options) {\n if (options === void 0) options = {};\n return cssRules.map(function(args) {\n var id = args[0];\n var css = args[1];\n return /*#__PURE__*/ React__default[\"default\"].createElement(\"style\", {\n id: \"__\" + id,\n // Avoid warnings upon render with a key\n key: \"__\" + id,\n nonce: options.nonce ? options.nonce : undefined,\n dangerouslySetInnerHTML: {\n __html: css\n }\n });\n });\n}\nvar StyleSheetRegistry = /*#__PURE__*/ function() {\n function StyleSheetRegistry(param) {\n var ref = param === void 0 ? {} : param, _styleSheet = ref.styleSheet, styleSheet = _styleSheet === void 0 ? null : _styleSheet, _optimizeForSpeed = ref.optimizeForSpeed, optimizeForSpeed = _optimizeForSpeed === void 0 ? false : _optimizeForSpeed;\n this._sheet = styleSheet || new StyleSheet({\n name: \"styled-jsx\",\n optimizeForSpeed: optimizeForSpeed\n });\n this._sheet.inject();\n if (styleSheet && typeof optimizeForSpeed === \"boolean\") {\n this._sheet.setOptimizeForSpeed(optimizeForSpeed);\n this._optimizeForSpeed = this._sheet.isOptimizeForSpeed();\n }\n this._fromServer = undefined;\n this._indices = {};\n this._instancesCounts = {};\n }\n var _proto = StyleSheetRegistry.prototype;\n _proto.add = function add(props) {\n var _this = this;\n if (undefined === this._optimizeForSpeed) {\n this._optimizeForSpeed = Array.isArray(props.children);\n this._sheet.setOptimizeForSpeed(this._optimizeForSpeed);\n this._optimizeForSpeed = this._sheet.isOptimizeForSpeed();\n }\n if (typeof window !== \"undefined\" && !this._fromServer) {\n this._fromServer = this.selectFromServer();\n this._instancesCounts = Object.keys(this._fromServer).reduce(function(acc, tagName) {\n acc[tagName] = 0;\n return acc;\n }, {});\n }\n var ref = this.getIdAndRules(props), styleId = ref.styleId, rules = ref.rules;\n // Deduping: just increase the instances count.\n if (styleId in this._instancesCounts) {\n this._instancesCounts[styleId] += 1;\n return;\n }\n var indices = rules.map(function(rule) {\n return _this._sheet.insertRule(rule);\n })// Filter out invalid rules\n .filter(function(index) {\n return index !== -1;\n });\n this._indices[styleId] = indices;\n this._instancesCounts[styleId] = 1;\n };\n _proto.remove = function remove(props) {\n var _this = this;\n var styleId = this.getIdAndRules(props).styleId;\n invariant(styleId in this._instancesCounts, \"styleId: `\" + styleId + \"` not found\");\n this._instancesCounts[styleId] -= 1;\n if (this._instancesCounts[styleId] < 1) {\n var tagFromServer = this._fromServer && this._fromServer[styleId];\n if (tagFromServer) {\n tagFromServer.parentNode.removeChild(tagFromServer);\n delete this._fromServer[styleId];\n } else {\n this._indices[styleId].forEach(function(index) {\n return _this._sheet.deleteRule(index);\n });\n delete this._indices[styleId];\n }\n delete this._instancesCounts[styleId];\n }\n };\n _proto.update = function update(props, nextProps) {\n this.add(nextProps);\n this.remove(props);\n };\n _proto.flush = function flush() {\n this._sheet.flush();\n this._sheet.inject();\n this._fromServer = undefined;\n this._indices = {};\n this._instancesCounts = {};\n };\n _proto.cssRules = function cssRules() {\n var _this = this;\n var fromServer = this._fromServer ? Object.keys(this._fromServer).map(function(styleId) {\n return [\n styleId,\n _this._fromServer[styleId]\n ];\n }) : [];\n var cssRules = this._sheet.cssRules();\n return fromServer.concat(Object.keys(this._indices).map(function(styleId) {\n return [\n styleId,\n _this._indices[styleId].map(function(index) {\n return cssRules[index].cssText;\n }).join(_this._optimizeForSpeed ? \"\" : \"\\n\")\n ];\n })// filter out empty rules\n .filter(function(rule) {\n return Boolean(rule[1]);\n }));\n };\n _proto.styles = function styles(options) {\n return mapRulesToStyle(this.cssRules(), options);\n };\n _proto.getIdAndRules = function getIdAndRules(props) {\n var css = props.children, dynamic = props.dynamic, id = props.id;\n if (dynamic) {\n var styleId = computeId(id, dynamic);\n return {\n styleId: styleId,\n rules: Array.isArray(css) ? css.map(function(rule) {\n return computeSelector(styleId, rule);\n }) : [\n computeSelector(styleId, css)\n ]\n };\n }\n return {\n styleId: computeId(id),\n rules: Array.isArray(css) ? css : [\n css\n ]\n };\n };\n /**\n * selectFromServer\n *\n * Collects style tags from the document with id __jsx-XXX\n */ _proto.selectFromServer = function selectFromServer() {\n var elements = Array.prototype.slice.call(document.querySelectorAll('[id^=\"__jsx-\"]'));\n return elements.reduce(function(acc, element) {\n var id = element.id.slice(2);\n acc[id] = element;\n return acc;\n }, {});\n };\n return StyleSheetRegistry;\n}();\nfunction invariant(condition, message) {\n if (!condition) {\n throw new Error(\"StyleSheetRegistry: \" + message + \".\");\n }\n}\nvar StyleSheetContext = /*#__PURE__*/ React.createContext(null);\nStyleSheetContext.displayName = \"StyleSheetContext\";\nfunction createStyleRegistry() {\n return new StyleSheetRegistry();\n}\nfunction StyleRegistry(param) {\n var configuredRegistry = param.registry, children = param.children;\n var rootRegistry = React.useContext(StyleSheetContext);\n var ref = React.useState(function() {\n return rootRegistry || configuredRegistry || createStyleRegistry();\n }), registry = ref[0];\n return /*#__PURE__*/ React__default[\"default\"].createElement(StyleSheetContext.Provider, {\n value: registry\n }, children);\n}\nfunction useStyleRegistry() {\n return React.useContext(StyleSheetContext);\n}\n\n// Opt-into the new `useInsertionEffect` API in React 18, fallback to `useLayoutEffect`.\n// https://github.com/reactwg/react-18/discussions/110\nvar useInsertionEffect = React__default[\"default\"].useInsertionEffect || React__default[\"default\"].useLayoutEffect;\nvar defaultRegistry = typeof window !== \"undefined\" ? createStyleRegistry() : undefined;\nfunction JSXStyle(props) {\n var registry = defaultRegistry ? defaultRegistry : useStyleRegistry();\n // If `registry` does not exist, we do nothing here.\n if (!registry) {\n return null;\n }\n if (typeof window === \"undefined\") {\n registry.add(props);\n return null;\n }\n useInsertionEffect(function() {\n registry.add(props);\n return function() {\n registry.remove(props);\n };\n // props.children can be string[], will be striped since id is identical\n }, [\n props.id,\n String(props.dynamic)\n ]);\n return null;\n}\nJSXStyle.dynamic = function(info) {\n return info.map(function(tagInfo) {\n var baseId = tagInfo[0];\n var props = tagInfo[1];\n return computeId(baseId, props);\n }).join(\" \");\n};\n\nexports.StyleRegistry = StyleRegistry;\nexports.createStyleRegistry = createStyleRegistry;\nexports.style = JSXStyle;\nexports.useStyleRegistry = useStyleRegistry;\n","module.exports = require('./dist/index').style\n","'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { StatsBar } from './components/StatsBar';\nimport { TopoGraph } from './components/TopoGraph';\nimport { AgentCard } from './components/AgentCard';\nimport { LoadingSkeleton } from './components/LoadingSkeleton';\nimport { NodesEmptyState as EmptyState } from './components/EmptyState';\nimport { CommandCenter, useCommandCenter } from './components/CommandCenter';\nimport { useSessions, useHealth, useTasks, useStats } from './lib/hooks';\nimport { useSSE } from './lib/useSSE';\nimport { useSWRConfig } from 'swr';\n\nexport default function Dashboard() {\n // #214 F1: the old \"V3 auto-upgrade\" guard here checked per-tab\n // sessionStorage and, when empty (every new tab / browser restart /\n // bookmark visit), logged out the *valid* cookie session and bounced\n // to /login. Unauthenticated access is already handled server-side\n // by proxy.ts (no cookie → /login); the sessionStorage blob is just\n // a client cache that pages re-hydrate on demand. A genuinely stale\n // pre-V3 cookie now surfaces as API 401s with the error banner\n // instead of silent session destruction.\n\n const { sessions, hint: sessHint, error: sessError, isLoading } = useSessions();\n const { health } = useHealth();\n const { tasks } = useTasks({ limit: '500' });\n const { stats } = useStats();\n const [showTopo, setShowTopo] = useState(typeof window !== 'undefined' && window.innerWidth >= 1024);\n const cmd = useCommandCenter();\n const [agentFilter, setAgentFilter] = useState<'all' | 'working' | 'idle' | 'offline'>('all');\n // #84: last node.renamed event — passed to TopoGraph so an open chat\n // popover follows the rename instead of pointing at a dead alias. `ts`\n // makes the effect re-fire even when the same from/to repeats.\n const [renameSignal, setRenameSignal] = useState<{ from: string; to: string; ts: number } | null>(null);\n const { mutate } = useSWRConfig();\n\n // SSE: instant revalidation on CommHub events\n // Opt-out via NEXT_PUBLIC_DISABLE_SSE=1 — avoids HTTP/1.1 head-of-line blocking on navigation\n const sseEnabled = process.env.NEXT_PUBLIC_DISABLE_SSE !== '1';\n const { connected: sseConnected, supported: sseSupported } = useSSE({\n url: '/api/hub/events',\n enabled: sseEnabled,\n onEvent: (event) => {\n // #84 (RFC-010 §3.4): node.renamed — revalidate the session list so the\n // new alias propagates instantly (TopoGraph + node grid re-render, the\n // avatar hue recomputes as a pure fn of alias) instead of waiting for\n // the next 5s poll. The renamed node's history keeps the old alias\n // server-side, so the task list needs no revalidation here.\n if (event.type === 'node.renamed') {\n mutate('/api/hub/status');\n const d = event.data as { old_alias?: string; new_alias?: string } | undefined;\n if (d?.old_alias && d?.new_alias) {\n setRenameSignal({ from: d.old_alias, to: d.new_alias, ts: Date.now() });\n }\n return;\n }\n if (['new_task', 'new_message', 'new_reply', 'node_status_changed', 'broadcast'].includes(event.type)) {\n mutate('/api/hub/status');\n mutate((key: string) => typeof key === 'string' && key.startsWith('/api/hub/tasks'), undefined, { revalidate: true });\n }\n },\n });\n\n if (isLoading) return <LoadingSkeleton />;\n\n const sseSessions = health?.sse_sessions || {};\n // SSE keys are `network_id:alias` since v0.7+ (per-network scoping).\n // Fall back to alias-only for legacy hubs.\n const sseLookup = (s: { alias: string; network_id?: string }) =>\n (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n // #214 F2: online = SSE-reachable (\"can I talk to it right now\").\n // The old definition (status !== 'offline' OR sse) inflated the count\n // with stale hub statuses and disagreed with /admin and /nodes, which\n // both count pure SSE — and a \"online\" card you can't chat with\n // contradicts the M2 tap-to-chat model. One definition everywhere.\n const isOnline = (s: { alias: string; status: string; network_id?: string }) => !!sseLookup(s);\n const online = sessions.filter(isOnline).length;\n const total = sessions.length;\n const working = sessions.filter(s => s.status === 'working').length;\n // Task stats: prefer /api/stats, fallback to manual\n const taskStats: Record<string, number> = {};\n if (stats?.tasks?.by_status?.length) {\n for (const s of stats.tasks.by_status) {\n taskStats[s.status] = s.count;\n }\n } else {\n for (const t of tasks) {\n taskStats[t.status] = (taskStats[t.status] || 0) + 1;\n }\n }\n\n const sortedSessions = [...sessions].sort((a, b) => {\n const aOnline = isOnline(a) ? 1 : 0;\n const bOnline = isOnline(b) ? 1 : 0;\n if (aOnline !== bOnline) return bOnline - aOnline;\n const aWorking = a.status === 'working' ? 1 : 0;\n const bWorking = b.status === 'working' ? 1 : 0;\n return bWorking - aWorking;\n });\n\n return (\n <div className=\"min-h-screen bg-[#0b0b0d] text-gray-100 p-4 sm:p-6\">\n <StatsBar online={online} working={working} total={total} />\n\n {/* #217 S5 (Vincent: \"极简极简,这些都可以放到设置里面去\"): the\n Dispatch button and UserBar row are gone from Overview.\n Dispatch lives in /admin (Send Task panel); sign-out lives in\n Settings and the sidebar; network switching lives in the\n sidebar. */}\n\n {/* Task summary. #217 S5 (Vincent: \"乱七八糟的东西太多\"): the\n 9-status colored chip wall is now a single quiet line — the\n only two numbers that drive action are running and failed;\n everything else lives on /tasks. */}\n {Object.keys(taskStats).length > 0 && (\n <section className=\"mb-4 sm:mb-6 flex items-center justify-between rounded-lg border border-[#26262b] bg-[#161618] px-4 py-2.5 text-xs\">\n <div className=\"text-gray-400 tabular-nums\">\n <span className={taskStats['running'] ? 'text-green-400' : 'text-gray-500'}>{taskStats['running'] || 0} running</span>\n <span className=\"text-gray-600\"> · </span>\n <span className={taskStats['failed'] ? 'text-red-400' : 'text-gray-500'}>{taskStats['failed'] || 0} failed</span>\n </div>\n <Link href=\"/tasks\" prefetch={false} className=\"text-cyan-400 hover:text-cyan-300\">View all →</Link>\n </section>\n )}\n\n {/* #217 S5 (less is more): Quick Navigation stat cards, the nav\n rail, and Recent Activity are deleted. Every route they linked\n to is one tap away in the bottom tab bar (mobile) or sidebar\n (desktop), the headline numbers already live in the KPI cards\n and the task summary line, and recent tasks live on /tasks.\n Restore path: git revert this commit. */}\n\n {sessError && (\n <div className=\"bg-red-900/20 border border-red-800/40 text-red-300 px-4 py-3 rounded-lg mb-6 text-sm flex items-center justify-between\" role=\"alert\">\n <span>{String(sessError)}</span>\n <span className=\"text-gray-500 text-xs\">Check CommHub connection</span>\n </div>\n )}\n\n {/* #217 M1: topology toggle is lg-only — on a 390px phone the\n graph needed a 70vh cap (R41) just to stay scrollable, i.e.\n it never really worked there. Desktop keeps the full feature;\n mobile Overview loses its last piece of chrome. */}\n {sessions.length > 0 && (\n <div className=\"hidden lg:flex justify-center mb-4\">\n <button\n onClick={() => setShowTopo(!showTopo)}\n className=\"text-xs text-gray-500 hover:text-gray-300 border border-gray-700/50 px-4 py-1.5 rounded-lg transition-colors hover:border-gray-600 cursor-pointer\"\n >\n {showTopo ? 'Hide Topology' : 'Show Topology'}\n </button>\n </div>\n )}\n\n {showTopo && sessions.length > 0 && (\n // #209 R41: mobile-only soft cap on the rendered TopoGraph.\n // Default behaviour on desktop is unchanged (lg: drops the cap\n // entirely). On phones the SVG card was free to claim ~600+ px\n // of vertical real-estate, which on a 667-844 px viewport meant\n // tapping \"Show Topology\" pushed the agent grid completely off\n // screen. Capping to 70 vh with overflow-y-auto keeps the graph\n // interactive (the SVG already does its own pan + zoom inside\n // the viewport) while letting the page below stay reachable\n // with a single ordinary scroll past the cap.\n <div className=\"lg:max-h-none lg:overflow-visible max-h-[70vh] overflow-y-auto rounded-xl border border-transparent lg:border-0\">\n <TopoGraph sessions={sessions} sseSessions={sseSessions} renameSignal={renameSignal} />\n </div>\n )}\n\n {sessions.length === 0 && !sessError ? (\n <EmptyState\n hint={sessHint}\n taskHistoryCount={Object.values(taskStats).reduce((a, b) => a + b, 0)}\n />\n ) : (() => {\n const counts = {\n all: sortedSessions.length,\n working: sortedSessions.filter(s => isOnline(s) && s.status === 'working').length,\n idle: sortedSessions.filter(s => isOnline(s) && s.status !== 'working').length,\n offline: sortedSessions.filter(s => !isOnline(s)).length,\n };\n const filtered = sortedSessions.filter(s => {\n if (agentFilter === 'all') return true;\n if (agentFilter === 'offline') return !isOnline(s);\n if (agentFilter === 'working') return isOnline(s) && s.status === 'working';\n if (agentFilter === 'idle') return isOnline(s) && s.status !== 'working';\n return true;\n });\n // Filter chip color keyed to status (round 34): working=green, idle=cyan,\n // offline=gray, all=neutral. Inline hex dots avoid Tailwind v4 purge.\n const chips: { key: typeof agentFilter; label: string; dot?: string }[] = [\n { key: 'all', label: 'All' },\n { key: 'working', label: 'Working', dot: '#4ade80' },\n { key: 'idle', label: 'Idle', dot: '#22d3ee' },\n { key: 'offline', label: 'Offline', dot: '#6b7280' },\n ];\n return (\n <>\n <div className=\"mb-3 flex flex-wrap items-center gap-1\">\n {chips.map(c => (\n <button\n key={c.key}\n type=\"button\"\n onClick={() => setAgentFilter(c.key)}\n disabled={counts[c.key] === 0 && c.key !== 'all'}\n className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors disabled:opacity-30 disabled:cursor-not-allowed ${\n agentFilter === c.key\n ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'\n : 'text-gray-500 hover:text-gray-200 hover:bg-[#1c1c1f] border border-transparent'\n }`}\n >\n {c.dot && <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full\" style={{ backgroundColor: c.dot }} />}\n {/* #217 D5: counts dropped — the KPI cards directly above\n already carry 53/100/153; repeating them here doubled\n every number on the first screen. */}\n <span>{c.label}</span>\n </button>\n ))}\n </div>\n {/* Round 48: previous breakpoints had `lg:grid-cols-2 xl:grid-cols-3`\n which kept lg (1024-1279px) at only 2 columns even though\n each AgentCard is fine ≥260px wide. With the sidebar (208px),\n main area at lg is ~816px so 3 cols at ~272px each fits.\n xl breakpoint auto-inherits lg=3 cols; 2xl bumps to 4. */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4\">\n {filtered.map(s => (\n <AgentCard key={s.alias} session={s} hasSse={isOnline(s)} sseCount={sseLookup(s) || 0} onChat={cmd.openTab} />\n ))}\n </div>\n {filtered.length === 0 && (\n <div className=\"mt-4 text-center text-xs text-gray-600\">\n No agents match \"{agentFilter}\" — <button onClick={() => setAgentFilter('all')} className=\"underline hover:text-gray-400\">Show all</button>\n </div>\n )}\n </>\n );\n })()}\n\n {/* Round 111 (issue #82): dropped the license badge — \"trial (12d\n left)\" read like a paywall countdown on an open-source dashboard\n and Vincent flagged it as misleading more than once. The SSE /\n polling dot stays: it's a real connection-status indicator, not\n a sales surface. */}\n <div className=\"mt-8 text-center text-xs text-gray-600 flex items-center justify-center gap-2 flex-wrap\">\n {sseSupported && (\n <>\n <span className={`w-1.5 h-1.5 rounded-full ${sseConnected ? 'bg-green-400' : 'bg-gray-600'}`} />\n {sseConnected ? 'SSE live' : 'SWR polling'}\n </>\n )}\n </div>\n\n {/* Command Center (multi-tab chat) */}\n {cmd.tabs.length > 0 && (\n <CommandCenter\n tabs={cmd.tabs}\n activeTab={cmd.activeTab}\n onOpenTab={cmd.openTab}\n onCloseTab={cmd.closeTab}\n onSetActive={cmd.setActiveTab}\n onClose={cmd.closeAll}\n />\n )}\n </div>\n );\n}\n","'use client';\n\ninterface StatsBarProps {\n online: number;\n working: number;\n total: number;\n}\n\nexport function StatsBar({ online, working, total }: StatsBarProps) {\n const onlinePercent = total > 0 ? Math.round((online / total) * 100) : 0;\n const fleetEmpty = total === 0;\n\n // #209 R29 (mobile vertical rhythm — goal \"大幅提升移动端体验\"):\n // populated branch previously left mb-8 (32 px) below the 4-card grid,\n // the single biggest gap on the Overview page. Drop to mb-4 (16 px)\n // on phones, restore mb-8 from sm: up. Pairs with R28's section-gap\n // tighten — together they reclaim ~48 px of pure scroll waste before\n // the agent grid. The empty-fleet branch already used mb-4 since R72,\n // so this only touches the populated case.\n return (\n <div className={fleetEmpty ? 'mb-4' : 'mb-4 sm:mb-8'}>\n {/* Title row. #217 S3 dropped the version/uptime subtitle (lives in\n Settings → CommHub Connection). S9 (Vincent tg 613 \"没居中\"):\n WeChat-style centered title on phones — centering also clears\n the fixed hamburger symmetrically, replacing the old ml-10\n indent that read as misalignment. Left-aligned from lg: up. */}\n <div className=\"mb-4\">\n <h1 className=\"text-2xl font-bold text-white tracking-tight text-center lg:text-left\">Agent Network</h1>\n </div>\n\n {fleetEmpty ? (\n /* Round 72: thin status strip replaces the 4-card grid when fleet\n is empty. Saves ~280px on mobile (CTA y=650 → ~370) and keeps\n the same data visible in a single inline row. */\n <div className=\"anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#26262b] py-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> online\n </span>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> working\n </span>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> registered\n </span>\n </div>\n ) : (\n <>\n {/* #217 M1 (Vincent: \"再简洁点你自己理解一下\"): on phones the\n populated state reuses the round-72 thin strip instead of\n the card grid — one quiet line of numbers, ~70px reclaimed.\n Cards return from sm: up where they have room to breathe. */}\n <div className=\"sm:hidden anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#26262b] py-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-green-400\" />\n <span className=\"text-gray-200 tabular-nums\">{online}</span> online\n </span>\n {working > 0 && (\n <>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-green-400 anet-brand-pulse\" />\n <span className=\"text-gray-200 tabular-nums\">{working}</span> working\n </span>\n </>\n )}\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-200 tabular-nums\">{total - online}</span> offline\n </span>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"text-gray-200 tabular-nums\">{total}</span> total\n </span>\n </div>\n {/* Populated state, sm: up. #217 S2 (less is more): the Working\n card only earns its grid cell when something is actually\n working. */}\n <div className={`hidden sm:grid gap-3 ${working > 0 ? 'sm:grid-cols-4' : 'sm:grid-cols-3'}`}>\n <StatCard\n value={online}\n label=\"Online\"\n sub={`${onlinePercent}% of fleet`}\n color=\"text-green-400\"\n />\n {working > 0 && (\n <StatCard\n value={working}\n label=\"Working\"\n sub={online > 0 ? `${Math.round((working / online) * 100)}% utilization` : '--'}\n color=\"text-cyan-400\"\n />\n )}\n <StatCard\n value={total - online}\n label=\"Offline\"\n sub={total - online === 0 ? 'All systems go' : `${total - online} disconnected`}\n color=\"text-gray-400\"\n />\n <StatCard\n value={total}\n label=\"Total\"\n sub=\"Registered nodes\"\n color=\"text-white\"\n />\n </div>\n </>\n )}\n </div>\n );\n}\n\nfunction StatCard({ value, label, sub, color }: {\n value: number; label: string; sub: string; color: string;\n}) {\n // #217 D2 (OpenWebUI-style color restraint): the per-color gradient\n // wash + tinted borders made the KPI row read as four neon billboards.\n // Surfaces are now neutral (shared border + bg); color survives only\n // on the number itself. Mobile density values are #209 R39.\n return (\n <div className=\"anet-stat-card rounded-xl border border-[#26262b] bg-[#161618] px-3 sm:px-4 py-2.5 sm:py-3 transition-all\">\n <div className={`text-2xl sm:text-3xl font-bold ${color} tabular-nums leading-tight`}>{value}</div>\n <div className=\"text-xs sm:text-sm text-gray-300 mt-0.5\">{label}</div>\n <div className=\"text-[10px] sm:text-xs text-gray-600 mt-1\">{sub}</div>\n </div>\n );\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport useSWR from 'swr';\nimport { useRouter } from 'next/navigation';\nimport { Session } from './types';\nimport { aliasAvatarColors, aliasInitial } from './AliasAvatar';\nimport { ChatPopover } from './ChatPopover';\nimport { vendorForModel, runtimeIdentity, identityLine } from '../lib/vendorIdentity';\nimport { parseHubTime, relativeAgo } from '../lib/time';\nimport { DASHBOARD_VERSION } from '../lib/version';\nimport { useChatUnread } from '../lib/chat-unread';\n\n/** v0.10.0 Hero 1+2 / §3.F server-health hook — fetches the normalized\n * /api/hub/servers payload (preview.370 unblocked real-data via the\n * proxy schema-normalize layer) and exposes a per-hostname health\n * tier. red → worst-of-CPU/Mem/Disk ≥ 85%; amber → 60-85%; green\n * → < 60%; null when telemetry hasn't shipped yet OR the host is\n * offline.\n *\n * Shared with ServersDrawer via SWR's key-based dedup — both\n * consumers point at /api/hub/servers so the cache layer fans out\n * to a single fetch per refresh interval.\n */\ninterface ServerHealthRow {\n hostname: string;\n cpu_load_1min: number | null;\n cpu_cores: number;\n mem_used_gb: number | null;\n mem_total_gb: number | null;\n disk_used_gb: number | null;\n disk_total_gb: number | null;\n status: 'online' | 'offline';\n}\ntype ServerTier = 'red' | 'amber' | 'green';\nfunction classifyServer(s: ServerHealthRow): ServerTier | null {\n if (s.status === 'offline') return null;\n const cpuPct = s.cpu_load_1min != null && s.cpu_cores > 0 ? (s.cpu_load_1min / s.cpu_cores) * 100 : null;\n const memPct = s.mem_used_gb != null && s.mem_total_gb != null && s.mem_total_gb > 0 ? (s.mem_used_gb / s.mem_total_gb) * 100 : null;\n const diskPct = s.disk_used_gb != null && s.disk_total_gb != null && s.disk_total_gb > 0 ? (s.disk_used_gb / s.disk_total_gb) * 100 : null;\n const vals = [cpuPct, memPct, diskPct].filter((v): v is number => typeof v === 'number');\n if (vals.length === 0) return null;\n const worst = Math.max(...vals);\n if (worst >= 85) return 'red';\n if (worst >= 60) return 'amber';\n return 'green';\n}\nconst serversFetcher = async (url: string): Promise<{ servers: ServerHealthRow[] } | null> => {\n const res = await fetch(url);\n if (!res.ok) return null;\n return res.json();\n};\nfunction useServerHealthMap(): Map<string, ServerTier> {\n const { data } = useSWR<{ servers: ServerHealthRow[] } | null>(\n '/api/hub/servers',\n serversFetcher,\n { refreshInterval: 15000, dedupingInterval: 5000 },\n );\n return useMemo(() => {\n const m = new Map<string, ServerTier>();\n for (const s of data?.servers ?? []) {\n const tier = classifyServer(s);\n if (tier) m.set(s.hostname, tier);\n }\n return m;\n }, [data]);\n}\n\ninterface MessageFlow {\n from_alias: string;\n to_alias: string;\n content: string;\n created_at: string;\n}\n\ninterface TaskFlow {\n from_name?: string | null;\n from_alias?: string | null;\n to_name?: string | null;\n to_alias?: string | null;\n content?: string | null;\n status?: string | null;\n created_at?: string | null;\n delivered_at?: string | null;\n started_at?: string | null;\n completed_at?: string | null;\n}\n\ninterface TopoGraphProps {\n sessions: Session[];\n sseSessions: Record<string, number>;\n // #84: a node.renamed event from the Overview's SSE listener. When the\n // currently open chat popover targets `from`, it follows the rename to `to`.\n renameSignal?: { from: string; to: string; ts: number } | null;\n}\n\ninterface Point {\n x: number;\n y: number;\n}\n\n/** #170 tree-view MVP — payload for the org-chart layout. `lines` are the\n * parent→child elbow connectors (raw endpoints; the render branch draws\n * the right-angle path). `synthLabels` are placeholder chips for the\n * synthetic nodes (指挥中心 root / 未分组 bucket) that have no backing\n * session. Empty in ring/grid layouts. */\ninterface TreeConnectorLine {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n}\ninterface TreeLayout {\n lines: TreeConnectorLine[];\n synthLabels: { x: number; y: number; label: string }[];\n synthRoot: boolean;\n}\n\ninterface FlowLink {\n key: string;\n from: string;\n to: string;\n count: number;\n content: string;\n /** ISO timestamp of the most recent message on this edge — drives\n * the Round 10 freshness fade so dormant links recede visually. */\n last_at: string;\n}\n\nconst cx = 500;\nconst cy = 330;\nconst onlineRadius = 220;\n// Round 97 (issue #50): tier into two rings when N > 8; round 98\n// (issue #61): tier into three rings when N > 14. At N=22 (Vincent's\n// network) two rings still leave 11 nodes per ring → inner chord of\n// 88px can't fit a 100px label; three rings give ~⌈N/3⌉ per ring so\n// every tier has enough arc room.\nconst onlineTierThreshold = 8;\nconst onlineTripleThreshold = 14;\nconst onlineInnerRadius = 175;\nconst onlineOuterRadius = 260;\nconst onlineTripleInnerR = 145;\nconst onlineTripleMidR = 215;\nconst onlineTripleOuterR = 285;\nconst offlineRadius = 325;\n\n/** Polar coordinate for a node on a ring. `rotateBy` lets the caller offset\n * the whole ring so two stacked rings don't align radially (round 25: the\n * offline ring gets a half-step rotation so its nodes sit in the gaps\n * between online ones). */\nfunction polarPoint(index: number, total: number, radius: number, rotateBy = 0) {\n const spread = total <= 2 ? Math.PI : Math.PI * 1.78;\n const start = -Math.PI / 2 - spread / 2;\n const angle = total <= 1 ? -Math.PI / 2 : start + (spread * index) / (total - 1) + rotateBy;\n return {\n x: cx + radius * Math.cos(angle),\n y: cy + radius * Math.sin(angle),\n };\n}\n\nfunction curvePath(from: Point, to: Point, lift = 0) {\n const mx = (from.x + to.x) / 2;\n const my = (from.y + to.y) / 2;\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const length = Math.hypot(dx, dy) || 1;\n const normalX = -dy / length;\n const normalY = dx / length;\n const control = {\n x: mx + normalX * lift,\n y: my + normalY * lift,\n };\n\n return `M${from.x},${from.y} Q${control.x},${control.y} ${to.x},${to.y}`;\n}\n\nfunction truncate(value: string, max: number) {\n return value.length > max ? `${value.slice(0, max - 1)}...` : value;\n}\n\n/** Round 46 / Loop: SWR-freshness chip.\n *\n * TopoGraph's data refreshes every 5 s via SWR but the user has no\n * visible signal that the canvas they're looking at is current. The\n * flow particles and chips (R42 active-links, R43 hub) tell you when\n * the FLEET last did something, not when the DATA last syncing. This\n * chip ticks `live · Ns` against a 1-second interval so the operator\n * can trust freshness without doing internal math.\n *\n * Owns its own setInterval so the parent topology doesn't re-render\n * every second (only this small chip does). lastSyncRef is updated\n * whenever the `sessions` prop reference changes — that's the SWR\n * refresh signal. */\nfunction FreshnessChip({ sessions }: { sessions: unknown }) {\n const lastSyncRef = useRef<number>(Date.now());\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n // sessions reference changed → SWR just delivered a new payload.\n lastSyncRef.current = Date.now();\n }, [sessions]);\n useEffect(() => {\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, []);\n const sec = Math.max(0, Math.floor((now - lastSyncRef.current) / 1000));\n // Tint the chip warmer when the data goes stale (>10s since last sync).\n // SWR's default refreshInterval here is 5s, so anything past ~10s is\n // either a poll miss or a network hiccup.\n const stale = sec > 10;\n // Round 187 / Loop: chip transitions between fresh (gray) and stale\n // (amber) colour palettes smoothly. Pre-R187 the className swap\n // snapped every time the stale boundary was crossed — could happen\n // multiple times per minute on a flaky network. Adding\n // transition-colors makes the stale-onset (gray → amber) and\n // recovery (amber → gray) ease through the bg / text / border\n // palette together. 300ms matches R161/R162 active-links chip\n // freshness fade timing for visual consistency in the chip row.\n // Round 315 / Loop: FreshnessChip joins the R313-R314 chip-row\n // data-weight family. When it appears (stale state only — R275\n // gated rendering to stale), it sits next to working/online/\n // active-links chips that all carry font-medium (R313) plus the\n // vendor letter chips (R314). Without font-medium the warning\n // chip would render at default 400 next to data chips at 500\n // — visual inconsistency right at the moment the chip exists to\n // grab attention. font-medium adds it to the HTML-context data\n // tier (R312-R314 family); the amber bg/text/border still does\n // the warning-state work, the weight just keeps the chip in the\n // same data-typography ladder as its siblings.\n // Round 377 / Loop: FreshnessChip baseClass picks up `tabular-nums`.\n // The chip-row's last untouched chip joins the R224-R357 broader\n // tabular-nums sweep:\n // R224 edge badge digit\n // R225 hub digit / panel header counts / recent row count\n // R232 chip row counts (working / online / active-links)\n // R321 recent row timestamp\n // R322 recent panel hot count\n // R323 filter pin pill counts\n // R333 vendor count suffix\n // R357 active-links freshness suffix wrapper\n // R377 FreshnessChip body (this round)\n // `font-mono` already gives equal-width glyphs but `tabular-nums`\n // is the explicit-invariant the rest of the chip row carries.\n // FreshnessChip body reads `lag · {sec}s` — the {sec} digit grows\n // every second; tabular-nums explicitly locks digit width so the\n // chip stays planted as the seconds counter ticks past 9 → 10 →\n // 99 → 100. R187 transition-colors duration-300 + R275 stale-only\n // render gate + R315 font-medium R313 family alignment all\n // preserved.\n const baseClass = \"hidden sm:inline px-2.5 py-1 rounded-md font-mono font-medium tabular-nums border transition-colors duration-300\";\n const colorClass = stale\n ? \"bg-amber-500/10 text-amber-300 border-amber-500/20\"\n : \"bg-gray-500/10 text-gray-400 border-gray-500/20\";\n /* Round 275 / Loop: simplification per Vincent 5214/5215-5217 visual\n audit (clutter cleanup for Twitter screenshot). Pre-R275 the chip\n ALWAYS rendered — \"live · 5s\" gray-on-gray at rest, \"lag · 15s\"\n amber when stale. The fresh state is an \"everything's fine\"\n affirmation that's implicit elsewhere on the canvas (counts\n updating, flows animating). Adding a permanent chip to the chip-\n row's right end for that affirmation is added visual chrome\n without proportional info value.\n\n R275 converts the chip to a CONDITIONAL warning indicator: render\n only when stale (sec > 10). Fresh state → null (chip absent). The\n amber stale chip still appears as a warning when SWR lags, so\n users see the problem signal; the fresh state implicitly relies\n on other liveness signals (recent-signal panel rows, edge\n animations, count updates).\n\n Net effect: chip-row at rest has 1 fewer chip (cleaner Twitter\n screenshot, less right-edge chrome), but signals appear on\n stale-onset to direct attention. */\n if (!stale) return null;\n return (\n /* Round 505 / Loop — FreshnessChip mount animation. Pre-R505 the\n chip popped into the chip-row instantly when SWR data crossed\n the 10s stale threshold; users saw an abrupt amber pill appear\n mid-row. R505 adds the existing `anet-fade-in` class so the\n chip eases through opacity 0→1 over 150ms (R51 globals.css\n keyframe) on first appearance. The chip itself only renders\n when stale (R275 conditional), so the fade plays exactly when\n the stale signal first arrives — perfectly aligned with the\n semantic. Mount-once via React reconciliation (key not used\n since FreshnessChip is a singleton in the parent).\n a11y respected via R29 blanket — `@media (prefers-reduced-\n motion: reduce)` neutralizes anet-fade-in to `animation:none`\n (globals.css line 1083-1089 includes anet-fade-in in the\n blanket list). Reduced-motion users see the chip pop instantly,\n same as pre-R505 behavior — no regression.\n Pure paint-axis addition (opacity animation, no geometry),\n bbox unchanged. data-freshness-chip-mount-fade attr exposes\n the gate for tests. */\n <span\n className={`${baseClass} ${colorClass} anet-fade-in`}\n title={stale ? `Last sync ${sec}s ago — SWR refresh may be lagging` : `Live data · refreshes every 5s · last sync ${sec}s ago`}\n data-freshness-chip\n data-freshness-chip-stale={stale ? 'true' : 'false'}\n data-freshness-chip-mount-fade=\"true\"\n >\n {/* Round 272 / Loop: swap prefix word to match color state so\n text and color point the same way. Pre-R272 the chip read\n \"live · {sec}s\" in BOTH fresh (gray) and stale (amber)\n states — the amber color signals \"concerning\" but \"live\"\n still says \"fresh data flowing\", a visual contradiction.\n Post-R272: fresh=\"live · {sec}s\" (gray + reassuring), stale=\n \"lag · {sec}s\" (amber + signals lagging). Same monospace\n cell count (3 chars + \" · \" + digits + \"s\") so no chip\n width jitter on threshold crossing; R187 transition-colors\n duration-300 still eases the bg/color flip. Title (hover\n tooltip) still spells out the full meaning in either\n state. */}\n {/* Round 410 / Loop: FreshnessChip body picks up the chip-\n internal-hierarchy arc. Pre-R410 the body rendered as a\n single text node `lag · {sec}s` with the parent's font-\n medium (fw=500) applied uniformly. R410 splits the digit\n and unit into separate spans so the chip's internal\n typography mirrors the family pattern R333-R341/R362/R369/\n R389 established for the chip row:\n digit (fw=600) data tier\n unit (fw=500 + opacity=0.7) label tier\n The `lag` prefix stays at the chip's baseline (fw=500\n from parent font-medium) — it labels the state, not a\n data value. data-freshness-chip-digit / -unit attrs\n surface the spans for tests. tabular-nums + transition-\n colors + R275 stale-only gate all preserved. */}\n {stale ? 'lag' : 'live'} · <span className=\"font-semibold\" data-freshness-chip-digit>{sec}</span><span className=\"opacity-70\" data-freshness-chip-unit>s</span>\n </span>\n );\n}\n\n/** Round 36 / Loop: prefers-reduced-motion hook.\n *\n * Round 29's a11y sweep zeroed CSS animations via media query, but SVG\n * SMIL `<animate>` / `<animateMotion>` elements aren't reachable from CSS\n * — they need JS to opt out. This hook reads the media query and listens\n * for changes so the topology's flow particles, pulses, click ripple\n * and hub breath honour the OS-level preference. */\nfunction useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false);\n useEffect(() => {\n if (typeof window === 'undefined' || !window.matchMedia) return;\n const mq = window.matchMedia('(prefers-reduced-motion: reduce)');\n setReduced(mq.matches);\n const onChange = (e: MediaQueryListEvent) => setReduced(e.matches);\n mq.addEventListener('change', onChange);\n return () => mq.removeEventListener('change', onChange);\n }, []);\n return reduced;\n}\n\n// Round 38 / Loop: parseHubTime + relativeAgo factored to app/lib/time.ts\n// — the Round 35 fix lives there now alongside the Round 37 mirror so\n// any future TZ-safe parse update happens in one place.\n\n/** Round 12 / Loop: status trio audit.\n *\n * Each (status × theme) cell returns a {primary, halo, text} trio. The trio\n * invariant — keep it when adding states or tweaking shades:\n *\n * light: primary = <hue>-600 halo = <hue>-100 * text = <hue>-800/900\n * dark: primary = <hue>-400/500 halo = <hue>-900 text = <hue>-100\n *\n * * offline.halo light deviates intentionally to slate-200 (#e2e8f0):\n * slate-100 (#f1f5f9) is too close to the panel bg (#f8fafc) and the\n * halo would vanish. The other three rows keep the 100-shade.\n *\n * Audit caught one cross-family drift before this round: online-other halo\n * light was #dbeafe (blue-100) while its primary (#0284c7 sky-600) and text\n * (#0c4a6e sky-900) were sky-family — now sky-100 (#e0f2fe). Tiny visual\n * difference; large hygiene win — every trio is now mono-hue. */\nfunction nodeStatus(session: Session, isOnline: boolean, isLight: boolean) {\n if (!isOnline) {\n return {\n label: 'offline',\n primary: isLight ? '#94a3b8' : '#6b7280', // slate-400 / gray-500\n halo: isLight ? '#e2e8f0' : '#111827', // slate-200* / gray-900\n text: isLight ? '#475569' : '#9ca3af', // slate-600 / gray-400\n };\n }\n if (session.status === 'working') {\n return {\n label: 'working',\n primary: isLight ? '#059669' : '#22c55e', // emerald-600 / green-500\n halo: isLight ? '#d1fae5' : '#14532d', // emerald-100 / green-900\n text: isLight ? '#065f46' : '#dcfce7', // emerald-800 / green-100\n };\n }\n if (session.status === 'idle') {\n return {\n label: 'idle',\n primary: isLight ? '#0d9488' : '#2dd4bf', // teal-600 / teal-400\n halo: isLight ? '#ccfbf1' : '#134e4a', // teal-100 / teal-900\n text: isLight ? '#115e59' : '#ccfbf1', // teal-800 / teal-100\n };\n }\n return {\n label: session.status || 'online',\n primary: isLight ? '#0284c7' : '#38bdf8', // sky-600 / sky-400\n halo: isLight ? '#e0f2fe' : '#0c4a6e', // sky-100 / sky-900 (was blue-100 — drift fixed Round 12)\n text: isLight ? '#0c4a6e' : '#e0f2fe', // sky-900 / sky-100\n };\n}\n\n/** Theme-aware color palette for the topology SVG. */\ninterface Palette {\n panelStops: [string, string, string];\n radarStops: { color: string; opacity: number }[];\n arrowFill: string;\n ringStroke: string;\n spokeStroke: { active: string; idle: string };\n flowEdge: string;\n flowPath: string;\n flowParticle: string;\n nodeFill: { online: string; offline: string };\n labelBox: { fill: string; stroke: string };\n legendBox: { fill: string; stroke: string };\n legendText: string;\n legendHeadline: string;\n legendAccent: string;\n containerBg: string;\n containerBorder: string;\n topRailGradient: string;\n}\n\nconst DARK_PALETTE: Palette = {\n panelStops: ['#0b1220', '#0a0a0c', '#101018'],\n radarStops: [\n { color: '#22d3ee', opacity: 0.18 },\n { color: '#22c55e', opacity: 0.045 },\n { color: '#060608', opacity: 0 },\n ],\n arrowFill: '#67e8f9',\n ringStroke: '#164e63',\n spokeStroke: { active: '#22d3ee', idle: '#155e75' },\n flowEdge: '#67e8f9',\n flowPath: '#e0f2fe',\n flowParticle: '#fef08a',\n nodeFill: { online: '#060608', offline: '#0a0a0c' },\n labelBox: { fill: '#060608', stroke: '#1f2937' },\n legendBox: { fill: '#060608', stroke: '#1f2937' },\n legendText: '#94a3b8',\n legendHeadline: '#e5e7eb',\n legendAccent: '#67e8f9',\n containerBg: '#0a0a0c',\n containerBorder: '#26262b',\n topRailGradient: 'from-transparent via-cyan-400/70 to-transparent',\n};\n\nconst LIGHT_PALETTE: Palette = {\n panelStops: ['#f8fafc', '#ffffff', '#f1f5f9'],\n radarStops: [\n { color: '#10b981', opacity: 0.06 },\n { color: '#3b82f6', opacity: 0.03 },\n { color: '#ffffff', opacity: 0 },\n ],\n arrowFill: '#10b981',\n ringStroke: '#cbd5e1',\n spokeStroke: { active: '#10b981', idle: '#cbd5e1' },\n flowEdge: '#10b981',\n flowPath: '#475569',\n flowParticle: '#f59e0b',\n nodeFill: { online: '#ffffff', offline: '#f8fafc' },\n labelBox: { fill: '#ffffff', stroke: '#e2e8f0' },\n legendBox: { fill: '#ffffff', stroke: '#e2e8f0' },\n legendText: '#475569',\n legendHeadline: '#18181b',\n legendAccent: '#0d9488',\n containerBg: '#ffffff',\n containerBorder: '#e3e6eb',\n topRailGradient: 'from-transparent via-emerald-500/40 to-transparent',\n};\n\nfunction useTheme(): 'light' | 'dark' {\n const [theme, setTheme] = useState<'light' | 'dark'>('dark');\n useEffect(() => {\n const read = () => {\n const t = document.documentElement.getAttribute('data-theme') || 'cyber';\n setTheme(t === 'light' || t === 'mint' ? 'light' : 'dark');\n };\n read();\n const obs = new MutationObserver(read);\n obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });\n return () => obs.disconnect();\n }, []);\n return theme;\n}\n\n/** Round 100 (issue #79): brand showcase mode. Activate via `?brand=intern`\n * on any dashboard page — TopoGraph nodes show the 书小生 mascot instead\n * of alias initials. Stored in localStorage so the flag persists across\n * navigation. Clears with `?brand=none` or `?brand=` (empty). */\nfunction useBrand(): string | null {\n const [brand, setBrand] = useState<string | null>(null);\n useEffect(() => {\n try {\n const url = new URL(window.location.href);\n const param = url.searchParams.get('brand');\n if (param !== null) {\n if (param === '' || param === 'none') {\n localStorage.removeItem('anet-brand');\n setBrand(null);\n } else {\n localStorage.setItem('anet-brand', param);\n setBrand(param);\n }\n } else {\n setBrand(localStorage.getItem('anet-brand'));\n }\n } catch {}\n }, []);\n return brand;\n}\n\n/** Round 106 (issue #83): cluster agents by shared alias prefix so a team\n * reads as one unit in the topology. Adjacent (sorted) aliases that share\n * a ≥2-char prefix join the same group; the group key is the prefix common\n * to every member. Singletons map to their own alias (no hue shift). The\n * group key feeds aliasAvatarColors() so e.g. all 通信* nodes get one hue,\n * all 研究员* another — the \"同色相 tint\" clustering option from #83. */\nfunction commonPrefix(a: string, b: string): string {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) i++;\n return a.slice(0, i);\n}\n\n/** #83 + #111: group nodes by shared ≥2-char alias prefix. Union-find over\n * the sessions; the component label is the common alias prefix. Returns\n * alias → groupKey.\n *\n * #172/#174 short-term fix: grouping is alias-prefix ONLY. The earlier\n * project_dir co-union (Vincent 4724) was dropped — live /api/status\n * data showed it actively wrong: 视频测试马 reported project_dir\n * `/Users/vansin/intern-aip`, the same dir as the unrelated standalone\n * nodes 知识马 / 群星马, so union-find transitively bridged 知识马 /\n * 群星马 into the 视频 team. project_dir data quality is also poor\n * (corrupted nested paths like `VideoNode/~/code/VideoNode/~/…`).\n * alias prefix is the reliable signal; prefix-less nodes correctly\n * fall out as singletons → the 未分组 bucket. The real fix is a\n * first-class node.team field — tracked as #175. */\nfunction computeGroups(sessions: { alias: string }[]): Record<string, string> {\n const n = sessions.length;\n const parent = sessions.map((_, i) => i);\n const find = (i: number): number => {\n while (parent[i] !== i) { parent[i] = parent[parent[i]]; i = parent[i]; }\n return i;\n };\n const union = (a: number, b: number) => {\n const ra = find(a), rb = find(b);\n if (ra !== rb) parent[ra] = rb;\n };\n\n // union by shared ≥2-char alias prefix — sort, link adjacent pairs\n const order = sessions.map((_, i) => i).sort((a, b) => sessions[a].alias.localeCompare(sessions[b].alias));\n for (let k = 0; k + 1 < order.length; k++) {\n if (commonPrefix(sessions[order[k]].alias, sessions[order[k + 1]].alias).length >= 2) {\n union(order[k], order[k + 1]);\n }\n }\n\n // label each connected component with the common alias prefix\n const comps: Record<number, number[]> = {};\n for (let i = 0; i < n; i++) (comps[find(i)] ||= []).push(i);\n const keys: Record<string, string> = {};\n for (const members of Object.values(comps)) {\n let label: string;\n if (members.length === 1) {\n label = sessions[members[0]].alias;\n } else {\n label = members.map(i => sessions[i].alias).reduce((a, b) => commonPrefix(a, b));\n if (label.length < 2) label = sessions[members[0]].alias;\n }\n for (const i of members) keys[sessions[i].alias] = label;\n }\n return keys;\n}\n\nfunction buildFlowLinks(messages: MessageFlow[], tasks: TaskFlow[], positions: Record<string, Point>) {\n const links = new Map<string, FlowLink>();\n\n const commandAlias = Object.keys(positions).find(a => a.includes('副指挥'))\n ?? Object.keys(positions).find(a => a.includes('总指挥'))\n ?? null;\n\n const resolveAlias = (alias: string | null | undefined) => {\n const clean = (alias || '').trim();\n if (!clean) return null;\n if (positions[clean]) return clean;\n if ((clean === 'api' || clean === 'admin') && commandAlias) return commandAlias;\n return clean;\n };\n\n const addLink = (fromRaw: string | null | undefined, toRaw: string | null | undefined, content: string, createdAt: string) => {\n const from = resolveAlias(fromRaw);\n const to = resolveAlias(toRaw);\n if (!from || !to || !positions[from] || !positions[to] || from === to) {\n return;\n }\n\n const key = `${from}->${to}`;\n const current = links.get(key);\n\n // Keep the most-recent timestamp per pair so the render can fade\n // dormant edges (Round 10 freshness fade).\n const incoming = createdAt || '';\n const last_at = !current\n ? incoming\n : (incoming > current.last_at ? incoming : current.last_at);\n\n links.set(key, {\n key,\n from,\n to,\n count: (current?.count || 0) + 1,\n content: current?.content || content,\n last_at,\n });\n };\n\n messages.forEach(message => {\n addLink(message.from_alias, message.to_alias, message.content, message.created_at);\n });\n\n tasks.forEach(task => {\n const from = task.from_alias ?? task.from_name;\n const to = task.to_alias ?? task.to_name;\n const when = task.completed_at || task.started_at || task.delivered_at || task.created_at || '';\n const label = task.content || (task.status ? `task:${task.status}` : 'task');\n addLink(from, to, label, when);\n });\n\n return [...links.values()]\n .sort((a, b) => (b.last_at || '').localeCompare(a.last_at || ''))\n .slice(0, 18);\n}\n\nexport function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProps) {\n const { hasUnread } = useChatUnread();\n const theme = useTheme();\n const isLight = theme === 'light';\n const reducedMotion = useReducedMotion();\n const pal = isLight ? LIGHT_PALETTE : DARK_PALETTE;\n const brand = useBrand();\n const isIntern = brand === 'intern';\n // v0.10.0 Hero 1+2 / §3.F — per-host health tier map. Composes\n // with #119 ServersDrawer (same SWR key, deduped). When a node's\n // host server crosses into the red tier (CPU/Mem/Disk ≥ 85%),\n // the per-node SVG render adds a faint amber outer ring to flag\n // the issue without leaving the topology view.\n const hostHealthMap = useServerHealthMap();\n // R133: Next.js client-router for the recent-signal panel \"+N more\"\n // navigation. TopoGraph hasn't needed routing before — every other\n // affordance composes back into the canvas's own state — but the\n // truncated-flow footer is the one place where the user logically\n // wants to leave: \"show me the rest of the flows\" → /messages.\n const router = useRouter();\n const [messages, setMessages] = useState<MessageFlow[]>([]);\n const [tasks, setTasks] = useState<TaskFlow[]>([]);\n // Issue #87: ring | grid layout toggle. Ring is the tiered-radial default;\n // grid arranges nodes in an N×M grid (better for 30+ nodes). Persisted to\n // localStorage like the zoom/pan view state. Declared above nodePositions\n // since that useMemo branches on it.\n const [layout, setLayout] = useState<'ring' | 'grid' | 'tree'>('ring');\n useEffect(() => {\n try {\n const saved = localStorage.getItem('anet-topo-layout');\n if (saved === 'grid' || saved === 'ring' || saved === 'tree') {\n setLayout(saved);\n } else if (sessions.length >= 20) {\n // v0.10.0 Hero 3 Wave 1 §3.D — auto-grid for dense fleets.\n // When the user hasn't explicitly chosen a layout (no\n // localStorage entry), default to `grid` once the fleet\n // crosses 20 nodes. Below 20, the ring layout reads more\n // attractive (per #87 + R97-99 tier-ring history); at 20+\n // the ring starts packing tier 3 (R98 triple-tier\n // threshold) and grid scales cleaner — every cell visible\n // at the same density, no overlap-test risk from tier\n // crowding. The user's explicit toggle (R163 chrome\n // Ring|Grid) always wins by writing to anet-topo-layout,\n // so this only sets the *initial* preference for first-\n // time users on a dense fleet. Threshold 20 picked to\n // align with the existing density-aware breakpoints (R98\n // tier flip; R109 dense-label gating at 16). */}\n setLayout('grid');\n }\n } catch {}\n // sessions.length intentionally NOT in deps — this runs once on\n // mount and shouldn't re-fire when nodes join/leave (which would\n // override a user's mid-session toggle). The Ring|Grid chrome\n // button remains the authoritative source post-mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n // Round 170 / Loop: layout toggle (Ring ↔ Grid) used to teleport\n // every node from its old position to its new one in one paint\n // frame — the most jarring single user action left on the\n // canvas. Solution: dim the viewport <g> to ~45% opacity for\n // the duration of the swap so the eye reads it as a soft\n // crossfade-blink rather than a hard teleport. layoutSwitching\n // is a one-shot flag; the inline style on the viewport <g>\n // reads it and lerps opacity 1 → 0.45 → 1 across the swap.\n // Auto-clears after 400ms (covers fade-down 250ms + buffer for\n // React to commit the new layout's positions). Same pattern as\n // R168's smoothView arming but on a different visual axis\n // (opacity, not transform).\n const [layoutSwitching, setLayoutSwitching] = useState(false);\n // #170 tree-view MVP — layout is now 3-way (ring | grid | tree).\n // selectLayout sets a specific mode (used by the chrome segmented\n // control's 3 buttons); toggleLayout cycles ring → grid → tree → ring\n // (used by the `l` keyboard shortcut). Both arm the R170 crossfade-\n // blink so the node teleport between layouts reads as a soft swap.\n const LAYOUT_CYCLE: ('ring' | 'grid' | 'tree')[] = ['ring', 'grid', 'tree'];\n const selectLayout = (next: 'ring' | 'grid' | 'tree') => {\n setLayoutSwitching(true);\n setTimeout(() => setLayoutSwitching(false), 400);\n setLayout(prev => {\n if (prev === next) return prev;\n try { localStorage.setItem('anet-topo-layout', next); } catch {}\n return next;\n });\n };\n const toggleLayout = () => {\n setLayoutSwitching(true);\n setTimeout(() => setLayoutSwitching(false), 400);\n setLayout(prev => {\n const next = LAYOUT_CYCLE[(LAYOUT_CYCLE.indexOf(prev) + 1) % LAYOUT_CYCLE.length];\n try { localStorage.setItem('anet-topo-layout', next); } catch {}\n return next;\n });\n };\n // Issue #113: node size scale (Vincent 4727 — nodes too big / crowded at\n // ~22 nodes). S/M/L → 0.7/0.84/1.0; default M (one notch down from the old\n // fixed size). Persisted like the layout toggle.\n const [nodeScale, setNodeScale] = useState(0.84);\n useEffect(() => {\n try {\n const saved = parseFloat(localStorage.getItem('anet-topo-nodescale') || '');\n if (saved === 0.7 || saved === 0.84 || saved === 1) setNodeScale(saved);\n } catch {}\n }, []);\n // Round 171 / Loop: nodeSize change picks up the R170 crossfade\n // pattern. Clicking S/M/L in the chrome re-derives every node's\n // radius + label sizing + (in grid layout) cell spacing — a\n // wholesale visual shift. Pre-R171 the resize snapped in one\n // paint frame; with this flag the viewport <g> opacity dims to\n // 0.45 for ~400ms, masking the redraw as a soft blink (same\n // vocabulary R170 uses for layout toggle). Bails early when\n // the picked scale matches the current one — clicking the\n // already-active button shouldn't fire a fade. Composes with\n // R170 layoutSwitching via `||` in the opacity expression; both\n // flags drive the same opacity transition but expose distinct\n // data-* attributes so tests can disambiguate which gesture\n // armed the crossfade.\n const [nodeSizeSwitching, setNodeSizeSwitching] = useState(false);\n const pickNodeScale = (v: number) => {\n if (v === nodeScale) return;\n setNodeSizeSwitching(true);\n setTimeout(() => setNodeSizeSwitching(false), 400);\n setNodeScale(v);\n try { localStorage.setItem('anet-topo-nodescale', String(v)); } catch {}\n };\n\n useEffect(() => {\n const fetchFlows = async () => {\n try {\n const [messageRes, taskRes] = await Promise.all([\n fetch('/api/hub/messages?limit=50'),\n fetch('/api/hub/tasks?limit=100'),\n ]);\n if (messageRes.status === 401 || taskRes.status === 401) {\n window.location.assign('/login');\n return;\n }\n\n const [messageData, taskData] = await Promise.all([messageRes.json(), taskRes.json()]);\n setMessages(messageData.messages || []);\n setTasks(taskData.tasks || []);\n } catch {}\n };\n fetchFlows();\n const interval = setInterval(fetchFlows, 5000);\n return () => clearInterval(interval);\n }, []);\n\n const {\n onlineNodes,\n offlineNodes,\n nodePositions,\n flowLinks,\n activeAliases,\n groupKeys,\n groupBoxes,\n gridContentBottom,\n treeConnectors,\n treeContentBox,\n } = useMemo(() => {\n const sseCount = (s: { alias: string; network_id?: string }) =>\n (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n // Round 106 (issue #83): sort by alias so same-prefix agents\n // (通信龙 / 通信牛 / 通信工程马 …, or 研究员1号 / 研究员2号 …) end up\n // adjacent in the array — the tier layout below assigns angles by\n // index, so contiguous-in-array becomes contiguous-in-ring, i.e.\n // each team visually clusters. localeCompare keeps CJK ordering sane.\n const byAlias = (a: Session, b: Session) => a.alias.localeCompare(b.alias);\n // #112 umbrella: ghost age-out — an offline node not seen recently is\n // almost certainly a deleted node the server `/api/status` still\n // returns (#74 root cause is server-side; this is the dashboard-side\n // fallback so stale ghosts stop cluttering the topology). A missing\n // last_seen_at is kept (conservative — could be a legitimately new node).\n // Round 27 / P0: the 24h threshold was too lenient — Vincent's preview.29\n // screenshot showed B站马 nodes deleted ~4 h earlier still visible. A\n // healthy agent heartbeats every few seconds; if it's been silent for\n // an hour it's effectively gone. 1 h gives a fresh disconnect time to\n // come back while removing dead nodes well within an operator session.\n const GHOST_MS = 60 * 60 * 1000;\n const now = Date.now();\n const isGhost = (s: Session) => {\n if (s.status !== 'offline' || sseCount(s) || !s.last_seen_at) return false;\n // Round 35 / Loop: parseHubTime normalises SQL-style timestamps to UTC\n // before parsing so non-UTC browsers don't see a phantom 8-hour skew\n // and ghost freshly-disconnected nodes.\n const t = parseHubTime(s.last_seen_at);\n return t !== null && now - t > GHOST_MS;\n };\n const online = sessions.filter(s => s.status !== 'offline' || sseCount(s)).sort(byAlias);\n const offline = sessions.filter(s => s.status === 'offline' && !sseCount(s) && !isGhost(s)).sort(byAlias);\n const positions: Record<string, Point> = {};\n\n if (layout === 'grid') {\n // Issue #87 + #111: group-banded grid. Nodes are sorted by alias so\n // same-prefix aliases are adjacent; each multi-member prefix group then\n // gets its OWN row(s) starting at column 0, while singletons pack into\n // shared rows. Group-banded placement keeps every group's bounding box\n // (#111) a clean rectangle — no box ever overlaps another group's.\n const all = [...online, ...offline];\n const groupKeys = computeGroups(all);\n const cols = Math.max(1, Math.ceil(Math.sqrt(all.length)));\n // #112: gy0 starts below the (now compact) recent-signal / legend\n // overlay panels — their max bottom edge is y≈112 — so grid row 0 is\n // never occluded; gy1 extends near the canvas bottom for breathing room.\n const gx0 = 150, gx1 = 850, gy0 = 126, gy1 = 652;\n const cellW = (gx1 - gx0) / cols;\n // largest node radius at the current size scale — drives the group\n // label band + the row-height floor so nothing ever overlaps.\n const nodeR = Math.round(26 * nodeScale);\n\n // ordered runs of consecutive same-group-key nodes (≥2 = real group)\n const runs: { key: string; members: Session[] }[] = [];\n for (const s of all) {\n const gk = groupKeys[s.alias];\n const last = runs[runs.length - 1];\n if (last && last.key === gk) last.members.push(s);\n else runs.push({ key: gk, members: [s] });\n }\n\n // Pass 1 — assign each run to a band.\n //\n // v0.10.4 #150 (Vincent /goal 5453): \"不是一起的落单的怎么散落在中间了\".\n // Pre-#150 algo interleaved singletons between real groups as\n // centred bands, so orphan nodes appeared scattered in the middle\n // between cluster boxes. Vincent screenshot called this out as\n // \"layout 算法一点都不好\". Fix: bundle ALL singletons into ONE\n // band at the bottom of the grid + render an \"其他\" cluster box\n // around them. Multi-member prefix groups still go first in\n // alias order (existing #83/#111 behaviour). Net effect:\n // row 0..N-1: real prefix groups (left-aligned, own cluster box)\n // row N..M: single \"其他\" band collecting all orphans\n // (left-aligned, single cluster box at bottom)\n // No orphans → no orphan band → behaviour identical to pre-#150\n // for fleets where every node has a prefix-group match.\n type Band = { members: Session[]; startRow: number; centred: boolean; isGroup: boolean; isOrphan?: boolean };\n const bands: Band[] = [];\n let row = 0;\n const orphanMembers: Session[] = [];\n for (const run of runs) {\n if (run.members.length >= 2) {\n bands.push({ members: run.members, startRow: row, centred: false, isGroup: true });\n row += Math.ceil(run.members.length / cols);\n } else {\n // single-member run → collect for the bottom orphan band\n orphanMembers.push(...run.members);\n }\n }\n if (orphanMembers.length > 0) {\n bands.push({ members: orphanMembers, startRow: row, centred: false, isGroup: true, isOrphan: true });\n row += Math.ceil(orphanMembers.length / cols);\n }\n const totalRows = Math.max(1, row);\n // #112: the group label sits in a band ABOVE the topmost node, so the\n // band must clear the node radius — GROUP_TOP is node-relative, never\n // cellH-derived (cellH-derived was the label↔node overlap bug Vincent\n // hit). cellH then floors at GROUP_TOP + 30 so the label band + bottom\n // padding always fit and stacked group boxes never touch; past the\n // floor the grid overflows and zoom/pan handles it.\n const GROUP_TOP = nodeR + 20;\n // Round 27 / P0 (Vincent screenshot preview.29): dense plain-text\n // node labels (`pos.y + radius + denseDrop`, with a 3 px containerBg\n // stroke halo for readability) at the BOTTOM row of band N would\n // paint OVER the start of band N+1's group label, creating the\n // \"blueleap → eleap\", \"agent-network-dashboard → t-network / board\"\n // visual chopping. Geometry of the collision:\n // dense label visual bottom = node_N.y + radius + denseDrop + halo\n // group label glyph top = boxY + 4 (text y=boxY+14, ~10px ascent)\n // = node_N+1.y - GROUP_TOP + 4\n // node_N+1.y - node_N.y = cellH for consecutive rows, so no-collide:\n // cellH ≥ radius + denseDrop + halo + GROUP_TOP - 4 + buffer\n // With halo=3 buffer=4: cellH ≥ nodeR + denseDrop + GROUP_TOP + 3.\n const denseDrop = nodeScale < 0.8 ? 12 : 14;\n const cellH = Math.max(\n 2 * nodeR + 22, // node + dense label within band\n GROUP_TOP + 12, // group-label band + box padding\n nodeR + denseDrop + GROUP_TOP + 3, // round-27 dense↔group label clearance\n Math.min(100, (gy1 - gy0) / totalRows),\n );\n\n // Pass 2 — place each band's members.\n for (const band of bands) {\n band.members.forEach((s, idx) => {\n const rowInBand = Math.floor(idx / cols);\n const c = idx % cols;\n const inRow = Math.min(cols, band.members.length - rowInBand * cols);\n const inset = band.centred ? ((cols - inRow) * cellW) / 2 : 0;\n positions[s.alias] = {\n x: gx0 + inset + (c + 0.5) * cellW,\n y: gy0 + (band.startRow + rowInBand + 0.5) * cellH,\n };\n });\n }\n\n const links = buildFlowLinks(messages, tasks, positions);\n const active = new Set<string>();\n links.forEach(link => { active.add(link.from); active.add(link.to); });\n // #111: one bounding box per multi-member group (Vincent 4722). Each\n // group owns its rows; GROUP_PAD fills the row space left below the\n // nodes after the label band, so stacked group boxes always have a\n // gap between them (GROUP_TOP is defined above, with cellH).\n const GROUP_PAD = Math.max(8, Math.min(26, cellH - GROUP_TOP - 8)); // side/bottom\n const groupBoxes = bands\n .filter(b => b.isGroup)\n .map(band => {\n const pts = band.members.map(s => positions[s.alias]).filter(Boolean);\n const xs = pts.map(p => p.x);\n const ys = pts.map(p => p.y);\n const minX = Math.min(...xs), minY = Math.min(...ys);\n // Round 58 / Loop: per-group status mix for the label pip strip.\n // Working = status==='working'. Idle = online but not working.\n // Offline = !isOnline (either status==='offline' AND no SSE, or\n // ghost-purged elsewhere — but ghosts never reach groupBoxes\n // since they're filtered out upstream). Counts feed the label\n // tspans directly so the strip stays inside the label's bbox,\n // preserving the node↔label overlap-test guarantee from R19.\n let w = 0, i = 0, o = 0;\n for (const s of band.members) {\n const isOn = s.status !== 'offline' || !!sseCount(s);\n if (s.status === 'working') w++;\n else if (isOn) i++;\n else o++;\n }\n // v0.10.4 #150 — orphan band (singletons bundled at bottom)\n // renders with a \"其他\" cluster box; the box-key drives the\n // R63 label render + R86 hover-pin keying + #99 tooltip\n // member listing, so all the existing group-box machinery\n // applies uniformly to the orphan bucket too.\n // Round 499 / Loop — surface `isOrphan` flag on the box\n // shape so downstream renderers (label text, future polish)\n // can apply orphan-specific typography (italic) without\n // re-deriving the flag from key === '其他' (key matching\n // would also catch a legitimate \"其他\" prefix-group, this\n // flag is canonical from the band assignment pass).\n return {\n key: band.isOrphan\n ? '其他'\n : band.members.length\n ? groupKeys[band.members[0].alias]\n : '',\n isOrphan: !!band.isOrphan,\n count: band.members.length,\n statuses: { working: w, idle: i, offline: o },\n x: minX - GROUP_PAD,\n y: minY - GROUP_TOP,\n w: Math.max(...xs) - minX + GROUP_PAD * 2,\n h: Math.max(...ys) - minY + GROUP_TOP + GROUP_PAD,\n };\n });\n // Round 28 / Loop: surface the grid's natural content bottom so the\n // mount effect can auto-fit zoom when the layout would overflow the\n // viewBox. = bottom of the last node row + its label drop + a small\n // breathing buffer. Round 27's cellH bump makes this overflow more\n // common (30-node fleets reach ~774 px, viewBox is 680).\n const gridContentBottom = gy0 + totalRows * cellH + 8;\n return {\n onlineNodes: online,\n offlineNodes: offline,\n nodePositions: positions,\n flowLinks: links,\n activeAliases: active,\n groupKeys,\n groupBoxes,\n gridContentBottom,\n treeConnectors: { lines: [], synthLabels: [], synthRoot: false } as TreeLayout,\n treeContentBox: null as { x: number; y: number; w: number; h: number } | null,\n };\n }\n\n if (layout === 'tree') {\n // #170 tree-view MVP — classic top-down org chart, derived purely\n // from alias + runtime (zero-config; org.json override is the\n // RFC-017 formal version, out of scope here).\n //\n // layer 0 总指挥 ── synthetic root if none / multiple\n // layer 1 副指挥 ── report to 总指挥\n // layer 2 team leads ── one per computeGroups() team\n // + a 未分组 bucket for orphans\n // layer 3 deputy + members hanging under their team lead\n //\n // The classifier per team: LEAD = runtime claude-code-cli OR alias\n // contains 负责人; DEPUTY = runtime codex-sdk (or runtime string\n // containing \"codex\"); everyone else = members.\n const all = [...online, ...offline];\n const groupKeys = computeGroups(all);\n const isCommander = (a: string) => a.includes('总指挥');\n const isDeputyCmdr = (a: string) => a.includes('副指挥');\n const runtimeOf = (s: Session) => (s.runtime || '').toLowerCase();\n const isCodexRt = (s: Session) => runtimeOf(s).includes('codex');\n const isCliRt = (s: Session) => runtimeOf(s) === 'claude-code-cli';\n\n // commanders are roots, never team members\n const commanders = all.filter(s => isCommander(s.alias));\n const deputyCommanders = all.filter(s => isDeputyCmdr(s.alias) && !isCommander(s.alias));\n const rankAliases = new Set([...commanders, ...deputyCommanders].map(s => s.alias));\n\n // teams = computeGroups components, minus the commander aliases.\n // A \"real\" team is a group key shared by ≥2 non-commander members.\n const teamMembers: Record<string, Session[]> = {};\n const orphans: Session[] = [];\n for (const s of all) {\n if (rankAliases.has(s.alias)) continue;\n const gk = groupKeys[s.alias] ?? s.alias;\n (teamMembers[gk] ||= []).push(s);\n }\n const realTeams: { key: string; members: Session[] }[] = [];\n for (const [key, members] of Object.entries(teamMembers)) {\n if (members.length >= 2) realTeams.push({ key, members });\n else orphans.push(...members);\n }\n realTeams.sort((a, b) => a.key.localeCompare(b.key));\n\n // within a team: pick the LEAD (claude-code-cli runtime, or 负责人\n // in the alias forces it), then DEPUTY (codex runtime), rest = members.\n type TeamTree = { key: string; lead: Session; deputy: Session | null; members: Session[] };\n const pickLead = (members: Session[]): Session => {\n const forced = members.find(s => s.alias.includes('负责人'));\n if (forced) return forced;\n const cli = members.find(s => isCliRt(s));\n if (cli) return cli;\n return [...members].sort((a, b) => a.alias.localeCompare(b.alias))[0];\n };\n const teamTrees: TeamTree[] = realTeams.map(({ key, members }) => {\n const lead = pickLead(members);\n const rest = members.filter(s => s.alias !== lead.alias);\n const deputy = rest.find(s => isCodexRt(s)) ?? null;\n const others = rest\n .filter(s => !deputy || s.alias !== deputy.alias)\n .sort((a, b) => a.alias.localeCompare(b.alias));\n return { key, lead, deputy, members: others };\n });\n\n // ---- swimlane org layout (Vincent dispatch: \"一行一个团队,成员\n // 不换行,最左边放总指挥和副指挥\") ------------------------------------\n // Each team is ONE horizontal lane — a wide box whose members sit in\n // a single NON-WRAPPING row. Lanes stack top-to-bottom (one team per\n // row). The command layer (总指挥 + 副指挥) is pinned in its own box\n // down the left edge. Reverses the iter1–3 2D box-grid; the iter5\n // treeContentBox auto-fit still scales + centres the whole chart.\n const nodeR = Math.round(26 * nodeScale);\n const cellW = Math.max(118, 2 * nodeR + 66); // member slot — node + dense label\n const BOX_LABEL = 26; // box top label band\n const LANE_BODY = Math.max(82, 2 * nodeR + 36);// member-row band height\n const LANE_H = BOX_LABEL + LANE_BODY; // full lane box height\n const LANE_GAP = 16; // vertical gap between lanes\n const BOX_PAD = 14; // lane inner side padding\n const CMD_W = Math.max(150, 2 * nodeR + 100); // command column width\n const CMD_GAP = 44; // gap: command column → lanes\n const CMD_SLOT = Math.max(116, 2 * nodeR + 64);// command node vertical pitch\n const TOP = 92;\n const LEFT = 44;\n const laneX0 = LEFT + CMD_W + CMD_GAP; // x where team lanes start\n\n // ordered lanes: one per team (teamTrees is already alias-sorted) +\n // a trailing 未分组 lane collecting every orphan so none are dropped.\n type Lane = { key: string; isOrphan: boolean; members: Session[] };\n const lanes: Lane[] = teamTrees.map(tt => ({\n key: tt.key, isOrphan: false,\n members: [tt.lead, ...(tt.deputy ? [tt.deputy] : []), ...tt.members],\n }));\n if (orphans.length > 0) {\n lanes.push({\n key: '未分组', isOrphan: true,\n members: [...orphans].sort((a, b) => a.alias.localeCompare(b.alias)),\n });\n }\n // Vincent feedback: stack lanes longest-team-first — sort by member\n // count DESCENDING so the swimlane steps down long → short. A stable\n // secondary sort by key keeps equal-length lanes from jittering\n // between renders.\n lanes.sort((a, b) => b.members.length - a.members.length || a.key.localeCompare(b.key));\n // Vincent 5923 (#181): keep related team lanes adjacent. Two teams\n // are \"related\" when one's key is a substring of the other's\n // (设计 ⊂ 海报设计师). Union-find over lane keys → substring-relation\n // components; each component is emitted contiguously, anchored at its\n // largest lane's size-sorted slot (lanes within a component stay\n // size-desc). Unrelated teams keep their size order — this only stops\n // a related pair from being split apart by an unrelated team.\n {\n const lp = lanes.map((_, i) => i);\n const lfind = (i: number): number => {\n while (lp[i] !== i) { lp[i] = lp[lp[i]]; i = lp[i]; }\n return i;\n };\n for (let i = 0; i < lanes.length; i++) {\n for (let j = i + 1; j < lanes.length; j++) {\n const a = lanes[i].key, b = lanes[j].key;\n if (a.length >= 2 && b.length >= 2 && (a.includes(b) || b.includes(a))) {\n lp[lfind(i)] = lfind(j);\n }\n }\n }\n const seenRoot = new Set<number>();\n const clustered: Lane[] = [];\n for (let i = 0; i < lanes.length; i++) {\n const root = lfind(i);\n if (seenRoot.has(root)) continue;\n seenRoot.add(root);\n for (let j = i; j < lanes.length; j++) {\n if (lfind(j) === root) clustered.push(lanes[j]);\n }\n }\n lanes.splice(0, lanes.length, ...clustered);\n }\n\n // each lane = a wide box; its members in a single row inside it.\n const laneStep = LANE_H + LANE_GAP;\n const laneBoxes = lanes.map((lane, i) => {\n const y = TOP + i * laneStep;\n const w = 2 * BOX_PAD + Math.max(1, lane.members.length) * cellW;\n lane.members.forEach((s, j) => {\n positions[s.alias] = {\n x: laneX0 + BOX_PAD + (j + 0.5) * cellW,\n y: y + BOX_LABEL + LANE_BODY / 2,\n };\n });\n return { lane, x: laneX0, y, w, h: LANE_H };\n });\n const laneBlockH = lanes.length ? lanes.length * laneStep - LANE_GAP : LANE_H;\n const laneBlockBottom = TOP + laneBlockH;\n\n // command column — 总指挥 + 副指挥 stacked in a compact box pinned to\n // the far left, vertically centred against the lane block.\n const cmdMembers: Session[] = [...commanders, ...deputyCommanders];\n const cmdX = LEFT + CMD_W / 2;\n const cmdH = cmdMembers.length > 0 ? BOX_LABEL + cmdMembers.length * CMD_SLOT : 0;\n const cmdBox = cmdMembers.length > 0\n ? { x: LEFT, y: TOP + Math.max(0, (laneBlockH - cmdH) / 2), w: CMD_W, h: cmdH }\n : null;\n if (cmdBox) {\n cmdMembers.forEach((s, i) => {\n positions[s.alias] = {\n x: cmdX,\n y: cmdBox.y + BOX_LABEL + (i + 0.5) * CMD_SLOT,\n };\n });\n }\n\n // org link: a light connector from the command column to each lane.\n const connectors: TreeConnectorLine[] = [];\n if (cmdBox) {\n const cmdRight = cmdBox.x + cmdBox.w;\n const cmdMidY = cmdBox.y + cmdBox.h / 2;\n for (const lb of laneBoxes) {\n connectors.push({ x1: cmdRight, y1: cmdMidY, x2: lb.x, y2: lb.y + LANE_H / 2 });\n }\n }\n\n // swimlane has no synthetic root chip — the command box (labelled\n // 指挥中心 when there is no single 总指挥) is the command layer.\n const synthRoot = false;\n const synthBoxes: { x: number; y: number; label: string }[] = [];\n\n // lanes + command column surfaced as groupBoxes — reuses the cluster\n // box render + per-box working/idle/offline pip strip.\n const tally = (members: Session[]) => {\n let w = 0, i = 0, o = 0;\n for (const s of members) {\n const isOn = s.status !== 'offline' || !!sseCount(s);\n if (s.status === 'working') w++;\n else if (isOn) i++;\n else o++;\n }\n return { working: w, idle: i, offline: o };\n };\n const teamGroupBoxes = [\n ...(cmdBox ? [{\n key: commanders.length === 1 ? commanders[0].alias : '指挥中心',\n isOrphan: false, count: cmdMembers.length,\n statuses: tally(cmdMembers),\n x: cmdBox.x, y: cmdBox.y, w: cmdBox.w, h: cmdBox.h,\n }] : []),\n ...laneBoxes.map(lb => ({\n key: lb.lane.key, isOrphan: lb.lane.isOrphan,\n count: lb.lane.members.length,\n statuses: tally(lb.lane.members),\n x: lb.x, y: lb.y, w: lb.w, h: lb.h,\n })),\n ];\n\n // content bottom for auto-fit — bottom of the lowest box.\n const treeBottom = Math.max(\n laneBlockBottom,\n cmdBox ? cmdBox.y + cmdBox.h : TOP + LANE_H,\n );\n const gridContentBottom = treeBottom + 48;\n\n // iter5 (Vincent UX): full content bounding box → the auto-fit effect\n // scales the chart to fit BOTH viewBox dimensions AND centres it.\n const treeBX: number[] = [], treeBY: number[] = [];\n for (const gb of teamGroupBoxes) { treeBX.push(gb.x, gb.x + gb.w); treeBY.push(gb.y, gb.y + gb.h); }\n for (const a of Object.keys(positions)) { treeBX.push(positions[a].x); treeBY.push(positions[a].y); }\n // TREE_M pads the raw box/node extent so node radii + label drops\n // never clip at the fitted edge.\n const TREE_M = 52;\n const treeContentBox: { x: number; y: number; w: number; h: number } | null = treeBX.length\n ? {\n x: Math.min(...treeBX) - TREE_M,\n y: Math.min(...treeBY) - TREE_M,\n w: Math.max(...treeBX) - Math.min(...treeBX) + 2 * TREE_M,\n h: Math.max(...treeBY) - Math.min(...treeBY) + 2 * TREE_M,\n }\n : null;\n\n // Vincent feedback: restore the agent↔agent message flow-links in\n // tree/swimlane. iter4 (a6689b6) suppressed them as \"杂线\"; Vincent\n // now explicitly wants them back — same buildFlowLinks the ring/grid\n // layouts use, drawn against the swimlane node positions.\n const links = buildFlowLinks(messages, tasks, positions);\n const active = new Set<string>();\n links.forEach(link => { active.add(link.from); active.add(link.to); });\n\n return {\n onlineNodes: online,\n offlineNodes: offline,\n nodePositions: positions,\n flowLinks: links,\n activeAliases: active,\n groupKeys,\n groupBoxes: teamGroupBoxes,\n gridContentBottom,\n treeConnectors: { lines: connectors, synthLabels: synthBoxes, synthRoot } as TreeLayout,\n treeContentBox,\n };\n }\n\n // Round 97 (issue #50) + 98 (issue #61): three layout modes by N.\n // N ≤ 8 → single ring (r=220)\n // 8 < N ≤ 14 → two rings (inner r=175, outer r=260, half-step rot)\n // N > 14 → three rings (r=145/215/285, ⌈N/3⌉ per ring)\n // Each ring's spread is 1.78π so its node count drives chord length:\n // labels are 100px wide so each ring needs ≥110px chord per node.\n const tripleTier = online.length > onlineTripleThreshold;\n const dualTier = !tripleTier && online.length > onlineTierThreshold;\n let outerOnlineCount = online.length;\n if (tripleTier) {\n const per = Math.ceil(online.length / 3);\n const r1 = online.slice(0, per);\n const r2 = online.slice(per, 2 * per);\n const r3 = online.slice(2 * per);\n r1.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r1.length, 1), onlineTripleInnerR);\n });\n const r1Spread = r1.length <= 2 ? Math.PI : Math.PI * 1.78;\n const r1Step = r1.length > 1 ? r1Spread / (r1.length - 1) : 0;\n r2.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r2.length, 1), onlineTripleMidR, r1Step / 2);\n });\n r3.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r3.length, 1), onlineTripleOuterR);\n });\n outerOnlineCount = r3.length;\n } else if (dualTier) {\n const innerCount = Math.ceil(online.length / 2);\n const outerCount = online.length - innerCount;\n const innerNodes = online.slice(0, innerCount);\n const outerNodes = online.slice(innerCount);\n innerNodes.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(innerCount, 1), onlineInnerRadius);\n });\n const innerSpread = innerCount <= 2 ? Math.PI : Math.PI * 1.78;\n const innerStep = innerCount > 1 ? innerSpread / (innerCount - 1) : 0;\n outerNodes.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(outerCount, 1), onlineOuterRadius, innerStep / 2);\n });\n outerOnlineCount = outerCount;\n } else {\n online.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(online.length, 1), onlineRadius);\n });\n }\n\n // Offset the offline ring radially by half the outermost online step so\n // offline bubbles sit in the angular gaps between online bubbles instead\n // of stacking directly behind them. Also push the outer ring further when\n // there are many offline nodes so labels don't crowd the legend.\n const outerSpreadBase = outerOnlineCount <= 2 ? Math.PI : Math.PI * 1.78;\n const outerStep = outerOnlineCount > 1 ? outerSpreadBase / (outerOnlineCount - 1) : 0;\n const offlineRotation = outerOnlineCount > 0 ? outerStep / 2 : 0;\n const offlineR = offlineRadius + Math.max(0, offline.length - 4) * 6;\n\n offline.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(offline.length, 1), offlineR, offlineRotation);\n });\n\n const links = buildFlowLinks(messages, tasks, positions);\n const active = new Set<string>();\n links.forEach(link => {\n active.add(link.from);\n active.add(link.to);\n });\n\n // Round 106 (issue #83): group key per alias → shared hue per team.\n const groupKeys = computeGroups([...online, ...offline]);\n\n return {\n onlineNodes: online,\n offlineNodes: offline,\n nodePositions: positions,\n flowLinks: links,\n activeAliases: active,\n groupKeys,\n // #111: group boxes are a grid-layout feature only — radially scattered\n // ring nodes can't be cleanly boxed. Ring keeps the #83 prefix hue.\n groupBoxes: [] as { key: string; isOrphan?: boolean; count: number; statuses: { working: number; idle: number; offline: number }; x: number; y: number; w: number; h: number }[],\n // ring fits within VIEWBOX_H by construction (offlineRadius=325 + centre at y=330)\n gridContentBottom: 0,\n treeConnectors: { lines: [], synthLabels: [], synthRoot: false } as TreeLayout,\n treeContentBox: null as { x: number; y: number; w: number; h: number } | null,\n };\n }, [messages, tasks, sessions, sseSessions, layout, nodeScale]);\n\n const workingCount = onlineNodes.filter(s => s.status === 'working').length;\n // Round 744 / Loop: legend header node-count. The legend panel's header\n // (\"N nodes\") must equal the sum of its own three status rows\n // (working + idle + offline). Those rows break down onlineNodes +\n // offlineNodes — the nodes actually DRAWN on the canvas. Pre-R744 the\n // header rendered sessions.length, which also counts #112 aged-out GHOST\n // nodes (offline + not seen recently, deliberately not rendered). With\n // ghosts present the header (e.g. 81) exceeded its own row sum (e.g. 51)\n // by the ghost count — the panel disagreed with its own breakdown. Count\n // the drawn nodes so header ≡ working + idle + offline.\n const drawnNodeCount = onlineNodes.length + offlineNodes.length;\n // Round 6 / Loop: vendor distribution for the header chip — at a glance\n // \"what's in the fleet\" (A:5 M:2 O:8 书:12 …) without opening a node.\n // Sorted by count desc; \"unknown\" vendors collapse into a \"?\" bucket.\n const vendorDist = useMemo(() => {\n const tally = new Map<string, { initial: string; count: number; color: string }>();\n for (const s of [...onlineNodes, ...offlineNodes]) {\n const v = vendorForModel(s.model, s.runtime);\n const key = v.id === 'unknown' ? '?' : v.initial;\n const cur = tally.get(key);\n if (cur) cur.count++;\n else tally.set(key, { initial: key, count: 1, color: v.mono.text });\n }\n return [...tally.values()].sort((a, b) => b.count - a.count);\n }, [onlineNodes, offlineNodes]);\n // Round 109 (Vincent 4582 P0): hover-gated labels above this node count\n // so dense fleets show clean avatars instead of a wall of overlapping\n // label cards. 16 ≈ where the triple-tier rings start to crowd.\n const denseLayout = onlineNodes.length + offlineNodes.length > 16;\n const [hoveredAlias, setHoveredAlias] = useState<string | null>(null);\n // Round 86 / Loop: pointer-over-label preview. R63 wired the group\n // label to click-pin but hover gave no preview — the same hover/click\n // gap R83 closed for pressure-bar segments. This state lets the\n // label trigger the same dim mechanism a node hover already drives,\n // independently of whether the cursor happens to be over a member\n // node. ORs into hoveredGroup below so the existing derivation logic\n // (which composes with hoveredAlias, activeGroup, R85 marching ants)\n // keeps working unchanged.\n const [hoveredGroupLabel, setHoveredGroupLabel] = useState<string | null>(null);\n // Round 8 / Loop: which group is currently focused. Nodes outside this\n // group + other group boxes fade so the eye locks onto the team you're\n // pointing at. Singletons use their own alias as the group key.\n const hoveredGroup = hoveredGroupLabel\n ?? (hoveredAlias ? (groupKeys[hoveredAlias] ?? hoveredAlias) : null);\n // Round 63 / Loop: sticky group focus. R8 dims non-group nodes while\n // a member is hovered, but releasing the hover lets the focus fade.\n // Clicking the group label pins the group key here; activeGroup =\n // hoveredGroup ?? pinnedGroup so hover transiently overrides the\n // pin (handy for spot-comparing teams). Same compose pattern as\n // R60/R61 pinnedStatus.\n const [pinnedGroup, setPinnedGroup] = useState<string | null>(() => {\n // R66: same per-tab persistence treatment as pinnedStatus above.\n // The group key is opaque text (could be any prefix), so the init\n // reader can't validate it against a known enum — it just reads\n // whatever's stored. A useEffect below clears it if the value no\n // longer matches any current group (sessions changed since save).\n if (typeof window === 'undefined') return null;\n try { return sessionStorage.getItem('anet-topo-pinned-group'); } catch { return null; }\n });\n const activeGroup = hoveredGroup ?? pinnedGroup;\n // R66: sync pinnedGroup into sessionStorage when it changes; clear\n // it if it no longer matches any current group (the session set\n // can change between loads). The pinnedStatus sync sits next to\n // its declaration further down.\n useEffect(() => {\n try {\n if (pinnedGroup) sessionStorage.setItem('anet-topo-pinned-group', pinnedGroup);\n else sessionStorage.removeItem('anet-topo-pinned-group');\n } catch {}\n }, [pinnedGroup]);\n useEffect(() => {\n if (!pinnedGroup) return;\n const known = new Set(Object.values(groupKeys));\n if (!known.has(pinnedGroup)) setPinnedGroup(null);\n }, [pinnedGroup, groupKeys]);\n // Round 49 / Loop: reverse-direction of R40's edge-on-node-hover linkage.\n // R48 widened the flow hitbox to 16 px, so edges are precise enough to\n // serve as a state trigger. When the user hovers a flow edge, light up its\n // two endpoint nodes and dim the rest — \"who is this edge between\" becomes\n // visible without reading the tooltip. The set is the link's two aliases\n // (null when no edge hovered); node opacity composes this after inFocus.\n const [hoveredEdgeKey, setHoveredEdgeKey] = useState<string | null>(null);\n // Round 116 / Loop: sticky variant of hoveredEdgeKey. R56 made the\n // recent-signal rows brighten the matching edge on hover, but the\n // filter released on mouseleave — no way to lock a flow for a\n // closer look. Click-to-pin closes that gap, matching the\n // established hover→pin idiom (R60 status, R61 legend, R63 group,\n // R88 vendor). activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey\n // below so hover still wins for spot-comparison while a pin is set.\n const [pinnedEdgeKey, setPinnedEdgeKey] = useState<string | null>(null);\n const activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey;\n // Round 77 / Loop: hovering the \"N active links\" header chip globally\n // brightens every flow edge (1.5× opacity). Transient affordance — the\n // chip already says HOW MANY active flows exist; this answers WHERE\n // they are in one glance, without the user scanning the canvas for\n // moving particles. Reset on mouse leave.\n const [hoveredActiveLinks, setHoveredActiveLinks] = useState(false);\n // Round 115 / Loop: hub-center hover state. R52 made the hub\n // clickable (fit view) + cursor:pointer, but there was no\n // hover-time visual feedback — the click target felt guessed at\n // rather than confirmed. This state drives a subtle hint ring on\n // hover so users see the affordance before committing the click.\n const [hoveredHub, setHoveredHub] = useState(false);\n // R133: hover state for the recent-signal panel's \"+N more flows\"\n // navigation footer. Drives the on-hover opacity boost + underline\n // that signals interactivity, mirroring the hoveredHub idiom above.\n const [hoveredRecentMore, setHoveredRecentMore] = useState(false);\n // Round 346 / Loop: minimap-container hover affordance. The minimap\n // is a click-target (role=button at line ~7810, recenter-on-click +\n // Enter→resetView) but pre-R346 nothing visually changed on hover —\n // the only hint was the `cursor: crosshair` style. R346 lifts the\n // viewport rect (strokeWidth 1.5 → 1.75 + opacity 0.9 → 1.0) when\n // the user enters the minimap, marking \"this is the recenter target\n // and it's alive\". Sibling polish to the R332 minimap rounded-md →\n // rounded-lg corner family — that round refined geometry, this one\n // gives the viewport indicator inside the geometry a hover state.\n // 280ms ease-out transition list matches R199 smoothView vocabulary\n // so the visual joins the existing rhythm on the same rect.\n const [hoveredMinimap, setHoveredMinimap] = useState(false);\n // Round 347 / Loop: zoom-level readout hover-state letter-spacing\n // tween (0 → 0.5 px). The readout sandwiched between zoom-out /\n // zoom-in is a passive percent display — pre-R347 it had no hover\n // feedback at all (only a `title` tooltip). R347 extends the R344\n // (`+N more flows` footer) + R345 (panel titles) hover-letter-\n // spacing family from panel/footer surfaces into the HTML chrome\n // strip. Hovering the readout spreads its digits 0.5 px, signalling\n // \"this is alive\". tabular-nums + minWidth: 46 from R225 still lock\n // the column so the tween doesn't shove neighbouring controls.\n // 200ms ease-out joins the existing R264 color/border transition\n // list on the same span.\n const [hoveredZoomLevel, setHoveredZoomLevel] = useState(false);\n // Round 350 / Loop: reset-button icon hover-rotate preview of the\n // R184 click-spin. Pre-R350 hovering the reset button only changed\n // the button bg (white/5); the icon inside stayed perfectly still.\n // R350 nudges the icon -8° on hover — a tactile hint that this\n // button rotates the icon on click. When the click fires, the\n // R184 anet-reset-spin keyframe animation overrides the hover\n // transform for its 450 ms run (CSS animations win over transitions\n // on the same property); when the animation ends + React removes\n // the className, the inline transform eases back to whatever the\n // hover state says — either -8° (still hovering) or 0 (mouse left).\n // 350th-round milestone polish.\n const [hoveredReset, setHoveredReset] = useState(false);\n // R595: hover state for the chrome fullscreen button — drives the\n // brightness(1.15) filter on hover, sibling to hoveredReset above.\n // Same pattern as R593 hoveredZoomLevel + R594 hoveredReset; closes\n // the standalone chrome button pair (reset + fullscreen) at\n // brightness parity.\n const [hoveredFullscreen, setHoveredFullscreen] = useState(false);\n // R673 — hover state for the chrome zoom-in / zoom-out buttons.\n // Drives the multi-layer halo filter (pal.legendAccent 2+4 stride)\n // that completes the chrome zoom-strip parity: zoom-out + zoom-level\n // pill (R668) + zoom-in now all emit unified cyan halo on hover.\n // Pre-R673 these buttons had only Tailwind `hover:brightness-[1.15]`\n // (R596) — a single-axis paint lift. Sibling pattern to R594\n // hoveredReset + R595 hoveredFullscreen.\n const [hoveredZoomIn, setHoveredZoomIn] = useState(false);\n const [hoveredZoomOut, setHoveredZoomOut] = useState(false);\n // R674 — hover state for the chrome Layout ring|grid toggle pair.\n // Value-typed (single state covers both buttons) drives the multi-\n // layer halo filter completing the chrome toggle-controls family\n // (after R673 zoom-in/-out paired-sibling round). Pre-R674 the Ring/\n // Grid buttons had only Tailwind hover:brightness-[1.15] (R597) —\n // a single-axis paint lift. Post-R674 inline filter applies the\n // same 2+4 stride at pal.legendAccent tint as R667/R668 chrome-\n // control siblings.\n const [hoveredLayout, setHoveredLayout] = useState<'ring' | 'grid' | 'tree' | null>(null);\n // R675 — hover state for the chrome nodeSize S/M/L segmented trio.\n // Value-typed (single state covers all three buttons) drives the\n // multi-layer halo filter completing the chrome strip's nodeSize\n // segmented-control group. Pre-R675 the trio had only Tailwind\n // hover:brightness-[1.15] (R598). Post-R675 inline filter applies\n // the same 2+4 stride at pal.legendAccent tint as R667/R668/R673/\n // R674 chrome-control siblings. With R675 the chrome strip is\n // FULLY halo-extended at every interactive control (Ring/Grid +\n // S/M/L + zoom-in/-out + zoom-level + reset + fullscreen).\n const [hoveredNodeSize, setHoveredNodeSize] = useState<'S' | 'M' | 'L' | null>(null);\n // R135: panel-wide hover-elevation. The recent-signal + legend\n // panels both already host clickable rows (R56/R116 recent rows,\n // R55/R61 legend rows) and a clickable footer (R133), so the\n // chrome itself is interactive territory. Drop-shadow boost on\n // mouseenter says \"this whole panel is alive\" — matches the R18\n // KPI-card-hover idiom from the Overview page. Single state for\n // both panels since they don't overlap; null when neither hovered.\n const [hoveredPanel, setHoveredPanel] = useState<'recent' | 'legend' | null>(null);\n // Round 80 / Loop: vendor-letter hover in the distribution chip. The\n // chip already names vendor mix (`C:5 G:3 ?:1`); hover a letter and\n // every node from OTHER vendors dims. Surfaces the breakdown spatially\n // without inventing a new pin slot. Stores the vendor `initial`\n // (single char or \"?\") that matches the tally key.\n const [hoveredVendor, setHoveredVendor] = useState<string | null>(null);\n // Round 88 / Loop: vendor filter pin. R80 added hover-to-dim on the\n // vendor letters but the filter released as soon as the cursor left\n // — vendor was the only filter dimension without a sticky variant\n // (R60 status, R63 group, R69 Cmd+K all support pin). This state\n // closes the gap. Same pattern as pinnedStatus / pinnedGroup:\n // per-tab sessionStorage persistence, Esc clears, activeVendor =\n // hoveredVendor ?? pinnedVendor so hover still wins for spot-\n // comparison while a pin is active.\n const [pinnedVendor, setPinnedVendor] = useState<string | null>(() => {\n if (typeof window === 'undefined') return null;\n try { return sessionStorage.getItem('anet-topo-pinned-vendor'); } catch { return null; }\n });\n const activeVendor = hoveredVendor ?? pinnedVendor;\n useEffect(() => {\n try {\n if (pinnedVendor) sessionStorage.setItem('anet-topo-pinned-vendor', pinnedVendor);\n else sessionStorage.removeItem('anet-topo-pinned-vendor');\n } catch {}\n }, [pinnedVendor]);\n // R89: stale-purge. If the fleet's vendor distribution changes such\n // that a previously-pinned vendor is gone (last node of that\n // vendor disconnected), clear the pin so the chip row doesn't\n // show \"filter: A · 0\" forever. Matches the same defensive purge\n // pattern pinnedGroup uses higher up — sessionStorage survives\n // reloads, but a stored value that no longer matches reality\n // would paint with an impossible filter.\n useEffect(() => {\n if (pinnedVendor && !vendorDist.some(v => v.initial === pinnedVendor)) {\n setPinnedVendor(null);\n }\n }, [pinnedVendor, vendorDist]);\n // Round 55 / Loop: hovering a legend status row dims nodes whose status\n // doesn't match. The legend was passive — \"what does this colour mean\".\n // Now it answers \"show me all of these\" the same way R8 group-focus\n // answers \"show me this team\". Three values match the legend rows.\n const [hoveredStatus, setHoveredStatus] = useState<'working' | 'idle' | 'offline' | null>(null);\n // Round 60 / Loop: sticky variant of `hoveredStatus`. R55 only filters\n // while the user is actively hovering the legend; for sweeping a fleet\n // you want to LOCK the filter. Each segment of the R31 pressure bar\n // toggles a pin — click again on the same segment to release. The node\n // opacity formula reads `activeStatus = hoveredStatus ?? pinnedStatus`\n // so hover transiently overrides a pin (handy for spot-comparison)\n // without nuking it.\n // Round 66 / Loop: pin survives a page reload via sessionStorage —\n // per-tab not per-browser (a new tab starts clean, intentionally).\n // The init reader validates against the known status set so a stale\n // / corrupt value can't paint the canvas with an impossible filter.\n const [pinnedStatus, setPinnedStatus] = useState<'working' | 'idle' | 'offline' | null>(() => {\n if (typeof window === 'undefined') return null;\n try {\n const v = sessionStorage.getItem('anet-topo-pinned-status');\n return (v === 'working' || v === 'idle' || v === 'offline') ? v : null;\n } catch { return null; }\n });\n const activeStatus = hoveredStatus ?? pinnedStatus;\n // R66: sync pinnedStatus into sessionStorage when it changes. Paired\n // with the matching effect for pinnedGroup higher up.\n useEffect(() => {\n try {\n if (pinnedStatus) sessionStorage.setItem('anet-topo-pinned-status', pinnedStatus);\n else sessionStorage.removeItem('anet-topo-pinned-status');\n } catch {}\n }, [pinnedStatus]);\n // R69: listen for Cmd+K palette pin actions. The palette can't reach\n // this component's state directly so it dispatches a CustomEvent;\n // we react by setting pinnedStatus / pinnedGroup. The palette also\n // writes to sessionStorage in lockstep, so the R66 init readers\n // would pick it up on reload — the event handler is what keeps the\n // currently-mounted canvas in sync without one.\n useEffect(() => {\n const onPin = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'clear') {\n // R90: extend the universal clear to vendor. R69 only cleared\n // status + group because pinnedVendor didn't exist yet; R88\n // shipped the third pin and R90 closes the palette gap so\n // \"Clear topology filters\" really means all of them.\n // R117: extend again to pinnedEdgeKey — R116 added the 4th\n // pin dim and this command must clear it too or the edge stays\n // bright in the canvas while every other pill clears.\n setPinnedStatus(null);\n setPinnedGroup(null);\n setPinnedVendor(null);\n setPinnedEdgeKey(null);\n } else if (detail.kind === 'clear-vendor') {\n // R90: granular vendor-only clear so power users can release\n // just the vendor without disturbing status/group pins.\n setPinnedVendor(null);\n } else if (detail.kind === 'clear-edge') {\n // R117: granular edge-only clear, mirror of R90 clear-vendor.\n setPinnedEdgeKey(null);\n } else if (detail.kind === 'status') {\n const v = detail.value;\n if (v === 'working' || v === 'idle' || v === 'offline') setPinnedStatus(v);\n } else if (detail.kind === 'group' && typeof detail.value === 'string') {\n setPinnedGroup(detail.value);\n } else if (detail.kind === 'vendor' && typeof detail.value === 'string') {\n // R108: palette pin-vendor support. R69 added pin-status, R88\n // added clickable vendor letters in the chip row, R90 added\n // granular clear-vendor — but the palette never gained a\n // PIN-vendor command. This branch listens for it; the matching\n // commands live in CommandPalette R108. The stale-purge\n // useEffect higher up already drops a pin whose vendor isn't\n // in the current distribution, so commands for an absent\n // vendor are harmless.\n setPinnedVendor(detail.value);\n }\n };\n window.addEventListener('anet:topo-pin', onPin);\n return () => window.removeEventListener('anet:topo-pin', onPin);\n }, []);\n // R74 listener for layout + view palette commands lives below\n // fitView's declaration — see further down in the file.\n const hoveredEdgeEndpoints = useMemo<Set<string> | null>(() => {\n // R116: compose hover ?? pin so pinning a row via click keeps the\n // endpoint ring + edge ladder lit after mouseleave.\n if (!activeEdgeKey) return null;\n const link = flowLinks.find(l => l.key === activeEdgeKey);\n return link ? new Set([link.from, link.to]) : null;\n }, [activeEdgeKey, flowLinks]);\n\n // --- Round 103 (issue #81): fullscreen + zoom + pan interaction layer ---\n // DIY native (no d3 / svg-pan-zoom): wrap the topology content in a single\n // <g transform> and drive it with wheel + pointer-drag. The panel <rect>\n // backdrop stays fixed so panning never reveals empty canvas. View state\n // {zoom,x,y} persists to localStorage (same sticky pattern as brand flag).\n const VIEWBOX_W = 1000;\n const VIEWBOX_H = 680;\n // tree mode can render a tall multi-row org chart — it needs a lower\n // zoom-out floor than ring/grid so the whole tree can fit (Vincent iter\n // 3: \"缩放最多 50%\" couldn't shrink the tree enough).\n const ZOOM_MIN = layout === 'tree' ? 0.3 : 0.5;\n const ZOOM_MAX = 4;\n const containerRef = useRef<HTMLDivElement>(null);\n const svgRef = useRef<SVGSVGElement>(null);\n const [view, setView] = useState({ zoom: 1, x: 0, y: 0 });\n const viewRef = useRef(view);\n const dragRef = useRef({ active: false, startX: 0, startY: 0, baseX: 0, baseY: 0 });\n const [isFullscreen, setIsFullscreen] = useState(false);\n // Round 184 / Loop: chrome reset button gets a one-shot icon spin\n // on click. resetSpinning is armed for 450ms (matches the CSS\n // animation duration in globals.css `.anet-reset-spin`); the SVG\n // icon picks up the class while armed. Same arming pattern as\n // R168/R169/R170/R171 smoothView flags but driving a CSS animation\n // instead of a CSS transition. prefers-reduced-motion blanket\n // override in R29 globals.css neutralises animation-duration\n // universally so the spin completes in 1ms when reduced motion is\n // requested.\n const [resetSpinning, setResetSpinning] = useState(false);\n const armResetSpin = () => {\n setResetSpinning(true);\n setTimeout(() => setResetSpinning(false), 460);\n };\n // Round 186 / Loop: chrome zoom-in / zoom-out buttons get a brief\n // icon pop on click — same click-feel idiom R184 added for the\n // reset spin, but a scale pulse rather than a rotation since +/−\n // icons rotating wouldn't read semantically. Tracks which button\n // is currently popping so only that icon picks up the class.\n // Arms for 240ms (CSS animation 220ms + 20ms buffer) so a quick\n // re-click can replay cleanly.\n // Round 249 / Loop: extend chromePopping to cover ring/grid layout\n // toggle + fullscreen button alongside the existing zoom-in/zoom-out.\n // Pre-R249 only the zoom buttons fired the R186 .anet-chrome-pop scale\n // pulse on click; the other chrome controls (layout toggle, fullscreen)\n // had no transient \"I just clicked\" signal — silent click → state change\n // with only the post-click visual difference to confirm action. R249\n // gives every clickable chrome control the same 220ms scale pulse, so\n // the whole strip speaks one consistent click vocabulary. Reset button\n // keeps its own R184 rotation animation (different gesture, semantic).\n // Node-size S/M/L keep their R171 layoutSwitching crossfade (already\n // gestural). Type union grows but the helper signature stays one-arg.\n type ChromePop = 'zoom-in' | 'zoom-out' | 'layout-ring' | 'layout-grid' | 'layout-tree' | 'fullscreen' | 'size-S' | 'size-M' | 'size-L';\n const [chromePopping, setChromePopping] = useState<ChromePop | null>(null);\n const popChrome = (which: ChromePop) => {\n setChromePopping(which);\n setTimeout(() => setChromePopping(prev => prev === which ? null : prev), 240);\n };\n // Issue #100: singleton chat popover. One alias at a time — clicking\n // another node swaps the target and the conversation switches in place.\n const [chatAlias, setChatAlias] = useState<string | null>(null);\n // Round 14 / Loop: one-shot click ripple — fades outward from the clicked\n // node so the click registers physically before the popover snaps in.\n // Pairs with the Round 11 chat-focus ring: ripple expands, ring locks on.\n // Keyed by ts so re-clicking the same node remounts the <circle> and the\n // SMIL <animate> replays. Cleared 600ms after click (longer than the\n // 500ms animation so a final frame at opacity 0 is still in the tree).\n const [clickRipple, setClickRipple] = useState<{\n ts: number; x: number; y: number; r0: number; color: string;\n } | null>(null);\n // #84: when a node is renamed while its chat popover is open, follow the\n // rename so the conversation keeps targeting a live alias.\n useEffect(() => {\n if (renameSignal && renameSignal.from === chatAlias) {\n setChatAlias(renameSignal.to);\n }\n // chatAlias intentionally omitted — only react to a new rename signal,\n // not to the user opening/closing the popover.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [renameSignal]);\n\n useEffect(() => { viewRef.current = view; }, [view]);\n\n // restore persisted view once on mount\n useEffect(() => {\n try {\n const raw = localStorage.getItem('anet-topo-view');\n if (raw) {\n const v = JSON.parse(raw);\n if (typeof v?.zoom === 'number') {\n setView({\n zoom: Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, v.zoom)),\n x: typeof v.x === 'number' ? v.x : 0,\n y: typeof v.y === 'number' ? v.y : 0,\n });\n }\n }\n } catch {}\n }, []);\n\n // Round 28 / Loop: first-paint auto-fit. Round 27's cellH bump means\n // dense grids (≥6-7 rows) overflow the 680-px viewBox at the natural\n // 100% zoom — a new user sees the topology with its bottom rows\n // clipped and no hint that there's more below. Auto-fit on first\n // paint (and only if the user has no persisted view) sets the\n // initial zoom so all content fits. Subsequent zoom changes persist\n // and override the auto-fit on reload; explicit reset (0 key) still\n // goes back to 100% so the gesture's \"reset to natural size\" semantic\n // is preserved.\n //\n // Capture pre-mount persistence in a useState initializer — the\n // existing `persist` effect (declared below) runs on first render\n // with the default {1,0,0} view and writes it to localStorage before\n // the auto-fit effect (deps on async-arriving sessions) gets a turn.\n // Reading localStorage from the effect would see that write and\n // skip the fit. The useState snapshot fires once, before any effects.\n const [hadPersistedViewOnMount] = useState<boolean>(\n () => typeof window !== 'undefined' && !!localStorage.getItem('anet-topo-view'),\n );\n const autoFitDoneRef = useRef(false);\n useEffect(() => {\n if (autoFitDoneRef.current) return;\n if (hadPersistedViewOnMount) {\n autoFitDoneRef.current = true;\n return;\n }\n if (sessions.length === 0) return;\n // iter5 (Vincent UX): tree auto-fit scales the org chart to fit BOTH\n // viewBox dimensions and CENTRES it. Pre-iter5 the shared grid path\n // only fit height and left-anchored the chart, so the tree hugged the\n // top-left corner with dead canvas down the right side.\n if (layout === 'tree') {\n if (!treeContentBox) return;\n const b = treeContentBox;\n if (b.w <= 0 || b.h <= 0) { autoFitDoneRef.current = true; return; }\n const pad = 24;\n const z = Math.max(ZOOM_MIN, Math.min(\n 1, (VIEWBOX_W - 2 * pad) / b.w, (VIEWBOX_H - 2 * pad) / b.h));\n setView({\n zoom: z,\n x: VIEWBOX_W / 2 - (b.x + b.w / 2) * z,\n y: VIEWBOX_H / 2 - (b.y + b.h / 2) * z,\n });\n autoFitDoneRef.current = true;\n return;\n }\n if (layout !== 'grid' || !gridContentBottom) return;\n if (gridContentBottom <= VIEWBOX_H) {\n autoFitDoneRef.current = true; // no overflow → no fit needed\n return;\n }\n const fitZoom = Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));\n setView({ zoom: fitZoom, x: 0, y: 0 });\n autoFitDoneRef.current = true;\n }, [layout, sessions.length, gridContentBottom, treeContentBox, hadPersistedViewOnMount]);\n\n // persist view\n useEffect(() => {\n try { localStorage.setItem('anet-topo-view', JSON.stringify(view)); } catch {}\n }, [view]);\n\n // track fullscreen state (button label + Esc-exit sync)\n useEffect(() => {\n const onFsChange = () => setIsFullscreen(document.fullscreenElement === containerRef.current);\n document.addEventListener('fullscreenchange', onFsChange);\n return () => document.removeEventListener('fullscreenchange', onFsChange);\n }, []);\n\n // wheel zoom — native non-passive listener so preventDefault() actually\n // stops the page from scrolling. Uses functional setState so the listener\n // never goes stale and can attach once.\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n const onWheel = (e: WheelEvent) => {\n // Round 23 / Loop: only zoom on Ctrl/Meta+wheel when inline; plain\n // wheel over the canvas keeps scrolling the page (the topo sits at\n // the top of /; trapping page scroll mid-page is the classic\n // \"scroll-jail\" anti-pattern). In fullscreen the canvas owns the\n // viewport so any wheel zooms — no page scroll to preserve. Pinch-\n // zoom on a trackpad surfaces as ctrlKey=true natively, which is\n // also what we want (zoom the canvas, not the browser).\n if (!isFullscreen && !e.ctrlKey && !e.metaKey) return;\n e.preventDefault();\n const rect = svg.getBoundingClientRect();\n const mx = ((e.clientX - rect.left) / rect.width) * VIEWBOX_W;\n const my = ((e.clientY - rect.top) / rect.height) * VIEWBOX_H;\n setView(prev => {\n // Round 104 (issue #81 follow-up): Vincent 实测 zoom 太灵敏. The\n // old factor was a fixed 1.15x per wheel *event* — fine for a\n // mouse notch but a trackpad fires dozens of events per gesture\n // so it compounded into huge jumps. Scale the factor by deltaY\n // magnitude with a small coefficient, then clamp the per-event\n // change so no single tick moves more than ~8%.\n const factor = Math.min(1.08, Math.max(0.926, Math.exp(-e.deltaY * 0.0006)));\n const nz = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, prev.zoom * factor));\n const ratio = nz / prev.zoom;\n // keep the point under the cursor stationary\n return { zoom: nz, x: mx - (mx - prev.x) * ratio, y: my - (my - prev.y) * ratio };\n });\n };\n svg.addEventListener('wheel', onWheel, { passive: false });\n return () => svg.removeEventListener('wheel', onWheel);\n // Re-attach when the fullscreen flag toggles so the handler's\n // closure picks up the new value (capture-by-closure means we'd\n // otherwise read the stale boolean forever).\n }, [isFullscreen]);\n\n // Round 21 / Loop: pan-aware cursor. Static `grab` gave no tactile cue\n // that the canvas was actually being dragged — \"grabbing\" while\n // dragRef.current.active = true tells the hand it's moving the\n // viewport. Mirroring dragRef.active into state is the only way to\n // re-render the SVG style; the ref itself doesn't trigger renders.\n const [isPanning, setIsPanning] = useState(false);\n const onPointerDown = (e: React.PointerEvent<SVGSVGElement>) => {\n if (e.button !== 0) return;\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n dragRef.current = {\n active: true,\n startX: e.clientX,\n startY: e.clientY,\n baseX: viewRef.current.x,\n baseY: viewRef.current.y,\n };\n setIsPanning(true);\n };\n const onPointerMove = (e: React.PointerEvent<SVGSVGElement>) => {\n const d = dragRef.current;\n if (!d.active) return;\n const svg = svgRef.current;\n if (!svg) return;\n const rect = svg.getBoundingClientRect();\n const dx = ((e.clientX - d.startX) / rect.width) * VIEWBOX_W;\n const dy = ((e.clientY - d.startY) / rect.height) * VIEWBOX_H;\n setView(prev => ({ ...prev, x: d.baseX + dx, y: d.baseY + dy }));\n };\n const onPointerUp = (e: React.PointerEvent<SVGSVGElement>) => {\n if (!dragRef.current.active) return;\n dragRef.current.active = false;\n setIsPanning(false);\n try {\n (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);\n } catch {}\n };\n\n // zoom buttons — zoom around the canvas center\n const zoomBy = (factor: number) => {\n setView(prev => {\n const nz = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, prev.zoom * factor));\n const ratio = nz / prev.zoom;\n const cx0 = VIEWBOX_W / 2;\n const cy0 = VIEWBOX_H / 2;\n return { zoom: nz, x: cx0 - (cx0 - prev.x) * ratio, y: cy0 - (cy0 - prev.y) * ratio };\n });\n };\n // Round 169 / Loop: discrete-zoom wrapper that arms the R168\n // smoothView flag before invoking zoomBy. Keyboard + / − and\n // the chrome zoom buttons fire once per gesture, so each\n // 1.2× step deserves the same 300ms glide R168 gives\n // reset/fit. Wheel zoom keeps calling zoomBy() directly —\n // every tick should respond live without lag. The arming\n // path is identical to resetView/fitView so all four\n // discrete-zoom surfaces share one timing constant.\n const zoomByDiscrete = (factor: number) => {\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n zoomBy(factor);\n };\n // Round 168 / Loop: smoothView is a one-shot flag that arms a\n // CSS transition on the viewport <g> transform attribute. Set\n // true when resetView/fitView fires; the inline style on the\n // viewport <g> reads this flag and conditionally applies\n // `transition: transform 300ms ease-out`. Auto-clears after\n // 350ms (just past the transition end) via setTimeout so\n // subsequent pan/wheel zoom stays snappy with no lag. Keeping\n // it as state (vs ref) so the React rerender re-applies the\n // style attribute synchronously when the transform also\n // changes — both attribute mutation and transition class\n // arrive in the same paint frame, which is what triggers the\n // browser to animate between the old and new transform values.\n const [smoothView, setSmoothView] = useState(false);\n const armSmoothView = () => {\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n };\n const resetView = () => {\n armSmoothView();\n setView({ zoom: 1, x: 0, y: 0 });\n };\n\n // Round 29 / Loop: `f` = fit-to-content. Shared by the Round 28\n // first-paint auto-fit effect and the keyboard handler so the math is\n // in one place. When content already fits at natural zoom, this is\n // effectively a \"recenter\" — `f` always lands on a known good view.\n // R168: arm smoothView so the transition glides instead of snapping\n // when the operator invokes fit-to-content via the hub click (R52),\n // chrome button, `f` key, or palette command.\n const fitView = useCallback(() => {\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n // iter5: tree fits-and-centres via its content bbox (sister to the\n // auto-fit effect); grid/ring keep the height-only fit-to-content.\n if (layout === 'tree' && treeContentBox && treeContentBox.w > 0 && treeContentBox.h > 0) {\n const b = treeContentBox;\n const pad = 24;\n const z = Math.max(ZOOM_MIN, Math.min(\n 1, (VIEWBOX_W - 2 * pad) / b.w, (VIEWBOX_H - 2 * pad) / b.h));\n setView({\n zoom: z,\n x: VIEWBOX_W / 2 - (b.x + b.w / 2) * z,\n y: VIEWBOX_H / 2 - (b.y + b.h / 2) * z,\n });\n return;\n }\n const zoom = !gridContentBottom || gridContentBottom <= VIEWBOX_H\n ? 1\n : Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));\n setView({ zoom, x: 0, y: 0 });\n }, [gridContentBottom, layout, treeContentBox]);\n\n // R74: listen for layout + view palette commands. Sister to R69's\n // pin listener — palette dispatches a CustomEvent, the reducer here\n // calls toggleLayout / fitView. Sits below fitView's declaration so\n // the deps list resolves cleanly.\n useEffect(() => {\n const onLayout = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'toggle') toggleLayout();\n };\n const onView = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'fit') fitView();\n };\n window.addEventListener('anet:topo-layout', onLayout);\n window.addEventListener('anet:topo-view', onView);\n return () => {\n window.removeEventListener('anet:topo-layout', onLayout);\n window.removeEventListener('anet:topo-view', onView);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fitView]);\n\n // Round 22 / Loop: keyboard zoom — +/= zoom in, - zoom out, 0 reset.\n // Round 29 / Loop: +f to fit content.\n // Listen on window so the user doesn't need to focus the SVG first,\n // but only act when no text input has focus (otherwise typing \"-\" in\n // a chat would zoom the topology — surprising and bad). Modifier keys\n // pass through so Cmd/Ctrl combos still hit their owners. The button\n // titles below document these shortcuts so users discover them.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.ctrlKey || e.metaKey || e.altKey) return;\n const ae = document.activeElement as HTMLElement | null;\n if (ae) {\n const tag = ae.tagName;\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || ae.isContentEditable) return;\n }\n if (e.key === '+' || e.key === '=') { zoomByDiscrete(1.2); e.preventDefault(); }\n else if (e.key === '-' || e.key === '_') { zoomByDiscrete(1 / 1.2); e.preventDefault(); }\n else if (e.key === '0') { resetView(); e.preventDefault(); }\n else if (e.key === 'f' || e.key === 'F') { fitView(); e.preventDefault(); }\n // Round 32 / Loop: `l` toggles ring|grid. The vim-style `g l` route\n // (Audit Log) requires a preceding `g` within 1500ms; a bare `l`\n // outside that window is free for topology use.\n else if (e.key === 'l' || e.key === 'L') { toggleLayout(); e.preventDefault(); }\n // Round 62 / Loop: Esc clears the R60/R61 pinned status filter so\n // users have a universal-cancel keyboard out. Esc on an open chat\n // is owned by ChatPopover (which only mounts when chatAlias is\n // set), so this handler is effectively scoped to \"no chat open\".\n // We additionally guard on chatAlias to be explicit — if the chat\n // closed mid-cycle, the pin can still be cleared on the next Esc.\n // R63: extends to pinnedGroup too. Clears WHATEVER pin is active\n // (one Esc collapses all topology pins) so the keyboard escape\n // route stays a single key, not \"Esc maybe Esc again\".\n else if (e.key === 'Escape' && !chatAlias && (pinnedStatus || pinnedGroup || pinnedVendor || pinnedEdgeKey)) {\n // R88/R116: extend the universal-cancel to vendor + edge too.\n // One Esc collapses every topology pin (matches R62/R63's\n // \"single key, not Esc-maybe-Esc-again\" promise).\n if (pinnedStatus) setPinnedStatus(null);\n if (pinnedGroup) setPinnedGroup(null);\n if (pinnedVendor) setPinnedVendor(null);\n if (pinnedEdgeKey) setPinnedEdgeKey(null);\n e.preventDefault();\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n // zoomBy / resetView are stable wrt setView callback; fitView changes\n // with gridContentBottom so deps list catches it. R62 adds chatAlias\n // and pinnedStatus so the Escape branch reads fresh state (re-binding\n // the listener on these state changes is sub-ms — cheaper than refs).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fitView, chatAlias, pinnedStatus, pinnedGroup, pinnedVendor, pinnedEdgeKey]);\n\n const toggleFullscreen = () => {\n const el = containerRef.current;\n if (!el) return;\n if (document.fullscreenElement) {\n document.exitFullscreen?.();\n } else {\n const req =\n el.requestFullscreen ||\n (el as unknown as { webkitRequestFullscreen?: () => void }).webkitRequestFullscreen;\n req?.call(el);\n }\n };\n\n // #209 R27 (Vincent msg 545+547 — TopoGraph \"中间太小,往左右扩\"):\n // drop the unique `max-w-6xl mx-auto` cap. Every other section on\n // the Overview page (KPI cards, agent grid, Recent Activity, etc.)\n // is full-width; only TopoGraph was self-limiting to 1152 px,\n // leaving ~70 px slack on each side of the typical (~1290 px main\n // area) viewport. Now matches the rest of the page — the inner SVG\n // layout is already resize-aware (ring/grid/tree all re-fit on\n // container width change), so widening the card naturally spreads\n // the node cluster. Less is more: one fewer arbitrary constraint.\n // Picked option A over a 1536 px cap (option B) per Vincent msg 547.\n return (\n <section className=\"w-full mb-8\">\n {/* Round 299 / Loop: title block bottom margin mb-3 (12px) →\n mb-4 (16px). After R298 tightened the title-block internal\n gap (12→10px) packing brand-logo + kicker + h2 into a more\n cohesive editorial unit, the outer bottom margin to the\n topology canvas should breathe more — denser title block\n + tighter follow-on space read as cramped. Bumping the\n gap below the title block lets the canvas frame\n itself more clearly as the main visual subject. 16px is\n the conventional SaaS-product section-header-to-content\n baseline (Stripe / Linear / Vercel marketing). Geometry:\n adds 4 CSS px between title block bottom and topology\n frame top — small but cumulative with the R298 internal\n tighten, the title block reads as a *deliberate* badge\n rather than a casually-stacked label. */}\n {/* Round 334 / Loop: header outer wrapper mobile gap-3 → gap-2.5\n (12 px → 10 px). The wrapper is `flex flex-col` on narrow\n viewports (title-block above, chip-row below) — at mobile\n its vertical gap was 12 px while the title-block internal\n (R298) and chip-row internal (R328) both rhythm at 10 px.\n R334 unifies the OUTER vertical gap to 10 px so mobile\n stacked layout matches the established gap-rhythm tier\n (title-block 10 / chip-row 10 / chrome 8 — R298/R328/R326).\n Desktop `sm:flex-row` is unaffected: in row mode the gap-3\n would have applied horizontally but the wrapper relies on\n `sm:justify-between` for left/right anchoring (gap is then\n decorative only between the two flex-grow groups). Net\n mobile bump: 2 px tighter vertical breathing between\n title-block + chip-row. Geometry-safe — topo-overlap-test\n reads SVG-internal bbox, not header layout. */}\n <div\n className={`flex flex-col gap-2.5 sm:flex-row sm:items-end sm:justify-between mb-4 px-1${isFullscreen ? ' hidden' : ''}`}\n data-topo-header-row\n data-topo-header-hidden={isFullscreen ? 'true' : 'false'}\n >\n {/* Round 267 / Loop: title block adopts leading-tight on both\n kicker and h2 for a tighter editorial-style rhythm. Pre-\n R267 the kicker used Tailwind's compound `text-xs` (line-\n height 16px = 1.33 ratio) and the h2 used `text-lg` (line-\n height 28px = 1.56) — adequate but loose for a kicker→\n title sequence. R267 applies `leading-tight` (1.25) to\n both, shrinking effective line-heights to 15px + 22.5px =\n 37.5px total title block height (vs 44px pre-R267 → ~15%\n more compact) while preserving the cap-top to descender\n visual proportions. Result: kicker and title read as a\n single typographic unit rather than two loosely-stacked\n lines. data-topo-section-kicker / data-topo-section-title\n attrs make both probe-able. */}\n {/* P0 (Vincent 5222 / 通信龙 R278 dispatch): integrate sleep2agi\n brand logo into the title-block.\n Why HTML title-block instead of SVG canvas: the SVG region\n has a high-frequency concurrent-editor edit race with codex\n (see project_dashboard_concurrent_editors). The title block\n is HTML-side, low edit traffic, and gives the brand mark\n first-glance presence above the topology canvas — the exact\n \"Twitter screenshot 一眼看出 sleep2agi\" outcome Vincent 5215\n asked for.\n Logo construction: inline SVG so currentColor inherits\n from the parent text-{color} class — theme-aware without\n an extra asset request. Same crescent geometry as\n public/sleep2agi-logo.svg. 36×36 px so it's clearly\n readable at a16:9 Twitter crop, paired with the kicker +\n h2 typography via flex layout. text-cyan-300 in cyber +\n text-emerald-600 in light keeps the moon brand-aligned\n with the canvas accent palette. */}\n {/* Round 298 / Loop: title-block gap-3 (12px) → gap-2.5 (10px).\n The R297 codex-bundle brand-logo at 36×36 paired with the\n R285 tracking-widest kicker + R286 tracking-tight h2 forms\n an editorial \"logo + title\" unit. At gap-3 the 12px between\n logo right-edge and text left-edge reads as two separate\n elements — visual spacing slightly outpaces the relationship\n density (logo IS the brand mark for the kicker's\n \"Network Topology\"). gap-2.5 (10px) is the tight-pack\n convention SaaS-product header logos use (Stripe / Vercel /\n Linear top-nav logo + product name spacing), grouping logo\n + title as one read. Geometry: 36 + 10 + ~120 (title text\n width) = 166px total title-block width vs 168px pre-R298 —\n no measurable layout shift, just a deliberate tighter\n grouping. */}\n {/* Round 554 / Loop — title-block wrapper picks up `group` so\n the H2 below can subscribe to `group-hover:tracking-tighter`.\n Pairs with R548/R549 brand-logo hover gestures: cursor\n sweeping anywhere across the title cluster fires the brand\n logo's scale-105 + rotate-6 + breath ↔ AND tightens the\n H2's tracking from -0.025em → -0.05em.\n Makes the title-block read as one coherent hover cluster —\n brand mark provides the loud gesture (scale + rotate), H2\n provides the subtle editorial gesture (kerning tighten).\n data-topo-section-titleblock-group attr surfaces the gate\n for tests. */}\n {/* Round 706 / Loop — title-block wrapper joins the nested-\n envelope respiration pattern, mirroring the canvas-brand-\n pair (R704/R705) at the title-block scope. Closes the\n ENVELOPE SYMMETRY across the topo canvas's two structural\n text regions:\n canvas-brand-pair: 13s × 15s envelopes (R705 + R704)\n title-block: 11s envelope (R706 — this round)\n 11s coprime with all 3 inner trio cadences (5, 6, 10) — no\n common factor with any → no phase lock with inner anchors,\n so the outer envelope always offsets gently against the\n inner breaths. Triply-nested respiration: 3 inner anchors\n (brand-logo 5s + kicker 6s + H2 10s) inside an outer\n wrapper envelope (11s).\n\n Tighter alpha range than canvas-brand-pair envelopes\n (0.92 ↔ 1, ~8%) — title-block carries the section's\n primary text identity; even subtle dimming on the H2 +\n kicker headline would feel uncomfortable. 8% range reads\n as \"barely-perceptible aliveness\", not flicker.\n\n Implementation: HTML wrapper, so CSS animation works\n directly on the parent <div>. No inline opacity to clobber.\n Group-hover Tailwind utilities on descendants are unaffected\n — wrapper opacity stays continuous; only descendants\n (brand-logo/kicker/H2) get group-hover state changes via\n their own classes. prefers-reduced-motion: reduce\n neutralizes inside .anet-topo-title-block-envelope-breath\n CSS class, sibling to R699/R702/R703/R704/R705 a11y guards.\n\n Rolodex now 13 anchors, 11-tier ladder:\n 3s row hot · 4s hub idle · 5s brand logo\n 6s watermark text · 6s kicker · 7s crescent inner\n 8s recent title · 8s legend title\n 9s zoom-level readout · 10s H2\n 11s title-block envelope ← this round\n 13s crescent wrapper envelope\n 15s watermark wrapper envelope\n\n data-topo-section-titleblock-envelope-breath attr surfaces\n the cadence for tests. */}\n {/* Round 742 / Loop — title-block joins entrance family as 2nd\n one-shot-mount member. Staggered after R740 canvas entrance:\n canvas plays 0-600ms; title-block delayed 200ms then plays\n 200-700ms for 500ms ease-out. The 100ms gap between canvas\n settle (600ms) and title-block settle (700ms) creates a\n perceived \"shake out\" — canvas arrives, then the heading\n writes itself into place. Slide from translateY(-4px) +\n opacity 0.7 → translateY(0) opacity 1.\n\n Gated by reducedMotion at JSX level (sibling pattern to R740).\n See globals.css R742 block for cadence rationale. */}\n <div className={`anet-topo-title-block-envelope-breath${reducedMotion ? '' : ' anet-topo-title-block-entrance'} group flex items-center gap-2.5`} data-topo-section-titleblock-group data-topo-section-titleblock-envelope-breath=\"11s\" data-topo-section-titleblock-entrance={reducedMotion ? 'false' : 'true'}>\n {/* Round 297 / Loop: brand-logo color picks up the 200ms ease-\n out transition. Pre-R297 the moon glyph had theme-\n conditional color (cyber #67e8f9 cyan ↔ light #0d9488\n teal) but no transition declaration — flipping themes\n made the brand mark snap to its new color in one frame,\n jarring against the surrounding R245/R246/R247/R253/R254\n family that smooths every neighbouring fill / stroke /\n filter at the same 200ms cadence. Adding the transition\n brings the brand mark into the coordinated theme-toggle\n choreography: title block + canvas + chrome all ease as\n one unit. CSS color transition is well supported on the\n `color` property (which currentColor inside the masked\n <rect> inherits), so no SMIL trick needed. */}\n {/* Round 316 / Loop: brand-logo width/height 36 → 40 for\n slightly stronger first-glance presence. After R298\n tightened the title-block flex gap (12→10px) and R299\n widened the bottom margin (12→16px), the logo can hold\n more visual weight to balance the kicker+h2 stack on\n its right. 40px is 11% larger by edge, ~23% by area —\n visible bump on a Twitter-screenshot crop without\n overpowering the h2 at text-lg/font-semibold (R286).\n viewBox 32×32 unchanged so the inner crescent geometry\n scales proportionally. */}\n {/* Round 548 / Loop — title-block brand logo gains subtle\n hover-scale gesture. Pre-R548 the 40×40 crescent was\n fully static — no hover affordance. R548 adds hover:\n scale-105 (Tailwind 4 emits as `scale: 1.05`) so the\n brand mark gently responds to attention as the user's\n cursor sweeps across the title block. 5% scale is\n intentionally subtle (vs R350 chrome icon hover-scale-\n 110): the brand logo is a passive identity mark, not\n an interactive control, so the gesture stays small.\n cursor: default to clarify non-clickability (the SVG\n isn't a button; just a brand element).\n transition-transform duration-200 ease-out matches the\n dashboard's R350-family hover-scale cadence so the\n brand logo's response shares the same motion vocabulary\n as the chrome strip's icon scales. transform-gpu hint\n promotes the SVG to its own compositor layer for crisp\n edges during the tween.\n Brand-mark delight gesture family (1 anchor):\n R548 title-block brand logo hover:scale-105\n The crescent at canvas top-left (data-topo-brand-\n canvas-mark) and the watermark at bottom-left (data-\n topo-brand-watermark) are intentionally LEFT STATIC —\n both have pointerEvents:none and exist as ambient\n decoration with their own breath/recede dynamics\n (R519/R525/R526/R528). The title-block logo is the\n ONLY brand surface that's a candidate for hover affordance,\n since it sits in the chrome-band where the cursor\n naturally passes during normal use. */}\n {/* Round 549 / Loop — extends R548 (hover:scale-105) with a\n subtle hover:rotate-6 rotation. Pairs scale + rotate on\n the brand mark, same idiom as R547 added to the pill ×\n close buttons (scale-110 + rotate-12) but at gentler\n amounts: 105% vs 110% scale and 6° vs 12° rotation —\n brand marks want restraint vs interactive close buttons.\n\n The crescent-moon shape (curved cutout via mask) reads\n visually distinct as it rotates — the asymmetric cutout\n swings into a fresh angle, telegraphing \"this brand mark\n is alive without being loud\". The 6° landing lands the\n moon's cusp pointing slightly NE rather than straight up,\n a small but legible reveal of the shape's geometry.\n\n Tailwind 4 emits BOTH `scale: 1.05` AND `rotate: 6deg`\n as INDIVIDUAL CSS properties (not a combined transform\n string — see R547 banked pattern). The className keeps\n transition-transform so both axes ease at the same 200ms\n cadence; transform-gpu hint stays so the compositor\n promotes both axes to GPU layers.\n\n data-topo-brand-logo-hover-rotate attr surfaces the\n landing rotation for tests. Pair with R548's\n data-topo-brand-logo-hover-scale attr — both attrs\n advertise the dual-axis hover signature. */}\n {/* Round 553 / Loop — title-block brand logo gains subtle\n idle opacity breath (~0.92 ↔ 1, 5s ease-in-out cycle).\n 5th anchor in the 呼吸感 breath family, slotting into\n the ascending cadence ladder between hub idle (4s) and\n watermark (6s):\n row hot 3s\n hub idle 4s\n brand logo 5s ← this round\n watermark 6s\n crescent 7s\n Composes cleanly with R548 hover:scale-105 + R549\n hover:rotate-6 — opacity, scale, and rotate are\n independent CSS properties; the moon keeps breathing\n as it scales and rotates on hover. Layered effect\n reads as \"this brand mark is alive even before you\n touch it, and lights up further on hover\".\n Reduced-motion gate: component-side `!reducedMotion`\n toggles the className (canonical TopoGraph breath\n pattern); R29 globals.css blanket provides a\n defense-in-depth fallback (animation-duration →\n 0.001ms under prefers-reduced-motion: reduce).\n data-topo-brand-logo-breath attr exposes the gate\n state for tests. */}\n {/* Round 557 / Loop — brand logo gains 4th hover axis:\n hover:brightness-110 (filter). Adds a chromatic axis\n to the brand-mark hover signature alongside R548\n scale, R549 rotate, R553 idle breath:\n R548 hover:scale-105 transform-scale\n R549 hover:rotate-6 transform-rotate\n R553 idle breath (5s) opacity (animation)\n R557 hover:brightness-110 filter ← this round\n All 4 axes ride on INDEPENDENT CSS properties (scale,\n rotate, opacity, filter) — they compose freely without\n clobbering each other. The cyan/teal crescent gains a\n soft +10% brightness boost on hover, layered on top of\n the existing scale + rotate lift + ongoing idle breath.\n Why +10% (vs more aggressive 125/150): brand mark wants\n restraint. The eye reads the brightness shift as \"this\n mark lights up under attention\" without crossing into\n \"this mark is now glowing\".\n Implementation: className extends transition-transform\n → transition-[transform,filter] so the brightness\n tweens at the same 200ms ease-out cadence as the scale\n + rotate axes — one motion-coherent 3-property hover\n lift on the className tier (plus the inline color 200ms\n transition for theme-toggle eases).\n Brand-mark family axis count: 4 hover-state axes\n cleanly factor across:\n geometry (scale + rotate)\n paint (opacity breath + brightness on hover)\n Cluster reads as \"alive, lifting, and lighting up under\n attention\" — three independent gesture vocabularies on\n one surface.\n data-topo-brand-logo-hover-brightness attr surfaces\n the landing value for tests. */}\n <svg\n width=\"40\" height=\"40\" viewBox=\"0 0 32 32\" aria-hidden\n /* R604 — brand 书生 logo gains hover-drop-shadow as 5th\n axis (4 hover + 1 always-on breath). Tailwind v4 arbitrary\n drop-shadow with currentColor — the glow inherits the\n brand-mark's own colour (light teal #0d9488 / cyber cyan\n #67e8f9 per inline style.color). 8px radius reads as a\n \"lit up under attention\" halo without overwhelming the\n 40×40 logo footprint.\n\n Tailwind v4 emits filter utilities through CSS-var system\n (--tw-drop-shadow, --tw-brightness) that combine in the\n `filter` shorthand — so hover:brightness-110 + hover:\n drop-shadow-[0_0_8px_currentColor] stack instead of\n clobbering each other. Same banked R564/R570 \"halo +\n glow\" pattern, now at the brand-mark scope.\n\n Brand-logo hover signature now 5 axes:\n R548 hover:scale-105 transform-scale\n R549 hover:rotate-6 transform-rotate\n R553 idle breath (5s) opacity (always-on)\n R557 hover:brightness-110 filter brightness\n R604 hover:drop-shadow filter drop-shadow ← this round\n\n Existing inline transition already covers 'filter 200ms\n ease-out' (R557 cadence) — both brightness AND drop-\n shadow ease at the same beat. */\n /* R683 — replaced Tailwind `hover:brightness-110 hover:drop-\n shadow-[0_0_8px_currentColor]` with the new `.anet-topo-\n brand-logo-mark` CSS class (defined in globals.css) which\n applies a MULTI-LAYER filter on :hover — near 8px + far\n 16px drop-shadow at currentColor + brightness(1.10). This\n brings the brand mark into the multi-layer halo family\n using the same near + far layered glow vocabulary that\n 41 other surfaces share. Tailwind utility composition for\n multi-drop-shadow doesn't stack cleanly (R659 detour),\n so a custom CSS class wins over the cascade.\n Transform classes (hover:scale-105, hover:rotate-6) +\n breath className preserved unchanged. */\n className={`anet-topo-brand-logo-mark shrink-0 transition-[transform,filter] duration-200 ease-out hover:scale-105 hover:rotate-6 transform-gpu${!reducedMotion ? ' anet-topo-brand-logo-breath' : ''}`}\n data-topo-brand-logo\n data-topo-brand-logo-hover-scale=\"1.05\"\n data-topo-brand-logo-hover-rotate=\"6deg\"\n data-topo-brand-logo-hover-brightness=\"1.1\"\n data-topo-brand-logo-hover-drop-shadow=\"0_0_8px_currentColor;0_0_16px_currentColor\"\n data-topo-brand-logo-halo-layers=\"2\"\n data-topo-brand-logo-breath={!reducedMotion ? 'true' : 'false'}\n style={{\n color: isLight ? '#0d9488' : '#67e8f9',\n cursor: 'default',\n // R557 — extend transition list to include filter (and\n // re-spec transform for cadence parity) so the new\n // hover:brightness-110 axis eases at 200ms alongside\n // the existing color 200ms (theme-toggle ease) and the\n // className-based hover:scale-105 / hover:rotate-6.\n // Inline transition is a shorthand and overrides the\n // className's transition-[transform,filter] — listing\n // all axes here ensures the eased property set covers\n // color (theme) + transform (scale + rotate) + filter\n // (brightness) at uniform 200ms ease-out.\n transition: 'color 200ms ease-out, transform 200ms ease-out, filter 200ms ease-out',\n }}\n >\n {/* Round 731 / Loop — SVG <title> a11y child for the title-\n block brand logo. Sibling treatment to R730 watermark/\n panel-title a11y sweep. Brand logo is always-visible\n (title-block top-of-canvas); screen readers now identify\n it as the brand mark. */}<title>sleep2agi · brand logo</title>\n <mask id=\"s2a-titleblock-moon-mask\">\n <rect width=\"32\" height=\"32\" fill=\"black\" />\n <circle cx=\"16\" cy=\"16\" r=\"13\" fill=\"white\" />\n <circle cx=\"20.5\" cy=\"14.5\" r=\"11\" fill=\"black\" />\n </mask>\n <rect width=\"32\" height=\"32\" fill=\"currentColor\" mask=\"url(#s2a-titleblock-moon-mask)\" />\n </svg>\n <div>\n {/* Round 285 / Loop: kicker tracking-wider → tracking-widest.\n An uppercase eyebrow label at text-xs benefits from\n wider letter-spacing — Tailwind's tracking-widest is\n 0.1em vs tracking-wider's 0.05em. At small caps,\n 0.1em is the conventional SaaS-eyebrow spacing (Stripe,\n Linear, Vercel marketing kicker style); 0.05em reads\n closer to body-text density. The widened spacing\n telegraphs \"this is a label, not a sentence\" without\n changing color or size, deepening the editorial\n hierarchy R267 set up between kicker and h2. */}\n {/* Round 296 / Loop: kicker text-gray-600 → text-gray-500\n for slightly better legibility on the dark cyber backdrop.\n gray-600 (#4b5563) read as a near-invisible label on\n cyber (the canvas + side rail are deeply dark); gray-500\n (#6b7280) lifts the eyebrow into the band where the eye\n registers it as a deliberate label vs swallowed text,\n while still sitting clearly below text-white h2 title\n in the visual hierarchy. Tailwind classes are theme-\n neutral so the bump applies to both themes; in light\n theme gray-500 is still appropriate as a muted-label\n shade on white bg. Hierarchy preserved: title-white >\n kicker-gray-500 (R285 tracking-widest still in place). */}\n {/* Round 300 / Loop (milestone): kicker picks up font-medium\n (500). Pre-R300 the eyebrow used default font-weight\n (400/normal). At text-xs (12px) + uppercase + R285\n tracking-widest, default-weight letters read slightly\n under-authored — uppercase at small sizes wants a touch\n more stroke weight to feel like a deliberate label.\n font-medium (500) is the conventional SaaS-eyebrow\n weight (Stripe / Vercel / Linear marketing kicker\n style — same family that informed R285's tracking-\n widest decision). Stays clearly below the h2's font-\n semibold (600) + larger size so hierarchy is preserved:\n h2 (text-lg/600) > kicker (text-xs/500/gray-500).\n R300 marks the milestone of 25 rounds (R275-R300) of\n continuous TopoGraph polish + codex's Vincent 5215/\n 5222 logo asset+integration work. */}\n {/* Round 555 / Loop — kicker \"Network Topology\" gains group-\n hover affordance via the R554 wrapper's `group` flag,\n closing the title-block cluster's hover coverage at 3\n surfaces (brand logo + H2 + kicker).\n Picks up the small-label SPREAD direction (R554 banked\n \"small labels SPREAD on hover / large headlines TIGHTEN\"\n — kicker is xs uppercase, definitely a small label) plus\n a color brighten (text-gray-500 #6b7280 → text-gray-400\n #9ca3af).\n Spread: tracking-widest (0.1em rest) → 0.13em hover —\n +30% kerning bump. At text-xs (12px) the per-gap shift\n is 1.2px → 1.56px (+0.36px/gap), legible without\n overshooting the rest's tracking-widest editorial base.\n Color: text-gray-500 → text-gray-400 — one tier lighter,\n same idiom as R296 (kicker rest tone-up from gray-600 to\n gray-500), now extended at the hover-state tier.\n transition-[letter-spacing,color] duration-200 ease-out\n matches the 200ms cadence of R554 H2 ls + the rest of\n the hover-ls family (R344/R345/R347/R351/R420/R427/R431/\n R432/R434/R527/R539).\n Title-block cluster signature post-R555 (3 surfaces):\n brand logo loud scale + rotate + breath\n (R548/R549/R553)\n H2 subtle tracking-tighter\n (R554, editorial-tighten)\n kicker subtle tracking-spread + color lift\n (R555, data-spread) ← this round\n Two of the three surfaces are typographic; the brand\n logo carries the geometric+chromatic motion. Cluster\n reads as ONE coherent hover unit through three\n independent gesture vocabularies.\n data-topo-section-kicker-hover-tracking + -hover-color\n attrs expose the landing values for tests. */}\n {/* Round 699 / Loop: kicker gains a slow at-rest breathing\n fade — opacity 0.78 ↔ 1 at 6s ease-in-out infinite, via\n `.anet-topo-kicker-breath` (globals.css). Pre-R699 the\n kicker sat at a fixed alpha; the H2 below has a live\n breathing pulse cycle (`anet-current-step-pulse-kf` family\n cousins exist) but the kicker eyebrow itself was static.\n R699 adds a 6s respiration — visible but never agitating.\n When hovered, the group-hover:text-gray-400 (R685 halo +\n R555 tracking-expand) takes over, so the breath only\n registers at rest. prefers-reduced-motion neutralizes the\n keyframe (animation: none) inside the CSS class.\n\n Geometry-safe: pure opacity animation, no transform / size /\n filter blur change → bounding box stable → topo-overlap-test\n unaffected at both grid + ring. First respiratory anchor\n on the title-block — sibling to the chip-row gesture grammar\n (R697/R698 wrapper halos) but operating on a different\n axis (rest-state aliveness vs hover gesture).\n\n data-topo-section-kicker-breath=\"6s\" attr surfaces the\n cadence for tests. */}\n <div className=\"anet-topo-kicker-breath text-xs uppercase text-gray-500 group-hover:text-gray-400 tracking-widest group-hover:tracking-[0.13em] transition-[letter-spacing,color] duration-200 ease-out leading-tight font-medium\" data-topo-section-kicker data-topo-section-kicker-hover-tracking=\"0.13em\" data-topo-section-kicker-hover-color=\"text-gray-400\" data-topo-section-kicker-halo-layers=\"2\" data-topo-section-kicker-breath=\"6s\">Network Topology</div>\n {/* Round 286 / Loop: title 'Command mesh' adopts tracking-tight\n (-0.025em) to complement R285 kicker tracking-widest. Wide\n eyebrow + tight headline is the conventional editorial\n pairing — Apple / Stripe / Vercel / Linear all use this\n dual-axis typographic rhythm. The kicker's 0.1em pushes\n letters APART (label feel); the headline's -0.025em pulls\n them TOGETHER (deliberate, designed-headline feel). At\n text-lg (18px) the shift is ~0.45px per gap — small but\n cumulatively legible across 12 characters. font-semibold\n (600) stays — tracking-tight does the heavy lifting for\n the editorial register. */}\n {/* Round 554 / Loop — H2 \"Command mesh\" gains group-hover-\n gated tracking-tighter via the wrapper's R554 `group`\n flag. Pre-R554 the H2 was fully static (R286 tracking-\n tight only at rest). R554 adds an editorial-tighten\n gesture: when cursor sweeps anywhere across the title\n cluster (brand logo OR title text), the headline tightens\n from -0.025em → -0.05em.\n Inverts the typical hover-letter-spacing direction:\n small labels (chip counts, panel titles, edge digits)\n SPREAD on hover → \"data telegraphing\"\n large headlines (h2 Command mesh)\n TIGHTEN on hover → \"editorial emphasis\"\n Both directions are coherent design language — small\n data wants spacing for legibility; large headlines want\n tightening for designed-headline polish. Same idiom as\n the conventional editorial pairing of \"wide kicker +\n tight headline\" R285/R286 set up (kicker spreads 0.1em\n tracking-widest; headline tightens -0.025em tracking-\n tight) — R554 deepens that pairing's tighten side at\n the hover-state tier.\n At text-lg (18px) the shift is -0.45px → -0.9px per\n gap (~5.4px total tightening across \"Command mesh\" 12\n chars). Subtle but legible when the cursor sweeps in.\n transition-[letter-spacing] duration-200 ease-out\n matches the 200ms hover-ls cadence used at R344/R345/\n R347/R351/R420/R427/R431/R432/R434/R527/R539 family\n anchors.\n data-topo-section-title-hover-tracking attr surfaces\n the landing tracking class for tests. */}\n {/* Round 556 / Loop — H2 \"Command mesh\" gains a 2nd\n editorial-emphasis axis: group-hover:font-bold paired\n with R554's group-hover:tracking-tighter. Both lifts\n fire on the same R554 wrapper's `group` flag (hover\n anywhere in the title cluster → BOTH H2 axes intensify\n simultaneously).\n H2 hover signature post-R556 (2 typographic axes\n intensify together):\n rest font-semibold 600 + tracking-tight -0.025em\n hover font-bold 700 + tracking-tighter -0.05em\n Editorial emphasis through TWO axes — heavier AND\n tighter on hover. Mirrors the conventional \"designed-\n headline emphasis\" idiom (heavier + tighter = more\n authoritative; the eye reads both axes as intensifying\n the same semantic).\n Hover-fw family extension (6 anchors now):\n R416 chip-row count digit (chip group-hover)\n R420 chrome zoom-level (hover)\n R425 hub-center digit (hub hover)\n R520 +N more flows footer (recent panel hover)\n R521 chrome nodeSize S/M/L (inactive hover)\n R556 title-block H2 (cluster group-hover) ← this round\n Transition list extends to include 'font-weight 200ms\n ease-out' alongside the existing 'letter-spacing'\n 200ms cadence. data-topo-section-title-hover-fw attr\n surfaces the landing weight for tests. */}\n {/* Round 702 / Loop — H2 \"Command mesh\" gains at-rest breathing\n fade, completing the title-block respiratory trio:\n R553 brand-logo 5 s\n R699 kicker 6 s\n R702 H2 10 s ← this round (slowest in trio)\n The H2 is the heaviest visual element in the title-block;\n a slower 10 s respiration reads as \"calm, steady, anchoring\".\n Tighter alpha range (0.88 ↔ 1, ~12%) than the kicker\n (0.78 ↔ 1, ~22%) — H2 carries more visual weight, so a\n gentler breath keeps it from looking flickery. Faster\n cadence would feel anxious on heavy text.\n\n The title-block trio now reads as a layered chord: the\n small brand mark at 5 s, the tertiary eyebrow at 6 s, the\n main headline at 10 s. Three independent rhythms phase-\n drift, so the title-block never settles into a static\n snapshot — there's always something subtly shifting.\n\n Group-hover takes precedence via the existing\n group-hover:font-bold + group-hover:tracking-tighter\n transitions; R684 CSS descendant halo lights up on hover\n too. So the breath only registers at rest, identical to\n the R699 kicker pattern.\n\n Geometry-safe: pure opacity animation, no transform / size\n / filter blur change → bounding box stable → topo-overlap-\n test grid + ring unaffected. New 10 s tier extends the\n respiratory rolodex (3/4/5/6/6/7/8/8/10).\n\n prefers-reduced-motion: reduce → animation: none\n (handled in the .anet-topo-section-title-breath CSS class,\n sibling to R699 .anet-topo-kicker-breath guard).\n data-topo-section-title-breath=\"10s\" attr surfaces the\n cadence for tests. */}\n <h2 className=\"anet-topo-section-title-breath text-lg text-white font-semibold group-hover:font-bold leading-tight tracking-tight group-hover:tracking-tighter transition-[letter-spacing,font-weight] duration-200 ease-out\" data-topo-section-title data-topo-section-title-hover-tracking=\"tracking-tighter\" data-topo-section-title-hover-fw=\"700\" data-topo-section-title-halo-layers=\"2\" data-topo-section-title-breath=\"10s\">Command mesh</h2>\n </div>\n </div>\n {/* Round 328 / Loop: chip-row strip wrapper gap 2 → 2.5\n (8px → 10px between chips). Pre-R328 the inter-chip gap\n sat at 8px while each chip's own horizontal padding was\n `px-2.5` (10px) — the chip's internal space was wider\n than the gap between chips, so adjacent chips read as\n \"touching\" rather than \"neighboring\". Bumping the gap\n to 10px makes inter-chip = chip-padding, visually\n balancing the rhythm. Sibling treatment to R298 title-\n block `gap-2.5` (brand-logo ↔ kicker/title) and R326\n chrome strip `gap-2` extension family. Layout strip:\n R298 title-block gap-2.5 (top of canvas)\n R328 chip-row gap-2.5 (below title) ← NEW\n R326 chrome gap-2 (bottom of canvas)\n Risk-bounded: chip-row uses `flex-wrap`; if it wraps to\n a new line on narrow viewports the row-gap also bumps to\n 10px, which only helps mobile rhythm. Topo-overlap-test\n is HTML-overlay-only at this scope; SVG viewBox layout\n untouched. */}\n <div className=\"flex flex-wrap items-center gap-2.5 text-xs\">\n {/* Issue #87: ring | grid layout toggle — segmented control,\n persisted to localStorage anet-topo-layout.\n Round 163 / Loop: bring the layout toggle into the R154\n chrome-button focus convention. Pre-R163 the buttons had\n aria-pressed + transition-colors but no focus-visible ring\n (browser default — invisible against dark canvas) and the\n inactive variant only nudged text from gray-500 → gray-400\n on hover with no bg tint, so the hover-to-click affordance\n was barely perceptible.\n\n R163 closes both gaps to match R154:\n focus-visible:ring-2 focus-visible:ring-cyan-400/60\n → keyboard users see exactly which segment is focused\n hover:bg-cyan-500/5 (inactive)\n → mouse hover shows a faint cyan ghost of the active\n state, signalling 'click to switch'\n hover:bg-cyan-500/20 (active)\n → active button responds too, says 'still clickable'\n data-topo-chrome-layout for testability symmetric with the\n R154 chrome buttons (data-topo-chrome-zoom-in / -reset /\n -fullscreen etc).\n\n Round 260 / Loop: chip-row semantic gap — Layout toggle is\n the only CONTROL in the chip row; everything that follows\n (working / online / pressure / vendor letters / active-\n links / filter pills / freshness) is READ-ONLY display.\n Pre-R260 all 8 children sat at uniform gap-2 (8px) — the\n spatial signal read as \"8 separate things\" instead of\n \"1 control + 7 display\". mr-1 (4px) on the Layout toggle\n stacks on top of the parent flex's gap-2 (8px) for an\n effective 12px gap before the first status chip — same\n law-of-proximity pattern R255 applied to the bottom-right\n chrome strip (fleet vs view groups). data-topo-chrome-\n layout-trailer marks the boundary surface for the gap\n probe. */}\n {/* Round 268 / Loop: Layout toggle border unified with the\n chrome strip's theme-aware borderColor. Pre-R268 the\n wrapper + Grid button's internal divider used hardcoded\n `border-gray-500/25` (pale gray, fixed in both themes)\n while the bottom-right chrome strip (nodeSize, zoom)\n used pal.containerBorder (cyber #26262b dark indigo ↔\n light #e3e6eb pale gray). Visible mismatch in cyber\n theme: Layout toggle border read as pale gray while\n chrome strip borders read as darker indigo — two\n different border colors on visually-analogous\n segmented controls. R268 replaces the hardcoded class\n with inline pal.containerBorder + a border-color\n transition, so the Layout toggle (a) matches the chrome\n strip border color and (b) joins the canvas-wide\n theme-ease vocabulary (eases on cyber↔light toggle\n instead of snapping). Same change applied to the Grid\n button's border-l on line ~1493. */}\n {/* Round 329 / Loop: Layout toggle wrapper `mr-1` → `mr-0.5`\n to compensate for R328's chip-row gap bump (8 → 10 px).\n R260 designed for an effective 12 px gap between the\n Layout CONTROL and the first DISPLAY chip (working /\n online / etc): mr-1 (4 px) + chip-row gap-2 (8 px) = 12.\n R328 widened chip-row to gap-2.5 (10 px), pushing the\n effective gap to 14 px — semantically still \"control\n vs display\" but louder than R260 specified.\n R329 dials mr-1 → mr-0.5 (2 px) so the effective gap\n returns to 12 px (mr-0.5 + chip-row gap-2.5 = 2 + 10).\n Keeps the law-of-proximity semantic R260 designed\n while honoring R328's wider baseline rhythm. data-topo-\n chrome-layout-trailer attr unchanged — it still marks\n the boundary surface for the gap probe. */}\n {/* Round 375 / Loop: Layout-toggle wrapper rounded-md → rounded-\n lg (6 → 8 px). Extends the corner-radius cascade family\n to the chrome-strip layout-toggle wrapper:\n R330 canvas wrapper rounded-xl 12 px\n R331 SVG panels rx=10 10 px\n R332 minimap container rounded-lg 8 px\n R375 Layout-toggle wrapper rounded-lg 8 px (this round)\n Pre-R375 the wrapper at rounded-md (6 px) was the only\n chrome-strip container still using the smaller corner\n radius — both R330 outer wrapper and R332 minimap sit at\n ≥ 8 px, so the Layout toggle's 6 px stood out as a\n tighter corner against the family. R375 brings it into\n the rounded-lg tier where the minimap already lives.\n Pure paint change — overflow-hidden still clips the\n inner buttons' bg-cyan-500/15 tints; no layout shift.\n R268 border-color + 200ms transition + R329 mr-0.5 +\n data-topo-chrome-layout-trailer all preserved. */}\n {/* Round 707 / Loop — chrome Layout wrapper joins the\n respiratory family at 17 s. Extends chrome strip from\n single-anchor (R703 zoom-level 9 s, the chrome's only\n data display) to a 2-anchor chrome respiratory voice:\n 9 s zoom-level readout (R703 — data tier)\n 17 s Layout wrapper envelope (R707 — control tier)\n The two chrome anchors occupy DISTINCT functional tiers\n (data display vs control group), so distinct cadences\n read as different roles rather than competing rhythms.\n\n 17 s is prime → coprime with every other cadence in\n the rolodex (3/4/5/6/7/8/9/10/11/13/15). Never phase-\n locks with any other anchor. New slowest tier among the\n HTML chrome respiratory anchors (zoom-level was 9 s;\n the slower 13 s and 15 s tiers are SVG-based on the\n canvas-brand-pair envelopes).\n\n Tighter alpha range (0.94 ↔ 1, ~6%) — tightest yet,\n looser than the chrome zoom-level (9 s at 15%) but\n tighter than the title-block envelope (11 s at 8%).\n The chrome Layout buttons are interactive controls\n with active-state cyan tint; even ~10% dimming on the\n inner active button color would feel jarring. 6% =\n \"barely-perceptible aliveness\" on a control group.\n\n Hover gate via CSS `:has(button:hover)` — when user\n hovers any inner Ring/Grid button, the wrapper's R697\n halo (cyan filter) takes precedence visually; pause the\n breath simultaneously via `:has(button:hover)` →\n animation: none so the halo reads crisp without\n underlying alpha drift. Sibling pattern to R703 hover\n data-attr gate and R704/R705 recede gates — third\n gate-off mechanism in the family vocabulary.\n\n Rolodex now 14 anchors, 12-tier ladder:\n 3 s row hot · 4 s hub idle · 5 s brand logo\n 6 s watermark text · 6 s kicker · 7 s crescent inner\n 8 s recent title · 8 s legend title\n 9 s zoom-level readout · 10 s H2\n 11 s title-block envelope · 13 s crescent wrapper envelope\n 15 s watermark wrapper envelope\n 17 s Layout wrapper ← this round\n\n data-topo-chrome-layout-trailer-breath attr surfaces\n the cadence for tests. */}\n <div\n className=\"anet-topo-chrome-layout-trailer-breath mr-0.5 inline-flex rounded-lg border overflow-hidden\"\n style={{ borderColor: pal.containerBorder, transition: 'border-color 200ms ease-out' }}\n role=\"group\"\n aria-label=\"Topology layout\"\n data-topo-chrome-layout-trailer\n data-topo-chrome-layout-radius=\"rounded-lg\"\n data-topo-chrome-layout-trailer-breath=\"17s\"\n /* R697 — wrapper-level multi-layer halo on inner button hover\n (via CSS :has() in globals.css). 53rd anchor in family. */\n data-topo-chrome-wrapper-halo-family=\"layout\"\n >\n <button\n onClick={() => { popChrome('layout-ring'); selectLayout('ring'); }}\n onMouseEnter={() => setHoveredLayout('ring')}\n onMouseLeave={() => setHoveredLayout((prev) => prev === 'ring' ? null : prev)}\n aria-pressed={layout === 'ring'}\n title=\"Ring layout (l to cycle)\"\n data-topo-chrome-layout=\"ring\"\n data-topo-chrome-layout-active={layout === 'ring' ? 'true' : 'false'}\n data-topo-chrome-layout-ring-popping={chromePopping === 'layout-ring' ? 'true' : 'false'}\n /* R674 — Ring button joins multi-layer halo family.\n Inline filter overrides the Tailwind hover:brightness-\n [1.15] (R597) with 2-layer drop-shadow at pal.legendAccent\n tint, 2+4 stride. Sibling pattern to R673 zoom-in/-out. */\n data-topo-chrome-layout-ring-halo-layers={hoveredLayout === 'ring' ? '2' : '0'}\n // Round 196 / Loop: add active: (pressed) state for tactile\n // click feedback — bridges mouse-down → R186/R184/R192 pop-on-\n // release. Selected variant deepens to cyan-500/25 (one tier\n // above its hover:cyan-500/20); unselected variant deepens\n // to cyan-500/15 (one tier above its hover:cyan-500/5).\n // Round 249 / Loop: chrome-pop joins the click handshake —\n // mouse-down deepens cyan press (R196), release fires\n // .anet-chrome-pop on the button (R249) AND triggers\n // toggleLayout if state changes. The pop runs even when\n // clicking the already-active layout (no state change),\n // confirming the click was received either way.\n /* Round 306 / Loop: focus-visible:ring-2 → ring-1 unifies\n with the rest of the chrome button family. Pre-R306\n the Layout toggle (Ring/Grid) used `focus-visible:\n ring-2` (2px outline) while nodeSize S/M/L (line\n ~7291), zoom -/+ (~7328/~7395), reset (~7417), and\n fullscreen (~7477) all use `focus-visible:ring-1`\n (1px outline). Two different focus-ring widths on\n visually-analogous chrome controls — same R268\n border-color unification + R288 icon-stroke\n unification family. Reducing Ring/Grid to ring-1\n lets all 7 chrome buttons share one focus-ring\n weight; cyan-400/60 + ring-inset retained. The\n R163/R196 hover/active deeps + R249 chrome-pop\n click feedback continue unchanged. */\n // R351: hover:tracking-wide extends the R344/R345/R347\n // hover-letter-spacing family to a 4th surface (chrome-\n // strip Ring/Grid pair). transition-colors className\n // dropped in favour of an inline transition spec that\n // bundles bg/color (150ms ease) + letter-spacing\n // (200ms ease-out) — Tailwind's transition-colors\n // doesn't list letter-spacing, so without this the\n // hover:tracking-wide would snap. Sibling change on\n // the Grid button below.\n // Round 492 / Loop — add `active:scale-95` press feedback\n // alongside R196's `active:bg-cyan-500/25` color-deepen.\n // Pre-R492 the chrome-strip Ring/Grid buttons had color\n // tactile (deeper cyan on mouse-down) + R249 chrome-pop\n // on release, but no transform during the press itself —\n // the button stayed planted between mouse-down and pop.\n // Adding `active:scale-95` (5% compression) on the\n // pressed pseudo-state, with `transform 150ms ease-out`\n // bundled into the inline transition list, gives haptic-\n // like push-back feedback. The press-down (down to 95%\n // scale) eases in over 150ms in sync with the bg/color\n // deepen; the release auto-springs back to scale-100 via\n // the same transition, then R249's anet-chrome-pop class\n // overlays the release-pop. Matching `transform-gpu`\n // promotes the layer so the scale doesn't trigger\n // layout/paint thrash. Sibling change on Grid below.\n /* Round 522 / Loop — extends R521's typography-preview\n idiom (chrome nodeSize hover:font-medium 400 → 500) to\n the Ring/Grid layout toggle's inactive variant. Pre-\n R522 the inactive Ring/Grid had `hover:text-cyan-300\n hover:bg-cyan-500/5` (R270 color + bg previews of the\n active state) but no typography preview — the active\n variant uses `font-medium` (fw 500), inactive sat at\n default fw 400 even on hover. R522 adds `hover:font-\n medium` to the inactive Ring/Grid so the rest-vs-hover\n transition previews the typography state the click\n would commit to, matching the click commits's locked\n weight.\n font-weight 150ms appended to the transition list\n matching the existing 150ms color/bg cadence at this\n button — when hover lifts color (gray-400 → cyan-300)\n + bg (transparent → cyan-500/5) + fw (400 → 500), all\n 3 ease at the same 150ms beat.\n Hover-fw family extension (6 anchors): R416/R420/R425/\n R520/R521/R522. R522 closes the chrome toggle group\n typography preview at the last remaining toggle —\n layout (Ring/Grid). After R521 (nodeSize) + R522\n (layout), every multi-state chrome toggle has hover-\n fw preview on its inactive variant.\n data-topo-chrome-layout-hover-preview-fw=\"500\" attr\n on inactive button exposes the polish for tests. */\n /* Round 552 / Loop — chrome active-variant gains hover:\n text-cyan-200, lifting text one brightness tier alongside\n the existing hover:bg-cyan-500/20 bg deepen. Coordinated\n 4-anchor edit (replace_all touched 4 sibling lines sharing\n the identical active-variant className substring):\n Ring (this line) layout === 'ring'\n Grid (line ~2097) layout === 'grid'\n S/M/L (line ~12635) nodeScale === v\n Fscrn (line ~13030) isFullscreen\n Pre-R552 the active variant's hover state only deepened bg\n (cyan-500/15 → /20); text stayed planted at cyan-300. The\n inactive variant already lifts text on hover (text-gray-400\n → text-cyan-300). R552 brings parity: active variant lifts\n text one tier brighter (cyan-300 → cyan-200) on hover,\n mirroring the inactive variant's \"text brightens on hover\"\n gesture at the next brightness step.\n Brightness ladder snapshot (cyan):\n cyan-400 brand chrome focus ring\n cyan-300 active-variant rest ←─┐\n │ +1 tier on hover\n cyan-200 active-variant hover ←─┘ (this round)\n Pure paint axis (text color); bbox/geometry unchanged.\n transition-colors already in the class list so the cyan-\n 300 → cyan-200 swap eases at the existing 200ms cadence.\n hover-color brighten family extension at the chrome strip\n active-variant scope; sibling to the inactive variant's\n R163/R178/R179/R270 hover:text-cyan-300 idiom. */\n className={`px-2.5 py-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide hover:brightness-[1.15] active:scale-95 transform-gpu ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}\n data-topo-chrome-layout-hover-preview-fw={layout === 'ring' ? null : '500'}\n data-topo-chrome-layout-ring-brightness-hover=\"1.15\"\n /* R597 — Ring/Grid segmented buttons gain hover:\n brightness-[1.15] (37+38th anchors in per-element\n brightness family, 6+7th HTML). Paired-anchor round\n mirroring R596's zoom +/- closure at the second\n segmented control. Same segmented-unity rule\n (brightness = pure paint, no geometry break).\n Inline transition list extends with 'filter 150ms\n ease' so brightness eases under the existing R522\n 150ms beat that bg/color/fw share (letter-spacing\n + transform on their own 200ms/150ms cadences).\n R557 banked pattern: when an element has both\n inline transition AND new transition-driven axis,\n extend the INLINE list — not the className. */\n style={{\n transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out, transform 150ms ease-out, font-weight 150ms ease, filter 150ms ease',\n filter: hoveredLayout === 'ring' ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n }}\n >\n Ring\n </button>\n <button\n onClick={() => { popChrome('layout-grid'); selectLayout('grid'); }}\n onMouseEnter={() => setHoveredLayout('grid')}\n onMouseLeave={() => setHoveredLayout((prev) => prev === 'grid' ? null : prev)}\n aria-pressed={layout === 'grid'}\n title=\"Grid layout (l to cycle)\"\n data-topo-chrome-layout=\"grid\"\n data-topo-chrome-layout-active={layout === 'grid' ? 'true' : 'false'}\n data-topo-chrome-layout-grid-popping={chromePopping === 'layout-grid' ? 'true' : 'false'}\n /* R674 sibling — Grid button mirrors Ring above. Together\n they close the Layout segmented-control halo coverage. */\n data-topo-chrome-layout-grid-halo-layers={hoveredLayout === 'grid' ? '2' : '0'}\n // Round 196 / Loop: R163 layout-toggle Grid variant picks up\n // press-state — same tier pattern as Ring above.\n // Round 249 / Loop: chrome-pop on click — same as Ring.\n // Round 306 / Loop: focus-visible:ring-2 → ring-1 sibling\n // change to Ring above — unifies focus-ring width across\n // all chrome buttons.\n // R351 sibling — Grid button picks up hover:tracking-wide\n // + inline transition spec. Same vocabulary as Ring.\n // R492 sibling — Grid button picks up active:scale-95\n // press feedback + transform in transition list. Same\n // vocabulary as Ring above.\n /* Round 522 sibling — Grid button mirrors Ring above:\n inactive variant gains `hover:font-medium` typography\n preview + font-weight 150ms in inline transition list.\n Same idiom, same family (R522 chrome layout). */\n className={`px-2.5 py-1 border-l focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide hover:brightness-[1.15] active:scale-95 transform-gpu ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}\n data-topo-chrome-layout-hover-preview-fw={layout === 'grid' ? null : '500'}\n data-topo-chrome-layout-grid-brightness-hover=\"1.15\"\n /* Round 268 / Loop: Grid button's left border (the\n internal divider between Ring and Grid) picks up\n pal.containerBorder, matching the wrapper change at\n line ~1460 and the chrome strip's segmented borders\n (nodeSize, zoom). The R268 transition-colors className\n used to carry the border-color ease; R351 unfolds the\n transition list into the inline spec below so the\n letter-spacing tween rides alongside without snapping\n the border-color flip — border-color 200ms ease-out\n keeps R268's theme-toggle smoothness intact.\n R492 adds `transform 150ms ease-out` so active:scale-95\n eases smoothly.\n R597 sibling — Grid button mirrors Ring above:\n hover:brightness-[1.15] + filter 150ms ease in\n the inline transition list. */\n style={{\n borderColor: pal.containerBorder,\n transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out, transform 150ms ease-out, font-weight 150ms ease, filter 150ms ease',\n filter: hoveredLayout === 'grid' ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n }}\n >\n Grid\n </button>\n {/* #170 tree-view MVP — third segment in the layout control.\n Org-chart view: 总指挥/副指挥 root → teams → members.\n Mirrors the Grid button's border-l divider + active/\n inactive paint vocabulary; the heavier R-series polish\n (halo layers, hover-fw preview attrs) is deferred to the\n RFC-017 formal version. */}\n <button\n onClick={() => { popChrome('layout-tree'); selectLayout('tree'); }}\n onMouseEnter={() => setHoveredLayout('tree')}\n onMouseLeave={() => setHoveredLayout((prev) => prev === 'tree' ? null : prev)}\n aria-pressed={layout === 'tree'}\n title=\"Tree layout — org chart (l to cycle)\"\n data-topo-chrome-layout=\"tree\"\n data-topo-chrome-layout-active={layout === 'tree' ? 'true' : 'false'}\n data-topo-chrome-layout-tree-popping={chromePopping === 'layout-tree' ? 'true' : 'false'}\n className={`px-2.5 py-1 border-l focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide hover:brightness-[1.15] active:scale-95 transform-gpu ${layout === 'tree' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'} ${chromePopping === 'layout-tree' ? ' anet-chrome-pop' : ''}`}\n style={{\n borderColor: pal.containerBorder,\n transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out, transform 150ms ease-out, font-weight 150ms ease, filter 150ms ease',\n filter: hoveredLayout === 'tree' ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n }}\n >\n Tree\n </button>\n </div>\n {/* R79: working + online count chips become hover affordances —\n extends R77's chip-hover pattern to status counts. Hover the\n working chip → setHoveredStatus('working') so the R55 dim\n mechanic kicks in (same as hovering the SVG legend \"working\"\n row, just a different surface for the same gesture).\n The online chip uses the same setHoveredStatus('idle') path\n when there's idle but no working — falling back to working\n if any working node exists; \"online\" without a single bucket\n isn't part of the R55 type set, so this chip routes to the\n dominant online sub-tier instead of inventing a new state.\n Cursor only flips when there's anything to highlight. */}\n {/* R82: pin-mirror. R60 pressure-bar segments visualise the\n pinned status via an inset boxShadow; R61 legend rows via a\n concentric r=8 ring; the chip-row chips next to them did\n not — so a user who pinned via Cmd+K (R69) or the legend\n had no chip-row signal that \"working\" was the current\n filter. Mirror the pressure-bar treatment here so all\n status surfaces sing in unison. The online chip mirrors\n for idle pins (working ⊆ online; the routing in\n onMouseEnter already treats online as the idle fallback). */}\n {(() => {\n // R113 / Loop: extend the R97-R102/R101 alias-list tooltip\n // sweep to the remaining chip-row affordances. R79 made\n // these chips hoverable; their generic \"Hover to highlight\"\n // titles never said WHICH nodes match. Compute the alias\n // lists once for both chips to share.\n const workingAliases = onlineNodes.filter(s => s.status === 'working').map(s => s.alias);\n const onlineAliases = onlineNodes.map(s => s.alias);\n /* Round 565 (50-round milestone) / Loop — extend the\n inspection-overrides-encoding family to a 7th anchor at\n the chip-row chip scope. Computes the hovered alias's\n status tier (same idiom as R562/R563) so each chip's\n className can include the \"lit\" bg/border treatment when\n operator hovers a node matching its tier.\n Family progression — 7 anchors complete:\n R484 recent-row timestamp alias hover\n R485 edge particle opacity alias hover\n R486 minimap dot opacity alias hover\n R561 group-label + ants-gate member-alias hover\n R562 legend-swatch r + glow member-alias status match\n R563 pressure-seg brightness member-alias status match\n R565 chip-row chip bg/border member-alias status match ← this round\n Status-tier-match feedback now SATURATES across panel\n chrome at 4 surfaces simultaneously:\n minimap dot (R486)\n legend swatch (R562)\n pressure-seg (R563)\n chip-row chip (R565) ← this round\n When operator hovers a 'working' node alias, ALL FOUR\n surfaces light up in green; 'idle' → all four in teal;\n 'offline' → all four in slate. The eye gets 4-way\n confirmation of \"your inspected node is in this tier\"\n across every persistent status-reference surface. */\n const hoveredAliasTierKey: 'working' | 'idle' | 'offline' | null = (() => {\n if (!hoveredAlias) return null;\n const s = onlineNodes.find(n => n.alias === hoveredAlias)\n ?? offlineNodes.find(n => n.alias === hoveredAlias);\n if (!s) return null;\n if (s.status === 'working') return 'working';\n return offlineNodes.includes(s) ? 'offline' : 'idle';\n })();\n const isWorkingChipLit = hoveredAliasTierKey === 'working';\n const isOnlineChipLit = hoveredAliasTierKey === 'idle';\n const truncate = (list: string[]) => {\n const head = list.slice(0, 8).join(', ');\n const tail = list.length > 8 ? ` + ${list.length - 8} more` : '';\n return head + tail;\n };\n const workingTitle = workingCount === 0\n ? undefined\n : pinnedStatus === 'working'\n ? `${truncate(workingAliases)} — pinned, Esc to clear`\n : `${truncate(workingAliases)} — hover highlights, click to pin`;\n // R140: online chip title gains a \"click to open /nodes\" tail\n // when interactive. R79 made the cursor pointer-shaped but\n // wired nothing; R136 + R139 closed the same lie on two\n // sibling chips by wiring real actions. The online chip\n // can't pin a single status (online = working + idle,\n // not a single pinnedStatus value), so a pin idiom would\n // be semantically wrong. /nodes is the natural full-list\n // destination — same \"click chip for the full list\" idiom\n // the active-links chip uses (R136 → /messages).\n const onlineTitle = onlineNodes.length === 0\n ? undefined\n : pinnedStatus === 'idle'\n ? `${truncate(onlineAliases)} — pinned, Esc to clear`\n : `${truncate(onlineAliases)} — hover highlights · click to open /nodes`;\n return (\n <>\n <span\n // Round 201 / Loop: the working chip joins the\n // \"chip's hover state deepens its OWN identity colour\"\n // family that R193 opened (active-links chip) and R195\n // extended (recent-panel footer). Pre-R201 hovering the\n // working chip fired R55 canvas dim + chip-row highlight\n // but the chip itself stayed at bg-green-500/10 — cause\n // silent, effect loud. R201 deepens its OWN green hue\n // (10→15 bg, 20→30 border) only when clickable.\n // transition-colors duration-200 blends the swap to\n // match R193's timing on the active-links chip.\n /* Round 232 / Loop: HTML chip row picks up the\n tabular-nums info-density treatment R224-R230\n established on the SVG side. The chip text reads\n \"{N} working\"; when workingCount crosses 9→10\n the leading digit's width shift propagates the\n trailing ' working' label right by the digit-vs-\n control glyph delta, and the chip's right edge\n re-flows because the parent is inline flex. The\n Tailwind `tabular-nums` utility sets font-variant-\n numeric: tabular-nums, locking the digit width\n so the chip text + chip width stay stable across\n all counter values. Sibling chips (online,\n active-links) get the same treatment in this\n round so the three-chip row reads uniformly.\n 7th surface in the info-density tabular-nums\n sweep — and the first on the HTML side\n (previous 6 were SVG <text>/<tspan>). */\n /* Round 398 / Loop: chip-row chips gain hover translateY\n (-1px) lift on the CLICKABLE variant only (workingCount\n > 0 here / onlineNodes.length > 0 below / activeLinks\n > 0 deeper). Pre-R398 the chips brightened bg + border\n on hover (R201) but didn't lift — only their clickable\n siblings (filter pin pills R397, recent rows R143,\n legend rows R144) acknowledged cursor entry with a\n translate-y. R398 closes the chip-row by extending\n the same gesture to the static header chips, gated\n on the clickable role so empty chips (which have\n no role=\"button\") stay planted at their R205\n opacity-50 receded paint. transition-transform\n + duration-200 + ease-out + transform-gpu added\n alongside existing transition-colors so the lift\n and the color tween share rhythm.\n Gesture-vocabulary table (post-R398):\n recent-signal row -1 px (R143)\n legend row -1 px (R144)\n group cluster box fill+sw lift (R142)\n filter pin pills -1 px (R397)\n chip-row chips -1 px (R398, this round)\n Empty chips: no lift. Pin-mirror chips: no\n conflict (R180 inset double-ring is a box-shadow\n not a transform). new data-chip-hover-lift attr\n surfaces the lift surface for tests. */\n // R414: chip-row chips gain `group` so inner unit\n // span brightens via group-hover:opacity-100 — sibling\n // to R355 filter pin pill inner-span hover-brighten.\n // Hover-brighten family extends from filter pills to\n // chip-row chips at the inner-span scope.\n // Round 494 / Loop — chip-row working chip joins the\n // active:scale-95 press-feedback family (R492 Ring/Grid +\n // R493 chrome-strip rest). Gated on the clickable branch\n // (workingCount > 0) — when the chip is a placeholder\n // at count=0, scale-95 stays off to match the existing\n // R398 hover-lift conditional. Composes with hover:-\n // translate-y-px for the same lift-and-compress\n // tactile signature R493 brought to reset/fullscreen.\n /* R565: when isWorkingChipLit (operator hovers a working\n node), chip stays in its \"lit\" bg-green-500/15 +\n border-green-500/30 state at rest. Same visual as\n hover; member-alias-matching pins the lift without\n requiring cursor on the chip. */\n className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${\n workingCount > 0\n ? `${isWorkingChipLit ? 'bg-green-500/15 border-green-500/30' : 'bg-green-500/10 border-green-500/20'} text-green-300 hover:bg-green-500/15 hover:border-green-500/30 hover:-translate-y-px active:scale-95`\n : 'bg-green-500/10 text-green-300 border-green-500/20'\n }`}\n data-chip-hover-lift={workingCount > 0 ? 'true' : 'false'}\n data-chip-group-hover-brighten=\"true\"\n data-working-chip-member-alias-lit={isWorkingChipLit ? 'true' : 'false'}\n data-working-chip\n data-working-chip-aliases={workingAliases.join(',')}\n data-pin-mirror={pinnedStatus === 'working' ? 'true' : 'false'}\n data-working-chip-clickable={workingCount > 0 ? 'true' : 'false'}\n data-working-chip-empty={workingCount === 0 ? 'true' : 'false'}\n title={workingTitle}\n role={workingCount > 0 ? 'button' : undefined}\n tabIndex={workingCount > 0 ? 0 : undefined}\n aria-pressed={workingCount > 0 ? (pinnedStatus === 'working') : undefined}\n // Round 180 / Loop: pin-mirror inset rings now ease in/out\n // instead of snapping. R165 added this transition to the\n // pressure-bar segments; R180 closes the smooth-pin-mirror\n // family across the three remaining chip-row pin chips\n // (working / online / vendor letter). The visual is small\n // — a 1-2 px inset double ring — but the eye catches the\n // pop on every pin/unpin without the ease.\n // Round 201 / Loop: inline transition list now also covers\n // background-color + border-color so the R201 hover tint\n // eases. Tailwind transition-colors on the className would\n // be overridden by this inline declaration, so we splice\n // the colour properties directly into the existing\n // R180 box-shadow transition.\n // Round 205 / Loop: chip recedes to opacity 0.5 when its\n // tier is empty (workingCount=0). Pre-R205 \"0 working\"\n // displayed at full bg-green-500/10 chrome — visually\n // indistinguishable from \"12 working\". Eye got zero\n // empty-tier signal. R205 mirrors R204's legend count\n // recede-on-empty pattern at the chip-row scope. Inline\n // transition list extends `opacity 200ms ease-out` so\n // the crossing-zero ease matches R201's bg/border\n // timing. Empty-state combines with R139's clickable=\n // false + tooltip-undefined: visual + interactive +\n // affordance all say \"this tier has nothing to act on\".\n /* R687 — working chip joins the multi-layer halo family\n using its OWN per-tier color (green-400 #4ade80,\n matching the pinnedStatus boxShadow inset ring).\n Pre-R687 the chip had 4 hover axes (bg/border swap\n + R136 translate-y + scale-95 press) but no paint-\n axis halo. R687 adds 2-layer drop-shadow at 2+4\n stride, alpha 80/40 — gated on hoveredStatus ===\n 'working' && workingCount > 0 (the existing\n hoveredStatus state plus empty-state guard).\n R689 — extends gate to ALSO fire on pinnedStatus\n === 'working'. Pinned chips become \"permanently\n lit\" until unpinned, telegraphing the active\n filter state more strongly than the inset ring\n alone. Coherent with R664 status filter pin pill\n halo (which lights up the post-pin filter pill);\n R689 closes the symmetry by lighting the SOURCE\n chip too while pin holds. Sibling change on online\n chip below. */\n data-working-chip-halo-layers={(hoveredStatus === 'working' || pinnedStatus === 'working') && workingCount > 0 ? '2' : '0'}\n style={{\n cursor: workingCount > 0 ? 'pointer' : undefined,\n opacity: workingCount === 0 ? 0.5 : 1,\n boxShadow: pinnedStatus === 'working' ? 'inset 0 0 0 1px #4ade80, inset 0 0 0 2px rgba(255,255,255,0.45)' : undefined,\n filter: (hoveredStatus === 'working' || pinnedStatus === 'working') && workingCount > 0\n ? 'drop-shadow(0 0 2px #4ade8080) drop-shadow(0 0 4px #4ade8040)'\n : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',\n }}\n onMouseEnter={() => { if (workingCount > 0) setHoveredStatus('working'); }}\n onMouseLeave={() => setHoveredStatus(prev => prev === 'working' ? null : prev)}\n // R139: the title hover-text has been promising \"click to\n // pin\" since R79 but no onClick was ever wired. The cursor:\n // pointer at line 1363 set up the same lie R136 fixed on\n // the active-links chip. Wire it now: click toggles the\n // status pin to 'working', composing with R60 (pressure-\n // bar segments) and R61 (legend rows) — three different\n // surfaces that all toggle the same pinnedStatus. boxShadow\n // pin-mirror at line 1364 already reflects the state; aria-\n // pressed now exposes it for screen readers too.\n onClick={() => {\n if (workingCount > 0) setPinnedStatus(prev => prev === 'working' ? null : 'working');\n }}\n onKeyDown={(e) => {\n if (workingCount === 0) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedStatus(prev => prev === 'working' ? null : 'working');\n }\n }}\n >\n {/* Round 337 / Loop: split working chip into digit +\n \" working\" unit, with the unit at opacity-70.\n Extends the R333/R335/R336 chip-internal-hierarchy\n arc from SVG (panel headers) and pin-chip prefix\n surfaces into HTML chip-row chips. Recurring\n pattern: small label spans demote, value stays\n prominent. data-working-chip-unit exposes the\n span for tests. */}\n {/* Round 362 / Loop: digit picks up font-semibold\n (fw 500 → 600) for within-chip weight tier. The\n chip's outer className stays at font-medium (R313\n data-weight baseline); the digit overrides to\n semibold so it reads heavier than its \" working\"\n unit (which keeps fw 500 + R338 opacity-70).\n Joins the R333-R341 chip-internal-hierarchy arc\n at the chip-count scope. Sibling edits on the\n online + active-links chip digits below. data-\n working-chip-digit attr exposes the digit span. */}\n {/* Round 539 / Loop — chip-row digit gains group-hover:\n tracking-wide alongside the existing R362 group-\n hover:font-bold. Pre-R539 the chip digit lifted\n only on the font-weight axis (600 → 700 on chip\n hover); R539 adds the kerning axis (tracking\n normal → tracking-wide ≈ 0.025em ≈ 0.3px on a 12px\n digit) so hover lifts BOTH typography axes\n together — same idiom R420/R517 establish at the\n chrome zoom-level (letter-spacing + fontWeight\n hover delta) and R531/R530 mirror at the panel\n label scope. transition-[font-weight] extends to\n transition-[font-weight,letter-spacing] for the\n smooth dual-axis tween.\n Sibling treatment across the 3 chip-row digits\n (working / online / active-links) — single concept\n replicated at 3 surfaces by replace_all.\n Hover-letter-spacing family extension (12 anchors\n now): R344/R345/R347/R420/R427/R431/R432/R433/\n R434/R517/R518 + R539 (this round). */}\n <span className=\"font-semibold transition-[font-weight,letter-spacing] duration-200 group-hover:font-bold group-hover:tracking-wide\" data-working-chip-digit>{workingCount}</span><span className=\"opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide\" data-working-chip-unit> working</span>\n </span>\n <span\n // Round 201 / Loop: online chip — mirror of the working\n // chip treatment above. cyan hue 10→15 bg + 20→30 border\n // on hover, only when there's at least one online node\n // to highlight. Three sibling chips in the chip row now\n // all speak the same gesture vocabulary:\n // working chip · green 10→15 (R201)\n // online chip · cyan 10→15 (R201)\n // active-links · gray → cyan (R193)\n /* Round 232 / Loop: tabular-nums on online chip\n (sibling treatment to working chip — same row,\n same digit-jitter physics on count crossings). */\n // R398: hover translate-y lift on clickable variant — see working chip above.\n // R414: `group` parent + inner unit span group-hover-brighten — see working chip above.\n // R494 sibling — online chip joins the active:scale-95 press\n // family (gated on onlineNodes.length > 0 clickable branch,\n // same conditional pattern as the working chip above).\n /* R565: same lit-on-member-alias-match pattern as\n working chip — online chip routes hover to 'idle'\n tier (see onMouseEnter below), so its member-alias\n gate is `hoveredAliasTierKey === 'idle'`. */\n className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${\n onlineNodes.length > 0\n ? `${isOnlineChipLit ? 'bg-cyan-500/15 border-cyan-500/30' : 'bg-cyan-500/10 border-cyan-500/20'} text-cyan-300 hover:bg-cyan-500/15 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95`\n : 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'\n }`}\n data-chip-hover-lift={onlineNodes.length > 0 ? 'true' : 'false'}\n data-chip-group-hover-brighten=\"true\"\n data-online-chip-member-alias-lit={isOnlineChipLit ? 'true' : 'false'}\n data-online-chip\n data-online-chip-aliases={onlineAliases.join(',')}\n data-pin-mirror={pinnedStatus === 'idle' ? 'true' : 'false'}\n data-online-chip-clickable={onlineNodes.length > 0 ? 'true' : 'false'}\n data-online-chip-empty={onlineNodes.length === 0 ? 'true' : 'false'}\n title={onlineTitle}\n role={onlineNodes.length > 0 ? 'link' : undefined}\n tabIndex={onlineNodes.length > 0 ? 0 : undefined}\n // R180: smooth-pin-mirror family — see working chip above.\n // R201: inline transition list also covers bg + border so\n // the new R201 hover tint eases (mirror of working chip).\n // R205: empty-tier recede — opacity 0.5 when onlineNodes\n // is empty (mirror of working chip above).\n /* R687 sibling — online chip mirrors working chip above.\n Per-tier tint cyan-300 #67e8f9 (matches its pinnedStatus\n boxShadow inset ring). Gated on (hoveredStatus ===\n 'working' || hoveredStatus === 'idle') && onlineNodes\n > 0 — matches the onMouseEnter routing logic (online\n count includes BOTH working AND idle tiers, so either\n hover bucket triggers the chip's lift).\n R689 sibling — gate extended to ALSO fire on\n pinnedStatus === 'idle' (the online chip's pin\n target, per its onClick handler routing pin to\n 'idle' when clicked). Mirrors the working chip's\n pin-gated extension above. */\n data-online-chip-halo-layers={((hoveredStatus === 'working' || hoveredStatus === 'idle') || pinnedStatus === 'idle') && onlineNodes.length > 0 ? '2' : '0'}\n style={{\n cursor: onlineNodes.length > 0 ? 'pointer' : undefined,\n opacity: onlineNodes.length === 0 ? 0.5 : 1,\n boxShadow: pinnedStatus === 'idle' ? 'inset 0 0 0 1px #67e8f9, inset 0 0 0 2px rgba(255,255,255,0.45)' : undefined,\n filter: ((hoveredStatus === 'working' || hoveredStatus === 'idle') || pinnedStatus === 'idle') && onlineNodes.length > 0\n ? 'drop-shadow(0 0 2px #67e8f980) drop-shadow(0 0 4px #67e8f940)'\n : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',\n }}\n onMouseEnter={() => {\n // If a working filter would isolate nothing, route to idle.\n const idleCount = onlineNodes.length - workingCount;\n if (workingCount > 0) setHoveredStatus('working');\n else if (idleCount > 0) setHoveredStatus('idle');\n }}\n onMouseLeave={() => setHoveredStatus(prev => prev === 'working' || prev === 'idle' ? null : prev)}\n // R140: click → /nodes. Mirrors R136 active-links→/messages\n // idiom. The chip's hover semantics (R79 highlight all\n // online) keep their meaning — hover for canvas preview,\n // click for the full list. Pinning idle here would\n // semantically misrepresent the chip (which means\n // working+idle), so we navigate instead of pin.\n onClick={() => {\n if (onlineNodes.length > 0) router.push('/nodes');\n }}\n onKeyDown={(e) => {\n if (onlineNodes.length === 0) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/nodes');\n }\n }}\n >\n {/* R337 sibling — online chip unit demotion. */}\n {/* R362 sibling — online-chip digit gains font-semibold. */}\n {/* R539 sibling — online chip digit. Same idiom as\n working chip above (group-hover:tracking-wide). */}\n <span className=\"font-semibold transition-[font-weight,letter-spacing] duration-200 group-hover:font-bold group-hover:tracking-wide\" data-online-chip-digit>{onlineNodes.length}</span><span className=\"opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide\" data-online-chip-unit> online</span>\n </span>\n </>\n );\n })()}\n {/* Round 31 / Loop: fleet-health pressure bar. The \"X working /\n Y online\" chips already carry the raw counts; the bar lets\n the eye get the working/idle/offline RATIO in one glance\n without mental math. Stacked 3-segment chip, ~64 px wide. */}\n {(() => {\n const w = workingCount;\n const i = onlineNodes.length - workingCount; // idle = online - working\n const o = offlineNodes.length;\n const total = w + i + o;\n if (total === 0) return null;\n /* Round 563 / Loop — inspection-overrides-encoding family\n 6th anchor at the pressure-bar segment scope. When\n operator hovers a NODE ALIAS on the canvas, the segment\n matching that node's status tier lights up with its\n R210 brightness + R542 drop-shadow treatment — mirror\n of R562 legend-swatch pattern at the pressure-bar scope.\n Family progression (6 anchors):\n R484 recent-row timestamp alias hover\n R485 edge particle opacity alias hover\n R486 minimap dot opacity alias hover\n R561 group-label + ants-gate member-alias hover\n R562 legend-swatch r + glow member-alias status match\n R563 pressure-seg brightness member-alias status match ← this round\n Same status-tier-match computation as R562 (banked\n idiom): find the hovered alias's session, map to\n working/idle/offline tier, then per-segment check\n `hoveredAliasRowKey === key`. Computed once at IIFE\n scope, used inside the seg() closure. */\n const hoveredSession = hoveredAlias\n ? (onlineNodes.find(s => s.alias === hoveredAlias) ?? offlineNodes.find(s => s.alias === hoveredAlias))\n : null;\n const hoveredAliasTierKey: 'working' | 'idle' | 'offline' | null = !hoveredSession ? null\n : hoveredSession.status === 'working' ? 'working'\n : offlineNodes.includes(hoveredSession) ? 'offline'\n : 'idle';\n // Round 60 / Loop: each segment toggles a sticky filter via\n // `pinnedStatus`. Click the working segment → all non-working\n // nodes dim; click again → release. Segments share width with\n // their proportion of `total`, so a 1-node working share is\n // ~3 px wide on a 64-px bar. We pad the click target with a\n // negative-margin overlay wrapper to give thin slices a\n // 14-px minimum hit area without disturbing the rendered\n // chip width. cursor:pointer + a one-line title hint at the\n // affordance.\n const seg = (n: number, color: string, key: 'working' | 'idle' | 'offline', label: string) => {\n if (n === 0) return null;\n const isPinned = pinnedStatus === key;\n // R563: member-alias-matching flag — when operator hovers\n // a node alias whose status matches this segment's tier.\n const isMemberAliasMatching = hoveredAliasTierKey === key;\n const isSegLit = hoveredStatus === key || isMemberAliasMatching;\n // R102: list the aliases that match this segment's bucket\n // so the title answers WHICH n, not just HOW MANY. Closes\n // the last \"info-density gap\" in the chip-row surfaces\n // (R97 pills / R99 group labels / R101 vendor letters all\n // already enumerate). Truncates at 8 with \"+N more\".\n const matchAliases = key === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : key === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const previewList = matchAliases.slice(0, 8).join(', ');\n const suffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n const titleAction = isPinned ? 'click to release filter' : 'click to highlight';\n return (\n <span\n key={key}\n data-pressure-seg={key}\n data-pressure-seg-aliases={matchAliases.join(',')}\n data-pressure-seg-hovered={hoveredStatus === key ? 'true' : 'false'}\n data-pressure-seg-member-alias-matching={isMemberAliasMatching ? 'true' : 'false'}\n data-pressure-seg-lit={isSegLit ? 'true' : 'false'}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n className=\"anet-topo-chip-focus\"\n title={`${n} ${label}\\n${previewList}${suffix}\\n${titleAction}`}\n // Round 165 / Loop: smooth width transitions on the\n // pressure-bar segments. Pre-R165 the widths snapped\n // instantly when fleet composition shifted (a node\n // going idle → working would visibly jump the green\n // segment by a few px). 220ms ease-out makes the bar\n // visually breathe with state — segment shifts now\n // glide into place instead of cutting. Pure CSS\n // transition on width; respects prefers-reduced-\n // motion via globals.css blanket override that\n // neutralises transition-duration universally.\n // boxShadow gets its own transition so pin\n // state-changes also fade smoothly, not snap.\n //\n // Round 210 / Loop: segment deepens its OWN colour on\n // hover via filter: brightness(1.2). Closes the\n // chip-row \"hover deepens own identity\" family at the\n // pressure-bar scope — R83 already setHoveredStatus\n // to drive canvas dim, but the segment itself stayed\n // at flat `background: color`. R210 makes the cause\n // element (segment) light up with the same gesture\n // it fires on the canvas (effect element). Family\n // surfaces (6 with R210):\n // R193 active-links chip · gray → cyan\n // R195 recent-panel footer · gray → cyan\n // R201 working / online · own /10 → /15 (×2)\n // R202 vendor letter · per-vendor color-mix\n // R210 pressure-bar seg · brightness(1.2) ← NEW\n // brightness(1.2) lightens the segment's own hue by\n // 20% — keeps the tier identity (green stays green,\n // teal stays teal, gray stays gray) while signalling\n // \"this is hovered\". transition adds filter 150ms\n // ease-out alongside the existing width / box-shadow.\n style={{\n width: `${(n / total) * 100}%`,\n background: color,\n height: '100%',\n cursor: 'pointer',\n boxShadow: isPinned ? `inset 0 0 0 1px ${color}, inset 0 0 0 2px rgba(255,255,255,0.6)` : undefined,\n /* Round 542 / Loop — pressure-bar segments gain\n drop-shadow tier-color glow on hover, stacked\n on R210 brightness(1.2). Sibling to R537 legend\n swatch + R541 vendor chip glow at the chip-row\n scope — three same-pattern surfaces (legend\n swatch / vendor chip / pressure segment) all\n radiate their identity color on hover.\n 3rd anchor in the chip-row tier-color paint\n glow sub-family:\n R537 legend swatch row.fill (status hex)\n R541 vendor chip v.color (hsl via color-mix)\n R542 pressure seg color (status hex) ← this round\n Stacked filter syntax (brightness + drop-shadow\n in same filter declaration): `brightness(1.2)\n drop-shadow(...)`. CSS filter supports multiple\n functions; they apply left-to-right. Brightness\n boosts the segment's own color, drop-shadow\n paints the outer halo. Together: hovered seg\n looks \"lit up\" with both inner glow + outer\n halo in its tier color.\n Hue: `${color}99` hex+alpha (60%) — color here\n is a 6-char hex (e.g., '#22c55e' for working\n cyber, '#0d9488' for idle light), not hsl, so\n hex+alpha concat works (unlike R541 vendor\n which needed color-mix for hsl). Banked\n pattern: hex sources use hex+alpha; hsl/color()\n sources use color-mix.\n 2px blur (vs R537's 3px) since pressure-seg is\n small (h-2 = 8px tall, variable width) — a\n smaller blur keeps the glow tight to the\n segment without bleeding into neighbors.\n filter is paint-only; bbox unchanged; R51\n overlap-test invariants hold. Transition list\n already includes `filter` (post-R524). */\n /* R563: filter lifts on EITHER direct hover OR member-\n alias-matching (operator inspecting a node whose\n status matches this segment's tier). Same R210\n brightness + R542 drop-shadow value across both\n gate sources — uniform visual response, distinct\n semantic gates. */\n /* R662 — pressure-bar segment drop-shadow gains a\n 2nd outer layer at 4px + 0x4c alpha (half R542\n inner 0x99). Family extension to chip-bar tier;\n same 2+4 stride + 50% falloff as R643-R645/R648\n /R659. ${color} per-tier (working green / idle\n teal / offline slate) preserved across both\n layers. */\n filter: isSegLit ? `brightness(1.2) drop-shadow(0 0 2px ${color}99) drop-shadow(0 0 4px ${color}4c)` : undefined,\n transition: 'width 220ms ease-out, box-shadow 150ms ease-out, filter 150ms ease-out',\n }}\n onClick={(e) => {\n e.stopPropagation();\n setPinnedStatus(prev => prev === key ? null : key);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedStatus(prev => prev === key ? null : key);\n }\n }}\n // R83: hover preview — segments now match the R55 legend,\n // R79 working/online chips, and R80 vendor letters in\n // offering a hover-transient highlight before the click\n // commits to a pin. Users get to feel what the filter\n // does before they lock it in. Same activeStatus =\n // hoveredStatus ?? pinnedStatus formula; releasing the\n // pointer falls back to the pin if one is set, or to\n // baseline. Thin (3-px) segments still hit-test fine\n // because the chip itself is a flex row — span doesn't\n // need any extra hit padding for hover.\n onMouseEnter={() => setHoveredStatus(key)}\n onMouseLeave={() => setHoveredStatus(prev => prev === key ? null : prev)}\n />\n );\n };\n // Round 47 / Loop: hidden on mobile — at <640px the chip row\n // wraps to multiple lines and crowds the topology header;\n // pressure ratio is best read with the working+online raw\n // counts (kept visible) anyway.\n return (\n <span\n className=\"hidden sm:inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono\"\n title={`${w} working · ${i} idle · ${o} offline`}\n data-fleet-pressure\n >\n {/* Round 373 / Loop: pressure-bar kicker label gains\n font-medium (fw 400 → 500). Sibling small-text fw\n lift family with R363 recent-row alias + R364\n legend-row label + R366 group-label count + R368\n +N more flows footer — extends to a 5th surface\n (the chip-row's 'pressure' label). At fontSize\n 10 px tracking-wide against the chip's gray bg,\n the default fw 400 sat below the deliberate-data\n band; fw 500 brings it into parity with the\n chip-row 'working / online / active links' unit\n spans (chip-level font-medium R313). data-fleet-\n pressure-kicker attr exposes the kicker for tests. */}\n <span className=\"text-[10px] tracking-wide font-medium\" data-fleet-pressure-kicker>pressure</span>\n {/* Round 374 / Loop: pressure-bar height h-1.5 → h-2\n (6 → 8 px) — sibling visual-weight bump (8th anchor\n in the family):\n R287 minimap viewport stroke 1 → 1.5\n R295 legend swatch base radius 5.5 → 6\n R359 recent-row pip base radius 1.6 → 1.8\n R360 hub digit fontSize 11 → 12\n R361 edge-badge digit fontSize 10 → 11\n R365 hub-highlight base radius 5 → 5.5\n R367 edge-badge rest stroke 1 → 1.25\n R374 pressure-bar height 1.5 → 2 (this round)\n +33 % bar height gives the working/idle/offline\n segments more visibility — at h-1.5 the 3-segment\n proportion bar was readable but slim; at h-2 the\n segments parse cleanly even when one tier is\n < 10 % share. Geometry-safe: items-center flex\n centers the bar inside the chip's py-1 (4 px top +\n 4 px bottom) — bar at 8 px stays comfortably\n inside the 10-px text-row height. R165 segment\n width transitions + R210 brightness hover + R83\n pin-mirror box-shadow on segments all preserved\n (segments inherit width from parent so the height\n bump propagates without segment-side edits).\n data-fleet-pressure-bar-height attr exposes the\n height token for tests. */}\n <span className=\"inline-flex h-2 w-16 rounded-full overflow-hidden\" style={{ background: 'rgb(75 85 99 / 0.25)' }} data-fleet-pressure-bar-height=\"h-2\">\n {seg(w, isLight ? '#059669' : '#22c55e', 'working', 'working')}\n {seg(i, isLight ? '#0d9488' : '#2dd4bf', 'idle', 'idle')}\n {seg(o, isLight ? '#94a3b8' : '#6b7280', 'offline', 'offline')}\n </span>\n </span>\n );\n })()}\n {/* Round 64 / Loop: active-filter pills. When pinnedStatus or\n pinnedGroup is set, show a small \"filter: <key> ×\" pill so\n the user can see the pin from the chip row even if they\n scrolled the canvas off-screen. × button clears the\n specific pin (Esc still clears all — both paths are\n valid). pinnedStatus and pinnedGroup pin independently so\n both pills may render simultaneously. */}\n {/* R67: pills enter via anet-fade-in so they appear softly\n instead of popping. The \"filter: \" prefix collapses below\n sm — at narrow viewports the chip row is precious real\n estate (R47 already hides pressure / vendor / freshness),\n so dropping the redundant label keeps the working/idle/\n alpha keys readable without overflow. */}\n {/* R71: each pill picks up a \"· N\" match count tail. Tells the\n user at a glance how many sessions the active filter\n matches without scanning the canvas. Counts come from the\n already-computed workingCount + onlineNodes + offlineNodes\n for status, and groupKeys for group. */}\n {/* R73: entire pill body is a click-to-clear target — matches\n the Notion / Linear tag UX (the whole chip releases). The\n × keeps its dedicated <button> + aria-label for screen\n readers; the outer span just adds an extra mouse-friendly\n hit area with a title hint. ×'s onClick stopPropagation\n so the redundant outer onClick doesn't double-fire (no\n functional difference since both clear, but cleaner\n event flow). */}\n {pinnedStatus && (() => {\n const matchCount = pinnedStatus === 'working' ? workingCount\n : pinnedStatus === 'idle' ? (onlineNodes.length - workingCount)\n : offlineNodes.length;\n // Round 97 / Loop: the pill says HOW MANY but not WHICH.\n // Hovering it now shows the matched alias list in the\n // tooltip — answers \"exactly who is this filtering to\"\n // without forcing the user to scan the dim mask on the\n // canvas. Truncates at 8 names so a 50-node working\n // bucket doesn't produce a 12-line tooltip; the count\n // chip already answers \"how many overall\".\n const matchAliases = pinnedStatus === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : pinnedStatus === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"status\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n // R355: `group` lets the inner opacity-70 spans (prefix\n // `filter:` + count `· N`) brighten to 100 % on pill hover.\n // Sibling treatment on group + vendor pills below.\n // R495 — filter pills (3 sibling `group` variants) join the\n // active:scale-95 press-feedback family. R490's !important\n // transition list on .anet-topo-chip-focus already covers\n // transform, so just appending active:scale-95 to the\n // className wires the press tactile in one token. Compound\n // with R400-era hover:-translate-y-px gives lift-and-compress.\n className=\"group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu\" data-topo-filter-pill-hover-lift=\"true\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}\n onClick={() => setPinnedStatus(null)}\n /* Round 543 / Loop — status filter pill gains always-on\n tier-color drop-shadow when rendered. Pre-R543 the\n pill carried bg-tint + tier-color text + border but\n no outer paint extent — it sat as a flat tinted chip\n in the chip row. R543 adds an outer glow at the\n pill's text color so the pill radiates a soft tier-\n colored halo signaling \"this filter is active.\" Pin\n pill only renders when pinnedStatus is set (the JSX\n gate above), so the drop-shadow appearing reinforces\n the visual \"active pin\" state.\n Sibling pattern: R477 legend pin-ring also paints a\n pin-gated tier-color drop-shadow. Pin pill follows\n the same \"pin-gated paint glow\" semantics but at the\n chip-row scope vs the panel-row scope. The chip-row\n tier-color glow trio (R537/R541/R542 hover-gated)\n plus R543 (pin-gated, this round) closes the chip-\n row paint-glow family across BOTH gate types\n (hover for transient affordance, pin for sticky\n active-state visual).\n Hue: explicit tier color (extracted from the\n existing `color` ternary). 0x99 alpha (~60%) +\n 3px blur. Stays inside the same color hierarchy\n as the pill's own text/border (currentColor).\n R543 status pill scope only — R543's pattern can\n future-extend to group/vendor/edge filter pills\n (3 more variants at lines 2683/2755/2824). Out of\n scope to keep R543 single-pill. */\n style={{\n background: pinnedStatus === 'working' ? (isLight ? '#05966914' : '#22c55e1f')\n : pinnedStatus === 'idle' ? (isLight ? '#0d948814' : '#2dd4bf1f')\n : (isLight ? '#94a3b814' : '#6b72801f'),\n color: pinnedStatus === 'working' ? (isLight ? '#047857' : '#86efac')\n : pinnedStatus === 'idle' ? (isLight ? '#0f766e' : '#5eead4')\n : (isLight ? '#475569' : '#9ca3af'),\n borderColor: 'currentColor',\n cursor: 'pointer',\n /* R664 — status filter pin pill drop-shadow gains a 2nd\n outer layer at 6px + 0x4c alpha (half R543 inner 0x99).\n 23rd anchor in multi-layer halo family — completes\n the chip-row tier with all 4 chip variants now multi-\n layer (filter-pills R661 + pressure-bar R662 + vendor\n R663 + status pin R664). Per-tier color (working /\n idle / offline emerald/teal/slate text shade) preserved\n across both layers. */\n filter: `drop-shadow(0 0 3px ${\n pinnedStatus === 'working' ? (isLight ? '#047857' : '#86efac')\n : pinnedStatus === 'idle' ? (isLight ? '#0f766e' : '#5eead4')\n : (isLight ? '#475569' : '#9ca3af')\n }99) drop-shadow(0 0 6px ${\n pinnedStatus === 'working' ? (isLight ? '#047857' : '#86efac')\n : pinnedStatus === 'idle' ? (isLight ? '#0f766e' : '#5eead4')\n : (isLight ? '#475569' : '#9ca3af')\n }4c)`,\n }}\n >\n {/* Round 412 / Loop: filter pin pill VALUE picks up the\n chip-internal-hierarchy arc. Pre-R412 the value span\n (pinnedStatus / pinnedGroup / pinnedVendor) inherited\n the parent's font-medium (fw=500); prefix and suffix\n were opacity-70 label-tier but the VALUE itself sat\n at the same baseline weight. R412 wraps the value in\n a font-semibold span (fw=600) so the pill now reads\n with proper data-tier emphasis — sibling treatment\n to R333/R335-R341/R362/R369/R389/R410. data-filter-\n value attr surfaces the value span for tests.\n 4-pill replace family — status / group / vendor / edge. */}\n <span><span className=\"hidden sm:inline opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide\" data-filter-prefix>filter: </span><span className=\"font-semibold\" data-filter-value>{pinnedStatus}</span><span className=\"opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear ${pinnedStatus} filter`}\n onClick={(e) => { e.stopPropagation(); setPinnedStatus(null); }}\n /* Round 356 / Loop: filter pin pill × buttons gain\n hover:scale-110 (Tailwind 4 modern CSS `scale` property,\n not legacy transform). Sibling polish to R354 vendor\n letter glyph + R350/R352/R353 chrome icon hover-scales.\n Pre-R356 the × had only hover:opacity-70 — the target\n dimmed under cursor but didn't lift. R356 adds a 10 %\n scale on hover so the click-target reads as \"press me\"\n alongside the dim. transform-gpu hint promotes the\n button to its own compositor layer for crisper edges\n during the scale tween. transition-transform duration-\n 200 matches the chrome icon hover-scale timing family.\n inline-block is default for <button> so no display\n tweak needed. replace_all covers all 4 filter pin\n pills (status / group / vendor / edge) at once. */\n /* Round 547 / Loop — extends pill × close-button hover\n gesture from scale-110 (R356) + opacity-70 to ALSO\n include rotate-12 on hover. Pre-R547 the × dimmed\n and grew on hover; R547 adds a 12° twist so the\n close action telegraphs \"discarding/spinning away\"\n with a small delight gesture. Composes with\n transition-transform (existing) — Tailwind's\n hover:rotate-12 + hover:scale-110 stack into one\n transform under the same 200ms ease-out tween.\n Applied to all 4 pill × buttons (status / group /\n vendor / edge) via replace_all since the className\n is identical. Closes the pill × hover gesture\n vocabulary at 3 axes:\n hover:opacity-70 paint dim\n hover:scale-110 geometry grow (R356)\n hover:rotate-12 geometry twist (R547, this round)\n Hover-gesture parity across the 4-pill family. */\n className=\"ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {pinnedGroup && (() => {\n const matchCount = Object.values(groupKeys).filter(k => k === pinnedGroup).length;\n // R97: list group members in the tooltip.\n const matchAliases = Object.entries(groupKeys)\n .filter(([, key]) => key === pinnedGroup)\n .map(([alias]) => alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"group\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n // R355 sibling — `group` parent + group-hover on inner spans.\n // R495 — filter pills (3 sibling `group` variants) join the\n // active:scale-95 press-feedback family. R490's !important\n // transition list on .anet-topo-chip-focus already covers\n // transform, so just appending active:scale-95 to the\n // className wires the press tactile in one token. Compound\n // with R400-era hover:-translate-y-px gives lift-and-compress.\n className=\"group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu\" data-topo-filter-pill-hover-lift=\"true\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}\n onClick={() => setPinnedGroup(null)}\n /* Round 544 / Loop — extends R543 pin-active filter-pill\n drop-shadow pattern to the GROUP pill (2nd of 4 pill\n variants). Pre-R544 the group pill carried bg-tint +\n pal.legendAccent text/border but no outer paint glow.\n R544 adds the matching cyan-accent drop-shadow so the\n group pin pill radiates the same paint glow as R543\n status pill — pin-active visual signal at chip-row\n scope.\n Hue: pal.legendAccent (cyber #67e8f9 cyan-300 /\n light #0d9488 teal-600). Uses color-mix() syntax\n because pal.legendAccent may resolve to hex; same\n syntax works for both hex and hsl sources (banked\n R541 lesson). 60% alpha + 3px blur — same intensity\n as R543 status pill so the pin-active visual signal\n reads with matching brightness across pill variants.\n Pin-active tier-color paint glow sub-family\n (CLOSED progressively):\n R477 legend pin-ring (panel-row, row.fill)\n R543 status pill (chip-row, tier-color)\n R544 group pill (chip-row, legendAccent)\n ← this round\n Out of scope: vendor pill (line ~2755) + edge pill\n (line ~2824) — can future-extend in subsequent\n rounds (R545/R546). Both use the same R543 idiom:\n always-on drop-shadow when rendered, color from the\n pill's existing text color. */\n style={{\n background: isLight ? '#67e8f914' : '#67e8f91f',\n color: pal.legendAccent,\n borderColor: 'currentColor',\n cursor: 'pointer',\n filter: `drop-shadow(0 0 3px color-mix(in srgb, ${pal.legendAccent} 60%, transparent)) drop-shadow(0 0 6px color-mix(in srgb, ${pal.legendAccent} 30%, transparent))`,\n }}\n >\n {/* R412: see status pill above — filter value fw=600 data tier. */}\n <span><span className=\"hidden sm:inline opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide\" data-filter-prefix>filter: </span><span className=\"font-semibold\" data-filter-value>{pinnedGroup}</span><span className=\"opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear group filter ${pinnedGroup}`}\n onClick={(e) => { e.stopPropagation(); setPinnedGroup(null); }}\n /* Round 356 / Loop: filter pin pill × buttons gain\n hover:scale-110 (Tailwind 4 modern CSS `scale` property,\n not legacy transform). Sibling polish to R354 vendor\n letter glyph + R350/R352/R353 chrome icon hover-scales.\n Pre-R356 the × had only hover:opacity-70 — the target\n dimmed under cursor but didn't lift. R356 adds a 10 %\n scale on hover so the click-target reads as \"press me\"\n alongside the dim. transform-gpu hint promotes the\n button to its own compositor layer for crisper edges\n during the scale tween. transition-transform duration-\n 200 matches the chrome icon hover-scale timing family.\n inline-block is default for <button> so no display\n tweak needed. replace_all covers all 4 filter pin\n pills (status / group / vendor / edge) at once. */\n /* Round 547 / Loop — extends pill × close-button hover\n gesture from scale-110 (R356) + opacity-70 to ALSO\n include rotate-12 on hover. Pre-R547 the × dimmed\n and grew on hover; R547 adds a 12° twist so the\n close action telegraphs \"discarding/spinning away\"\n with a small delight gesture. Composes with\n transition-transform (existing) — Tailwind's\n hover:rotate-12 + hover:scale-110 stack into one\n transform under the same 200ms ease-out tween.\n Applied to all 4 pill × buttons (status / group /\n vendor / edge) via replace_all since the className\n is identical. Closes the pill × hover gesture\n vocabulary at 3 axes:\n hover:opacity-70 paint dim\n hover:scale-110 geometry grow (R356)\n hover:rotate-12 geometry twist (R547, this round)\n Hover-gesture parity across the 4-pill family. */\n className=\"ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* R89: vendor pin gets its own filter pill, matching the R64\n status + group pattern. R88 added the pin but only the\n letter itself carried the state; the chip-row had no\n \"filter: A · 2 ×\" affordance the other two pins have. The\n pill colour borrows the vendor's own swatch so each pin\n still reads in its native hue (A green, O cyan, 书 blue,\n ? slate). Same body-click-clears + × button pattern as\n R64. */}\n {pinnedVendor && (() => {\n const matchEntry = vendorDist.find(v => v.initial === pinnedVendor);\n const matchCount = matchEntry?.count ?? 0;\n const vendorColor = matchEntry?.color ?? pal.legendText;\n // R97: list vendor users in the tooltip. The vendorIdentity\n // resolver maps model strings to a vendor initial — match\n // any session whose initial equals the pinned letter (with\n // unknowns folded to '?').\n const matchAliases = [...onlineNodes, ...offlineNodes]\n .filter(s => {\n const v = vendorForModel(s.model, s.runtime);\n return (v.id === 'unknown' ? '?' : v.initial) === pinnedVendor;\n })\n .map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"vendor\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n // R355 sibling — `group` parent + group-hover on inner spans.\n // R495 — filter pills (3 sibling `group` variants) join the\n // active:scale-95 press-feedback family. R490's !important\n // transition list on .anet-topo-chip-focus already covers\n // transform, so just appending active:scale-95 to the\n // className wires the press tactile in one token. Compound\n // with R400-era hover:-translate-y-px gives lift-and-compress.\n className=\"group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu\" data-topo-filter-pill-hover-lift=\"true\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}\n onClick={() => setPinnedVendor(null)}\n /* Round 545 / Loop — extends pin-active filter-pill drop-\n shadow pattern to VENDOR pill (3rd of 4 pill variants\n after R543 status + R544 group). vendorColor is HSL\n format (banked R541 lesson — vendorDist.color sources\n from mono.text in vendorIdentity.ts, which is `hsl(...)`),\n so the filter uses color-mix() syntax — same as R544.\n 60% alpha + 3px blur, matching R543/R544 intensity for\n consistent pin-active visual signal across all pill\n variants.\n Pin-active tier-color paint glow sub-family (progressive\n extension, 1 pill variant remaining):\n R477 legend pin-ring (panel-row, row.fill, hex+alpha)\n R543 status pill (chip-row, tier-color, hex+alpha)\n R544 group pill (chip-row, legendAccent, color-mix)\n R545 vendor pill (chip-row, vendorColor, color-mix)\n ← this round\n Out of scope: edge pill (line ~2824 pre-R545, now ~2900+).\n Final 1/4 pill remaining for a future round closes the\n sub-family. */\n style={{\n background: `${vendorColor}1f`,\n color: vendorColor,\n borderColor: 'currentColor',\n cursor: 'pointer',\n filter: `drop-shadow(0 0 3px color-mix(in srgb, ${vendorColor} 60%, transparent)) drop-shadow(0 0 6px color-mix(in srgb, ${vendorColor} 30%, transparent))`,\n }}\n >\n {/* R412: see status pill above — filter value fw=600 data tier. */}\n <span><span className=\"hidden sm:inline opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide\" data-filter-prefix>filter: </span><span className=\"font-semibold\" data-filter-value>{pinnedVendor}</span><span className=\"opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear vendor filter ${pinnedVendor}`}\n onClick={(e) => { e.stopPropagation(); setPinnedVendor(null); }}\n /* Round 356 / Loop: filter pin pill × buttons gain\n hover:scale-110 (Tailwind 4 modern CSS `scale` property,\n not legacy transform). Sibling polish to R354 vendor\n letter glyph + R350/R352/R353 chrome icon hover-scales.\n Pre-R356 the × had only hover:opacity-70 — the target\n dimmed under cursor but didn't lift. R356 adds a 10 %\n scale on hover so the click-target reads as \"press me\"\n alongside the dim. transform-gpu hint promotes the\n button to its own compositor layer for crisper edges\n during the scale tween. transition-transform duration-\n 200 matches the chrome icon hover-scale timing family.\n inline-block is default for <button> so no display\n tweak needed. replace_all covers all 4 filter pin\n pills (status / group / vendor / edge) at once. */\n /* Round 547 / Loop — extends pill × close-button hover\n gesture from scale-110 (R356) + opacity-70 to ALSO\n include rotate-12 on hover. Pre-R547 the × dimmed\n and grew on hover; R547 adds a 12° twist so the\n close action telegraphs \"discarding/spinning away\"\n with a small delight gesture. Composes with\n transition-transform (existing) — Tailwind's\n hover:rotate-12 + hover:scale-110 stack into one\n transform under the same 200ms ease-out tween.\n Applied to all 4 pill × buttons (status / group /\n vendor / edge) via replace_all since the className\n is identical. Closes the pill × hover gesture\n vocabulary at 3 axes:\n hover:opacity-70 paint dim\n hover:scale-110 geometry grow (R356)\n hover:rotate-12 geometry twist (R547, this round)\n Hover-gesture parity across the 4-pill family. */\n className=\"ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* R119: edge pin filter pill — completes the R64 / R89 pill\n pattern across all four pin dimensions. R116 added\n pinnedEdgeKey but the chip row never grew the matching\n pill, leaving the locked flow visible only on the canvas +\n recent-signal row tint. This pill shows \"filter:\n alpha→beta · 3\" so the locked flow appears in the same\n row as the other three pin types. Body-click + × button\n clear, same pattern as R64. */}\n {pinnedEdgeKey && (() => {\n const link = flowLinks.find(l => l.key === pinnedEdgeKey);\n if (!link) return null;\n // R150 / Loop: extend the hot-lane amber convention from the\n // canvas badge (R126) / recent-row count (R127) / panel\n // header (R129) to the R119 edge filter pill — when the\n // pinned edge has count ≥ 10 the count tspan flips to\n // amber + 700 weight, and the tooltip grows a \"(hot lane\n // · ≥ 10)\" marker. Closes the 4th hot-lane surface,\n // completing R150's milestone: every place that surfaces\n // an edge count now uses the same amber-when-hot vocab.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <span\n data-active-filter=\"edge\"\n data-filter-match-count={link.count}\n data-filter-match-aliases={`${link.from},${link.to}`}\n data-active-filter-edge-hot={isHot ? 'true' : 'false'}\n // R495 sibling — 4th filter pill (no `group` prefix variant)\n // joins active:scale-95 press family alongside the 3 group\n // variants above. Same recipe.\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu\" data-topo-filter-pill-hover-lift=\"true\"\n title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}\n onClick={() => setPinnedEdgeKey(null)}\n /* Round 546 / Loop — CLOSES pin-active filter-pill drop-\n shadow sub-family at the 4th and final pill variant\n (edge pill). R543 (status) + R544 (group) + R545\n (vendor) covered the first three; R546 closes at\n edge.\n Pin-active tier-color paint glow sub-family CLOSED\n (4 anchors):\n R477 legend pin-ring (panel-row, row.fill)\n R543 status pill (chip-row, tier-color text)\n R544 group pill (chip-row, legendAccent)\n R545 vendor pill (chip-row, vendorColor)\n R546 edge pill (chip-row, pal.flowEdge)\n ← this round, family CLOSED\n All 4 filter pin pills now radiate paint glow in the\n same hue family as their text/border on render —\n pin-active visual signal uniform across the chip-row\n pill family.\n pal.flowEdge is theme-driven (dynamic); color-mix\n syntax safe-defaults regardless of resolved format\n (banked R541/R544/R545 pattern). 60% alpha + 3px blur\n — same intensity as R543/R544/R545 for consistent\n cross-pill visual signal. */\n style={{\n background: isLight ? `${pal.flowEdge}14` : `${pal.flowEdge}1f`,\n color: pal.flowEdge,\n borderColor: 'currentColor',\n cursor: 'pointer',\n filter: `drop-shadow(0 0 3px color-mix(in srgb, ${pal.flowEdge} 60%, transparent)) drop-shadow(0 0 6px color-mix(in srgb, ${pal.flowEdge} 30%, transparent))`,\n }}\n >\n {/* R412: filter pin pill value (edge variant) picks up fw=600.\n Sibling treatment to the status/group/vendor pills above. */}\n <span>\n <span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>\n <span className=\"font-semibold\" data-filter-value>{link.from}→{link.to}</span>\n {/* Round 323 / Loop: edge filter pill count digit picks\n up tabular-nums (Tailwind class on both cold +\n hot branches). Sibling treatment to the status /\n group / vendor pin pills (R323 replace_all upstream\n in this same round added `tabular-nums` to those\n three pills' count spans). Pre-R323 a matchCount /\n link.count crossing 9→10 widened the digit and\n shifted the trailing × button right ~3px in font-\n mono (mono digits still have natural-vs-tabular\n variance). Locks the slot so the × button stays\n planted as the count grows. 9th surface in the\n info-density tabular-nums sweep after R322 panel\n hot count. */}\n {isHot ? (\n <span\n className=\"opacity-90 tabular-nums\"\n style={{ color: hotStroke, fontWeight: 700 }}\n data-active-filter-edge-count-hot\n >\n {' · '}{link.count}\n </span>\n ) : (\n <span className=\"opacity-70 tabular-nums\" data-active-filter-edge-count>\n {' · '}{link.count}\n </span>\n )}\n </span>\n <button\n type=\"button\"\n aria-label={`Clear edge filter ${link.from} → ${link.to}`}\n onClick={(e) => { e.stopPropagation(); setPinnedEdgeKey(null); }}\n /* Round 356 / Loop: filter pin pill × buttons gain\n hover:scale-110 (Tailwind 4 modern CSS `scale` property,\n not legacy transform). Sibling polish to R354 vendor\n letter glyph + R350/R352/R353 chrome icon hover-scales.\n Pre-R356 the × had only hover:opacity-70 — the target\n dimmed under cursor but didn't lift. R356 adds a 10 %\n scale on hover so the click-target reads as \"press me\"\n alongside the dim. transform-gpu hint promotes the\n button to its own compositor layer for crisper edges\n during the scale tween. transition-transform duration-\n 200 matches the chrome icon hover-scale timing family.\n inline-block is default for <button> so no display\n tweak needed. replace_all covers all 4 filter pin\n pills (status / group / vendor / edge) at once. */\n /* Round 547 / Loop — extends pill × close-button hover\n gesture from scale-110 (R356) + opacity-70 to ALSO\n include rotate-12 on hover. Pre-R547 the × dimmed\n and grew on hover; R547 adds a 12° twist so the\n close action telegraphs \"discarding/spinning away\"\n with a small delight gesture. Composes with\n transition-transform (existing) — Tailwind's\n hover:rotate-12 + hover:scale-110 stack into one\n transform under the same 200ms ease-out tween.\n Applied to all 4 pill × buttons (status / group /\n vendor / edge) via replace_all since the className\n is identical. Closes the pill × hover gesture\n vocabulary at 3 axes:\n hover:opacity-70 paint dim\n hover:scale-110 geometry grow (R356)\n hover:rotate-12 geometry twist (R547, this round)\n Hover-gesture parity across the 4-pill family. */\n className=\"ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* Round 124 / Loop: pin-intersection summary chip. The four\n filter pills (R64 status, R63 group, R89 vendor, R119\n edge) each report their own dim's match count in\n isolation — that answers \"what does THIS filter catch\"\n but not \"what survives ALL pins\". Since the node-opacity\n chain AND-composes the four pin dimensions, two active\n pins routinely produce an intersection smaller than\n either individual count. With three or four pins active\n the gap widens further. This chip appears ONLY when\n ≥ 2 pin dims are active (a single pin's pill already\n tells the whole story) and shows the count of nodes\n that satisfy every active pin simultaneously. Color is\n deliberately neutral (gray) since the chip represents\n the AND of mixed-color filters — borrowing any one of\n the pill hues would mis-signal which dim dominates.\n Tooltip lists the surviving aliases with the same\n 8-truncate + \"+N more\" pattern as the individual pill\n tooltips (R97). No click handler — it's a pure readout;\n Esc / per-pill × are still the cancel paths. */}\n {(() => {\n const pinDimCount =\n (pinnedStatus ? 1 : 0) +\n (pinnedGroup ? 1 : 0) +\n (pinnedVendor ? 1 : 0) +\n (pinnedEdgeKey ? 1 : 0);\n if (pinDimCount < 2) return null;\n const edgeLink = pinnedEdgeKey\n ? flowLinks.find(l => l.key === pinnedEdgeKey)\n : null;\n const edgeEndpoints: Set<string> | null = edgeLink\n ? new Set([edgeLink.from, edgeLink.to])\n : null;\n const allSessions = [...onlineNodes, ...offlineNodes];\n const survivors = allSessions.filter(s => {\n const isOnline = s.status !== 'offline';\n if (pinnedStatus === 'working' && s.status !== 'working') return false;\n if (pinnedStatus === 'idle' && !(isOnline && s.status !== 'working')) return false;\n if (pinnedStatus === 'offline' && isOnline) return false;\n if (pinnedGroup) {\n const gk = groupKeys[s.alias] ?? s.alias;\n if (gk !== pinnedGroup) return false;\n }\n if (pinnedVendor) {\n const v = vendorForModel(s.model, s.runtime);\n const initial = v.id === 'unknown' ? '?' : v.initial;\n if (initial !== pinnedVendor) return false;\n }\n if (edgeEndpoints && !edgeEndpoints.has(s.alias)) return false;\n return true;\n });\n const matchAliases = survivors.map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n const isEmpty = matchAliases.length === 0;\n const tooltip = !isEmpty\n ? `${matchPreview}${matchSuffix} — nodes passing all ${pinDimCount} pinned filters`\n : `No nodes pass all ${pinDimCount} pinned filters — release one to widen (Esc clears all)`;\n // R125: when the intersection drops to zero, the chip flips\n // from neutral gray to a warning amber. Zero-overlap is the\n // exact case users get confused — canvas dims to 0.28\n // everywhere, no positive signal explains why. The \"· 0\"\n // tail in neutral gray reads as just another number,\n // indistinguishable from \"· 12\". Amber + a ⚠ glyph lifts\n // it to \"your filters cancel out\" at a glance. Color\n // choice: amber (#d97706 light, #fbbf24 dark) — same hue\n // family as warning chips elsewhere in the dashboard,\n // distinct from any pill color (status green/teal/slate,\n // group cyan, vendor varies, edge cyan, neutral gray for\n // non-empty intersection) so the empty state stands out.\n const emptyColor = isLight ? '#d97706' : '#fbbf24';\n return (\n <span\n data-pin-intersection\n data-pin-dim-count={pinDimCount}\n data-pin-intersection-count={matchAliases.length}\n data-pin-intersection-empty={isEmpty ? 'true' : 'false'}\n data-pin-intersection-aliases={matchAliases.join(',')}\n /* Round 235 / Loop: pin-intersection chip joins the\n info-density tabular-nums sweep. The chip has TWO\n digits visible at once — '{pinDimCount} pins ·\n {matchAliases.length}' — and the matchAliases count\n in particular rolls frequently as filters tighten /\n widen against the live fleet. font-mono already\n makes the digits uniform-ish, but tabular-nums\n further locks digit width within the mono cell so\n the gap between 'pins' and '·' stays stable, and\n the chip's overall width doesn't bump when either\n number changes. 9th surface in the sweep — the\n third and last HTML chip surface, completing\n coverage across chip-row + vendor-row + pin-\n intersection. */\n className=\"hidden sm:inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono tabular-nums text-xs border anet-fade-in anet-topo-chip-focus\"\n title={tooltip}\n /* Round 236 / Loop: smooth the empty/non-empty colour\n crossing. Pre-R236 the chip snap-flipped between\n slate-on-slate (non-empty filter intersection) and\n amber-on-amber (empty intersection — the 'your\n pinned filters cancel out' warning). bg, color,\n and borderColor (which inherits currentColor) all\n changed in one frame. R236 adds a 200ms ease-out\n transition on all three so when a filter tightens\n matches across the 0-boundary the chip eases\n through the colour shift instead of snapping. Same\n 200ms cadence the other chip-row members use\n (R201 working/online tint, R193 active-links\n tint, R202 vendor letter color-mix). One more\n surface where colour state-changes ease rather\n than snap — consistent with the topology's\n broader transitions vocabulary. */\n style={{\n background: isEmpty\n ? (isLight ? '#d97706' + '14' : '#fbbf24' + '1f')\n : (isLight ? '#94a3b814' : '#94a3b81f'),\n color: isEmpty\n ? emptyColor\n : (isLight ? '#475569' : '#9ca3af'),\n borderColor: 'currentColor',\n transition: 'background-color 200ms ease-out, color 200ms ease-out, border-color 200ms ease-out',\n }}\n >\n <span>\n <span className=\"hidden sm:inline opacity-70\" data-pin-intersection-prefix>match: </span>\n {/* Round 324 / Loop: pin-intersection chip carries TWO\n numeric counts in one breath — pinDimCount (\"how\n many filter pins are active\") and matchAliases.\n length (\"how many aliases land in the intersection\n after pins compose\"). Both jitter on digit-width\n crossings (1→10 etc) without tabular-nums even\n under font-mono. Pre-R324 a fleet busying up so\n one dimension flips from 0→non-zero (chip mounts\n via R237 always-mount opacity gate) AND the match\n count digit ticks 9→10 simultaneously visibly\n jolted the trailing `× pins` / ` × ` segments.\n Two dedicated tabular-nums spans (one per count)\n lock both digit slots so the chip's text geometry\n stays planted through both crossings. 10th\n surface in the info-density tabular-nums sweep\n after R323 filter pin pill counts (R64/R89/R119/\n R150 pin-pill family parity now complete with\n this composed-pin sibling). data-pin-intersection-\n count-* attrs expose both spans for tests. */}\n {/* Round 341 / Loop: middle \" pins\" unit word\n previously sat as a bare text node between the\n two count spans, while the matches-count span\n already carried opacity-0.7 (R335 + R324 era).\n The pinDimCount span is prominent and the\n matches count is recessive — but the literal\n \" pins\" was at FULL opacity, breaking the\n chip-internal hierarchy unified across R333/\n R335/R336/R337/R338/R340. R341 wraps \" pins\"\n in an opacity-0.7 span so the chip reads:\n pinDimCount (prominent value)\n \" pins\" (recessive unit)\n \" · {N}\" (recessive count)\n Three-tier hierarchy on a single chip; 7th\n surface in the chip-internal-hierarchy arc. */}\n <span className=\"tabular-nums\" data-pin-intersection-count-dims>{pinDimCount}</span><span className=\"opacity-70\" data-pin-intersection-unit> pins</span><span className=\"opacity-70 tabular-nums\" data-pin-intersection-count-matches> · {matchAliases.length}</span>\n {/* Round 237 / Loop: ⚠ warning glyph picks up the\n always-mount-opacity-gate idiom. Pre-R237 the\n glyph was conditionally rendered on isEmpty,\n snap-mounting when filter intersection\n narrowed to 0 AND introducing a layout shift\n (ml-1 margin appears alongside the glyph,\n widening the chip by ~16px). The R236 color\n easing made the colour crossing smooth but\n the glyph still pop-jumped, breaking the\n polish that R236 just installed at this\n same chip.\n\n Always-mount the glyph with opacity gated by\n isEmpty + the same 200ms ease-out that R236\n uses on the chip's colour transition. Now the\n whole isEmpty crossing — bg, color, border,\n AND glyph visibility — eases as one\n coordinated 200ms event. ml-1 margin is\n reserved permanently, so the chip width\n stays stable through the crossing (no\n layout-shift jank against neighbouring\n chips). data-pin-intersection-warning attr\n surfaces the visibility state for test\n introspection. 11th surface in the always-\n mount-opacity-gate family (R181 / R182 /\n R183 / R213 ×2 / R214 / R215 / R221 / R222 /\n R223 / R237). */}\n <span\n className=\"ml-1\"\n aria-hidden\n data-pin-intersection-warning={isEmpty ? 'true' : 'false'}\n style={{ opacity: isEmpty ? 1 : 0, transition: 'opacity 200ms ease-out' }}\n >⚠</span>\n </span>\n </span>\n );\n })()}\n {/* Round 281 / Loop: vendor letters chip threshold tightens\n from >1 to >2 per 减法 cut #7. Pre-R281 the chip showed\n whenever ≥2 vendor types existed in the fleet — for a\n typical demo (claude + 1 other = 2 types), the chip\n rendered \"A:N C:M\" adding ~50-80px to the chip-row width.\n Tightening to >2 keeps the chip useful for fleets with\n ACTUAL vendor diversity (3+ types) where the\n composition matters at a glance, but hides it for the\n common 1-2 vendor case where the info is low-signal.\n Continues the R275-R280 simplification arc. */}\n {vendorDist.length > 2 && (\n <span\n className=\"hidden sm:inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono\"\n title=\"Hover to highlight; click to pin\"\n /* Round 698 — vendor-distribution chip-row outer wrapper\n joins the R697 wrapper-level :has() halo family. Pre-R698\n only the chrome-strip wrappers (Layout/nodeSize/zoom)\n echoed inner-button hover at the parent scope. R698\n extends the vocabulary to a NEW parent: the vendor\n distribution chip-row. When user hovers any inner\n vendor letter chip (A:N / O:N / 书:N / ?:N — all are\n `<span role=\"button\">`), the outer wrapper itself\n halos in cyan-300 (2+4 stride, 0.5/0.25 alpha), matching\n R697's cadence. Reads as \"this vendor-distribution\n control group is active\" — same gesture R697 introduced\n at the chrome strip, now at the chip strip. 54th anchor\n in multi-layer halo family — 2nd wrapper-level :has()\n anchor. CSS rule lives in globals.css ~1228+ using\n :has([role=\"button\"]:hover) instead of :has(button:hover)\n because vendor chips are role=\"button\" spans (not real\n <button>). The selector variant extends R697's :has()\n vocabulary from real buttons to ARIA buttons. */\n data-topo-chrome-vendor-distribution-wrapper=\"true\"\n >\n {vendorDist.map(v => {\n const isPinned = pinnedVendor === v.initial;\n // R88: click toggles a sticky filter the same way R60\n // pressure-bar segments toggle pinnedStatus. Visual\n // mirror = inset boxShadow using the vendor's own\n // colour, so each pinned letter sings in its own hue\n // (Anthropic green / OpenAI cyan / 书 blue / ?).\n // R101: tooltip lists the aliases that use this vendor —\n // completes the info-density triple started by R97 pills,\n // R98 node titles, R99 group-label titles. Anywhere the\n // UI shows \"A:3\" should hover-explain which 3.\n const aliases = [...onlineNodes, ...offlineNodes]\n .filter(s => {\n const vid = vendorForModel(s.model, s.runtime);\n return (vid.id === 'unknown' ? '?' : vid.initial) === v.initial;\n })\n .map(s => s.alias);\n const preview = aliases.slice(0, 8).join(', ');\n const suffix = aliases.length > 8 ? ` + ${aliases.length - 8} more` : '';\n const tooltip = isPinned\n ? `${preview}${suffix} — click again or Esc to clear`\n : `${preview}${suffix} — click to pin`;\n return (\n <span\n key={v.initial}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n /* Round 234 / Loop: vendor letter chip picks up\n tabular-nums to lock the ':N' suffix's digit\n width. Each vendor chip renders 'A:3', 'C:2',\n etc. inline at gap-0.5 — when one vendor's\n count rolls 9→10 the chip widens by the digit\n glyph delta and pushes downstream chips right,\n making the row visibly jitter. 8th surface\n in the info-density tabular-nums sweep,\n completing the HTML chip-side coverage after\n R232 working/online/active-links chips. The\n digit lives in the inner <span> at line 2194,\n but font-variant-numeric inherits, so applying\n it at the outer chip span reaches every\n descendant glyph for free. */\n /* Round 314 / Loop: vendor letter chip joins the\n R312-R313 'HTML-context data chip = font-medium'\n family. R313 weighted the 3 main chips\n (working/online/active-links); R314 closes the\n chip-row weight sweep by extending to the\n vendor letter chips ('A:N', 'O:N', '书:N',\n '?:N'). They display vendor-distribution\n data; same tier as the sibling data chips. */\n /* Round 401 / Loop: vendor letter chip closes the\n hover-lift gesture family at its last unaddressed\n interactive HTML surface. R397/R398/R399 lifted\n filter pin pills + chip-row chips (working /\n online / active-links); R400 lifted standalone\n chrome buttons (reset / fullscreen). The vendor\n letter chips (A:N / O:N / 书:N / ?:N) are\n sibling interactive chips in the same chip-row\n — clickable to toggle the vendor filter pin —\n but were not yet on the hover-lift family.\n R401 closes the gap with hover:-translate-y-px\n + transition-transform + transform-gpu added\n to the className. The inline transition list\n (box-shadow + background-color) keeps eaching\n independently — different property axes compose\n cleanly. Existing R354 glyph scale-1.1 (inner\n span) + R202 chip bg color-mix + R180 pin-mirror\n box-shadow + R354 glyph hover transform all\n preserved. data-vendor-letter-hover-lift attr\n surfaces the lift for tests. */\n // R417: `group` parent enables the count suffix to\n // brighten on chip hover via group-hover:opacity-100\n // — sibling to R355 filter-pill prefix/suffix + R414\n // chip-row unit brighten. Closes the inner-span\n // hover-brighten family at the vendor chip surface.\n // R496 — vendor letter chip joins active:scale-95 press\n // family. Last vendor-row clickable joining the family\n // R495 cashed via R490's transition-cascade dividend.\n // Same compound w/ R401 hover-lift idiom — lift-and-\n // compress on press, springs back on release.\n className=\"group tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu hover:-translate-y-px active:scale-95\"\n data-vendor-letter={v.initial}\n data-vendor-letter-count={v.count}\n data-vendor-letter-hover-lift=\"true\"\n data-vendor-pinned={isPinned ? 'true' : 'false'}\n data-vendor-hovered={hoveredVendor === v.initial ? 'true' : 'false'}\n data-vendor-aliases={aliases.join(',')}\n title={tooltip}\n // R180: smooth-pin-mirror family — see working chip above.\n // Round 202 / Loop: vendor letter chip joins the \"hover\n // deepens own identity hue\" family R193/R195/R201 built\n // up across the rest of the chip row. Pre-R202 hovering\n // a vendor letter (A/C/G/K/书/?) fired R88 canvas dim\n // via setHoveredVendor, but the chip itself stayed at\n // bg=transparent — cause silent, effect loud. R202\n // tints the chip with its OWN vendor colour at 12%\n // alpha via color-mix() so each vendor's chip lights\n // up in its own hue (Anthropic green / OpenAI cyan /\n // 书 blue / ?). No layout shift: only background-color\n // changes, no border/padding swap. transition list\n // extends the existing R180 box-shadow 150ms with\n // background-color 200ms ease-out (same splice idiom\n // R201 used on the working/online chips). color-mix()\n // is supported Chrome ≥ 111 / Safari ≥ 16.2 / FF ≥ 113;\n // for older browsers the chip falls back to its idle\n // transparent bg (graceful degradation — the canvas-\n // dim effect still fires regardless).\n /* Round 541 / Loop — vendor letter chip gains drop-\n shadow glow on hover/pin using its OWN vendor\n identity color (v.color). Sibling to R537 legend-\n swatch tier-color glow at the chip-row scope.\n Pre-R541 the vendor chip lifted on multiple\n axes (R354 inner-glyph scale-1.1, R202 bg color-\n mix tint, R180 box-shadow pin-mirror inset, R401\n hover-translate-y -1px, R496 active:scale-95\n press) but no paint-axis glow extending past\n the chip's bbox. R541 adds the outer glow at\n the paint axis so the vendor chip's identity\n color radiates beyond the chip on attention —\n same idiom as legend swatch tier-color glow.\n 2-tier alpha ladder (mirrors R538 group-label):\n pin (committed) v.color 99 (~60%)\n hover (preview) v.color 66 (~40%)\n rest none\n Pin is brighter to distinguish locked vs preview\n at the paint axis. The R180 inset box-shadow\n (pin-mirror) and R541 outer drop-shadow compose\n at pin — inside chrome reads as \"this is pinned\"\n (inset white double-ring), outside paint reads\n as \"vendor identity is locked\" (vendor-colour\n outer glow). Hover gets only the outer glow.\n 3px blur tuned to read as soft chip-halo without\n overwhelming adjacent chips in the chip row.\n filter property is in the .anet-topo-chip-focus\n class transition list (R524 banked fix), so the\n filter eases at 200ms naturally.\n Drop-shadow visual-polish family — R541 adds\n chip-row tier-color paint glow as another anchor\n in the same family pattern R537 established.\n data-vendor-glow attr ('pin' | 'hover' | 'false')\n exposes the gate state for tests. */\n data-vendor-glow={isPinned ? 'pin' : hoveredVendor === v.initial ? 'hover' : 'false'}\n style={{\n cursor: 'pointer',\n backgroundColor: (hoveredVendor === v.initial && !isPinned)\n ? `color-mix(in srgb, ${v.color} 12%, transparent)`\n : 'transparent',\n boxShadow: isPinned\n ? `inset 0 0 0 1px ${v.color}, inset 0 0 0 2px rgba(255,255,255,0.45)`\n : undefined,\n /* R578 sibling — vendor chip stacks brightness(1.15)\n onto R541 drop-shadow. Closes chip-row tier-color\n glow trio at consistent stacked-filter pattern.\n R663 — extends both pin + hover branches with a\n 2nd outer drop-shadow at 6px with halved color-\n mix opacity (60%→30%, 40%→20%). 22nd anchor in\n multi-layer halo family (1st vendor-chip anchor).\n Same R661 filter-pill stride + falloff. */\n filter: isPinned\n ? `drop-shadow(0 0 3px color-mix(in srgb, ${v.color} 60%, transparent)) drop-shadow(0 0 6px color-mix(in srgb, ${v.color} 30%, transparent)) brightness(1.15)`\n : hoveredVendor === v.initial\n ? `drop-shadow(0 0 3px color-mix(in srgb, ${v.color} 40%, transparent)) drop-shadow(0 0 6px color-mix(in srgb, ${v.color} 20%, transparent)) brightness(1.15)`\n : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, filter 200ms ease-out',\n }}\n onMouseEnter={() => setHoveredVendor(v.initial)}\n onMouseLeave={() => setHoveredVendor(prev => prev === v.initial ? null : prev)}\n onClick={() => setPinnedVendor(prev => prev === v.initial ? null : v.initial)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedVendor(prev => prev === v.initial ? null : v.initial);\n }\n }}\n >\n {/* Round 354 / Loop: vendor letter glyph scales\n 1.0 → 1.1 on hover. R88 already dims OTHER\n vendors on hover via canvas-wide opacity\n masking; R202 added a chip-level bg tint\n (color-mix 12 % alpha) so the chip itself\n responds. R354 closes the trio with a glyph-\n level lift: the focused vendor LETTER actively\n rises (transform scale) rather than the chip\n merely changing colour. Three layers of positive\n feedback on the hovered vendor + canvas-wide\n negative feedback on the others — a clean\n figure/ground separation.\n\n display: inline-block is required for transform\n to apply (inline elements ignore transform).\n transformOrigin: 'center' so the glyph pivots\n around its centre instead of arcing from the\n baseline anchor. transition rides the existing\n Tailwind 4 transform/scale list (no new\n property — Tailwind already lists transform in\n the default transition-property set). 200ms\n matches the R202 chip bg-tint timing so the\n glyph lift and chip background ease in concert. */}\n {/* Round 369 / Loop: vendor letter glyph picks up\n fontWeight 600 (font-semibold). The glyph is the\n vendor identifier — the DATA the operator scans\n in this chip (A / O / 书 / C / G / ?). R333 set\n the count suffix `:N` to text-gray-400 + tabular-\n nums and (via parent inheritance) fw 500. Pre-\n R369 the LETTER also inherited fw 500 from the\n chip's font-medium — letter and count read at\n the same weight, contradicting the data-vs-label\n hierarchy the rest of the chip-row already speaks.\n R369 lifts the letter to fw 600 so the chip now\n reads as the same two-tier pattern R362 closed\n on the working / online / active-links chips:\n chip digit/letter fw 600 (data)\n chip unit/count fw 500 (label)\n Sibling treatment to R362 — extends the R333-R341\n chip-internal-hierarchy arc to the vendor-letter\n chip surface (9th surface family). R354 transform-\n scale-on-hover + R88 canvas-dim-others + R202\n chip bg color-mix all preserved on the same span.\n data-vendor-letter-glyph-font-weight attr exposes\n the value for tests. */}\n <span\n data-vendor-letter-glyph={v.initial}\n data-vendor-letter-glyph-hover={hoveredVendor === v.initial ? 'true' : 'false'}\n data-vendor-letter-glyph-font-weight=\"600\"\n /* R676 — vendor letter glyph joins the multi-layer\n halo family using its OWN per-vendor color (v.color)\n as tint. Pre-R676 the glyph had transform: scale(1.1)\n on hover (R354) — a single geometry axis lift. Post-\n R676 adds 2-layer drop-shadow at v.color tint with\n 2+4 stride and alpha 80/40 — sibling to R671 legend-\n row count's per-tier row.fill pattern. The glyph now\n glows in its OWN brand hue under hover, carrying\n per-vendor semantic information at the chip-row\n scope (parity with the per-tier semantic at the\n legend scope).\n transition list extends with 'filter 200ms ease-out'\n matching the existing 200ms transform cadence.\n 35th anchor in multi-layer halo family — first\n per-vendor anchor. */\n /* R690 — extends R676 halo gate from hover-only to\n (hover || pin). When a vendor is pinned, the inner\n letter glyph now glows persistently in its brand\n hue alongside the outer chip (R663, already pin-\n aware via isPinned ? ... : hover). Closes pin-\n gesture symmetry across vendor chip's nested\n layers — sibling to R689 (working/online chip\n pin-gated halo). The transform scale(1.1) lift\n stays hover-only — it's a transient gesture\n affordance, not a persistent identity signal. */\n data-vendor-letter-glyph-halo-layers={(hoveredVendor === v.initial || isPinned) ? '2' : '0'}\n style={{\n color: v.color,\n display: 'inline-block',\n fontWeight: 600,\n transform: hoveredVendor === v.initial ? 'scale(1.1)' : 'scale(1)',\n transformOrigin: 'center',\n filter: (hoveredVendor === v.initial || isPinned) ? `drop-shadow(0 0 2px ${v.color}80) drop-shadow(0 0 4px ${v.color}40) brightness(1.15)` : undefined,\n transition: 'transform 200ms ease-out, filter 200ms ease-out',\n }}\n >{v.initial}</span>\n {/* Round 333 / Loop: vendor count suffix `:{N}` joins\n the R317 subordinate-text-lift family (gray-500 →\n gray-400) plus picks up tabular-nums for digit\n width-lock. Pre-R333 a vendor whose count\n crossed 9→10 widened the suffix and (since the\n parent chip has `px-2.5` padding but no fixed\n width) shifted the chip-row's downstream chips\n a couple px right. Tabular-nums locks the slot;\n gray-400 lifts the digit into the band where eye\n reads it as \"deliberate subordinate metadata\"\n rather than near-invisible chrome. data-vendor-\n letter-count exposes the span for tests. */}\n {/* R417: count suffix opacity-70 + group-hover:\n opacity-100 brightens on chip hover. Inner-span\n hover-brighten family (3rd anchor) — sibling to\n R355 filter pill prefix/suffix and R414 chip-row\n unit. Effective shade at rest: text-gray-400 ×\n 70 % alpha; on hover: full gray-400. The label-\n tier-vs-glyph differentiation persists on hover\n since the glyph (R369 fw=600) stays at full\n opacity. R333 :{count} format preserved. */}\n {/* R688 — vendor count suffix `:N` joins the multi-\n layer halo family using the SAME per-vendor v.color\n tint as the inner letter glyph (R676) and the\n outer chip (R663). Pre-R688 the suffix had only\n 2 group-hover axes (opacity 0.7→1 + tracking-wide);\n the paint-axis halo was missing. R688 closes\n vendor letter chip 3/3 (outer R663 + letter R676 +\n count R688) — all three nested elements glow in\n the SAME vendor brand hue under hover for full\n chip-level color identity coherence.\n Stride: 2+4 (text scale, same as R676 letter).\n Alpha 80/40, gated on hoveredVendor === v.initial.\n transition list extends with 'filter 200ms ease-out'.\n 48th anchor in family. */}\n <span\n className=\"text-gray-400 tabular-nums opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide\"\n data-vendor-letter-count-suffix\n /* R690 sibling — count suffix halo gate extends from\n hover-only to (hover || pin). Closes vendor chip\n 3/3 nested layers at pin-symmetry — outer (R663)\n + letter (R690 just above) + count (R690 here)\n all persistently glow when vendor is pinned. */\n data-vendor-letter-count-suffix-halo-layers={(hoveredVendor === v.initial || isPinned) ? '2' : '0'}\n style={{\n filter: (hoveredVendor === v.initial || isPinned)\n ? `drop-shadow(0 0 2px ${v.color}80) drop-shadow(0 0 4px ${v.color}40)`\n : undefined,\n transition: 'filter 200ms ease-out',\n }}\n >:{v.count}</span>\n </span>\n );\n })}\n </span>\n )}\n {/* Round 42 / Loop: extend active-links chip with the timestamp\n of the most-recent flow event. Tells the operator at a glance\n whether the topology is currently humming (last 30s) or has\n been quiet for a while — the visual flow particles and\n edge brightness only show that there IS traffic, not when\n the last one was. Reuses Round 38's relativeAgo. */}\n {(() => {\n const recent = flowLinks.reduce<number | null>((acc, l) => {\n if (!l.last_at) return acc;\n const t = parseHubTime(l.last_at);\n if (t === null) return acc;\n return acc === null || t > acc ? t : acc;\n }, null);\n const rel = recent !== null ? relativeAgo(new Date(recent).toISOString()) : null;\n // R114: tooltip lists the actual flows. Closes the\n // info-density sweep on the last chip-row hover surface\n // (R97-R113 covered everything else). Format:\n // \"alpha→beta (3), gamma→delta (1) — hover brightens all\"\n // Truncates at 6 flows with \"+N more\" so a busy fleet\n // doesn't paint a tall tooltip; the recent-signal panel\n // already shows the top 3 in detail.\n const flowList = flowLinks\n .slice(0, 6)\n .map(l => `${l.from}→${l.to} (${l.count})`)\n .join(', ');\n const flowSuffix = flowLinks.length > 6 ? ` + ${flowLinks.length - 6} more` : '';\n // R136: the chip already had cursor:pointer when flowLinks\n // > 0 (line 1877) — but no onClick was wired. The cursor\n // lied; users got the affordance signal with no follow-\n // through. Wire it to /messages, mirroring R133's footer-\n // nav idiom. Hover (R77) keeps its semantic \"preview all\n // flows on canvas\"; click is the action \"open the full\n // list\". Two distinct gestures, both meaningful. The\n // tooltip grows a \"click to open\" tail when interactive.\n // Drop the chip out of click territory entirely when\n // flowLinks is empty — no flows = no list to open.\n const isInteractive = flowLinks.length > 0;\n const tooltip = !isInteractive\n ? undefined\n : `${flowList}${flowSuffix} — hover brightens all · click to open /messages`;\n return (\n // Round 193 / Loop: the chip itself adopts a subtle cyan\n // tint on its own hover, mirroring the cyan flowEdge\n // highlight it fires on the canvas. Pre-R193 the gesture\n // was visually asymmetric:\n // hover this chip → canvas edges brighten (cyan)\n // chip itself → stays gray (silent)\n // The *cause element* (chip) gave no response while the\n // *effect element* (canvas edges) painted loud. Same\n // pin-mirror logic R165 / R180 use on the four other\n // chip-row surfaces — the chip and the canvas edge it\n // pins should speak the same color vocabulary. Tailwind\n // :hover variant cyan-500/10 bg + cyan-500/30 border +\n // cyan-200 text matches the same palette R178/R163 use\n // for the active chrome buttons. transition-colors\n // duration-200 blends the swap smoothly. Hover variant\n // only attaches when isInteractive — a chip showing\n // \"0 active links\" has no list to open and should\n // stay gray. data-active-links-clickable already\n // exposes that gate to tests.\n <span\n // Round 206 / Loop: extend the R204/R205 empty-recede\n // family to the active-links chip. Pre-R206 \"0 active\n // links\" rendered at the same bg-gray-500/10 + full\n // opacity as \"12 active links\" — same eye-no-signal\n // problem the legend count (R204) and working/online\n // chips (R205) just solved at their own grain levels.\n // Inline opacity 0.5 when !isInteractive (flowLinks=0)\n // joins R136's already-removed cursor + R114's tooltip-\n // text gate to give the empty state visual + interactive\n // + affordance signals in lockstep.\n // Tailwind transition-colors duration-200 on className\n // would be overridden by the inline transition list, so\n // we replicate color/bg/border transitions inline\n // alongside the new opacity 200ms — same splice idiom\n // R201 used on the working/online chips.\n /* Round 232 / Loop: tabular-nums on active-links chip\n (third chip in the row — matches working + online\n chip treatment so all three digits in the chip row\n stay width-stable across counter crossings). */\n /* Round 399 / Loop: active-links chip closes the 3-chip\n chip-row by extending R398's hover translateY(-1px)\n lift onto the third (rightmost) chip. The R398 family\n already covered working + online chips on the\n clickable variant; R399 adds the same gate (isInter-\n active = flowLinks.length > 0) so empty active-links\n stays planted at R206's opacity-50 receded paint.\n transition-transform + ease-out + transform-gpu join\n the inline transition list (different property axes\n compose cleanly: inline handles color/bg/border/\n opacity, className handles transform).\n Gesture-vocabulary table (post-R399 — now complete\n across the chip-row):\n working chip -1 px (R398)\n online chip -1 px (R398)\n active-links chip -1 px (R399, this round)\n filter pin pills -1 px (R397)\n recent-signal row -1 px (R143)\n legend row -1 px (R144)\n Every interactive chip in TopoGraph lifts on hover.\n data-chip-hover-lift attr exposes the lift surface\n state ('true' clickable, 'false' empty) for tests. */\n // R414: `group` parent + inner unit span group-hover-brighten — see working chip above.\n // R496 — active-links chip joins active:scale-95 press\n // family. Sibling to working+online chips (R494). Gated\n // on `isInteractive` (flowLinks.length > 0) — same R399\n // conditional pattern used for hover-lift.\n className={`group tabular-nums font-medium hidden sm:inline px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu ${\n isInteractive\n ? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95'\n : 'bg-gray-500/10 text-gray-400 border-gray-500/20'\n }`}\n data-chip-hover-lift={isInteractive ? 'true' : 'false'}\n data-chip-group-hover-brighten=\"true\"\n data-active-links-chip\n data-active-links-flow-count={flowLinks.length}\n data-active-links-clickable={isInteractive ? 'true' : 'false'}\n data-active-links-empty={isInteractive ? 'false' : 'true'}\n /* R686 — active-links chip joins the multi-layer halo\n family. Pre-R686 the chip had ~4 hover axes (bg/text/\n border swap to cyan + translate-y-px lift) but no\n paint-axis halo. R686 adds 2-layer drop-shadow at\n pal.legendAccent tint with 2+4 stride, alpha 80/40 —\n gated on hoveredActiveLinks (the state already exists\n for the canvas-edge brighten gesture). Coherent with\n R661 filter-pill chips (3+6) at the slightly tighter\n 2+4 stride matching the chip-row's smaller-chip tier.\n transition list extends with 'filter 200ms ease-out'\n so the halo eases under the same cadence as the\n existing color/bg/border tweens.\n 45th anchor in multi-layer halo family — first active-\n links-chip anchor (the dashboard's primary live-\n traffic indicator). */\n data-active-links-chip-halo-layers={hoveredActiveLinks && isInteractive ? '2' : '0'}\n title={tooltip}\n role={isInteractive ? 'link' : undefined}\n tabIndex={isInteractive ? 0 : undefined}\n style={{\n cursor: isInteractive ? 'pointer' : undefined,\n opacity: isInteractive ? 1 : 0.5,\n filter: hoveredActiveLinks && isInteractive\n ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40)`\n : undefined,\n transition: 'color 200ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',\n }}\n onMouseEnter={() => { if (isInteractive) setHoveredActiveLinks(true); }}\n onMouseLeave={() => setHoveredActiveLinks(false)}\n onClick={() => { if (isInteractive) router.push('/messages'); }}\n onKeyDown={(e) => {\n if (!isInteractive) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/messages');\n }\n }}\n >\n {/* R338 — active-links chip digit/unit split, completes\n the 5th chip surface in the R333/R335/R336/R337\n chip-internal-hierarchy arc. data-active-links-\n chip-unit exposes the unit span for tests. */}\n {/* R362 sibling — active-links chip digit gains font-semibold. */}\n {/* R539 sibling — active-links chip digit. Same idiom\n as working + online above. */}\n <span className=\"font-semibold transition-[font-weight,letter-spacing] duration-200 group-hover:font-bold group-hover:tracking-wide\" data-active-links-chip-digit>{flowLinks.length}</span><span className=\"opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide\" data-active-links-chip-unit> active link{flowLinks.length === 1 ? '' : 's'}</span>\n {rel ? (() => {\n // Round 161 / Loop: extend R160's recency-pip\n // vocabulary up one scope — from per-flow row to\n // fleet-aggregate chip. The chip already shows\n // recency in text (\"last 5s ago\"); the \" · \"\n // separator bullet was dead gray. Color the\n // bullet by freshness using the same alpha\n // ramp R160 uses on recent-signal rows:\n // ageSec ≤ 30 → 1.0 (fully fresh)\n // 30-300s → smooth decay 1.0 → 0.25\n // > 300s → 0.25 (stale floor)\n // Cyan bullet pulse when fresh + gray text tail\n // = \"is the network firing right now\" readable\n // at a glance, without parsing the timestamp\n // numerals. Same vocabulary the canvas uses\n // (R10 edge fade) and the recent-signal panel\n // uses (R160 row pip) — three nested scopes\n // now share one freshness ladder.\n const ageSec = recent !== null\n ? Math.max(0, (Date.now() - recent) / 1000)\n : 999;\n const alpha = ageSec <= 30\n ? 1\n : ageSec <= 300\n ? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */\n : 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */\n // Cyan dark / teal light to match palette legendAccent.\n const dotColor = isLight\n ? `rgba(13, 148, 136, ${alpha.toFixed(2)})`\n : `rgba(34, 211, 238, ${alpha.toFixed(2)})`;\n // Round 342 / Loop: active-links chip freshness suffix\n // wrapper text-gray-500 → text-gray-400 (R317\n // subordinate-text-lift family applied to chrome\n // inactive Layout toggle + R333 vendor count suffix).\n // The \"last 5s ago\" suffix is chip-subordinate\n // metadata; gray-500 sat near-invisible against the\n // chip's outer color, gray-400 lifts it into the band\n // where the eye reads it as deliberate freshness\n // annotation. The freshness DOT keeps its own inline\n // color: dotColor — the lift only affects the trailing\n // literal \"last {rel}\" text.\n return (\n // Round 357 / Loop: active-links chip freshness\n // suffix wrapper picks up `tabular-nums` for digit\n // width-lock. Pre-R357 the literal \"last {rel}\"\n // text (e.g. \"last 5s ago\", \"last 10s ago\",\n // \"last 1m ago\") had natural-figure digits — the\n // freshness ticker updates every second, so the\n // 9→10 boundary on the seconds counter and the\n // 59→60s → 1m flip both jittered ~1-2 px of glyph\n // width which propagated through the chip-row's\n // inline-flex layout, nudging the freshness DOT\n // and the chip's left edge. Tabular-nums on the\n // wrapper applies to all descendant digits only\n // (letters render at natural widths) so the\n // ticker stays planted across every count cross.\n // Joins the R224-R232 info-density tabular-nums\n // sweep at the chip-row freshness scope. Pure\n // paint-level change, no geometry shift on rest.\n // The R342 text-gray-400 lift + R161 dot freshness\n // alpha ramp + R317 subordinate-text-lift family\n // all preserved. data-active-links-freshness-\n // wrapper attr exposes the wrapper for tests.\n <span className=\"text-gray-400 tabular-nums\" data-active-links-freshness-wrapper>\n <span\n data-active-links-freshness-dot\n data-active-links-freshness-alpha={alpha.toFixed(2)}\n style={{\n color: dotColor,\n fontWeight: alpha > 0.7 ? 700 : 400,\n transition: 'color 200ms ease-out',\n }}\n >{' · '}</span>\n last {rel}\n </span>\n );\n })() : null}\n </span>\n );\n })()}\n <FreshnessChip sessions={sessions} />\n </div>\n </div>\n\n <div\n ref={containerRef}\n /* Round 330 / Loop (milestone): canvas wrapper rounded-lg\n → rounded-xl (8px → 12px corner radius). The biggest\n single surface on the dashboard by pixel area now reads\n as modern-SaaS-contemporary rather than 2020-conservative\n — same 4px bump R197 applied to the legend swatch and\n R295 applied to the title-block crescent. R330 ports\n the gesture to the OUTER envelope.\n Inner content (SVG viewBox 1000×680) sits behind\n `overflow-hidden`, so corner-radius change only affects\n the wrapper's own paint area and the shadow contour;\n the topo-overlap-test reads SVG-internal geometry and is\n unaffected. R254 background-color / R254 border-color /\n R263 box-shadow transitions all carry through unchanged.\n Marks R330 milestone of 5 rounds (R326-R330) of layout-\n geometry polish (gap-tier + crescent fade + trailer\n compensator + corner radius). */\n className={`relative overflow-hidden rounded-xl border shadow-2xl ${isLight ? 'shadow-zinc-900/5' : 'shadow-cyan-950/30'} ${isFullscreen ? 'flex items-center justify-center' : ''}`}\n data-topo-wrapper\n /* Round 254 / Loop: top-level TopoGraph wrapper gains theme-\n toggle transition. This is the BIGGEST theme-driven surface\n on the dashboard by pixel area — pal.containerBg fills the\n entire visible canvas area (cyber #0a0a0c ↔ light #ffffff),\n and pal.containerBorder rims it. Pre-R254 every inner\n element eased through theme but the outer wrapper hard-cut,\n visually anchoring the snap. R253 declared\n \"no visible snap remains\" prematurely — this wrapper was\n the largest holdout. 200ms ease-out matches the panel\n treatment (R247) so wrapper + panels ease as one unit.\n\n Round 263 / Loop: close R254's holdover gap — the wrapper's\n shadow-2xl + theme-conditional `shadow-{color}/{opacity}`\n Tailwind class (cyber `shadow-cyan-950/30` ↔ light\n `shadow-zinc-900/5`) ALSO changes on theme toggle, but the\n inline transition list only covered background-color +\n border-color. Result: every inner element eased through\n theme, the wrapper bg/border eased, but the wrapper's\n DROP-SHADOW snapped — a subtle but real holdover from\n R254's \"TRULY complete\" claim. Adding `box-shadow 200ms\n ease-out` to the transition list catches the className-\n driven box-shadow swap (CSS transition on box-shadow eases\n the shadow property even when its color comes from a\n Tailwind class change, because the property itself is\n transition-eligible regardless of source). */\n style={{\n background: pal.containerBg,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out, box-shadow 200ms ease-out',\n }}\n >\n {/* Round 265 / Loop: top-rail (1px-tall colored line at the top\n of the canvas wrapper) picks up theme-toggle transition.\n Pre-R265 the className `bg-gradient-to-r ${pal.topRail\n Gradient}` was theme-conditional — cyber `via-cyan-400/70`\n ↔ light `via-emerald-500/40` — but no inline transition,\n so the rail SNAPPED on theme flip while the wrapper bg\n (R254) + border (R254) + shadow (R263) all eased. The top-\n rail is the THIN BRIGHT LINE that visually anchors the\n canvas top edge — its hard color flip was a small but real\n theme-snap that broke the otherwise-eased canvas envelope.\n transition: background-image catches the className-driven\n gradient swap; Chrome ≥ 89 / Safari ≥ 14.1 / FF ≥ 96\n interpolate linear-gradients with matching stop structures\n (both gradients are `from-transparent via-X to-transparent`\n → same 3-stop layout). data-topo-top-rail makes the probe\n deterministic. */}\n <div\n className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${pal.topRailGradient}`}\n data-topo-top-rail\n style={{ transition: 'background-image 200ms ease-out' }}\n />\n\n {/* Round 158 / Loop: give the canvas SVG itself an accessible\n name + role description. R151-R157 added a11y to every\n interactive surface inside the canvas (nodes, group labels,\n badges, rows, minimap, chrome buttons) — but the canvas\n container itself was a nameless 1000×680 box. A screen\n reader that hit the SVG before tab-diving into its\n children heard nothing identifying it as \"the topology\".\n aria-roledescription gives it a meaningful announcement;\n aria-label provides a live snapshot of the network\n (online / working / active links / offline) plus the two\n canvas-scope gestures (Tab + double-click). Default SVG\n role is graphics-document so children remain navigable —\n we deliberately don't override role with \"img\" which\n would flatten the SVG to a single opaque image. */}\n <svg\n ref={svgRef}\n viewBox=\"0 0 1000 680\"\n /* Round 740 / Loop — one-time canvas entrance animation.\n SVG scales 0.99 → 1 and opacity 0.8 → 1 over 600ms with\n ease-out on FIRST PAINT only (CSS keyframes animation\n runs once per mount, no `infinite` repeat). Pure visible\n entrance polish — distinct from breath, ambient, meta,\n and a11y threads. The class is conditional on\n !reducedMotion so users who prefer reduced motion see\n the canvas at its final state immediately. After 600ms\n the canvas sits at scale(1) opacity(1); subsequent\n paints don't replay. See globals.css R740 block. */\n className={`w-full h-auto block${reducedMotion ? '' : ' anet-topo-canvas-entrance'}`}\n data-topo-canvas-entrance={reducedMotion ? 'false' : 'true'}\n preserveAspectRatio=\"xMidYMid meet\"\n aria-roledescription=\"agent network topology\"\n aria-label={(() => {\n const online = onlineNodes.length;\n const working = workingCount;\n const offline = offlineNodes.length;\n const flows = flowLinks.length;\n const parts: string[] = [];\n parts.push(`${online} agent${online === 1 ? '' : 's'} online`);\n if (working > 0) parts.push(`${working} working`);\n if (offline > 0) parts.push(`${offline} offline`);\n parts.push(`${flows} active link${flows === 1 ? '' : 's'}`);\n return `Agent network topology — ${parts.join(' · ')}. Tab to navigate nodes, double-click canvas to reset view.`;\n })()}\n /* Round 734 / Loop — aria-describedby points at the <desc>\n child added below. The existing aria-label (R7/R469) is\n DYNAMIC — encodes live counts. The <desc> is STABLE —\n encodes the structural vocabulary of the canvas. Together\n they form the WAI-ARIA name + description pair: SR\n announces the accessible name (label) first, then\n optionally reads the description on follow-up. Distinct\n a11y mechanism from R730-R733's <title> a11y children\n (which are accessible NAMES on individual decorative\n elements; this is a DESCRIPTION on the root canvas). */\n aria-describedby=\"anet-topo-canvas-desc\"\n data-topo-canvas-aria\n /* Round 469 / Loop — fleet-split numeric attrs on the root\n svg. The aria-label already encodes online/working/offline\n /flow counts in text form (R7 origin) but DOM probes had\n to PARSE the label string to extract the numbers. R469\n surfaces them as 4 numeric data-attrs alongside the R462\n dashboard-version + R466 any-hover + R467 any-pinned set\n that already live on the root svg:\n data-topo-online-count total online sessions\n data-topo-working-count subset currently working\n data-topo-offline-count offline / ghost-purged\n data-topo-flow-count active flow links\n Use cases:\n - Playwright: one-line `svg.getAttribute('data-topo-\n working-count')` instead of parsing aria-label\n - external CSS: data-attribute selectors for empty\n vs populated states (`[data-topo-online-count='0']`)\n - a11y enrichment: screen-reader scripts can read the\n numeric attrs directly\n - hub-aria parity: the hub-center text already shows\n `workingCount` digit (R130); R469 puts the same scalar\n on the canvas root for non-visual consumers.\n Composed from existing onlineNodes / workingCount /\n offlineNodes / flowLinks — no new state. */\n /* Round 502 / Loop — categorical density-tier paired with the\n R469 numeric counts. data-topo-fleet-density-tier classifies\n the fleet size into 5 buckets so external consumers (CSS\n selectors, Playwright probes, future density-conditional\n polish gates like R109 dense-label collapse at 16+ nodes)\n can branch on a stable tier name without re-deriving the\n threshold logic from the raw numeric. Buckets:\n 'empty' — onlineNodes.length === 0\n 'sparse' — 1-3 nodes\n 'normal' — 4-15 nodes\n 'dense' — 16-30 nodes (matches R109 collapse gate)\n 'very-dense' — 31+ nodes\n Picks the gate boundaries that already drive CONDITIONAL\n RENDER decisions elsewhere (R109 denseLayout = >16, R110\n plain-text fallback) so the tier name is semantically\n aligned with the visual mode the canvas already switches\n to. Composed from existing onlineNodes — no new state.\n 12th attr in the canvas state surface set (R462/R466/R467/\n R469×4/R471×2/R487/R488/R502). 12 attrs covers: build\n identity, transient/sticky inspection modes, fleet split\n numerics, fleet density tier, canvas layout/theme, canvas\n zoom, hover identity. A test harness can snapshot the\n full canvas state with 12 getAttribute calls. */\n data-topo-fleet-density-tier={\n onlineNodes.length === 0 ? 'empty' :\n onlineNodes.length <= 3 ? 'sparse' :\n onlineNodes.length <= 15 ? 'normal' :\n onlineNodes.length <= 30 ? 'dense' :\n 'very-dense'\n }\n data-topo-online-count={onlineNodes.length}\n data-topo-working-count={workingCount}\n data-topo-offline-count={offlineNodes.length}\n data-topo-flow-count={flowLinks.length}\n /* Round 471 / Loop — surface 2 remaining canvas-level mode\n attrs alongside the R462/R466/R467/R469 set. Pre-R471 the\n root svg exposed 7 attrs but tests probing \"what layout\n is active\" had to query DOM internals (data-topo-chrome-\n layout-active on the chrome button row) or parse the URL\n for theme. R471 puts both modes on the root for one-stop\n snapshot reads:\n data-topo-layout — 'ring' | 'grid'\n data-topo-theme — 'cyber' | 'light'\n Together with R469 the canvas root now carries 9 cross-\n cutting attrs (1 build identity + 2 inspection mode + 4\n fleet split + 2 layout/theme). Test harness can read the\n FULL canvas state with 9 getAttribute calls; no traversal\n into chrome strip / theme provider / panel rows.\n Composed from existing `layout` (R138 ring↔grid toggle\n state) + `isLight` (R12 theme palette gate) — no new\n state, zero re-render cost. */\n data-topo-layout={layout}\n data-topo-theme={isLight ? 'light' : 'cyber'}\n /* Round 487 / Loop — extends R469/R471 root-svg state surface\n with current zoom level (numeric attr, 2 decimals). Pre-\n R487 the canvas zoom was queryable via `data-topo-minimap-\n viewport-glow='true'` boolean (R481, gated at > 1.5) but\n the exact zoom number only lived in the chrome-strip span\n (`{Math.round(view.zoom * 100)}%`). Tests + external CSS\n that need the zoom value had to traverse to the chrome\n strip or read view state via React internals.\n R487 surfaces it at the canvas root, consistent with\n R469's fleet-count numeric pattern. Two-decimal precision\n matches the internal `view.zoom` float without losing\n info. Composed from existing state — no new state.\n Root svg attribute set now 10 attrs total:\n R462 data-dashboard-version build identity\n R466 data-topo-any-hover transient mode\n R467 data-topo-any-pinned sticky mode\n R469 data-topo-online-count fleet (4 numeric)\n R469 data-topo-working-count\n R469 data-topo-offline-count\n R469 data-topo-flow-count\n R471 data-topo-layout canvas mode\n R471 data-topo-theme canvas mode\n R487 data-topo-zoom canvas zoom */\n data-topo-zoom={view.zoom.toFixed(2)}\n /* Round 488 / Loop — pairs the R466 hover-aggregate BOOLEAN\n with the corresponding hover IDENTITY attr. Pre-R488 a\n test harness could query \"is anything hovered\" but had to\n traverse per-node `data-node` elements with focus-state\n attrs to recover WHICH alias. R488 surfaces it directly\n at canvas root. Empty string when null (always-present\n attr, consistent with the 10-attr state-surface set —\n never `undefined`-collapsed so observers can rely on a\n single `getAttribute('data-topo-hovered-alias')` returning\n either '' or the alias string).\n Note: only the `hoveredAlias` axis (R466's first source)\n gets the identity twin in R488. The other 5 hover sources\n (hoveredHub / hoveredEdgeKey / hoveredGroupLabel / hovered\n Status / hoveredVendor) are non-alias-shaped (hub center\n is singleton; edge has `from→to` key; status/vendor are\n categorical) — separate dedicated attrs if/when needed.\n Root svg attribute set now 11 attrs total. */\n data-topo-hovered-alias={hoveredAlias ?? ''}\n /* Round 504 / Loop — categorical pin-aspect attr paired with\n R467 any-pinned boolean and R488 hovered-alias identity.\n Pre-R504 the canvas state surface set told tests WHETHER\n any pin was active (R467 boolean) but tests had to enumerate\n 4 individual state vars to determine WHICH pin axis fired:\n pinnedStatus legend-row status filter\n pinnedGroup prefix-cluster lock\n pinnedVendor vendor-chip filter\n pinnedEdgeKey edge-focus\n R504 surfaces the active aspect as a single categorical\n attribute: data-topo-pinned-aspect ∈\n 'none' no pin active\n 'status' pinnedStatus only\n 'group' pinnedGroup only\n 'vendor' pinnedVendor only\n 'edge' pinnedEdgeKey only\n 'multi' 2 or more pins active simultaneously\n ('multi' covers cross-cutting filters — e.g. user pins\n status='working' AND vendor='claude' simultaneously to\n narrow the canvas. Each pin axis is independently\n dismissable via Esc / individual chip click, so multi\n states are reachable and worth surfacing as a distinct\n tier.)\n 13th attr in the canvas state surface set after R502.\n Composed from 4 existing state vars — no new state. */\n /* Round 512 / Loop — 14th canvas state attr. groupBoxes.length\n surfaces the count of cluster boxes currently rendered in\n grid layout (always 0 in ring). Paired with R502 categorical\n density tier + R469 fleet numerics for a complete cluster-\n cardinality surface:\n R469 data-topo-online-count node-count\n R502 data-topo-fleet-density-tier categorical\n R512 data-topo-cluster-count cluster-count ← this round\n Use cases:\n - Playwright: assert orphan-band existence by\n `cluster-count === N + 1` vs prefix-only `=== N`\n - external CSS: `[data-topo-cluster-count='1']` to apply\n single-cluster grid-specific layout adjustments\n - future polish gates: cluster-count > N could trigger\n dense-grid mode\n Composed from existing `groupBoxes.length` — no new state.\n Always renders (0 in ring layout, N in grid), so tests can\n rely on attribute presence + value. */\n data-topo-cluster-count={groupBoxes.length}\n /* Round 513 / Loop — 15th canvas state attr. Surfaces the\n user's prefers-reduced-motion preference directly on the\n root SVG so external CSS / Playwright tests can branch on\n a11y state without re-reading the media query.\n reducedMotion is already in component scope (R29 a11y\n blanket reads it via a useEffect listener); R513 just\n exposes it as a stable attribute handle.\n Use cases:\n - Playwright: assert reduced-motion gates from one attr\n read instead of mocking media-query state per test\n - External CSS hooks: `[data-topo-prefers-reduced-motion=\n \"true\"]` to apply paint-only overrides (e.g. mute\n hover glows entirely on a11y instead of just\n disabling transitions)\n - Future polish rounds: any motion-gated render can\n read this attr server-side without the media-query\n hydration mismatch risk\n 'true' / 'false' string values (consistent with R466/R467\n boolean attrs). */\n data-topo-prefers-reduced-motion={reducedMotion ? 'true' : 'false'}\n /* Round 515 / Loop — 16th canvas state attr. Surfaces the\n fullscreen-mode state directly on root SVG so external\n consumers don't have to traverse the chrome strip's\n `data-topo-chrome-fullscreen-active` button attr (which\n measures the BUTTON state, not the canvas state — they\n agree, but reading from the root is semantically cleaner\n for canvas-state probes).\n Composed from existing isFullscreen React state (R103\n fullscreen toggle).\n Use cases:\n - Playwright: assert canvas mode in one attr read\n (paired with R471 data-topo-layout for ring/grid +\n R487 data-topo-zoom for zoom level + R513 reduced-\n motion for a11y mode = 4-axis canvas-mode probe)\n - External CSS: `[data-topo-fullscreen=\"true\"]` to\n apply fullscreen-only paint adjustments outside the\n React tree (e.g. body-level scrollbar hide)\n 'true' / 'false' string values (consistent with R466/\n R467/R513 boolean attrs). */\n data-topo-fullscreen={isFullscreen ? 'true' : 'false'}\n /* Round 516 / Loop — 17th canvas state attr. Surfaces the\n grid layout's content-bottom y-coordinate so tests can\n verify grid content doesn't extend past the viewBox or\n collide with chrome elements positioned below the canvas.\n Composed from existing gridContentBottom derived state\n (computed at line ~915 from gy0 + totalRows * cellH + 8).\n In ring layout, gridContentBottom is 0 (no grid). In grid\n layout it's the actual pixel y-coordinate where the\n cluster bands end.\n Use cases:\n - Playwright: assert grid layout doesn't exceed viewBox\n height (680) without re-computing the layout math\n - External CSS: `[data-topo-grid-content-bottom='0']` to\n distinguish ring-mode (no grid content) from grid-mode\n in CSS without parsing layout attr\n - Future polish gates: if cluster count grows large\n enough to push grid bottom past viewBox, can trigger\n a 'compact' mode automatically */\n data-topo-grid-content-bottom={gridContentBottom}\n /* Round 710 / Loop (milestone) — 18th canvas state attr.\n Surfaces the 呼吸感 (breath) family's full cadence rolodex\n as a JSON catalog directly on the root SVG. Pre-R710 the\n 16 individual breath anchors each had their own data attrs\n (data-topo-section-title-breath=\"10s\", data-recent-panel-\n title-breath=\"8s\", ...) but tests / dev probes had to\n enumerate per-element attrs to reconstruct the family\n map. R710 closes the breath family vocabulary AS DATA —\n one attr read, full catalog.\n\n Format: JSON object — KEYS are cadence in seconds (string-\n coded for JSON), VALUES are arrays of anchor names. Anchors\n at the same cadence share a key entry (e.g.\n \"6\": [\"watermark text\",\"kicker\"] — both at 6s).\n\n Cadence catalog as of R710 (5 mature respiratory patterns):\n title-block trio + envelope 5/6/10/11 s\n panel-pair parity 8 s (× 2)\n chrome data + control-trio 9 + 17/19/23 s\n canvas-brand-pair coprime nested 6/7 × 13/15 s\n background baseline 3/4 s\n 16 anchors / 14-tier ladder.\n\n Inline JSON.stringify on each render — small cost, identical\n string. The rolodex is invariant across props/state so the\n attr value never changes; React's attribute-write skip on\n identical strings means no DOM thrash.\n\n Use cases:\n - Playwright: snapshot the breath family in ONE attr read\n (assert against expected catalog without enumerating\n per-element attrs)\n - External CSS hooks (rare but possible): substring match\n like [data-topo-respiratory-rolodex*='\"23\"']\n - Dev tools / docs generators: parse the catalog to\n produce a visual map of the family\n - Future rounds: when adding a new breath anchor, this\n attr's update is the documentation step\n\n R710 = milestone closure on the breath family vocabulary\n as a single-attr DOM-surface contract. */\n data-topo-respiratory-rolodex={JSON.stringify({\n \"3\": [\"row hot\"],\n \"4\": [\"hub idle\"],\n \"5\": [\"brand logo\"],\n \"6\": [\"watermark text\", \"kicker\"],\n \"7\": [\"crescent inner\"],\n \"8\": [\"recent title\", \"legend title\"],\n \"9\": [\"zoom-level readout\"],\n \"10\": [\"H2 section title\"],\n \"11\": [\"title-block envelope\"],\n \"13\": [\"crescent wrapper envelope\"],\n \"15\": [\"watermark wrapper envelope\"],\n \"17\": [\"Layout wrapper\"],\n \"19\": [\"nodeSize wrapper\"],\n \"21\": [\"reset button\"],\n \"23\": [\"zoom wrapper\"],\n \"25\": [\"fullscreen button\"],\n })}\n /* Round 716 / Loop — companion to R710 rolodex catalog: surface\n the 5 DUAL-AXIS surfaces (R711/R712/R713×2/R714/R715) as a\n second JSON catalog directly on the root SVG. R710 tracks\n which cadences host which anchors; R716 tracks which anchors\n also carry a 2nd animation property (multi-axis breath),\n closing the breath family's meta-documentation thread.\n\n Format: JSON array of {anchor, cadence_s, axes} entries.\n `axes` lists the animation property names in order:\n [\"opacity\", \"transform-scale\"] — CSS/HTML dual\n [\"opacity\", \"letter-spacing\"] — SVG text wordmark dual\n [\"opacity\", \"font-size\"] — SVG text header dual\n\n 5 dual-axis surfaces / 6 underlying elements (panel-pair\n counts as 2):\n R699 + R714 kicker 6s opacity + transform-scale\n R703 + R715 zoom-level 9s opacity + transform-scale\n R702 + R711 H2 10s opacity + transform-scale\n R519 + R712 watermark 6s opacity + letter-spacing\n R700 + R713 recent 8s opacity + font-size\n R701 + R713 legend 8s opacity + font-size\n\n Inline JSON.stringify on each render — small cost, identical\n string. React skips identical-string attribute writes; no\n DOM thrash.\n\n Use cases:\n - Playwright: snapshot which surfaces are dual-axis in\n ONE attr read (assert against expected list without\n enumerating per-element axis attrs)\n - External CSS: substring match like\n [data-topo-respiratory-dual-axis-surfaces*='\"kicker\"']\n - Dev tools / docs generators: combine with R710 to\n produce full breath-family map (cadences + axes)\n - Future rounds: when adding a new dual-axis to an\n existing breath anchor, append to this list */\n /* Round 721 / Loop — kicker entry promoted from dual-axis to\n TRIPLE-axis. R699 opacity + R714 transform-scale joined\n by R721 text-shadow at the same 6 s cadence. The catalog\n attribute name stays \"dual-axis-surfaces\" for back-compat\n (consumers querying `*='\"kicker\"'` continue to work) but\n the axes array now lists 3 entries. R721 widens the test\n to allow axes.length 2..3 and to recognise \"text-shadow\"\n alongside the prior 2nd-axis taxonomy. Future polysemic\n surfaces (3+ axes) append in the same shape. */\n data-topo-respiratory-dual-axis-surfaces={JSON.stringify([\n { anchor: \"kicker\", cadence_s: 6, axes: [\"opacity\", \"transform-scale\", \"text-shadow\"] },\n { anchor: \"zoom-level\", cadence_s: 9, axes: [\"opacity\", \"transform-scale\", \"text-shadow\"] },\n { anchor: \"H2\", cadence_s: 10, axes: [\"opacity\", \"transform-scale\", \"text-shadow\"] },\n { anchor: \"watermark\", cadence_s: 6, axes: [\"opacity\", \"letter-spacing\", \"text-shadow\"] },\n { anchor: \"recent\", cadence_s: 8, axes: [\"opacity\", \"font-size\", \"text-shadow\"] },\n { anchor: \"legend\", cadence_s: 8, axes: [\"opacity\", \"font-size\", \"text-shadow\"] },\n ])}\n /* Round 717 / Loop — 3rd META catalog, completing the breath\n family's self-describing meta-documentation triangle. R710\n catalogs cadences (cadence → anchors); R716 catalogs axes\n (per dual-axis surface); R717 catalogs PATTERNS (5 mature\n structural respiratory patterns with their cadence shapes).\n\n Together these 3 attrs let any consumer reconstruct the\n FULL breath family graph in 3 attr reads — one for each\n axis of family organization:\n R710 WHAT cadences exist + WHO breathes at each\n R716 WHICH surfaces breathe on multiple axes\n R717 HOW the cadences cluster into structural patterns\n 3 orthogonal axes of family-shape introspection.\n\n Pattern catalog:\n title-block trio + envelope (5/6/10s + 11s wrapper)\n panel-pair parity (8s × 2)\n chrome strip data + control-trio (9s + 17/19/23s)\n canvas-brand-pair coprime nested envelopes (6/7s × 13/15s)\n background baseline single-anchor pair (3s + 4s)\n 5 patterns / 16 anchors / 14 cadence tiers — these are the\n same numbers as R710 (sum of anchors per pattern = 16, sum\n of unique cadences per pattern = 14).\n\n Each pattern entry: {name, cadences, anchors, shape}. The\n `shape` field is a one-word taxonomy:\n \"trio-with-envelope\" 3 inner + 1 outer wrapper\n \"parity\" same cadence on two siblings\n \"tiered-with-trio\" single anchor + N-cadence trio\n \"coprime-nested-pair\" each member has wrapper + inner\n \"baseline-pair\" two single-anchor cadences\n\n Inline JSON.stringify on each render — invariant string,\n no DOM thrash. R710/R716 unchanged (each catalog is\n orthogonal). Future rounds: when adding a new structural\n pattern, append to this list as the doc step. */\n /* Round 724 / Loop — 6TH pattern entry added: `triple-axis-pair`.\n R721 made kicker the first triple-axis surface; R722 made\n watermark the second; R723 surfaced the triple-axis tier\n as its own catalog. R724 promotes the \"6 s triple-axis\n pair\" observation to a NAMED pattern in R717, completing\n the family's pattern-axis documentation of this structural\n motif (both 6 s, both with text-shadow, both sparse-hover\n read-target text).\n\n Anchors overlap with prior patterns: kicker is also in\n \"title-block\" (cadences 5/6/10/11), watermark text is\n also in \"canvas-brand-pair\" (cadences 6/7/13/15). That\n non-exclusivity is INTENDED — patterns slice the family\n by STRUCTURAL SHAPE, not partition by tier. The \"triple-\n axis-pair\" pattern is orthogonal to the other 5: it\n groups by AXIS COUNT + CADENCE COINCIDENCE rather than\n by spatial role.\n\n Cross-check (R724 test): the anchors here must equal\n R723's triple-axis-surfaces anchor set. */\n data-topo-respiratory-patterns={JSON.stringify([\n { name: \"title-block\", cadences: [5, 6, 10, 11], anchors: [\"brand logo\", \"kicker\", \"H2 section title\", \"title-block envelope\"], shape: \"trio-with-envelope\" },\n { name: \"panel-pair\", cadences: [8], anchors: [\"recent title\", \"legend title\"], shape: \"parity\" },\n { name: \"chrome-strip\", cadences: [9, 17, 19, 21, 23, 25], anchors: [\"zoom-level readout\", \"Layout wrapper\", \"nodeSize wrapper\", \"reset button\", \"zoom wrapper\", \"fullscreen button\"], shape: \"tiered-with-quintet\" },\n { name: \"canvas-brand-pair\", cadences: [6, 7, 13, 15], anchors: [\"watermark text\", \"crescent inner\", \"crescent wrapper envelope\", \"watermark wrapper envelope\"], shape: \"coprime-nested-pair\" },\n { name: \"background\", cadences: [3, 4], anchors: [\"row hot\", \"hub idle\"], shape: \"baseline-pair\" },\n { name: \"triple-axis-pair\", cadences: [6], anchors: [\"kicker\", \"watermark text\"], shape: \"6s-triple-pair\" },\n /* Round 728 / Loop — 8 s triple-axis pair (recent + legend\n panel titles). Mirror to R724's 6 s pair: both pairs are\n parity-shape (members at same cadence) and serve as\n structural mirrors at different cadences. */\n { name: \"triple-axis-pair-8s\", cadences: [8], anchors: [\"recent title\", \"legend title\"], shape: \"8s-triple-pair\" },\n /* Round 726 / Loop — companion to \"triple-axis-pair\" (R724).\n R725 added H2 as a 3rd triple-axis surface at 10 s, proving\n the tier is multi-cadence rather than 6 s-locked. R724's\n pair pattern still captures the 6 s subset; R726 adds the\n TIER-LEVEL pattern containing all 3 triple-axis members\n across both cadences. Pattern axis now has BOTH grain\n sizes — pair (6s subset) and tier (full set):\n triple-axis-pair cadences [6] anchors [kicker, watermark text]\n triple-axis-tier cadences [6, 10] anchors [kicker, watermark text, H2 section title]\n Pair anchors ⊂ Tier anchors; pair cadences ⊂ tier cadences.\n The R726 test verifies this strict subset relationship\n at runtime. Future triple-axis surfaces append HERE (the\n tier) and conditionally to the pair (only if at 6 s). */\n { name: \"triple-axis-tier\", cadences: [6, 8, 9, 10], anchors: [\"kicker\", \"watermark text\", \"recent title\", \"legend title\", \"zoom-level readout\", \"H2 section title\"], shape: \"tier-multi-cadence\" },\n /* Round 737 / Loop — 9TH pattern entry formalizes the\n R735/R736 scan beam ambient pair. CROSS-FAMILY entry:\n prior 8 patterns are all in the respiratory (breath)\n family; this one lives in the AMBIENT family but uses\n the same R717 \"pattern axis\" introspection vocabulary.\n\n Cadences [23, 30] are coprime (gcd = 1) — the beams\n never phase-lock; over time the visual rhythm reads as\n \"ambient crosshair drift\". Shape \"coprime-crosshair\":\n - \"coprime\" captures the cadence pair property\n - \"crosshair\" captures the spatial geometry\n (orthogonal sweep directions intersecting at moving\n canvas points each cycle)\n\n Anchors use the SVG element data-attr names rather\n than rolodex anchor keys (the breath family's pattern\n entries use rolodex names; ambient family pattern\n entries use element names since rolodex is breath-\n scoped). R737 test verifies anchor presence in the\n catalog regardless of the naming convention. */\n { name: \"scan-beam-pair\", cadences: [23, 30], anchors: [\"scan beam horizontal\", \"scan beam vertical\"], shape: \"coprime-crosshair\" },\n /* R739 — scan-beam-trio supersets the pair with the new\n diagonal beam. cadences [19, 23, 30] are pairwise\n coprime (19 prime, 23 prime, gcd(23,30)=1). Shape\n \"coprime-trio\": all 3 cadences mutually coprime,\n 3 sweep directions (horizontal, vertical, diagonal). */\n { name: \"scan-beam-trio\", cadences: [19, 23, 30], anchors: [\"scan beam horizontal\", \"scan beam vertical\", \"scan beam diagonal\"], shape: \"coprime-trio\" },\n ])}\n /* Round 720 / Loop — 4TH orthogonal meta-doc catalog: tiers.\n Joins R710 (cadences) + R716 (axes) + R717 (patterns) to\n form a 4-axis introspection quadrilateral over the breath\n family:\n\n R710 data-topo-respiratory-rolodex — cadence axis\n R716 data-topo-respiratory-dual-axis-surfaces — axis axis\n R717 data-topo-respiratory-patterns — pattern axis\n R720 data-topo-respiratory-tiers — tier axis ← this round\n\n Patterns (R717) group anchors by STRUCTURAL SHAPE; tiers\n (R720) group anchors by FUNCTIONAL ROLE. The two slicings\n are orthogonal:\n - chrome-strip pattern spans 3 tiers (data + control-wrapper + atomic-control)\n - title-block pattern is exactly 1 tier\n - canvas-brand-pair pattern is exactly 1 tier\n A given anchor belongs to exactly 1 tier (tiers are\n mutually exclusive), so the catalog is a partition of the\n 18-anchor family into 7 buckets:\n\n data (1) zoom-level readout\n control-wrapper (3) Layout / nodeSize / zoom wrappers\n atomic-control (2) reset / fullscreen buttons\n title-block (4) brand logo, kicker, H2, envelope\n panel-title (2) recent / legend titles\n canvas-brand (4) watermark text, crescent inner, two envelopes\n background (2) row hot, hub idle\n\n Cross-check at runtime: sum of tier anchor counts must\n equal the flat anchor count in the rolodex (R710); each\n tier-listed anchor must appear in the rolodex.\n\n Inline JSON.stringify — invariant string, no DOM thrash.\n R710/R716/R717 unchanged. */\n data-topo-respiratory-tiers={JSON.stringify([\n { name: \"data\", anchors: [\"zoom-level readout\"] },\n { name: \"control-wrapper\", anchors: [\"Layout wrapper\", \"nodeSize wrapper\", \"zoom wrapper\"] },\n { name: \"atomic-control\", anchors: [\"reset button\", \"fullscreen button\"] },\n { name: \"title-block\", anchors: [\"brand logo\", \"kicker\", \"H2 section title\", \"title-block envelope\"] },\n { name: \"panel-title\", anchors: [\"recent title\", \"legend title\"] },\n { name: \"canvas-brand\", anchors: [\"watermark text\", \"watermark wrapper envelope\", \"crescent inner\", \"crescent wrapper envelope\"] },\n { name: \"background\", anchors: [\"row hot\", \"hub idle\"] },\n ])}\n /* Round 723 / Loop — 5TH orthogonal meta-doc catalog: triple-\n axis surfaces. R721 made kicker the first triple-axis\n surface; R722 made watermark the second. R723 surfaces\n the triple-axis TIER as a first-class catalog rather\n than leaving it implicit in R716's dual-axis-surfaces\n (where these entries appear with axes.length === 3 — a\n back-compat semi-overload of the catalog's name).\n\n Meta-doc surfaces post-R723 (pentagon):\n R710 data-topo-respiratory-rolodex — cadence axis\n R716 data-topo-respiratory-dual-axis-surfaces — axis axis\n R717 data-topo-respiratory-patterns — pattern axis\n R720 data-topo-respiratory-tiers — tier axis\n R723 data-topo-respiratory-triple-axis-surfaces — multi-axis axis ← this round\n\n Entries are a STRICT SUBSET of R716's entries — every\n triple-axis surface here also appears in R716 with the\n same anchor + cadence + axes (consumers querying R716\n by anchor still work; R723 is the dedicated index for\n \"give me only the >=3-axis surfaces\"). When future\n surfaces gain a 3rd axis, they append to both catalogs.\n\n Structural pattern note: both current triple-axis members\n sit at the SAME 6 s cadence and share text-shadow as the\n 3rd axis — the \"6 s triple-axis PAIR\". A future R7?? can\n elevate this to a named entry in the R717 patterns catalog\n (`triple-axis-pair`, `6s-triple-pair` shape) if a 3rd\n triple-axis surface arrives at a different cadence and\n requires the pattern to be the discriminator. */\n data-topo-respiratory-triple-axis-surfaces={JSON.stringify([\n { anchor: \"kicker\", cadence_s: 6, axes: [\"opacity\", \"transform-scale\", \"text-shadow\"] },\n { anchor: \"watermark\", cadence_s: 6, axes: [\"opacity\", \"letter-spacing\", \"text-shadow\"] },\n { anchor: \"recent\", cadence_s: 8, axes: [\"opacity\", \"font-size\", \"text-shadow\"] },\n { anchor: \"legend\", cadence_s: 8, axes: [\"opacity\", \"font-size\", \"text-shadow\"] },\n { anchor: \"zoom-level\", cadence_s: 9, axes: [\"opacity\", \"transform-scale\", \"text-shadow\"] },\n { anchor: \"H2\", cadence_s: 10, axes: [\"opacity\", \"transform-scale\", \"text-shadow\"] },\n ])}\n /* Round 729 / Loop — 6TH orthogonal meta-doc catalog: aggregate\n stats. R710 (cadences) + R716 (axes) + R717 (patterns) +\n R720 (tiers) + R723 (triple-axis) + R729 (stats). The\n introspection layer goes from pentagon → HEXAGON.\n\n Prior 5 catalogs are STRUCTURAL (list anchors by some axis).\n R729 is the aggregate stats — totals + ranges derivable\n from the others but explicit here as a queryable surface\n for tooling (debug overlays, family-size sanity checks).\n\n Stats post-R728 (frozen at this round's snapshot):\n total_anchors: 18 (rolodex flat count)\n cadences: 16 (R710 keys)\n cadence_range_s: { min: 3, max: 25 } span 22 s\n cadence_arc_s: 22 (max - min)\n axis_counts: { single: 12, dual: 0, triple: 6 }\n patterns_count: 8 (R717 entries)\n tiers_count: 7 (R720 entries)\n triple_axis_pairs: 2 (6 s + 8 s pairs in R717)\n triple_axis_solos: 2 (zoom-level + H2 in R717 tier)\n\n axis_counts.dual === 0 is intentionally surfaced: R725-R728\n promoted all prior dual-axis members to triple-axis. The\n dual-axis tier being empty is a structural milestone, not\n a documentation bug. R716's name stays \"dual-axis-surfaces\"\n for back-compat (consumers querying by anchor still work);\n the axis-count semantic is now reflected in this stats\n catalog.\n\n Cross-checks at runtime (R729 test):\n total_anchors === Σ rolodex anchor counts\n axis_counts.triple === R723 entries length\n patterns_count === R717 length\n tiers_count === R720 length\n cadence_range matches rolodex keys */\n data-topo-respiratory-axis-count-stats={JSON.stringify({\n total_anchors: 18,\n cadences: 16,\n cadence_range_s: { min: 3, max: 25 },\n cadence_arc_s: 22,\n axis_counts: { single: 12, dual: 0, triple: 6 },\n /* R737 — patterns_count bumped 8 → 9 to admit the new\n cross-family `scan-beam-pair` entry. triple_axis_pairs\n and triple_axis_solos stay unchanged (the new pattern\n isn't triple-axis). */\n /* R739 — patterns_count bumped 9 → 10 to admit the new\n cross-family `scan-beam-trio` entry. ambient family\n grows: ambient_patterns 1 → 2 (pair + trio coexist),\n ambient_cadences 2 → 3 (added 19s for diagonal beam),\n scan_beam_pairs stays 1 (pair pattern unchanged). */\n patterns_count: 10,\n tiers_count: 7,\n triple_axis_pairs: 2,\n triple_axis_solos: 2,\n breath_patterns: 8,\n ambient_patterns: 2,\n pattern_families: 2,\n scan_beam_pairs: 1,\n ambient_cadences: 3,\n })}\n /* Round 732 / Loop — 7TH orthogonal meta-doc catalog: a11y\n titles. R730/R731 added SVG <title> children to 5 decorative\n SVG elements; R732 surfaces that coverage as a queryable\n catalog. Pentagon (R723) → Hexagon (R729) → HEPTAGON.\n\n Distinct from the 6 respiratory meta-docs (R710/R716/R717/\n R720/R723/R729): those document the BREATH family by\n various axes; R732 documents the A11Y sub-family by\n accessible-name mapping. Same introspection-as-data\n philosophy, different family.\n\n R732 test cross-validates each entry: the surface key\n selector finds an element, and the element has a direct\n <title> child whose textContent equals the catalog's\n accessible_name. Future a11y additions append here\n alongside the actual <title> child. */\n data-topo-a11y-titles={JSON.stringify([\n { surface: \"watermark text\", selector: \"[data-topo-brand-watermark]\", accessible_name: \"sleep2agi · brand watermark\" },\n { surface: \"legend panel title\", selector: \"[data-legend-panel-title]\", accessible_name: \"legend · status / vendor / runtime swatch panel\" },\n { surface: \"title-block brand logo\", selector: \"[data-topo-brand-logo]\", accessible_name: \"sleep2agi · brand logo\" },\n { surface: \"canvas-corner crescent\", selector: \"[data-topo-brand-canvas-mark]\", accessible_name: \"sleep2agi · canvas-corner brand mark\" },\n /* R733 — hub-highlight added; first non-brand a11y surface.\n Note the 3-part accessible_name: \"<name> · <role> · <state>\".\n The R732 canonical-format regex matches /^[^·]+ · [^·]+$/\n (exactly 2 parts) — R733 hub uses an extended 3-part form.\n The R733 test handles this by relaxing the canonical-format\n regex to `[^·]+( · [^·]+)+` (≥2 parts). */\n { surface: \"hub-highlight disc\", selector: \"[data-topo-hub-highlight]\", accessible_name: \"hub · network center · idle indicator\" },\n /* R735 — canvas scan beam ambient animation gains a11y <title>.\n First non-static (animated) titled surface; 3-part canonical\n form with cadence as state context. */\n { surface: \"canvas scan beam\", selector: \"[data-topo-canvas-scan-beam]\", accessible_name: \"canvas scan beam · ambient sweep · 30s cycle\" },\n /* R736 — vertical scan beam companion to R735. Coprime\n cadence (23s) with R735 (30s) — ambient crosshair drift. */\n { surface: \"canvas scan beam vertical\", selector: \"[data-topo-canvas-scan-beam-vertical]\", accessible_name: \"canvas scan beam vertical · ambient sweep · 23s cycle\" },\n /* R739 — diagonal scan beam, completes ambient trio. */\n { surface: \"canvas scan beam diagonal\", selector: \"[data-topo-canvas-scan-beam-diagonal]\", accessible_name: \"canvas scan beam diagonal · ambient sweep · 19s cycle\" },\n ])}\n /* Round 741 / Loop — 8TH orthogonal meta-doc catalog:\n animation temporal modes. The introspection layer goes\n from heptagon → OCTAGON. Distinct from prior 7 catalogs\n (rolodex / dual-axis / patterns / tiers / triple-axis /\n stats / a11y-titles): R741 documents WHEN animations run\n (lifecycle / temporal mode) rather than HOW (axes /\n cadences / shapes). Orthogonal to all prior dimensions.\n\n Three temporal modes established by R735–R740:\n\n infinite-rest breath family (R699+)\n - rest-axis animation, runs forever\n - members: opacity/scale/text-shadow\n pulses on kicker, watermark, H2,\n recent/legend titles, zoom-level,\n chrome wrappers, etc.\n\n infinite-sweep ambient family (R735+)\n - sweep-axis animation, runs forever\n - members: scan beams (horizontal /\n vertical / diagonal)\n\n one-shot-mount entrance family (R740)\n - mount-axis animation, plays once\n on first paint\n - members: canvas root (scale 0.99→1\n + opacity 0.8→1 over 600 ms)\n\n Each entry has {mode, family, members, examples}. The\n members count is a snapshot at R741 time; future rounds\n update as new surfaces join. examples is a small sample\n (≤3 per mode) — not the full list (that's in the per-\n family catalogs already). */\n data-topo-animation-temporal-modes={JSON.stringify([\n { mode: \"infinite-rest\", family: \"breath\", members: 18, examples: [\"kicker\", \"watermark text\", \"H2 section title\"] },\n { mode: \"infinite-sweep\", family: \"ambient\", members: 3, examples: [\"scan beam horizontal\", \"scan beam vertical\", \"scan beam diagonal\"] },\n /* R742 — entrance family +1 member: title-block joins\n canvas root as 2nd one-shot-mount surface. Staggered\n choreography: canvas 0-600ms, title-block 200-700ms. */\n /* R743 — entrance family +1 → 3 members: chrome strip joins\n canvas root + title-block, completing the entrance trio.\n 3-stage cascade: 0-600 / 200-700 / 400-900 ms. */\n { mode: \"one-shot-mount\", family: \"entrance\", members: 3, examples: [\"canvas root\", \"title-block\", \"chrome strip\"] },\n ])}\n data-topo-pinned-aspect={(() => {\n const aspects: string[] = [];\n if (pinnedStatus) aspects.push('status');\n if (pinnedGroup) aspects.push('group');\n if (pinnedVendor) aspects.push('vendor');\n if (pinnedEdgeKey) aspects.push('edge');\n if (aspects.length === 0) return 'none';\n if (aspects.length === 1) return aspects[0];\n return 'multi';\n })()}\n /* Round 466 / Loop — aggregate hover signal on the root SVG.\n Exposes a single boolean `data-topo-any-hover` that\n reflects whether ANY hover state in the topology is\n active. Composed from the existing per-surface hover\n vars; doesn't introduce new state. Useful for:\n - Playwright tests asserting \"topology entered a hover\n mode\" without enumerating per-surface attrs\n - external CSS hooks targeting `[data-topo-any-hover=\n \"true\"]` to dim adjacent UI (e.g. chrome strip)\n while the user is inspecting the canvas\n - debug overlays that visualise hover dwell-time\n The 6 hover sources contributing:\n hoveredAlias (node circle / card / alias text)\n hoveredHub (hub center, halo, ring)\n hoveredEdgeKey (flow link path / particle / endpoint)\n hoveredGroupLabel (cluster name / count / pips)\n hoveredStatus (legend row)\n hoveredVendor (vendor chip in chip row)\n Read-only computed attr — zero re-render cost beyond the\n React update that already fires when any of those state\n vars flips. Geometry / paint untouched. */\n data-topo-any-hover={\n (hoveredAlias || hoveredHub || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) ? 'true' : 'false'\n }\n /* Round 467 / Loop — pin-aggregate sibling to R466 hover-\n aggregate. Exposes `data-topo-any-pinned` reflecting\n whether ANY sticky inspection mode is active. Composed\n from the 4 pinned state vars:\n pinnedStatus (legend row click → status filter)\n pinnedGroup (group label click → cluster lock)\n pinnedVendor (vendor chip click → vendor filter)\n pinnedEdgeKey (edge click → edge focus)\n Together with R466 the root svg now carries a 2-bit\n inspection-mode surface:\n data-topo-any-hover — transient (mouse hover)\n data-topo-any-pinned — sticky (click-to-lock)\n Useful for:\n - Playwright tests: one-line query for either mode\n - external CSS hooks: render a persistent \"filter\n active\" badge when pinned, distinct from the\n transient hover dim\n - Esc-handler tests: assert all 4 pins clear after\n the universal-cancel Escape press (R62/R63/R88/\n R116 — single Esc collapses every pin)\n Read-only computed disjunction; no new state, zero\n re-render cost beyond the React pin-flip updates. */\n data-topo-any-pinned={\n (pinnedStatus || pinnedGroup || pinnedVendor || pinnedEdgeKey) ? 'true' : 'false'\n }\n /* Round 462 / Loop — surface DASHBOARD_VERSION on the root SVG\n element as `data-dashboard-version`. Directly closes the\n feedback_dash_zombie_port_3000.md memory rule: \"verify ships\n via SVG DOM, not tmux 'Ready' — zombie next-servers + stale\n global installs silently serve old code\". Pre-R462 the only\n ways to know which preview the dash was serving were:\n 1. parse the npm registry for the latest tag (network)\n 2. fetch /api/dashboard/version (API surface, no DOM)\n 3. inspect the /login footer or /settings page (off-route)\n Test scripts that probe TopoGraph DOM (overlap, group-label\n tint, pip strip, etc.) couldn't tell whether the dash was\n actually serving the build they expected to verify. R462\n threads DASHBOARD_VERSION through to the root <svg> so:\n - Playwright probes can read svg[data-dashboard-version]\n directly + fail-fast on stale-build mismatch\n - the memory rule's manual zombie check (\"inspect SVG\n dom\") becomes a one-attr probe\n - operators DOM-inspect to confirm the live version\n matches the npm tag without leaving the topology page\n Geometry/visual impact: ZERO (data-* attrs don't paint).\n The version string is build-time injected via the existing\n DASHBOARD_VERSION constant (R51 footer + R51 settings page\n already consume it from app/lib/version.ts → reads\n package.json pkg.version). No business logic added. */\n data-dashboard-version={DASHBOARD_VERSION}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerLeave={onPointerUp}\n // Round 41 / Loop: the reset-button title (R22) and the Help\n // overlay (R25) both advertise \"double-click the canvas to\n // reset\", but the handler was never actually wired. The text\n // was lying. Wire it here, guarded so dbl-clicking a node\n // (which would also trigger the SVG-level handler via event\n // bubbling) doesn't unexpectedly reset the view on the user.\n onDoubleClick={(e) => {\n const t = e.target as Element | null;\n if (t?.closest('g[data-node]')) return;\n resetView();\n }}\n style={{ cursor: isPanning ? 'grabbing' : 'grab', touchAction: 'none' }}\n >\n {/* Round 734 / Loop — SVG <desc> stable structural description\n targeted by aria-describedby on the root <svg>. Complements\n the dynamic aria-label (R7/R469 live-state counts) with\n stable structural vocabulary. WAI-ARIA name + description\n pair: SR reads aria-label first (current state), then\n optionally reads this desc on follow-up for structural\n context. Distinct mechanism from R730-R733 SVG <title>\n children (those are NAMES on decorative elements; this is\n a DESCRIPTION on the canvas root). */}\n <desc id=\"anet-topo-canvas-desc\">{'Live agent network topology canvas. Hub at center surrounded by per-agent node circles arranged in a ring (or grid prefix-cluster layout). Flow links between nodes animate during inter-agent activity. Recent-signal panel top-left, legend top-right, chrome strip bottom-right. Tab to focus nodes, double-click empty canvas to reset zoom and pan, l to toggle ring/grid layout.'}</desc>\n <defs>\n <linearGradient id=\"topo-panel\" x1=\"0\" x2=\"1\" y1=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={pal.panelStops[0]} />\n <stop offset=\"48%\" stopColor={pal.panelStops[1]} />\n <stop offset=\"100%\" stopColor={pal.panelStops[2]} />\n </linearGradient>\n <radialGradient id=\"topo-radar\" cx=\"50%\" cy=\"50%\" r=\"55%\">\n <stop offset=\"0%\" stopColor={pal.radarStops[0].color} stopOpacity={pal.radarStops[0].opacity} />\n <stop offset=\"45%\" stopColor={pal.radarStops[1].color} stopOpacity={pal.radarStops[1].opacity} />\n <stop offset=\"100%\" stopColor={pal.radarStops[2].color} stopOpacity={pal.radarStops[2].opacity} />\n </radialGradient>\n {!isLight && (\n <filter id=\"topo-glow\">\n <feGaussianBlur stdDeviation=\"4\" result=\"blur\" />\n <feMerge>\n <feMergeNode in=\"blur\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n )}\n {/* R142 / Loop: drop-shadow filter for group-box hover-lift.\n Mirrors R135's panel hover-elevation idiom but applied\n at the per-group canvas level. R68 already gives a\n hovered/pinned group box a solid accent stroke; R142\n adds a soft outward shadow on top so the box visually\n \"rises off the canvas\" when selected — same visual\n vocabulary as the panels + the Overview KPI cards\n (R18). Theme-aware flood: darker shadow on cyber so\n it reads above the dark canvas, lighter for light\n theme. Bounding box of the group box is unchanged —\n filters only affect paint area, not bbox geometry, so\n the overlap-test invariant is preserved. */}\n <filter id=\"topo-groupbox-lift\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n <feDropShadow\n dx=\"0\" dy=\"3\" stdDeviation=\"4\"\n floodColor={isLight ? '#18181b' : '#000000'}\n floodOpacity={isLight ? 0.18 : 0.55}\n />\n </filter>\n {/* Round 16 / Loop: 3-tier flow-link arrow markers.\n The single marker had `markerUnits` defaulting to\n `strokeWidth`, so heavy edges (stroke=7) rendered 35-user-\n unit arrowheads — visually dominant, the head outweighed\n the line. Switching to `userSpaceOnUse` decouples arrow\n size from stroke; binning by count gives a clearer\n hierarchy than continuous linear scaling:\n s (count 1-2) → 12 user units\n m (count 3-4) → 16 user units (alias for `topo-arrow`)\n l (count 5+) → 22 user units\n `topo-arrow` stays bound to the medium tier so the legend\n swatch (line ~1500) renders without change. */}\n {[\n { id: 'topo-arrow-s', size: 12 },\n { id: 'topo-arrow', size: 16 },\n { id: 'topo-arrow-l', size: 22 },\n ].map(m => (\n <marker\n key={m.id}\n id={m.id}\n viewBox=\"0 0 10 10\"\n refX=\"8\"\n refY=\"5\"\n markerWidth={m.size}\n markerHeight={m.size}\n markerUnits=\"userSpaceOnUse\"\n orient=\"auto-start-reverse\"\n >\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill={pal.arrowFill} />\n </marker>\n ))}\n {/* Round 45: radial radar sweep gradient — bright at center\n (where it meets the hub) fading to leading edge. */}\n <radialGradient id=\"topo-sweep\" cx=\"0%\" cy=\"50%\" r=\"100%\">\n <stop offset=\"0%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity={isLight ? 0.18 : 0.32} />\n <stop offset=\"70%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity={isLight ? 0.10 : 0.18} />\n <stop offset=\"100%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n\n {/* panel backdrop stays fixed — panning never reveals empty canvas */}\n <rect width=\"1000\" height=\"680\" fill=\"url(#topo-panel)\" />\n\n {/* Round 735 / Loop — SVG scan beam: ambient living-system\n signal sweeping the canvas every 30 s. Pivot round: first\n new VISIBLE animation outside the breath / a11y / meta-doc\n threads since R720. Pure paint:\n - 1 px tall horizontal cyan stripe\n - y animates -2 → 680 over 30 s (full canvas sweep)\n - opacity 0 → 0.08 → 0.08 → 0 at keyTimes 0/0.05/0.95/1\n - pointer-events: none, no business logic touch\n Renders directly after the panel backdrop and BEFORE all\n nodes/panels — sits in the background layer; nodes paint\n over it. The overlap-test selectors (g[data-node],\n g[data-group], specific panel translate prefixes) don't\n match this <rect>, so the zero-overlap invariant stays\n green by selector exclusion.\n Gated by !reducedMotion at JSX level — when the user\n prefers reduced motion the <animate> children don't\n mount; opacity stays at the static \"0\" baseline, beam\n remains invisible. data-topo-canvas-scan-beam attr\n exposes the mount state for tests. */}\n <rect\n x=\"0\" y=\"-2\" width=\"1000\" height=\"1\"\n fill={pal.legendAccent}\n opacity=\"0\"\n data-topo-canvas-scan-beam\n data-topo-canvas-scan-beam-active={!reducedMotion ? 'true' : 'false'}\n style={{ pointerEvents: 'none' }}\n >\n {!reducedMotion && (\n <animate attributeName=\"y\" values=\"-2;680;-2\" dur=\"30s\" repeatCount=\"indefinite\" />\n )}\n {!reducedMotion && (\n <animate attributeName=\"opacity\" values=\"0;0.08;0.08;0\" keyTimes=\"0;0.05;0.95;1\" dur=\"30s\" repeatCount=\"indefinite\" />\n )}\n <title>canvas scan beam · ambient sweep · 30s cycle</title>\n </rect>\n\n {/* Round 736 / Loop — vertical scan beam companion to R735.\n Sweeps left→right at 23 s. The pair forms the \"scan beam\"\n ambient family — both beams in background layer, both\n subtle (max opacity 0.08), but at COPRIME cadences:\n R735 horizontal beam 30 s (top → bottom)\n R736 vertical beam 23 s (left → right) ← this round\n gcd(23, 30) = 1 → the beams never phase-lock, so they\n cross at different canvas points each cycle. Over time the\n visual rhythm reads as \"ambient crosshair drift\", a\n living-system signal that's never quite the same twice.\n\n 23 is prime → coprime with every other rolodex cadence\n too (the breath family's prime ladder R703/R707/R708/\n R718/R719: 9/17/19/21/23/25 — but R736 lives outside\n the breath family in the ambient layer, so no actual\n phase-lock concern with rolodex members).\n\n Same z-order (background layer, before nodes), same\n reducedMotion JSX gate, same overlap-test safety by\n selector exclusion. Same canonical 3-part a11y title:\n \"<name> · <role> · <state>\". */}\n <rect\n x=\"-2\" y=\"0\" width=\"1\" height=\"680\"\n fill={pal.legendAccent}\n opacity=\"0\"\n data-topo-canvas-scan-beam-vertical\n data-topo-canvas-scan-beam-vertical-active={!reducedMotion ? 'true' : 'false'}\n style={{ pointerEvents: 'none' }}\n >\n {!reducedMotion && (\n <animate attributeName=\"x\" values=\"-2;1000;-2\" dur=\"23s\" repeatCount=\"indefinite\" />\n )}\n {!reducedMotion && (\n <animate attributeName=\"opacity\" values=\"0;0.08;0.08;0\" keyTimes=\"0;0.05;0.95;1\" dur=\"23s\" repeatCount=\"indefinite\" />\n )}\n <title>canvas scan beam vertical · ambient sweep · 23s cycle</title>\n </rect>\n\n {/* Round 739 / Loop — diagonal scan beam, 3rd member of the\n ambient scan beam family. Sweeps a short 45° line segment\n from upper-left → lower-right across the canvas every 19s.\n Coprime with both existing beams: gcd(19, 23) = 1 and\n gcd(19, 30) = 1 (19 is prime, never shares factors).\n The 3-element family now spans:\n R735 horizontal beam 30 s (top → bottom)\n R736 vertical beam 23 s (left → right)\n R739 diagonal beam 19 s (NW → SE) ← this round\n 19 < 23 < 30 → cadence ladder ascending with the\n dimensionality (1D horizontal/vertical → 2D diagonal); the\n diagonal as the fastest fits — it has 2× the displacement\n distance (sqrt(2) factor) so faster cadence keeps the\n perceived sweep velocity comparable across all 3 beams.\n\n Geometry: <line> with 4 synchronized SMIL <animate>\n children on x1/y1/x2/y2 endpoints. The line is ~100 px\n long (sqrt(70² + 70²)) and stays at constant 45° angle\n throughout the sweep. Starts off-canvas at (-100, -100)\n → (-30, -30), ends off-canvas at (1100, 780) → (1170, 850).\n Same opacity ramp pattern as R735/R736 (max 0.06 — slightly\n dimmer than the orthogonal pair because the diagonal\n traverses the full diagonal of the canvas rather than\n one orthogonal axis, so per-pixel exposure is higher).\n\n Same z-order rules (background layer, before nodes),\n same reducedMotion JSX gate, same overlap-test safety\n by selector exclusion. Same canonical 3-part a11y title. */}\n <line\n x1=\"-100\" y1=\"-100\" x2=\"-30\" y2=\"-30\"\n stroke={pal.legendAccent} strokeWidth=\"1\"\n opacity=\"0\"\n data-topo-canvas-scan-beam-diagonal\n data-topo-canvas-scan-beam-diagonal-active={!reducedMotion ? 'true' : 'false'}\n style={{ pointerEvents: 'none' }}\n >\n {!reducedMotion && (<animate attributeName=\"x1\" values=\"-100;1100;-100\" dur=\"19s\" repeatCount=\"indefinite\" />)}\n {!reducedMotion && (<animate attributeName=\"y1\" values=\"-100;780;-100\" dur=\"19s\" repeatCount=\"indefinite\" />)}\n {!reducedMotion && (<animate attributeName=\"x2\" values=\"-30;1170;-30\" dur=\"19s\" repeatCount=\"indefinite\" />)}\n {!reducedMotion && (<animate attributeName=\"y2\" values=\"-30;850;-30\" dur=\"19s\" repeatCount=\"indefinite\" />)}\n {!reducedMotion && (<animate attributeName=\"opacity\" values=\"0;0.06;0.06;0\" keyTimes=\"0;0.05;0.95;1\" dur=\"19s\" repeatCount=\"indefinite\" />)}\n <title>canvas scan beam diagonal · ambient sweep · 19s cycle</title>\n </line>\n\n {/* Round 103 (issue #81): everything inside this <g> zooms + pans\n together. transform order = translate then scale.\n Round 168 / Loop: smoothView arms a one-shot transition on\n the transform attribute, active only when resetView/fitView\n fires. Pan (R103 pointer drag) and wheel zoom never set the\n flag, so they stay snappy with no lag. Pressing `0`, `f`,\n clicking the hub (R52), or chrome reset/fit buttons triggers\n a 300ms ease-out glide instead of a jolt. Respects prefers-\n reduced-motion via the R29 globals.css blanket override that\n neutralises transition-duration universally. */}\n <g\n transform={`translate(${view.x} ${view.y}) scale(${view.zoom})`}\n data-topo-viewport\n data-topo-viewport-smooth={smoothView ? 'true' : 'false'}\n data-topo-viewport-layout-switching={layoutSwitching ? 'true' : 'false'}\n data-topo-viewport-nodesize-switching={nodeSizeSwitching ? 'true' : 'false'}\n style={{\n // R170 / Loop: opacity always carries a transition so the\n // layout-switch crossfade fires cleanly. R168 smoothView\n // transition on transform is added only when armed (pan\n // and wheel zoom MUST stay snappy). The two arming flags\n // compose without conflict — different visual axes.\n // R171: nodeSizeSwitching shares the same opacity-dim\n // pathway as layoutSwitching — clicking S/M/L in the\n // chrome triggers the same soft-blink masking. ORing\n // both flags keeps the opacity expression simple while\n // the two distinct data-* attributes let tests\n // disambiguate which gesture armed the crossfade.\n transition: smoothView\n ? 'opacity 250ms ease-out, transform 300ms ease-out'\n : 'opacity 250ms ease-out',\n opacity: (layoutSwitching || nodeSizeSwitching) ? 0.45 : 1,\n }}\n >\n {/* Issue #87: radar/ring ambiance renders only in ring layout —\n grid mode drops it so the concentric rings don't sit behind a\n rectangular grid. */}\n {layout === 'ring' && (<>\n {/* R52: radar bg is pure decoration — drop its pointer events so\n the hub <g> under it (and any future inner-disk affordances)\n can receive clicks. Previously the r=330 disc intercepted\n the hub click outright. */}\n <circle cx={cx} cy={cy} r=\"330\" fill=\"url(#topo-radar)\" style={{ pointerEvents: 'none' }} />\n\n {/* Round 45: subtle star field — deterministic dots scattered\n across the canvas give the radar bg some depth. Skipped on\n light theme so the white surface stays clean.\n Round 291 / Loop: starfield dot count 28 → 14 (50%\n reduction). Post-R290 inner radar ring retirement the\n canvas has cleared meaningfully — sweep + 3 radar rings\n + tier guides + nodes + edges are doing the visual work.\n The starfield's role is atmospheric depth, not\n information; cutting density by half preserves the\n \"space/radar\" feel while removing decoration the eye\n has to skip. Same R275-R281 减法 family idiom as the\n orbit / halo / spoke retirements; same R290 pivot back\n to subtractive register. data-topo-starfield-dot\n attribute makes the dots probe-able for the regression\n test. */}\n {!isLight && (\n <g opacity=\"0.5\" style={{ pointerEvents: 'none' }} data-topo-starfield>\n {Array.from({ length: 14 }).map((_, i) => {\n // Deterministic pseudo-random scatter so positions are\n // stable between renders (no JS hydration mismatch).\n const seed = i * 9301 + 49297;\n const x = ((seed * 13) % 1000);\n const y = ((seed * 7) % 680);\n const r = (i % 3 === 0) ? 1.2 : 0.7;\n /* Round 523 / Loop — 配色 family extension to a 3rd anchor.\n Pre-R523 all 14 starfield dots painted at the same\n hardcoded `#a5b4fc` (indigo-300). The starfield's role\n is atmospheric depth (R45, R291 comment), but a flat\n single-hue field reads more like a regular dot grid\n than a star field — real starlight has color\n temperature variation (blue-white hot stars / yellow\n sun-like / cool red).\n R523 cycles a 3-color deterministic rotation based on\n `i % 3`:\n i % 3 === 0 → #a5b4fc indigo-300 (original, cool)\n i % 3 === 1 → #67e8f9 cyan-300 (cyber accent, hot)\n i % 3 === 2 → #cbd5e1 slate-300 (neutral, warm white)\n All three hues sit inside the cyber theme's palette\n family (indigo / cyan / slate) so the starfield reads\n varied-but-coherent rather than rainbow. At opacity\n 0.5 (parent <g>) * 0.35-0.50 (per-dot) the temperature\n shifts are gentle but perceptible — closes the gap\n between 'dot grid' and 'star field'.\n 配色 family extension (3 anchors): R509/R510 hub-\n highlight cross-theme fill + R523 starfield color\n temperature variation. Light theme unaffected\n (starfield gated `!isLight` so light theme stays\n clean per R45's original 'white surface stays clean'\n intent).\n Deterministic on `i` — no JS hydration mismatch,\n same SSR/client output. data-topo-starfield-dot-hue\n attr exposes the resolved hue category for tests. */\n const hues = ['#a5b4fc', '#67e8f9', '#cbd5e1'] as const;\n const hueNames = ['indigo', 'cyan', 'slate'] as const;\n const hueIdx = i % 3;\n return <circle key={i} cx={x} cy={y} r={r} fill={hues[hueIdx]} opacity={0.35 + (i % 4) * 0.05} data-topo-starfield-dot={i} data-topo-starfield-dot-hue={hueNames[hueIdx]} />;\n })}\n </g>\n )}\n\n {/* Round 45: rotating radar sweep — a 40° wedge with a soft\n leading-edge gradient. Slow 6s rotation reads as a radar\n scan without being noisy. Inline transform-origin on the\n <g> wrapper ensures Chrome / Firefox rotate around (cx,cy)\n instead of the SVG viewBox corner.\n\n v0.10.0 Hero 3 Wave 1 / RFC §3.B (Vincent 5222 holdover):\n sweep arc retired. The diagonal rotating wedge competes\n with working-halo SMIL, hub busyness breath, and edge\n flow animation — on a 16:9 Twitter screenshot it reads\n 'wow lots of motion' rather than 'agents communicating'.\n Same idiom as R278/R279/R280 retirements — `false &&`\n short-circuits the IIFE so it's a one-line rollback. */}\n {false && (() => {\n // R146: radar sweep rotation buckets on workingCount, joining\n // R84 hub breath / R131 outer orbit / R132 group march /\n // R145 idle spokes as the 5th and final layer in the busyness-\n // driven motion family. 8 / 6 / 4 / 3 seconds — same 0 / 1-2 /\n // 3-5 / 6+ thresholds R84 uses. \"Busier fleet = more frequent\n // scans\" feels semantically right for a radar idiom. R45\n // baseline 6s sits at bucket 1 so the calm/busy spread bracks\n // around the historical default.\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const sweepDur = [8, 6, 4, 3][busy];\n return (\n <g\n style={{\n transformOrigin: `${cx}px ${cy}px`,\n transformBox: 'view-box',\n pointerEvents: 'none',\n // CSS var consumed by `.anet-topo-sweep` (line 848 of\n // globals.css). React's CSSProperties type doesn't model\n // custom properties → cast through Record<string, string>.\n ...({ ['--sweep-dur']: `${sweepDur}s` } as Record<string, string>),\n } as React.CSSProperties}\n className=\"anet-topo-sweep\"\n opacity={isLight ? 0.7 : 1}\n data-topo-sweep-bucket={busy}\n data-topo-sweep-dur={sweepDur}\n >\n <path\n d={`M ${cx} ${cy} L ${cx + 330} ${cy} A 330 330 0 0 0 ${cx + 330 * Math.cos(-Math.PI / 4.5)} ${cy + 330 * Math.sin(-Math.PI / 4.5)} Z`}\n fill=\"url(#topo-sweep)\"\n />\n </g>\n );\n })()}\n\n {/* radar rings — pure decoration at fixed radii, independent of\n node positions so the radar aesthetic is preserved across tier\n changes.\n Round 290 / Loop: drop the innermost radar ring at r=90.\n That ring sat ~66px outside the hub (hub radius 24, halo\n r=18), in the exact zone R276 (orbit particles) / R278\n (working halo) / R280 (backdrop spokes) cleared during\n the R275-R281 减法 arc. Post-cleanup the lone r=90 ring\n read as a leftover decorative loop hugging the hub — a\n visual element with no remaining sibling to anchor.\n Dropping it returns to the subtractive register after\n R282-R289's 8 加法 rounds and lets the hub breathe. The\n outer three rings (170 / 250 / 330) still carry the\n radar aesthetic across the canvas. New data-topo-radar-\n ring attribute exposes each remaining ring radius for\n test probing. */}\n {[170, 250, 330].map(radius => (\n <circle\n key={radius}\n cx={cx} cy={cy} r={radius}\n fill=\"none\" stroke={pal.ringStroke} strokeWidth=\"1\"\n opacity={isLight ? 0.6 : 0.35}\n data-topo-radar-ring={radius}\n />\n ))}\n\n {/* Round 54 / Loop: tier-radius guide rings. The radar rings above\n are decorative and don't match the actual tier radii nodes sit\n on (single 220 / dual 175,260 / triple 145,215,285). Drawing\n a faint dashed ring at each ACTIVE tier radius lets the eye\n anchor \"this is the inner / outer ring\" without inferring from\n node spacing. Picked based on online node count so only the\n tiers currently in use draw — empty tiers stay quiet. pointer-\n events:none so they never intercept hub or node clicks. The\n 0.7 stroke + dashed pattern reads as guide, not feature. */}\n {(() => {\n const tierRadii = onlineNodes.length > onlineTripleThreshold\n ? [onlineTripleInnerR, onlineTripleMidR, onlineTripleOuterR]\n : onlineNodes.length > onlineTierThreshold\n ? [onlineInnerRadius, onlineOuterRadius]\n : onlineNodes.length > 0\n ? [onlineRadius]\n : [];\n // Round 92 / Loop: tier-ring occupancy. R54 drew the guide\n // rings at fixed opacity regardless of how many nodes lived\n // on each tier. With pinned filters dimming most nodes to\n // 0.28, the ring at a deserted tier looked identical to a\n // crowded one — wasting a free piece of canvas. Count\n // online nodes whose hub-distance falls within ±15 px of\n // each ring (15 px = half the inter-tier gap, so each\n // node is assigned to exactly one ring). Empty tier → skip\n // entirely. Crowded tier → stronger opacity, says \"look\n // here\". Buckets so the ladder feels intentional, not\n // jittery as one node migrates between tiers.\n const occupancyOf = (r: number) => onlineNodes.reduce((acc, s) => {\n const p = nodePositions[s.alias];\n if (!p) return acc;\n const d = Math.hypot(p.x - cx, p.y - cy);\n return Math.abs(d - r) < 15 ? acc + 1 : acc;\n }, 0);\n // Round 93 / Loop: when any pin is active, tint the tier\n // rings to the legend accent so the spatial guide visually\n // shares the canvas's \"filtered mode\" colour. The chip\n // row already says WHICH filter is on (pills + letter\n // mirror); rings answering \"we're filtered\" reinforces\n // the state when the eye is on the canvas, not the chip\n // row. Composes cleanly with R92 occupancy — same opacity\n // bucket logic; just the stroke colour swaps.\n const anyPin = !!(pinnedStatus || pinnedGroup || pinnedVendor);\n const tierStroke = anyPin ? pal.legendAccent : pal.ringStroke;\n return tierRadii.map((r, tierIdx) => {\n const n = occupancyOf(r);\n if (n === 0) return null;\n const bucket = n <= 2 ? 0 : n <= 6 ? 1 : 2;\n const opLight = [0.24, 0.36, 0.50][bucket];\n const opDark = [0.32, 0.46, 0.62][bucket];\n // Round 174 / Loop: tier guide rings fade-in alongside\n // the R9/R72/R172/R173 first-paint wave. Ring layout's\n // structural scaffolding (R54 dashed concentric guides)\n // was the last instant-pop element after R173 closed\n // group boxes in grid. Same vocabulary — .anet-fade-in\n // mount-once CSS animation + per-ring stagger 60ms ×\n // tierIdx (cap 8). Tier rings are at most 3 (single /\n // dual / triple), so the visible range is 0-120ms.\n // Inner ring leads outward — emanates from the hub.\n // transition list grows `opacity 250ms ease-out` so the\n // post-animation snap from animation's end state (1) to\n // the bucket opacity (0.24-0.62) eases instead of cuts.\n // Same pattern node fade-in uses (R9 + transition-opacity\n // from R3 className). data-tier-fade-delay exposes the\n // computed delay for test probing.\n const fadeDelay = Math.min(tierIdx, 8) * 60;\n return (\n <circle\n key={`tier-${r}`}\n cx={cx} cy={cy} r={r}\n fill=\"none\"\n stroke={tierStroke}\n strokeWidth=\"0.7\"\n /* Round 303 / Loop: tier guide dashes tighten from\n \"2 8\" → \"2 6\" (8px gap → 6px gap). R54 set \"2 8\"\n to read as a faint hint behind everything else;\n after R290 (inner radar ring retired) + R291\n (starfield 50%) cleared the surrounding backdrop\n density, the tier guides carry more \"this is the\n ring nodes sit on\" visual responsibility. Pulling\n the gap from 8→6 puts dashes closer together so\n the ring reads as a clearer continuous mark\n rather than scattered dots, without bumping\n strokeWidth (0.7) or opacity (R92 bucketed). The\n 2px dash itself unchanged — same density signal\n per dash, just fewer-px space between them. */\n strokeDasharray=\"2 6\"\n opacity={isLight ? opLight : opDark}\n className=\"anet-fade-in\"\n style={{\n pointerEvents: 'none',\n transition: 'stroke 200ms ease-out, opacity 250ms ease-out',\n animationDelay: `${fadeDelay}ms`,\n }}\n data-tier-ring={r}\n data-tier-occupancy={n}\n data-tier-bucket={bucket}\n data-tier-tinted={anyPin ? 'true' : 'false'}\n data-tier-fade-delay={fadeDelay}\n />\n );\n });\n })()}\n\n {/* Round 50: 4 small particles slowly orbiting the outer ring\n (r=330). Each starts at a different angle (offset 0/0.25/0.5/0.75\n of the cycle) so they're evenly spaced. 16s per revolution is\n slow enough to feel ambient, not noisy. Skipped on light theme\n so the white surface stays clean.\n\n R131 / Loop: orbit period now buckets on workingCount,\n mirroring R84's hub-busyness breath cadence. An idle\n fleet keeps the original 16s \"calm sweep\"; as work\n accumulates the orbit subtly accelerates (capped at\n 10s so it never feels frantic). Two-layer motion\n coordination now: R84 breathes the hub, R131 spins\n the outer ring — both reading the same underlying\n \"is the network busy\" signal, both visible\n simultaneously without competing. Same bucket\n thresholds (0 / 1-2 / 3-5 / 6+) the R84 block uses\n at line ~2702 so the two cadences stay in sync if\n a future refactor shifts buckets.\n\n Round 276 / Loop: orbit particles DISABLED by default\n per Vincent 5214/5215-5217 visual-audit relay\n (clutter cleanup for Twitter screenshot). The 4\n particles encode workingCount busyness via speed\n (R131) + opacity (R216) — but that signal is\n ALREADY conveyed by:\n · hub halo opacity breath (R244, R84)\n · hub digit workingCount text (R130)\n · pressure-bar working/idle/offline ratio (R31)\n So orbit particles are info-redundant decoration:\n they don't add new signal, just add visual noise at\n the canvas outer edge. R276 gates the render block\n with `false &&` so the code stays (commented context\n + tests preserved for hypothetical rollback) but\n nothing renders. R131 busy-bucket constant + R216\n opacity-bucket constant are dead code post-R276 —\n acceptable since the family was R50/R131/R216 and\n R276 retires the family entirely. Net: 4 fewer\n moving dots on canvas. */}\n {false && !isLight && (() => {\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const dur = [16, 14, 12, 10][busy];\n // Round 216 / Loop: orbit particle opacity scales with busy\n // bucket alongside R131's speed scaling. Pre-R216 the\n // particles sat at flat opacity 0.9 regardless of fleet\n // workload — speed conveyed busyness but brightness was\n // mute. R216 layers brightness on top of speed so idle\n // fleets read calm (dim particles) and busy fleets read\n // bright (loud particles). Same R84 hub-breath /\n // R131 orbit-speed bucket thresholds (0 / 1-2 / 3-5 / 6+)\n // so the three motion layers (hub breath / orbit / group\n // march) plus the new brightness channel all derive from\n // one busyness metric. transition: opacity 300ms ease-out\n // matches R167 status-flip + R213 hub crossfade timing —\n // when first working node appears, hub focal point AND\n // outer ring particles brighten on the same 300ms beat.\n const orbitOpacity = [0.5, 0.7, 0.85, 1.0][busy];\n return [0, 0.25, 0.5, 0.75].map((phase, i) => (\n <g\n key={`orbit-${i}`}\n data-topo-orbit-bucket={busy}\n data-topo-orbit-dur={dur}\n data-topo-orbit-opacity={orbitOpacity}\n >\n <circle\n cx={cx + 330} cy={cy}\n r={i === 0 ? 2.8 : 2.2}\n fill=\"#22d3ee\"\n opacity={orbitOpacity}\n filter=\"url(#topo-glow)\"\n style={{ transition: 'opacity 300ms ease-out' }}\n >\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from={`${phase * 360} ${cx} ${cy}`}\n to={`${phase * 360 + 360} ${cx} ${cy}`}\n dur={`${dur}s`}\n repeatCount=\"indefinite\"\n />\n </circle>\n </g>\n ));\n })()}\n\n {/* Round 240 / Loop: extend R93 anyPin tinting from tier-rings\n to backdrop spokes. Pre-R240 the 6 radar-style spokes\n stayed at pal.ringStroke regardless of filter state,\n while R93 already shifted tier-rings to pal.legendAccent\n on any active pin. Result: ring scaffolding said\n 'filtered mode' but spoke scaffolding said 'rest' — the\n two halves of the canvas's spatial guide were out of\n sync. R240 ties them together; whole backdrop now reads\n as one filtered-mode-coloured unit when a pin is active.\n\n Same anyPin signal R93 uses (pinnedStatus || pinnedGroup\n || pinnedVendor). Same legendAccent tint colour. Same\n 200ms ease-out transition timing — pin a status, both\n tier-rings AND spokes ease to cyan together. */}\n {/* Round 280 / Loop: backdrop spokes RETIRED (R93 family with\n R240 tinting) per 减法 cut #6. The 6 radial lines at\n every 30° formed 12 rays from canvas center — even at\n opacity 0.18 (cyber) / 0.35 (light) they added explicit\n radial-line clutter behind the hub-and-spoke topology.\n The radial-gradient backdrop (topo-radar) ALREADY\n provides soft hub-centered glow; explicit lines on top\n were decorative density without structural signal that\n the topology itself doesn't already convey (hub at\n center + nodes on rings = radial structure inherent).\n `false &&` gates the render; code preserved for\n rollback. Same idiom as R276 orbit / R278 working halo\n / R279 ping+pulse retirements. */}\n {false && (() => {\n const anyPin = !!(pinnedStatus || pinnedGroup || pinnedVendor);\n const spokeStroke = anyPin ? pal.legendAccent : pal.ringStroke;\n return [0, 30, 60, 90, 120, 150].map(angle => (\n <line\n key={angle}\n x1={cx - 360 * Math.cos(angle * Math.PI / 180)}\n y1={cy - 360 * Math.sin(angle * Math.PI / 180)}\n x2={cx + 360 * Math.cos(angle * Math.PI / 180)}\n y2={cy + 360 * Math.sin(angle * Math.PI / 180)}\n stroke={spokeStroke}\n strokeWidth=\"1\"\n opacity={isLight ? 0.35 : 0.18}\n data-topo-spoke-angle={angle}\n data-topo-spoke-tinted={anyPin ? 'true' : 'false'}\n style={{ transition: 'stroke 200ms ease-out' }}\n />\n ));\n })()}\n </>)}\n\n {/* hub links — round 46: idle spokes now have animated\n stroke-dashoffset so dashes flow outward from the hub\n (\"command relay\" feel). Active spokes carrying live message\n flow stay as solid bright strokes.\n R145 / Loop: idle-spoke animation cadence buckets on\n workingCount, mirroring R84 hub-breath / R131 outer-\n ring orbit / R132 groupbox-march coordination. Same\n 0 / 1-2 / 3-5 / 6+ thresholds. Idle ↔ idle network\n has a slow \"command relay\" feel; as work accumulates\n the dashes accelerate outward, completing the 4th\n motion layer in the busyness-driven family:\n R84 hub breath (centre)\n R131 outer ring orbit (periphery)\n R132 groupbox march (per-team, grid layout)\n R145 idle-spoke flow (ring layout, hub→nodes)\n 4 surfaces, 1 signal. Same cadence ladder 2.8/2.4/2.0/\n 1.6s — 1.75× range from calm to busy, capped so the\n network never feels frantic. */}\n {layout === 'ring' && (() => {\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const spokeDur = [2.8, 2.4, 2.0, 1.6][busy];\n return onlineNodes.map((session, idx) => {\n const pos = nodePositions[session.alias];\n if (!pos) return null;\n const path = curvePath({ x: cx, y: cy }, pos, 0);\n const isActiveSpoke = activeAliases.has(session.alias);\n\n /* Round 241 / Loop: hub-link spokes (agent→hub paths in\n ring layout) eased state-flip between idle and active.\n Pre-R241 when a node sent or received a message its\n hub-spoke jumped one-frame from idle gray (pal.spoke-\n Stroke.idle + strokeWidth=1 + opacity=0.45) to active\n cyan (pal.spokeStroke.active + strokeWidth=2 + opacity\n =0.7) — three discrete property snaps in lockstep.\n R241 adds a 250ms ease-out transition list covering\n stroke + stroke-width + opacity so the activation\n 'lights up' smoothly. strokeDasharray stays binary\n (none ↔ '6 14') — dasharray doesn't interpolate\n cleanly between continuous and discrete forms across\n browsers (same lesson R167 documented for the node\n status ring). The CSS keyframe animation on idle\n spokes (anet-topo-spoke-flow) drives stroke-dashoffset\n separately and stays untouched. data-topo-hub-spoke-\n active surfaces the activity state for test probes\n (active spokes don't carry the bucket/dur attrs so\n they need their own data anchor). */\n // Round 382 / Loop: hub-spoke path picks up\n // strokeLinecap='round'. Sibling polish to R378 flow-\n // rail dashes + R380 group box dashes — three dashed-\n // stroke surfaces now share 'round' linecap:\n // R378 flow-rail '2 12' -> soft 3-px pills\n // R380 group box '6 6' -> soft 7.5-px pills\n // R382 hub spoke '6 14' -> soft 7-px pills (this round)\n // For idle spokes (dashed at sw=1), each 6-px dash gains\n // 0.5-px round caps and reads as a soft pill instead of\n // a sharp 6 x 1 rectangle. Active spokes (solid, no\n // dasharray) have caps mostly hidden by the hub center +\n // node radius. Geometry-safe; paint-only. R51 sentinel\n // strokeWidth 1.5/3 untouched (idle=1, active=2). data-\n // topo-hub-spoke-linecap attr exposes the value for tests.\n // Round 419 / Loop: hub-spoke idle opacity 0.45 → 0.50.\n // Stale-state legibility lift family 9th anchor — pairs\n // with R391 (active 0.7 → 0.8) and R415 (active sw 2 →\n // 2.25) so the same spoke path is now polished on BOTH\n // active AND idle tiers. Pre-R419 idle spokes painted\n // at α=0.45 with R46 anet-topo-spoke-flow dashed\n // animation; the dashed pulses sat at the \"background\n // chatter\" floor — visible but understated. R419\n // lifts to 0.50 so idle spokes read more confidently\n // while the active/idle contrast ratio stays clear\n // (0.8/0.50 = 1.6× vs prior 0.8/0.45 = 1.78×; still\n // a sharp two-tier distinction).\n // Stale-state legibility lift family (9 anchors now):\n // R317 subordinate-text gray-500 → gray-400\n // R358 freshness floor 0.25 → 0.30\n // R372 minimap offline-dot 0.5 → 0.6\n // R404 hub-halo cyber trough 0.08 → 0.10\n // R405 hub-halo light trough 0.32 → 0.34\n // R406 edge freshness floor 0.35 → 0.40\n // R407 node halo offline opacity (cyber + light)\n // R413 active-node pulse trough (cyber + light)\n // R419 hub-spoke idle opacity 0.45 → 0.50 (this round)\n // data-topo-hub-spoke-opacity attr (R391) updates to\n // surface the resolved per-state value.\n //\n // Round 415 / Loop: hub-spoke active strokeWidth 2 → 2.25.\n // Pairs with R391 (active opacity 0.7 → 0.8) so the same\n // active-state path lifts BOTH stroke weight AND opacity\n // in concert. Pre-R415 active strokes sat at sw=2 — clear\n // step over idle sw=1, but a touch lighter than the\n // weight family's other \"active\" indicators (R385 hub\n // hover-ring sw=1.75 / R402 legend pin-ring sw=1.75 /\n // R367 edge-badge rest sw=1.25). R415 bumps to 2.25 so\n // the active spoke reads with proportional weight to its\n // role — the line connecting the focal point to the\n // active node deserves the heaviest active stroke in the\n // family (after pin/hot edge-badge sw=2). Stays clear of\n // R51 sentinels (1.5 / 3) at 2.25.\n // Visual-weight bump family (14 anchors now):\n // R287 minimap viewport stroke 1 → 1.5\n // R295 legend swatch radius 5.5 → 6\n // R359 recent-row pip radius 1.6 → 1.8\n // R360 hub digit fontSize 11 → 12\n // R361 edge-badge digit fontSize 10 → 11\n // R365 hub-highlight radius 5 → 5.5\n // R367 edge-badge rest stroke 1 → 1.25\n // R374 pressure-bar height 1.5 → 2\n // R383 recent-row pip radius 1.8 → 2.0\n // R384 minimap online dot 1.7 → 1.9\n // R385 hub hover-ring stroke 1.5 → 1.75\n // R402 legend pin-ring stroke 1.5 → 1.75\n // R408 hub-halo radius 18 → 20\n // R415 hub-spoke active stroke 2 → 2.25 (this round)\n // R382 strokeLinecap='round' + R391 opacity 0.45/0.8 +\n // R51-safe idle sw=1 all preserved. 250ms transition\n // list already covers stroke-width — the new tier eases\n // naturally. data-topo-hub-spoke-stroke-width-active\n // attr surfaces the active value for tests.\n //\n // Round 391 / Loop: hub-spoke active opacity 0.7 → 0.8.\n // Pre-R391 active spokes (the spoke connecting the hub\n // to the currently-active alias — hovered or pinned)\n // lifted opacity from rest 0.45 to active 0.7 — a clear\n // step but slightly understated against the canvas\n // chrome. R391 lifts active to 0.8 so the \"this spoke\n // connects to your active node\" signal reads with\n // matching weight to the R370 hub hover-ring opacity\n // (0.7 → 0.8 cyber) — paired canvas signals now share\n // the same active-state alpha (0.8) so when a user\n // hovers a node, both the spoke and the hub-ring lift\n // to identical opacity. Rest 0.45 invariant preserved.\n // Theme-consistency / canvas-presence polish family\n // (6th anchor):\n // R370 hub hover-ring opacity 0.7 → 0.8 cyber\n // R371 edge-badge rest opacity 0.82 → 0.85 cyber\n // R372 minimap offline-dot opacity 0.5 → 0.6\n // R386 hub-highlight idle opacity 0.9 → 0.95\n // R387 hover-detail panel opacity 0.94 → 0.97 cyber\n // R391 hub-spoke active opacity 0.7 → 0.8 (this round)\n // Idle path (45% alpha + dashed flow animation) entirely\n // untouched — R391 is an active-state-only lift.\n // data-topo-hub-spoke-opacity attr exposes the resolved\n // value for tests. R382 strokeLinecap='round' + R51\n // sentinel-safe sw (1 idle / 2 active) preserved.\n /* Round 430 / Loop: hub-spoke opacity hover lift on\n hoveredAlias === session.alias. Adds a \"this node's\n spoke\" affordance to the node-hover gesture — in a\n dense ring layout the spokes are visually quiet\n (idle α=0.50 dashed, active α=0.80 solid) so hovering\n a node didn't telegraph which line connects to it.\n R430 lifts the matched spoke's opacity:\n idle 0.50 → 0.70 (hover-α=0.70, +0.20)\n active 0.80 → 0.95 (hover-α=0.95, +0.15)\n The +0.15-to-0.20 lift keeps the active/idle two-tier\n distinction (0.95 vs 0.70 still a clear gap) while\n making the hovered-node's spoke visibly brighter than\n every other spoke at its own activity tier. R241\n transition list already covers opacity 250ms so the\n lift eases for free. Sibling to R429 label-card body\n solidity lift — both surface a single-node-focused\n attention cue with the same easing cadence.\n Stacks with the 6-layer node hover cue stack at the\n inter-node-link scope:\n R26 group translateY -2px (per-node)\n R217 stroke tint legendAccent (per-node card)\n R142 drop-shadow boost (per-node card)\n R427 alias letter-spacing (per-node text)\n R428 sub-text letter-spacing (per-node text)\n R429 body opacity 0.94 → 1.0 (per-node card)\n R430 spoke opacity α+ (this round) (link to hub)\n data-topo-hub-spoke-hovered exposes the gate. */\n /* R622 — extend isHoveredSpoke gate to include\n chatAlias === session.alias. Single conceptual change\n cascades the spoke's hover axes:\n R430 opacity 0.50 → 0.70 (idle hover)\n 0.80 → 0.95 (active hover)\n R435 sw 1.0 → 1.25 (idle hover)\n 2.25 → 2.5 (active hover)\n R533/R580 drop-shadow + brightness already gated on\n hub-hover, not per-spoke isHovered — so those don't\n join this gate union, only the geometry/opacity axes.\n\n The radial spoke from hub to the chat partner now\n thickens + brightens — completing the \"line of\n attention\" from hub center to chat target.\n\n 8th anchor in chat-target-gated brightness family\n (R615 ring + R618 card + R616 alias + R617 sub +\n R619 avatar + R620 badge + R621 status ring + R622\n hub-spoke). */\n const isHoveredSpoke = !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias);\n const spokeOpacity = isActiveSpoke\n ? (isHoveredSpoke ? 0.95 : 0.80)\n : (isHoveredSpoke ? 0.70 : 0.50);\n /* Round 435 / Loop: hub-spoke stroke-width hover lift —\n sibling to R430 opacity hover at the same surface. When\n hoveredAlias matches, BOTH opacity AND stroke-width\n lift on the matched spoke so the eye registers a\n 2-axis \"this node's spoke\" gesture (paint + geometry).\n idle 1.00 → 1.25 (Δ +0.25, +25%)\n active 2.25 → 2.50 (Δ +0.25, +11%)\n Same +0.25 absolute delta keeps the idle/active visual\n progression consistent — at rest sw ratio 2.25:1 = 2.25,\n on hover 2.50:1.25 = 2.0; both still clearly two-tier.\n R241 transition list already covers stroke-width 250ms\n so the lift eases for free.\n R51 sentinel-safe: spoke is canvas <path>, not\n data-node <circle> (the sentinel selector is gated to\n g[data-node] descendants). 1.25 and 2.5 are not in the\n reserved {1.5, 3} set so the overlap-test sentinel\n attribute selector wouldn't match either way. */\n const spokeStrokeWidth = isActiveSpoke\n ? (isHoveredSpoke ? 2.5 : 2.25)\n : (isHoveredSpoke ? 1.25 : 1);\n return (\n <path\n key={`hub-${session.alias}`}\n d={path}\n fill=\"none\"\n stroke={isActiveSpoke ? pal.spokeStroke.active : pal.spokeStroke.idle}\n strokeWidth={spokeStrokeWidth}\n strokeDasharray={isActiveSpoke ? 'none' : '6 14'}\n strokeLinecap=\"round\"\n opacity={spokeOpacity}\n className={isActiveSpoke ? undefined : 'anet-topo-spoke-flow'}\n data-topo-spoke-bucket={isActiveSpoke ? undefined : busy}\n data-topo-spoke-dur={isActiveSpoke ? undefined : spokeDur}\n data-topo-hub-spoke-active={isActiveSpoke ? 'true' : 'false'}\n data-topo-hub-spoke-hovered={isHoveredSpoke ? 'true' : 'false'}\n data-topo-hub-spoke-opacity={spokeOpacity}\n data-topo-hub-spoke-stroke-width={spokeStrokeWidth}\n data-topo-hub-spoke-stroke-width-active=\"2.25\"\n data-topo-hub-spoke-linecap=\"round\"\n /* Round 533 / Loop — extends drop-shadow visual-polish\n family to a 9th anchor: hub spokes gain filter:drop-\n shadow glow on hub-hover. Subtle 1.5px cyan/teal blur\n applied across ALL spokes simultaneously when the\n user hovers the hub — the network mesh visually\n \"lights up\" in response to focal attention. Sibling\n to R476 hub-digit + R532 hub-highlight glow at the\n same gate (hoveredHub && !reducedMotion); together\n the three anchors (digit + highlight disc + spokes)\n form a unified focal-cluster glow that signals\n \"you're focused on the hub\" across geometry,\n paint, and mesh-extent axes.\n Theme-aware glow palette matches the spoke stroke\n family:\n light: rgba(13, 148, 136, 0.4) teal-600\n cyber: rgba(34, 211, 238, 0.4) cyan-400\n 0.4 alpha keeps the glow subtle across N spokes\n (30+ at peak fleet sizes) — loud bloom across many\n edges would compete with the focal cluster itself.\n 1.5px blur is conservative; tuned so each spoke\n gains a faint outer halo rather than a wide bloom.\n filter is paint-only; bbox unchanged; existing\n R241 transition list extends to 'filter 250ms\n ease-out' matching the spoke transition cadence\n (250ms, distinct from the 200ms hub-cluster\n cadence — spokes ease slightly slower since they\n respond to per-alias state, not just hub state).\n data-topo-hub-spoke-glow attr exposes the gate\n state for tests. */\n data-topo-hub-spoke-glow={!reducedMotion && (hoveredHub || isHoveredSpoke) ? 'true' : 'false'}\n data-topo-hub-spoke-halo-layers={!reducedMotion && (hoveredHub || isHoveredSpoke) ? '2' : '0'}\n /* Round 580 (65-round milestone) — hub-spokes complete\n the hub-cluster brightness coverage at 5/5 concentric\n elements. Stacks brightness(1.15) onto R533's drop-\n shadow — same R564/R570/R571/R572/R573/R574/R575/\n R577/R578/R579 stacked-filter pattern.\n Hub-cluster brightness now FULLY CLOSED:\n hub digit (R575) innermost typo\n hub-highlight (R574) middle disc\n hub-hover-ring (R579) outer ring boundary\n hub-halo (R577) outermost atmosphere\n hub-spokes (R580) mesh radial lines ← this round\n 5 concentric elements + N mesh radial lines all lift\n uniformly through stacked drop-shadow + brightness on\n hub-hover. The hub focal cluster now responds as ONE\n unified motion-coherent paint pulse from center\n outward through every layer. */\n /* Round 636 / Loop — extend the hub-spoke filter gate\n from hub-wide (hoveredHub) to ALSO fire per-spoke\n on isHoveredSpoke. Pre-R636 the filter was bound to\n \"user is hovering the hub center\" — ALL spokes glow\n uniformly. R622 already extended the spoke's geometry\n (sw) + paint (opacity) axes to per-spoke triggers\n (hoveredAlias || chatAlias), but the FILTER axis\n (drop-shadow + brightness) stayed on hub-wide only,\n leaving a partial 4-axis coverage on per-spoke focus\n (sw + opacity per-spoke, filter only hub-wide).\n R636 closes that — when the user hovers a single\n node OR chat is open with one, ONLY that node's\n radial spoke lights up with the drop-shadow halo +\n brightness, surfacing a focused \"line of attention\"\n from hub to participant. Hub-wide hub-hover behavior\n preserved (filter still fires across all spokes when\n hovering the hub center).\n Same teal (light) / cyan (cyber) drop-shadow as the\n R533 hub-hover gate — colour vocabulary continues\n uniform across both spoke focus triggers.\n Sibling completion of the chat-target-gated brightness\n family at the spoke surface: R622 (sw/opacity) +\n R636 (filter) — 4-axis chat-target focus signature\n on the hub-spoke now matches the node body's R615/\n R618/R619/R620/R621 multi-axis treatment.\n Reduced-motion gate preserved on the OR-chain.\n data-topo-hub-spoke-brightness-self attr exposes\n the per-spoke gate axis for tests (vs the existing\n -brightness which stays bound to the wider gate). */\n data-topo-hub-spoke-brightness={!reducedMotion && (hoveredHub || isHoveredSpoke) ? '1.15' : '1'}\n data-topo-hub-spoke-brightness-self={!reducedMotion && isHoveredSpoke ? 'true' : 'false'}\n style={{\n transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out, filter 250ms ease-out',\n /* Round 654 / Loop — hub-spoke drop-shadow gains a\n SECOND outer layer at 3px blur + 0.2 alpha (half\n R533 inner 0.4). Closes the hub-cluster glow\n QUINTET (R476/R532/R533/R535/R536) at 5/5 multi-\n layer. R650-R653 already covered digit/halo/\n highlight/hover-ring; R654 finishes hub-spokes —\n the mesh radial lines connecting hub to nodes.\n Spokes use 1.5px + 3px stride (smaller than rings)\n and teal/cyan tint (R533 mesh palette, not the\n emerald focal-cluster palette). The wide outer\n 3px glow at half alpha keeps the spokes thin\n (sw 1-2.5) but radiant when active. Same R642-\n R653 0.5x falloff vocabulary applied to the\n mesh tier. */\n filter: !reducedMotion && (hoveredHub || isHoveredSpoke)\n ? (isLight\n ? 'drop-shadow(0 0 1.5px rgba(13, 148, 136, 0.4)) drop-shadow(0 0 3px rgba(13, 148, 136, 0.2)) brightness(1.15)'\n : 'drop-shadow(0 0 1.5px rgba(34, 211, 238, 0.4)) drop-shadow(0 0 3px rgba(34, 211, 238, 0.2)) brightness(1.15)')\n : undefined,\n ...(isActiveSpoke ? {} : {\n animationDelay: `${-(idx * 0.25)}s`,\n // CSS var consumed by `.anet-topo-spoke-flow`\n // (line 859 of globals.css). React's CSSProperties\n // type doesn't model custom properties → cast\n // through Record<string, string>.\n ...({ ['--spoke-dur']: `${spokeDur}s` } as Record<string, string>),\n }),\n } as React.CSSProperties}\n />\n );\n });\n })()}\n\n {/* #170 tree-view MVP — org-chart elbow connectors. Tree layout\n only (treeConnectors.lines is empty in ring/grid). Rendered\n here, behind the nodes + labels, as right-angle polylines:\n vertical drop from the parent, horizontal at the mid-y, then\n vertical into the child. pointer-events off so they never\n intercept a node click. The synthetic-node chips (指挥中心\n root / 未分组 bucket) render here too — they have no backing\n session so the node-map below skips them. */}\n {layout === 'tree' && treeConnectors.lines.map((ln, i) => {\n const midY = (ln.y1 + ln.y2) / 2;\n // drop from just below the parent ring to just above the child\n // ring — keeps the connector clear of the avatars themselves.\n const startY = ln.y1;\n const endY = ln.y2;\n const d = `M ${ln.x1} ${startY} L ${ln.x1} ${midY} L ${ln.x2} ${midY} L ${ln.x2} ${endY}`;\n return (\n <path\n key={`tree-conn-${i}`}\n d={d}\n fill=\"none\"\n stroke={pal.containerBorder}\n strokeWidth={1.5}\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n opacity={0.55}\n style={{ pointerEvents: 'none' }}\n />\n );\n })}\n {layout === 'tree' && treeConnectors.synthLabels.map((s, i) => {\n // synthetic node chip: a small pill with a centred label, drawn\n // at the synthetic node's slot. No status ring → never counted\n // by the node↔node overlap test; sized to clear neighbours.\n const chipW = Math.max(58, s.label.length * 13 + 20);\n const chipH = 26;\n return (\n <g key={`tree-synth-${i}`} style={{ pointerEvents: 'none' }} data-topo-tree-synth={s.label}>\n <rect\n x={s.x - chipW / 2}\n y={s.y - chipH / 2}\n width={chipW}\n height={chipH}\n rx={13}\n fill={pal.containerBg}\n stroke={pal.containerBorder}\n strokeWidth={1.25}\n strokeDasharray=\"3 3\"\n opacity={0.92}\n />\n <text\n x={s.x}\n y={s.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n fontSize={12}\n fontWeight={600}\n fill={pal.legendAccent}\n >\n {s.label}\n </text>\n </g>\n );\n })}\n\n {/* #111: prefix-group boundary boxes (Vincent 4722). Grid layout\n only — groupBoxes is empty in ring mode. Rendered behind the\n flow links + nodes; pointer-events off so they never intercept\n a node click. Restrained dashed container + group-name label. */}\n {groupBoxes.map((box, boxIdx) => {\n /* R626 — extend isHovered gate to also fire when box.key\n matches the chat partner's prefix-group. Single-line\n gate union cascades the group cluster's hover\n treatments (R587 brightness + R68 stroke tint to\n legendAccent + R142 url(#topo-groupbox-lift) drop-\n shadow + R68 dashed→solid stroke + R610 label tint\n rect brightness + group label glow R479) to the chat\n partner's prefix-group cluster.\n\n 12th anchor in chat-target-gated brightness family —\n extends chat identification gesture from per-node +\n edges + recent-rows (R615-R625) to the GROUP CLUSTER\n scope. The chat partner's group box, label tint,\n drop-shadow lift, and stroke all light up softly when\n chat is open — providing a baseline \"your chat\n partner is in this cluster\" signal that composes with\n the per-node R618-R624 cascade.\n\n Computed locally here (rather than at the line ~1061\n activeGroup declaration) because chatAlias is declared\n at line ~1369, after activeGroup. Local computation\n avoids the forward-reference TS error while delivering\n the same effective gate union at the only call site\n (line 5385). */\n const chatGroupKey = chatAlias ? (groupKeys[chatAlias] ?? chatAlias) : null;\n const isHovered = activeGroup === box.key || chatGroupKey === box.key;\n /* Round 561 / Loop — inspection-overrides-encoding family\n 4th anchor. When operator hovers a NODE ALIAS on the\n canvas, the group-label that CONTAINS that node lifts\n to full opacity, signalling \"this is the cluster\n whose member you're inspecting\".\n Family progression (4 anchors now):\n R484 recent-row timestamp brightens on alias hover\n R485 edge particle opacity lifts on alias hover\n R486 minimap dot opacity lifts on alias hover\n R561 group-label opacity lifts on member-alias hover ← this round\n Pure paint axis (opacity only) — same restraint as\n R486. NOT bundled into the existing `isHovered` flag\n so the marching-ants live animation (gated on\n `!isPinned && !isHovered`) keeps running; the box\n rect stroke widen / fill brighten / letter-spacing\n / filter glow gates stay tied to direct label\n hover/pin semantics.\n Mirror of R486's pattern: `hoveredAlias === s.alias`\n extends a focused opacity branch independent from\n the rest-state encoding (online/offline). R561 here:\n `groupKeys[hoveredAlias] === box.key` extends a\n cluster-awareness opacity branch independent from\n the existing isHovered / isPinned semantics. */\n const isMemberAliasHovered = !!hoveredAlias && groupKeys[hoveredAlias] === box.key;\n // R68: distinguish \"locked by click\" from \"currently hovered\".\n // R63 made pinned and hovered identical (both hit isHovered\n // via activeGroup). A user with one team pinned should see at\n // a glance which is the locked one even while their cursor\n // sweeps elsewhere. isPinned reads pinnedGroup directly\n // (NOT activeGroup) so the visual is specific to the sticky\n // state — transient hover keeps the R63 isHovered styling.\n const isPinned = pinnedGroup === box.key;\n // Round 18 / Loop: group-box hover linkage. The Round 8 fade\n // already dropped OUT-of-focus groups to 0.28, but the IN-focus\n // group sat at its baseline appearance — no positive emphasis.\n // Hovering now upgrades the box to an \"accent\" treatment:\n // solid stroke (not dashed), thicker, accent-coloured; brighter\n // text and slightly stronger fill. Label and box read as one\n // selected unit. Geometry unchanged → overlap test untouched.\n // R132: per-group marching-ants duration computed once,\n // reused on the data attribute + the inline custom property.\n const w = box.statuses.working;\n const marchDur = w >= 6 ? 8 : w >= 4 ? 10 : w >= 2 ? 12 : 14;\n // Round 468 / Loop — single-tier classifier. Surfaces the\n // semantic the R319 pip-strip already encodes implicitly:\n // a cluster where every member sits in one status tier\n // renders as `name · count` only (offending duplicate pip\n // dropped). Pre-R468 that \"all members in tier X\" fact\n // was visible to the eye (no pips) but not queryable from\n // the DOM. R468 attaches the classifier as\n // `data-group-tier`:\n // 'all-working' — w===count, fleet uniformly busy\n // 'all-idle' — i===count, fleet uniformly waiting\n // 'all-offline' — o===count, fleet uniformly down\n // 'mixed' — at least 2 tiers present\n // Sibling R466/R467 pattern — expose composed state as a\n // data-attr without changing paint. Use cases: Playwright\n // assertions, external CSS hooks, accessibility enrichment.\n const groupTier =\n box.statuses.working === box.count ? 'all-working' :\n box.statuses.idle === box.count ? 'all-idle' :\n box.statuses.offline === box.count ? 'all-offline' :\n 'mixed';\n return (\n <g\n key={`grp-${box.key}`}\n data-group={box.key}\n data-group-tier={groupTier}\n // Round 173 / Loop: group boxes pick up the first-paint\n // fade-in wave alongside R9 staggered nodes (0-540ms)\n // and R172 staggered edges (280-980ms). Pre-R173 the\n // structural box frames appeared instantly while the\n // nodes inside eased in — the reveal felt like\n // 'frame slams down, nodes drift in'. The .anet-fade-in\n // CSS animation (R3 origin, 0.15s ease-out, mount-once)\n // plays alongside the node R9 stagger; each box offset\n // by boxIdx × 60ms (cap at 8 indices so a fleet with\n // many groups still finishes within ~500ms) so boxes\n // appear like a soft sweep across the grid rather than\n // a single pop. animation-fill-mode default 'none' →\n // post-animation control reverts to the inline opacity\n // style below (1 / 0.28 based on filter pin state).\n // data-group-fade-delay exposes the computed delay for\n // test probes.\n /* Round 470 / Loop — sync the R8 out-of-focus dim\n transition cadence from Tailwind's `transition-\n opacity` default (150ms ease-in-out) to 200ms\n ease-out to match the rest of the cluster's\n motion vocabulary. Hero D #147 stack established\n 200ms ease-out across every cluster axis:\n parent text (codex p.125)\n parent rect (R461 xywh + R464 rx + R248 paint)\n hitbox rect (R459 fill+opacity + R460 x+width\n + R465 rx)\n The wrapper <g>'s opacity flip (1 → 0.28 when\n another group is active) was the LAST surface\n still at 150ms — when the user hovers a group\n label, out-of-focus groups dimmed 50ms faster\n than the focused group's tint brightened, a\n small but perceivable rate-desync. R470 lifts\n the wrapper to 200ms ease-out. duration-200 +\n ease-out are Tailwind v4 utility classes; the\n anet-fade-in mount-once keyframe stays in the\n className for first-paint stagger (R173). */\n className=\"transition-opacity duration-200 ease-out anet-fade-in\"\n data-group-fade-delay={Math.min(boxIdx, 8) * 60}\n data-group-fade-transition=\"200ms\"\n // R63: drop the blanket pointerEvents:'none' that\n // previously sat here. Chrome's SVG impl doesn't let a\n // child override a parent's `none` even though the spec\n // says it should — moving the property onto just the\n // rect (where it's needed so nodes underneath stay\n // clickable) lets the label text receive its own click.\n style={{\n opacity: !activeGroup || isHovered ? 1 : 0.28,\n animationDelay: `${Math.min(boxIdx, 8) * 60}ms`,\n }}\n >\n <rect\n x={box.x}\n y={box.y}\n width={box.w}\n height={box.h}\n /* Round 464 / Loop: group-box rx 14 → 16 on isPinned.\n Geometric softening at the corner radius — locked\n groups read with subtly rounder shoulders than\n hovered/idle. +2px reads as a calm \\\"settled in\\\"\n posture (subtler than a fill or stroke bump but\n unmistakable across the whole cluster boundary).\n Pin signature on the group-box rect now spans 7\n axes:\n R63 text fill brighten\n R142 drop-shadow filter\n R432 text letter-spacing 0→0.5\n R444 count tspan fw 500→600\n R457 parent text fw 700→800\n codex p.125 text opacity 0.55→1\n R464 corner rx 14→16 (this round)\n transition list (R461) already covers x/y/width/\n height 200ms ease-out; appended `rx 200ms ease-\n out` so the rounding eases alongside the geometry\n axes. SVG2 CSS animation on rx: Chrome 95+ /\n Safari 16+ / FF 70+ (same matrix as x/y/w/h).\n data-group-box-rx exposes the resolved value. */\n rx={isPinned ? '16' : '14'}\n data-group-box-rx={isPinned ? '16' : '14'}\n fill={isLight ? '#18181b' : '#a5b4fc'}\n // R68: 3-tier opacity + stroke ladder.\n // pinned → fill 0.08 / 0.13, stroke 3 px (locked)\n // hovered → fill 0.05 / 0.09, stroke 2 px (inspecting)\n // idle → fill 0.025 / 0.045, stroke 1.5 px dashed\n /* Round 506 / Loop — category-differentiation family\n 3rd anchor. Orphan band rest-state fillOpacity drops\n slightly below prefix-group rest (0.025/0.045 →\n 0.015/0.028). Adds a 3rd independent paint\n differentiator to the orphan visual signature:\n R499 fontStyle: italic (label text)\n R503 '3 6' dash pattern (rect stroke)\n R506 lower fillOpacity (rect fill) ← this round\n Three independent channels (typography + stroke\n pattern + fill density) collectively encode the\n catchall semantic at rest. Pin and hover branches\n UNCHANGED (still 0.05/0.09 hover, 0.08/0.13 pin) —\n orphan box gets full visual emphasis on inspection\n identical to prefix groups; the differentiation\n lives ONLY in the unsolicited rest state. The\n ~40% drop (0.045 → 0.028 cyber, 0.025 → 0.015\n light) is subtle enough that the orphan box stays\n visible at rest, just quieter — matches the\n \"misc bucket, less attention-deserving\" semantic\n without losing the visual anchor.\n Pure paint axis; bbox unchanged; R51 SVG sentinel\n safety untouched (overlap-test gates to g[data-\n node], cluster rect invisible to it).\n data-group-box-fill-opacity attr surfaces the\n resolved value for tests. */\n fillOpacity={\n isPinned ? (isLight ? 0.08 : 0.13)\n : isHovered ? (isLight ? 0.05 : 0.09)\n : box.isOrphan ? (isLight ? 0.015 : 0.028)\n : (isLight ? 0.025 : 0.045)\n }\n data-group-box-fill-opacity={\n isPinned ? (isLight ? 0.08 : 0.13)\n : isHovered ? (isLight ? 0.05 : 0.09)\n : box.isOrphan ? (isLight ? 0.015 : 0.028)\n : (isLight ? 0.025 : 0.045)\n }\n stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}\n strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}\n /* Round 503 / Loop — category-differentiation family\n 2nd anchor (R499 = orphan label italic, 1st anchor).\n Orphan band rest-state strokeDasharray switches from\n '6 6' (prefix-group default) to '3 6' (tighter\n dashes). Pre-R503 the rect dash pattern was uniform\n across all bands; combined with R499's italic label,\n the orphan box now has TWO independent paint/\n typography differentiators at rest:\n R499 fontStyle: italic (label text)\n R503 '3 6' dash pattern (rect stroke) ← this round\n The R85 marching-ants animation continues to work\n with the new dash size (uses --march-dur custom\n property, dash-length-agnostic) — orphan's ants\n just have a different visual rhythm than prefix-\n group ants, reinforcing the catchall semantic.\n Pinned/hovered orphan still gets 'none' (solid\n stroke) so the hover/pin affordance is preserved\n — the differentiation lives ONLY in the rest\n state, never blocking inspection.\n Pure paint axis; no geometry change; bbox unchanged\n (strokeDasharray is paint-only). R51 SVG sentinel\n safety untouched (overlap-test gates to g[data-\n node], this cluster rect is invisible to it).\n data-group-box-orphan attr surfaces the gate for\n tests + future polish references. */\n strokeDasharray={\n (isPinned || isHovered) ? 'none' :\n box.isOrphan ? '3 6' : '6 6'\n }\n data-group-box-orphan={box.isOrphan ? 'true' : 'false'}\n /* Round 380 / Loop: cluster box stroke gets round\n linecap + round linejoin. Sibling SVG stroke-\n softening polish to R378 flow-rail linecap + R379\n minimap viewport linejoin — extends the family to\n the group cluster boundary box (grid layout only):\n R288 chrome icons strokeLinecap='round'\n R378 flow-rail dashes strokeLinecap='round'\n R380 group box dashes strokeLinecap='round' (this round)\n R379 viewport rect strokeLinejoin='round'\n R380 group box corners strokeLinejoin='round' (this round)\n Linecap rounds the R85 '6 6' marching-ants dash\n pills at rest — each 6 px dash gains a ~0.75 px\n round cap (sw=1.5 idle), reading as soft pills\n instead of sharp 6 × 1.5 px rectangles. Linejoin\n rounds the 4 sharp 90° corners (any state — solid\n or dashed); at sw=1.5 the join arc is ~0.75 px,\n matching R379 viewport vocabulary. Geometry-safe:\n stroke-* properties only affect paint, not bbox.\n The R51 sentinel 1.5/3 strokeWidth values stay\n intact (the overlap probe is gated to g[data-\n node], so this cluster-internal rect is invisible\n to it anyway). data-group-box-linecap + -linejoin\n attrs expose the values for tests. */\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n data-group-box-pinned={isPinned ? 'true' : 'false'}\n data-group-box-linecap=\"round\"\n data-group-box-linejoin=\"round\"\n data-group-box-geom-transition=\"x,y,width,height\"\n data-group-box-brightness={(isPinned || isHovered) ? '1.15' : '1'}\n /* R682 — group cluster box joins the multi-layer halo\n family. Pre-R682 the filter chain was `url(#topo-\n groupbox-lift) brightness(1.15)` (R587) — the SVG\n filter primitive provides a vertical ELEVATION\n shadow (dy=3, blur=4) but no radial halo. R682\n prepends 2-layer drop-shadow at pal.legendAccent\n tint between the SVG lift and the brightness:\n url(#topo-groupbox-lift) elevation\n drop-shadow(0 0 3px ${pal.legendAccent}80) near halo\n drop-shadow(0 0 6px ${pal.legendAccent}40) far halo\n brightness(1.15) paint lift\n\n Now the pinned cluster has a colored cyan aura\n around its rectangle ALONGSIDE its elevation\n shadow — coherent with R648 group LABEL TEXT's\n 3+6 stride cyan halo. The cluster box and its\n label glow in lockstep when the group is locked\n in or hover-previewed.\n\n 3+6 stride matches the panel-tier scale (cluster\n box is ~200×140px — wider than per-node surfaces);\n alpha 80/40 matches the family standard. SVG\n elevation filter PRECEDES the drop-shadows in the\n chain so it composes the shadow BEFORE the halo\n wraps around the rect — preserves the existing\n elevation reading.\n\n 41st anchor in multi-layer halo family — first\n group-CLUSTER anchor. */\n data-group-box-halo-layers={(isPinned || isHovered) ? '2' : '0'}\n // R85: ambient \"marching ants\" drift on the perimeter\n // when this group has at least one working member, and\n // neither pin nor hover is active (those treatments\n // already shout for attention via solid stroke). 12s\n // cycle reads as ambient — the eye parses \"live work\n // here\" without registering the box as animating.\n // R132: per-group ant rate buckets on box.statuses.working\n // — the same coupling-to-busyness idiom R84 uses for the\n // hub and R131 uses for the outer-ring orbit, applied at\n // the GROUP scale. A team with one working member ambles;\n // a team with five working members visibly accelerates.\n // Bucket boundaries (1 / 2-3 / 4-5 / 6+) chosen to land\n // on the same 14/12/10/8 cadence ladder so the three\n // motion layers (hub / ring / group) keep a coherent\n // tempo grammar. Default 12s when working=0 doesn't\n // matter — the className is only applied when working>0.\n /* Round 561 — marching-ants gate refined to halt only\n on direct-label-hover (or pin), NOT on member-alias-\n hover. Pre-R561 `isHovered` covered BOTH cases via\n the line 1044 fallback `hoveredGroup ?? (hoveredAlias\n → groupKeys[hoveredAlias])` — so hovering ANY member\n node halted the ants, treating indirect inspection\n the same as direct attention.\n R561 differentiates: ants now keep running during\n member-alias hover (indirect / cluster-awareness\n inspection), halting ONLY on direct label hover or\n pin. The cluster's live signal stays alive while\n operator inspects member nodes — distinct visual\n telegraph for \"directly attending this cluster\"\n vs \"inspecting one of its members\".\n Gate uses `hoveredGroupLabel === box.key` directly\n (the LABEL hover state) instead of `isHovered`\n (which combines label-hover + member-alias-hover\n via activeGroup). data-group-box-live + the live\n className both flip on the same refined gate. */\n data-group-box-live={!isPinned && hoveredGroupLabel !== box.key && box.statuses.working > 0 ? 'true' : 'false'}\n data-group-box-march-dur={marchDur}\n data-group-box-lifted={(isPinned || isHovered) ? 'true' : 'false'}\n className={!isPinned && hoveredGroupLabel !== box.key && box.statuses.working > 0 ? 'anet-topo-groupbox-live' : undefined}\n // R142: drop-shadow filter when pinned or hovered. Box\n // visually \"rises off the canvas\" — same vocabulary\n // R18 KPI cards + R135 overlay panels use. Idle group\n // boxes carry no filter (purely flat dashed outline)\n // so the unstyled canvas stays uncluttered. Filter\n // affects paint area only, not the geometric bbox\n // the overlap-test reads, so zero-overlap invariant\n // is preserved.\n filter={(isPinned || isHovered) ? 'url(#topo-groupbox-lift)' : undefined}\n style={{\n /* R587 — group box gains stacked brightness(1.15)\n on hover/pin. 26th anchor in per-element\n brightness family, 19th in stacked-filter\n sub-pattern. Inline style.filter overrides the\n attribute filter (kept intact for the R142\n documentation trail); stacked syntax preserves\n the R142 url(#topo-groupbox-lift) SVG drop-shadow\n lift on hover/pin, with brightness layered on\n top.\n\n Same R582/R583 stacked-filter pattern at the\n group-cluster scope.\n\n Group box inspection signature now 6 layers\n (spans paint + stroke + geometry + filter):\n R68 fillOpacity 0.045 → 0.13 (cyber pin)\n R68 strokeWidth 1.5 → 2 → 3\n R68 stroke tint → legendAccent\n R503 strokeDasharray → 'none' on activation\n R142 filter → url(#topo-groupbox-lift)\n R587 filter stack → brightness(1.15) ← this round\n\n Same existing transition list already includes\n 'filter 200ms ease-out' (R142 cadence). No\n transition change needed. */\n filter: (isPinned || isHovered)\n ? `url(#topo-groupbox-lift) drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) brightness(1.15)`\n : undefined,\n /* Round 248 / Loop: append fill 200ms ease-out to\n the existing R66 transition list. Pre-R248 the\n rect's fill (isLight ? '#18181b' (slate-900) :\n '#a5b4fc' (indigo-300)) snapped on theme toggle\n while stroke / fill-opacity / filter all eased.\n Closes the last theme-toggle snap on the group\n box surface — same idiom R246 + R247 used at\n per-node label-card and side-panel scopes.\n Round 461 / Loop: extend the transition list to\n all 4 geometry axes (x, y, width, height) so\n when a cluster grows / shrinks (member joins,\n leaves, prefix rebalance, dense toggle, status\n flip) the BIG outer container slides into the\n new bounds at the same 200ms cadence the R460\n inner hitbox tint rect now uses. Pre-R461 the\n outer 200×140 px box snap-jumped on cluster\n resize while the inner 160×18 hitbox slid —\n jarring two-rate motion at the same surface.\n R461 unifies both rects to slide as one, with\n the parent box driving the visual envelope and\n the inner hitbox tracking the bottom-edge tint.\n Hero D #147 motion-coherence at the FULL cluster\n container tier (not just the label tint).\n data-group-box-geom-transition attr exposed. */\n transition: 'stroke 200ms ease-out, stroke-width 200ms ease-out, fill-opacity 200ms ease-out, filter 200ms ease-out, fill 200ms ease-out, x 200ms ease-out, y 200ms ease-out, width 200ms ease-out, height 200ms ease-out, rx 200ms ease-out',\n pointerEvents: 'none',\n // CSS var consumed by `.anet-topo-groupbox-live`\n // (line 877 of globals.css). React's CSSProperties\n // type doesn't model custom properties, so cast\n // through Record<string, string>.\n ...({['--march-dur']: `${marchDur}s`} as Record<string, string>),\n }}\n />\n {/* R63: wrap label in a clickable <g> with an invisible\n rect hitbox. The text alone wasn't getting hit-tested\n reliably — the SVG-wide topo-panel <rect> intercepts\n at the label's screen position when the label sits at\n a high viewBox-y (it lands below where the compositor\n expects the text to paint on top, same gotcha as the\n recent-signal panel rows in R56). Hitbox rect + the\n parent <g> taking the click + onPointerDown stop-\n propagation match the R55/R56/R61 pattern. */}\n <g\n role=\"button\"\n tabIndex={0}\n aria-pressed={pinnedGroup === box.key}\n data-group-label-hit={box.key}\n className=\"anet-topo-svg-focus\"\n style={{ pointerEvents: 'all', cursor: 'pointer' }}\n onPointerDown={(e) => e.stopPropagation()}\n onClick={() => setPinnedGroup(prev => prev === box.key ? null : box.key)}\n // R86: hover the label → transient group focus. Releases\n // on leave; activeGroup = hoveredGroup ?? pinnedGroup\n // formula already handles transient-over-pin so a\n // user can spot-compare teams without losing their\n // pinned one. Closes the same R83-style hover/click\n // gap (segments) — now group labels carry it too.\n onPointerEnter={() => setHoveredGroupLabel(box.key)}\n onPointerLeave={() => setHoveredGroupLabel(prev => prev === box.key ? null : prev)}\n // R152: a11y completeness — R63 added role + tabIndex +\n // aria-pressed but never wired onKeyDown, so the focused\n // group label was tab-reachable but Enter/Space was a\n // no-op. Closes the last keyboard gap among the\n // role=\"button\" surfaces. Other group-pin trigger paths\n // (R69 palette, R74 cmdk, R86 hover, dispatchEvent) are\n // unchanged. Matches the onKeyDown idiom from R116 /\n // R139 / R140 / R151 (Enter & Space → same setter as\n // onClick, preventDefault on Space to stop SVG scroll).\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedGroup(prev => prev === box.key ? null : box.key);\n }\n }}\n >\n {/* R99: SVG <title> tooltip listing group members +\n status breakdown. Same info-density spirit as\n R97 pill tooltips + R98 node tooltips — anywhere\n a UI element says \"alpha · 3\" should hover-\n explain WHICH 3. Truncates at 8 aliases with a\n \"+N more\" suffix so a 20-member band doesn't\n paint a 22-line tooltip. */}\n {(() => {\n const members = Object.entries(groupKeys)\n .filter(([, key]) => key === box.key)\n .map(([alias]) => alias);\n const memberPreview = members.slice(0, 8).join(', ');\n const suffix = members.length > 8 ? ` + ${members.length - 8} more` : '';\n const statusSummary = [\n box.statuses.working > 0 ? `${box.statuses.working} working` : null,\n box.statuses.idle > 0 ? `${box.statuses.idle} idle` : null,\n box.statuses.offline > 0 ? `${box.statuses.offline} offline` : null,\n ].filter(Boolean).join(' · ');\n return (\n <title>{[\n `${box.key} (${members.length} member${members.length === 1 ? '' : 's'})`,\n statusSummary || null,\n `${memberPreview}${suffix}`,\n pinnedGroup === box.key ? 'click to release pin' : 'click to pin this group',\n ].filter(Boolean).join('\\n')}</title>\n );\n })()}\n {/* v0.11.0 #147 Hero D — Vincent 5401: \"太大太丑,\n 都放到框的右下角的小字\". First-cut bottom-right\n placement collided with bottom-row nodes (cluster\n geometry has no bottom padding; only GROUP_TOP=12\n top band). Pivoted to Option C from #147 spec:\n keep top-left anchor BUT shrink fontSize (13 → 9)\n and dim default opacity (1 → 0.55, hover/pin\n restore to 1). Satisfies \"太大太丑\" via the size +\n opacity axes while keeping the existing geometry\n contract that topo-overlap-test gates. Hitbox\n rect width tightens to min(box.w-12, 160) to\n track the narrower label render. */}\n {/* Round 465 / Loop — hitbox tint rect rx 4 → 5 on\n pinnedGroup match. Mirrors R464 (parent group-box\n rx 14 → 16 on isPinned) at the hitbox tier. The\n R460 hitbox carried fixed rx=4 since codex p.125\n pivoted it to the bottom-of-band position; the\n pin-state geometric softening was only on the BIG\n outer container, not the small hitbox underneath.\n R465 adds +1 px corner rounding on pin so the\n tint rect echoes the parent's locked posture at\n its own scale (8% relative bump matches R464's\n 14→16 ≈ 14% scaled to the smaller rect).\n Transition list (R460 fill/opacity/x/width 200ms\n ease-out) extends to include `rx 200ms ease-out`\n so the rounding eases under the same cadence.\n SVG2 CSS animation on rx: Chrome 95+ / Safari\n 16+ / FF 70+ (same matrix as x/y/w/h).\n data-group-label-tint-rx exposes the resolved\n value for tests. */}\n <rect\n x={box.x + 6}\n y={box.y + 2}\n width={Math.min(box.w - 12, 160)}\n height={18}\n rx={pinnedGroup === box.key ? '5' : '4'}\n data-group-label-tint-rx={pinnedGroup === box.key ? '5' : '4'}\n fill={pinnedGroup === box.key || hoveredGroupLabel === box.key ? pal.legendAccent : 'transparent'}\n opacity={pinnedGroup === box.key ? (isLight ? 0.16 : 0.20)\n : hoveredGroupLabel === box.key ? (isLight ? 0.09 : 0.13)\n : 1}\n data-group-label-tinted={pinnedGroup === box.key ? 'pinned' : hoveredGroupLabel === box.key ? 'hover' : 'none'}\n /* Round 459 / Loop — cadence-sync follow-on to codex\n preview.125 (Hero D #147). Codex's parent <text>\n transition list now reads:\n 'fill 200ms, letter-spacing 200ms,\n font-weight 200ms, opacity 200ms'\n — 200ms ease-out across every axis. The label\n hitbox tint rect underneath was still at 150ms\n (legacy R107 cadence), so the tint snapped in\n 50ms ahead of the parent label brightening —\n a small but perceivable mistimed cascade when\n hovering or clicking to pin a cluster. R459\n lifts both axes to 200ms to lock the tint\n under the label as one motion-coherent state\n flip. Hover/pin/unpin all feel as a single\n unified ease rather than \"tint pops, label\n follows\". data-group-label-tint-transition\n attr exposes the timing for tests. */\n /* Round 460 / Loop — extend the R459-200ms tint rect\n transition list to include `x` + `width` so the\n hitbox slides into place when a cluster grows or\n shrinks (member joins / leaves / status change\n re-pricing box.w). Pre-R460 every resize snap-\n jumped the hitbox bounds — a small but visible\n glitch right at the moment the operator's\n attention is on the cluster. SVG2 CSS animation\n on geometry attrs has shipped in Chrome 95+ /\n Safari 16+ / FF 70+; the runtime gracefully\n no-ops on older browsers. Sibling motion idiom\n to R134 / R141 / R142 (panel rect transitions)\n at the group-label hitbox tier.\n data-group-label-tint-geom-transition attr\n exposes the geometry-axis presence for tests. */\n data-group-label-tint-transition=\"200ms\"\n data-group-label-tint-geom-transition=\"x,width,rx\"\n data-group-label-tint-brightness={(pinnedGroup === box.key || hoveredGroupLabel === box.key) ? '1.15' : '1'}\n style={{\n /* R610 — group label tint rect gains filter\n brightness(1.15) on the same pin OR hover-label\n gate that drives the fill swap. The pal.legend-\n Accent cyan/teal fill (0.20 pin / 0.13 hover\n alpha) lifts +15% — small but perceivable lift\n that ties the tint band's paint axis to its\n already-strong fill/opacity/geometry response\n on inspection.\n\n Pin/hover-gated brightness family extension:\n tint-band joins the legend pin-ring (R607) +\n group box (R587) + group label text (R571) +\n flow-arrow on legend hover (R609) at the\n panel-tier brightness coverage.\n\n Pure paint axis; no geometry shift. transition\n list extends with 'filter 200ms ease-out'\n alongside the existing 200ms fill/opacity/geom\n cadence. */\n filter: (pinnedGroup === box.key || hoveredGroupLabel === box.key)\n ? 'brightness(1.15)'\n : undefined,\n transition: 'fill 200ms ease-out, opacity 200ms ease-out, x 200ms ease-out, width 200ms ease-out, rx 200ms ease-out, filter 200ms ease-out',\n }}\n />\n {/* Round 218 / Loop: group label gains a letter-spacing\n transition on pin — the text subtly spaces out\n (0px → 0.5px) when the group is locked, giving the\n pinned state its own typographic signature distinct\n from R63's transient hover fill brighten. Hover and\n pin share the same fill colour (legendHeadline), so\n pre-R218 the only thing distinguishing them was\n R142 drop-shadow + R68 rect stroke. R218 adds a\n type-level signal: pinned text spreads slightly,\n feels \"locked in\" / \"open and held\". Letter-\n spacing is one of the few SVG text properties that\n interpolates smoothly across the major browsers.\n Hover stays at default tracking — the spread is\n pin-exclusive so users can read pinned vs\n hovered at the text alone. transition 200ms\n matches R142 fill timing so all the group-label\n state-flip channels (fill colour, rect stroke,\n rect drop-shadow, label tracking) ease as one. */}\n {/* Round 432 / Loop: extend the group-label letter-\n spacing tween from 2-tier (rest/pin) to 3-tier\n (rest/hover/pin → 0/0.25/0.5). Pre-R432 R218\n spread the text only on pin; hover got an\n R63 fill brighten (legendText → legendHeadline)\n but no typographic axis of its own. R432 adds\n the missing mid tier so hover telegraphs through\n BOTH the fill brighten AND a subtle kerning\n spread — sibling pattern to R427 node-alias\n (0/0.3/0.5) and R431 edge-badge (0/0.2/0.4) at\n group-label scope. Pin tier (0.5) still wins.\n Subtler mid tier (0.25 vs alias 0.3) because the\n group label is a structural anchor — too much\n spread would steal weight from the per-node\n alias identity it groups. Hover-letter-spacing\n family extension (8 anchors now):\n R344 chip count digit\n R345 panel title\n R347 active-links chip\n R351 vendor chip\n R420 zoom-level chip\n R427 node alias text\n R431 edge-badge digit\n R432 group label text (this round)\n R218 transition list ('fill 200ms, letter-spacing\n 200ms') untouched — additive conditional case. */}\n {/* Round 457 / Loop: group label parent text fontWeight\n 700 → 800 on isPinned. Adds typographic weight axis\n to the group-label parent text, sibling to R432\n letter-spacing tween at the same surface. Pre-R457\n pin lifted ls 0 → 0.5px (R218→R432 3-tier) but the\n fw stayed planted at R63's 700 — locked groups\n read as wider-but-same-weight. R457 adds the\n weight axis so pinned groups read as tightened\n AND wider, matching the R416/R424/R425/R426/R444/\n R445/R446 \"data tightens under attention\" idiom\n (now extended to the parent-text scope at the\n group-label tier). R63 fill brighten + R432\n letter-spacing 0/0.25/0.5 3-tier + R55 transition\n list all preserved; extends to include 'font-\n weight 200ms ease-out' so the bump eases under\n the same cadence. */}\n {/* v0.11.0 #147 Hero D — Vincent 5401 ask: \"dash 网络\n 图里面这个工程的名字也太大了, 超级丑\". Per Vincent\n screenshot 实测. Initial attempt moved label to\n bottom-right (#147 spec Option A); topo-overlap-test\n caught 7 grid collisions because cluster boxes have\n no bottom padding. Pivot to Option C: keep top-left\n anchor, shrink fontSize 13 → 9 (-31%, watermark\n register), dim default opacity 1 → 0.55 (hover/pin\n restore to 1). Net Twitter-grok improvement:\n cluster labels no longer dominate the canvas at\n rest; operator still hovers to find specific groups.\n Position unchanged to preserve the existing\n geometry that overlap-test gates. */}\n <text\n x={box.x + 12}\n y={box.y + 12}\n fill={isHovered ? pal.legendHeadline : pal.legendText}\n fontSize=\"9\"\n fontFamily=\"monospace\"\n fontWeight={isPinned ? '800' : '700'}\n /* Round 551 / Loop — category-differentiation family\n 4th anchor. Orphan band (\"其他\" catchall) rest-state\n LABEL opacity drops 0.55 → 0.4 (~27% relative dim),\n mirroring R506's rect fillOpacity drop pattern at\n the label-paint tier. Adds a 4th independent\n channel to the orphan visual signature at rest:\n R499 fontStyle italic (typography style)\n R503 '3 6' dash pattern (rect stroke)\n R506 lower rect fill-opacity (rect fill)\n R551 lower label opacity (label paint) ← this round\n Four independent channels (typography style +\n stroke pattern + rect fill density + label paint\n density) collectively encode the catchall semantic\n at rest — orphan band reads as \"misc bucket, less\n attention-deserving\" through every available paint\n channel, no chrome / color / geometry change.\n Pin and hover branches UNCHANGED — orphan label\n restores to full opacity 1 on inspection, same as\n prefix groups. The differentiation lives ONLY in\n the unsolicited rest state. The ~27% drop (0.55 →\n 0.4) is dimmer than R506's ~40% (rect could\n tolerate it; small 9px text needs more residual\n paint to stay legible) — orphan label stays\n readable when scanning, just clearly quieter.\n Pure paint axis; bbox unchanged; R51 SVG sentinel\n safety untouched (overlap-test gates to g[data-\n node], this group-label is invisible to it).\n transition list (R55/R432/R457/R479: fill, ls,\n fw, filter all 200ms) already eases opacity since\n `opacity 300ms ease-out` lives in the parent <text>\n CSS — wait, only filter/ls/fw/fill 200ms are\n listed. Need to add 'opacity 200ms ease-out' for\n smooth orphan opacity flip on pin/hover transitions\n (currently opacity 0.55 → 1 was snapping).\n data-group-label-opacity attr exposes the resolved\n value for tests. */\n /* Round 561 — opacity ladder gains inspection-\n overrides-encoding branch via isMemberAliasHovered.\n Resolution order (most-emphatic first):\n isPinned || isHovered → 1 (direct attention)\n isMemberAliasHovered → 1 (R561 inspection)\n box.isOrphan → 0.4 (R551 orphan rest)\n (default) → 0.55\n R484/R485/R486-family mirror. */\n opacity={isPinned || isHovered || isMemberAliasHovered ? 1 : box.isOrphan ? 0.4 : 0.55}\n data-group-label-opacity={isPinned || isHovered || isMemberAliasHovered ? 1 : box.isOrphan ? 0.4 : 0.55}\n data-group-label-hovered={isHovered && !isPinned ? 'true' : 'false'}\n data-group-label-member-alias-hovered={isMemberAliasHovered ? 'true' : 'false'}\n data-group-label-font-weight={isPinned ? '800' : '700'}\n /* Round 479 / Loop — extend drop-shadow visual-polish\n family to a 4th anchor: group-label parent text\n on isPinned. Continues the R476/R477/R478 arc:\n R476 hub digit hover-gated emerald\n R477 legend pin-ring pin-gated row.fill\n R478 recent-row pip freshness-gated cyan\n R479 group-label text pin-gated cyan\n Hue: pal.legendAccent at 0x80 alpha (≈50%) — same\n accent family R107/R477 use for tint surfaces. 3px\n blur reads as a soft cyan halo around the locked\n cluster name. Stacks with the R432 letter-spacing\n spread + R457 fw lift + R63 fill brighten + R142\n drop-shadow on the parent rect — pin signature on\n group label scope now spans typography + chroma +\n paint + container-lift + text-glow.\n Filter is paint-only; bbox unchanged; overlap-test\n invariants hold (R51 selector gated to g[data-node]\n descendants, this label is invisible to the probe).\n transition list extends to include 'filter 200ms\n ease-out' alongside the existing fill/ls/fw/opacity\n 200ms tweens. */\n /* Round 538 / Loop — extends R479 group-label drop-\n shadow from pin-only to ALSO fire on hover, with\n a 2-tier alpha ladder matching the R432 letter-\n spacing 3-tier (hover at 0.25px / pin at 0.5px)\n pattern. Pre-R538 the paint axis was binary (lit\n on pin, dark on hover); R538 adds a softer hover\n glow that distinguishes from the stronger pin\n glow without losing the \"active state lights up\"\n gesture.\n 2-tier alpha ladder:\n pin (committed) cyan 80 hex (~50% alpha)\n hover (preview) cyan 4d hex (~30% alpha)\n rest none\n Pin signature stays distinctively brighter, but\n hover now telegraphs paint-axis attention too.\n Sibling to R534 edge-badge hover-precedence\n extension at the drop-shadow family. R479 hue\n (pal.legendAccent) preserved across both tiers.\n data-group-label-glow attr upgraded from binary\n ('true'/'false') to 3-value ('pin' | 'hover' |\n 'false') so tests can distinguish gate cause. */\n data-group-label-glow={isPinned ? 'pin' : isHovered ? 'hover' : 'false'}\n /* Round 499 / Loop — orphan band \"其他\" label gets\n fontStyle: italic to visually distinguish the\n catchall from real prefix-group bands. Pre-R499\n the orphan box label rendered identically to\n prefix-group labels (Hero D fontSize=9, fw=700,\n opacity 0.55 rest), so users had to read the\n literal text \"其他\" to identify the catchall. R499\n adds a pure-typography differentiation: italic\n signals \"this is the misc bucket, not a real\n named group\" while preserving full opacity\n affordance on hover/pin — the orphan box stays\n equally inspectable, just typographically marked\n as a different category. No geometry change\n (italic shifts glyph slant within the same bbox),\n no opacity loss, no behavior change. Sibling to\n R432 letter-spacing 3-tier + R457 pin fw-lift +\n R479 pin drop-shadow at the group-label scope.\n Falls under 配色 / 节点视觉 themes per the prompt;\n advances the \"信息密度\" axis by encoding\n category-distinction into a single typography\n channel without adding visual chrome. */\n /* Round 571 / Loop — group-label parent text joins the\n per-element brightness consistency family (R501/\n R558/R564/R567/R570) at uniform +15%. 8th anchor.\n Stacks brightness(1.15) onto the existing R479/R538\n drop-shadow filter — same R564/R570 stacked filter\n pattern (drop-shadow + brightness in one CSS chain).\n Pre-R571 the group-label parent text lifted in 5\n axes on hover/pin (fill + ls 3-tier + fw on pin +\n drop-shadow + opacity) but the glyph chromatically\n stayed at flat fill brightness. R571 adds the\n brightness axis to the glyph itself for cross-\n element consistency with the rest of the per-\n element brightness family.\n Filter chain on isPinned: `drop-shadow(0 0 3px\n ${pal.legendAccent}80) brightness(1.15)`.\n On isHovered (weaker tier): `drop-\n shadow(0 0 3px ${pal.legendAccent}4d) brightness(1.15)`.\n Rest: undefined.\n Per-element brightness family — 8 anchors at +15%:\n R501 vendor.logo image\n R558 vendor monogram\n R558 prefix-group fallback\n R564 alias text (stacked w/ DS)\n R567 node sub-text\n R570 edge-badge digit\n R571 group-label parent text ← this round\n transition list already includes 'filter 200ms ease-\n out' from R479 — no change needed. R432 ls + R457\n fw + R479 drop-shadow + R551 orphan opacity all\n preserved. */\n style={{\n transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',\n letterSpacing: isPinned ? '0.5px' :\n isHovered ? '0.25px' : '0px',\n fontStyle: box.isOrphan ? 'italic' : undefined,\n /* Round 648 / Loop — group-label drop-shadow gains a\n SECOND outer layer at 6px blur + halved alpha.\n Extends multi-layer halo family (R642-R647) from\n per-node + per-edge + freshness-pip to group\n cluster label — 2nd panel-tier anchor.\n Pin branch: inner 3px+0x80 → outer 6px+0x40\n Hover branch: inner 3px+0x4d → outer 6px+0x26\n Outer alpha = ~half inner (0x80→0x40, 0x4d→0x26 = 38/77)\n Both branches use pal.legendAccent tint; same\n 0.5x falloff vocabulary + 2x blur stride.\n Group label is a panel-row-tier glyph; uses 3px+\n 6px stride matching the panel-tier R647 pip and\n per-edge R646 badge. Pin branch already pre-R648\n at stronger alpha; outer layer makes the locked-\n in pin gesture radiate further.\n data-group-label-halo-layers attr exposes the\n gate state for tests. */\n filter: isPinned\n ? `drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) brightness(1.15)`\n : isHovered\n ? `drop-shadow(0 0 3px ${pal.legendAccent}4d) drop-shadow(0 0 6px ${pal.legendAccent}26) brightness(1.15)`\n : undefined,\n }}\n data-group-label={box.key}\n data-group-label-pinned={isPinned ? 'true' : 'false'}\n data-group-label-orphan={box.isOrphan ? 'true' : 'false'}\n data-group-label-brightness={(isPinned || isHovered) ? '1.15' : '1'}\n data-group-label-halo-layers={(isPinned || isHovered) ? '2' : '0'}\n >\n {box.key}\n {/* Round 19 / Loop: member-count chip. Inline tspan stays\n in the single <text> bbox the overlap test reads, so\n the node↔label guard still catches if the chip ever\n pushes the label far enough right to clip a node.\n Smaller + lighter weight reads as metadata, not name. */}\n {/* Round 229 / Loop: member-count chip drops its explicit\n fill so it inherits from the parent <text>, which means\n R142's hover-fill transition (legendText → legend-\n Headline, 200ms ease-out) NOW carries the count chip\n with it. Pre-R229 the parent name brightened on\n hover while the count tspan stayed at legendText —\n \"name lit, count dimmer than at rest\" inverted the\n tonal hierarchy. Inheriting matches the name's\n transition; both rest and hover keep the SAME\n tonal relationship between name and count.\n 7th surface in the hover-deepen-own-hue family\n (legend rows, chip-row counts, status pip, recent\n row text, pressure-bar segments, group-box fill +\n this round's group-label-count chip).\n\n Also picks up tabular-nums (5th surface in the\n info-density tabular-nums sweep after R224 edge\n badge / R225 hub digit / R225 panel header /\n R225 recent row count). The member count rolls\n over often (4→5→…→9→10 as a group grows) and\n lives at a fixed dx=6 offset from the name, so a\n digit-width jitter at 9→10 used to shift the\n whole count visibly. Tabular locks it. */}\n {/* Round 366 / Loop: group label member-count tspan\n fontWeight 400 → 500. Sibling polish to R363\n recent-row alias text fw 400 → 500 + R364 legend-\n row label fw 400 → 500 — closes the per-row 'count\n is fw 500 against label-tier fw 700' pattern at\n the group-label scope (grid layout cluster mark).\n Hierarchy snapshot post-R366 across all 3 row\n surfaces:\n recent count(hot/cold) fw 700/600 (R320)\n recent alias fw 500 (R363)\n legend count fw 600 (R309)\n legend label fw 500 (R364)\n group name fw 700 (legacy)\n group count fw 500 (R366, this round)\n Monospace family + R225 tabular-nums lock digit\n width, so the fw bump is paint-only — bbox\n unchanged + overlap-test invariants hold. R229\n fill-inherit from parent label (hover-deepen-own-\n hue family) preserved. data-group-label-count-\n font-weight attr exposes the value for tests. */}\n {/* Round 444 / Loop: group label count tspan\n fontWeight 500 → 600 on isPinned. Extends the\n \"data tightens under attention\" typographic-\n weight pattern to a 5th anchor at the group-\n label-count scope:\n R416 chip-digit (chip hover)\n R424 panel-digit (panel hover)\n R425 hub-digit (hub hover)\n R426 edge-badge-digit (pin/hot)\n R444 group-label-count (pinned) ← this round\n Same idiom — when the group is locked, its\n member-count tightens typographically alongside\n the R432 letter-spacing spread (0 → 0.5px) on\n the parent label. Hover keeps rest fw (500) so\n the locked vs preview distinction at the type\n level stays intact — same gate R432 used.\n Monospace + R225 tabular-nums lock the digit\n width across fw changes; bbox unchanged; overlap-\n test invariants hold. transition list adds\n 'font-weight 200ms ease-out' matching R432\n letter-spacing cadence. R229 fill-inherit\n preserved (parent text fill still drives the\n hover/pin color). data-group-label-count-font-\n weight + -pinned attrs exposed for tests. */}\n {/* v0.11.0 #147 — count tspan tracks parent fontSize:\n 11 → 8 to match the new 9px label scale (parent\n dropped 13 → 9 with same -2px gap to the count\n suffix). dx=\"4\" replaces dx=\"6\" — the smaller\n glyph baseline doesn't need the wider gutter. */}\n <tspan\n dx=\"4\"\n fontSize=\"8\"\n fontWeight={isPinned ? '600' : '500'}\n data-group-label-count={box.key}\n data-group-label-count-value={box.count}\n data-group-label-count-pinned={isPinned ? 'true' : 'false'}\n data-group-label-count-font-weight={isPinned ? '600' : '500'}\n style={{\n fontVariantNumeric: 'tabular-nums',\n transition: 'font-weight 200ms ease-out',\n }}\n >· {box.count}</tspan>\n {/* Round 58 / Loop: status mix pip strip. Compact text-\n based chips (e.g. \"2w 1i\") so the strip stays inside\n the same <text> bbox the overlap-test reads — keeps\n the R27 label↔label and R19 node↔label guards intact.\n Each tier is colour-coded against the legend swatches\n and only renders when count > 0, so a healthy all-\n working group reads simply \" · 2w\".\n\n Round 207 / Loop: each tspan eases in on mount\n via anet-fade-in. Pre-R207 when a group's first\n working node went idle (or first idle node went\n working), the new tier's tspan snap-popped into\n the label. Same snap-on-mount issue R203 fixed\n for recent-signal rows, applied at the group-\n label scope. Each tier is keyed on its boolean\n mount, so the animation fires once when the\n tspan first appears (count crosses 0 → 1+),\n not on every count update (e.g., 1 → 2 working\n preserves the tspan via React reconciliation).\n Exit remains snap — matches R190's \"fade-IN\n smooth, accept exit snap\" trade-off used for\n the R129 hot-tail. */}\n {/* Round 230 / Loop: tabular-nums on the 3 status pips\n so the count digit doesn't jitter the adjacent\n pip when a tier crosses 9 → 10. The pips render\n in sequence at dx=8/4/4 — width-shift on any\n tier propagates rightward through the strip,\n visibly compressing or stretching the gap\n between adjacent tier chips. Tabular locks the\n digit so the strip stays stable as tiers grow.\n 6th surface in the info-density tabular-nums\n sweep after R224 edge badge / R225 hub digit /\n R225 panel header / R225 recent row count /\n R229 group-label count. Tier-specific fill\n colours stay (semantic — working green /\n idle teal / offline slate). */}\n {/* Round 253 / Loop: append fill 200ms ease-out to\n each tspan's style so theme toggle eases the\n tier-coloured pips alongside every other\n theme-driven element. R230's tabular-nums\n stays. */}\n {/* Round 319 / Loop: drop a tier pip when its count\n equals box.count — i.e. single-tier groups (all\n working, all idle, all offline). Pre-R319 a 4-all-\n idle group rendered as `P站 · 4 4i` with the \"4\"\n visually doubled; Vincent telegram 5304 flagged\n this as 比较难看 in a real-data screenshot\n (ai-insight · 6 6i, blueleap · 3 3i, P站 · 4 4i).\n The dropped pip's information is already conveyed\n by the group-box stroke colour (R68 isPinned/\n hover accent uses the dominant-tier hue) plus\n the SVG <title> tooltip listing the status\n breakdown. Multi-tier groups (e.g. `alpha · 3\n 2w 1i`) render unchanged — those pips genuinely\n add breakdown info that the total doesn't carry. */}\n {/* Round 458 / Loop — Hero D #147 finishing polish on top of\n N站牛/codex preview.125 (Option C: top-left label fontSize\n 13→9 + opacity 0.55 rest / 1 hover+pin, count tspan 11→8).\n That ship left the 3 status pips at fontSize=11 — visibly\n DOMINATING the now-9px parent label they trail. Result on\n a 5-member cluster: `alpha · 5 3w 2i` renders inside-out\n as \"tiny name + tiny count + BIG bright pips\" rather than\n a coherent right-tail of metadata. R458 scales the 3 pips\n to fontSize=8 (matches count tspan) and tightens dx 8/4/4\n → 6/3/3 (gutter ratio 0.73/0.36 glyph-widths @ 11px ≈\n 0.75/0.38 glyph-widths @ 8px — same visual rhythm at the\n smaller scale). The whole group-label bottom-right strip\n now reads as a unified 9/8/8/8 typographic ladder:\n name (parent <text>) fontSize 9 fw 700/800\n · count (1st tspan) fontSize 8 fw 500/600\n Nw (2nd tspan) fontSize 8 fw 600\n Ni (3rd tspan) fontSize 8 fw 600\n No (4th tspan) fontSize 8 fw 600\n Closes Vincent /goal 5401 (\"太大太丑\") at the pip-strip\n tier; with codex preview.125 the spec is fully realized.\n Geometry-only attribute changes — bbox tightens slightly\n (8px chars vs 11px chars stay inside the original 240px\n hitbox max) so topo-overlap-test invariants hold.\n tabular-nums + anet-fade-in + theme-eased fill 200ms\n preserved on every tspan. */}\n {box.statuses.working > 0 && box.statuses.working !== box.count && (\n <tspan\n dx=\"6\"\n fill={isLight ? '#059669' : '#22c55e'}\n fontSize=\"8\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"working\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.working}w</tspan>\n )}\n {box.statuses.idle > 0 && box.statuses.idle !== box.count && (\n <tspan\n dx=\"3\"\n fill={isLight ? '#0d9488' : '#2dd4bf'}\n fontSize=\"8\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"idle\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.idle}i</tspan>\n )}\n {box.statuses.offline > 0 && box.statuses.offline !== box.count && (\n <tspan\n dx=\"3\"\n fill={isLight ? '#94a3b8' : '#6b7280'}\n fontSize=\"8\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"offline\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.offline}o</tspan>\n )}\n </text>\n </g>\n </g>\n );\n })}\n\n {/* directed message flows */}\n {flowLinks.map((link, index) => {\n const from = nodePositions[link.from];\n const to = nodePositions[link.to];\n if (!from || !to) return null;\n\n // Round 7 / Loop: lift now scales with distance so short links\n // aren't over-bent (long links keep the ~36px hump), and the\n // particle period shortens with link.count so busier edges\n // visibly pulse faster — instant info density on top of the\n // existing stroke-width-by-count chip.\n const dist = Math.hypot(to.x - from.x, to.y - from.y);\n const lift = (index % 2 === 0 ? 1 : -1) * Math.min(36, dist * 0.18);\n const path = curvePath(from, to, lift);\n const width = Math.min(2 + link.count, 7);\n const duration = Math.max(0.9, 2.6 / Math.sqrt(link.count));\n // Round 231 / Loop: per-edge phase stagger lifted into a\n // named constant so the R75 arrival ping + R76 dispatch\n // pulse SMIL animates can RE-COUPLE to the R103 particle's\n // cycle. Pre-R103 (when particle started at phase 0) the\n // ping fired at \"near end of cycle\" (-0.92*dur) and dispatch\n // at \"cycle start\" (0) — both phase-coincident with particle\n // arrival/departure respectively. R103's golden-ratio\n // stagger broke that coupling — particle now started at\n // phase (index*0.37)%dur while ping+pulse stayed at fixed\n // offsets, so the rings fired at random moments relative\n // to particle position. R231 expresses dispatch_begin and\n // arrival_begin in terms of THIS stagger so they fire\n // exactly when the particle is at source / near destination\n // respectively — restoring R75/R76's original semantic\n // and unifying the three SMIL elements into one\n // synchronised per-edge animation set.\n const stagger = (index * 0.37) % duration;\n // Round 10 / Loop: freshness fade. An edge that fired ≤30s ago\n // stays at full intensity; over 5 minutes it decays to a\n // floor. Surfaces \"what's happening now\" vs background\n // chatter without hiding old flow entirely (some context\n // still useful). `now` captured at useMemo-recompute time\n // (every 5s message refresh) — accuracy is within the poll\n // interval, plenty.\n //\n // Round 406 / Loop: edge freshness fade floor 0.35 → 0.40.\n // Stale-state legibility lift family (6th anchor) — pre-\n // R406 edges older than 5 minutes faded to α=0.35 (a 65 %\n // dim against full intensity). The decay rate is the same\n // 1 - ageMs/300s curve; only the FLOOR shifts. Sibling\n // treatment to:\n // R317 subordinate-text gray-500 → gray-400\n // R358 freshness ramp floor 0.25 → 0.30\n // R372 minimap offline-dot opacity 0.5 → 0.6\n // R404 hub-halo cyber trough 0.08 → 0.10\n // R405 hub-halo light trough 0.32 → 0.34\n // R406 edge freshness floor 0.35 → 0.40 (this round)\n // Edges past 5min now sit at 40% intensity instead of 35%\n // — they still recede against fresh edges but read\n // legibly enough to convey \"this conversation existed\".\n // ageMs threshold for the 5-minute decay unchanged; the\n // decay curve shape (linear) unchanged. The visual delta\n // is most pronounced on edges between 5-60 minutes old —\n // where the floor was binding pre-R406.\n const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;\n const fresh = Math.max(0.40, 1 - ageMs / (5 * 60 * 1000));\n // Round 16 arrow-tier binning — keep `topo-arrow` as the\n // medium tier id so the legend swatch picks it up unchanged.\n const arrowId = link.count <= 2 ? 'topo-arrow-s'\n : link.count <= 4 ? 'topo-arrow'\n : 'topo-arrow-l';\n\n // Round 39 / Loop: edge hover tooltip — surface the same\n // last_at + count info the freshness fade and arrow tier\n // already encode visually, in plain text. The stroke is the\n // hover target; SVG `<title>` honours newlines on every\n // browser the dashboard targets.\n const lastAt = relativeAgo(link.last_at);\n const tooltip = `${link.from} → ${link.to}\\n${link.count} message${link.count === 1 ? '' : 's'}${lastAt ? ` · last ${lastAt}` : ''}`;\n // Round 40 / Loop: edges follow node hover — when an alias is\n // hovered, every edge touching it brightens, the rest fade.\n // Pairs with the Round 8 group-focus fade on nodes: hover a\n // node to find \"who is this agent talking to\" at a glance.\n // No hover → multiplier is 1.0 (current behaviour preserved).\n // Round 50 / Loop: edge-on-self priority. When the user hovers\n // a flow edge directly (R48 widened the hitbox so this is now\n // a precise gesture), THAT edge gets the strongest boost (2.0)\n // and every other edge dims to 0.35. Node-hover (R40) keeps\n // its own ladder. Edge-hover and node-hover are mutually\n // exclusive in practice — the cursor is over one or the\n // other — but the order below makes edge-hover win if both\n // ever read truthy at the same React tick.\n // Round 53 / Loop: in-group edges follow team focus. R40\n // brightened only edges touching the exact hovered alias —\n // but with R8/R18 prefix-clustering, a user hovering one\n // member is asking about the team. So when BOTH endpoints of\n // an edge share the hoveredGroup (and neither is the exact\n // hovered alias — that gets the stronger 1.7×), the edge\n // boosts to 1.3×. Edges leaving the team (one endpoint in,\n // one out) still dim to 0.35× per R40 since they read as\n // background to the focus. Singletons fall through unchanged\n // (their group key is the alias itself, so bothInHoveredGroup\n // is impossible for a non-self-edge).\n // R116: composes hover ?? pin — a pinned edge stays \"hot\" after the cursor leaves.\n const isHoveredEdge = activeEdgeKey === link.key;\n const fromGroup = groupKeys[link.from] ?? link.from;\n const toGroup = groupKeys[link.to] ?? link.to;\n const bothInHoveredGroup = !!activeGroup && fromGroup === activeGroup && toGroup === activeGroup;\n // R63: also gate \"no filter\" on activeGroup so pinnedGroup\n // alone activates the in-group 1.3× boost + non-group dim.\n // When neither a node nor a group is in focus, mul is 1.\n // R77: when the user hovers the \"N active links\" chip, the\n // baseline 1× becomes 1.5× — every flow brightens at once.\n // Sits inside the no-other-hover branch so it doesn't fight\n // the edge-hover (2.0×) or node-hover (1.7×) priorities.\n const edgeOpacityMul = isHoveredEdge\n ? 2.0\n : activeEdgeKey\n ? 0.35\n : !hoveredAlias && !activeGroup\n ? (hoveredActiveLinks ? 1.5 : 1)\n : (link.from === hoveredAlias || link.to === hoveredAlias)\n ? 1.7\n : bothInHoveredGroup\n ? 1.3\n : 0.35;\n // Round 50: the hovered edge also visibly thickens so the eye\n // tracks it even at low message counts (width was 3 → 4.5 on\n // hover). 1.4× is enough to read as \"lifted\" without\n // breaching the 16-px hitbox bound.\n // Round 436 / Loop: extend the thickening to the\n // \"hovered-endpoint\" case — when hoveredAlias matches one\n // of this edge's endpoints, lift width by 1.15× (capped at\n // 8 px to stay clear of the 16-px hitbox). Pre-R436 the\n // edgeOpacityMul=1.7 (line 4703) lifted the matched edge's\n // OPACITY when an endpoint was hovered but the stroke-WIDTH\n // stayed at base — so the edge faded brighter without\n // thickening, leaving the paint+geometry axes mismatched.\n // Mirror of R430/R435 hub-spoke pattern (opacity + stroke-\n // width co-lift on hoveredAlias); R436 brings the same\n // dual-axis \"this node's link\" gesture to the edge scope.\n // 1.15× is subtler than isHoveredEdge=1.4× because\n // endpoint-hover lifts MANY edges at once (every edge\n // incident on the hovered node) while edge-hover lifts ONE\n // — the gesture should read as \"highlighted\" not \"loud\".\n // R166 stroke-width 300ms transition already in the\n // visible-path style list so the lift eases for free.\n /* R624 — extend isEndpointHoveredEdge to include chatAlias.\n Single conceptual change cascades all edge-endpoint-\n related axes (R94 visible-path α 1.7×, R436 sw 1.15×,\n R164 endpoint ring r+sw+opacity) to lift on ANY edge\n incident on the chat partner — same gate union as the\n other R615-R623 chat-target gates.\n\n 10th anchor in chat-target-gated brightness family:\n R615 chat ring + R618 card + R616 alias + R617 sub +\n R619 avatar + R620 badge + R621 status ring + R622\n spoke + R623 halo + R624 incident edges ← this round.\n\n Pre-R624 the chat partner's connecting edges stayed\n at rest opacity/sw while every other surface lit up —\n visual disconnect at the message-flow tier. Post-R624\n all edges in/out of the chat partner thicken + brighten,\n completing the canvas-wide \"I am chatting with this\n agent and these are its conversation partners\" gesture. */\n const isEndpointHoveredEdge = (!!hoveredAlias && (link.from === hoveredAlias || link.to === hoveredAlias))\n || (!!chatAlias && (link.from === chatAlias || link.to === chatAlias));\n /* R692 — hoist pinnedEdgeKey check from inside the badge IIFE\n (line ~7409) to the outer edge map closure so the visible\n path (R677), flow-rail (R678), and particle (R679) filter\n gates can also fire on pin state. Pre-R692 only the badge\n + digit halo'd when an edge was pinned (R646/R672 included\n isPinned); the visible path/rail/particle stayed hover-only.\n Post-R692 ALL 5 per-edge surfaces respond to pin. Sibling\n to R689 (working/online chip pin-gated halo) + R690 (vendor\n chip nested pin-gated halo) — pin-gesture symmetry now\n spans the per-edge tier. */\n const isEdgePinned = pinnedEdgeKey === link.key;\n const renderWidth = isHoveredEdge ? Math.min(width * 1.4, 10)\n : isEndpointHoveredEdge ? Math.min(width * 1.15, 8)\n : width;\n return (\n <g\n key={link.key}\n // Round 172 / Loop: edges fade-in alongside the R9\n // staggered node reveal so the canvas first-paint\n // reads as one coordinated wave instead of \"edges\n // pop, nodes ease in\". The .anet-fade-in CSS\n // animation (0.15s ease-out, runs once on mount,\n // R3 origin) plays only on first render — React\n // preserves the <g> via the link.key on subsequent\n // re-renders so the animation doesn't replay when\n // flowLinks recomputes (every 5s SSE poll).\n // Animation-delay offsets each edge by 280ms +\n // 35ms × index so edges start fading in AFTER\n // most nodes have appeared (node R9 stagger caps\n // at ~600ms; ring layout R72 emanates 0→540ms).\n // Cap at 20 indices so a busy fleet with 50\n // flowLinks still finishes within ~1s. Respects\n // prefers-reduced-motion via R29 globals.css\n // blanket that neutralises animation-duration.\n className=\"anet-fade-in\"\n style={{\n animationDelay: `${280 + Math.min(index, 20) * 35}ms`,\n }}\n data-edge-group={link.key}\n >\n {/* Round 48 / Loop: invisible hover hitbox — visible flow\n path is 3-7 px wide and damn hard to hover precisely.\n Stack a transparent 16-px-wide stroke behind it so the\n cursor only needs to be ~8 px from the line for the\n tooltip to fire. Native <title> moves here; the\n visible path no longer needs it. pointer-events on\n the visible path drop to \"none\" since the hitbox\n owns the hover surface. */}\n <path\n d={path}\n fill=\"none\"\n stroke=\"transparent\"\n strokeWidth={Math.max(width + 10, 16)}\n style={{ pointerEvents: 'stroke' }}\n data-edge-hitbox\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n >\n <title>{tooltip}</title>\n </path>\n {/* Round 166 / Loop: stroke-width transition pairs\n with R164 edge badge r-lift. Pre-R166 the\n visible flow path's hover thickening (R50:\n renderWidth = isHoveredEdge ? width * 1.4 :\n width) snapped instantly even though opacity\n transitioned smoothly. Edge hover now lifts\n the line AND the badge in coordinated 300ms\n ease-out motion. Drop the Tailwind transition\n class for inline style so both opacity and\n stroke-width pick up the same timing without\n arbitrary-property class compilation risk.\n data-edge-visible exposes the path for test\n probes (the R48 hitbox sibling already has\n data-edge-hitbox). Respects prefers-reduced-\n motion via the R29 globals.css blanket\n override that neutralises transition-duration\n universally. */}\n {/* Round 245 / Loop: edge surface picks up stroke\n color transition for theme-toggle smoothing.\n R166 already eased opacity + stroke-width on the\n visible flow path; the stroke COLOR (pal.flowEdge:\n cyber cyan ↔ light emerald) and the underlying\n flow-rail's stroke (pal.flowPath: cyber pale-sky\n ↔ light slate-600) still snapped on theme switch.\n The rest of the topology smooths theme through R4\n transitions (status rings) / R242 chat-target ring\n / R244 halos / R241 hub spokes / R240 backdrop\n spokes — R245 closes the edge surface.\n\n Visible flow path: append 'stroke 300ms ease-out'\n to the existing transition list (300ms matches\n R166 opacity + stroke-width pace).\n\n Flow rail (dashed underline): convert the Tailwind\n `transition-opacity` className to inline style so\n we can list opacity AND stroke together at 300ms\n ease-out (same idiom R201 used on the working/\n online chips to splice in additional properties\n beside Tailwind's). data-edge-flow-rail attr\n surfaces the path for test introspection. */}\n {/* Round 381 / Loop: edge visible flow path picks up\n strokeLinecap='round'. Sibling polish to R378\n flow-rail dashed linecap — both flow-element paths\n (visible primary + dashed secondary rail) now share\n 'round' linecap vocabulary. The visible path runs\n source-node → dest-node as one continuous line, so\n the dest-end is covered by the markerEnd arrow and\n the source-end usually sits inside the source-node\n circle. At certain alignments (post-zoom, post-\n layout-switch transitions), the source-end may peek\n out by a fraction of a px past the node edge —\n round caps render that overshoot as a smooth half-\n disc instead of a sharp rectangle. Pure paint\n refinement, geometry-safe (bbox of the stroke\n unchanged at the join with the arrow marker).\n data-edge-visible-linecap attr exposes the value\n for tests. */}\n {/* Round 582 — edge visible flow path joins per-element\n brightness family at 21st anchor. Stacks\n brightness(1.15) with the existing url(#topo-glow)\n SVG filter (cyber) or applies plain brightness\n (light). CSS filter accepts mixed url() + function\n values; inline style.filter overrides any\n attribute-level filter. Closes edge-tier brightness\n sub-family at 2 surfaces:\n R581 flow-rail (dashed underline) brightness\n R582 visible path (primary curve) brightness ← this round\n transition list extends to include 'filter 300ms\n ease-out' matching the existing opacity/sw/stroke\n cadence. */}\n <path\n d={path}\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth={renderWidth}\n strokeLinecap=\"round\"\n opacity={Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)}\n markerEnd={`url(#${arrowId})`}\n data-edge-visible={link.key}\n data-edge-visible-linecap=\"round\"\n data-edge-visible-endpoint-hovered={isEndpointHoveredEdge ? 'true' : 'false'}\n data-edge-visible-stroke-width={renderWidth}\n data-edge-visible-brightness={(isHoveredEdge || isEndpointHoveredEdge) ? '1.15' : '1'}\n /* R677 — edge visible PRIMARY path joins the multi-layer\n halo family. Pre-R677 the filter was brightness(1.15)\n (light) or url(#topo-glow) brightness(1.15) (cyber) —\n a single paint axis on hover (R582). Post-R677 prepends\n 2-layer drop-shadow at pal.flowEdge tint with 2+4\n stride and alpha 80/40 so the edge stroke glows in\n its own flow-edge hue on inspection.\n\n Cyber stack: drop-shadow + drop-shadow + url(#topo-\n glow) + brightness — the SVG Gaussian glow is\n PRESERVED behind the new layered halos.\n Light stack: drop-shadow + drop-shadow + brightness\n — no SVG glow on light theme.\n\n Per-edge identity tier now FULLY halo-extended at\n all 3 anchors (closes the per-edge sub-family):\n R646 edge-badge CIRCLE (3+6, pal.legendAccent/hotStroke)\n R672 edge-badge DIGIT (2+4, pal.legendAccent/hotStroke)\n R677 edge visible PATH (2+4, pal.flowEdge) ← this round\n\n 36th anchor in multi-layer halo family. The primary\n curve, the count badge, and the digit inside the\n badge all glow in lockstep when the edge surfaces\n under hover — full per-edge identity coherence. */\n data-edge-visible-halo-layers={(isHoveredEdge || isEndpointHoveredEdge || isEdgePinned) ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out, filter 300ms ease-out',\n filter: (isHoveredEdge || isEndpointHoveredEdge || isEdgePinned)\n ? (isLight\n ? `drop-shadow(0 0 2px ${pal.flowEdge}80) drop-shadow(0 0 4px ${pal.flowEdge}40) brightness(1.15)`\n : `drop-shadow(0 0 2px ${pal.flowEdge}80) drop-shadow(0 0 4px ${pal.flowEdge}40) url(#topo-glow) brightness(1.15)`)\n : (isLight ? undefined : 'url(#topo-glow)'),\n }}\n />\n {/* Round 378 / Loop: edge flow-path dashed-rail picks\n up strokeLinecap='round'. Pre-R378 the rail\n rendered '2 12' dashes as sharp 1×2 rectangles\n against the canvas backdrop; default 'butt' caps\n leave dash ends square. R378 rounds each cap so\n the dashes read as soft 3-px pills (1 px stroke +\n 0.5 px round cap each end). The flow-rail is the\n secondary 'invisible-spine' line that gives the\n R57 spoke flow a directional rail to slide along\n — rounding the dashes softens its presence\n against the primary visible flow path (R245 has\n no strokeLinecap so it inherits 'butt' on a\n continuous line, irrelevant). Geometry-safe:\n round caps only widen the visible dash; the\n bbox of the path is unchanged so overlap-test\n invariants hold. data-edge-flow-rail-linecap\n attr exposes the value for tests. */}\n <path\n id={`flow-path-${index}`}\n d={path}\n fill=\"none\"\n stroke={pal.flowPath}\n /* Round 437 / Loop: flow-rail strokeWidth hover lift —\n 1 → 1.5 on (isHoveredEdge || isEndpointHoveredEdge).\n Pre-R437 the dashed rail sat at sw=1 always while the\n visible flow path above it lifted (R50 ×1.4 on\n isHoveredEdge / R436 ×1.15 on isEndpointHoveredEdge).\n The two edge paint layers were mismatched on hover:\n top layer thickened, underline stayed thin — so the\n hover gesture lifted only half the edge surface.\n R437 lifts the underline too so the whole edge\n reads as \"raised\" on hover, not just its bright\n top stripe. Same +0.5 absolute delta R435 used at\n hub-spoke scope (1→1.25 there, slightly bigger\n here because the rail's base 1 is at the kerning\n floor and needs more lift to register).\n Transition list extends to include stroke-width\n 300ms so the new lift eases under the same R166\n cadence as the visible path's stroke-width. */\n strokeWidth={(isHoveredEdge || isEndpointHoveredEdge) ? 1.5 : 1}\n strokeDasharray=\"2 12\"\n strokeLinecap=\"round\"\n opacity={Math.min(1, (isLight ? 0.4 : 0.75) * fresh * edgeOpacityMul)}\n data-edge-flow-rail={link.key}\n data-edge-flow-rail-linecap=\"round\"\n data-edge-flow-rail-stroke-width={(isHoveredEdge || isEndpointHoveredEdge) ? 1.5 : 1}\n data-edge-flow-rail-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}\n /* Round 581 — flow-rail joins per-element brightness\n family at 20th anchor. Adds brightness(1.15) on\n edge hover or endpoint hover. Joins R437 sw-lift\n paint pattern at the dashed-underline tier — when\n an edge is in focus, the rail's stroke widens\n (R437) AND brightens (R581) together, reading\n as a coherent rail-lift gesture under the flow.\n transition list extends to include 'filter 300ms\n ease-out' matching the R245/R437 cadence on this\n surface. */\n data-edge-flow-rail-brightness={(isHoveredEdge || isEndpointHoveredEdge) ? '1.15' : '1'}\n /* R678 — edge flow-rail (dashed underline) joins the\n multi-layer halo family. Pre-R678 the rail had only\n brightness(1.15) on hover (R581) — single paint axis.\n Post-R678 prepends 2-layer drop-shadow at pal.flowPath\n tint (the rail's OWN stroke color, different from\n visible-path's pal.flowEdge) with 2+4 stride, alpha\n 80/40. Same chrome-family vocabulary as R677 visible\n path.\n\n Because the rail is dashed (strokeDasharray=\"2 12\"),\n the drop-shadow paints only where the visible dashes\n are — creating a glow-stones-along-path effect:\n each 2px dash halos individually. Adds a pleasing\n \"guide-rail lit-up\" gesture on edge inspection.\n\n Edge stroke pair now FULLY halo-extended at both\n paint layers:\n R677 visible primary path (pal.flowEdge 2+4)\n R678 flow-rail underline (pal.flowPath 2+4) ← this\n\n Per-edge ALL surfaces now multi-layer:\n R646 badge circle R672 badge digit\n R677 visible path R678 flow-rail ← this round\n 37th anchor in multi-layer halo family. */\n data-edge-flow-rail-halo-layers={(isHoveredEdge || isEndpointHoveredEdge || isEdgePinned) ? '2' : '0'}\n style={{\n transition: 'opacity 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, filter 300ms ease-out',\n filter: (isHoveredEdge || isEndpointHoveredEdge || isEdgePinned)\n ? `drop-shadow(0 0 2px ${pal.flowPath}80) drop-shadow(0 0 4px ${pal.flowPath}40) brightness(1.15)`\n : undefined,\n }}\n />\n {!reducedMotion && (\n /* Round 103 / Loop: phase-stagger the particles so\n concurrent edges don't pulse in lockstep. SMIL\n `begin` accepts negative offsets to shift the cycle\n backwards in time, which means the particle starts\n mid-flight on first paint — no visible \"all\n particles spawn from source simultaneously\" tell.\n `(index * 0.37) % duration` gives a deterministic,\n well-distributed offset (the golden-ratio-ish 0.37\n fraction prevents lining up when N is a small\n multiple). Edge order is stable (sorted by recent\n activity), so the offsets feel calm rather than\n reshuffling each refresh. */\n /* Round 422 / Loop: edge flow particle radius 4 → 4.5.\n Visual-weight bump family (15th anchor) — particles\n riding along the edge animateMotion path get +0.5px\n radius lift, increasing visual area by ~27%\n (π·4.5² / π·4² = 1.27). Sibling magnitude to R383\n recent-row pip 1.8 → 2.0 (+25% area), R384 minimap\n online dot 1.7 → 1.9 (+25% area). R251 fill +\n R252 transitions + R103 phase-stagger animateMotion\n all preserved. data-edge-particle-radius attr\n exposes the value for tests. */\n <circle\n /* Round 439 / Loop: edge flow particle radius hover\n lift — r 4.5 → 5.5 on (isHoveredEdge ||\n isEndpointHoveredEdge). Continues edge paint-\n layer parity arc (R436 visible path sw / R437\n flow-rail sw / R439 particle r) so the whole\n edge surface — including the moving particle —\n lifts on hover, not just the static stripes.\n +1px radius gives ~50% area boost. Subtler than\n 1.4× sw bump on visible path because the\n particle is already small + motion-bright;\n +1px reads as \"the dot caught attention\"\n without overshadowing the path lift. R252\n transition list extends to include r 200ms so\n the size change eases under the same fill/\n opacity cadence. */\n r={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}\n fill={pal.flowParticle}\n filter={isLight ? undefined : 'url(#topo-glow)'}\n /* Round 485 / Loop — extends R484's \"inspection\n overrides encoding\" pattern to a 2nd anchor:\n edge particle opacity lifts to 1.0 on\n isHoveredEdge OR isEndpointHoveredEdge (user\n hovering the edge directly OR hovering one\n of its endpoint nodes). Pre-R485 the particle\n inherited freshness × edgeOpacityMul decay\n so a stale edge's particle painted near the\n 0.30 floor even when the operator was\n inspecting it; R485 lifts to 1.0 on attention.\n data-recent-row-ts-alpha-attribute analog —\n freshness encoding preserved on rest tier,\n opacity override engages only on inspection.\n Sibling lift family — inspection-overrides-\n encoding pattern, now 2 anchors:\n R484 recent-row timestamp freshness → 1.0\n R485 edge particle freshness → 1.0 (this)\n data-edge-particle-opacity-lifted attr exposes\n the override gate; data-edge-particle-opacity-\n rest preserves the freshness reading. */\n opacity={(isHoveredEdge || isEndpointHoveredEdge) ? 1 : Math.min(1, fresh * edgeOpacityMul)}\n data-edge-particle={link.key}\n data-edge-particle-radius={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}\n data-edge-particle-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}\n data-edge-particle-opacity-rest={Math.min(1, fresh * edgeOpacityMul).toFixed(2)}\n data-edge-particle-opacity-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}\n /* Round 583 — flow particle joins per-element\n brightness family at 22nd anchor. Adds\n brightness(1.15) on edge hover or endpoint\n hover, joining R485 opacity inspection-override\n + R422 r-lift (4 → 4.5) + R164 hover-r-lift\n (4.5 → 5.5). Particle now has 4-axis active\n signature on edge inspection:\n R485 opacity (freshness → 1.0)\n R164 r 4.5 → 5.5\n R422 r-base 4 → 4.5 (visual-weight bump)\n R583 brightness(1.15) ← this round\n Particle becomes the brightest paint element\n along the edge during inspection. */\n data-edge-particle-brightness={(isHoveredEdge || isEndpointHoveredEdge) ? '1.15' : '1'}\n /* R679 — flow particle joins the multi-layer halo\n family. Pre-R679 the filter was brightness(1.15)\n (light) or url(#topo-glow) brightness(1.15) (cyber)\n — single paint axis on hover (R583). Post-R679\n prepends 2-layer drop-shadow at pal.flowParticle\n tint (the particle's OWN fill color) with 2+4\n stride, alpha 80/40. The cyber stack preserves\n url(#topo-glow) behind the new layered halos.\n\n Per-edge ALL 5 SURFACES now multi-layer (closes\n the per-edge family across every paint element):\n R646 badge circle (pal.legendAccent | hotStroke 3+6)\n R672 badge digit (pal.legendAccent | hotStroke 2+4)\n R677 visible path (pal.flowEdge 2+4)\n R678 flow-rail (pal.flowPath 2+4 — dashed)\n R679 flow particle (pal.flowParticle 2+4) ← this\n\n Visual effect: the moving particle now drags a\n small halo along its trajectory — like a comet\n trail tinted to the particle's hue. Coherent with\n R677/R678's static path/rail glows so all 5\n surfaces emit hue-matched halos in lockstep when\n the edge surfaces under inspection.\n\n 38th anchor in multi-layer halo family. */\n data-edge-particle-halo-layers={(isHoveredEdge || isEndpointHoveredEdge || isEdgePinned) ? '2' : '0'}\n style={{\n transition: 'fill 200ms ease-out, opacity 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',\n filter: (isHoveredEdge || isEndpointHoveredEdge || isEdgePinned)\n ? (isLight\n ? `drop-shadow(0 0 2px ${pal.flowParticle}80) drop-shadow(0 0 4px ${pal.flowParticle}40) brightness(1.15)`\n : `drop-shadow(0 0 2px ${pal.flowParticle}80) drop-shadow(0 0 4px ${pal.flowParticle}40) url(#topo-glow) brightness(1.15)`)\n : undefined,\n }}\n >\n <animateMotion\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n path={path}\n />\n </circle>\n )}\n {/* Round 75 / Loop: arrival ping at the destination. The\n particle currently fades into the arrow marker\n silently — adding a small radiating ring synchronised\n to the particle's period turns message delivery into\n a visible event. begin = -dur*0.92 offsets the\n animation so the ring expands NEAR the end of each\n cycle (≈when the particle arrives). Gated by\n reducedMotion and on fresh > 0.5 — stale edges that\n haven't fired in minutes don't need the eye-grab.\n data-arrival-ping for testability.\n\n Round 279 / Loop: arrival ping RETIRED (R75 + R228\n + R231 + R252 family) per 减法 cut #5. Per active\n edge the SMIL family was: particle (R50\n animateMotion) + arrival ping (R75 r+opacity SMIL)\n + dispatch pulse (R76 r+opacity SMIL). For a 5-\n edge fleet that's 5×3 = 15 simultaneous SMIL.\n The PARTICLE (a moving dot along the path) is the\n primary \"data flowing from A → B\" visual signal;\n ping + pulse are secondary \"arrival/dispatch\n confirmation\" that the moving particle already\n conveys. Cull ping + pulse, keep particle.\n `false &&` gates the render; code preserved for\n rollback. */}\n {false && !reducedMotion && fresh > 0.5 && (\n <circle\n cx={to.x}\n cy={to.y}\n r=\"0\"\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth=\"1.5\"\n opacity=\"0\"\n /* Round 252 / Loop: stroke transition for theme\n toggle. SMIL animates r + opacity continuously;\n stroke is static per render but theme-driven\n (pal.flowEdge: cyber cyan ↔ light emerald). */\n style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }}\n data-arrival-ping={link.key}\n >\n {/* Round 228 / Loop: pulse-pop ease curves on the\n arrival ping SMIL — extends R227's keySplines\n adoption from the click ripple (one-shot, two-\n value linear) to the canvas's repeating delivery-\n confirmation surfaces. Both <animate>s use\n calcMode=spline with keyTimes='0;0.5;1' (two\n segments).\n • r grows 0→14→22 (monotonic): unified ease-out\n across both segments — the ring decelerates\n as it expands, settling at its widest.\n • opacity bumps 0→0.55→0 (pulse): ease-out on\n the rise (fast appearance), ease-in on the\n fall (slow start of fade then accelerates) —\n the canonical \"pulse-pop\" kinetic shape.\n Together r decelerating and opacity pulse-popping\n give the arrival ping a real-event physicality\n instead of the prior linear-velocity tick. */}\n <animate\n attributeName=\"r\"\n values=\"0;14;22\"\n dur={`${duration}s`}\n begin={`-${((stagger + duration * 0.92) % duration).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.25 0.1 0.25 1\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0;0.55;0\"\n dur={`${duration}s`}\n begin={`-${((stagger + duration * 0.92) % duration).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.42 0 1 1\"\n />\n </circle>\n )}\n {/* Round 76 / Loop: source dispatch pulse — mirror to the\n R75 arrival ping. begin = 0s (start of cycle) so the\n ring expands as the particle LEAVES the source. Only\n fires for high-traffic edges (link.count >= 3) — on\n quiet conversations the canvas should stay calm; on\n busy senders the pulse plus arrival ping bookend\n every message in flight, making the topology feel\n alive. Same fresh/reducedMotion gates as R75. Slightly\n smaller radius (0→12→18 vs R75's 0→14→22) so the\n source reads as \"smaller event than arrival\" — the\n destination is the meaningful endpoint.\n\n Round 279 / Loop: dispatch pulse RETIRED with the\n arrival ping (R75) above — same 减法 rationale.\n Particle remains as the sole \"data flow\" SMIL\n signal per active edge. */}\n {false && !reducedMotion && fresh > 0.5 && link.count >= 3 && (\n <circle\n cx={from.x}\n cy={from.y}\n r=\"0\"\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth=\"1.5\"\n opacity=\"0\"\n /* Round 252 / Loop: stroke transition for theme\n toggle. Same idiom as arrival ping above. */\n style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }}\n data-dispatch-pulse={link.key}\n >\n {/* Round 228 / Loop: same pulse-pop curves as the\n arrival ping above. r ease-out + opacity\n ease-out→ease-in. The dispatch pulse is smaller\n (0→12→18 vs arrival's 0→14→22), but the kinetic\n feel should be identical — both bookend a\n single message in flight, so they should\n physically ease the same way. */}\n <animate\n attributeName=\"r\"\n values=\"0;12;18\"\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.25 0.1 0.25 1\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0;0.45;0\"\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.42 0 1 1\"\n />\n </circle>\n )}\n {/* Round 100 / Loop: midpoint count badge for high-\n traffic edges. Width already gauges intensity but\n \"5 vs 12\" is hard to read off stroke alone. Render\n a compact pill at the bezier midpoint (t=0.5 →\n midpoint + perpendicular × lift/2) showing the\n integer count. Threshold link.count >= 3 keeps\n the canvas quiet for sparse traffic. Bezier\n midpoint math: for a quadratic curve M A Q C B,\n the t=0.5 point is (A + 2C + B) / 4, which\n simplifies to (midAB) + 0.5 × (control - midAB)\n = midAB + perpendicular × lift/2. */}\n {(() => {\n // Round 215 / Loop: badge always-mounts; visibility\n // crossfades via the wrapper <g>'s opacity instead of\n // React conditional mount/unmount on link.count >= 3.\n // Pre-R215 a flow's first 2 → 3 message crossing made\n // the badge appear in one frame, and a hot flow\n // tapering 3 → 2 vanished in one frame. R215 extends\n // the always-mount-opacity-gate idiom (R181 / R182 /\n // R183 ring family + R213 hub digit/highlight + R214\n // pulse dot) to the canvas-edge surface. Inner R164\n // r-lift / R188 stroke transitions continue to ease\n // their respective state flips independently. pointer\n // events gated to 'none' when invisible so a sub-\n // threshold badge can't intercept the hitbox click.\n const visible = link.count >= 3;\n const midX = (from.x + to.x) / 2;\n const midY = (from.y + to.y) / 2;\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const len = Math.hypot(dx, dy) || 1;\n const badgeX = midX + (-dy / len) * lift * 0.5;\n const badgeY = midY + ( dx / len) * lift * 0.5;\n const badgeOpacity = visible ? Math.min(1, fresh * edgeOpacityMul) : 0;\n /* R121: the badge becomes a canvas-side click-to-pin\n affordance. R100 introduced it as a passive count\n display; R116 added pinnedEdgeKey. Joining them\n lets users pin a flow directly from the canvas\n without crossing to the recent-signal panel.\n pointerEvents move from 'none' to 'all' on the\n wrapper <g>; the underlying R48 edge hitbox is\n wider (16 px) than the 18-px badge diameter so\n clicks landing on either still route correctly\n — the badge consumes its small footprint, the\n hitbox owns the rest. stopPropagation on\n pointerdown so the SVG pan capture doesn't\n redirect the click. */\n const isPinned = pinnedEdgeKey === link.key;\n // R126: hot-edge accent. The edge stroke width (line\n // 2344) already scales with count but clamps at 7 —\n // so count=5 and count=50 look identical at the line,\n // and the only signal differentiating them is the\n // integer in the badge. Bucket count into a \"hot\"\n // band (≥ 10) and flip the badge stroke to a warmer\n // tone + thicker ring so busy lanes telegraph at a\n // glance without reading the digit. Reuses the amber\n // family from R125 (chip empty-state) — semantic is\n // \"draw the eye here\"; R125 amber is in the chip row\n // above the SVG, R126 amber is on the canvas, so the\n // surfaces never compete in the same eye-sweep. Pin\n // still wins (uses legendHeadline) so a pinned hot\n // edge reads as \"locked + busy\" via the badge text\n // and edge brightness, not via a third stroke colour.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <g\n data-edge-count-badge={link.key}\n data-edge-count-badge-pinned={isPinned ? 'true' : 'false'}\n data-edge-count-badge-hot={isHot ? 'true' : 'false'}\n data-edge-count-badge-visible={visible ? 'true' : 'false'}\n role={visible ? 'button' : undefined}\n tabIndex={visible ? 0 : -1}\n aria-pressed={visible ? isPinned : undefined}\n aria-hidden={visible ? undefined : true}\n className=\"anet-topo-svg-focus\"\n style={{\n pointerEvents: visible ? 'all' : 'none',\n cursor: visible ? 'pointer' : undefined,\n transition: 'opacity 300ms ease-out',\n }}\n opacity={badgeOpacity}\n onPointerDown={(e) => e.stopPropagation()}\n // R122: badge hover propagates to hoveredEdgeKey so\n // moving the cursor onto the badge lights the\n // same endpoint rings + edge brighten as hovering\n // the line. R121 only wired click; the badge sat\n // visually separate from the line on hover,\n // which felt like two surfaces rather than one.\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n // Round 185 / Loop: edge badge click fires the same\n // one-shot expanding-ring ripple R14 uses for node\n // click and R52 uses for hub click — anchored at\n // the badge midpoint with the edge's own flowEdge\n // colour. Closes the click-feel idiom across all\n // three pinnable canvas surfaces (hub / node /\n // edge badge). Reused setClickRipple state machine\n // so only one ripple at a time; ts coordinate\n // guard in the setTimeout cleanup prevents an\n // older ripple from clobbering a newer one if\n // the user clicks two badges in quick succession.\n onClick={(e) => {\n e.stopPropagation();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n const ts = Date.now();\n setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });\n setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n const ts = Date.now();\n setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });\n setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);\n }\n }}\n >\n <title>{isPinned\n ? `${link.from} → ${link.to} (${link.count}) — click to release pin`\n : `${link.from} → ${link.to} (${link.count}) — click to pin`}</title>\n {/* Round 164 / Loop: edge badge gains hover-lift\n to match the 5-surface hover-elevation\n family (R51 node / R135 panel / R142 group\n box / R143 recent row / R144 legend row).\n Pre-R164 the badge had R122 hover→edge-brighten\n propagation but the badge ITSELF stayed\n static, so the cursor-on-target feedback\n felt mismatched with every other interactive\n surface. Bumping r 9 → 10.5 on hover OR pin\n gives the same \"lift\" gesture in canvas\n space (the badge sits on a curved path, so\n translate-Y wouldn't track the line; radius\n growth is the SVG-native equivalent). Pin\n and hover share the lift so a pinned badge\n stays visually raised even after mouseleave —\n mirrors R143/R144 where row pin gets the\n same lift as row hover. Pinned still keeps\n its R121 stroke change (legendHeadline +\n width 2) so pin and hover stay\n discriminable on the same lifted state.\n strokeWidth stays at 1 / 2 — won't trip the\n R51 overlap-test sentinels (1.5 / 3 are\n reserved). transition keeps the lift smooth\n (180ms ease-out) and respects prefers-\n reduced-motion via the globals.css blanket\n override that neutralises transitions.\n\n Six surfaces now share the hover-elevation\n idiom: nodes (R51), panels (R135), group\n boxes (R142), recent rows (R143), legend\n rows (R144), edge badges (R164). */}\n {/* Round 188 / Loop: extend the badge transition to\n include stroke + stroke-width. R164 added the\n r 9↔10.5 lift; R188 closes the smoothness gap\n on the R121 pin-stroke flip (cyan flowEdge ↔\n legendHeadline) and R126 hot-lane flip (cyan\n ↔ amber). Both used to snap when crossing the\n state boundary (pin click, or count crossing\n 10). Now they ease 300ms through the colour\n and width change — same idiom R167 uses for\n the node status ring. The badge strokeWidth\n values are 1/2 (not R51 sentinels 1.5/3) so\n the always-rendered badge stays invisible to\n the overlap-test guard rails. */}\n {/* Round 251 / Loop: edge badge circle transition\n list grows fill + opacity at 200ms so theme\n toggle no longer snaps the badge background\n while the rest of the circle eases.\n Pre-R251:\n r 180ms (R164 hover lift)\n stroke 300ms (R188 hot/pinned colour flip)\n stroke-width 300ms (R188 hot/pinned width flip)\n fill (pal.legendBox.fill: cyber #060608 ↔ light\n #ffffff) and opacity (cyber 0.82 ↔ light 0.95)\n were theme-driven but missed from the list —\n the badge chrome snapped on theme switch while\n the per-edge ring + visible flow path (R245)\n and per-node surfaces (R246) all eased.\n R251 closes the per-edge surface theme-toggle\n smoothness — every theme-driven property on\n every edge element now eases under cyber↔light. */}\n {/* Round 367 / Loop: edge midpoint badge rest\n stroke-width 1 → 1.25. Sibling visual-weight\n bump family (7th canvas anchor now):\n R287 minimap viewport stroke 1 → 1.5\n R295 legend swatch base radius 5.5 → 6\n R359 recent-row pip base radius 1.6 → 1.8\n R360 hub digit fontSize 11 → 12\n R361 edge-badge digit fontSize 10 → 11\n R365 hub-highlight base radius 5 → 5.5\n R367 edge-badge rest stroke 1 → 1.25 (this round)\n Cold edge badges gain ~25 % stroke presence\n (1.25/1.0 = 1.25). Stays clear of the R51\n overlap-test sentinel values (1.5 / 3 reserved\n for node strokes — the test selector is gated\n to g[data-node] ancestors so this edge-internal\n circle is invisible to that probe anyway, but\n 1.25 is a safe non-sentinel value regardless).\n R188 transition list 'stroke-width 300ms ease-\n out' still smoothes the hot/pin flip — now\n 1.25 → 2 instead of 1 → 2, same ease pace.\n data-edge-badge-stroke-width-rest exposes the\n new baseline for tests. */}\n {/* Round 371 / Loop: edge-badge cyber opacity 0.82\n → 0.85. Sibling theme-consistency polish to R370\n hub hover-ring 0.7 → 0.8. R251 designed this\n badge with opacity 0.82 (cyber) / 0.95 (light)\n — 13 % delta. Cyber-theme dark bg needs more\n alpha to read as 'present'; R371 narrows the\n gap to 10 %, bringing the badge closer to light\n theme's 0.95 floor. Light stays at 0.95\n (already in the legibility band). data-edge-\n badge-opacity attr exposes the resolved value.\n Theme-consistency polish family:\n R246/R247 panel transition family\n R251 edge badge fill/opacity baseline\n R370 hub hover-ring cyber 0.7 → 0.8\n R371 edge badge cyber 0.82 → 0.85 (this round)\n R164 r=9/10.5 hover-lift + R188/R251 transition\n list + R367 strokeWidth=1.25 cold rest preserved. */}\n {/* Round 394 / Loop: edge-badge gains a hover\n strokeWidth tier (1.5) between cold rest\n (R367 1.25) and pin/hot (2). Pre-R394 the\n badge lifted only its radius on hover (R164\n 9 → 10.5); the stroke stayed at cold rest\n 1.25 unless pin/hot kicked in, so a plain\n hover felt half-lifted — geometry expanded\n while the contour stayed thin. R394 adds\n strokeWidth=1.5 on isHoveredEdge so hover\n now lifts both r AND stroke in concert —\n same pattern R385 used for the hub hover-\n ring (1.5 → 1.75) where the ring's three\n hover axes (r grow / opacity fade-in /\n stroke thicken) all rise together.\n Three-tier stroke hierarchy now:\n cold rest 1.25 (R367)\n hovered 1.5 (R394 — this round)\n pinned / hot 2.0 (R188)\n R51 sentinel concern: strokeWidth=1.5 is\n one of the two sentinels reserved for node\n detection, but the R51 selector is gated\n to `g[data-node]` ancestors so this edge-\n internal circle is invisible to the probe\n (same lesson R177 hub hover-ring + R367\n cold rest documented). 300ms strokeWidth\n transition already in the style list eases\n the new tier naturally. data-edge-badge-\n stroke-width-hover attr exposes the hover\n value for tests. */}\n {/* Round 395 / Loop: edge-badge gains a third\n hover axis — opacity 0.85 (cyber) / 0.95\n (light) → 1.0 on isHoveredEdge. Pre-R395\n hovering thickened the stroke (R394 1.25 →\n 1.5) and grew the radius (R164 9 → 10.5)\n but the badge's translucency stayed put at\n R371's rest alpha (cyber 0.85 / light 0.95).\n R395 lifts hover to a clean 1.0 — fully\n opaque — so the hovered badge reads as\n \"in focus\" against the dim siblings.\n Three-axis hover-lift parity now complete:\n hub hover-ring (R177/R370/R385):\n r 14 → 17, opacity 0 → 0.8 cyber, sw 1.5 → 1.75\n edge badge (R164/R394/R395):\n r 9 → 10.5, sw 1.25 → 1.5, opacity → 1.0\n 200ms opacity transition (already in the\n style list) eases the new axis naturally.\n R371 rest opacity (0.85 cyber / 0.95 light)\n preserved as the resting alpha — R395\n adds an isHoveredEdge override on top.\n data-edge-badge-opacity-hover attr exposes\n the hover value for tests. */}\n {/* Round 396 / Loop: extend the R395 opacity → 1.0\n lift to the pinned state. Pre-R396 the badge\n shared `r=10.5` on both hover AND pin (R164\n unified-lift) but R395's opacity lift fired\n ONLY on isHoveredEdge — pinned badges stayed\n at R371 rest alpha (cyber 0.85 / light 0.95).\n That left pin (sticky selection) reading\n softer than hover (transient preview), even\n though pin is the stronger commitment.\n R396 unifies hover + pin at opacity=1.0\n so the same data-edge-badge-lifted='true'\n surface uniformly carries full alpha. Pin\n stroke (R188 sw=2 + pal.legendHeadline color)\n continues to differentiate pin from hover —\n the opacity track now closes the lift parity.\n The new gate (isHoveredEdge || isPinned)\n mirrors the existing R164 r-lift gate, so\n the badge has a single \"active state\"\n signature across r + opacity.\n 200ms opacity transition (already in style\n list) eases pin/unpin naturally. R371 rest\n opacity preserved as the resting alpha.\n data-edge-badge-opacity-hover renamed\n semantically to -active (covers hover+pin)\n via the new -opacity-active attr; the\n legacy -opacity-hover attr kept for R395\n test compatibility. */}\n {/* Round 480 / Loop — 5th anchor in the drop-shadow\n visual-polish family. Gates on isHot (link.\n count >= 10, R129 hot-lane threshold) so the\n badge gets a warm-amber halo when its edge\n crosses the high-traffic boundary.\n Drop-shadow family ledger now:\n R476 hub digit hover-gated emerald\n R477 legend pin-ring pin-gated row.fill\n R478 freshness pip freshness-gated cyan\n R479 group label pin-gated cyan\n R480 edge badge hot-lane-gated amber ← this round\n 5th gate type — traffic volume — joins hover,\n pin, freshness, pin. Each polish anchor uses\n a distinct semantic gate but the same paint\n vocabulary. Hue: hotStroke (amber-tinted\n palette member) at 0x80 alpha — picks up the\n R126/R188 hot-edge accent colour family so\n the glow reads as a chromatic extension of\n the existing hot-lane stroke. 3-px blur\n radius reads as soft heat rather than\n emergency klaxon.\n R51 sentinel safety: badge sw=2 only matters\n when the overlap probe runs on g[data-node]\n descendants, which this edge-internal badge\n is not. Filter is paint-only, bbox unchanged.\n transition list extends to include 'filter\n 200ms ease-out' so the heat halo eases on\n the count-crosses-threshold flip. */}\n <circle\n cx={badgeX} cy={badgeY}\n r={isHoveredEdge || isPinned ? 10.5 : 9}\n fill={pal.legendBox.fill}\n stroke={isPinned ? pal.legendHeadline : isHot ? hotStroke : pal.flowEdge}\n strokeWidth={isPinned ? 2 : isHot ? 2 : isHoveredEdge ? 1.5 : 1.25}\n opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}\n data-edge-badge-lifted={(isHoveredEdge || isPinned) ? 'true' : 'false'}\n data-edge-badge-stroke-width-rest=\"1.25\"\n data-edge-badge-stroke-width-hover=\"1.5\"\n data-edge-badge-opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}\n data-edge-badge-opacity-rest={isLight ? 0.95 : 0.85}\n data-edge-badge-opacity-hover=\"1\"\n data-edge-badge-opacity-active=\"1\"\n data-edge-badge-glow={(isHoveredEdge || isPinned || isEndpointHoveredEdge) ? 'hover' : isHot ? 'hot' : 'false'}\n data-edge-badge-halo-layers={((isHoveredEdge || isPinned || isEndpointHoveredEdge) || isHot) ? '2' : '0'}\n data-edge-badge-brightness={(isHoveredEdge || isPinned || isHot || isEndpointHoveredEdge) ? '1.15' : '1'}\n data-edge-badge-endpoint-active={isEndpointHoveredEdge ? 'true' : 'false'}\n /* Round 633 / Loop — edge-badge CIRCLE joins the\n hot-pulse family at the geometric axis. R498\n originated panel-row digit opacity breath; R632\n extended it to the canvas-edge digit. R633\n extends the same family to the badge CIRCLE\n via stroke-width SMIL: sw 2 ↔ 2.5 over 3s\n ease-in-out cubic-bezier(0.42, 0, 0.58, 1) on\n both halves, in lockstep with the R632 digit\n opacity pulse.\n The badge now reads with FOUR coordinated hot\n signatures:\n R126/R332 stroke fill → amber hotStroke\n R426 digit fw → 700 → 800\n R220/R431 digit ls → 0 → 0.4\n R480 drop-shadow → amber halo\n R632 digit pulse → opacity 0.85↔1.0\n R633 circle pulse → sw 2↔2.5 ← this\n Geometry (R633) + Paint (R632) breathe together\n at 3s cadence — the badge swells AND brightens\n as one organic gesture. Pre-R633 only the digit\n breathed; the surrounding ring stayed visually\n static even when its rim color and filter both\n signalled hot.\n SMIL on attributeName='stroke-width' mounts\n with first frame = 2 (matching React-set sw on\n hot), so no snap at the hot threshold crossing.\n Unmounts smoothly on hot→cold transition; React\n sets sw=1.25 and CSS transition (already 300ms\n in style) eases it down.\n R51 sentinel safety: badge is edge-internal\n (not g[data-node] descendant); SMIL sw range\n [2, 2.5] never matches reserved {1.5, 3} mid-\n cycle anyway. topo-overlap-test invariants\n hold across hot lanes.\n Hot-pulse family ledger (3 anchors):\n R498 panel-row count digit (opacity)\n R632 edge-badge digit (opacity)\n R633 edge-badge circle (stroke-width) ← this\n data-edge-badge-hot-pulse attr exposes the\n new gate axis for tests. */\n data-edge-badge-hot-pulse={isHot && !reducedMotion ? 'on' : 'off'}\n /* Round 534 / Loop — extends edge-badge drop-shadow\n coverage from hot-only (R480 amber) to also fire\n on hover/pin with a cyan accent glow. Pre-R534\n the badge's hover/pin lifted r (R164 9 → 10.5)\n + sw (R394 1.25 → 1.5) + opacity (R395/R396 →\n 1.0), but the paint axis stayed at the badge's\n rest fill — no glow to telegraph \"in focus\" at\n the paint layer. R534 closes that 4-axis hover-\n lift parity by adding drop-shadow glow on\n (hovered || pinned).\n Precedence: (hover || pin) wins over isHot when\n BOTH true — interactive signal (user is\n inspecting) overrides informational signal\n (hot lane). When only isHot fires (no hover/\n pin) the amber R480 glow remains; the hover/\n pin case paints cyan/teal `pal.legendAccent`\n at 0x99 alpha (~60%) — bright enough to read\n as \"lit\" but won't overwhelm at small badge\n size (r=10.5).\n Edge-badge 4-axis hover-lift parity now:\n R164 r 9 → 10.5\n R394 stroke-wd 1.25 → 1.5\n R395 opacity rest → 1.0\n R534 filter none → drop-shadow glow ← this round\n Drop-shadow visual-polish family extension —\n edge-badge surface upgraded from single-gate\n (R480 isHot) to two-gate (isHot OR hover-pin).\n transition list already includes filter 200ms\n ease-out (R480). data-edge-badge-glow attr\n upgraded from `isHot ? true : false` to a\n 3-value string: 'hot' | 'hover' | 'false' so\n tests can distinguish gate cause.\n R51 sentinel safety: badge is edge-internal\n (not g[data-node] ancestor); filter is paint-\n only; bbox unchanged. */\n style={{\n /* R603 — edge-badge CIRCLE stacks brightness(1.15)\n onto the R534/R480 drop-shadow filter on hover/\n pin/hot. 5th hover axis on the badge ring\n (after R164 r, R394 sw, R395 opacity, R534\n filter drop-shadow → R603 filter brightness).\n\n Brings the CIRCLE's brightness coverage to\n parity with the DIGIT's R570 brightness:\n both elements (ring + text) now brighten\n together on the same 3-state gate\n (hover/pin/hot). The whole edge-badge reads\n as one coherent \"lit up under attention\"\n unit at the brightness axis.\n\n Banked R564/R570 stacked-filter pattern:\n `drop-shadow(...) brightness(1.15)` chains\n the SVG drop-shadow halo + brightness lift\n in one CSS filter chain on the same element.\n\n Per-element brightness family extension —\n edge-badge circle joins as a stacked-filter\n anchor. Hot-only state gets amber drop-shadow\n + brightness; hover/pin gets cyan/teal drop-\n shadow + brightness; rest gets neither.\n\n transition list already includes 'filter\n 200ms ease-out' (R534 cadence). No\n transition change needed. */\n /* R629 — extend the badge CIRCLE's filter gate\n to also fire on isEndpointHoveredEdge. Pre-\n R629 the badge only glowed on direct edge\n hover/pin or hot threshold, leaving a visible\n inconsistency: R624 brightened the visible\n path on endpoint-hover (including the chat-\n target case via R624's chat extension), but\n the badge sitting ON that brightened path\n stayed at rest filter. R629 closes that\n badge↔edge parity at the brightness axis.\n Adds two gate axes simultaneously through\n one expression: endpoint-hover (hover-gated)\n AND chat-target endpoint match (chat-gated,\n 14th anchor in chat-target-gated family,\n indirectly via R624's isEndpointHoveredEdge\n extension). The drop-shadow halo uses the\n cyan/teal pal.legendAccent — same colour as\n the existing hover/pin branch. */\n /* Round 646 / Loop — edge-badge CIRCLE filter\n gains a SECOND drop-shadow layer at 6px blur\n with halved alpha. Mirrors R642/R643/R644/R645\n multi-layer halo pattern from the per-node\n identity surfaces to the per-edge identity\n badge. The badge ring now radiates a near +\n far layered glow on hover/pin/endpoint OR on\n hot-only state — both branches stack 2\n drop-shadows ahead of brightness.\n Hover/pin/endpoint branch (cyan):\n R534: inner 3px + 0x99 alpha (~60%)\n R646: outer 6px + 0x50 alpha (~31%)\n Hot-only branch (amber):\n R480: inner 3px + 0x80 alpha (~50%)\n R646: outer 6px + 0x40 alpha (25%)\n Outer alpha = ~half inner alpha — matches\n R642/R643/R644 0.5x falloff vocabulary. 2x\n blur stride (3→6) matches R642 chat-target\n ring outer stride (the badge ring at\n r=9-10.5 sits at a similar small-scale tier\n as identity rings).\n Both layers share the same tint per branch\n (legendAccent or hotStroke), so chromatic\n identity preserved. R51 sentinel safety:\n badge is edge-internal (not g[data-node]\n descendant); filter paint-only, no bbox\n change. transition list already covers\n 'filter 200ms ease-out'.\n Multi-layer halo family extends from rings\n (R642-R644) + text (R645) to edge-badge\n (R646) — first per-edge surface in the\n family.\n data-edge-badge-halo-layers attr exposes\n the new 2-layer state for tests. */\n filter: (isHoveredEdge || isPinned || isEndpointHoveredEdge)\n ? `drop-shadow(0 0 3px ${pal.legendAccent}99) drop-shadow(0 0 6px ${pal.legendAccent}50) brightness(1.15)`\n : isHot\n ? `drop-shadow(0 0 3px ${hotStroke}80) drop-shadow(0 0 6px ${hotStroke}40) brightness(1.15)`\n : undefined,\n transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',\n }}\n >\n {isHot && !reducedMotion && (\n <animate\n attributeName=\"stroke-width\"\n values=\"2;2.5;2\"\n dur=\"3s\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n repeatCount=\"indefinite\"\n />\n )}\n </circle>\n {/* Round 224 / Loop: edge badge text gains the 4th\n pin-signature typography. Pre-R224 the digit\n rendered with no transition surface: when the\n flow crossed count=10 (isHot flip) the badge\n stroke eased 300ms (R188) but the digit itself\n stayed dead-typographic. R224 adds two clean\n improvements stacked on the same <text> node:\n\n 1) fontVariantNumeric: 'tabular-nums' — locks\n digit width so a 9→10 transition doesn't\n jitter the textAnchor='middle' centering by\n half a glyph. The badge is on a curved\n flow path; any width-change of the centered\n digit visibly shifts the anchor relative\n to the underlying circle. Info-density\n win — digits transition cleanly without\n pixel-jitter at the boundary.\n\n 2) letterSpacing pin signature, 4th surface\n after R218 group label / R219 legend row /\n R220 recent row. Baseline 0px; widens to\n 0.4px when isPinned || isHot. The transition\n marks the \"this lane just went special\"\n event typographically — same 300ms cadence\n as the R188 stroke flip, so the badge stroke\n + text co-ease on the hot/pin threshold.\n '0px' resolves to keyword 'normal' in\n computed style (R218 test trap learned);\n test parsers must accept either form.\n\n data-edge-badge-text-pin attr surfaces the\n isPinned||isHot state for introspection. */}\n <text\n x={badgeX} y={badgeY + 3}\n textAnchor=\"middle\"\n fill={pal.legendHeadline}\n /* Round 361 / Loop: edge midpoint badge text\n fontSize 10 → 11. Sibling visual-weight bump\n to R360 hub digit 11 → 12. The badge digit\n is the per-edge equivalent of the hub digit\n — a high-information scalar (link.count) at\n a stable canvas position. Pre-R361 fontSize=\n 10 + R220 letter-spacing 0.4 + R224 tabular-\n nums made the digit READABLE but small\n against the r=9 / 18-px badge envelope;\n fontSize=11 nudges the glyph ~10 % bigger\n (bbox ~7×10 px from ~6×9 px) so the count\n reads more cleanly at glance — still well\n inside the r=9 idle circle and the r=10.5\n hover/pin lift (R164). y=badgeY+3 empirical\n vertical centring kept (1px drift at the\n bumped size is below the noise floor in\n the on-curve flow path).\n Visual-weight bump family:\n R287 minimap viewport stroke 1 → 1.5\n R295 legend swatch base radius 5.5 → 6\n R359 recent-row pip base radius 1.6 → 1.8\n R360 hub digit fontSize 11 → 12\n R361 edge-badge digit fontSize 10 → 11 (this round)\n data-edge-badge-text-font-size attr exposes\n the value for tests. R220 pin/hot letter-\n spacing tween + R224 tabular-nums + R188\n stroke-width pin/hot transitions all preserved. */\n fontSize=\"11\"\n fontFamily=\"monospace\"\n /* R426 — edge-badge digit fontWeight 700 → 800 on\n (isPinned || isHot). 4th anchor on the \"data\n tightens under attention\" typographic-weight\n pattern:\n R416 chip-digit (chip-row hover trigger)\n R424 panel-digit (panel hover trigger)\n R425 hub-digit (hub hover trigger)\n R426 edge-badge-digit (pin/hot trigger) ← this\n The badge digit is the per-edge equivalent of\n the hub digit (R361 sibling fontSize bump\n reasoning). Stacks with R188 stroke-width pin/\n hot lift (1.25 → 1.5) + R220 letter-spacing pin/\n hot tween (0 → 0.4) for a 3-axis pin/hot signa-\n ture (edge structure + text spacing + text\n weight). The R408 transition is letter-spacing\n 300ms; R426 appends font-weight 300ms so the\n weight bump co-eases under the same cadence. */\n fontWeight={(isPinned || isHot) ? '800' : '700'}\n data-edge-badge-text={link.key}\n data-edge-badge-text-pin={(isPinned || isHot) ? 'true' : 'false'}\n data-edge-badge-text-font-size=\"11\"\n /* Round 570 / Loop — edge-badge digit joins the per-\n element brightness consistency family (R501/R558/\n R564/R567) at uniform +15%. 7th anchor.\n Gate: (isHoveredEdge || isPinned || isHot) — same\n 3-tier set as R431 ls (with hover the mid step\n and pin/hot the strong step). Brightness lifts\n uniformly across all 3 active sub-states; the\n ls/fw axes still distinguish hover from pin/hot.\n Pure paint axis on the digit glyph; bbox\n unchanged. The R540 badge-circle drop-shadow\n sits on the parent CIRCLE element (separate\n filter); the digit's filter is independent and\n doesn't compound with circle filter (different\n SVG element).\n Per-element brightness family — 7 anchors at +15%:\n R501 vendor.logo image\n R558 vendor monogram\n R558 prefix-group fallback\n R564 alias text (stacked w/ DS)\n R567 node sub-text\n R570 edge-badge digit ← this round\n Plus runtime badge drop-shadow (R559) on same\n isNodeActive gate. data-edge-badge-text-brightness\n attr surfaces the lift for tests. */\n data-edge-badge-text-brightness={(isHoveredEdge || isPinned || isHot || isEndpointHoveredEdge) ? '1.15' : '1'}\n data-edge-badge-text-endpoint-active={isEndpointHoveredEdge ? 'true' : 'false'}\n /* Round 632 / Loop — canvas-side edge-badge digit\n picks up R498's `anet-recent-hot-pulse` className\n when isHot && !reducedMotion. The panel-row count\n digit (R498) already breathes 0.85 ↔ 1.0 over 3s\n on hot lanes (count ≥ 10); R632 extends that\n gesture to the canvas-edge badge digit — the\n per-edge equivalent surface that displays the\n same count.\n Both digit surfaces now pulse together on the\n SAME hot threshold, so a high-traffic lane\n reads as \"alive\" simultaneously in the panel\n AND on the canvas — 信息密度 consistency.\n Pre-R632 only paint (R127 amber fill) + weight\n (R426 fw 700→800) + spacing (R220 0→0.4)\n differentiated hot at the badge; the digit\n stayed visually motionless while its panel\n counterpart breathed. R632 closes that\n cross-surface motion parity.\n Same 3s ease-in-out cycle as R498 (sibling at\n the canvas-side surface). The pulse animates\n opacity at the <text> level; the existing R570/\n R629 inline filter (brightness) animates filter\n via CSS transition — different CSS properties,\n no conflict. R498's prefers-reduced-motion\n handling via the R29 blanket override applies\n automatically (animation-duration: 0.001ms\n !important). Component-side `!reducedMotion`\n gate also keeps the className absent for the\n no-motion preference, avoiding a node-tree\n thrash.\n Hot-pulse family now 2 anchors:\n R498 panel-row count digit (recent-signal)\n R632 edge-badge digit (canvas-edge) ← this\n data-edge-badge-text-hot-pulse attr exposes the\n new gate axis for tests. */\n className={isHot && !reducedMotion ? 'anet-recent-hot-pulse' : undefined}\n data-edge-badge-text-hot-pulse={isHot && !reducedMotion ? 'true' : 'false'}\n style={{\n pointerEvents: 'none',\n fontVariantNumeric: 'tabular-nums',\n /* R431 — edge-badge digit 3-tier letter-spacing:\n rest 0 / isHoveredEdge 0.2 / (isPinned || isHot) 0.4.\n Mirrors R427 node-alias 3-tier (rest/hover/chat-\n target → 0/0.3/0.5) at edge-badge scope. Pre-R431\n letter-spacing only fired on pin/hot (R220) while\n pure edge hover lifted stroke (R394) + opacity\n (R395) + radius (R164) but left the text dead-\n typographic. R431 adds the missing typographic\n spacing axis to the edge-hover gesture so the\n text rises with the badge geometry. Pin/hot\n tier (0.4) still wins; hover is the mid step.\n Hover-letter-spacing family extension (7 anchors\n now): R344/R345/R347/R351/R420/R427/R431. */\n letterSpacing: (isPinned || isHot) ? '0.4px' :\n isHoveredEdge ? '0.2px' : '0px',\n /* R629 — sibling extension to the badge CIRCLE\n above. Text digit's brightness gate now\n also fires on isEndpointHoveredEdge, so the\n digit glyph lifts in concert with the circle\n ring + the underlying edge path on endpoint\n hover OR chat-target match. Keeps the badge\n CIRCLE + TEXT brightness coverage in lockstep\n (the R603/R570 parity arc the ring and digit\n established for the 3-state gate). */\n /* R672 — edge-badge DIGIT joins the multi-layer\n halo family in lockstep with R646 edge-badge\n CIRCLE. Pre-R672 the digit had brightness(1.15)\n only on a unified 4-condition gate (hover/pin/\n hot/endpoint). R672 splits into the same TWO-\n branch pattern as R646 — cyan for non-hot\n (pal.legendAccent), amber for hot (hotStroke).\n Stride 2+4 (text scale, matches R645 alias\n text); alpha 80/40 matches the family. The\n badge circle (R646) + digit (R672) now emit\n multi-layer halo in lockstep when the edge\n surfaces under attention — full per-edge\n identity badge halo closure.\n Hover/pin/endpoint branch (cyan):\n R672 digit: 2px + 4px, alpha 80/40\n R646 circle: 3px + 6px, alpha 99/50\n Hot-only branch (amber):\n R672 digit: 2px + 4px, alpha 80/40\n R646 circle: 3px + 6px, alpha 80/40\n Hot-only branch precedes the unified hover\n branch in the chain so when the edge is BOTH\n hot AND hovered, the cyan halo wins (matches\n R646 chain order — hover priority over hot\n since hover is user intent).\n Multi-layer halo family — 31st anchor, 2nd\n per-edge surface after R646 circle. */\n filter: (isHoveredEdge || isPinned || isEndpointHoveredEdge)\n ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)`\n : isHot\n ? `drop-shadow(0 0 2px ${hotStroke}80) drop-shadow(0 0 4px ${hotStroke}40) brightness(1.15)`\n : undefined,\n transition: 'letter-spacing 300ms ease-out, font-weight 300ms ease-out, filter 300ms ease-out',\n }}\n data-edge-badge-digit-halo-layers={(isHoveredEdge || isPinned || isEndpointHoveredEdge || isHot) ? '2' : '0'}\n data-edge-badge-digit-halo-branch={\n (isHoveredEdge || isPinned || isEndpointHoveredEdge) ? 'cyan'\n : isHot ? 'amber'\n : 'none'\n }\n >{link.count}</text>\n </g>\n );\n })()}\n </g>\n );\n })}\n\n {/* center hub — round 39 sized + round 13 restraint.\n The hub is the control-plane anchor. r39 gave it two outward\n pulses (r 10→38, 2.4s, double-phase) which read as \"loudest\n node in the room\" — wrong semantics: anchors don't emit,\n they hold. r13 swaps the kinetic pulse for a slow opacity\n breath on the static halo (4s, ±15% from base), so the hub\n stays alive without throwing kinetic energy outward. The\n dual-circle \"lit lamp\" core is unchanged. */}\n {layout === 'ring' && (<g\n data-topo-hub\n data-topo-hub-hovered={hoveredHub ? 'true' : 'false'}\n // Round 159 / Loop: the hub is the most visually\n // prominent interactive element on the canvas (R39\n // enlarged it, R52 made it click to fitView, R115\n // added a hover ring, R43 gave it a tooltip) — but\n // R151-R157's a11y sweep skipped it. role/tabIndex/\n // aria-label/onKeyDown bring it to parity with node <g>\n // (R151), group label hit (R152), and minimap (R157).\n // anet-topo-svg-focus picks up R156's explicit cyan\n // focus ring (default browser SVG focus rect is hard\n // to see against the dark canvas).\n role=\"button\"\n tabIndex={0}\n aria-label={(() => {\n const parts = ['Network hub'];\n if (onlineNodes.length > 0) parts.push(`${onlineNodes.length} online`);\n if (workingCount > 0) parts.push(`${workingCount} working`);\n if (flowLinks.length > 0) parts.push(`${flowLinks.length} active link${flowLinks.length === 1 ? '' : 's'}`);\n return parts.join(' · ') + ' — Enter to fit view';\n })()}\n // Round 176 / Loop: hub joins the first-paint fade-in\n // family as the 6th surface. The hub is the visual anchor\n // — every other ring-layout reveal layer (R174 tier rings,\n // R9 nodes, R172 edges) emanates outward FROM it — yet\n // pre-R176 the hub itself popped in instantly while the\n // wave it should be leading staggered around it. Adding\n // .anet-fade-in at delay 0 (no animation-delay needed)\n // places the hub as the canvas-center anchor that the\n // tier wave grows from. Composes cleanly with the existing\n // anet-topo-svg-focus class (R159 keyboard focus ring).\n className=\"anet-topo-svg-focus anet-fade-in\"\n data-topo-hub-fade-delay={0}\n style={{ cursor: 'pointer' }}\n // Stop pointerdown from reaching the SVG pan handler — same\n // reason as the node <g>: a captured pointer makes Chromium\n // fire the follow-up click on the SVG, not this group.\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredHub(true)}\n onMouseLeave={() => setHoveredHub(false)}\n // Round 52 / Loop: hub is the visual anchor but had no click\n // action — users discovered fit-to-content only via the `f`\n // key or the chrome button. Wire the hub to fitView() so the\n // most prominent element in the canvas is also the \"re-\n // center\" affordance. A click-ripple keyed by ts confirms\n // the gesture; the hub <title> now ends with a hint.\n onClick={() => {\n fitView();\n setClickRipple({ ts: Date.now(), x: cx, y: cy, r0: 18, color: isLight ? '#059669' : '#10b981' });\n setTimeout(() => setClickRipple(prev => prev && prev.x === cx && prev.y === cy ? null : prev), 600);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n fitView();\n setClickRipple({ ts: Date.now(), x: cx, y: cy, r0: 18, color: isLight ? '#059669' : '#10b981' });\n setTimeout(() => setClickRipple(prev => prev && prev.x === cx && prev.y === cy ? null : prev), 600);\n }\n }}\n >\n {/* Round 43 / Loop: hub `<title>` summary — hovering the\n central glow now answers \"what is this?\" with a one-line\n fleet snapshot. Duplicates the header chips by design;\n hovering the most visually prominent element is the\n most natural impulse, so satisfy it where the cursor\n already is. Falls clean: omits sub-clauses when their\n counts are zero. R52 appends a \"click to fit view\" hint.\n */}\n <title>{(() => {\n const total = sessions.length;\n const parts = [`Network hub`, `${total} session${total === 1 ? '' : 's'}`];\n if (onlineNodes.length > 0) parts.push(`${onlineNodes.length} online`);\n if (workingCount > 0) parts.push(`${workingCount} working`);\n if (flowLinks.length > 0) parts.push(`${flowLinks.length} active link${flowLinks.length === 1 ? '' : 's'}`);\n return parts.join(' · ') + '\\nclick to fit view';\n })()}</title>\n {/* grounding halo — breathes in opacity, no expansion.\n R84: breath amplitude + tempo reflect workingCount. An\n idle fleet keeps the original gentle 0.32→0.52 (light) /\n 0.08→0.16 (dark) cycle over 4s — \"heart at rest\". As\n working sessions accumulate, the peak climbs and the\n period shortens, capped at 2.4s so it never feels\n manic. Quiet fleets see zero change; busy fleets feel\n the canvas working. */}\n {(() => {\n // Bucket workingCount to keep the visual feedback discrete\n // rather than continuous: 0 / 1-2 / 3-5 / 6+. Three buckets\n // are enough — finer gradations are imperceptible.\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n // Round 404 / Loop: hub-halo cyber trough opacity 0.08 →\n // 0.10. Pre-R404 the breath's low-point sat at α=0.08\n // cyber (per R84 family tuning) — the halo nearly faded\n // out at trough on the dark canvas. R404 lifts cyber\n // trough to 0.10. Per-bucket peak amplitudes [0.16/0.20/\n // 0.26/0.32] stay exactly tuned.\n //\n // Round 405 / Loop: hub-halo LIGHT trough 0.32 → 0.34 —\n // symmetric +0.02 lift to mirror R404's cyber treatment\n // across both themes. Pre-R405 only cyber got the lift\n // (R404 docstring noted \"light already at the strong\n // end\" as deliberate); but the cyber/light delta in\n // R404 was an inconsistency in the family pattern.\n // R405 closes the symmetry — both themes get +0.02\n // baseline lift, so the breath low-point reads with\n // matching confidence regardless of theme. Light peak\n // array [0.52/0.58/0.65/0.72] stays tuned.\n //\n // Stale-state legibility lift family (5 anchors now):\n // R317 subordinate-text gray-500 → gray-400\n // R358 freshness floor 0.25 → 0.30\n // R372 minimap offline-dot opacity 0.5 → 0.6\n // R404 hub-halo cyber trough 0.08 → 0.10\n // R405 hub-halo light trough 0.32 → 0.34 (this round)\n //\n // R84 per-bucket peak/dur + R245 ease-in-out spline\n // keySplines all preserved. Test fixture probes the\n // SMIL <animate> values via data-topo-hub-halo-trough\n // attr (now exposes both light + cyber resolved values).\n const peakLight = [0.52, 0.58, 0.65, 0.72][busy];\n const peakDark = [0.16, 0.20, 0.26, 0.32][busy];\n const troughLight = 0.34;\n const troughDark = 0.10;\n const dur = [4.0, 3.2, 2.7, 2.4][busy];\n const valuesLight = `${troughLight};${peakLight};${troughLight}`;\n const valuesDark = `${troughDark};${peakDark};${troughDark}`;\n // Round 408 / Loop: hub-halo radius 18 → 20. The\n // grounding halo (the breathing outer circle around\n // the hub center) is the canvas's signature breath\n // element — R84 family. R408 bumps r=18 → 20 so the\n // breath extends slightly further while keeping 4px\n // clearance before the spoke origin (still room for\n // spoke start anchors). Visual presence on the\n // canvas focal point lifts ~23% area (π·20²/π·18²\n // = 1.23) without changing the per-bucket opacity\n // envelope or breath rhythm. Visual-weight bump\n // family 13th anchor — pairs with R404/R405 trough\n // lifts so the halo now breathes both with more\n // visible amplitude AND more visual footprint.\n // R84 per-bucket peak/dur invariants + R244 calc-\n // Mode='spline' + R245 ease-in-out keySplines all\n // preserved. data-topo-hub-halo-radius attr exposes\n // value for tests.\n /* Round 451 / Loop: hub center halo radius lift on\n hoveredHub — r 20 → 22 (+2px, ~21% area). Adds another\n geometric axis to the hub-hover signature stack\n alongside R177 ring radius lift + R209 digit scale +\n R425 digit fw + R370 halo opacity + R386 highlight\n opacity + R441 core fill chroma. Pre-R451 the halo\n r stayed planted at R408's 20px while the rest of\n the hub structure responded to hover. R451 makes\n the halo breath outward on hover so the focal pulse\n intensifies under attention. SMIL `<animate>` on\n opacity continues independently (animateAttr=\n 'opacity' vs CSS-property r — non-conflicting). R408\n base radius 20 preserved as rest; +2 hover delta\n keeps clearance from the R177 hub-hover-ring at\n r=17 hover (halo is BEHIND the ring, halo r=22 sits\n 5px beyond the ring's hover-r=17, still well within\n the hub canvas envelope). data-topo-hub-halo-radius\n attr now reports the dynamic value. */\n const isHaloHovered = !reducedMotion && hoveredHub;\n const haloR = isHaloHovered ? 22 : 20;\n return (\n <circle\n cx={cx} cy={cy}\n fill={isLight ? '#d1fae5' : '#10b981'}\n opacity={isLight ? 0.42 : 0.12}\n data-hub-busyness={busy}\n data-topo-hub-halo-radius={haloR}\n data-topo-hub-halo-hovered={isHaloHovered ? 'true' : 'false'}\n data-topo-hub-halo-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}\n data-topo-hub-halo-halo-layers={!reducedMotion && hoveredHub ? '2' : '0'}\n data-topo-hub-halo-trough={isLight ? troughLight : troughDark}\n data-topo-hub-halo-peak={isLight ? peakLight : peakDark}\n /* Round 253 / Loop: hub grounding halo fill transition\n for theme toggle. Pre-R253 the base fill (cyber\n #10b981 ↔ light #d1fae5) snapped while R244's SMIL\n animate on opacity continued running. CSS fill\n transition is independent of the SMIL animate\n (different attributes), so they compose without\n conflict.\n R451: r as CSS property (R197/R198 idiom) so the\n hover-radius tween eases smoothly under the same\n 200ms cadence as fill.\n R536: extends hub-cluster glow QUARTET (R476 digit\n + R532 disc + R535 ring + R533 spokes) to a 5th\n tier — the halo gains drop-shadow at the outermost\n concentric ring on hub-hover. 2px blur + 0.3 alpha\n keeps the halo's glow subtle since (a) the halo is\n the LARGEST hub element (r=22 hover) and a heavier\n glow would bleed visibly past the ring tier into\n the spoke origin, and (b) the halo already SMIL-\n animates opacity (R84/R244 breath), so the visible\n glow pulses with the breath — an atmospheric\n \"breathing glow\" idiom rather than a static rim.\n Hub-cluster glow QUINTET (R476/R532/R533/R535/R536):\n digit (typo center) 3px emerald 0.6\n disc (r=5.5/6) 3px emerald 0.6\n ring (r=14/17) 3px emerald 0.5\n halo (r=20/22) 2px emerald 0.3 ← this round\n spokes (mesh) 1.5px cyan/teal 0.4\n Emerald palette continues through the focal-disc\n family (digit/disc/ring/halo); spokes break out\n into cyan/teal at the mesh tier. The 4-step alpha\n ladder 0.6→0.6→0.5→0.3 reads as the focal cluster\n fading outward — the OUTERMOST emerald glow is the\n softest, the focal digit is the brightest.\n filter is paint-only; SMIL animate on opacity\n continues independently (attribute vs CSS-property\n non-conflicting). transition list extends to\n 'filter 200ms ease-out' alongside fill + r.\n Drop-shadow visual-polish family extension (12\n anchors). preview.50 milestone round. data-topo-\n hub-halo-glow attr exposes the gate state. */\n data-topo-hub-halo-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}\n /* Round 577 — hub-halo joins per-element brightness\n family at 15th anchor. Stacks brightness(1.15)\n onto R536's hub-hover drop-shadow — closes the\n hub-cluster focal brightness coverage at 3\n concentric elements:\n hub digit (R575)\n hub-highlight (R574)\n hub-halo (R577) ← this round\n All 3 hub focal elements now lift through stacked\n drop-shadow + brightness on hub-hover. Halo is\n the OUTERMOST so a slight chromatic lift reads as\n the focal cluster intensifying its ambient glow\n outward. */\n /* Round 651 / Loop — hub-halo drop-shadow gains a\n SECOND outer layer at 4px blur + 0.15 alpha\n (half R536's inner 0.3). Extends multi-layer halo\n family (R642-R650) to the 10th anchor — 2nd hub-\n cluster anchor (after R650 hub digit). The hub\n halo is the OUTERMOST concentric hub element\n (r=20-22) so the outer layer adds 4px blur at\n half alpha — same 2x stride + 50% falloff as\n R643 status ring (also 2+4 stride). Same emerald\n tint across both layers; chromatic identity\n preserved. Hub-cluster surfaces now have parity\n halo coverage: digit (R650) + halo (R651) both\n 2-layer on hub-hover.\n data-topo-hub-halo-halo-layers attr exposes the\n gate. */\n style={{\n r: `${haloR}px`,\n filter: !reducedMotion && hoveredHub\n ? (isLight\n ? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.3)) drop-shadow(0 0 4px rgba(16, 185, 129, 0.15)) brightness(1.15)'\n : 'drop-shadow(0 0 2px rgba(52, 211, 153, 0.3)) drop-shadow(0 0 4px rgba(52, 211, 153, 0.15)) brightness(1.15)')\n : undefined,\n transition: 'fill 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',\n } as React.CSSProperties}\n >\n {/* Round 244 / Loop: hub grounding halo breath gets\n ease-in-out keySplines, matching the active-node\n pulse (R243) treatment. Pre-R244 default linear\n calcMode marched opacity at constant velocity\n through the 3-value trough→peak→trough bounce —\n mechanical pacing for a 'heartbeat at rest'\n visual. R244 adds calcMode='spline' + keyTimes\n '0;0.5;1' + keySplines '0.42 0 0.58 1' ×2 (CSS\n ease-in-out on both halves), so the breath\n decelerates near the troughs AND the peak —\n lingers briefly at each extreme like a real\n heart-rest cycle. Same SMIL-easing family as\n R227 (click ripple) / R228 (edge ping+pulse) /\n R243 (active-node pulse). The breath family\n is now 2 surfaces deep at this single hub\n element — R84 amplitude/tempo bucket + R244\n curve shape. */}\n {!reducedMotion && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? valuesLight : valuesDark}\n dur={`${dur}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n )}\n </circle>\n );\n })()}\n {/* core — 20px diameter, larger inner highlight reads as a \"lit lamp\"\n Round 248 / Loop: hub center core gets a fill transition.\n Pre-R248 the core circle (the visual anchor at the centre\n of the canvas, fill=isLight ? '#059669' emerald-600 :\n '#10b981' emerald-500) hard-flipped on theme toggle —\n the most visually prominent element on the canvas\n snapping while everything else (R244 halo / R241 hub\n spokes / R246 label cards / R247 side panels) eased.\n Inline transition closes the gap. data-topo-hub-core\n attr added for test introspection (the parent <g> at\n line 3587 has data-topo-hub but the core specifically\n is the canvas anchor). */}\n {(() => {\n /* Round 441 / Loop: hub center core fill brighten on\n hoveredHub. Pre-R441 the core was static (cyber\n emerald-500 #10b981 / light emerald-600 #059669) and\n the hub-hover gesture lifted ring radius (R177) +\n digit scale (R209) + digit fw (R425) + halo opacity\n (R370) + highlight opacity (R386) but the focal core\n ITSELF stayed planted at rest tone. R441 shifts the\n fill one emerald tier brighter on hover so the canvas\n anchor itself responds:\n cyber emerald-500 → emerald-400 (#10b981 → #34d399)\n light emerald-600 → emerald-500 (#059669 → #10b981)\n Same +100 step on the emerald scale across both themes.\n Pure paint axis; no geometry change. R248 fill 200ms\n transition already in the style list eases the shift.\n Closes the chroma axis on the hub-hover gesture stack:\n R177 ring radius lift geometry\n R209 digit scale 1.08 geometry\n R425 digit fw 700 → 800 typography\n R370 halo opacity 0.7 → 0.8 paint\n R386 highlight opacity paint\n R441 core fill brighten chroma ← this round\n data-topo-hub-core-hovered + -fill attrs exposed\n for tests. */\n const isCoreHovered = !reducedMotion && hoveredHub;\n const coreFill = isLight\n ? (isCoreHovered ? '#10b981' : '#059669')\n : (isCoreHovered ? '#34d399' : '#10b981');\n return (\n <circle\n cx={cx} cy={cy} r=\"10\"\n fill={coreFill}\n data-topo-hub-core\n data-topo-hub-core-hovered={isCoreHovered ? 'true' : 'false'}\n data-topo-hub-core-fill={coreFill}\n data-topo-hub-core-glow={isCoreHovered ? 'true' : 'false'}\n data-topo-hub-core-brightness={isCoreHovered ? '1.15' : '1'}\n /* R614 — hub-core gains stacked drop-shadow + brightness\n on hub-hover. Extends hub-cluster brightness coverage\n to a 6th concentric element (innermost emerald r=10\n disc), beyond R580's 5/5 closure (digit + highlight +\n hover-ring + halo + spokes).\n\n Hub-cluster concentric brightness coverage now 6/6:\n hub digit (R575) innermost typo\n hub-core (R614) innermost emerald disc ← this round\n hub-highlight (R574) middle disc\n hub-hover-ring (R579) outer ring boundary\n hub-halo (R577) outermost atmosphere\n hub-spokes (R580) mesh radial lines\n\n Compounds R441's fill brighten (emerald-600→500 or\n emerald-500→400) with stacked drop-shadow + brightness.\n Triple-paint multiplicative interaction at hub-core:\n R441 fill swap (emerald tier brighter) ×\n R614 brightness(1.15) ×\n R614 drop-shadow (emerald halo)\n The focal anchor reads as fully \"lit and pulled\n forward\" on hub-hover, with paint-chroma + glow +\n brightness all firing in unison.\n\n Theme-aware drop-shadow palette matches the cluster:\n light: rgba(16, 185, 129, 0.5) emerald-500\n cyber: rgba(52, 211, 153, 0.5) emerald-400\n 2px blur — slightly tighter than the halo's 2px\n (R536) since core is smallest concentric element.\n 0.5 alpha matches the hover-ring (R535).\n\n Same banked R582/R583 stacked-filter pattern.\n transition list extends with 'filter 200ms ease-out'\n alongside the existing fill 200ms cadence. */\n /* R681 — hub core circle joins the multi-layer halo\n family. Pre-R681 the filter was single-layer drop-\n shadow at 2px + 0.5 alpha (R536). Post-R681 adds a\n SECOND outer drop-shadow at 4px + 0.25 alpha (half\n the inner alpha, double the blur — matches the\n family's 0.5×-falloff + 2× blur stride vocabulary\n established by R642-R667).\n\n Hub core is the canvas's MOST CENTRAL focal point —\n elevating it to multi-layer halo gives the user's\n attention focal anchor the same near + far layered\n glow vocabulary that 38 other surfaces now share.\n\n Light: drop-shadow(0 0 2px emerald 0.5)\n drop-shadow(0 0 4px emerald 0.25)\n brightness(1.15)\n Cyber: drop-shadow(0 0 2px emerald-400 0.5)\n drop-shadow(0 0 4px emerald-400 0.25)\n brightness(1.15)\n\n Tints unchanged (emerald light / emerald-400 cyber)\n for visual continuity with R536. Only the outer\n ambient layer is new. 40th anchor in family —\n first hub-CORE anchor (R650-R653 + R654 + R651\n all targeted hub-related ELEMENTS around the core,\n not the core disc itself).\n\n data-topo-hub-core-halo-layers attr exposes the\n gate for tests. */\n data-topo-hub-core-halo-layers={isCoreHovered ? '2' : '0'}\n style={{\n filter: isCoreHovered\n ? (isLight\n ? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.5)) drop-shadow(0 0 4px rgba(16, 185, 129, 0.25)) brightness(1.15)'\n : 'drop-shadow(0 0 2px rgba(52, 211, 153, 0.5)) drop-shadow(0 0 4px rgba(52, 211, 153, 0.25)) brightness(1.15)')\n : undefined,\n transition: 'fill 200ms ease-out, filter 200ms ease-out',\n }}\n />\n );\n })()}\n {/* R130 / Loop: when workingCount > 0, the decorative inner\n highlight gets replaced with the workingCount digit. The\n R84 busyness breath already encodes the same metric\n through motion — adding the digit gives it a second\n visual channel right at the canvas's focal point. A\n user glancing at the hub now sees both \"the network is\n pulsing\" (motion) AND \"3 agents are working\" (digit)\n without having to scan the chip row or panels.\n\n Geometry: text at (cx, cy) with fontSize 11 monospace\n + fontWeight 700 sits inside the r=10 core (a 2-digit\n 12 reads ~12 px wide × 11 px tall, well inside the\n 20-px diameter core). Centered vertically via dy=\n \"0.36em\" — the standard SVG trick for text-vertical-\n center without measuring fontMetrics.\n\n pointerEvents:none so the digit can't intercept the\n hub click (R52 fit-to-view still fires).\n\n workingCount=0 falls through to the existing\n decorative highlight so the hub never looks empty. */}\n {/* Round 213 / Loop: hub centre crossfades the workingCount\n digit and the R130 decorative highlight when count\n crosses zero, instead of mount/unmount snap. Pre-R213 a\n fleet going from idle (workingCount=0, highlight circle)\n to first-working-node (workingCount=1, digit \"1\") swapped\n the elements in a single frame — visible flash at the\n hub's focal point. R213 uses the always-mount + opacity-\n gate pattern (R181/R182/R183 family) so both render\n concurrently and crossfade via opacity transitions.\n Geometry already overlaps (both centred on cx,cy with\n r=5 / digit-bbox ~7×11), so the dual-render adds zero\n layout cost. Reduced-motion users see a 0ms duration\n via the R29 globals.css blanket override. */}\n {/* digit (visible when workingCount > 0) */}\n <text\n x={cx} y={cy}\n textAnchor=\"middle\"\n dy=\"0.36em\"\n fill={isLight ? '#d1fae5' : '#ecfdf5'}\n /* Round 360 / Loop: hub working-count digit fontSize 11\n → 12. The hub is the canvas's focal point — its digit\n is the most-read scalar on the whole topology. R130\n sized it at 11 (well inside the r=10 / 20-px core);\n R360 nudges it to 12 (~13 px wide × 12 px tall, still\n well inside the 20-px diameter) for ~9 % more presence.\n Sibling visual-weight bump family:\n R287 minimap viewport stroke 1 → 1.5\n R295 legend swatch base radius 5.5 → 6\n R359 recent-row pip radius 1.6 → 1.8\n R360 hub digit fontSize 11 → 12 (this round)\n The R209 scale-1.08-on-hub-hover, R225 tabular-nums,\n R253 fill transition, R213 always-mount opacity gate\n all preserved. data-topo-hub-working-count-font-size\n attr exposes the value for tests. */\n fontSize=\"12\"\n fontFamily=\"monospace\"\n /* R425 — hub digit fontWeight 700 → 800 on hoveredHub.\n Closes the \"data tightens under attention\" pattern\n across three focal scopes: chip-digit (R416, chip\n scope) → panel-digit (R424, panel-header scope) →\n hub-digit (R425, hub focal scope). The hub digit is\n the most-read scalar on the topology; adding a weight\n axis on hover stacks with the R209 scale-1.08 + R177\n ring grow + R370 halo opacity + R386 highlight\n opacity hub-hover gestures, giving the focal point\n a typographic axis alongside its scale/structure cues.\n R360 fontSize=12 + R225 tabular-nums + R209 scale +\n R253 fill transition all preserved. Transition list\n extends to include font-weight 200ms ease-out. */\n fontWeight={hoveredHub ? '800' : '700'}\n opacity={workingCount > 0 ? 1 : 0}\n data-topo-hub-working-count={workingCount}\n data-topo-hub-working-count-font-size=\"12\"\n data-topo-hub-working-count-hovered={hoveredHub ? 'true' : 'false'}\n data-topo-hub-working-count-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}\n data-topo-hub-working-count-visible={workingCount > 0 ? 'true' : 'false'}\n // Round 209 / Loop: hub workingCount digit scales 1.0 →\n // 1.08 on hub-hover, matching R177's r 14→17 ring grow.\n // Pre-R209 hovering the hub grew the ring while the\n // focal-point digit at the centre stayed planted — the\n // gesture lifted only half the structure. R209 ties the\n // digit's scale into the same hoveredHub state R177\n // already drives, so ring + digit rise as one unit.\n // transform-box: fill-box + transform-origin: center\n // anchors the scale to the digit's own bbox (same\n // idiom R184 reset-spin + R186 chrome-pop use for\n // SVG icon transforms). 200ms matches R167 node-ring\n // stroke-width interpolation pace. Reduced-motion users\n // skip the scale via the !reducedMotion gate (R29 a11y).\n // Round 225 / Loop: tabular-nums on hub digit — info-\n // density sibling to R224's edge badge tabular-nums.\n // Same physics: when workingCount crosses the 9 → 10\n // boundary the textAnchor='middle' centering jitters\n // ~3-4px because monospace fonts still have width\n // variance at the digit-vs-control boundary. Tabular\n // locks digit width so the focal point stays planted\n // through every count change. Pure visual tightening;\n // no test trap (computed font-variant-numeric resolves\n // to the keyword 'tabular-nums' verbatim).\n /* Round 253 / Loop: append fill 200ms to the hub\n digit transition list — theme toggle (cyber #ecfdf5\n ↔ light #d1fae5) was the last hub-area snap. */\n /* Round 476 / Loop — hub working-count digit gains a\n filter: drop-shadow glow on hub-hover. Stacks with\n the existing 4-axis hub-hover gesture stack on this\n element:\n R209 transform: scale(1.08) geometry\n R425 fontWeight 700 → 800 typography\n R253 fill ease-out chroma (theme)\n R213 opacity gate fade (count cross)\n R476 filter drop-shadow glow paint (this round)\n The glow uses the cyber emerald-400 (#34d399) /\n light emerald-500 (#10b981) hue family so the\n chroma stays inside the hub-area palette. Subtle\n 2-3 px blur radius at 0.6 opacity — visible but\n not loud, reads as \"the focal digit lit up under\n attention\".\n Reduced-motion users skip the filter via the\n !reducedMotion gate (R29 a11y blanket).\n Filter is a paint-only attribute — bbox stays\n the same, R51 overlap-test invariants hold.\n transition list extends to 'filter 200ms ease-out'\n so the glow eases under the same cadence as the\n scale + fw + fill axes. */\n data-topo-hub-working-count-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}\n data-topo-hub-working-count-halo-layers={!reducedMotion && hoveredHub ? '2' : '0'}\n /* Round 507 / Loop — focal recede. When ANY non-hub\n canvas surface is hovered (a node / an edge / a\n group label / a legend row / a vendor chip), the\n hub-center workingCount digit fades to 0.85 opacity,\n signaling \"you're inspecting elsewhere, hub recedes\n to background.\" When the user un-hovers (or hovers\n the hub itself), opacity returns to 1.0. Pure paint\n polish at the canvas's most prominent focal point.\n Hits 信息密度 + 动效 themes — the hub digit gives\n way visually to the surface under inspection,\n reinforcing the \"this is the focal point right now\"\n gesture without requiring users to track which\n surface holds attention.\n Gate excludes hoveredHub specifically: hovering the\n hub itself should LIFT the digit (R425 fw bump +\n R476 glow + R209 scale 1.08) — the existing hover-\n on-hub signature is intact; only inspection\n ELSEWHERE recedes the hub.\n Composed from existing hoveredAlias / hoveredEdge-\n Key / hoveredGroupLabel / hoveredStatus / hovered-\n Vendor — no new state. 300ms ease-out opacity\n transition already in the style list (existing R213\n transition spec), so the fade rides on existing\n infrastructure.\n data-topo-hub-recede attr surfaces the gate state\n for tests. */\n data-topo-hub-recede={\n (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'\n }\n /* Round 527 / Loop — focal-amplify family extension to a\n 2nd anchor. R511 introduced focal-amplify at the hub-\n highlight disc (base opacity 0.95 → 1.0 on hover); R527\n extends to the hub-center workingCount digit with a\n letter-spacing tween 0 → 0.3px on hub-hover.\n Composes with existing 3-axis hub-hover signature on\n this element:\n R209 transform scale(1.08) geometry\n R425 fontWeight 700 → 800 typography weight\n R476 filter drop-shadow glow paint\n R527 letter-spacing 0 → 0.3px typography kerning ← this round\n tabular-nums (R225) preserved — each digit cell keeps\n fixed width; the inter-digit advance grows by 0.3px\n per gap. Single-digit counts (1-9) show no kerning\n effect; multi-digit counts (10+) show the spread as\n info-density signaling. Sibling to R427/R431/R432/\n R433/R434 (hover-letter-spacing family at panel-text\n scope) — R527 brings the same idiom to the canvas's\n most-read scalar.\n Reduced-motion gate matches R209 scale, R425 fw, R476\n filter — !reducedMotion gates the lift; reducedMotion\n users see static digit baseline regardless of hover.\n Focal-amplify family extension (2 anchors): R511 hub-\n highlight opacity / R527 hub-digit letter-spacing.\n transition list extends to include `letter-spacing\n 200ms ease-out`, matching the cadence of the other\n hub-hover axes. data-topo-hub-working-count-letter-\n spacing attr exposes the resolved value for tests. */\n data-topo-hub-working-count-letter-spacing={!reducedMotion && hoveredHub ? '0.3px' : '0px'}\n style={{\n pointerEvents: 'none',\n transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',\n transformBox: 'fill-box',\n transformOrigin: 'center',\n opacity: (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) && !hoveredHub\n ? 0.85\n : 1,\n /* Round 575 (60-round milestone) — hub digit joins per-\n element brightness family at 14th anchor. Stacks\n brightness(1.15) onto R476's hub-hover drop-shadow\n — same R564/R570/R571/R572/R573/R574 pattern (drop-\n shadow + brightness in one filter chain). Closes\n the hub-cluster focal-element brightness coverage\n symmetrically: hub digit + hub-highlight disc\n (R574) now BOTH have stacked filter on hub-hover.\n Hub digit hub-hover signature post-R575 — 5 active\n axes:\n R209 scale 1.08 (geometry)\n R425 fw 700 → 800 (typography)\n R527 ls 0 → 0.3px (typography)\n R476 drop-shadow glow (paint halo)\n R575 brightness(1.15) (paint glow) ← this round\n Hub-cluster focal cluster (digit + highlight) now\n has UNIFIED 5-axis hub-hover signature reading as\n one tightly-coupled motion-coherent lift. */\n /* Round 650 / Loop · MILESTONE — hub digit drop-shadow\n gains a SECOND outer layer. Extends multi-layer\n halo family (R642-R649) to the 9th anchor — first\n hub-cluster anchor. The focal canvas digit now\n radiates a near + far emerald glow on hub-hover.\n light branch:\n inner R476 2px + rgba(16,185,129,0.6) emerald-600\n outer R650 4px + rgba(16,185,129,0.3) ← this round\n cyber branch:\n inner R476 3px + rgba(52,211,153,0.6) emerald-400\n outer R650 6px + rgba(52,211,153,0.3) ← this round\n Outer alpha = half inner (0.6 → 0.3 = 50%). Light\n uses 2+4 stride (smaller scale per existing R476\n light blur); cyber uses 3+6 stride matching the\n family panel-tier vocabulary. Same chromatic\n emerald tint preserved across both layers.\n Multi-layer halo family (9 anchors at R650):\n chat-target ring (R637+R642): 3+6\n endpoint ring (R639+R644): 2+4\n status ring (R638+R643): 2+4\n alias text (R500+R645): 2+4\n edge-badge ring (R534/R480+R646): 3+6\n freshness pip (R478+R647): 3+6\n group label (R479+R648): 3+6\n legend pin-ring (R477+R649): 3+6\n hub digit (R476+R650): 2+4 light / 3+6 cyber ← this\n First hub-cluster anchor in the family — the focal\n control-plane digit now matches the per-node + per-\n edge + panel-tier halo grammar.\n transition list already covers 'filter 200ms ease-\n out' (R476 cadence). Reduced-motion gate preserved.\n data-topo-hub-working-count-halo-layers attr\n exposes the gate state for tests. */\n filter: !reducedMotion && hoveredHub\n ? (isLight\n ? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6)) drop-shadow(0 0 4px rgba(16, 185, 129, 0.3)) brightness(1.15)'\n : 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6)) drop-shadow(0 0 6px rgba(52, 211, 153, 0.3)) brightness(1.15)')\n : undefined,\n letterSpacing: !reducedMotion && hoveredHub ? '0.3px' : '0px',\n /* R425: font-weight 200ms appended so the hover fw\n bump 700 → 800 eases under the same cadence as\n R209 scale + R253 fill + R213 opacity.\n R476: filter 200ms appended so the new drop-\n shadow glow eases at the same cadence.\n R507: opacity 300ms (existing in list) covers\n the new focal-recede fade.\n R527: letter-spacing 200ms appended so the new\n hover-kerning bump eases at the same cadence\n as the other axes. */\n transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out, letter-spacing 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {workingCount}\n </text>\n {/* decorative highlight (visible when workingCount === 0) */}\n {/* Round 365 / Loop: hub-center 'lit-lamp' decorative highlight\n circle r 5 → 5.5. Sibling visual-weight bump family —\n each round lifts one canvas anchor's geometric presence\n without disturbing its bbox envelope:\n R287 minimap viewport stroke 1 → 1.5\n R295 legend swatch base radius 5.5 → 6\n R359 recent-row pip base radius 1.6 → 1.8\n R360 hub digit fontSize 11 → 12\n R361 edge-badge digit fontSize 10 → 11\n R365 hub-highlight base radius 5 → 5.5 (this round)\n The highlight only renders when workingCount === 0\n (decorative 'lamp lit but idle' state per R130 + R213\n always-mount opacity-gate). At idle, the 0.5-px radius\n bump (21 % area, π*5.5² / π*5² = 1.21) lifts the lamp's\n presence — still well inside the r=10 hub-core (R130).\n opacity=0 when working preserved so the hub-digit's R130\n takeover stays seamless. R213 always-mount opacity-gate\n + 300ms opacity transition + pointerEvents:none all\n preserved. data-topo-hub-highlight-radius attr exposes\n the value for tests. */}\n {/* Round 386 / Loop: hub-highlight idle opacity 0.9 → 0.95.\n When workingCount===0 the highlight paints as the visible\n idle \"lamp lit but no work\" core (R130 takeover gate).\n Pre-R386 idle opacity was 0.9 — a ~6 % fade against full\n paint that read as slightly-dimmed-ghost on the focal\n point. R386 lifts to 0.95 (idle alpha gap halved 0.10\n → 0.05) so the canvas anchor reads more confidently\n as a present-but-idle state rather than a faded ghost.\n Theme-consistency / canvas-presence polish family (4th\n anchor):\n R370 hub hover-ring opacity 0.7 → 0.8 cyber\n R371 edge-badge rest opacity 0.82 → 0.85 cyber\n R372 minimap offline-dot opacity 0.5 → 0.6\n R386 hub-highlight idle opacity 0.9 → 0.95 (this round)\n opacity=0 when working preserved so the hub-digit's\n R130 takeover stays seamless. 300ms opacity transition\n + R213 always-mount opacity-gate + pointerEvents:none\n + R365 r=5.5 all preserved. data-topo-hub-highlight-\n opacity attr exposes the resolved value for tests. */}\n {/* Round 508 / Loop — focal-recede pattern 2nd anchor.\n Extends R507's hub-digit recede to the hub-highlight\n circle so the hub focal CLUSTER (digit at z-top + this\n idle-state highlight beneath) recedes as a unit when\n canvas attention is elsewhere. Computed once: a single\n non-hub-hover gate drives BOTH the digit (R507) AND\n this highlight (R508) so they always co-move.\n Recede multiplies the visible opacity by 0.85 — when\n workingCount===0 the rest opacity 0.95 becomes 0.81\n during external-hover; when workingCount>0 the\n opacity stays 0 (invisible) regardless of recede.\n Additionally, when recede is active the SMIL breath\n animation halts (animate node un-mounts) so the\n receded state reads as quietly static, not pulsing\n at 0.85↔1.0 against the recede multiplier (which\n would visually conflict — competing 15% drops). On\n un-hover the animate re-mounts and breath resumes.\n data-topo-hub-recede on both digit AND highlight\n provides a stable test handle for the unified-recede\n gate.\n Composed from existing hover state vars — no new\n state. Pure paint axis. */}\n {(() => {\n const hubRecede = !!((hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) && !hoveredHub);\n /* Round 511 / Loop — hub-highlight gains a 3rd opacity tier\n (hovered-amplify). Pre-R511 the highlight had 2 visible\n states: rest (0.95) and recede (0.81 = 0.95 × 0.85).\n When the hub itself was hovered, the digit got R425 fw\n lift + R476 drop-shadow + R209 scale-1.08, but the\n highlight disc sibling stayed at 0.95 — the focal\n cluster lifted in 3 channels (typography/paint/scale)\n but the highlight didn't participate.\n R511 closes that asymmetry: when hoveredHub is true,\n highlight base opacity lifts to 1.0 (5% boost from\n rest 0.95). Cluster now lifts as a unit on hub-hover,\n just like it recedes as a unit on non-hub-hover\n (R508).\n 3-state opacity ladder:\n hub-hovered: baseOpacity = 1.0 (R511 NEW)\n rest (no hover): baseOpacity = 0.95 (existing)\n non-hub canvas hov: baseOpacity × 0.85 = 0.81 (R508)\n Composes cleanly: hubRecede gate requires !hoveredHub,\n so the hovered-amplify and recede states are mutually\n exclusive (they can't both fire). breathActive\n continues to halt on either non-rest state (recede OR\n hub-hover would visually compete with the 0.85↔1\n breath — clean for the unit-lift semantic too). */\n const baseOpacity = workingCount > 0 ? 0\n : hoveredHub ? 1.0\n : 0.95;\n const resolvedOpacity = hubRecede ? baseOpacity * 0.85 : baseOpacity;\n const breathActive = !reducedMotion && workingCount === 0 && !hubRecede && !hoveredHub;\n /* Round 529 / Loop — focal-amplify family 3rd anchor.\n Hub-highlight gains geometric amplify r 5.5 → 6 on\n hub-hover, mirroring R451's hub-halo r 20 → 22 hover\n pattern. Pre-R529 the highlight had paint-axis\n amplify only (R511 opacity 0.95 → 1.0 on hub-hover);\n R529 adds geometric amplify so the focal disc\n BREATHES outward on hub attention, like the halo\n does. Composes with the existing 2-axis hub-hover\n lift on this element:\n R511 opacity 0.95 → 1.0 paint (focal-amplify 1st)\n R529 r 5.5 → 6 geometry (this round)\n Implementation matches R451: CSS `r` property\n (R197/R198 idiom) for smooth interpolation. SVG\n attribute `r=\"5.5\"` provides SSR fallback and serves\n as default; inline style.r overrides for animated\n value. transition list extends to include `r 200ms\n ease-out`, matching the fill cadence (also 200ms);\n opacity transition stays at 300ms (existing).\n r 6 sits well inside the existing visual envelope\n (next-larger sibling r=10 hub core, r=14 hub hover\n ring). The 0.5px lift is +9% radius / +19% area —\n enough to read as 'lift' without breaching the core\n boundary or invalidating overlap-test invariants.\n SMIL animate on opacity continues independently\n (animateAttr='opacity' vs CSS-property r — non-\n conflicting, same pattern R451 noted for halo).\n Focal-amplify family extension (3 anchors):\n R511 hub-highlight opacity 0.95 → 1.0\n R527 hub-digit letter-spacing 0 → 0.3px\n R529 hub-highlight radius 5.5 → 6 ← this round\n data-topo-hub-highlight-radius attr now reports the\n dynamic value (was static '5.5'). */\n const highlightR = !reducedMotion && hoveredHub ? 6 : 5.5;\n return (\n <circle\n cx={cx} cy={cy} r=\"5.5\"\n /* Round 509 / Loop — 配色 cross-theme cleanup. Pre-R509\n the hub-highlight fill was hardcoded `#d1fae5`\n (emerald-100, a pale tone). On the light theme this\n near-white green ran against a pale background at\n 0.95 opacity — the disc was effectively invisible.\n Matches the existing R253 halo theme-inversion\n pattern (line ~6481): light theme picks the dark\n vibrant emerald (#10b981 emerald-600), dark theme\n keeps the pale emerald (#d1fae5 emerald-100). Both\n read at the same 0.95 opacity against their\n respective backdrops — light gets a saturated\n focal dot; dark keeps the soft glow signature.\n Pure paint axis (fill change only); bbox unchanged;\n R51 SVG sentinel safety untouched.\n transition list already includes `fill 200ms`?\n Actually the existing transition spec is `opacity\n 300ms ease-out` — fill change on theme toggle\n will be instant. That's acceptable: theme toggle\n is a discrete event, and the halo (line 6500)\n already snaps fill on theme toggle the same way\n (`fill 200ms ease-out` was added later to halo\n via R253). Future round could add `fill 200ms`\n to highlight too if theme-switch flicker is\n noticed. */\n fill={isLight ? '#10b981' : '#d1fae5'}\n opacity={resolvedOpacity}\n data-topo-hub-highlight\n data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}\n data-topo-hub-highlight-radius={highlightR}\n data-topo-hub-highlight-opacity={resolvedOpacity}\n data-topo-hub-highlight-breath={breathActive ? 'true' : 'false'}\n data-topo-hub-highlight-recede={hubRecede ? 'true' : 'false'}\n data-topo-hub-highlight-hovered={!reducedMotion && hoveredHub ? 'true' : 'false'}\n data-topo-hub-highlight-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}\n data-topo-hub-highlight-halo-layers={!reducedMotion && hoveredHub ? '2' : '0'}\n /* Round 574 — hub-highlight joins per-element brightness\n family at 13th anchor. Stacks brightness(1.15) onto\n R532's drop-shadow filter — same R564/R570/R571/R572/\n R573 pattern (drop-shadow + brightness in one filter\n chain). Hub idle disc now has 3 active hub-hover\n axes: R511 opacity 0.95 → 1.0 + R529 r 5.5 → 6 +\n R574 brightness(1.15). data-topo-hub-highlight-\n brightness attr surfaces the lift. */\n data-topo-hub-highlight-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}\n /* Round 510 / Loop — R509 follow-on: theme-toggle fill\n ease. Pre-R510 the hub-highlight transition spec only\n listed `opacity 300ms ease-out`. When R509 introduced\n theme-conditional fill (#d1fae5 ↔ #10b981), the fill\n change SNAPPED on theme toggle because the transition\n list didn't include `fill`. R510 extends to `fill\n 200ms ease-out` so theme cycles smoothly through the\n emerald palette. 200ms timing matches the R253 halo\n fill transition (line ~6500) — both hub-cluster\n theme transitions now share a cadence so the focal\n cluster (digit + highlight + halo) eases as a unit.\n R508's recede opacity transition unchanged (300ms);\n fill is independent.\n R529: r as CSS property (R197/R198 idiom) + `r\n 200ms ease-out` appended to transition list so\n the new hub-hover radius lift (5.5 → 6) eases\n under the same fill cadence. SVG attr r=\"5.5\"\n above provides SSR fallback; inline style.r\n wins the cascade for the dynamic value.\n R532: filter drop-shadow glow on hub-hover —\n sibling to R476 hub-digit drop-shadow at the\n same gate (hoveredHub && !reducedMotion). Two\n adjacent hub focal elements (digit + highlight\n disc) now BOTH glow on hub-hover, reading as\n one unified focal cluster. Emerald palette\n matches R476:\n light: drop-shadow(0 0 2px rgba(16,185,129,0.6)) emerald-500\n cyber: drop-shadow(0 0 3px rgba(52,211,153,0.6)) emerald-400\n filter is paint-only (bbox unchanged); SMIL\n animate on opacity continues independently\n (animateAttr='opacity' vs CSS-property filter\n — non-conflicting). transition list extends to\n 'filter 200ms ease-out' alongside fill/r.\n Drop-shadow visual-polish family extension\n (8 anchors): R476 hub-digit / R477 legend pin-\n ring / R478 recent freshness / hot edge / group\n label / zoom-state / node alias + R532 hub-\n highlight (this round). data-topo-hub-highlight-\n glow attr exposes the gate state. */\n style={{\n pointerEvents: 'none',\n r: `${highlightR}px`,\n /* Round 652 / Loop — hub-highlight disc drop-shadow\n gains a SECOND outer layer at 4px/6px blur with\n halved alpha. 11th anchor in multi-layer halo\n family (3rd hub-cluster anchor after R650 digit\n + R651 halo). Both light + cyber branches stack\n 2 drop-shadows ahead of brightness — same R650\n stride pattern (light 2+4 / cyber 3+6) and 50%\n alpha falloff (0.6 → 0.3). The MIDDLE disc of\n the hub focal cluster now matches the outermost\n halo (R651) + innermost digit (R650) halo\n grammar — 3-tier hub focal cluster now fully\n multi-layer on hover. Same emerald tint across\n both layers, chromatic identity preserved. */\n filter: !reducedMotion && hoveredHub\n ? (isLight\n ? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6)) drop-shadow(0 0 4px rgba(16, 185, 129, 0.3)) brightness(1.15)'\n : 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6)) drop-shadow(0 0 6px rgba(52, 211, 153, 0.3)) brightness(1.15)')\n : undefined,\n transition: 'opacity 300ms ease-out, fill 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',\n } as React.CSSProperties}\n >\n {/* Round 733 / Loop — SVG <title> a11y child for the hub-\n highlight disc, the most prominent focal element on\n the canvas (network center). Sibling treatment to R730/\n R731 brand-surface a11y sweep. The hub-highlight is\n visibility-gated (only shows when workingCount === 0)\n but its <title> stays in DOM regardless — screen\n readers can identify it at all times. Canonical\n format \"<name> · <role>\": \"hub\" is the network-center\n name; \"network center · idle indicator\" is the role\n expanded to reflect both spatial and state semantics. */}<title>hub · network center · idle indicator</title>\n {/* Round 497 / Loop — idle-state breath (呼吸感 theme pivot\n from the R492-R496 press-family arc). Pre-R497 the hub\n idle highlight read as a static dim disc — present but\n motionless, visually mute. R497 adds a 4s opacity breath\n (0.85 ↔ 1.0 ↔ 0.85) so the hub reads \"alive but quiet\"\n instead of \"frozen\", giving the empty-fleet state a\n subtle living signature.\n Gates:\n - !reducedMotion (R29 a11y blanket) — reducedMotion\n users see static 0.95 disc, no animate\n - workingCount === 0 — when fleet is busy, the\n highlight is invisible (opacity=0) so the animate\n would waste paint cycles. Gating saves work.\n SMIL <animate> overrides the static opacity={0.95}\n during its run; falls back to 0.95 when reducedMotion\n flips on (the animate node simply doesn't render).\n 4s cycle is long enough to feel like ambient breath\n rather than a pulse, matching the \"quiet\" semantic.\n data-topo-hub-highlight-breath attr exposes the\n resolved gate state for tests. */}\n {breathActive && (\n <animate attributeName=\"opacity\" values=\"0.85;1;0.85\" dur=\"4s\" repeatCount=\"indefinite\" />\n )}\n </circle>\n );\n })()}\n {/* R115 / Loop: hover hint ring. Stroke-only circle at r=14\n that fades in when the hub is hovered — the same idea\n R44 used for node avatars (group-hover stroke). r=14\n sits comfortably outside the r=10 core and INSIDE the\n r=18 grounding halo, so the hover indicator is fully\n contained within the existing hub footprint (no bbox\n growth, overlap test unchanged). pointerEvents:none so\n the hint can't intercept the click that produced it. */}\n {/* Round 177 / Loop: hub hover ring picks up the same\n \"lift on hover\" gesture R164 added to the edge midpoint\n badge (r=9→10.5). Pre-R177 the ring faded opacity 0→0.7\n on hover but stayed static at r=14. Adding r=14→17 on\n hover gives the gesture extra weight — the hub responds\n more confidently. Stays within the r=18 halo bbox (no\n geometry growth), so the R51 overlap-test guard rails\n still hold. strokeWidth=1.5 is the offline-node\n sentinel but the overlap-test selector is gated to\n `g[data-node]` ancestors — this hub-internal circle\n is invisible to that probe. transition list grows `r`\n alongside opacity so both ease in concert; the lift\n feels like one continuous gesture. Seventh surface in\n the hover-elevation family (R51 nodes, R135 panels,\n R142 group boxes, R143/R144 rows, R164 edge badges,\n R177 hub ring). prefers-reduced-motion respected via\n R29 globals.css blanket. */}\n {/* Round 385 / Loop: hub hover-ring strokeWidth 1.5 → 1.75.\n Sibling visual-weight bump (11th anchor) to R367 edge-\n badge rest stroke 1 → 1.25. The ring is only visible\n during hub hover (opacity=0 rest, R177 + R370 control\n the hover-state alpha) so the change manifests purely\n as a thicker hover-state ring on the canvas focal\n point. R177 r 14 → 17 grow + R370 opacity 0 → 0.8\n already lift the hover cue; R385 adds stroke weight\n as the third lift axis. Stays clear of R51 overlap-\n test sentinel value 3 (1.75 is non-sentinel); the\n R51 selector is gated to g[data-node] ancestors so\n this hub-internal circle is invisible to the probe\n regardless. R253 stroke transition + pointerEvents:\n none preserved. data-topo-hub-hover-ring-stroke-width\n attr exposes the value for tests. */}\n <circle\n cx={cx} cy={cy}\n r={hoveredHub ? 17 : 14}\n fill=\"none\"\n stroke={isLight ? '#059669' : '#10b981'}\n strokeWidth=\"1.75\"\n /* Round 370 / Loop: hub hover-ring cyber opacity 0.7 →\n 0.8. R177 designed the hub hover-ring at opacity-0 →\n 0.85 (light) / 0 → 0.7 (cyber). The 15 % gap between\n the two themes meant cyber-theme operators got a\n noticeably softer hover cue than light-theme users\n against backgrounds that should equalise (dark bg\n needs more luminance to read as 'on'). R370 bumps\n cyber 0.7 → 0.8, narrowing the theme gap to 5 % —\n sibling theme-consistency polish to R251 edge badge\n fill/opacity (cyber 0.82 / light 0.95) and R246/R247\n panel transition families. Light theme 0.85 stays\n as is (already in the legibility band). data-topo-\n hub-hover-ring-opacity attr exposes the value for\n tests. */\n opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}\n data-topo-hub-hover-ring\n data-topo-hub-hover-ring-radius={hoveredHub ? 17 : 14}\n data-topo-hub-hover-ring-stroke-width=\"1.75\"\n data-topo-hub-hover-ring-opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}\n /* Round 535 / Loop — completes the hub-cluster glow\n QUARTET by adding drop-shadow to the hub-hover-ring.\n Pre-R535 the hub-hover trio (R476 digit + R532 highlight\n disc + R533 spokes) glowed in unified emerald (digit/\n disc) + cyan/teal (spokes) on hub-hover, but the ring\n itself — the outermost solid emerald boundary at\n r=14→17 — stayed flat. R535 adds the matching emerald\n drop-shadow to the ring so the FULL hub-cluster glows\n across all four concentric surfaces on hub-hover:\n digit (typography center) drop-shadow 0 0 3px emerald\n highlight disc (r=5.5/6) drop-shadow 0 0 3px emerald\n hover-ring (r=14/17) drop-shadow 0 0 3px emerald ← this round\n spokes (mesh) drop-shadow 0 0 1.5px cyan/teal\n The ring is only visible on hub-hover (opacity=0 rest);\n adding drop-shadow at the same gate means the glow shows\n the moment the ring shows — no extra state needed.\n Same R476/R532 emerald palette since the ring sits\n inside the focal-disc tier (its color is also emerald\n #059669/#10b981).\n transition list extends to include 'filter 200ms ease-\n out' alongside the existing 180ms opacity/r — slight\n cadence mismatch (180 vs 200) is acceptable; the filter\n only appears AFTER the ring fades in via opacity, and\n the 200ms vs 180ms 20ms tail difference is below\n perceptual threshold.\n Drop-shadow visual-polish family extension (11 anchors):\n the hub-cluster glow quartet (R476/R532/R533/R535) plus\n the 7 non-hub anchors (R477/R478/R479/R480/R481/R483/\n R534) makes for a thoroughly polished glow vocabulary\n across the canvas.\n data-topo-hub-hover-ring-glow attr exposes the gate\n state for tests. */\n data-topo-hub-hover-ring-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}\n /* Round 579 — hub-hover-ring joins per-element brightness\n family at 18th anchor. Stacks brightness(1.15) onto\n R535's drop-shadow — same R564/R570/R571/R572/R573/R574/\n R575/R577/R578 stacked-filter pattern. Closes hub-\n cluster brightness at 4 concentric elements (digit\n R575 + highlight R574 + halo R577 + hover-ring R579). */\n data-topo-hub-hover-ring-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}\n data-topo-hub-hover-ring-halo-layers={!reducedMotion && hoveredHub ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n /* Round 653 / Loop — hub-hover-ring drop-shadow gains a\n SECOND outer layer at 6px blur + 0.25 alpha (half R535's\n inner 0.5). 12th anchor in multi-layer halo family\n (4th hub-cluster anchor after R650 digit + R651 halo +\n R652 highlight). The hover-ring is the BOUNDARY between\n hub focal cluster and surrounding canvas — adding a\n 2nd layer extends the visual halo grammar outward\n without crossing into the node ring.\n 3+6 stride matches R650 cyber stride (same scale tier\n r=14-17). Same emerald tint across both layers;\n chromatic identity preserved. Outer alpha = half\n inner (0.5 → 0.25, 50%). */\n filter: !reducedMotion && hoveredHub\n ? (isLight\n ? 'drop-shadow(0 0 3px rgba(16, 185, 129, 0.5)) drop-shadow(0 0 6px rgba(16, 185, 129, 0.25)) brightness(1.15)'\n : 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.5)) drop-shadow(0 0 6px rgba(52, 211, 153, 0.25)) brightness(1.15)')\n : undefined,\n transition: 'opacity 180ms ease-out, r 180ms ease-out, stroke 200ms ease-out, filter 200ms ease-out',\n }}\n />\n </g>)}\n\n {/* agent nodes */}\n {[...onlineNodes, ...offlineNodes].map((session, nodeIdx) => {\n const pos = nodePositions[session.alias];\n if (!pos) return null;\n\n const sseCountFor = (session.network_id ? sseSessions[`${session.network_id}:${session.alias}`] : undefined) ?? sseSessions[session.alias];\n const isOnline = session.status !== 'offline' || !!sseCountFor;\n const status = nodeStatus(session, isOnline, isLight);\n const isActive = activeAliases.has(session.alias);\n // #113: node size scales with the S/M/L control; halos, labels,\n // badge and avatar all derive from `radius` so they follow.\n const radius = Math.round((isOnline ? 26 : 18) * nodeScale);\n // Round 109/110 (Vincent 4582 + 4583 P0): at high node counts\n // the 100px opaque label cards overlapped each other and\n // covered neighbouring avatars. But hiding text entirely went\n // too far (\"还是得有文字\"). So: below the density threshold the\n // full name+status card shows always; above it each node shows\n // a lightweight plain-text alias (no opaque card → can't\n // occlude an avatar), and the full card appears only for the\n // hovered node or once zoomed in past 1.4×.\n const showFullLabel = !denseLayout || hoveredAlias === session.alias || view.zoom >= 1.4;\n // Round 8: when a node in another group is hovered, fade this\n // one. Same-group nodes (incl. singletons matching the hovered\n // alias) stay full. Pure visual focus, geometry unchanged.\n const inFocus = !activeGroup || (groupKeys[session.alias] ?? session.alias) === activeGroup;\n // R72: in ring layout, classify the node into a tier by its\n // distance from hub centre so the first-paint stagger can\n // emanate inner → outer instead of running clockwise. Grid\n // layout doesn't have a radial structure; it keeps R9's pure\n // nodeIdx stagger. Thresholds bracket the actual tier radii\n // (single 220, dual 175/260, triple 145/215/285, offline 325+).\n let tierIdx = 0;\n if (layout === 'ring') {\n const p = nodePositions[session.alias];\n if (p) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n tierIdx = d < 195 ? 0 : d < 270 ? 1 : d < 310 ? 2 : 3;\n }\n }\n\n return (\n <g\n key={session.alias}\n data-node={session.alias}\n data-tier-idx={layout === 'ring' ? tierIdx : -1}\n // R151 / Loop: node a11y compliance — match the pattern\n // R116 / R139 / R140 / R148 / R149 applied to other\n // interactive surfaces. Node <g> has been clickable for\n // chat since R45 but tab-unreachable + screen-reader-\n // unannounced. role=\"button\" + tabIndex=0 + aria-label\n // expose it; aria-pressed reflects chat-target state so\n // SR users know which node currently has the popover\n // open. onKeyDown for Enter / Space fires the same\n // setChatAlias path as onClick. preventDefault on\n // Space stops the SVG canvas from scrolling.\n role=\"button\"\n tabIndex={0}\n aria-pressed={chatAlias === session.alias}\n aria-label={`Chat with ${session.alias} (${session.status})`}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setChatAlias(session.alias);\n setClickRipple({\n ts: Date.now(),\n x: pos.x, y: pos.y, r0: radius,\n color: status.primary,\n });\n setTimeout(() => setClickRipple(prev =>\n prev && Date.now() - prev.ts >= 590 ? null : prev), 600);\n }\n }}\n // Round 3 / Loop: `anet-fade-in` runs once when the <g>\n // mounts — a new session entering the fleet (or the topology\n // first rendering) eases in instead of popping. Re-renders of\n // an existing node don't re-trigger (React preserves the <g>\n // via the alias key), so status changes don't flicker. The\n // global prefers-reduced-motion sweep already neutralises it.\n className=\"group transition-opacity anet-fade-in anet-topo-svg-focus\"\n style={{\n cursor: 'pointer',\n // Round 17 / Loop: offline nodes drop to 0.6 at rest so\n // online nodes pop without losing the offline-as-ghost\n // information. The dashed stroke + smaller radius already\n // say \"offline\"; dimming the whole group strengthens the\n // online-vs-offline hierarchy at first glance. Exempt the\n // chat-focused node — if the user explicitly opened a\n // popover targeting an offline alias, that node stays\n // full-brightness so the focus ring + popover read as one\n // selected thing rather than a dimmed selection. Group-\n // hover fade (Round 8) still wins when inFocus is false.\n // Round 49 / Loop: edge-hover endpoint highlight composes\n // OVER the inFocus/online formula — a non-endpoint node\n // dims to 0.28 (just below the inFocus 0.32 to read as a\n // stronger \"not relevant\" signal), endpoints keep their\n // base opacity. chatAlias still wins to keep the focus\n // ring legible if the user clicked through.\n // Round 55 / Loop: legend-status hover composes on the\n // SAME level as edge-hover-endpoint. Non-matching nodes\n // dim to 0.28; matching nodes stay at their base.\n // activeStatus matches: working = status==='working',\n // idle = online but not working, offline = !isOnline.\n // Round 60 / Loop: activeStatus = hoveredStatus ?? pinnedStatus\n // so the pressure-bar segment pins (R60) and the legend\n // row hover (R55) feed the same branch.\n // Round 80 / Loop: vendor-letter hover composes ABOVE the\n // activeStatus branch so hovering the vendor chip's `C`\n // dims everything except C-vendor nodes. Same dim value\n // (0.28) as edge-endpoint and status filters — visually\n // consistent. Vendor lookup uses the same vendorForModel\n // helper the avatar render uses, keyed by initial so the\n // chip and the avatar always agree on grouping.\n opacity: hoveredEdgeEndpoints && !hoveredEdgeEndpoints.has(session.alias) && chatAlias !== session.alias\n ? 0.28\n : activeVendor && chatAlias !== session.alias && (() => {\n const v = vendorForModel(session.model, session.runtime);\n const initial = v.id === 'unknown' ? '?' : v.initial;\n return initial !== activeVendor;\n })()\n ? 0.28\n : activeStatus && chatAlias !== session.alias && !(\n activeStatus === 'working' ? session.status === 'working'\n : activeStatus === 'idle' ? (isOnline && session.status !== 'working')\n : /* offline */ !isOnline\n )\n ? 0.28\n : !inFocus\n ? 0.32\n : chatAlias === session.alias\n ? 1\n : isOnline ? 1 : 0.6,\n // Round 9 / Loop: stagger the anet-fade-in so the topology\n // reveals as a wave on first paint instead of one big pop.\n // Cap at 24 indices (≈600ms tail) so 50-node fleets still\n // finish revealing within a beat. CSS animation-delay only\n // applies during the keyframe — re-renders without a new\n // mount (same alias key) don't replay, so status changes\n // never trigger the stagger again.\n // Round 72 / Loop: in ring layout, stagger by tier radius\n // so the topology emanates from the hub outward — inner\n // tier at 0ms, middle at ~180ms, outer at ~360ms, offline\n // at ~540ms. A small within-tier offset (nodeIdx % 6 * 25)\n // adds variety so each ring rotates in instead of all-at-\n // once popping. Grid keeps R9's pure nodeIdx stagger.\n animationDelay: layout === 'ring'\n ? `${tierIdx * 180 + (nodeIdx % 6) * 25}ms`\n : `${Math.min(nodeIdx, 24) * 25}ms`,\n // Round 51 / Loop: hover micro-lift. The label already\n // lifts on group-hover (R26). The node body — circle,\n // avatar, status ring — stays planted, so the gesture\n // reads \"label moves, body doesn't\". Now the whole <g>\n // translates -2px when hovered: avatar + ring + label\n // move as one unit. 2 px is well inside the inter-row\n // gap (cellH headroom ≥22), and CSS transform on SVG\n // <g> never affects the overlap-test geometry (the\n // test never simulates hover). useReducedMotion drops\n // the lift to 0 for prefers-reduced-motion users.\n transform: !reducedMotion && hoveredAlias === session.alias ? 'translateY(-2px)' : undefined,\n transition: 'transform 180ms cubic-bezier(0.4,0,0.2,1)',\n }}\n // Stop the pointerdown from reaching the SVG pan handler: the\n // SVG calls setPointerCapture, and a captured pointer makes\n // Chromium fire the follow-up `click` on the SVG instead of\n // this node — so without this the node's onClick never runs.\n // Side effect (intended): you pan from empty canvas, not by\n // grabbing a node.\n onPointerDown={(e) => e.stopPropagation()}\n // Track hover globally (not just under denseLayout) so the\n // Round 8 group-focus fade works at any fleet size.\n onPointerEnter={() => setHoveredAlias(session.alias)}\n onPointerLeave={() => setHoveredAlias(prev => (prev === session.alias ? null : prev))}\n onClick={() => {\n setChatAlias(session.alias);\n // Round 14 ripple — capture position, radius and status\n // colour at click time so the one-shot circle is self-\n // contained (no re-render-time recomputation needed).\n setClickRipple({\n ts: Date.now(),\n x: pos.x, y: pos.y, r0: radius,\n color: status.primary,\n });\n setTimeout(() => setClickRipple(prev =>\n prev && Date.now() - prev.ts >= 590 ? null : prev), 600);\n }}\n >\n {/* Issue #96: native hover tooltip — \"Vendor · model · Runtime\".\n Falls back to just the alias when the node reports no\n model/runtime.\n Round 33 / Loop: cwd line answers \"what is this agent on?\".\n Round 34 / Loop: for offline nodes, append \"last seen: 6m ago\"\n so the operator knows whether to wait or chase. Online nodes\n skip the line (Round 27's 1 h ghost age-out means anything\n still online has heartbeated recently — the line would just\n be noise). Accepts both ISO (\"…T06:00:28Z\") and SQL-style\n (\"… 06:00:28\" assumed UTC) formats. */}\n {(() => {\n // Round 35 / Loop: TZ-safe parsing via the shared lib helper\n // — same parseHubTime is used by isGhost above (Round 38\n // factored it to app/lib/time.ts so both paths interpret SQL\n // bare timestamps as UTC on every browser).\n const lastSeen = !isOnline && session.last_seen_at ? relativeAgo(session.last_seen_at) : null;\n // Round 98 / Loop: enrich the node tooltip with status,\n // group membership, and inbound/outbound flow counts —\n // same info-density spirit as R97 active-filter pill\n // tooltips. Hovering any node now answers \"what is this\n // and how does it sit in the topology\" without forcing\n // a click into the chat popover. Group line only shows\n // when the alias is part of a multi-member band (R106\n // prefix grouping); singletons skip it. Flow line only\n // when at least one direction has count > 0.\n const groupKey = groupKeys[session.alias];\n const groupMembers = groupKey\n ? Object.values(groupKeys).filter(k => k === groupKey).length\n : 1;\n const groupLine = groupMembers > 1 ? `group: ${groupKey} · ${groupMembers}` : null;\n // R147 / Loop: node tooltip extends R98's flow summary\n // with the actual sender / receiver aliases. R98 told you\n // \"12 in / 5 out\" but not WHO. The R97 idiom — anywhere\n // the UI shows \"N\" should hover-explain WHICH N — applied\n // to filter pills, group labels, vendor letters, pressure\n // segments, recent-row text, active-links chip; the node\n // title was the last surface still showing only the\n // aggregate. Direction-tagged: senders are who's\n // messaging THIS node (inbound), receivers are who this\n // node is messaging (outbound). 6-truncate + \"+N more\"\n // matches the pattern other tooltips use so a 30-flow\n // node doesn't paint a 30-line tooltip.\n let flowIn = 0, flowOut = 0;\n const sendersMap = new Map<string, number>(); // alias → count, inbound\n const receiversMap = new Map<string, number>(); // alias → count, outbound\n for (const fl of flowLinks) {\n if (fl.from === session.alias) {\n flowOut += fl.count;\n receiversMap.set(fl.to, (receiversMap.get(fl.to) ?? 0) + fl.count);\n }\n if (fl.to === session.alias) {\n flowIn += fl.count;\n sendersMap.set(fl.from, (sendersMap.get(fl.from) ?? 0) + fl.count);\n }\n }\n const fmtPeers = (m: Map<string, number>) => {\n const pairs = [...m.entries()].sort((a, b) => b[1] - a[1]);\n const preview = pairs.slice(0, 6).map(([a, n]) => `${a} (${n})`).join(', ');\n const suffix = pairs.length > 6 ? ` + ${pairs.length - 6} more` : '';\n return preview + suffix;\n };\n const flowLine = (flowIn + flowOut) > 0 ? `flows: ${flowIn} in / ${flowOut} out` : null;\n const sendersLine = sendersMap.size > 0 ? `← from: ${fmtPeers(sendersMap)}` : null;\n const receiversLine = receiversMap.size > 0 ? `→ to: ${fmtPeers(receiversMap)}` : null;\n return (\n <title>{[\n `${session.alias} · ${session.status}`,\n identityLine(session.model, session.runtime),\n groupLine,\n session.project_dir ? `cwd: ${session.project_dir}` : null,\n lastSeen ? `last seen: ${lastSeen}` : null,\n flowLine,\n sendersLine,\n receiversLine,\n ].filter(Boolean).join('\\n')}</title>\n );\n })()}\n {/* Round 2 / Loop: hover ring — a thin outer stroke that fades\n in when the cursor enters the node, signalling clickability\n (real-user feedback for the chat-popover open). Pure CSS via\n Tailwind group-hover, so it costs nothing per frame and\n respects prefers-reduced-motion via the global media query.\n Round 489 / Loop — duration harmonized from 150ms → 200ms\n to join the Hero D #147 motion-coherence stack (R459-R475\n cluster surfaces + cadence-sync family). R2 originally\n picked 150ms for a \"snappier feel\" before the 200ms ease-\n out vocabulary was banked as the canvas-wide motion\n default. Bringing this ring into the family means hover-\n in / hover-out / cluster cadence / pip-strip transitions\n all settle on the same timing — the canvas now reads as\n one motion vocabulary instead of two competing tempos.\n 11th surface in the motion-coherence stack. */}\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 12}\n fill=\"none\"\n stroke={status.primary}\n // strokeWidth must NOT be 1.5 (offline status ring) or 3\n // (online status ring) — the overlap test selects by those\n // exact widths and would mis-count this invisible hover\n // ring as a node footprint.\n strokeWidth=\"2\"\n className=\"opacity-0 group-hover:opacity-70 transition-opacity duration-200\"\n /* Round 666 / Loop — per-node hover ring (r=radius+12)\n gains a stroke-tinted drop-shadow halo on\n `hoveredAlias === session.alias`. Pre-R666 this\n ring only fired the Tailwind opacity-0/group-\n hover:opacity-70 fade — no paint axis. R666\n introduces multi-layer halo (R642 family pattern)\n at this surface: status.primary tint at 2+4 stride\n with 50% falloff (0x80→0x40). 25th anchor in the\n multi-layer halo family.\n hoveredAlias React-state gate composes with the\n Tailwind group-hover gate — both fire at the same\n user input but on independent rules. transition\n list extended via inline style to include 'filter\n 200ms ease-out' alongside the Tailwind opacity\n duration-200. R51 sentinel safe: strokeWidth=2\n is not in the reserved {1.5, 3} set. */\n data-node-hover-ring={session.alias}\n data-node-hover-ring-halo-layers={hoveredAlias === session.alias ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n filter: hoveredAlias === session.alias\n ? `drop-shadow(0 0 2px ${status.primary}80) drop-shadow(0 0 4px ${status.primary}40)`\n : undefined,\n transition: 'filter 200ms ease-out',\n }}\n />\n {/* Round 11 / Loop: chat-focus ring — when the ChatPopover is\n open targeting this node, anchor a persistent ring around\n it so the floating popover visibly links back to its source\n node. Static (not pulsing) so it reads as \"selected state\"\n rather than \"this node is active\". strokeWidth=2.5 stays\n clear of the overlap-test selectors (1.5 / 3). Sits just\n outside the halo radius+8 so it never overlaps a neighbour\n (halos already pack flush in dense grids). */}\n {/* R51 chat-target ring. R120 / Loop: gentle SMIL\n breath on the ring's opacity (±0.1 over 3s) when\n chat is open + !reducedMotion. Says \"active session\n here\" continuously without animation noise — the\n ring only appears for one node at a time (the\n chatAlias), so it never competes with R84 hub\n busyness or R112 working halo for attention.\n\n Round 183 / Loop: 7th surface in the smooth-pin-\n mirror family. Pre-R183 the ring was conditionally\n mounted on `chatAlias === session.alias`; the\n className `transition-opacity duration-200` never\n fired because the element didn't exist before\n mount. Always-mounted now with opacity gated by\n isChat — the CSS transition fires cleanly on\n chat-close (smooth fade-out). The `<animate>`\n SMIL stays gated by `!reducedMotion && isChat`\n so it only runs for the active chat target; when\n chat is closed, SMIL unmounts and opacity reverts\n to attribute (0) → CSS transitions down smoothly.\n On chat-OPEN the SMIL takes over per spec, so the\n fade-in is snappier than the fade-out — acceptable\n because the user explicitly clicked the node.\n\n strokeWidth=2.5 is not a R51 sentinel value\n (sentinels are 1.5/3 inside g[data-node]), so the\n ring is invisible to the overlap-test selector\n even when always-mounted. */}\n {/* Round 242 / Loop: extend the chat-target ring's\n transition list to include stroke + filter.\n Pre-R242 only `opacity` eased (R183 200ms): a\n chat-target node going working → idle hard-\n flipped the ring's stroke colour (status.primary\n green → teal) in one frame even though the rest\n of the ring was a smooth presence. Filter (glow\n on cyber, none on light) also snapped on chat\n toggle AND on theme switch.\n\n Add `stroke 200ms ease-out` + `filter 200ms\n ease-out` so the colour and glow both ease at\n the same cadence as the opacity gate. Same\n idiom R167 (node status-ring) uses for\n coordinated colour-easing on status flip;\n R242 brings the chat-target ring up to that\n bar. */}\n {(() => {\n const isChat = chatAlias === session.alias;\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 14}\n fill=\"none\"\n stroke={status.primary}\n strokeWidth=\"2.5\"\n opacity={isChat ? (isLight ? 0.85 : 0.95) : 0}\n filter={!isLight && isChat ? 'url(#topo-glow)' : undefined}\n data-node-chat-ring-brightness={isChat ? '1.15' : '1'}\n /* 🎯 R615 — 100th consecutive visible-polish round.\n Chat-target ring (visible only when chatAlias ===\n session.alias) stacks brightness(1.15) onto its\n existing url(#topo-glow) SVG filter on cyber, or\n plain brightness on light. Same R582/R583 stacked-\n filter pattern.\n\n INTRODUCES A NEW 4th BRIGHTNESS GATE TYPE — chat-\n target-gated. The cross-gate brightness family\n taxonomy now spans 4 distinct trigger conditions:\n hover-gated: R501-R613 (many surfaces)\n pin/active-gated: R571 + R587 + R607 + R609-R612\n freshness-gated: R606 (recent-row pip)\n chat-target-gated: R615 (this round, chat ring)\n\n When the user opens a chat popover with a specific\n node, that node's surrounding ring (r=radius+14)\n identifies the chat target with status.primary\n stroke. R615 adds +15% brightness so the\n identification ring reads brighter — the active\n chat partner is unmistakable on the canvas.\n\n Inline style.filter overrides the attribute\n filter; stacked syntax preserves the url(#topo-\n glow) on cyber. Same pattern banked at R582\n visible-path + R583 particle + R584 status-ring. */\n /* Round 642 / Loop — chat-target ring gains a\n SECOND drop-shadow layer at wider blur + lower\n alpha for a multi-layer halo (near + far).\n Pre-R642 R637 stacked one drop-shadow at 3px\n blur + 0x40 alpha + status.primary tint —\n a single soft halo. R642 adds an outer\n drop-shadow at 6px blur + 0x20 alpha (same\n hue) so the chat-target ring radiates two\n layered halos: a tighter near-halo + a\n softer far-halo. The chat partner now reads\n as \"glowing outward in layers\" — same Apple-\n style soft-shadow vocabulary used in iOS\n focus indicators.\n Combined filter chain (banked R582/R583\n stacked-filter pattern, 3-deep):\n light: drop-shadow(0 0 3px ...40)\n drop-shadow(0 0 6px ...20)\n brightness(1.15)\n cyber: ... (same 2 drop-shadows) +\n url(#topo-glow) brightness(1.15)\n The 2x-blur outer halo is ~2x softer (40%→\n 20% alpha) so the eye reads it as ambient\n rather than a second discrete ring.\n R51 sentinel safety: filter is paint-only,\n no bbox change. transition list (R242 already\n covers filter 200ms) eases the layered glow\n on status flip together.\n data-chat-target-ring-halo-layers attr\n exposes the multi-layer fact for tests. */\n /* Round 637 / Loop — chat-target ring filter\n gains a status-tinted drop-shadow halo.\n Pre-R637 the filter was either `brightness(1.15)`\n (light) or `url(#topo-glow) brightness(1.15)`\n (cyber): a uniform halo colour (cyan on cyber,\n no halo on light) regardless of which status\n tier the chat partner sits in. The ring's\n STROKE colour already shifts with status\n (R242 status.primary easing: green/teal/slate),\n but its GLOW was decoupled from that identity.\n R637 closes that — the drop-shadow now uses\n status.primary at 0x40 alpha so the halo\n colour matches the stroke colour AND eases\n smoothly on status flip via the existing\n `filter 200ms ease-out` transition (R242).\n Stacked filter chain on cyber:\n drop-shadow(... status.primary 40%)\n url(#topo-glow)\n brightness(1.15)\n and on light (no glow filter):\n drop-shadow(... status.primary 40%)\n brightness(1.15)\n A working chat partner now reads as a GREEN-\n haloed ring, idle as TEAL-haloed, offline as\n SLATE-haloed — chat target identity becomes\n fully chromatic at glance (stroke + halo\n match), reinforcing \"this is your chat\n partner, and its status is X\".\n 3px blur stays subtle (R478 freshness pip\n scale), 40% alpha (0x40) softer than\n R533/R536's hub-cluster 0.4-0.6 halos —\n appropriate since the chat-target ring is\n the OUTERMOST per-node identifier and the\n halo should read as a confident-but-quiet\n ambient glow rather than a loud rim.\n R51 sentinel safety: filter is paint-only,\n no bbox change. topo-overlap-test gated to\n g[data-node] descendant <circle stroke-\n width=\"1.5\"|\"3\">; this ring's sw=2.5 and the\n filter doesn't affect overlap reads.\n transition list already includes 'filter\n 200ms ease-out' (R242). data-chat-target-\n ring-halo-color attr exposes the resolved\n color for tests. */\n data-chat-target-ring-halo-color={isChat ? status.primary : 'none'}\n data-chat-target-ring-halo-layers={isChat ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n filter: isChat\n ? (isLight\n ? `drop-shadow(0 0 3px ${status.primary}40) drop-shadow(0 0 6px ${status.primary}20) brightness(1.15)`\n : `drop-shadow(0 0 3px ${status.primary}40) drop-shadow(0 0 6px ${status.primary}20) url(#topo-glow) brightness(1.15)`)\n : undefined,\n transition: 'opacity 200ms ease-out, stroke 200ms ease-out, filter 200ms ease-out',\n } as React.CSSProperties}\n data-chat-target-ring\n data-chat-target-active={isChat ? 'true' : 'false'}\n data-chat-target-breath={!reducedMotion && isChat ? 'on' : 'off'}\n /* R630 — chat-target ring gains a SECOND breath axis:\n stroke-width. Pre-R630 R120 added a single opacity\n breath (0.72↔0.95 light / 0.82↔1.0 cyber) over 3s\n to say \"active session here\". R630 stacks a stroke-\n width breath 2.5↔2.75 over the same 3s cadence, in\n lockstep with the opacity peak — the ring \"swells\"\n as it brightens, then settles as it dims.\n Subtle (+10 % sw amplitude, identical to R243\n active-pulse r-breath +14 → +22 magnitude ratio\n on the active-pulse ring). 呼吸感 reads doubly —\n opacity + geometry breathe together as one\n organic gesture, not just an alpha modulation.\n R51 sentinel safety: SW range [2.5, 2.75] stays\n clear of the reserved {1.5, 3} sentinels inside\n g[data-node]; topo-overlap-test invariants hold.\n Gated to `!reducedMotion && isChat` like the\n existing opacity breath, so reduced-motion users\n and non-chat-target nodes see no extra motion.\n data-chat-target-ring-sw-breath attr exposes the\n new gate for tests. */\n data-chat-target-ring-sw-breath={!reducedMotion && isChat ? 'on' : 'off'}\n /* Round 640 / Loop — chat-target ring gains a THIRD\n breath axis: radius. Pre-R640 R120 ran the opacity\n breath (paint) and R630 layered a stroke-width\n breath (line weight) — 2 axes. R640 stacks an r\n breath `radius+14 ↔ radius+14.5` over the same\n 3s cadence, all three SMIL animates running in\n lockstep through the same fragment gate.\n Now the ring's 呼吸感 spans THREE concentric\n axes synchronized at 3s ease-out spline:\n opacity paint (R120)\n stroke-wd line (R630, +10% amplitude)\n radius bbox (R640, +3.6% amplitude) ← this\n The ring breathes paint + weight + size together\n as one organic gesture — the most complete\n 呼吸感 surface on the canvas now.\n Amplitude 0.5px is conservative: peak r =\n radius+14.5 stays under any neighbouring ring\n tier boundary. In ring layout (R72 tiered radii\n single 220 / dual 175+260 / triple 145/215/285\n / offline 325+) tier spacing is ≥50px, so\n +0.5px breath has no overlap risk. In grid\n layout the chat-target ring's box is one node-\n wide; the cluster box around it is much wider.\n Gated `!reducedMotion && isChat` like the\n existing breath axes — three-axis SMIL only\n runs for the active chat target; reduced-motion\n users see no extra motion.\n R51 sentinel safety: r is not a sentinel\n attribute (selector targets stroke-width). The\n ring's bbox grows by ±0.5px during peak but\n only when chat is open; topo-overlap-test runs\n without chat open so bbox stays at radius+14.\n data-chat-target-ring-r-breath attr exposes the\n new gate for tests. */\n data-chat-target-ring-r-breath={!reducedMotion && isChat ? 'on' : 'off'}\n data-chat-target-ring-breath-curve={!reducedMotion && isChat ? 'ease-in-out' : 'none'}\n >\n {!reducedMotion && isChat && (\n /* Round 641 / Loop — chat-target ring 3-axis breath\n gains SMIL ease-in-out keySplines on all three\n animates. Pre-R641 R120/R630/R640 ran their\n SMIL animates with default linear calcMode —\n opacity / sw / r interpolated at constant\n velocity through the 3-value trough→peak→\n trough bounce. Linear breath reads mechanical:\n dα/dt, dsw/dt, dr/dt all constant, no settle\n at the endpoints.\n R641 adds `calcMode=\"spline\" keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"`\n (canonical CSS ease-in-out cubic-bezier) to\n all three animates — same easing family as\n R227 click ripple / R243 active-pulse /\n R244 hub-halo. The ring's breath now settles\n briefly at each peak and trough, fast through\n the middle: a heart-rest cycle feel.\n Three coordinated organic eases at the SAME\n cadence + SAME curve = the most coherent\n 呼吸感 surface on the canvas. The breath now\n reads as ONE motion-coherent gesture across\n paint + stroke + geometry, not three\n independent oscillations.\n Reduced-motion gate unchanged; only the curve\n shape shifts inside the existing 3s window.\n data-chat-target-ring-breath-curve attr\n exposes the easing for tests. */\n <>\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.72;0.95;0.72' : '0.82;1;0.82'}\n dur=\"3s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n <animate\n attributeName=\"stroke-width\"\n values=\"2.5;2.75;2.5\"\n dur=\"3s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n <animate\n attributeName=\"r\"\n values={`${radius + 14};${radius + 14.5};${radius + 14}`}\n dur=\"3s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n </>\n )}\n </circle>\n );\n })()}\n {/* Round 243 / Loop: active-node pulse (the breathing\n aura ring at r=radius+14 fill=status.primary, shown\n on nodes participating in a recent flow) gains TWO\n polishes:\n\n 1) Always-mount + opacity-gate wrapper <g>. Pre-\n R243 the circle conditionally mounted on\n isActive — when a node joined a flow, the pulse\n snap-appeared at radius+8 (first SMIL phase\n value), and when the flow stopped, snap-\n disappeared. R243 keeps the SMIL animation\n running continuously inside an opacity-gated\n <g>; isActive flips the WRAPPER opacity\n 1↔0 with a 300ms ease-out transition so the\n pulse fades in/out at its current phase\n instead of restarting from +8. The reduced-\n motion gate stays at the conditional render\n level — reduced-motion users see no pulse at\n all (no point without the animation).\n\n 12th surface in the always-mount-opacity-gate\n family (R181/R182/R183/R213×2/R214/R215/R221/\n R222/R223/R237/R243).\n\n 2) SMIL ease-in-out keySplines on both r and\n opacity animates. Pre-R243 default linear\n calcMode produced a constant-velocity breath\n (radius marched +8 → +22 at fixed dr/dt;\n opacity dimmed 0.12 → 0.02 at fixed dα/dt) —\n mechanical, not organic. calcMode='spline'\n + keyTimes='0;0.5;1' + per-segment keySplines\n '0.42 0 0.58 1' (canonical CSS ease-in-out)\n both ways gives a settled breath: slow at\n both endpoints (small and large radius / lit\n and dim opacity), fast through the middle.\n Same SMIL-easing family R227 / R228 already\n inhabits at the click ripple + edge ping +\n pulse. */}\n {!reducedMotion && (\n <g\n opacity={isActive ? 1 : 0}\n data-node-pulse={session.alias}\n data-node-pulse-active={isActive ? 'true' : 'false'}\n style={{ transition: 'opacity 300ms ease-out' }}\n >\n <circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>\n <animate\n attributeName=\"r\"\n values={`${radius + 8};${radius + 22};${radius + 8}`}\n dur=\"2.4s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n {/* Round 409 / Loop: active-node pulse peak\n opacity lift — cyber 0.18 → 0.20 / light\n 0.12 → 0.14. Theme-consistency / canvas-\n presence family 9th anchor. R243 family\n rhythm preserved.\n Round 413 / Loop: trough lift mirrors R409\n peak — cyber 0.04 → 0.05 / light 0.02 →\n 0.03. Stale-state legibility lift family\n 8th anchor — pairs with R404 (hub-halo\n cyber trough 0.08 → 0.10) and R405 (light\n trough 0.32 → 0.34). The per-node breath's\n low-point now reads slightly above the\n \"nearly gone\" zone while preserving the\n breath amplitude (cyber Δ 0.16 vs Δ pre-\n R409+R413 of 0.14; light Δ 0.11 vs 0.10).\n Both peak (R409) AND trough (R413) lift\n together so the active-pulse signal stays\n confidently present at both ends of its\n 2.4s cycle.\n Stale-state legibility lift family (8):\n R317 subordinate-text gray-500→400\n R358 freshness floor 0.25→0.30\n R372 minimap offline-dot 0.5→0.6\n R404 hub-halo cyber trough 0.08→0.10\n R405 hub-halo light trough 0.32→0.34\n R406 edge freshness floor 0.35→0.40\n R407 node halo offline opacity (cyber+light)\n R413 active-node pulse trough (this round)\n cyber 0.04 → 0.05\n light 0.02 → 0.03\n R243 always-mount opacity-gate + R243\n ease-in-out keySplines + r animation\n (radius+8 ↔ radius+22) preserved.\n data-node-pulse-peak + new -pulse-trough\n attrs expose resolved per-theme values. */}\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.14;0.03;0.14' : '0.20;0.05;0.20'}\n dur=\"2.4s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n data-node-pulse-peak={isLight ? '0.14' : '0.20'}\n data-node-pulse-trough={isLight ? '0.03' : '0.05'}\n />\n </circle>\n </g>\n )}\n {/* Round 4 / Loop: transition-[fill,stroke,opacity] smooths\n status colour changes so idle↔working↔offline doesn't snap\n — task replies / node-rename / SSE updates ease in.\n Round 112 / Loop: working nodes get a subtle halo breath\n (±0.12 opacity at 3s cycle) so the eye can find \"what's\n busy\" at a glance without scanning chips. Idle + offline\n halos stay flat — they don't need to demand attention.\n R84 hub-center breath stays the loudest \"fleet busyness\"\n signal; this one is quieter, per-node. SMIL `<animate>`\n inside the circle, gated by reducedMotion. */}\n {/* Round 226 / Loop: working-halo breath gets per-node\n phase stagger. Pre-R226 every working node's halo\n pulsed in lockstep — all 0.73→0.92→0.73 starting at\n the same instant — which on a fleet of 4+ working\n agents reads as one mechanical metronome rather\n than an organic group of breathing entities.\n\n SMIL `<animate>` accepts negative `begin` to offset\n the cycle backwards in time (the animation starts\n mid-cycle on first paint). Using\n `(nodeIdx * 0.37) % 3` gives a deterministic,\n well-distributed offset across the 3s period —\n the same golden-ratio-ish 0.37 fraction R103 uses\n for particle phase stagger on edges. 0.37 doesn't\n line up for any small N (4 nodes → offsets 0,\n 0.37, 0.74, 1.11 — evenly spread, never\n coincident).\n\n Side benefit: when a new agent joins a busy fleet\n its halo phase is determined by its position in\n the order array, not \"when it joined\" — so a\n re-render doesn't reshuffle breath phases. Order\n is stable (R-onlineNodes sort), so the canvas\n feels calm rather than jittery on each refresh.\n\n Reduced-motion users skip the animate entirely\n (gate unchanged). Halo opacity transition on the\n parent stays for status flips. data-node-halo-\n breath-offset surfaces the chosen offset for\n test introspection. */}\n {/* Round 407 / Loop: offline node halo opacity lift —\n cyber 0.25 → 0.30 and light 0.4 → 0.45. Pre-R407\n offline node halos faded to α=0.25 cyber (75 %\n dim) / α=0.4 light. On the dark canvas the 0.25\n halo read as \"nearly gone\" — exactly the\n legibility floor R404/R405 just lifted on the\n hub-halo and R372 lifted on minimap offline dots.\n R407 closes the same family at the per-node halo\n surface: +0.05 lift on both themes so offline\n anchors stay legibly present without crossing into\n \"could be online\" territory (online cyber 0.65 /\n light 0.85 unchanged — the 0.30/0.65 cyber ratio\n still gives 2.17× contrast for online/offline).\n Stale-state legibility lift family (7 anchors now):\n R317 subordinate-text gray-500 → gray-400\n R358 freshness floor 0.25 → 0.30\n R372 minimap offline-dot 0.5 → 0.6\n R404 hub-halo cyber trough 0.08 → 0.10\n R405 hub-halo light trough 0.32 → 0.34\n R406 edge freshness floor 0.35 → 0.40\n R407 node halo offline opacity (this round)\n cyber 0.25 → 0.30\n light 0.4 → 0.45\n R278 retired-breath gate + R12 status.halo color\n + R226 phase stagger code-path preserved (the\n breath stays disabled per Vincent's R278 ask;\n only the BASE opacity floor shifts here). transi-\n tion list ('fill,opacity' 300ms ease-out) unchanged.\n data-node-halo-offline-opacity attr exposes the\n resolved value for tests. */}\n {(() => {\n /* Round 440 / Loop: node halo opacity hover lift —\n lifts toward full on the matched node. Pure paint\n axis: rest values unchanged for un-hovered halos,\n hover state lifts the matched halo's alpha by\n +0.15 on each tier:\n online cyber 0.65 → 0.80\n online light 0.85 → 1.00 (capped)\n offline cyber 0.30 → 0.45\n offline light 0.45 → 0.60\n Same paint-only mental model as R430 hub-spoke\n opacity lift + R429 label-card body opacity lift,\n now at the per-node halo scope. No geometry\n change so R51 sentinels stay safe and the overlap-\n test invariant is unchanged (test runs at rest).\n Closes a chroma/presence axis on the per-node\n hover signature alongside the 12-layer cue stack\n (R26/R217/R142/R427/R428/R429 card + R430/R435/\n R436/R437/R94 link + R438 ring). R407 offline\n halo opacity floor (cyber 0.30 / light 0.45) is\n the rest branch unchanged. Existing transition-\n [fill,opacity] duration-300 className handles\n the easing. data-node-halo-hovered exposes the\n gate; data-node-halo-resolved-opacity exposes\n the four-state resolved value for tests. */\n /* R623 — extend isHaloHovered gate to include\n chatAlias === session.alias. Single conceptual\n change cascades the halo's 4-state opacity tiers\n (online/offline × hover/rest) to also fire on\n chat-target. Per the R440 family doc, halo opacity\n lifts on hover:\n online cyber 0.65 → 0.80\n online light 0.85 → 1.00\n offline cyber 0.30 → 0.45\n offline light 0.50 → 0.60\n Now chat-target also triggers each lift.\n\n 9th anchor in chat-target-gated brightness family\n (R615 ring + R618 card + R616 alias + R617 sub +\n R619 avatar + R620 badge + R621 status ring +\n R622 hub-spoke + R623 node halo). The atmospheric\n halo behind the chat partner reads brighter,\n giving the locked-in node a stronger \"lit up at\n the boundary\" presence. */\n const isHaloHovered = !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias);\n /* Round 456 / Loop: light-theme offline node halo\n rest opacity 0.45 → 0.50. Stale-state legibility\n lift family extension (10th anchor) at the per-\n node halo light-theme scope:\n R317 subordinate-text gray-500 → gray-400\n R358 freshness floor 0.25 → 0.30\n R372 minimap offline-dot 0.5 → 0.6\n R404 hub-halo cyber trough 0.08 → 0.10\n R405 hub-halo light trough 0.32 → 0.34\n R406 edge freshness floor 0.35 → 0.40\n R407 node halo offline opacity\n cyber 0.25 → 0.30\n light 0.4 → 0.45\n R419 hub-spoke idle 0.45 → 0.50\n R452 dense alias rest 0.9 → 0.95\n R456 node halo offline LIGHT 0.45 → 0.50 ← this round\n Pre-R456 light-theme offline halo at 0.45 sat at\n the upper end of \"near-floor\" but read as soft-\n focus on the lighter canvas; +0.05 (~11 % opacity\n gain) lifts it to 0.50 — the midpoint between\n R407 rest 0.45 and R440 hover 0.60 — closing the\n gap so offline halos read more confidently as\n present-but-stale anchors. Cyber theme stays at\n R407's 0.30 (cyber backdrop is dark; the cyber\n offline halo against #0a0a0c contains a stronger\n contrast envelope than light, so doesn't need\n the same lift). R440 hover 0.45→0.60 light + R12\n status.halo color + R407 transition list all\n preserved. */\n const haloOpacity = (() => {\n if (isOnline) {\n return isLight ? (isHaloHovered ? 1 : 0.85) : (isHaloHovered ? 0.80 : 0.65);\n }\n return isLight ? (isHaloHovered ? 0.60 : 0.50) : (isHaloHovered ? 0.45 : 0.30);\n })();\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 8}\n fill={status.halo}\n opacity={haloOpacity}\n data-node-halo-offline-opacity={isOnline ? undefined : (isLight ? 0.45 : 0.30)}\n data-node-halo-hovered={isHaloHovered ? 'true' : 'false'}\n data-node-halo-resolved-opacity={haloOpacity}\n className=\"transition-[fill,opacity] duration-300 ease-out\"\n data-node-halo-breath={!reducedMotion && session.status === 'working' ? 'on' : 'off'}\n data-node-halo-breath-offset={\n !reducedMotion && session.status === 'working'\n ? ((nodeIdx * 0.37) % 3).toFixed(3)\n : undefined\n }\n >\n {/* Round 278 / Loop: per-node working halo breath\n (R112+R226+R244 family) RETIRED per Vincent\n 5214/5215-5217 simplification ask (减法 cut #4\n after R275 chip-row, R276 orbit, R277 legend).\n\n The breath was: each working agent's halo pulses\n 0.73→0.92→0.73 (cyber 0.53→0.78→0.53) at 3 s\n cycle, R226-staggered per-node, R244-eased. For\n a 4-working fleet that's 4 simultaneous SMIL\n breaths competing with the hub-halo breath\n (R244 hub) for the \"fleet busyness\" visual\n signal.\n\n The signal is info-redundant: the hub-halo\n breath ALREADY conveys \"the network is alive\n and busy\"; per-node halo breath duplicates it\n at 4× volume. Plus working nodes are ALREADY\n distinguished by their halo color (status.halo\n green/teal/slate via R12 trio) — the static\n halo carries identity, the moving breath was\n decorative motion on top.\n\n R278 gates the SMIL animate with `false &&` so\n the code remains for rollback. Halo opacity\n stays at the BASE (non-breathing) values via\n the parent circle's `opacity` attr (0.85/0.65/\n 0.4/0.25 from R12 + isOnline gate). Working\n nodes still show green halos; they just don't\n pulse.\n\n Net: -4 SMIL animations on canvas for typical\n 4-working fleet. Combined with R276 orbit\n retirement (-4) and the hub halo breath kept\n as the SOLE \"fleet busyness\" motion signal,\n the canvas reads quieter. R226 + R244 per-node\n stagger / ease constants are dead code post-\n R278 (acceptable — family retires together). */}\n {false && !reducedMotion && session.status === 'working' && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.73;0.92;0.73' : '0.53;0.78;0.53'}\n dur=\"3s\"\n begin={`-${((nodeIdx * 0.37) % 3).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n )}\n </circle>\n );\n })()}\n {/* Round 111 / Loop: edge-endpoint emphasis ring. R49\n already keeps endpoint nodes at opacity 1 while\n others dim when an edge is hovered, but the\n endpoints had no POSITIVE indicator — they just\n \"stayed bright\". An accent stroke at r=radius+7\n (just inside the halo's r=radius+8 bbox so we\n don't grow the overlap footprint) clearly says\n \"these are the two participants in this flow\".\n pointerEvents:none so the node hitbox stays alive.\n\n Round 182 / Loop: the ring used to mount/unmount\n on every edge hover, snapping despite the\n opacity transition on the style. Always-mount\n with opacity gated by hoveredEdgeEndpoints — same\n pattern R181 used for the legend pin ring. The\n transition now actually fires on hover entry\n and exit. 6th surface in the smooth-pin-mirror\n family (R165/R180/R181 + this round).\n\n strokeWidth=1.6 (was 1.5) deliberately escapes\n the R51 overlap-test sentinel `circle[stroke-\n width=\"1.5\"]`: an always-mounted r=radius+7 ring\n inside g[data-node] would otherwise be picked\n before the actual status ring (r=radius) by\n querySelector document order, breaking the\n test's node-bbox read. 1.5 → 1.6 is visually\n imperceptible (6.7% thicker) but the exact-\n string CSS attribute selector no longer\n matches. */}\n {(() => {\n const isEndpoint = hoveredEdgeEndpoints && hoveredEdgeEndpoints.has(session.alias);\n /* Round 233 / Loop: endpoint ring picks up a stroke-\n width thicken on edge-hover, completing the hover-\n elevation gesture across the whole edge surface.\n Pre-R233 hovering an edge eased the visible path\n stroke (R166) and lifted the badge r (R164) — but\n the two endpoint rings only faded IN (R182\n opacity gate). Now they ALSO thicken 1.6 → 2.4 on\n hover, in 180ms ease-out matching R164 badge lift.\n The endpoint nodes feel like they \"rise to meet\"\n the edge as the cursor approaches it, instead of\n just appearing.\n\n 1.6 and 2.4 both escape the R51 overlap-test\n sentinels (1.5 / 3 are reserved) — 2.4 sits\n comfortably between, visually 50% thicker than\n baseline so the gesture reads but the radius is\n unchanged (still r=radius+7) so geometry stays\n calm and the topo-overlap-test stays green. 9th\n surface in the hover-elevation family (R51\n nodes / R135 panels / R142 group boxes / R143-\n R144 rows / R164 edge badges / R177 hub ring /\n R229 group-label count brighten / R233 endpoint\n ring stroke-width). data-edge-endpoint-ring-\n stroke-width attr surfaces the chosen value for\n test introspection. */\n /* Round 442 / Loop: endpoint emphasis ring radius\n hover lift — r=radius+7 → radius+8 on isEndpoint,\n closing a 3-axis hover-elevation parity at endpoint\n ring scope (r + sw + opacity):\n opacity R182 0 → 0.85/0.9\n sw R233 1.6 → 2.4\n r R442 +7 → +8 ← this round\n Mirrors the 3-axis trios already established at\n hub hover-ring (R177/R370/R385) and edge badge\n (R164/R394/R395). Pre-R442 the endpoint ring\n faded in + thickened on edge-hover but its radius\n stayed locked at radius+7 — only the paint/weight\n axes lifted while the GEOMETRY stayed unchanged.\n +1px (~radius+7 to radius+8) gives a subtle outward\n pulse on hover without crowding the status ring\n (which sits at radius from R438 sw3.5 hover) or\n the halo (radius+8 from R440 opacity hover — the\n endpoint ring sits at the SAME radius as the halo\n but with stroke=cyan vs fill=status.halo so they\n don't visually collide). The transition list\n extends to include 'r 180ms ease-out' so the new\n axis eases under the same R233 cadence. SVG `r`\n on a <circle> uses CSS-property syntax for inter-\n polation (same idiom R197/R198 used on the\n legend swatch). data-edge-endpoint-ring-radius\n attr exposes the resolved value for tests. */\n const endpointR = isEndpoint ? radius + 8 : radius + 7;\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth={isEndpoint ? 2.4 : 1.6}\n opacity={isEndpoint ? (isLight ? 0.9 : 0.85) : 0}\n data-edge-endpoint-ring\n data-edge-endpoint-active={isEndpoint ? 'true' : 'false'}\n data-edge-endpoint-ring-stroke-width={isEndpoint ? 2.4 : 1.6}\n data-edge-endpoint-ring-radius={endpointR}\n data-edge-endpoint-ring-brightness={isEndpoint ? '1.15' : '1'}\n /* Round 639 / Loop — endpoint emphasis ring filter\n gains a stroke-tinted drop-shadow halo on\n isEndpoint. Sibling to R637 (chat-target ring,\n status.primary stroke) + R638 (status ring,\n status.primary stroke). R639 applies the same\n chromatic-identity pattern to the third per-node\n ring whose stroke colour is non-trivial:\n `pal.flowEdge` (cyber cyan ↔ light emerald).\n Pre-R639 the endpoint ring's hover filter was\n plain `brightness(1.15)` — no chromatic halo\n to telegraph \"this is an edge endpoint\" on the\n paint axis. The brightness lift made the cyan\n stroke read brighter but the surface lacked\n the ambient glow that R637 + R638 added to\n their respective ring tiers.\n R639 prepends `drop-shadow(0 0 2px\n ${pal.flowEdge}40)` so:\n cyber endpoint → cyan halo around cyan stroke\n light endpoint → emerald halo around emerald stroke\n halo always matches the existing stroke color\n since `pal.flowEdge` is the only colour source.\n 2px blur matches R638's status-ring halo (both\n rings sit at small radii: status at r=radius,\n endpoint at r=radius+7-8); R637's 3px is for\n the outermost r+14 chat-target ring.\n 0x40 alpha (~25 %) matches R637 + R638 so all\n three identity rings present a uniform halo\n intensity vocabulary.\n R51 sentinel safety: ring strokeWidth oscillates\n between 1.6 (rest) and 2.4 (hover) — neither\n matches reserved {1.5, 3}. Filter is paint-only,\n no bbox change. transition list already includes\n 'filter 180ms ease-out'.\n Chromatic identity completion family at the per-\n node ring surface (3 anchors now):\n R637 chat-target ring (status.primary, r+14)\n R638 status ring (status.primary, r=radius)\n R639 endpoint ring (pal.flowEdge, r+7→r+8) ← this\n data-edge-endpoint-ring-halo-color attr exposes\n the resolved halo color for tests. */\n data-edge-endpoint-ring-halo-color={isEndpoint ? pal.flowEdge : 'none'}\n data-edge-endpoint-ring-halo-layers={isEndpoint ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n r: `${endpointR}px`,\n /* R585 — endpoint emphasis ring gains filter\n brightness(1.15) when an edge endpoint\n activates. 24th anchor in per-element\n brightness family, and the FOURTH edge-tier\n paint layer:\n rail (R581)\n visible path (R582)\n flow particle (R583)\n endpoint ring (R585) ← this round\n Edge-tier brightness coverage closes at 4/4\n paint surfaces. The endpoint ring is the\n edge's affinity marker at the connected\n nodes — when an edge lights up, all four\n paint surfaces brighten together for a\n single coherent edge-active gesture spanning\n the curve + the node ends.\n Endpoint ring 4-axis active signature now:\n opacity R182 0 → 0.85/0.9\n sw R233 1.6 → 2.4\n r R442 +7 → +8\n brightness R585 — → 1.15 ← this round\n Plain brightness (no url-filter stack) since\n the endpoint ring has no rest-time filter\n attribute. Inline style.filter undefined at\n rest (no flicker; opacity=0 already hides\n the ring).\n\n R639 prepends `drop-shadow(0 0 2px\n ${pal.flowEdge}40)` to the filter chain so\n the halo color matches the stroke color —\n completes the chromatic identity at the\n endpoint-ring surface (3rd anchor in\n chromatic-identity family). */\n /* Round 644 / Loop — endpoint emphasis ring filter\n gains a SECOND drop-shadow layer at 4px blur +\n 0x20 alpha. Closes the multi-layer halo family\n at all 3 per-node identity rings:\n chat-target ring (r+14, OUTERMOST): 3px + 6px\n endpoint ring (r+7→+8, MID): 2px + 4px ← this\n status ring (r=R, INNERMOST): 2px + 4px\n Mid + inner rings share 2px + 4px stride\n because they sit at similar small radii; outer\n ring uses 3px + 6px for proportional scale.\n pal.flowEdge tint stays consistent across both\n layers (cyber #67e8f9 cyan / light #10b981\n emerald) — chromatic identity preserved. Alpha\n falloff 0x40 → 0x20 matches R642/R643 ambient\n gradient vocabulary. */\n filter: isEndpoint\n ? `drop-shadow(0 0 2px ${pal.flowEdge}40) drop-shadow(0 0 4px ${pal.flowEdge}20) brightness(1.15)`\n : undefined,\n transition: 'opacity 180ms ease-out, stroke-width 180ms ease-out, r 180ms ease-out, filter 180ms ease-out',\n } as React.CSSProperties}\n />\n );\n })()}\n {/* Round 167 / Loop: extend the node status-ring\n transition to include stroke-width — symmetric\n with R165 (pressure-bar width) and R166 (edge\n stroke-width). Pre-R167 only fill+stroke colors\n transitioned smoothly; stroke-width snapped from\n 3 (online) to 1.5 (offline) when a session\n transitioned. With stroke-width in the transition\n list the ring smoothly contracts as a node goes\n offline (or expands when it comes back).\n strokeDasharray stays binary (none ↔ '5 5')\n because dash values don't interpolate cleanly\n between continuous and discrete forms.\n Inline style replaces the Tailwind transition-\n [fill,stroke] className for stable arbitrary\n property compilation. Respects prefers-reduced-\n motion via R29 globals.css blanket override.\n data-node-status-ring exposes this circle for\n test probing — the overlap-test guard on\n stroke-width=\"3\"/\"1.5\" still works against the\n DOM attribute value (React-rendered, not\n interpolated). */}\n {(() => {\n /* Round 438 / Loop: status ring strokeWidth hover lift —\n when hoveredAlias matches, the node's status ring\n thickens by +0.5: online 3 → 3.5, offline 1.5 → 2.\n Same absolute delta as R435 hub-spoke (idle 1→1.25\n used Δ +0.25 because rest base was thinner; status\n ring's heavier rest values 1.5/3 need a bigger\n +0.5 to register as visible thickening).\n Status-ring axis joins the node-hover cue stack\n (now 9 layers including link surfaces):\n R26 group translateY -2px per-node geometry\n R217 stroke tint legendAccent per-node card\n R142 drop-shadow boost per-node card\n R427 alias letter-spacing per-node text\n R428 sub-text letter-spacing per-node text\n R429 body opacity 0.94 → 1.0 per-node card\n R430 hub-spoke α+ link to hub (paint)\n R435 hub-spoke sw+ link to hub (geo)\n R94 edge α 1.7× inter-node link (paint)\n R436 edge sw 1.15× inter-node link (geo)\n R437 flow-rail sw 1 → 1.5 edge paint-layer\n R438 status-ring sw +0.5 ring geometry ← this round\n R51 sentinel safety: rest values 3 / 1.5 unchanged\n so the overlap-test selector `circle[stroke-width=\n \"3\"]` / `circle[stroke-width=\"1.5\"]` inside\n g[data-node] still matches at rest. Hover values\n 3.5 / 2 are not in the reserved {1.5, 3} set so\n the selector wouldn't match them anyway; but the\n test runs WITHOUT hover so this never matters\n in practice. R167 stroke-width 300ms transition\n already in the style list eases the lift for\n free. data-node-status-ring-hovered exposes the\n gate for tests. */\n /* R621 — extend isRingHovered gate to include\n chatAlias === session.alias. Single conceptual\n change cascades across BOTH status-ring axes:\n R438 stroke-width 3 → 3.5 (online)\n 1.5 → 2 (offline)\n R584 brightness 1 → 1.15 (stacked w/\n url(#topo-glow))\n 7th anchor in chat-target-gated brightness family\n (R615 ring + R618 card + R616 alias + R617 sub +\n R619 avatar + R620 badge + R621 status ring).\n The chat partner's tier-colored status ring (the\n primary status identifier visible without hovering)\n now thickens + brightens on chat-target — its\n status color (green/teal/slate) reads as +15%\n more vivid at glance, distinguishing it from\n sibling rings across the canvas. */\n const isRingHovered = !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias);\n const ringStrokeWidth = isOnline\n ? (isRingHovered ? 3.5 : 3)\n : (isRingHovered ? 2 : 1.5);\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius}\n fill={isOnline ? pal.nodeFill.online : pal.nodeFill.offline}\n stroke={status.primary}\n strokeWidth={ringStrokeWidth}\n strokeDasharray={isOnline ? 'none' : '5 5'}\n filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}\n data-node-status-ring={status.label}\n data-node-status-ring-hovered={isRingHovered ? 'true' : 'false'}\n data-node-status-ring-stroke-width={ringStrokeWidth}\n data-node-status-ring-brightness={isRingHovered ? '1.15' : '1'}\n /* Round 638 / Loop — status ring filter gains a\n status-tinted drop-shadow halo on isRingHovered.\n Sibling pattern to R637 (chat-target ring at\n r+14 outer surface); R638 applies the same\n chromatic identity to the per-node status ring\n at r=radius (the INNERMOST identifying ring).\n Pre-R638 the status ring's hover filter was\n brightness-only (light) or `url(#topo-glow)\n brightness` (cyber+online) — the halo on cyber\n stayed cyan (the standard glow filter) regard-\n less of which status tier (green/teal/slate)\n the stroke painted. R638 stacks a status-tinted\n drop-shadow at the FRONT of the filter chain:\n `drop-shadow(0 0 2px ${status.primary}40)` so\n the halo color matches the stroke color.\n Now the ring's status identity reads chromatic-\n ally at BOTH the stroke AND the halo:\n working → green stroke + green halo on hover\n idle → teal stroke + teal halo on hover\n offline → slate stroke + slate halo on hover\n 2px blur is tighter than R637's 3px because the\n status ring sits at r=radius (much smaller than\n R637's r=radius+14) and a wider blur would bleed\n into the avatar surface inside.\n 0x40 alpha (~25 %) — matches R637's halo\n strength so both rings present the same chromat-\n ic halo intensity vocabulary.\n R51 sentinel safety: filter is paint-only, no\n bbox change. ringStrokeWidth still produces the\n sentinel values (1.5 offline / 3 online) which\n the overlap-test selector reads — filter\n affects neither the attribute nor the bbox.\n transition list already includes 'filter 300ms\n ease-out' (R584) so the halo COLOR easing on\n status flip composes with the existing R167\n stroke colour ease at the same cadence.\n R621 extended isRingHovered to also fire on\n chatAlias === session.alias — so chat-target\n nodes will also see the status-tinted halo on\n their status ring without further changes.\n Chromatic identity completion family at the\n per-node identity surface (sibling to R637):\n R637 chat-target ring (r+14, OUTERMOST)\n R638 status ring (r=radius, INNERMOST) ← this\n data-node-status-ring-halo-color attr exposes\n the resolved color for tests. */\n data-node-status-ring-halo-color={isRingHovered ? status.primary : 'none'}\n data-node-status-ring-halo-layers={isRingHovered ? '2' : '0'}\n style={{\n /* R584 — status ring gets brightness(1.15) on\n hover. 23rd anchor in per-element brightness\n family. Stacks with url(#topo-glow) on\n cyber+online to preserve the SVG glow filter;\n plain brightness on light or cyber+offline.\n Same R582/R583 stacked-filter pattern: inline\n style.filter overrides the attribute filter,\n stacked syntax preserves the glow on hover.\n\n Per-node hover signature now 10 layers (added\n to the R438 stack):\n R26 group translateY -2px\n R217 stroke tint\n R142 drop-shadow boost\n R427 alias letter-spacing\n R428 sub-text letter-spacing\n R429 body opacity 0.94 → 1.0\n R430 hub-spoke α+\n R435 hub-spoke sw+\n R438 status-ring sw +0.5\n R584 status-ring brightness(1.15) ← this round\n\n Per-element brightness family: 23 anchors.\n Stacked-filter sub-pattern: 17 anchors.\n\n R638 prepends `drop-shadow(0 0 2px\n ${status.primary}40)` to the stack so the halo\n color matches the stroke color (status.primary)\n — completes the chromatic identity at the\n innermost per-node identity surface. */\n /* Round 643 / Loop — status ring filter gains a\n SECOND drop-shadow layer at 4px blur + 0x20\n alpha. Mirrors R642's multi-layer halo pattern\n from the chat-target ring (3px + 6px) down to\n the innermost identity ring tier (2px + 4px).\n Per-node identity rings now share a uniform\n \"near + far\" halo vocabulary on hover/chat:\n chat-target ring (r+14): 3px + 6px (R637 + R642)\n status ring (r=R): 2px + 4px (R638 + R643) ← this\n Outer-ring uses 2x blur stride (3→6); inner-ring\n uses 2x stride (2→4) — same multiplier scaled to\n ring scale.\n Avatar circle sits at r ~ radius-2 (inside\n status ring); 4px outer halo extends paint\n past r=radius+2 outward, NOT inward toward\n avatar — bleed direction is safe.\n Both drop-shadows use the same status.primary\n tint at descending alphas (0x40 → 0x20), so\n chromatic identity preserved AND falloff\n reads as one organic ambient gradient. */\n filter: isRingHovered\n ? (isLight\n ? `drop-shadow(0 0 2px ${status.primary}40) drop-shadow(0 0 4px ${status.primary}20) brightness(1.15)`\n : (isOnline\n ? `drop-shadow(0 0 2px ${status.primary}40) drop-shadow(0 0 4px ${status.primary}20) url(#topo-glow) brightness(1.15)`\n : `drop-shadow(0 0 2px ${status.primary}40) drop-shadow(0 0 4px ${status.primary}20) brightness(1.15)`))\n : undefined,\n transition: 'fill 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, filter 300ms ease-out',\n }}\n />\n );\n })()}\n {/* v0.10.0 Hero 1+2 / §3.F server-health node-ring tint.\n When the host server this agent runs on is in the\n `red` tier (CPU/Mem/Disk worst-of ≥ 85% per\n classifyServer threshold), draw a faint amber outer\n halo at radius+8. strokeWidth=2.5 stays clear of the\n R51 overlap-test sentinels (1.5 = offline status ring,\n 3 = online status ring). pointerEvents:none so the\n halo can't intercept node clicks. Falls back silent\n when host telemetry hasn't shipped yet (hostHealthMap\n is empty until commhub returns telemetry). Composes\n with R209 hover-ring (which sits BETWEEN the avatar\n and this halo on hover — different radii). */\n }\n {(() => {\n const tier = hostHealthMap.get(session.server);\n if (tier !== 'red') return null;\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 8}\n fill=\"none\"\n stroke={isLight ? '#d97706' : '#fbbf24'}\n strokeWidth=\"2.5\"\n opacity=\"0.6\"\n data-node-server-health=\"red\"\n data-node-server-host={session.server}\n style={{\n pointerEvents: 'none',\n transition: 'stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n />\n );\n })()}\n {/* Issue #96: node \"avatar\" is now driven by the model\n vendor. Decision order:\n 1. ?brand=intern flag, or an intern-aliased node with\n no model field → 书生 coin (preserves #79).\n 2. vendor has a packaged logo asset → that logo image\n (intern-s1-* models land here via vendorForModel).\n 3. known vendor, logo asset not shipped yet → a\n vendor-tinted monogram (spec-mandated fallback).\n 4. unknown vendor / null model → the prefix-group\n hue-hashed initial (#83/#99 behaviour, unchanged). */}\n {(() => {\n const ar = Math.round((isOnline ? 14 : 10) * nodeScale);\n const size = radius * 2;\n const vendor = vendorForModel(session.model, session.runtime);\n const internByAlias = /书生|书小生|intern/i.test(session.alias);\n\n if (isIntern || internByAlias || vendor.logo) {\n /* Round 501 / Loop — vendor avatar inside node circles\n gains a hover-gated brightness lift. Pre-R501 the\n avatar <image> was the only per-node surface with\n NO hover treatment: R26 lifted the card, R242 tinted\n the card stroke, R427 spread the alias letter-\n spacing, R500 added the alias drop-shadow, R208\n lifted the runtime badge ring, R443 thickened\n the badge icon stroke, R177 brightened the\n halo — but the most visually-prominent element\n (the vendor logo / 书生 coin centred in each node)\n stayed paint-static. R501 closes the per-node\n hover-affordance arc by adding a 15% brightness\n lift on hover.\n Implementation: CSS filter: brightness(1.15)\n when hoveredAlias === session.alias. Pure paint\n axis on the <image> element — no geometry change,\n no bbox shift. Modern-browser supported (Chrome 64+\n / FF 56+ / Safari 9.1+).\n Hits 节点视觉 theme. data-node-avatar-hovered\n attr surfaces the gate for tests.\n Gated on !reducedMotion as a courtesy (brightness\n transition < ~50ms still feels instant; the gate\n avoids the transition cycle for a11y users). */\n /* R619 — extend isAvatarHovered gate to include\n chatAlias === session.alias. Single conceptual\n change cascades across ALL 4 avatar axes (R501\n brightness + R600 rotate + R602 scale + R605\n drop-shadow) for the image branch. The chat\n partner's vendor logo now performs the full\n 4-axis identification gesture, matching the\n brand 书生 logo's hub-center vocabulary.\n\n 5th anchor in chat-target-gated brightness\n family (R615 chat ring + R616 alias text +\n R617 sub-text + R618 label card + R619 avatar\n — image branch). The sibling fallback branches\n extend via isAvatarFallbackHovered below.\n Same hover treatments at this surface — gate\n union just widens the trigger condition. */\n const isAvatarHovered = !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias);\n return (\n <image\n href={vendor.logo ?? '/intern_avatar.png'}\n x={pos.x - size / 2}\n y={pos.y - size / 2}\n width={size}\n height={size}\n preserveAspectRatio=\"xMidYMid meet\"\n data-node-avatar={session.alias}\n data-node-avatar-hovered={isAvatarHovered ? 'true' : 'false'}\n data-node-avatar-rotate={isAvatarHovered ? '3' : '0'}\n data-node-avatar-scale={isAvatarHovered ? '1.05' : '1'}\n data-node-avatar-drop-shadow={isAvatarHovered ? `0 0 4px ${pal.legendAccent}99` : 'none'}\n style={{\n /* R600 milestone — node vendor avatar gains\n hover-rotate-3 via Tailwind v4 individual\n `rotate` CSS property. 6th anchor in hover-\n rotate idiom (R350 reset / R547 pill × /\n R549 brand logo / R576 fullscreen / R599\n runtime badge / R600 node avatar).\n\n Same +3° \"wobble awake\" tilt as R576\n fullscreen + R599 runtime badge — the\n vendor logo (Claude/OpenAI/etc.) at the\n center of every node gently tilts under\n cursor. Most visible per-node element gains\n a rotation signal on top of R501's\n brightness lift.\n\n transform-origin pinned to the node center\n (pos.x, pos.y) so the logo rotates around\n its own visual centre, not the <image>\n element's default bbox top-left.\n\n Per-node hover signature now 11 axes:\n R26 group translateY -2px\n R217 stroke tint\n R142 drop-shadow boost\n R427 alias letter-spacing\n R428 sub-text letter-spacing\n R429 body opacity 0.94 → 1.0\n R430 hub-spoke α+\n R435 hub-spoke sw+\n R438 status-ring sw +0.5\n R584 status-ring brightness(1.15)\n R600 avatar rotate 0 → 3deg ← this round\n\n Hover-rotate idiom family (6 anchors):\n R350 chrome reset icon -8°\n R547 chip pill × close (rotate)\n R549 brand 书生 logo (rotate)\n R576 chrome fullscreen icon +3°\n R599 node runtime badge +3°\n R600 node vendor avatar +3° ← milestone\n\n transition list extends 'rotate 200ms\n ease-out' alongside the existing 'filter\n 200ms ease-out' — both axes ride one beat. */\n rotate: isAvatarHovered ? '3deg' : '0deg',\n /* R602 — per-node avatar gains hover-scale-1.05.\n Mirrors R548 brand 书生 logo's scale-105 idiom\n at the per-node tier. Stacks with R600 rotate\n + R501 brightness so the avatar gets a 3-axis\n hover signature (scale + rotate + brightness)\n matching the brand-logo polish vocabulary.\n Tailwind v4 individual `scale` CSS property\n (banked R547 sibling to `rotate`) — independent\n of SVG <image>'s x/y/width/height attributes;\n composes cleanly with rotate. transform-origin\n shared with R600 — pivots around node center.\n Pure paint axis: SVG bbox attributes unchanged\n at rest, so overlap-test invariant holds (the\n probe reads attributes, not visual bbox post-\n CSS transform). Scale only fires on hover. */\n scale: isAvatarHovered ? '1.05' : '1',\n transformOrigin: `${pos.x}px ${pos.y}px`,\n /* R605 — per-node avatar gains drop-shadow on\n hover, stacked with R501 brightness(1.15).\n 4th hover axis on the avatar (brightness +\n rotate + scale + drop-shadow), bringing\n avatar coverage closer to brand 书生 logo's\n 5-axis signature (only missing R553 always-\n on breath, which would be visually noisy\n across 30 nodes).\n\n Color: pal.legendAccent at 0x99 alpha (~60%)\n — cyan/teal accent matches the edge-tier\n vocabulary (R534 edge-badge hover-glow,\n R478 recent-row pip glow, R479 group-label\n glow). The avatar joins the \"lit by accent\n on attention\" family at the per-node tier.\n\n 4px blur radius reads tight on the avatar's\n 14-28px footprint — smaller than R604 brand\n logo's 8px (40×40 footprint) for size\n proportionality.\n\n Same R582/R583 stacked-filter pattern.\n transition list already includes 'filter\n 200ms ease-out' from R501; no transition\n change needed.\n\n Per-node avatar hover signature now 4 axes:\n R501/R558 brightness 1 → 1.15\n R600/R601 rotate 0 → 3deg\n R602 scale 1 → 1.05\n R605 drop-shadow → pal.legendAccent ← this round */\n filter: isAvatarHovered\n ? `drop-shadow(0 0 4px ${pal.legendAccent}99) drop-shadow(0 0 8px ${pal.legendAccent}4c) brightness(1.15)`\n : undefined,\n transition: 'filter 200ms ease-out, rotate 200ms ease-out, scale 200ms ease-out',\n }}\n />\n );\n }\n /* Round 558 / Loop — closes the per-node avatar hover-\n affordance arc by extending R501's brightness lift\n (image-branch only) to the TWO remaining avatar\n variants: vendor monogram + prefix-group hue-hashed\n initial fallback. Pre-R558 only the vendor.logo\n image branch lifted on hover; the other two\n variants stayed paint-static under attention.\n\n Per-node avatar hover-brightness family (3 anchors,\n all gated on !reducedMotion && hoveredAlias matches):\n R501 vendor.logo image filter on <image>\n R558 vendor monogram filter on wrapping <g>\n R558 prefix-group fallback filter on wrapping <g>\n\n Implementation: each fallback branch returns a\n fragment with <circle> + <text> as siblings.\n Wrapping them in a single <g> with the filter\n centralizes the paint axis. Same brightness(1.15)\n value as R501 for cross-branch consistency. Same\n transition cadence (filter 200ms ease-out).\n\n data-node-avatar-monogram-hovered + -fallback-\n hovered attrs surface the gates for tests. */\n /* R619 sibling — extend isAvatarFallbackHovered gate to\n include chatAlias === session.alias. Cascades the\n same 4 axes to vendor monogram + prefix-group\n fallback branches. The chat partner's avatar\n identification gesture is now consistent across\n all 3 vendor-detection branches (image / monogram /\n fallback) — same as R600/R601 rotate closure and\n R501/R558 brightness closure. */\n const isAvatarFallbackHovered = !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias);\n if (vendor.id !== 'unknown') {\n // Known model house, logo asset not in public/vendors/\n // yet — vendor-tinted monogram stands in.\n /* Round 283 / Loop: monogram circle strokeWidth bumps\n 1 → 1.5 per Vincent 5216 \"书生头像风格延续 — 其他\n vendor 头像 plain text/abbreviation 比书生差, polish\n 升级\". Without real vendor logo PNG/SVG assets in\n public/vendors/, the monogram is the visual stand-\n in; bumping its ring weight from 1 to 1.5 narrows\n the visual-quality gap with the 书生 image avatar\n (which is a designed PNG, naturally more\n substantial). The 1px → 1.5px stroke is the same\n weight increment R268 used on the chrome-strip\n border unification — small but perceptible. The\n prefix-group fallback (line ~5172) stays at\n strokeWidth=1 since that's for UNKNOWN vendors\n where less visual weight signals \"we don't know\n what this is\" appropriately. */\n return (\n <g\n data-node-avatar-monogram={session.alias}\n data-node-avatar-monogram-hovered={isAvatarFallbackHovered ? 'true' : 'false'}\n data-node-avatar-monogram-rotate={isAvatarFallbackHovered ? '3' : '0'}\n data-node-avatar-monogram-scale={isAvatarFallbackHovered ? '1.05' : '1'}\n data-node-avatar-monogram-drop-shadow={isAvatarFallbackHovered ? `0 0 4px ${pal.legendAccent}99` : 'none'}\n style={{\n /* R601 — vendor-monogram fallback gains hover-\n rotate-3 mirroring R600's image-branch\n closure. Per-node avatar rotate coverage\n now 2/3 (image R600, monogram R601 here;\n prefix-group fallback completes 3/3 below).\n Same R558 closure-of-arc semantics applied\n to the rotate axis — what brightness did\n for the avatar 3-branch arc (R501 image +\n R558 monogram + R558 fallback), rotate\n now does (R600 + R601 monogram + R601\n fallback).\n transform-origin pinned to node center\n (pos.x, pos.y) — same idiom as R600. */\n rotate: isAvatarFallbackHovered ? '3deg' : '0deg',\n /* R602 sibling — vendor monogram fallback\n gains hover-scale-1.05 to match the image\n branch above. Per-node avatar scale coverage\n now 2/3 (image R602, monogram R602 here;\n prefix-group fallback completes 3/3 below). */\n scale: isAvatarFallbackHovered ? '1.05' : '1',\n transformOrigin: `${pos.x}px ${pos.y}px`,\n /* R605 sibling — monogram fallback gains\n stacked drop-shadow + brightness filter\n on hover, mirroring the image branch above. */\n filter: isAvatarFallbackHovered\n ? `drop-shadow(0 0 4px ${pal.legendAccent}99) drop-shadow(0 0 8px ${pal.legendAccent}4c) brightness(1.15)`\n : undefined,\n transition: 'filter 200ms ease-out, rotate 200ms ease-out, scale 200ms ease-out',\n }}\n >\n <circle cx={pos.x} cy={pos.y} r={ar} fill={vendor.mono.bg} stroke={vendor.mono.ring} strokeWidth=\"1.5\" />\n {/* Round 284 / Loop: known-vendor monogram letter\n swaps fontFamily monospace → system sans-serif.\n Continuation of R283 Vincent 5216 \"vendor 头像\n polish 升级\". A single centered letter does not\n benefit from monospace's digit-alignment\n property — its only effect at this scale is to\n land a slightly thinner, more code-text-like\n glyph. The system stack ('-apple-system',\n 'BlinkMacSystemFont', 'Segoe UI', 'Inter',\n 'sans-serif') picks the OS-preferred designed\n sans-serif, which renders a fuller, more\n \"badge-mark\" letterform — narrowing the\n visual-quality gap with the 书生 PNG (which is\n a hand-designed image). data-monogram-letter\n exposes the element for test probing.\n\n The prefix-group fallback at line ~5197\n INTENTIONALLY stays on monospace — same\n contrast pattern R283 established for ring\n stroke: \"designed glyph\" = known vendor,\n \"code text\" = unknown vendor bucket. */}\n <text\n x={pos.x} y={pos.y} dy=\"0.34em\" textAnchor=\"middle\"\n fill={vendor.mono.text} fontSize={ar}\n fontFamily=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif\"\n fontWeight=\"700\"\n data-monogram-letter={vendor.initial}\n >\n {vendor.initial}\n </text>\n </g>\n );\n }\n // Round 106 (issue #83): hue keyed to the prefix group,\n // not the full alias — every 通信* node shares one color.\n const c = aliasAvatarColors(groupKeys[session.alias] || session.alias);\n return (\n <g\n data-node-avatar-fallback={session.alias}\n data-node-avatar-fallback-hovered={isAvatarFallbackHovered ? 'true' : 'false'}\n data-node-avatar-fallback-rotate={isAvatarFallbackHovered ? '3' : '0'}\n data-node-avatar-fallback-scale={isAvatarFallbackHovered ? '1.05' : '1'}\n data-node-avatar-fallback-drop-shadow={isAvatarFallbackHovered ? `0 0 4px ${pal.legendAccent}99` : 'none'}\n style={{\n /* R601 — prefix-group hue-hashed initial fallback\n gains hover-rotate-3 closing per-node avatar\n rotate coverage at 3/3 branches (image R600 +\n monogram R601 + fallback R601 here).\n Same idiom across all 3 branches: rotate 3deg\n on hover, transform-origin pinned to node\n center, transition 'rotate 200ms ease-out'\n alongside the existing 'filter 200ms' filter\n transition from R558. Whatever the vendor-\n detection branch resolves to (image / monogram\n / hue-hashed initial), the user gets the same\n +3° wobble-awake gesture on alias hover. */\n rotate: isAvatarFallbackHovered ? '3deg' : '0deg',\n /* R602 sibling — prefix-group hue-hashed fallback\n gains hover-scale-1.05 closing per-node avatar\n scale coverage at 3/3 branches (image + monogram\n + fallback all share the same scale-1.05 +\n rotate-3deg + brightness-1.15 hover signature). */\n scale: isAvatarFallbackHovered ? '1.05' : '1',\n transformOrigin: `${pos.x}px ${pos.y}px`,\n /* R605 sibling — prefix-group hue-hashed fallback\n gains stacked drop-shadow + brightness filter\n on hover. Closes per-node avatar drop-shadow\n coverage at 3/3 branches (image + monogram +\n fallback all share the same stacked filter). */\n filter: isAvatarFallbackHovered\n ? `drop-shadow(0 0 4px ${pal.legendAccent}99) drop-shadow(0 0 8px ${pal.legendAccent}4c) brightness(1.15)`\n : undefined,\n transition: 'filter 200ms ease-out, rotate 200ms ease-out, scale 200ms ease-out',\n }}\n >\n <circle cx={pos.x} cy={pos.y} r={ar} fill={c.bg} stroke={c.ring} strokeWidth=\"1\" />\n <text\n x={pos.x}\n y={pos.y}\n dy=\"0.34em\"\n textAnchor=\"middle\"\n fill={c.text}\n fontSize={ar}\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n >\n {aliasInitial(session.alias)}\n </text>\n </g>\n );\n })()}\n {/* Issue #96: runtime badge — small corner glyph marking the\n execution shell (CLI / SDK / HTTP API). Sits bottom-right\n of the avatar; colours kept off the working/idle/offline\n status hues. Absent when the node reports no runtime. */}\n {(() => {\n const rt = runtimeIdentity(session.runtime);\n if (!rt) return null;\n const br = isOnline ? 7 : 5.5;\n const bx = pos.x + radius * 0.72;\n const by = pos.y + radius * 0.72;\n const icon = br * 2 * 0.62;\n // Round 208 / Loop: runtime badge joins the micro-lift\n // radius-axis family. R177 grew the hub ring on hover\n // (r 14→17); R197 grew the legend swatch (r 5.5→7);\n // R208 closes the trio at per-node grain — the runtime\n // badge (CLI/SDK/HTTP indicator at avatar bottom-right)\n // pops r 7→8 (online) / 5.5→6.5 (offline) when the\n // parent node is hovered, with stroke 1.5→2 for\n // matching emphasis. R26 already lifts the label 1.5px\n // and R194 elevates its drop-shadow; R208 gives the\n // runtime indicator its own hover acknowledgement so\n // every per-node surface participates in the gesture.\n // CSS r-as-property + stroke-width are transitionable\n // (same support matrix R197/R198/R199 leveraged:\n // Chrome ≥95 / Safari ≥16 / FF ≥70). data-runtime-\n // badge-active exposes the gate for tests.\n /* R620 — extend isNodeActive gate to include\n chatAlias === session.alias. Single conceptual\n change cascades across ALL 6 runtime badge axes:\n R208 ring r 7 → 8\n R208 ring sw 1.5 → 2\n R443 icon sw 2.4 → 2.8\n R559 drop-shadow → rt.color halo\n R586 brightness 1 → 1.15\n R599 outer rotate 0 → 3deg\n 6th anchor in chat-target-gated brightness family\n (R615 chat ring + R618 card + R616 alias + R617\n sub-text + R619 avatar + R620 runtime badge).\n The chat partner's runtime badge now performs the\n same densest-per-node hover gesture — full 6-axis\n cascade on chat-target match. */\n const isNodeActive = !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias);\n return (\n /* Round 559 / Loop — runtime badge outer <g> picks up\n a drop-shadow glow on node hover, using the runtime's\n own identity color (rt.color, hex). 16th anchor in\n the drop-shadow visual-polish family. Pairs with\n existing R208 ring r-lift + R443 icon sw-lift for\n a 3-axis runtime-badge hover signature now spanning\n geometry + stroke + paint glow:\n R208 ring r 7 → 8 (online)\n R208 ring sw 1.5 → 2\n R443 icon sw 2.4 → 2.8\n R559 outer filter none → drop-shadow(rt.color) ← this round\n Filter on the OUTER <g> applies uniformly to both\n the ring <circle> and the inner icon <path> —\n single paint-axis lift covers both layers in one\n motion-coherent gesture.\n Hue: `${rt.color}99` hex+alpha (60%) — rt.color is\n 6-digit hex (#a78bfa / #38bdf8 / #34d399 / #fbbf24\n per lib/vendorIdentity.ts), so hex+alpha concat is\n safe (banked R541 pattern: hex sources use hex+\n alpha; only hsl/color()/dynamic sources need color-\n mix).\n 2px blur reads tight on a small badge (r=7 online\n / r=5.5 offline). transition list adds 'filter\n 150ms ease-out' matching the R208 ring r/sw cadence\n at this surface.\n Drop-shadow visual-polish family extension (16\n anchors now): R476/R477/R478/R479/R480/R481 +\n R500/R532-R536/R537/R538/R540/R543-R546/R550 +\n R559 (this round).\n data-runtime-badge-glow attr surfaces the gate\n for tests. */\n <g\n data-runtime-badge-glow={isNodeActive ? 'true' : 'false'}\n data-runtime-badge-halo-layers={isNodeActive ? '2' : '0'}\n data-runtime-badge-brightness={isNodeActive ? '1.15' : '1'}\n data-runtime-badge-rotate={isNodeActive ? '3' : '0'}\n style={{\n pointerEvents: 'none',\n /* R599 — runtime badge gains hover-rotate-3.\n 5th anchor in hover-rotate idiom (R350 reset\n -8° / R547 pill × / R549 brand logo / R576\n fullscreen +3° / R599 runtime badge +3°).\n Same +3° tilt as R576 fullscreen icon — at the\n 14×14 badge scale, a 3° rotation reads as a\n subtle \"wobble awake under attention\" gesture\n that doesn't overwhelm the small surface.\n Applied via CSS individual `rotate` property\n (Tailwind v4 banked R547 — independent of SVG\n transform attribute, composes cleanly with\n the inner <g>'s translate+scale transform).\n transform-origin pinned to the badge center\n (bx, by) so rotation pivots around the\n visual centre, not the outer <g>'s default\n bbox corner — keeps the ring and icon spinning\n in place rather than orbiting offset.\n Runtime badge hover signature now 6 axes —\n densest per-node element in TopoGraph:\n R208 ring r 7 → 8\n R208 ring sw 1.5 → 2\n R443 icon sw 2.4 → 2.8\n R559 outer filter none → drop-shadow(rt.color)\n R586 outer filter stack brightness(1.15)\n R599 outer rotate 0 → 3deg ← this round\n transition list extends with 'rotate 150ms\n ease-out' matching R586/R559 cadence at this\n surface. Six axes all ride one 150ms beat.\n Hover-rotate idiom family extension\n (5 anchors): R350 / R547 / R549 / R576 / R599. */\n rotate: isNodeActive ? '3deg' : '0deg',\n transformOrigin: `${bx}px ${by}px`,\n /* R586 — runtime badge outer <g> stacks\n brightness(1.15) onto the existing R559\n drop-shadow on node hover. 25th anchor in\n per-element brightness family, 18th in\n stacked-filter sub-pattern.\n\n Runtime badge hover signature now CLOSED\n at 4 axes (geometry + stroke + paint glow\n + paint brightness):\n R208 ring r 7 → 8\n R208 ring sw 1.5 → 2\n R443 icon sw 2.4 → 2.8\n R559 outer filter none → drop-shadow(rt.color)\n R586 outer filter stack brightness(1.15) ← this round\n\n The drop-shadow + brightness stack is the\n banked R564/R570 \"halo + glow\" pattern —\n drop-shadow paints the colored halo, brightness\n lifts the underlying paint (ring stroke +\n icon path both gain ~15% luminance). Single\n CSS filter chain on the outer <g> covers\n both child layers uniformly.\n\n Same R208/R443/R559 150ms cadence preserved\n via the existing transition. */\n /* Round 656 / Loop — runtime badge filter gains a\n SECOND outer drop-shadow at 4px blur + 0x4c\n alpha (half R559 inner 0x99). 15th anchor in\n multi-layer halo family — first per-node badge\n anchor.\n Inner R559: 2px + 0x99 alpha (~60%), rt.color\n tint (vendor runtime palette)\n Outer R656: 4px + 0x4c alpha (~30%), same tint\n Same R642-R655 0.5x falloff vocabulary + 2x\n blur stride (2 → 4). The badge at r ~ 7-8\n sits at innermost-ring scale; 2+4 stride\n matches R643/R644/R645 small-element scope.\n rt.color tint (per-runtime hue: claude orange\n / cli-default cyan / etc.) preserved across\n both layers — chromatic identity uniform. */\n filter: isNodeActive\n ? `drop-shadow(0 0 2px ${rt.color}99) drop-shadow(0 0 4px ${rt.color}4c) brightness(1.15)`\n : undefined,\n transition: 'filter 150ms ease-out, rotate 150ms ease-out',\n }}\n >\n <circle\n cx={bx} cy={by} r={br}\n fill={pal.containerBg}\n stroke={rt.color}\n strokeWidth=\"1.5\"\n data-runtime-badge={session.alias}\n data-runtime-badge-active={isNodeActive ? 'true' : 'false'}\n style={{\n r: isNodeActive ? `${br + 1}px` : `${br}px`,\n strokeWidth: isNodeActive ? '2px' : '1.5px',\n transition: 'r 150ms ease-out, stroke-width 150ms ease-out',\n } as React.CSSProperties}\n />\n {/* Round 443 / Loop: runtime badge inner-icon\n strokeWidth lift on node hover — 2.4 → 2.8 on\n isNodeActive. Pre-R443 the outer badge ring\n lifted (R208 r + sw both grow on hover) but\n the inner icon path stayed locked at sw=2.4.\n The two layers of the runtime badge were\n out of phase: ring thickened, icon stayed\n thin. R443 closes the 2-axis hover signature\n on the badge so both ring and icon lift\n together. +0.4 absolute delta matches the\n R208 ring's +0.5 sw delta (badge ring 1.5 →\n 2.0 absolute), proportional to the icon's\n heavier base of 2.4. Pure paint axis;\n strokeLinecap='round' + strokeLinejoin='round'\n preserved. transition list extends to include\n 'stroke-width 150ms ease-out' matching R208\n outer-ring cadence. data-runtime-badge-icon\n + -active attrs exposed for tests. */}\n <g transform={`translate(${bx - icon / 2} ${by - icon / 2}) scale(${icon / 24})`}>\n <path\n d={rt.iconPath}\n fill=\"none\"\n stroke={rt.color}\n strokeWidth={isNodeActive ? '2.8' : '2.4'}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n data-runtime-badge-icon={session.alias}\n data-runtime-badge-icon-active={isNodeActive ? 'true' : 'false'}\n data-runtime-badge-icon-stroke-width={isNodeActive ? '2.8' : '2.4'}\n style={{ transition: 'stroke-width 150ms ease-out' }}\n />\n </g>\n </g>\n );\n })()}\n {hasUnread(session.alias) && (\n <g pointerEvents=\"none\" data-node-unread={session.alias}>\n <circle\n cx={pos.x + radius * 0.74}\n cy={pos.y - radius * 0.74}\n r={Math.max(5, radius * 0.22)}\n fill=\"#ef4444\"\n stroke={pal.containerBg}\n strokeWidth=\"2\"\n />\n <circle\n cx={pos.x + radius * 0.74}\n cy={pos.y - radius * 0.74}\n r={Math.max(8, radius * 0.34)}\n fill=\"none\"\n stroke=\"#ef4444\"\n strokeOpacity=\"0.22\"\n strokeWidth=\"1.5\"\n />\n </g>\n )}\n {/* Round 294 / Loop: per-node \"working\" pulse dot retired.\n The pulse was R24's per-node working indicator — a\n small green circle at the top of each working node,\n SMIL-animated opacity 1→0.25→1. After R278 retired the\n working halo, R279 retired arrival ping + dispatch\n pulse, R280 retired backdrop spokes, the pulse dot\n was the last surviving per-node SMIL animation in\n the original \"working = breathing\" visual family.\n Status info is preserved through 4 redundant signals:\n status ring green color (R167), label sub-text\n 'working' (R211), chip-row 'X working' count (top\n of canvas), hub centre digit (R130). With 30+\n working nodes on a real fleet, 30 simultaneous SMIL\n pulses add cognitive load with zero new information.\n Same R275-R281/R290/R291 减法 family idiom — the\n last 'wiggling per-node decoration' retires. Gated\n via `{false && ...}` per the R276/R278/R279/R280\n rollback-friendly pattern; the block stays in the\n file documented + dead-coded so future readers see\n the retired pulse-dot rationale + can A/B-restore\n it by flipping the gate. */}\n {false && (() => {\n const sse = sseCountFor ?? 0;\n const dur = sse >= 4 ? '0.7s' : sse >= 2 ? '0.9s' : '1.2s';\n const visible = session.status === 'working';\n return (\n <g\n data-pulse-wrapper={session.alias}\n data-pulse-visible={visible ? 'true' : 'false'}\n style={{\n opacity: visible ? 1 : 0,\n transition: 'opacity 300ms ease-out',\n pointerEvents: 'none',\n }}\n >\n <circle cx={pos.x} cy={pos.y - (radius - 6)} r=\"2.5\" fill={pal.flowParticle} data-pulse-dur={dur} opacity={reducedMotion ? 0.6 : undefined}>\n {!reducedMotion && (\n <animate attributeName=\"opacity\" values=\"1;0.25;1\" dur={dur} repeatCount=\"indefinite\" />\n )}\n </circle>\n </g>\n );\n })()}\n\n {/* Round 98 (issue #61): label rect 124px → 100px.\n Round 109/110 (Vincent P0): full opaque card below the\n density threshold / on hover / when zoomed; otherwise a\n lightweight plain-text alias that keeps every node\n labelled without an opaque box covering its neighbours.\n Round 15 / Loop: when nodeScale=S the node shrinks 30%\n but the label card was staying full-size, so the label\n visually outweighed the small node. Tighten the card\n frame, alias / sub fontSize, drop-offset and truncate\n length specifically for S; M and L keep their existing\n sizes (M ≈ L for labels by design — the S user is the\n one who explicitly asked for a denser view). */}\n {(() => {\n const isSmall = nodeScale < 0.8;\n const cardW = isSmall ? 88 : 100;\n const cardH = isSmall ? 36 : 42;\n const cardTopY = isSmall ? -12 : -14;\n const aliasFs = isSmall ? 11 : 12;\n const subFs = isSmall ? 8 : 9;\n const subY = isSmall ? 15 : 17;\n const dropY = isSmall ? 18 : 22;\n const fullMax = isSmall ? 11 : 12;\n const denseFs = isSmall ? 9 : 10;\n const denseDrop = isSmall ? 12 : 14;\n // Round 26 / Loop: micro-lift the label group on hover —\n // 1.5 px upward, 200 ms ease. Pairs with the Round 18\n // group-box hover-accent treatment to give the same\n // \"this is the focused element\" feedback at the per-\n // node level. CSS transform stacks onto the SVG\n // positioning transform attribute (SVG 2 cascade);\n // bbox is unchanged at rest, so the overlap-test gate\n // continues to see the geometric layout it expects.\n return showFullLabel ? (\n <g transform={`translate(${pos.x}, ${pos.y + radius + dropY})`} style={{ pointerEvents: 'none' }}\n className=\"transition-transform duration-200 group-hover:-translate-y-[1.5px]\">\n {/* Round 194 / Loop: label card picks up a subtle\n drop-shadow that intensifies when the node is\n hovered — pairs the existing R26 1.5px lift\n with physical weight. Pre-R194 the card lifted\n 1.5 px on hover but had no shadow follow, so\n the gesture read as \"card translated\" rather\n than \"card rose off the canvas\". Adding a\n baseline shadow at rest (1px/2px blur) plus a\n deeper hover state (3-4px/8-12px blur) makes\n the elevation feel earned.\n Same R57/R135 hover-elevation idiom that\n panels already use, now extended to per-node\n label cards. data-node-label-card-elevation\n ('idle'/'hover') exposes the state for tests.\n Filter is theme-aware (light: slate alpha;\n dark: black alpha) so the shadow stays visible\n against both surfaces. transition: filter\n 220ms ease-out matches R135's panel-elevation\n duration so a hovered node + a hovered panel\n fade at the same rhythm. Reduced-motion users\n collapse to the rest-state shadow only — no\n hover differentiation. Per-node filter is\n gated to showFullLabel which itself is gated\n to non-dense fleets (≤16 nodes) or hovered/\n zoomed-in branches, so the cost stays bounded\n (~20-30 cards max). */}\n {/* Round 217 / Loop: label card stroke tints to\n legendAccent (cyan) on parent-node hover,\n adding a 3rd hover-feedback channel alongside\n R26 1.5px lift + R194 drop-shadow elevation.\n The card now responds at three layers when its\n parent node is hovered: lift (motion) → shadow\n (depth) → stroke (color tint). All three are\n gated by the same hoveredAlias === session\n .alias state so they ease in unison. transition\n list extends `stroke 220ms ease-out` alongside\n R194's existing filter 220ms — single pair of\n eyes on the card reads \"this is the focused\n element\" via three independent channels. Pin\n + chat states don't compete: pinning a node\n opens chat (R136) but doesn't drive hovered\n Alias, so this stroke tint is exclusively a\n pointer-on-target signal. */}\n {/* Round 246 / Loop: label card chrome picks up\n fill + opacity in its transition list. R142\n already eased filter (drop-shadow) + stroke\n (R217 cyan tint on hover); the rect's fill\n (pal.labelBox.fill: cyber #060608 ↔ light\n #ffffff) and theme-derived opacity (0.94\n cyber / 1 light) still snapped on theme\n toggle. R211 already closed the alias/sub\n text-fill snap on the same card; R246\n closes the chrome-fill snap on the rect\n BEHIND that text, so the whole card\n (background + text) transitions as one\n unit through every theme switch. Same\n 220ms cadence the existing filter/stroke\n pair uses — coordinated 4-property easing\n across the card. */}\n {/* Round 411 / Loop: node label card rx 6 → 8.\n Pre-R411 the per-node label card painted at\n rx=6, sitting one tier BELOW the R332/R375/\n R376 compact-chrome tier (rx=8). Inside the\n corner-radius cascade family the cards used\n to be the only \"smaller\" tier — but the\n label card is a content-bearing surface\n (alias + sub text + ring), not a sub-\n element decoration. R411 lifts rx=6 → 8\n to align with the compact-chrome / segmented-\n control tier so all \"compact card\" surfaces\n read with the same corner radius.\n Corner-radius cascade (8 anchors now):\n R330 canvas rx 12 (root)\n R331 panels rx 10 (recent-signal, legend)\n R332 minimap container rx 8 (compact chrome)\n R375 Layout-toggle rx 8 (segmented control)\n R376 nodeSize/zoom rx 8 (segmented control)\n R390 hover-detail rx 10 (panel)\n R393 minimap viewport rx 2 (sub-element)\n R411 node label card rx 6 → 8 (compact card, this round)\n Pure paint — rx grows the corner curve\n inward without changing the card's outer\n cardW × cardH bbox (cardW=92/cardH=22 for\n standard nodes per R23 / R187 sizing). R217\n hover-stroke cyan tint + R142 drop-shadow\n + R246 fill+opacity 220ms transition list\n + R211 alias/sub text-fill ease all\n preserved. data-node-label-card-rx attr\n exposes the value for tests. */}\n {/* Round 429 / Loop: node label-card body opacity\n 0.94 → 1.0 on hover (cyber theme). Sibling\n treatment to R348 panel rect opacity lift —\n 0.92 → 0.97 cyber / 0.97 → 1.0 light at the\n panel scope. Pre-R429 the cyber theme card\n sat at 0.94 always; on hover R217 tinted the\n stroke + R142 grew the drop-shadow + R26\n lifted the group + R427/R428 spaced the text\n but the rect itself never solidified —\n the card glowed brighter through the\n shadow but the body alpha gap (6 pct) stayed\n fixed. R429 lifts the body to full alpha on\n hover so the card reads as a confidently\n present surface under the cursor (matching\n the panel-pair pattern). Light theme stays\n at 1.0 in both states (already maxed). R246\n transition list already covers opacity 220ms\n so the lift eases for free. R217 stroke tint\n + R142 drop-shadow + R211 fill ease all\n preserved (additive opacity branch only). */}\n <rect\n x={-cardW / 2} y={cardTopY} width={cardW} height={cardH} rx=\"8\"\n fill={pal.labelBox.fill}\n stroke={!reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias)\n ? pal.legendAccent\n : pal.labelBox.stroke}\n opacity={\n !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias)\n ? 1\n : (isLight ? 1 : 0.94)\n }\n data-node-label-card={session.alias}\n data-node-label-card-rx=\"8\"\n data-node-label-card-elevation={\n !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias) ? 'hover' : 'idle'\n }\n data-node-label-card-chat-target={chatAlias === session.alias ? 'true' : 'false'}\n data-node-label-card-brightness={!reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias) ? '1.15' : '1'}\n data-node-label-card-halo-layers={!reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias) ? '2' : '0'}\n style={{\n /* R613 — per-node label card stacks brightness\n (1.15) onto R142's hover drop-shadow. Same\n banked R582/R583 stacked-filter pattern.\n 4th paint axis on the card's hover signature:\n R217 stroke tint → pal.legendAccent\n R211 fill ease 200ms\n R142 drop-shadow rest → hover (deeper)\n R613 brightness 1 → 1.15 ← this round\n\n The card's bg fill (pal.labelBox.fill: dark\n navy on cyber / white on light) lifts +15%\n alongside the existing drop-shadow\n elevation — cyber theme reads as \"card lit\n warmer under attention\" since the dark fill\n gains visible luminance from the +15%\n multiplication. Light theme bg is already\n near-white so brightness barely shifts the\n rect, but the cyan-tinted stroke (R217)\n still lifts visibly through the brightness\n filter.\n\n Drop-shadow exists at BOTH rest + hover\n tiers (R142); brightness only stacks on\n the hover tier. Rest stays at plain DS.\n\n Existing 'filter 220ms ease-out' transition\n covers brightness at the same cadence. */\n /* R618 — extends R142 hover drop-shadow + R613\n stacked brightness gate to ALSO fire on\n chatAlias === alias. 4th anchor in chat-\n target-gated brightness family (R615 chat\n ring + R616 alias text + R617 sub-text +\n R618 label card).\n\n Pre-R618 the R217 comment (\"pin + chat\n states don't compete... this stroke tint\n is exclusively a pointer-on-target signal\")\n was authored when chat-target had no\n visual response. Post-R616/R617 the chat\n partner's alias + sub-text both brighten,\n but the label CARD around them stayed\n at idle elevation — visual disconnect\n between the lit text and its plain card.\n R618 closes that gap.\n\n Card 4-axis hover signature now also\n responds on chat-target:\n R217 stroke tint → pal.legendAccent\n R211 fill ease 200ms\n R142 drop-shadow rest → hover (deeper)\n R613 brightness 1 → 1.15\n\n All 4 axes now share gate union\n (hover || chat-target). Chat partner reads\n as fully identified across:\n R615 chat ring (glow)\n R618 card (lift + tint + glow + brightness)\n R616 alias text (brighter)\n R617 sub-text (brighter) */\n /* R694 — per-node label card backdrop joins the\n multi-layer halo family on the hover/chat\n branch. Pre-R694 hover/chat filter chain was\n `drop-shadow(0 Ypx Nblur elevation) brightness\n (1.15)` — vertical elevation + paint lift,\n no radial halo. R694 prepends 2-layer radial\n drop-shadow at pal.legendAccent tint with\n 3+6 stride and alpha 80/40 — radial halo\n echoes the R217 hover-stroke (pal.legendAccent)\n outward in soft glow. Sibling pattern to R693\n hover-detail card (same panel-tier 3+6\n stride, same pal.legendAccent tint).\n Rest branch unchanged — only the hover/chat\n branch gets the radial halo so the rest state\n stays at minimal ground shadow.\n Full hover/chat filter chain (5-layer):\n drop-shadow(0 0 3px ${pal.legendAccent}80) near radial\n drop-shadow(0 0 6px ${pal.legendAccent}40) far radial\n drop-shadow(0 3-4px Nblur elevation) vertical lift\n brightness(1.15) paint lift\n 50th anchor in multi-layer halo family —\n first per-node-card anchor. data-node-label-\n card-halo-layers attr exposes the gate. */\n filter: !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias)\n ? (isLight\n ? `drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) drop-shadow(0 3px 8px rgba(15,23,42,0.20)) brightness(1.15)`\n : `drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) drop-shadow(0 4px 12px rgba(0,0,0,0.60)) brightness(1.15)`)\n : (isLight\n ? 'drop-shadow(0 1px 2px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 1px 2px rgba(0,0,0,0.30))'),\n transition: 'filter 220ms ease-out, stroke 220ms ease-out, fill 220ms ease-out, opacity 220ms ease-out',\n }}\n />\n {/* Round 211 / Loop: alias + sub text fill eases on\n status flip, matching R167 status-ring fill 300ms.\n Pre-R211 a node going working → idle → offline made\n the ring smoothly recolor (R167) while the label\n card's text snap-cut to the new tier hue in a\n single frame — the node \"transitioned its ring,\n flipped its text\". 300ms inline transition syncs\n all four label-card fills (alias, sub, ring fill,\n ring stroke) to the same beat so the node reads\n as one coordinated status change.\n data-node-alias-text exposes the gate for tests. */}\n {/* Round 305 / Loop: node alias label text picks\n up the pin-signature letter-spacing family\n (R219 / R220) when the node is the chat\n target. The alias is the per-node identity\n label inside the label card; when chat is\n open targeting this node, R242 already adds\n a cyan-tint stroke to the card. R305 brings\n the alias text into the same pin-signature\n family — letter-spacing 0px → 0.5px when\n chatAlias === session.alias. Same vocabulary\n R219 established for recent-row text (line\n ~6354), legend-row text (~6881), and the\n R220 edge-badge text (~4327, with 0.4 for\n hot/pin). Now the per-node alias has its\n own pin signature when chat is open on it.\n transition list extends 'letter-spacing\n 200ms ease-out' so it eases alongside the\n existing 300ms fill transition. */}\n {/* Round 427 / Loop: extend the node alias label\n letter-spacing family to a 3-tier scale —\n rest 0px → hover 0.3px → chat-target 0.5px.\n Pre-R427 the alias text shifted only when\n chat was actively pinned (R305); pure node-\n hover left the text dead-typographic while\n the surrounding card lifted (R26 translateY\n + R242 stroke + filter cues). R427 adds the\n missing typographic axis to the hover gesture\n so the alias text rises with the card.\n The chat-target tier still wins (0.5 > 0.3)\n so the pin signature stays at the top of the\n scale — hover is the mid tier between rest\n and chat-target.\n Hover-letter-spacing family extension:\n R344 chip count digit\n R345 panel title (R423 sibling)\n R347 active-links chip\n R351 vendor chip\n R420 zoom-level chip\n R427 node alias text (this round)\n R211 fill 300ms + R305 letter-spacing 200ms\n transition list preserved; only the\n conditional gets a middle case. */}\n {/* Round 500 / Loop — milestone round, opens\n per-node alias drop-shadow polish. Extends the\n R476-R481 drop-shadow visual-polish family to a\n 7th anchor: hovered alias text gains a soft\n status-coloured text-glow. Pre-R500 hover on\n a node triggered card-lift (R26 translateY) +\n card-stroke (R242 tint) + alias letter-spacing\n (R427 0.3px tier) but the alias TEXT itself had\n no paint-axis cue beyond fill (R211). R500 adds\n a drop-shadow on the text glyph itself, so the\n identity glyph itself lights up under attention\n — matching the R476 idiom (hub-digit emerald\n glow on hover) at the per-node identity scope.\n 2px blur radius at 50% alpha — subtler than the\n R476 hub-digit (3px at 60%) because the alias\n text is smaller and more numerous (1 per node)\n so an aggressive glow would multiply into\n visual noise. Status-coloured (status.text) so\n the glow inherits the node's working/idle/\n offline palette — green/cyan/gray respectively.\n Drop-shadow visual-polish family — 7 anchors:\n R476 hub digit hover-gated emerald\n R477 legend pin-ring pin-gated row.fill\n R478 recent-row pip fresh-gated cyan\n R479 group-label text pin-gated cyan\n R480 hot-lane edge hot-gated amber\n R481 zoom-state minimap zoom-gated cyan\n R500 node alias text hover-gated status.text ← this round\n Filter is paint-only; bbox unchanged; overlap-\n test invariants hold (R51 selector gated to\n g[data-node] descendants with strokeWidth\n sentinels; text element doesn't carry stroke).\n transition list extends to include 'filter\n 200ms ease-out' alongside the existing fill\n 300ms + letter-spacing 200ms tweens.\n data-node-alias-glow attr surfaces the hover\n gate for tests. */}\n <text\n x=\"0\" y=\"1\" textAnchor=\"middle\"\n fill={status.text}\n fontSize={aliasFs} fontFamily=\"monospace\" fontWeight=\"700\"\n data-node-alias-text={session.alias}\n data-node-alias-chat-target={chatAlias === session.alias ? 'true' : 'false'}\n data-node-alias-hovered={hoveredAlias === session.alias ? 'true' : 'false'}\n data-node-alias-glow={!reducedMotion && hoveredAlias === session.alias ? 'true' : 'false'}\n style={{\n transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',\n letterSpacing:\n chatAlias === session.alias ? '0.5px' :\n hoveredAlias === session.alias ? '0.3px' : '0px',\n /* Round 564 / Loop — alias text filter stacks\n brightness(1.15) on top of R500's drop-shadow\n on hover. Mirrors R542 pressure-seg pattern\n (brightness + drop-shadow in one stacked\n filter declaration). Pre-R564 hover added\n only a drop-shadow halo around the glyph;\n post-R564 the glyph ALSO brightens, so the\n identity text reads as both \"glowing\" AND\n \"lit up\" under attention — dual paint axes\n through one filter chain.\n CSS filter supports multiple functions\n applied left-to-right. brightness(1.15)\n lifts the per-status text color (status.text:\n green/teal/slate per tier) by 15%; the drop-\n shadow then paints the outer halo in the\n status-tier hue. Together: the alias glyph\n both intensifies its identity color AND\n radiates outward in that same color.\n Same +15% brightness as R501 vendor logo\n avatar (banked per-node hover-brightness\n pattern). Consistent +15% across all per-\n node identity surfaces (logo, monogram,\n fallback avatar from R558, AND now alias\n text). Cross-element brightness consistency.\n data-node-alias-brightness attr surfaces\n the lift for tests. */\n /* R616 — extends R564 alias text brightness\n gate to ALSO fire on chatAlias === alias.\n 2nd anchor in chat-target-gated brightness\n family (sibling to R615 chat ring).\n\n Pre-R616: filter only fired on hoveredAlias\n match. The chat-target stayed at flat fill\n color while the chat ring glowed around it\n — visual disparity between identification\n elements that should read as a unified\n \"this is the chat partner\" gesture.\n\n Post-R616: alias text brightens on EITHER\n hover OR chat-target. Mirrors the existing\n 3-tier letter-spacing (R427/R427-chat) which\n already responds to both gates. The chat\n partner's alias text now reads:\n +15% brighter (R616)\n +0.5px letter-spaced (R427-chat tier)\n surrounded by a glowing ring (R615)\n Unified identification gesture across 3\n axes when chat is open.\n\n Same drop-shadow + brightness filter chain\n (no separate color for chat — same R564\n pattern, just expanded gate). */\n /* Round 645 / Loop — alias text gains a SECOND\n drop-shadow layer at 4px blur + 0x40 alpha\n (half the R500 inner-layer 0x80 alpha).\n Mirrors R642/R643/R644 multi-layer halo\n pattern from the per-node identity rings\n to the per-node identity TEXT — extends\n the chromatic-identity 2-layer halo family\n from rings to glyphs.\n Inner layer (R500): 2px blur, 0x80 alpha,\n status.text tint — sharp identity glow\n Outer layer (R645): 4px blur, 0x40 alpha,\n same status.text tint — soft ambient\n falloff\n Same 2x-blur stride + 0.5x alpha falloff\n as R643/R644 (small identity surface). On\n hover or chat-target, the alias glyph\n radiates a near + far layered glow in the\n node's status color — completing the\n chromatic-identity multi-layer halo family\n across BOTH rings (R642-R644) and TEXT\n (R645). transition list (R500 'filter 200ms\n ease-out') already covers the new layered\n filter; no transition change needed.\n data-node-alias-halo-layers attr exposes\n the 2-layer state for tests. */\n filter: !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias)\n ? `drop-shadow(0 0 2px ${status.text}80) drop-shadow(0 0 4px ${status.text}40) brightness(1.15)`\n : undefined,\n }}\n data-node-alias-brightness={!reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias) ? '1.15' : '1'}\n data-node-alias-halo-layers={!reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias) ? '2' : '0'}\n >\n {truncate(session.alias, fullMax)}\n </text>\n {/* Round 428 / Loop: node sub-text (status label\n line beneath the alias) adopts hover letter-\n spacing tween 0 → 0.2px on hoveredAlias.\n Sibling treatment to R427 alias-text hover\n tween (0 → 0.3) — the alias is the primary\n identity (top-tier kerning 0.3), the sub-text\n is the secondary status line (one tier lower\n at 0.2). Now both lines of the label card\n telegraph hover typographically as one unit,\n matching the R26 card lift + R242 stroke\n tint + R975 filter cues. Subtler delta on\n the sub-text (0.2 vs alias 0.3) preserves\n the alias > status visual hierarchy at the\n hover scope. R211 fill 300ms transition\n preserved (additive letter-spacing branch\n + appended 'letter-spacing 200ms ease-out'). */}\n {/* Round 448 / Loop: node sub-text fontWeight\n 400 → 500 (font-medium). Sibling to R363\n (recent-row text fw 400→500) + R364 (legend-\n row label fw 400→500) — same \"small mono\n text at fontSize=9-11 needs 500-tier weight\n for legibility\" pattern, now applied to the\n per-node sub-text line. At fontSize=8-9\n monospace against the label-card chrome\n (pal.labelBox.fill cyber #060608 / light\n #ffffff), the default fw=400 sits at the\n legibility floor; fw=500 (font-medium) lifts\n it into a clearly readable band without\n changing geometry. R211 fill 300ms +\n R428 letter-spacing 0→0.2 hover + R427\n alias-text + R429 body opacity all preserved.\n Pure typography lift; no layout shift; the\n alias-text fw=700 (R427) still wins so the\n alias > status hierarchy holds at the type\n level. data-node-sub-text-font-weight attr\n exposes the value for tests. */}\n {/* Round 567 / Loop — node sub-text joins the per-\n node hover-brightness consistency family. Pre-\n R567 sub-text had only fill brighten (R211) +\n ls 0→0.2 (R428); the alias text above lifted\n via R500 drop-shadow + R564 brightness(1.15)\n stacked. R567 adds brightness(1.15) to sub-\n text on the same isNodeActive gate so it\n chromatically lifts together with the alias.\n Per-node hover-brightness consistency family\n — 6 anchors at uniform +15%:\n R501 vendor.logo image filter on <image>\n R558 vendor monogram filter on <g>\n R558 prefix-group fallback filter on <g>\n R564 alias text (stacked w/ DS) brightness(1.15)\n R567 node sub-text brightness(1.15) ← this round\n (+ R559 runtime badge drop-shadow tier-color glow)\n Now every per-node identity surface (3 avatar\n variants + alias + sub-text + badge) lifts\n together on node hover with consistent visual\n response.\n Pure paint axis; bbox unchanged. transition\n list extends to include 'filter 200ms ease-\n out' matching R428 ls cadence at this scope.\n data-node-sub-text-brightness attr exposes\n the lift for tests. */}\n <text\n x=\"0\" y={subY} textAnchor=\"middle\"\n fill={status.primary}\n fontSize={subFs} fontFamily=\"monospace\"\n fontWeight=\"500\"\n data-node-sub-text={session.alias}\n data-node-sub-text-hovered={hoveredAlias === session.alias ? 'true' : 'false'}\n data-node-sub-text-chat-target={chatAlias === session.alias ? 'true' : 'false'}\n data-node-sub-text-font-weight=\"500\"\n data-node-sub-text-brightness={!reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias) ? '1.15' : '1'}\n /* R617 — extends R567 sub-text brightness gate\n to ALSO fire on chatAlias === alias. 3rd anchor\n in chat-target-gated brightness family (R615\n chat ring + R616 alias text + R617 sub-text).\n\n Both per-node text lines (alias + sub-text)\n now brighten together when their node is the\n chat partner — unified \"this is the chat\n partner\" gesture across all per-node text\n identification surfaces.\n\n Same R567 plain brightness(1.15) filter (no\n drop-shadow stack at sub-text scope per R567).\n Same gate-union pattern as R616. */\n style={{\n transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',\n letterSpacing: hoveredAlias === session.alias ? '0.2px' : '0px',\n filter: !reducedMotion && (hoveredAlias === session.alias || chatAlias === session.alias)\n ? 'brightness(1.15)'\n : undefined,\n }}\n >\n {status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}\n </text>\n </g>\n ) : (\n // Round 212 / Loop: dense plain-text alias gets fill\n // transition on status flip — extension of R211. Pre-\n // R212 the dense fallback (denseLayout > 16 nodes,\n // where label cards collapse to plain text + R110\n // stroke halo) snap-cut its fill on tier change while\n // the status ring smoothly transitioned (R167) — the\n // card-mode equivalent that R211 just fixed at the\n // ≤16-node grain. Inline transition list combines\n // R26 transform 200ms (group-hover lift) + R212 fill\n // 300ms (status flip ease) — Tailwind transition-\n // transform on className would be displaced by inline\n // transition, so the transform property is explicit\n // in the inline list too. The group-hover:-translate-\n // y-[1.5px] className still fires the transform via\n // CSS pseudo-class; only the transition-property\n // moves to inline. Big fleets benefit most — this is\n // the path users see when their dashboard is busiest.\n /* Round 452 / Loop: dense plain-text alias rest\n opacity 0.9 → 0.95. Closes the alpha gap on the\n dense fleet's per-node label, sibling to R449\n legend-count-active 0.95→1.0 and R450 minimap\n viewport rest 0.9→0.95 — same \"close the\n active-presence alpha gap\" idiom applied here\n to the dense-mode alias text at fontSize=9-10\n monospace. Pre-R452 dense aliases at α=0.9 sat\n just below full alpha; for un-hovered nodes in\n a busy >16-node fleet this is the only label\n readable, so the 10% alpha gap added a subtle\n \"soft-focused chrome\" feel where the labels\n should read as definitive. +0.05 lift makes\n them confidently present without erasing the\n status.text + R110 stroke halo + paintOrder\n layering. R26 group-hover translate + R212\n fill 300ms transition + R110 stroke=container-\n Bg halo all preserved. data-node-dense-alias-\n text-opacity attr exposes the resolved value\n for tests. */\n <text\n x={pos.x}\n y={pos.y + radius + denseDrop}\n textAnchor=\"middle\"\n fill={status.text}\n fontSize={denseFs}\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n opacity={0.95}\n className=\"group-hover:-translate-y-[1.5px]\"\n data-node-dense-alias-text={session.alias}\n data-node-dense-alias-text-opacity=\"0.95\"\n /* R680 — dense plain-text alias (the >16-node\n fallback label) joins the multi-layer halo\n family. Pre-R680 the dense alias had only\n group-hover:-translate-y-[1.5px] on hover —\n a single geometry-axis lift, NO paint signal.\n Post-R680 adds 2-layer drop-shadow at status.\n primary tint (per-tier color: working green /\n idle teal / offline slate) with 2+4 stride,\n alpha 80/40 — sibling to R645 normal-mode\n alias text's per-tier halo treatment.\n\n Closes the dense-fallback alias hover signature\n parity with the normal-mode card label.\n hoveredAlias === session.alias is the standard\n alias-hover gate (used in many other surfaces).\n transition extends with 'filter 200ms ease-out'\n matching the existing 200ms cadence.\n\n The dense fallback is the busiest-fleet view\n (>16 nodes) where users see ONLY this label —\n so the halo gives that mode the same hover\n affordance as the normal card mode. Closes the\n dense-mode visual signature.\n\n 39th anchor in multi-layer halo family — first\n dense-mode anchor. */\n /* R691 — extends R680 halo gate from hover-only to\n (hover || chat-target). The dense fallback is the\n busiest-fleet view (>16 nodes) where users see\n ONLY this label — when the user opens a chat\n with one of the dense-mode nodes, the alias label\n should glow to identify the chat partner among\n the many similarly-rendered siblings. Pre-R691\n the dense label stayed dim even when chatAlias\n matched; post-R691 it lights up in per-tier\n status.primary on the same gate as R645 normal-\n mode alias text (R616 chat-target-gated brightness\n family). Closes dense-mode + chat-target signal-\n ing parity at the per-node label scope. */\n data-node-dense-alias-text-halo-layers={(hoveredAlias === session.alias || chatAlias === session.alias) ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n paintOrder: 'stroke',\n filter: (hoveredAlias === session.alias || chatAlias === session.alias)\n ? `drop-shadow(0 0 2px ${status.primary}80) drop-shadow(0 0 4px ${status.primary}40)`\n : undefined,\n transition: 'transform 200ms ease-out, fill 300ms ease-out, filter 200ms ease-out',\n }}\n stroke={pal.containerBg}\n strokeWidth=\"3\"\n >\n {truncate(session.alias, isSmall ? 9 : 10)}\n </text>\n );\n })()}\n {/* v0.10.0 Hero 3 Wave 1 §3.E — hover detail card.\n Renders an extended-info SVG card next to the\n hovered node showing vendor / model / runtime /\n server fields that don't fit in the compact label\n card. Position flips to the left when the node is\n in the right half of the canvas so the card\n doesn't extend past the viewBox right edge. Only\n one card is visible at any time (gated on\n hoveredAlias === session.alias), so layout cost\n stays bounded.\n Reuses pal.labelBox.fill / pal.legendAccent for\n chrome consistency with the existing label card +\n legend panel. data-topo-hover-detail attribute\n exposes the element for test probes.\n Not rendered in dense layout (>16 nodes) — same\n gate as showFullLabel; dense fleets already have\n too much per-node chrome competing. */}\n {!reducedMotion && hoveredAlias === session.alias && !denseLayout && (() => {\n const v = vendorForModel(session.model, session.runtime);\n const rt = runtimeIdentity(session.runtime);\n const flipLeft = pos.x > VIEWBOX_W * 0.65;\n const detailW = 192;\n const detailH = 88;\n const detailX = flipLeft ? pos.x - radius - 18 - detailW : pos.x + radius + 18;\n const detailY = pos.y - detailH / 2;\n return (\n <g transform={`translate(${detailX}, ${detailY})`} data-topo-hover-detail={session.alias} style={{ pointerEvents: 'none' }}>\n {/* Round 387 / Loop: hover-detail panel cyber backdrop\n opacity 0.94 → 0.97. The hover-detail card is\n ALWAYS rendered in active-hover context (it IS\n the hover product), so it should carry the\n same backdrop weight as the R348 recent-signal /\n legend panel HOVER state (which lifts 0.92 →\n 0.97 cyber). Pre-R387 the card sat at 0.94\n cyber, leaving a 0.03 alpha gap against the\n R348 panel-hover state — small but visible\n when the hover-detail floats next to a hovered\n recent-signal panel. R387 unifies them at 0.97\n so all active-hover panels paint with the same\n confident backdrop opacity in cyber. Light\n stays at 0.98 (already at the strong end —\n R348 light also stays at 0.97/0.98 max).\n Theme-consistency / canvas-presence polish\n family (5th anchor):\n R370 hub hover-ring opacity 0.7 → 0.8 cyber\n R371 edge-badge rest opacity 0.82 → 0.85 cyber\n R372 minimap offline-dot opacity 0.5 → 0.6\n R386 hub-highlight idle opacity 0.9 → 0.95\n R387 hover-detail panel opacity 0.94 → 0.97 cyber (this round)\n data-topo-hover-detail-opacity attr exposes\n the resolved value for tests. R348 drop-shadow\n + rx=8 + stroke=pal.legendAccent + fill=pal.\n labelBox.fill all preserved. */}\n {/* Round 390 / Loop: hover-detail card rx 8 → 10.\n Corner-radius cascade family — the hover-detail\n card is a panel-tier surface (192×88 floating\n info card with drop-shadow + stroke), so its\n corner radius should match the R331 panel tier\n (rx=10) used by the recent-signal and legend\n panels. Pre-R390 it shared rx=8 with the R332\n minimap and R375/R376 segmented-control tier\n (Layout-toggle, nodeSize, zoom wrappers),\n which is the \"compact chrome control\" tier —\n a tier mismatch for a content-bearing panel.\n Corner-radius cascade (6 anchors now):\n R330 canvas rx 12 (root)\n R331 panels rx 10 (recent-signal, legend)\n R332 minimap rx 8 (compact chrome)\n R375 Layout-toggle rx 8 (segmented control)\n R376 nodeSize/zoom rx 8 (segmented control)\n R390 hover-detail rx 10 (panel — this round)\n Pure paint change; no layout shift (rx grows\n the corner curve INWARD without changing the\n card's outer bbox). data-topo-hover-detail-\n rx attr exposes the resolved value for tests.\n R348 drop-shadow + stroke + R387 opacity all\n preserved. */}\n {/* R693 — hover-detail card backdrop rect joins the\n multi-layer halo family. Pre-R693 the rect had\n only a vertical elevation shadow (`drop-shadow(0\n 4px 12px ...)`) — same vocabulary as the panel-\n pair rects. R693 prepends 2-layer drop-shadow at\n pal.legendAccent tint with 3+6 stride, alpha\n 80/40 — radial halo echoes the existing cyan\n stroke outward in soft glow. Coherent with R660\n panel titles + R669 panel counts (same panel-\n tier scale + halo vocabulary).\n Final filter chain (panel-tier 4-layer):\n drop-shadow(0 0 3px ${pal.legendAccent}80) near halo\n drop-shadow(0 0 6px ${pal.legendAccent}40) far halo\n drop-shadow(0 4px 12px elevation rgba()) vertical lift\n The card only renders when hovered (gated on\n hoveredAlias === session.alias) — halo is\n transient, focused identity signal. 49th anchor\n in multi-layer halo family — first hover-detail\n card anchor.\n data-topo-hover-detail-halo-layers attr exposes\n the gate for tests. */}\n <rect\n x=\"0\" y=\"0\" width={detailW} height={detailH} rx=\"10\"\n fill={pal.labelBox.fill}\n stroke={pal.legendAccent}\n opacity={isLight ? 0.98 : 0.97}\n data-topo-hover-detail-opacity={isLight ? 0.98 : 0.97}\n data-topo-hover-detail-rx=\"10\"\n data-topo-hover-detail-halo-layers=\"2\"\n style={{\n filter: isLight\n ? `drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) drop-shadow(0 4px 12px rgba(15,23,42,0.16))`\n : `drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) drop-shadow(0 4px 12px rgba(0,0,0,0.6))`,\n }}\n />\n <text x=\"10\" y=\"16\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendAccent} fontWeight=\"700\">\n {v.id !== 'unknown' ? v.label : '—'}\n </text>\n {/* Round 389 / Loop: hover-detail model line (y=32)\n fontWeight 400 → 600. R388 lifted body lines\n (runtime/host/task at fontSize=9) to fw=500;\n R389 closes the typography hierarchy by giving\n the model name (the dominant subhead text in\n the card) its own weight tier. Three-tier\n ladder now reads cleanly:\n vendor fontSize=9 fw=700 (label badge)\n model fontSize=10 fw=600 (subhead — this round)\n body 3× fontSize=9 fw=500 (R388)\n One tier step per dimension (size + weight)\n between adjacent levels — classic editorial\n hierarchy idiom adapted to a 192×88 SVG card.\n Sibling to the chip-internal-hierarchy arc\n (R333-R341/R362/R369) which uses fw=600/500\n for digit/unit pairs; R389 applies the same\n fw=600 to a content-bearing identity line.\n data-topo-hover-detail-model-fw attr exposes\n the resolved value for tests. pal.legendHeadline\n fill preserved (R389 doesn't touch color). */}\n <text x=\"10\" y=\"32\" fontSize=\"10\" fontFamily=\"monospace\" fontWeight=\"600\" fill={pal.legendHeadline} data-topo-hover-detail-model-fw=\"600\">\n {session.model || 'model · pending'}\n </text>\n {/* Round 388 / Loop: hover-detail body lines (the\n three fontSize=9 lines: runtime, host, task)\n gain fontWeight=500. Small-text fw lift family\n (6th anchor) — fontSize 9-10 px text reads\n consistently bolder at fw=500 than at the\n default 400 weight at small sizes, especially\n on the cyber-theme backdrop where stroke-\n rendering is the limiting factor.\n Sibling lifts in this family:\n R363 recent-row alias text 400 → 500\n R364 legend-row label 400 → 500\n R366 group-label count tspan 400 → 500\n R368 +N more flows footer 400 → 500\n R373 pressure-bar kicker (font-medium)\n R388 hover-detail body lines 400 → 500 (this round)\n Tier structure preserved:\n y=16 vendor (fw=700, headline)\n y=32 model (fontSize=10, subhead by size)\n y=48 runtime / y=64 host / y=80 task (body, now fw=500)\n The y=80 task line keeps opacity=0.7 so its\n caption-tier identity stays distinct from the\n y=48 / y=64 body lines despite shared fw.\n data-topo-hover-detail-body-fw attr exposes\n the resolved value for tests. */}\n <text x=\"10\" y=\"48\" fontSize=\"9\" fontFamily=\"monospace\" fontWeight=\"500\" fill={pal.legendText} data-topo-hover-detail-body-fw=\"500\">\n {rt ? rt.label : 'runtime · pending'}\n </text>\n <text x=\"10\" y=\"64\" fontSize=\"9\" fontFamily=\"monospace\" fontWeight=\"500\" fill={pal.legendText} data-topo-hover-detail-body-fw=\"500\">\n host · {session.server || 'unknown'}\n </text>\n <text x=\"10\" y=\"80\" fontSize=\"9\" fontFamily=\"monospace\" fontWeight=\"500\" fill={pal.legendText} opacity=\"0.7\" data-topo-hover-detail-body-fw=\"500\">\n {session.task ? truncate(session.task, 28) : 'no recent task'}\n </text>\n </g>\n );\n })()}\n </g>\n );\n })}\n\n {/* Round 14 / Loop: click ripple — one-shot expanding ring from\n the clicked node, ~500ms, status-coloured. Sits inside the\n zoom/pan <g> so it scales / pans with the topology. Keyed by\n ts so a re-click on any node (same or different) remounts the\n <circle> and the SMIL <animate> elements replay from t=0.\n strokeWidth=2 doesn't match the overlap-test selectors. */}\n {/* Round 227 / Loop: click-ripple SMIL gets ease-out curve.\n Pre-R227 both <animate>s ran with default calcMode=linear,\n which made the ripple grow at constant velocity from\n r0+4 → r0+30 over 500ms — a mechanical \"expansion at\n uniform rate\" feel that didn't match the rest of the\n topology's interaction vocabulary (every CSS transition\n on hover-lift, status-flip, pin-signature uses\n `ease-out`). On click, the ripple is the user's primary\n \"I did that\" confirmation feedback — it should feel\n fast-then-settle like a real pressure wave, not metric.\n\n calcMode=\"spline\" + keyTimes=\"0;1\" + keySplines=\"0.25 0.1\n 0.25 1\" maps directly onto CSS cubic-bezier(0.25, 0.1,\n 0.25, 1), the canonical ease-out curve. SMIL's keySplines\n uses the same 4 control-point convention as CSS but\n space-separated. Applied to BOTH the r and opacity\n <animate> elements so they ease in lockstep — the ring\n decelerates as it expands and fades, the two motions\n together feeling like one organic pulse.\n\n One change reaches three click surfaces — hub center\n (R52), node body (R14), edge midpoint badge (R185) — all\n reuse this single ripple element via the shared\n setClickRipple state. data-click-ripple attr surfaces\n the element for test introspection; calcMode attribute\n on the <animate> reflects the ease-out adoption. */}\n {clickRipple && !reducedMotion && (\n <circle\n key={clickRipple.ts}\n cx={clickRipple.x}\n cy={clickRipple.y}\n r={clickRipple.r0 + 4}\n fill=\"none\"\n stroke={clickRipple.color}\n strokeWidth=\"2\"\n opacity=\"0\"\n data-click-ripple\n data-click-ripple-glow={`0 0 4px ${clickRipple.color}99`}\n data-click-ripple-halo-layers=\"2\"\n style={{\n pointerEvents: 'none',\n /* R608 — click-ripple gains drop-shadow glow that\n matches the ripple's stroke color. The expanding\n ring (r0+4 → r0+30 over 500ms) now visually pulses\n outward as a \"lit\" ring rather than a plain stroke\n line — the user's \"I clicked\" moment reads more\n confidently as a feedback signal.\n\n Color: clickRipple.color (status.primary from the\n click target: green for working / teal for idle /\n slate for offline; cyan for hub clicks) at 0x99\n alpha (~60%) — matches the source element's status\n identity, so the ripple inherits the visual hue of\n what the user clicked.\n\n 4px blur radius is the same as R605 avatar\n drop-shadow; reads as soft halo, not loud bloom,\n at the ripple's wide 30+ px expansion range.\n\n Drop-shadow visual-polish family extension —\n click-feedback surface adds 1 more anchor to the\n family (now ~23 anchors with R604/R605 + R608).\n\n The SMIL animates r and opacity for 500ms; filter\n is static (no animation needed — the opacity fade\n carries the visual decay automatically). No\n transition needed since the element is created\n fresh each click. */\n /* Round 657 / Loop — click-ripple drop-shadow gains a\n SECOND outer layer at 8px blur + 0x4c alpha (half\n R608 inner 0x99). 16th anchor in multi-layer halo\n family (1st click-feedback anchor).\n 4+8 stride scales R642 chat-target ring outer\n stride for the ripple's large expansion range\n (r0+4 → r0+30 over 500ms — much wider scope than\n the rings). Inner 4px halo paints sharp ripple\n front; outer 8px halo extends the ambient glow,\n reading as a real expanding wavefront with soft\n falloff. clickRipple.color tint preserved across\n both layers — chromatic identity matches the\n click source's status (green for working, teal\n for idle, slate offline, cyan for hub).\n Filter is static; the opacity SMIL animate carries\n the visual decay naturally — both inner + outer\n halos fade together via the parent <circle>'s\n opacity (no separate filter animation needed). */\n filter: `drop-shadow(0 0 4px ${clickRipple.color}99) drop-shadow(0 0 8px ${clickRipple.color}4c)`,\n }}\n >\n <animate\n attributeName=\"r\"\n values={`${clickRipple.r0 + 4};${clickRipple.r0 + 30}`}\n dur=\"0.5s\"\n calcMode=\"spline\"\n keyTimes=\"0;1\"\n keySplines=\"0.25 0.1 0.25 1\"\n fill=\"freeze\"\n />\n {/* Round 403 / Loop: click-ripple SMIL initial opacity\n 0.7 → 0.8. Pre-R403 the ripple's opacity animation\n faded from 0.7 to 0 over 500ms, providing a clean\n click-feedback pulse. Theme-consistency / canvas-\n presence polish family (R370 hub hover-ring +\n R391 hub-spoke active) already lifted paired\n hover-state alphas from 0.7 → 0.8. R403 brings\n click-feedback into that same alpha — three canvas\n state-feedback indicators (hover-ring, active spoke,\n click ripple) now share a uniform 0.8 start alpha\n so the visual \"I responded\" signal carries the\n same weight regardless of which state fired it.\n Pre-R403 invariants preserved: 500ms duration,\n R227 calcMode='spline' + ease-out keySplines\n (0.25 0.1 0.25 1), fill='freeze', concurrent r\n animation. Theme-consistency family (8 anchors):\n R370 hub hover-ring 0.7 → 0.8\n R371 edge-badge rest 0.82 → 0.85 cyber\n R372 minimap offline-dot 0.5 → 0.6\n R386 hub-highlight idle 0.9 → 0.95\n R387 hover-detail panel 0.94 → 0.97 cyber\n R391 hub-spoke active 0.7 → 0.8\n R392 minimap online-dot 0.9 → 0.95\n R403 click-ripple start 0.7 → 0.8 (this round)\n data-click-ripple-start-opacity attr exposes the\n resolved value for tests. */}\n <animate\n attributeName=\"opacity\"\n values=\"0.8;0\"\n dur=\"0.5s\"\n calcMode=\"spline\"\n keyTimes=\"0;1\"\n keySplines=\"0.25 0.1 0.25 1\"\n fill=\"freeze\"\n data-click-ripple-start-opacity=\"0.8\"\n />\n {/* Round 631 / Loop — click-ripple gains a THIRD animate\n axis: stroke-width 2 → 0.5 over 500ms with the same\n ease-out keySplines as R227's r + opacity animates.\n Pre-R631 the ripple expanded (r0+4 → r0+30) and\n faded (0.8 → 0) at uniform stroke-width, reading as\n a constant-thickness ring growing and fading.\n Real water ripples THIN as they expand outward — the\n wavefront's perceived line-thickness decays alongside\n its amplitude. R631 stacks that geometric thinning\n onto the existing paint+area fade so the ripple\n reads as one coherent expanding wave instead of an\n expanding solid line.\n Three concurrent animates now ease in lockstep\n through ease-out cubic-bezier(0.25, 0.1, 0.25, 1)\n — fast-then-settle for the r + opacity + sw, all\n matching R227's organic-pulse rhythm. R608 drop-\n shadow glow remains static (no animation needed —\n the opacity fade carries its decay).\n R51 sentinel safety: stroke-width animates through\n the range [2, 0.5], which passes through the\n reserved 1.5 sentinel mid-cycle. The R51 selector\n is gated to `g[data-node]` descendants (banked\n documentation pattern); the ripple is rendered at\n the canvas root inside the zoom/pan <g> wrapper,\n OUTSIDE individual node groups, so the selector\n never matches it. Plus the ripple only mounts on\n click — topo-overlap-test runs without click.\n data-click-ripple-stroke-width-start='2' /\n -end='0.5' attrs expose the new range for tests. */}\n <animate\n attributeName=\"stroke-width\"\n values=\"2;0.5\"\n dur=\"0.5s\"\n calcMode=\"spline\"\n keyTimes=\"0;1\"\n keySplines=\"0.25 0.1 0.25 1\"\n fill=\"freeze\"\n data-click-ripple-stroke-width-start=\"2\"\n data-click-ripple-stroke-width-end=\"0.5\"\n />\n </circle>\n )}\n\n </g>\n\n {/* #112: overlay panels (recent-signal + legend) render OUTSIDE the\n zoom/pan <g> so they stay fixed while the topology pans/zooms.\n They're sized + tucked into the top corners so every corner of\n each panel is >325px from the canvas centre — i.e. fully outside\n the outermost (offline) ring. No node on any ring can reach the\n corner triangles, so the panels never overlap a node, in ring\n OR grid layout (Vincent 4727 zero-overlap criterion). */}\n {/* latest flow labels */}\n {/* Round 57 / Loop: drop-shadow on the panel rects gives them\n card-like elevation, especially on light theme where the\n near-white fill on a near-white canvas read as pasted-on.\n data-topo-panel-elevation tag so the test can verify both\n panels carry the filter. The filter is on the rect, not\n the parent <g>, so it doesn't shadow the rows + text inside\n — only the panel chrome lifts.\n\n v0.10.0 Hero 3 Wave 1 / RFC §3.C (Vincent 5222 holdover):\n hide recent-signal panel when there's no flow to show.\n Pre-v0.10.0 the panel always-mounted with a \"no flow yet\n · send a message between agents\" placeholder. On a fresh\n fleet that's a full corner of chrome doing nothing —\n exactly the always-mount-stack 5222 calls out. Render the\n panel only when flowLinks actually has rows. R175 fade-in\n still applies — first flow that arrives still eases in.\n Composes with §3.I canvas-corner watermark (only shows\n when this panel is absent). */}\n {/* #217 M6 (Vincent tg 664 乱七八糟看都看不清): the recent-signal\n flow panel that lived in this corner is gone — at fleet scale\n its micro-bars and 9px rows were unreadable noise. Live flows\n still render as animated lane edges on the canvas itself. */}\n\n {/* legend — Round 55 / Loop: each status row is now a hover\n target. Pointer enter sets `hoveredStatus`; pointer leave\n clears it. Node opacity formula composes the match below.\n The row text brightens to legendHeadline while hovered as\n a small affordance hint. Geometry unchanged — the new\n <g> wrappers only carry pointer handlers. */}\n <g\n transform=\"translate(760, 16)\"\n data-topo-panel=\"legend\"\n data-topo-panel-hovered={hoveredPanel === 'legend' ? 'true' : 'false'}\n // R175 / Loop: legend panel offset 100ms behind the\n // recent-signal panel so the two corner panels cascade\n // left-then-right rather than appearing in lockstep.\n // Same .anet-fade-in mechanism the four wave layers use.\n className=\"anet-fade-in\"\n data-topo-panel-fade-delay={800}\n style={{ animationDelay: '800ms' }}\n onMouseEnter={() => setHoveredPanel('legend')}\n onMouseLeave={() => setHoveredPanel(prev => prev === 'legend' ? null : prev)}\n >\n {/* R57: matching drop-shadow elevation to the legend panel.\n R106: panel height grew 96 → 104 to seat the new header\n line + 4 px row-shift below it (so the new header text\n doesn't overlap the row-1 hitbox region).\n R135: hover-elevation mirrors the recent-signal panel\n rect at line ~3299. Both panels grow their shadow on\n hover to telegraph \"the chrome is interactive\" since\n their rows pin / nav. */}\n {/* Round 247 / Loop: sibling treatment to the recent-signal\n panel — fill + stroke + opacity transitions added so\n the legend panel also eases through theme toggles\n (no snap on cyber↔light switch). Same 200ms cadence\n across the panel pair. */}\n {/* Round 331 / Loop: legend panel rect rx 8 → 10 — sibling\n treatment to the recent-signal panel above. Same\n proportional-rhythm step under R330's rounded-xl\n canvas wrapper envelope. */}\n <rect\n x=\"0\" y=\"0\" width=\"224\" height=\"88\" rx=\"10\"\n fill={pal.legendBox.fill}\n // R423 sibling — legend panel rect stroke tints to\n // legendAccent on hover (mirrors recent-signal panel\n // above). 4-layer hover cue stack now symmetric across\n // both side panels.\n stroke={hoveredPanel === 'legend' ? pal.legendAccent : pal.legendBox.stroke}\n // R348 sibling — legend panel rect opacity hover-state\n // bump 0.92 → 0.97 (cyber) / 0.97 → 1 (light) on\n // hoveredPanel === 'legend'. Pairs with the recent-signal\n // panel rect above so the two corner panels' hover cues\n // stay symmetric. Geometry-safe (paint-only).\n opacity={hoveredPanel === 'legend' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}\n style={{\n /* R695 sibling — legend panel rect mirrors recent panel\n above. Same 4-layer panel-tier composition on hover,\n same pal.legendAccent 3+6 radial halo prefix. Closes\n panel-pair backdrop halo symmetry (recent + legend). */\n filter: hoveredPanel === 'legend'\n ? (isLight ? `drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) drop-shadow(0 4px 12px rgba(15,23,42,0.14))`\n : `drop-shadow(0 0 3px ${pal.legendAccent}80) drop-shadow(0 0 6px ${pal.legendAccent}40) drop-shadow(0 4px 12px rgba(0,0,0,0.65))`)\n : (isLight ? 'drop-shadow(0 2px 6px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 2px 6px rgba(0,0,0,0.45))'),\n transition: 'filter 200ms ease-out, fill 200ms ease-out, stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n data-topo-panel-elevation=\"legend\"\n data-topo-panel-legend-halo-layers={hoveredPanel === 'legend' ? '2' : '0'}\n />\n {/* R106 / Loop: panel header — symmetric with the recent-\n signal panel's \"recent signal · N flows\" (R96). Same\n font + position vocabulary so the two side panels feel\n paired. Title text at x=13 y=21; total fleet count\n right-aligned at x=215 y=21 in the accent colour. */}\n {/* Round 266 / Loop: legend panel title fill picks up theme-\n toggle transition — sibling treatment to the recent-\n signal panel title at line ~5459. Pre-R266 both panel\n titles hard-flipped color on theme toggle while their\n surrounding chrome eased; R266 closes both at once. */}\n {/* R301: sibling to recent-signal panel title above —\n same letterSpacing 0.3 for editorial parity. */}\n {/* Round 483 / Loop — sibling to R482 (recent-signal panel\n title): legend panel title fontWeight 700 → 800 on\n pinnedStatus (any legend row pinned propagates to the\n panel title). Pre-R483 the title responded only to\n panel-chrome hover via R345 ls; the pinnedStatus row\n highlighted its own swatch + tint via R181/R477 but\n the title stayed flat — no upstream tightening to\n signal \"panel context = inspecting\".\n R483 closes the symmetry with R482: both panel titles\n (recent-signal + legend) now tighten typographically\n when ANY row inside them is in the active filter\n state. Same idiom, mirrored at the legend-row scope.\n data tightens family — now 10 anchors:\n R416/R424/R425/R426 chip/panel/hub/edge digits\n R444/R445/R446 group/recent/legend counts\n R457 group-label parent\n R482 recent-panel title\n R483 legend-panel title (this round)\n transition list extends to include 'font-weight 200ms\n ease-out' alongside R345's ls + R55's fill 200ms.\n data-legend-panel-title-fw + -active exposed for tests. */}\n {/* R345 sibling — legend panel title same hover letter-\n spacing tween 0.3 → 0.4 on panel hover. */}\n {/* Round 550 sibling — legend-panel header title mirrors\n the recent-panel title above: drop-shadow glow on\n pin-gated active state (pinnedStatus). Same hue\n (pal.legendAccent + hex alpha 80), same 2px blur,\n same 200ms ease-out cadence. Family lifts to 15\n anchors with this sibling (counted as R550-sibling\n for accounting parity with R532-R536 hub-cluster\n glow quintet pattern — two co-shipping anchors\n under a single round number).\n data-legend-panel-title-glow attr added. */}\n {/* Round 573 sibling — legend panel-title 12th anchor.\n Same stacked filter pattern at the legend-panel-title\n scope. Both panel titles now lift in lockstep. */}\n {/* Round 701 / Loop — legend panel title at-rest SVG opacity\n breath, sibling to R700 recent-panel-title breath. Closes\n the panel-pair breath symmetry: both panel titles now\n breathe at 8 s rest-state opacity 0.78 ↔ 1, gated off\n when the panel is in its pinned-active state (R483 fw +\n R345 ls + R550 glow take precedence). Recent: gates on\n activeEdgeKey; legend: gates on pinnedStatus. Same shape\n as R695 panel-pair backdrop halo (recent + legend\n paired) but operating on the rest axis.\n\n Cadence rolodex (each surface a distinct rhythm):\n 3 s row hot · 4 s hub idle · 5 s brand logo\n 6 s watermark · 6 s kicker (R699) · 7 s crescent\n 8 s recent panel title (R700)\n 8 s legend panel title (R701) ← this round\n Recent + legend SHARE the 8 s cadence intentionally —\n they're a typographic pair (matching font-weight/size/\n letter-spacing), so cadence parity reads as \"the panel-\n pair breathes together\". They never get desynced visually\n because the SVG `<animate>` elements share the same\n document time origin.\n\n Implementation: conditional SVG <animate> child element,\n identical pattern to R700 + R519 watermark. Paint-only\n opacity → bbox stable → topo-overlap-test untouched.\n data-legend-panel-title-breath attr exposes the gate\n state for tests. */}\n {/* Round 713 sibling — legend-panel-title gains 2nd breath\n axis (font-size), closing panel-pair dual-axis symmetry\n paired with recent-panel-title above. Same 8 s cadence,\n same 11.96 ↔ 12.04 range, same opacity-axis siblings —\n panels breathe both opacity AND font-size IN PHASE,\n phase-locked across the pair via shared cadence. */}\n <text x=\"13\" y=\"21\" fill={pal.legendHeadline} fontSize=\"12\" fontFamily=\"monospace\" fontWeight={pinnedStatus ? '800' : '700'} letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out', filter: pinnedStatus ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined }} data-legend-panel-title data-legend-panel-title-fw={pinnedStatus ? '800' : '700'} data-legend-panel-title-active={pinnedStatus ? 'true' : 'false'} data-legend-panel-title-glow={pinnedStatus ? 'true' : 'false'} data-legend-panel-title-brightness={pinnedStatus ? '1.15' : '1'} data-legend-panel-title-breath={!reducedMotion && !pinnedStatus ? '8s' : 'off'} data-legend-panel-title-breath-axis-2={!reducedMotion && !pinnedStatus ? 'font-size' : 'off'} data-legend-panel-title-breath-axis-3={!reducedMotion && !pinnedStatus ? 'text-shadow' : 'off'} className={!reducedMotion && !pinnedStatus ? 'anet-topo-panel-title-glow-breath' : undefined}>{/* R730 — SVG <title> a11y child, screen readers read this as the panel's accessible name. */}<title>legend · status / vendor / runtime swatch panel</title>legend{!reducedMotion && !pinnedStatus && (\n <animate attributeName=\"opacity\" values=\"0.78;1;0.78\" dur=\"8s\" repeatCount=\"indefinite\" />\n )}{!reducedMotion && !pinnedStatus && (\n <animate attributeName=\"font-size\" values=\"11.96;12.04;11.96\" dur=\"8s\" repeatCount=\"indefinite\" />\n )}</text>\n {/* Round 257 / Loop: legend panel header count picks up the\n symmetric 13L/13R inner-padding pattern from the recent-\n signal panel. Pre-R257 the legend header was 13px from\n the left edge (`x=13` title) but only 9px from the right\n edge (`x=215` end-anchored count → 224-215=9), while the\n recent-signal panel header used 13px on BOTH sides (x=13\n title + x=217 end-count → 230-217=13). The two panels\n sit as a side-by-side corner pair — mismatched header\n inner-padding read as a typographic nit on the panel\n chrome. x=211 (= 224-13) restores symmetric 13L/13R so\n the panel pair shares one inset rhythm. The per-row\n count text at x=215 (line ~6321) STAYS at 9px-from-right\n — that one is paired with the flow-arrow swatch geometry\n ('M140,80 Q164,56 196,80') and would visibly tighten\n against the arrow tip if moved further left. Header\n count has no such pairing; it stands alone. */}\n {/* Round 266 / Loop: legend count fill (pal.legendAccent\n — cyber #67e8f9 cyan ↔ light #10b981 emerald) picks up\n theme-toggle transition. Pre-R266 the count snapped\n color on theme flip; R266 eases it alongside the panel\n title (sibling text in the same header band). */}\n {/* Round 292 / Loop: legend panel header count adopts explicit\n fontVariantNumeric: 'tabular-nums' for parity with the\n recent-signal panel header count at line ~5814 (R232).\n The text is already fontFamily='monospace' so digit width\n is technically tabular by definition — the explicit\n directive documents intent at code level, survives a\n future font-family change without silently losing\n tabular alignment, and eliminates an asymmetry between\n two sibling panel-header counts. Sibling treatment to\n R225 (hub digit) / R224 (edge badge) / R232 (chip-row\n counts) — tabular-nums sweep continues wherever digits\n live next to non-digit characters. */}\n {/* Round 310 / Loop: legend panel-header count picks up\n fontWeight=600 for parity with R309 per-row count\n weight. Pre-R310 the header count 'N nodes' rendered\n at default 400 while the per-row counts (working\n 'N' / idle 'N' / offline 'N') went semibold in R309.\n Same hierarchy reason as R309: the count is the DATA\n operators scan; the label ('legend' panel title +\n row labels 'working/idle/offline') is stable\n structural anchor. R309 established the rule at the\n row scope; R310 propagates it up to the panel-\n summary scope so the count typography is consistent\n across both the rollup and per-row counts inside\n the same legend panel. Existing pal.legendAccent\n fill + tabular-nums + R266 fill transition all\n preserved. */}\n {/* Round 336 / Loop: split legend panel count digit from\n unit \" nodes\" with nested opacity-0.7 tspan — sibling\n treatment to the recent-signal panel count and hot-\n count splits above. Three-panel-header surface family\n now sharing the same chip-internal-hierarchy pattern:\n recent flows count + \" flows\" unit at 0.7\n recent hot count + \" hot\" unit at 0.7\n legend nodes count + \" nodes\" unit at 0.7\n data-legend-panel-count-unit on the inner tspan for\n R336 introspection; the parent .textContent still\n reads \"{N} node(s)\" so existing R310 count tests via\n textContent unchanged. */}\n {/* R424 sibling — legend panel count digit fontWeight 600\n → 700 on panel hover. Closes 5-layer panel hover cue\n stack symmetric across both side panels (recent-signal\n + legend): depth (R135) + solidity (R348) + spacing\n (R345) + edge color (R423) + weight (R424). R310 base\n fw=600 + R292 tabular-nums + R266 fill transition + R336\n unit-tspan opacity-0.7 all preserved. Same \"data tightens\n under attention\" idiom R416 established at chip scope. */}\n <text\n x=\"211\" y=\"21\" textAnchor=\"end\"\n fill={pal.legendAccent} fontSize=\"10\" fontFamily=\"monospace\" fontWeight={hoveredPanel === 'legend' ? '700' : '600'}\n // R349 sibling — legend panel header count picks up\n // letterSpacing=\"0.2\", one tier below the R301 panel\n // title 0.3. Pairs with the recent-signal panel count\n // letter-spacing above so the two corner panels' header\n // typography stays editorially symmetric.\n /* Round 566 / Loop — legend panel-count gains hover-state\n letter-spacing tween (0.2 → 0.4 on hoveredPanel ===\n 'legend'). Pairs with existing R310 fw 600→700 on the\n same gate — count now has 2-axis hover signature (fw\n + ls), matching the panel title's R345 ls + R482 fw\n lift pattern at the panel-header data-tspan scope.\n Hover-letter-spacing family extension (R566 = 2 sibling\n anchors at recent + legend panel-count). */\n letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.2'}\n data-legend-panel-count\n data-legend-panel-count-letter-spacing={hoveredPanel === 'legend' ? '0.4' : '0.2'}\n data-legend-panel-count-brightness={hoveredPanel === 'legend' ? '1.15' : '1'}\n /* R669 sibling — legend-panel count joins the multi-layer\n halo family symmetrically with the recent-panel count\n above. Pre-R669 the count had brightness(1.15) only\n (R588). R669 extends to 2-layer drop-shadow at pal.\n legendAccent tint with 2+4 stride. Panel-pair count\n halo parity now complete — both panel COUNT texts emit\n cyan halo on hover alongside the existing 3-axis lift\n (fw + ls + brightness). */\n data-legend-panel-count-halo-layers={hoveredPanel === 'legend' ? '2' : '0'}\n style={{\n /* R588 — legend panel count gains brightness(1.15) on\n panel hover. 27th anchor in per-element brightness\n family. Closes title↔count parity at the legend\n panel header — the title already has brightness\n (R571/R567 lineage on pinnedStatus gate at line\n ~11926); the count now matches at the broader\n hoveredPanel gate.\n\n Legend panel count hover signature now 3 axes:\n R310/R424 fontWeight 600 → 700\n R349/R566 letter-spacing 0.2 → 0.4\n R588 brightness 1 → 1.15 ← this round\n\n The cyan numeral (pal.legendAccent) lifts ~15%\n alongside the typographic tightening — 3-axis\n panel-count hover signal at full visual presence.\n\n Pure paint filter on <text> root element (NOT a\n nested tspan — SVG filter on tspan is unreliable\n cross-browser; the parent <text> takes the filter\n and inherits to children). */\n filter: hoveredPanel === 'legend' ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n transition: 'fill 200ms ease-out, font-weight 200ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >{drawnNodeCount}<tspan opacity=\"0.7\" data-legend-panel-count-unit> node{drawnNodeCount === 1 ? '' : 's'}</tspan></text>\n {(() => {\n const idleCount = onlineNodes.length - workingCount;\n // R106: rows shift +8 px (was y0=24, 48, 72 → 32, 56, 80)\n // to clear the new header row. R57 panel rect grew 96 →\n // 104 to seat them.\n const rows = [\n /* Round 277 / Loop: legend panel compress 104 → 88 (matches\n recent-signal panel height post-R256) per Vincent\n 5214/5215-5217 simplification ask. Row stride drops\n 24 → 18: row 1 working anchored at y0=32 (unchanged so\n R271 hitbox-swatch-center test at y=21 still passes);\n row 2 idle y0=56→50 (-6); row 3 offline y0=80→68 (-12).\n Flow-arrow swatch path (line ~6607) tracks new offline\n cy from y=80 to y=68. Net: legend panel takes ~15%\n less vertical chrome, panel pair (recent-signal+\n legend) now share same height = symmetric corner\n pair. Tests still pass: R257/R266/R269/R274 probe\n x attrs, fill transitions, text content — none\n sensitive to y0 stride. R271 probes working row\n hitbox y=21 (row.y0-11 with row.y0=32), unchanged.\n Corner-to-center distance increases (panel ends\n higher, further from center) — geometric ring-clear\n improves slightly. */\n /* Round 308 / Loop: continue the R307 legend label 减法.\n 'working node' (12 chars) → 'working' (7 chars):\n the 'node' qualifier is redundant — the row is in\n the LEGEND for a node graph, every row inherently\n describes a node state. 'online idle' (11 chars) →\n 'idle' (4 chars): the 'online' qualifier was\n disambiguation against the offline row, but the\n dashed-vs-solid status ring already discriminates\n online idle from offline visually. After R307+R308\n the three legend labels are all just status words:\n working / idle / offline — a clean 3-state list at\n roughly comparable lengths (7 / 4 / 7) for the\n tightest legend column to date. Pure 减法; visual\n information already encoded by row position +\n status-color swatch + status-ring dashing. */\n { key: 'working' as const, y0: 32, y1: 36, fill: isLight ? '#059669' : '#22c55e', label: 'working', count: workingCount },\n { key: 'idle' as const, y0: 50, y1: 54, fill: isLight ? '#0d9488' : '#2dd4bf', label: 'idle', count: idleCount },\n /* Round 269 / Loop: \" / \" → \" · \" delimiter unification.\n R138 swept the recent-signal row separators from\n ASCII \" / \" to typographic \" · \" (matching filter\n pills, node tooltips, edge badges, active-links\n tooltip). The legend's offline-row label was the\n LAST hardcoded \" / \" holdover in TopoGraph. Same\n monospace cell width (no layout shift), completes\n the R138 delimiter sweep.\n Round 307 / Loop: drop the ' · no SSE' qualifier.\n 'offline · no SSE' (16 chars) → 'offline' (7 chars).\n The visual already communicates the same idea\n redundantly: status ring strokeDasharray='5 5' for\n offline nodes (line ~5193) + gray fill + offline\n row's own gray swatch. Text qualifier was\n technical disambiguation that the visual encodes\n directly. Same R275-R281/R290/R291/R294 减法\n register — remove redundant text the eye doesn't\n need. Sibling row labels 'working node' (12 chars)\n + 'online idle' (11 chars) read at roughly the\n same length now too — legend rows look more\n balanced across the 3 lines. */\n { key: 'offline' as const, y0: 68, y1: 72, fill: isLight ? '#94a3b8' : '#6b7280', label: 'offline', count: offlineNodes.length },\n ];\n return rows;\n })().map(row => {\n // Round 61 / Loop: legend rows pin too — symmetric with the\n // R60 pressure-bar segments. R55 hover stays transient; the\n // new onClick toggles pinnedStatus so users can lock a\n // filter without holding the cursor still. Pinned row gets\n // an inset ring on the swatch (same vocab as R60).\n const isPinned = pinnedStatus === row.key;\n const isRowHovered = hoveredStatus === row.key;\n const isLifted = isRowHovered || isPinned;\n /* Round 562 / Loop — inspection-overrides-encoding family\n 5th anchor at the legend-swatch scope. When operator\n hovers a NODE ALIAS on the canvas, the legend swatch\n that matches that node's status tier lifts r + drop-\n shadow (R197 + R537 axes) — telegraphing \"your\n inspected node is in this status group\".\n Mirror of R486 minimap-dot inspection-override at the\n legend-swatch scope.\n Family progression (5 anchors):\n R484 recent-row timestamp on alias hover\n R485 edge particle opacity on alias hover\n R486 minimap dot opacity to 1.0 on alias hover\n R561 group-label opacity-1 + ants-gate refinement\n R562 legend-swatch r+glow on member-alias-matching ← this round\n Restraint: ONLY swatch lifts, NOT label/fill/ls/fw\n axes. Direct row-hover gets full treatment; inspection\n signal gets swatch-only lift — distinct \"lighter\"\n visual register matching R561's ants-gate approach\n (indirect inspection ≠ direct attention). */\n const hoveredSession = hoveredAlias\n ? (onlineNodes.find(s => s.alias === hoveredAlias) ?? offlineNodes.find(s => s.alias === hoveredAlias))\n : null;\n const hoveredAliasRowKey: 'working' | 'idle' | 'offline' | null = !hoveredSession ? null\n : hoveredSession.status === 'working' ? 'working'\n : offlineNodes.includes(hoveredSession) ? 'offline'\n : 'idle';\n const isMemberAliasMatching = hoveredAliasRowKey === row.key;\n const isSwatchLifted = isLifted || isMemberAliasMatching;\n return (\n <g\n key={row.key}\n data-legend-status={row.key}\n data-legend-row-lifted={isLifted ? 'true' : 'false'}\n className=\"anet-topo-svg-focus\"\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n // R144: mirror of R143 — extend the row-lift idiom to the\n // legend panel rows for symmetry with the recent-signal\n // panel rows. Same translate-1px transform, same 150ms\n // cubic-bezier timing. R55 hovers the row (transient),\n // R61 pins it (sticky); both states earn the lift.\n // Composes with R105 row-bg-tint and R135 panel hover-\n // shadow → three layers of feedback at three nested\n // scopes (panel chrome → row text lift → bg tint).\n style={{\n cursor: 'pointer',\n transform: isLifted ? 'translateY(-1px)' : undefined,\n transition: 'transform 150ms cubic-bezier(0.4, 0, 0.2, 1)',\n }}\n // R61: stopPropagation on pointerdown so the SVG-level\n // pan handler (R103) doesn't setPointerCapture and\n // redirect the follow-up click away from this <g>.\n // Same trick the node <g> uses (and R52 hub).\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredStatus(row.key)}\n onMouseLeave={() => setHoveredStatus(prev => prev === row.key ? null : prev)}\n onClick={() => setPinnedStatus(prev => prev === row.key ? null : row.key)}\n >\n {/* R149 / Loop: legend row gains a <title> tooltip\n symmetric with R148's recent-signal row tooltip.\n Same R97 idiom — anywhere the UI shows \"N\" should\n hover-explain WHICH N — applied to the legend\n panel which had been showing only the bucket\n count without alias context. Header line names\n the status bucket; body lists the matched aliases\n with 8-truncate + \"+N more\"; footer hint flips\n with pin state. Hot-lane convention doesn't apply\n here (status buckets aren't traffic counts), so\n no isHot suffix. */}\n <title>{(() => {\n const aliases = row.key === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : row.key === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const preview = aliases.slice(0, 8).join(', ');\n const suffix = aliases.length > 8 ? ` + ${aliases.length - 8} more` : '';\n return [\n `${row.label} · ${row.count}`,\n aliases.length > 0 ? `${preview}${suffix}` : null,\n isPinned ? 'click to release pin (Esc to clear)' : 'click to pin · hover to preview',\n ].filter(Boolean).join('\\n');\n })()}</title>\n {/* R55 hitbox covers the row so cursor doesn't need to\n be exactly on the 5-px swatch. R105 / Loop: the\n hitbox now also carries a subtle hover/pin tint —\n mirroring R104's recent-signal row treatment so\n both side panels share the list-item idiom. The\n tint borrows the ROW'S OWN swatch colour rather\n than legendAccent, so the user's eye associates\n the tinted row with its colour swatch instead of\n a generic accent. Pin is a stronger tint than\n hover; idle stays fully transparent. */}\n {/* Round 271 / Loop: hitbox y shifts row.y0-12 → row.y0-11\n so hitbox center aligns exactly with swatch cy=row.y0.\n Pre-R271 hitbox spanned y=row.y0-12 to y=row.y0+10\n (center at row.y0-1), with swatch cy at row.y0 — 1px\n asymmetric. Hover/pin tint band drifted 1px above\n the swatch. Post-R271: hitbox spans y=row.y0-11 to\n y=row.y0+11 (center exactly at row.y0), swatch sits\n 11px from both edges (symmetric). Label text\n vertical center also benefits — label baseline\n y=row.y1=row.y0+4, visual midpoint ~row.y0+1.25,\n now ~1.25px below hitbox center (vs ~2.25px pre).\n No height change, no test ripple (other than this\n one), no R260/R268/R270 chrome regressions. */}\n {/* Round 473 / Loop — final cadence-sync follow-on,\n closing the legacy 150ms transition at the\n LEGEND-ROW tint scope. R459 (group-label hitbox)\n + R472 (recent-row hitbox) already lifted the\n two sibling panel-row hitboxes to 200ms; the\n legend-row was the last per-row tint still\n snapping at 150ms.\n After R473 the 200ms ease-out vocabulary is\n uniform across ALL three panel-row scopes —\n group-label, recent-signal, and legend — so\n hover/pin state-change cascades read coherently\n at every panel-tier surface. data-legend-row-\n tint-transition='200ms' attr exposed for tests.\n Geometry/paint unchanged. */}\n <rect\n x=\"6\" y={row.y0 - 11}\n width=\"170\" height=\"22\" rx=\"3\"\n fill={hoveredStatus === row.key || isPinned ? row.fill : 'transparent'}\n opacity={isPinned ? (isLight ? 0.14 : 0.18)\n : hoveredStatus === row.key ? (isLight ? 0.08 : 0.12)\n : 1}\n data-legend-row-tinted={isPinned ? 'pinned' : hoveredStatus === row.key ? 'hover' : 'none'}\n data-legend-row-tint-transition=\"200ms\"\n data-legend-row-tint-brightness={(hoveredStatus === row.key || isPinned) ? '1.15' : '1'}\n /* R612 — legend-row tint rect brightness on hover/\n pin. Sibling to R610 group-label tint + R611\n recent-row tint — closes the PANEL-ROW TINT RECT\n BRIGHTNESS TRIO at full parity:\n R610 group label tint rect (pin/hover-label)\n R611 recent-row tint rect (hover/pin row)\n R612 legend-row tint rect (hover/pin row) ← this round\n\n All 3 panel-row tint rects (group / recent /\n legend) now lift +15% brightness alongside their\n fill/opacity response under inspection.\n\n Note: row.fill here is the TIER COLOR (green for\n working / teal for idle / slate for offline)\n rather than the cyan pal.legendAccent used by\n group + recent panels. Each tier brightens its\n OWN hue by +15% — the row reads as \"this status\n lit up\" with the row's status-identifying color\n intensified. Per-tier brightness lift carries\n semantic information at the legend scope.\n\n Pin/hover-gated brightness family at panel tier\n now 7 anchors (R571/R587/R607/R609/R610/R611/\n R612). transition list extends with 'filter\n 200ms ease-out' alongside fill/opacity. */\n style={{\n filter: (hoveredStatus === row.key || isPinned)\n ? 'brightness(1.15)'\n : undefined,\n transition: 'fill 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',\n }}\n />\n {/* Round 197 / Loop: swatch dot scales r 5.5 → 7 when its\n row is hovered or pinned. Pre-R197 the swatch was a\n flat constant size — the row got R55/R61 fill brighten,\n R105 bg tint, R144 1px row lift, R181 pin ring, but\n the swatch dot at the row's *visual identity anchor*\n stayed still. Adding the size response gives the\n swatch its own click/hover feel and visually rhymes\n with R177 hub hover-lift (hub ring r 14→17). Stays\n well inside the r=8 R181 pin ring (7 + 0 stroke vs\n 8 - 0.75 inner ≈ 7.25) so the two layers don't fight.\n CSS r-as-property is interpolatable by Chrome/Safari/\n FF post-2020. Geometry-unchanged at rest, so the\n overlap test sees the same baseline. */}\n {/* Round 295 / Loop: legend swatch base radius 5.5 → 6.\n Pre-R295 the swatch idle radius was 5.5 and R197\n grew it to 7 on hover/pin — a 1.5px jump (~27%\n area). Bumping idle to 6 keeps hover at 7, so\n the hover delta becomes a smoother 1px (~36%\n area but 17% radius). Side effect: idle swatch\n reads slightly more like an authored color\n anchor than a faint dot — matches the post-R294\n 减法 register where the legend is one of the\n few remaining persistent canvas-side info\n surfaces. Geometry stays well inside the r=8\n R181 pin ring (6 + 0 stroke vs 8 - 0.75 inner\n ≈ 7.25). data-legend-swatch is unchanged so\n R197 / R55 / R61 tests probe the same handle. */}\n {/* Round 537 / Loop — extends drop-shadow visual-polish\n family (12 anchors after R536) to a 13th anchor: the\n legend swatch gains drop-shadow glow on hover/pin\n using its OWN row fill color (working green / idle\n teal / offline slate). Pre-R537 the swatch lifted\n only r (R197/R295 6 → 7) on attention — geometry\n axis only, no paint glow. R537 adds the paint axis,\n composing with R181/R402 pin-ring (separate concen-\n tric circle in the same row.fill color) so on\n hover/pin the SWATCH AND its pin-ring both contri-\n bute to a unified tier-coloured glow signature.\n Hue: row.fill (status hex) concatenated with `99`\n hex alpha (~60%). Working green / idle teal /\n offline slate each glow in their OWN tier color\n — the legend acts as a color-keyed status mirror.\n 3px blur reads soft; 60% alpha legible without\n overwhelming the swatch's own paint.\n Drop-shadow visual-polish family extension (13\n anchors). filter is paint-only; bbox unchanged.\n transition list extends to include 'filter 150ms\n ease-out', matching the existing R197 r 150ms\n cadence at this swatch. data-legend-swatch-glow\n attr exposes the gate state for tests. */}\n <circle\n cx=\"16\" cy={row.y0}\n r=\"6\"\n fill={row.fill}\n data-legend-swatch={row.key}\n data-legend-swatch-state={isPinned ? 'pinned' : isRowHovered ? 'hover' : isMemberAliasMatching ? 'member-alias-matching' : 'idle'}\n data-legend-swatch-glow={isSwatchLifted ? 'true' : 'false'}\n data-legend-swatch-member-alias-matching={isMemberAliasMatching ? 'true' : 'false'}\n /* Round 578 — legend swatch joins per-element brightness\n family at 16th anchor. Stacks brightness(1.15) onto\n R537 drop-shadow. Closes chip-row tier-color glow\n trio at consistent stacked-filter pattern alongside\n R542 pressure-seg (already stacks brightness 1.2)\n and the sibling vendor chip (R578-sibling). */\n data-legend-swatch-brightness={isSwatchLifted ? '1.15' : '1'}\n style={{\n r: isSwatchLifted ? '7px' : '6px',\n /* R665 — legend swatch drop-shadow gains a 2nd\n outer layer at 6px + 0x4c alpha (half R537 inner\n 0x99). 24th anchor in multi-layer halo family.\n Same 3+6 stride + 50% falloff as R649 legend\n pin-ring. row.fill tier color (working green /\n idle teal / offline slate) preserved across\n both layers. */\n filter: isSwatchLifted\n ? `drop-shadow(0 0 3px ${row.fill}99) drop-shadow(0 0 6px ${row.fill}4c) brightness(1.15)`\n : undefined,\n transition: 'r 150ms ease-out, filter 150ms ease-out',\n } as React.CSSProperties}\n />\n {/* R61 pinned-state ring — concentric stroke at r=8 in\n the row colour, draws OUTSIDE the swatch so it\n doesn't fight the fill colour the user is matching.\n Round 181 / Loop: the ring used to mount/unmount\n with the conditional render, snapping on every\n pin/unpin. Now always mounted with opacity gated\n by isPinned + a 150ms transition so the ring\n eases in on pin and out on unpin — same gesture\n vocabulary the R165/R180 smooth-pin-mirror family\n uses for the chip-row pin chips. strokeWidth=1.5\n is the R51 overlap-test sentinel but the test\n selector is gated to g[data-node] ancestors,\n so this legend-internal circle is invisible to\n that probe. pointerEvents:none so the ring can't\n intercept the row click that produced it. */}\n {/* Round 402 / Loop: legend pin-ring strokeWidth 1.5\n → 1.75. Sibling visual-weight bump (12th anchor)\n to R385 hub hover-ring strokeWidth 1.5 → 1.75 —\n both are pin/hover state indicators painted as\n stroke-only circles outside their target swatch\n with the R51 sentinel value 1.5. R402 lifts to\n 1.75 (matching R385's choice) so the pin signal\n reads slightly heavier without crossing the\n R51 sentinel band (3 reserved for offline node).\n The R51 selector is gated to g[data-node]\n ancestors so this legend-internal circle (lives\n under a <g data-legend-status>) is invisible\n to the probe — same lesson R177/R385 documented.\n Visual-weight bump family (12 anchors now):\n R287 minimap viewport stroke 1 → 1.5\n R295 legend swatch radius 5.5 → 6\n R359 recent-row pip radius 1.6 → 1.8\n R360 hub digit fontSize 11 → 12\n R361 edge-badge digit fontSize 10 → 11\n R365 hub-highlight radius 5 → 5.5\n R367 edge-badge rest stroke 1 → 1.25\n R374 pressure-bar height 1.5 → 2\n R383 recent-row pip radius 1.8 → 2.0\n R384 minimap online dot 1.7 → 1.9\n R385 hub hover-ring stroke 1.5 → 1.75\n R402 legend pin-ring stroke 1.5 → 1.75 (this round)\n R181 always-mount opacity gate + 150ms transition\n + pointerEvents:none all preserved. data-legend-\n pin-ring-stroke-width attr exposes the value for\n tests. */}\n {/* Round 477 / Loop — legend pin-ring gains a filter:\n drop-shadow glow on isPinned. Extends R476's\n drop-shadow idiom from hub-digit (focal scope)\n to the legend-row pin-ring (sibling pin-state\n surface). When a status row is pinned, the\n concentric ring around the swatch now lights\n up with a colour-matched halo using row.fill,\n reinforcing \"this filter is locked\" via a\n glow layer above the R402 sw bump + R181\n opacity fade-in.\n Hue: row.fill at 0.55 alpha — picks up each\n status tier's signature colour (working green /\n idle teal / offline slate). 3px blur stays\n subtle but unmistakable when the row is locked.\n Reduced-motion users skip the filter via R29\n a11y blanket (transition-duration → 0.001ms\n so the glow appears/disappears instantly with\n pin toggle).\n Filter is paint-only — bbox unchanged, R51\n overlap-test gated to g[data-node] descendants\n so this legend-internal ring is invisible to\n the probe anyway. Transition list extends to\n include 'filter 200ms ease-out' so the glow\n eases under the same cadence as opacity. */}\n <circle\n cx=\"16\" cy={row.y0} r=\"8\"\n fill=\"none\"\n stroke={row.fill}\n strokeWidth=\"1.75\"\n opacity={isPinned ? 1 : 0}\n data-legend-pin-ring={row.key}\n data-legend-pin-ring-pinned={isPinned ? 'true' : 'false'}\n data-legend-pin-ring-stroke-width=\"1.75\"\n data-legend-pin-ring-glow={isPinned ? 'true' : 'false'}\n data-legend-pin-ring-brightness={isPinned ? '1.15' : '1'}\n data-legend-pin-ring-halo-layers={isPinned ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n /* R607 — legend pin-ring stacks brightness(1.15)\n onto R477's pin-gated drop-shadow. Extends the\n pin-gated brightness family to a 3rd anchor:\n R571 group label text (pin-gated)\n R587 group cluster box (pin-gated)\n R607 legend pin-ring (pin-gated) ← this round\n Same banked R582/R583 stacked-filter pattern.\n Pin-ring 3-axis pin signature now:\n R181 opacity 0 → 1\n R477 drop-shadow → row.fill 88 alpha\n R607 brightness 1 → 1.15 ← this round\n The row.fill tier color (green working / teal\n idle / slate offline) brightens ~15% alongside\n the drop-shadow halo — the pin-ring reads as\n \"lit + locked\" instead of just \"locked\". The\n color signal (which tier you pinned) reads more\n vividly under the brightness lift.\n Existing 'filter 200ms ease-out' transition\n covers brightness ease at the same cadence. */\n /* Round 649 / Loop — legend pin-ring drop-shadow\n gains a SECOND outer layer at 6px blur + 0x44\n alpha (half R477's inner 0x88). Extends multi-\n layer halo family (R642-R648) to the 3rd panel-\n tier anchor — legend filter chip pin signal.\n Inner layer (R477): 3px + 0x88 alpha (~53%),\n row.fill tier color (working green / idle\n teal / offline slate) — sharp pin halo\n Outer layer (R649): 6px + 0x44 alpha (~27%),\n same row.fill tint — soft ambient falloff\n Same R642-R648 0.5x falloff (0x88 → 0x44) + 2x\n blur stride (3 → 6) vocabulary. The pin-ring\n now radiates a near + far tier-coloured glow\n when a status filter is locked in — visually\n emphasises which tier the user has pinned at\n the panel-row scope.\n data-legend-pin-ring-halo-layers attr exposes\n the gate ('2' pin / '0' rest). */\n filter: isPinned\n ? `drop-shadow(0 0 3px ${row.fill}88) drop-shadow(0 0 6px ${row.fill}44) brightness(1.15)`\n : undefined,\n transition: 'opacity 150ms ease-out, filter 200ms ease-out',\n }}\n />\n {/* Round 219 / Loop: legend row text gains the same\n letter-spacing pin signature R218 added to group\n labels — 0px → 0.5px when isPinned. Pre-R219 the\n legend row's hover and pin states shared fill\n colour (R55/R61 both brighten to legendHeadline)\n so the text was typographically identical at the\n letter-form level for transient hover vs sticky\n pin. R181 pin ring + R197 swatch grow + R143 row\n lift differentiated the row chrome; R219 adds the\n text-level signature so the LABEL itself reads\n \"locked in\" at type. Mirror of R218's group label\n treatment — chrome-level + type-level pin\n vocabulary now unified across both interactive\n label surfaces (group + legend). transition adds\n `letter-spacing 150ms` alongside R55 fill 150ms;\n same ease pace, same beat. */}\n <text\n x=\"30\" y={row.y1}\n fill={hoveredStatus === row.key || isPinned ? pal.legendHeadline : pal.legendText}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n /* Round 364 / Loop: legend-row label fontWeight 400\n → 500. Sibling typography lift to R363 recent-row\n text fw 400 → 500. Both surfaces render small\n monospace text against panel chrome at fontSize\n 9-11 where SVG-default fw 400 sits at the\n legibility floor. font-medium tier (500) gives\n the label a more deliberate-data register.\n The R309 per-row count text (separate element\n below at x=215 textAnchor=end) keeps its own\n fontWeight 600 inline override, so the count >\n label hierarchy stays intact at the legend\n scope same as R363 holds it at the recent-row\n scope:\n legend label fw 500 (R364, this round)\n legend count fw 600 (R309)\n recent alias fw 500 (R363)\n recent count fw 600/700 (R320)\n data-legend-row-label-font-weight attr exposes\n the value for tests. R219 letter-spacing pin\n tween + R55 fill transition + R181 always-mount\n pin ring all preserved. */\n /* Round 531 / Loop — extends hover-fw family (R416/\n R420/R425/R520/R521/R522/R530, 7 anchors) to an\n 8th anchor at the legend-row label. Pre-R531\n R364 set fw=500 statically; hover/pin lifted\n other axes (R55 fill brighten / R433 letter-\n spacing 3-tier / R181 pin ring) but the fw\n stayed flat. R531 mirrors R530's recent-row\n alias pattern at the legend-row label scope.\n Hover OR pin (hoveredStatus===row.key ||\n isPinned) lifts fw to 600, matching the\n legend-row count tier (R309 fw=600 / R446\n pin lift 600→700). Active label now reads at\n the count's data tier — sibling treatment to\n R530 recent-row.\n Hover-fw family extension (8 anchors):\n R416 chip-row count digit\n R420 chrome zoom-level\n R425 hub-center digit\n R520 +N more flows footer\n R521 chrome nodeSize S/M/L inactive\n R522 chrome layout Ring/Grid inactive\n R530 recent-row alias text\n R531 legend-row label ← this round\n Two panel-row label surfaces (R530 recent-\n row alias + R531 legend-row label) now have\n parallel hover-fw signatures. R475 cadence\n at 200ms already covers font-weight via the\n existing transition list extension at this\n element. data-legend-row-label-font-weight\n attr flips '500' → '600' on isActive (was\n static '500' pre-R531). */\n fontWeight={(hoveredStatus === row.key || isPinned) ? '600' : '500'}\n data-legend-row-label={row.key}\n data-legend-row-label-pinned={isPinned ? 'true' : 'false'}\n data-legend-row-label-hovered={!isPinned && hoveredStatus === row.key ? 'true' : 'false'}\n data-legend-row-label-font-weight={(hoveredStatus === row.key || isPinned) ? '600' : '500'}\n /* Round 433 / Loop: legend-row text extends from\n R219's pin-only letter-spacing (0px → 0.5px on\n isPinned) to a 3-tier scale matching the R432\n group-label pattern:\n rest → 0px\n hoveredStatus → 0.25px ← this round\n isPinned → 0.5px (R219 preserved)\n Pre-R433 hover already brightened the fill\n (hoveredStatus===row.key || isPinned matches the\n legendHeadline branch) but the letter-form\n stayed dead-typographic on transient hover —\n only the pin tier carried a kerning signature.\n R433 adds the missing mid tier so hover\n telegraphs through BOTH fill brighten AND a\n subtle 0.25-px kerning spread, mirroring\n R427/R431/R432 at legend-row scope. Pin tier\n still wins so the locked vs preview distinction\n at the type level stays intact.\n Hover-letter-spacing family extension (9 anchors\n now): R344/R345/R347/R351/R420/R427/R431/R432/\n R433. 3-tier letter-spacing pattern now spans 4\n surfaces (node-alias R427, edge-badge R431,\n group-label R432, legend-row R433). R55 fill\n 150ms + R219 letter-spacing 150ms transition\n untouched — additive conditional case. */\n /* Round 475 / Loop — final closure of the panel-row\n text scope cadence-sync. R473 lifted the legend-\n row TINT RECT to 200ms; R474 lifted the recent-\n row TEXT to 200ms; R475 closes the matching\n legend-row text desync — fill + letter-spacing\n both 150 → 200ms ease-out. After R475 the 3-tier\n panel-row cadence family is fully 200ms across\n BOTH rect and text at every panel-row scope\n (group-label / recent-row / legend-row). Hover/\n pin state-flip at any panel-row tier reads as\n one motion-coherent unit. data-legend-row-\n label-transition='200ms' attr exposed for tests.\n R433 3-tier letter-spacing values (0/0.25/0.5)\n unchanged; R55 fill brighten unchanged — only\n the timing axis shifts. */\n data-legend-row-label-transition=\"200ms\"\n /* Round 569 / Loop — extends R568's panel-row drop-\n shadow pattern to the SIBLING legend-row label.\n Symmetric closure of the panel-row drop-shadow\n across both panel surfaces (recent + legend).\n Pre-R569 the legend-row label had 3 hover/pin\n axes (R55 fill + R433 ls 3-tier + R531 fw); R569\n adds the 4th paint axis to match R568 recent-\n row text exactly.\n Two-tier paint-axis cascade now SYMMETRIC across\n both side panels:\n recent panel title (R550) + row text (R568)\n legend panel title (R550-sibling) + label (R569) ← this round\n Each panel has glow at BOTH chrome tiers (title\n active + row hover/pin). The 4-axis row signature\n (fill + ls + fw + glow) is now identical at both\n panel-row text scopes — completes the panel-row\n text-treatment parity.\n Hue/blur/cadence: same as R568 (pal.legendAccent\n + hex alpha 80, 2px blur, 200ms ease-out). Gate\n matches R531/R433 (hoveredStatus === row.key ||\n isPinned) — single boolean drives all 4 axes\n together for motion-coherent state-flip.\n Drop-shadow family extends to 18 anchors total\n (R568 was 17; R569 = 18).\n data-legend-row-label-glow attr added for tests. */\n data-legend-row-label-glow={(hoveredStatus === row.key || isPinned) ? 'true' : 'false'}\n /* Round 572 / Loop — sibling to recent-row text above.\n Stacks brightness(1.15) onto R569's drop-shadow at\n legend-row label scope (10th anchor in per-element\n brightness family). Matches recent-row text 4-axis\n signature exactly: fill + ls + fw + drop-shadow +\n brightness now BOTH panel-row text surfaces. */\n data-legend-row-label-brightness={(hoveredStatus === row.key || isPinned) ? '1.15' : '1'}\n style={{\n transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',\n letterSpacing: isPinned ? '0.5px' :\n hoveredStatus === row.key ? '0.25px' : '0px',\n /* R665 sibling — legend-row label drop-shadow gains\n a 2nd outer layer at 4px + 0x40 alpha (half R589\n inner 0x80). Same 2+4 stride + 50% falloff as\n R659 recent-row text. pal.legendAccent tint\n preserved across both layers. */\n filter: (hoveredStatus === row.key || isPinned)\n ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)`\n : undefined,\n }}\n >{row.label}</text>\n {/* R95: live count anchored to the right edge of the\n panel (x=215, after the flow-arrow swatch). Same\n counts the chip-row shows (\"3 working\" etc.) but\n here next to the swatch the user is matching —\n saves crossing the canvas to the chip row for\n the number. text-anchor=end aligns the column\n visually like a table. pointerEvents:none so the\n count doesn't intercept the row hover hitbox.\n\n Round 204 / Loop: count text recedes when the\n tier is empty. Pre-R204 the \"0\" sat at the same\n opacity 0.65 as \"12\" — visually identical, so\n the eye got zero signal that a status tier was\n empty unless the operator read the digit. R204\n drops empty rows to 0.30 (dark) / 0.28 (light)\n so empty tiers fade into the panel chrome while\n populated tiers stay visually prominent. R204 a\n crossing zero / coming back from zero eases via\n the existing 150ms opacity transition. data-\n legend-count-empty exposes the binary signal\n for tests. */}\n {/* Round 239 / Loop: legend count text gains a tier-\n coloured fill on hover/pin, completing the hover-\n deepen-own-hue idiom at this surface. Pre-R239 the\n count digit's opacity bumped 0.65→0.95 on hover\n (R204 thinning) but its fill stayed at the neutral\n pal.legendText gray — same digit, brighter gray,\n no tier identity. R239 flips fill to row.fill\n (green/teal/slate per tier) when the row is\n hovered OR pinned, so the count lights up in its\n OWN colour, matching the swatch directly above it.\n The whole row now reads as one tier-coloured unit\n under cursor (swatch + label + count); R55/R197\n already do this for swatch + label, R239 closes\n the trio at the count. Opacity transition stays at\n 150ms; fill joins the same transition list at 150ms\n so the colour shift eases alongside the opacity\n ramp. data-legend-count-fill exposes the active\n fill state for tests; empty tiers (row.count===0)\n stay at pal.legendText regardless — empty doesn't\n get to claim tier identity. 8th surface in the\n hover-deepen-own-hue family. */}\n {/* Round 274 / Loop: legend per-row count picks up\n tabular-nums (sibling treatment to R225's recent-\n signal panel header flow-count + R230's group-\n label pip strip). The text uses fontFamily=\n 'monospace' which is typically tabular by\n nature, but some monospace implementations have\n subtle digit-pair width variance (e.g., '0' vs\n '1' at the visual boundary). Explicit\n fontVariantNumeric: 'tabular-nums' is belt-and-\n suspenders: locks digit widths regardless of\n the rendered monospace font, so the count\n column stays planted as offline/idle/working\n counters roll across 9→10 / 99→100 thresholds.\n Pure CSS-level addition, no layout shift.\n 10th surface in the info-density tabular-nums\n sweep family. */}\n {/* Round 309 / Loop: legend per-row count gains\n fontWeight=600 (semibold). The count is the\n DATA the operator scans (how many working /\n idle / offline nodes); the row label is the\n stable status word. Default weight (400) on\n both makes them visually equal — but a glance-\n first read pattern needs the digit to register\n faster than the label.\n fontWeight=600 gives the digit semibold\n emphasis (matching the h2 'Command mesh'\n semibold in the title block) while the row\n label stays at default 400/normal — a clean\n 'digit semibold > label regular' hierarchy\n that makes the count the optical anchor in\n each row. After R307+R308 simplified the\n labels to single status words, the count\n becomes proportionally more important; this\n round emphasizes that role typographically. */}\n {/* Round 446 / Loop: legend per-row count fontWeight\n lift 600 → 700 on isPinned. Mirror of R444 group-\n label-count + R445 recent-row-count at the\n legend-row scope. Closes the 3-panel-row family\n for the \"data tightens under attention\" pattern —\n every panel-row count now responds to pin with a\n typographic-weight bump:\n R444 group-label-count 500 → 600\n R445 recent-row-count 600 → 700 (cold-pin route)\n R446 legend-row-count 600 → 700 ← this round\n Hover gate (hoveredStatus===row.key) keeps rest\n fw=600 so the locked-vs-preview distinction at\n the type level stays intact — same gate R433 used\n on the parent <text> letter-spacing tween. R309\n fw=600 baseline + R204 empty-row opacity dim +\n R225 tabular-nums all preserved. transition list\n extends to include 'font-weight 150ms ease-out'\n matching R433 fill/letter-spacing cadence.\n data-legend-count-pinned + -font-weight attrs\n exposed for tests. */}\n <text\n x=\"215\" y={row.y1}\n textAnchor=\"end\"\n fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? row.fill : pal.legendText}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n fontWeight={isPinned ? '700' : '600'}\n /* Round 449 / Loop: legend-row count active-state\n opacity 0.95 → 1.0 on (hoveredStatus===row.key\n || isPinned). Pre-R449 R204 lifted populated-row\n active opacity from rest 0.65 to 0.95 — visibly\n brighter but kept a 5 pct alpha gap (1 - 0.95).\n R449 closes the gap to 1.0 so the active count\n reads as confidently present alongside the R446\n fw=600→700 + R433 letter-spacing tween. Theme-\n consistency / canvas-presence family extension\n (7th anchor on the active-presence lift sub-\n family): R370 hub hover-ring 0.7→0.8, R371 edge-\n badge rest 0.82→0.85, R372 minimap offline-dot\n 0.5→0.6, R386 hub-highlight idle 0.9→0.95, R387\n hover-detail panel 0.94→0.97, R429 label-card\n body 0.94→1.0, R449 legend-count active 0.95→1.0\n ← this round. Empty-row opacity (R204: 0.28\n light / 0.30 cyber) and idle 0.65 rest both\n preserved. */\n opacity={row.count === 0\n ? (isLight ? 0.28 : 0.30)\n : (hoveredStatus === row.key || isPinned ? 1 : 0.65)}\n data-legend-count={row.key}\n data-legend-count-empty={row.count === 0 ? 'true' : 'false'}\n data-legend-count-pinned={isPinned ? 'true' : 'false'}\n data-legend-count-font-weight={isPinned ? '700' : '600'}\n data-legend-count-fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? 'tier' : 'neutral'}\n /* Round 518 / Loop — extends R433's 3-tier hover-\n letter-spacing tween from the legend-row LABEL\n (text at x=30) to the SIBLING legend-row COUNT\n digit (this text at x=215). Pre-R518 the row's\n label spread on hover/pin (R433: 0/0.25/0.5px)\n while the count digit at the row's right edge\n stayed dead-typographic — same row, two halves,\n asymmetric kerning gesture. R518 mirrors the\n 3-tier scale at the count so the WHOLE row's\n typography reads as one unit under cursor: label\n + count spread together at matching values.\n Tabular-nums (R225) makes the kerning still\n visible on 2-digit counts — each digit cell\n keeps its fixed width, but the inter-digit\n advance grows. R518 also closes R475's panel-\n row TEXT cadence at the count surface — R475\n lifted the label text transitions to 200ms but\n the count was missed; R518 lifts opacity / fill\n / font-weight from 150 → 200ms AND adds the new\n letter-spacing axis at 200ms. One transition\n list, one cadence, one motion-coherent multi-\n axis hover/pin signature across the row.\n Hover-letter-spacing family extension (10\n anchors now): R344/R345/R347/R420/R427/R431/\n R432/R433/R517/R518. R518 closes the legend-\n row pair (label R433 + count R518). data-\n legend-count-letter-spacing attr exposes the\n resolved value for tests. */\n data-legend-count-letter-spacing={isPinned ? '0.5px' : hoveredStatus === row.key ? '0.25px' : '0px'}\n data-legend-count-transition=\"200ms\"\n /* R590 — legend-row count gains filter brightness(1.15)\n on hover/pin. 29th anchor in per-element brightness\n family. Closes legend-row label↔count brightness\n parity within each row:\n legend label (R572, 10th anchor — stacks w/ DS)\n legend count (R590 ← this round, plain brightness)\n Plain brightness (no DS stack) because the count\n has its own multi-axis hover signature already\n (opacity 0.65 → 1.0, fill neutral → row.fill tier\n color, fw 600 → 700 on pin, letter-spacing 3-tier)\n — adding brightness amplifies the tier-color fill\n to its hover-locked brightest possible.\n Triple multiplicative interaction: opacity 1.0 ×\n tier-color fill × brightness(1.15) — the count\n reads as confidently \"this is the active tier\".\n Existing transition list extends with 'filter\n 200ms ease-out' matching the existing 200ms\n cadence across opacity / fill / fw / letter-\n spacing.\n Legend-row scope now has parity with recent-row:\n both row text and count surfaces brighten +15%\n on row inspection. Recent-row count INHERITS its\n brightness via the parent <text>'s R572 filter\n (single ancestor filter covers all child tspans);\n legend-row count is a sibling <text> so it needs\n its OWN filter — R590 supplies that.\n data-legend-count-brightness attr exposes gate. */\n data-legend-count-brightness={(hoveredStatus === row.key || isPinned) ? '1.15' : '1'}\n /* R671 — legend-row COUNT joins the multi-layer halo\n family. Pre-R671 the count had brightness(1.15) only\n on hover/pin (R590) — explicitly plain because the\n count already had a 4-axis lift (opacity / fill / fw\n / ls). R671 extends to 2-layer drop-shadow using\n row.fill (PER-TIER color: working emerald / idle\n teal / offline slate) at 3+6 stride with alpha 99/4c\n — same convention as R665 legend swatch (also\n row.fill tint). Per-tier brightness now compounds\n per-tier halo at the count surface — the count\n reads as \"this status tier lit up\" with its OWN\n hue intensified AND a near-far hue glow.\n\n Legend-row tier now FULLY halo-extended (sibling\n closure with R665):\n R665 swatch (row.fill 3+6 alpha 99/4c)\n R665 row label (pal.legendAccent 2+4 alpha 80/40)\n R671 row count (row.fill 3+6 alpha 99/4c) ← this round\n\n 30th anchor in multi-layer halo family. */\n data-legend-count-halo-layers={(hoveredStatus === row.key || isPinned) ? '2' : '0'}\n style={{\n pointerEvents: 'none',\n filter: (hoveredStatus === row.key || isPinned)\n ? `drop-shadow(0 0 3px ${row.fill}99) drop-shadow(0 0 6px ${row.fill}4c) brightness(1.15)`\n : undefined,\n transition: 'opacity 200ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n letterSpacing: isPinned ? '0.5px' :\n hoveredStatus === row.key ? '0.25px' : '0px',\n }}\n >{row.count}</text>\n </g>\n );\n })}\n {/* Flow-arrow swatch tracks the offline row — R106 shifted\n rows down by 8 px to make space for the panel header so\n this moves from y=72 to y=80. Drop its pointerEvents so\n the offline legend row stays hoverable (R55). It's\n decoration, no need to receive events. */}\n {/* Round 254 / Loop: legend flow-arrow swatch stroke\n transition for theme toggle (cyber #67e8f9 ↔ light\n #10b981). Last theme-driven legend element snap. */}\n {/* Round 277 / Loop: flow-arrow path tracks new offline-row\n cy=68 after the legend panel compress (was 80 pre-R277).\n Endpoints follow the offline row to keep the swatch\n logically tied to the row it demonstrates; control point\n proportionally shifts so apex stays mid-arc between\n rows 2 and 3. */}\n <path\n d=\"M140,68 Q164,44 196,68\"\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth=\"3\"\n markerEnd=\"url(#topo-arrow)\"\n data-legend-flow-arrow\n data-legend-flow-arrow-glow={hoveredPanel === 'legend' ? 'true' : 'false'}\n data-legend-flow-arrow-brightness={hoveredPanel === 'legend' ? '1.15' : '1'}\n style={{\n pointerEvents: 'none',\n /* R609 — legend flow-arrow gains stacked drop-shadow +\n brightness on hoveredPanel === 'legend'. Banked\n R582/R583 stacked-filter pattern at the legend\n decoration scope.\n\n The arrow is the legend's \"what an edge flow looks\n like\" representation — when user hovers the legend\n panel to inspect rows, the demo arrow lights up\n too, tying it visually to the panel-wide hover\n gesture. Same pal.flowEdge cyan/teal hue as live\n edges, so the demo and the real edges share a\n coherent visual vocabulary.\n\n Pure paint axis: drop-shadow halo + brightness\n stack on the same panel-hover gate. transition\n list extends with 'filter 200ms ease-out'\n alongside the existing stroke 200ms cadence.\n\n data-legend-flow-arrow-glow + -brightness attrs\n expose the gate for tests. */\n /* R665 sibling — legend flow-arrow drop-shadow gains a\n 2nd outer layer at 6px + 0x40 alpha (half inner 0x80).\n Same 3+6 stride + 50% falloff. pal.flowEdge tint\n (cyber cyan / light emerald) preserved across both\n layers. */\n filter: hoveredPanel === 'legend'\n ? `drop-shadow(0 0 3px ${pal.flowEdge}80) drop-shadow(0 0 6px ${pal.flowEdge}40) brightness(1.15)`\n : undefined,\n transition: 'stroke 200ms ease-out, filter 200ms ease-out',\n }}\n />\n </g>\n\n {/* Round 282 / Loop: sleep2agi brand watermark per Vincent\n 5215 ask (relayed via 通信龙). Plain monospace text at\n canvas bottom-left (the only fully-empty corner — top\n corners hold recent-signal + legend panels, bottom-\n right holds the chrome strip). No icon yet — public/\n has only favicon.svg (small abstract network icon\n with hardcoded #0b0b0d dark bg that wouldn't blend on\n light theme) + intern_avatar.png (书生 brand-specific).\n Without a sleep2agi-specific crescent/lockup asset,\n R282 ships a low-opacity text-only mark; R283+ can\n swap in the real logo if Vincent provides the asset.\n\n Position: x=16 (matches the 16-unit SVG inset that the\n corner panels use); y=672 (≈12 px from viewBox bottom\n y=680, descender ≈ y=675, so the entire glyph sits\n clear of the bottom edge). Theme-aware fill:\n pal.legendText (cyber #94a3b8 slate-400 ↔ light\n #475569 slate-600). 0.4 opacity makes it a\n watermark — present but not visually loud. Pointer-\n events:none so it can't intercept clicks on the\n canvas backdrop.\n\n Note: the brand mark is INTENTIONALLY in a corner\n that no overlay/panel occupies, AND it's purely\n decorative additive after 7 rounds of 减法 (R275-\n R281). Adds 1 small text element back into the\n canvas — but Vincent specifically asked for it. */}\n {/* Round 289 / Loop: brand watermark picks up letterSpacing\n 0.5px. For a 9-character wordmark at fontSize 11 monospace,\n 0.5px between characters (8 gaps × 0.5 = 4px total\n widening) lifts \"sleep2agi\" from \"body text that happens\n to be a name\" to \"deliberate wordmark register\". Same\n R285-family idiom (kicker tracking-widest, title\n tracking-tight) applied to the brand mark — letter-\n spacing as typographic intent. Stays well inside the\n bottom-left corner; opacity 0.4 unchanged so the\n watermark stays a watermark. */}\n {/* Round 519 / Loop — 呼吸感 family 3rd anchor. Pre-R519 the\n breath family had 2 anchors (R497 hub idle digit + R498\n recent-row hot pulse). Both signal active state — the\n digit when canvas is idle (no work pending), the recent\n row when fresh signal arrives. R519 adds a SLOW ambient\n breath to the brand watermark — present always, not gated\n on activity state. The watermark IS the canvas-corner\n register that says \"the canvas is alive even when nothing\n is happening\"; a 6s opacity pulse around its 0.4 mean\n (±0.08 swing → 0.32 ↔ 0.48) reads as ambient liveness\n rather than foreground signal.\n Why 6s (not R497's 4s): the breath family now spans\n activity registers (R497 4s — idle-focal: present and\n waiting; R498 ~3s — hot signal: just arrived) and now\n ambient register (R519 6s — corner watermark: always-on\n background). Slower cadence keeps the watermark in the\n background; ~10 pct slower than R497 keeps it out of\n phase so the two anchors never beat together visibly.\n Gate: !reducedMotion. Inside the prefers-reduced-motion\n media query, SMIL animate isn't covered by globals.css\n R29 (which only kills CSS animation property), so we\n gate at JSX level — when reducedMotion is true the\n <animate> child isn't mounted and opacity stays at the\n static 0.4. data-topo-brand-watermark-breath attr\n exposes the gate state for tests.\n 呼吸感 family extension (3 anchors): R497 hub idle / R498\n recent-row hot / R519 brand watermark ambient. */}\n {/* Round 525 / Loop — focal-recede family 3rd anchor. R507\n receded the hub-center workingCount digit; R508 receded\n the hub-highlight disc; both fade to 0.85× when any non-\n hub canvas surface is hovered (alias / edge / group /\n status / vendor) — the \"you're inspecting elsewhere\"\n gesture. R525 extends the pattern to the brand watermark\n at canvas bottom-left, the always-on decorative brand\n element. Pre-R525 the watermark stayed at its R519\n breath baseline (0.32-0.48 SMIL pulse) regardless of\n canvas attention; post-R525 it fades to 70% wrapper\n opacity (effective 0.224-0.336 with breath) when canvas\n attention is elsewhere, matching the same focal-recede\n semantic R507/R508 establish at the hub focal cluster.\n Implementation: wrap the existing <text> in a <g>\n wrapper whose opacity multiplies with the inner text's\n SMIL-animated opacity. SVG opacity composes\n multiplicatively across the parent/child chain, so:\n normal: g.opacity=1.0 × text.opacity(SMIL 0.32-0.48) = 0.32-0.48\n recede: g.opacity=0.7 × text.opacity(SMIL 0.32-0.48) = 0.224-0.336\n SMIL on inner text continues running through both\n states; only the wrapper opacity flips. 300ms ease-out\n transition on wrapper (matches R508 hub-highlight recede\n transition).\n Gate matches R507/R508 — focal-recede is a UNIFIED\n non-hub-canvas-hover signal driving multiple anchors,\n so all three (hub digit / hub-highlight / brand\n watermark) fade together as the canvas's decorative\n register, leaving only the surface under inspection\n foregrounded.\n Focal-recede family extension (3 anchors): R507 hub\n digit / R508 hub-highlight / R525 brand watermark. */}\n {/* Round 704 / Loop — brand watermark WRAPPER gains an envelope\n breath at 15 s — the slowest tier in the respiratory rolodex.\n The inner \"sleep2agi\" text already breathes at 6 s (R519);\n the crescent moon at 7 s; this round adds an outer wrapper\n envelope at 15 s so the brand area has a layered respiratory\n effect — fast inner breaths nested inside a slow outer one.\n At wrapper trough (opacity 0.85) × inner text trough (0.32)\n → effective 0.272; at both peaks → 0.48. Soft envelope.\n\n Cadence: 15 s — slowest tier yet, sits at the top of the\n rolodex above the 10 s H2. Heaviest visual register\n (the only fixed-position corner watermark) deserves the\n slowest cadence — \"calmest signal of life at the canvas's\n quietest corner\". Tighter alpha range (0.85 ↔ 1, ~15%)\n matching R703 zoom-level — tabular/decorative surfaces\n shouldn't drift too far from full opacity.\n\n Hover gate via CSS:\n [data-topo-brand-watermark-recede=\"true\"] { animation: none }\n When user hovers a node/edge/group on the canvas, the\n wrapper's focal-recede (R525) drops opacity attr to 0.7;\n CSS animation gate-off lets that attr take effect cleanly,\n no breath underneath. Pattern sibling to R703 zoom-level\n data-attr selector gate. prefers-reduced-motion: reduce\n also neutralizes, joining the R699/R702/R703 a11y guard\n family.\n\n Rolodex (11 anchors now, 9-tier ladder):\n 3 s row hot · 4 s hub idle · 5 s brand logo\n 6 s watermark text · 6 s kicker · 7 s crescent\n 8 s recent title · 8 s legend title\n 9 s zoom-level readout\n 10 s H2\n 15 s watermark wrapper envelope ← this round\n\n data-topo-brand-watermark-envelope-breath attr surfaces\n the cadence for tests. */}\n <g\n opacity={(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) && !hoveredHub ? 0.7 : 1}\n className=\"anet-topo-brand-watermark-envelope-breath\"\n data-topo-brand-watermark-wrapper\n data-topo-brand-watermark-recede={\n (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'\n }\n data-topo-brand-watermark-envelope-breath=\"15s\"\n style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out' }}\n >\n <text\n x=\"16\" y=\"672\"\n fontSize=\"11\" fontFamily=\"monospace\" fontWeight=\"600\"\n letterSpacing=\"0.5\"\n fill={pal.legendText}\n opacity=\"0.4\"\n data-topo-brand-watermark\n data-topo-brand-watermark-breath={reducedMotion ? 'false' : 'true'}\n /* Round 722 / Loop — watermark promoted to 2ND triple-axis\n surface. CSS text-shadow axis joins the SMIL opacity (R519)\n + SMIL letter-spacing (R712) axes — all 3 at 6 s in phase.\n Mirrors R721 kicker; together they form the \"6 s triple-\n axis pair\", the first multi-member triple-axis structural\n pattern in the family. Gated by reducedMotion at the JSX\n level (sibling to the SMIL <animate> children below) so\n the CSS animation has the same a11y guard. See globals.css\n R722 block for glow tuning rationale. */\n data-topo-brand-watermark-glow-breath={reducedMotion ? 'false' : '6s'}\n className={reducedMotion ? undefined : 'anet-topo-brand-watermark-glow-breath'}\n style={{ pointerEvents: 'none', transition: 'fill 200ms ease-out' }}\n >{/* Round 730 / Loop — SVG <title> a11y child. Screen readers\n read this as the accessible name of the element; mouse\n tooltips don't fire because the wrapper has pointer-events:\n none (R282 — keeps the watermark non-interactive). The\n title text describes role + identity: \"sleep2agi\" is the\n brand name; \"brand watermark\" the structural role. */}<title>sleep2agi · brand watermark</title>sleep2agi{!reducedMotion && (\n <animate attributeName=\"opacity\" values=\"0.32;0.48;0.32\" dur=\"6s\" repeatCount=\"indefinite\" />\n )}{!reducedMotion && (\n /* Round 712 / Loop — watermark gains a SECOND breath axis,\n mirroring R711's dual-axis innovation on a different\n primary-identity surface. R519 already breathes opacity\n on the watermark at 6 s (0.32 ↔ 0.48); R712 adds an SVG\n <animate> for letter-spacing at the same 6 s cadence\n (0.45 ↔ 0.55, oscillating around the R289 static 0.5\n baseline). Two axes share cadence → IN PHASE breath:\n at the dimmest moment the text is also at its tightest\n kerning; at the brightest moment, widest kerning. Like\n a single coherent inhale-exhale, mirroring the H2 dual-\n axis (R711) but with letter-spacing instead of scale\n since SVG <text> letter-spacing is a more natural\n 2nd-axis for a typographic wordmark.\n\n Why ±0.05 units: at fontSize 11 monospace, 0.05 unit ≈\n half a pixel of widening across 8 inter-glyph gaps =\n ~4px total text-width swing. Wide enough to be felt\n as \"the wordmark breathes\" but well within the empty\n bottom-left corner — no overlap test risk (the corner\n is intentionally empty per R282 placement).\n\n Pattern budget extension: R711 H2 = dual-axis on HTML\n text; R712 watermark = dual-axis on SVG text. Two\n primary-identity surfaces, two media (HTML/SVG), one\n coherent \"primary-identity surfaces get dual-axis\"\n rule. R710 rolodex catalog stable — R712 adds axis\n to existing 6s anchor; catalog tracks cadences not\n axes. */\n <animate attributeName=\"letter-spacing\" values=\"0.45;0.55;0.45\" dur=\"6s\" repeatCount=\"indefinite\" />\n )}</text>\n </g>\n {/* v0.10.0 Hero 3 Wave 1 / RFC §3.I (Vincent 5215 + 通信龙\n lead-autonomy Q4 dual-anchor minimal): canvas top-left\n crescent moon brand mark, visible ONLY when the\n recent-signal panel is hidden (composes with §3.C). The\n two never co-exist — when flowLinks.length > 0 the\n recent-signal panel occupies the (16,16) corner; when\n flowLinks.length === 0 the corner is empty and the\n brand crescent fills it. R310 title-block crescent\n remains the primary mark; this one is the secondary\n canvas-internal anchor (Q4 dual-anchor minimal).\n Inline path geometry identical to public/sleep2agi-\n logo.svg + the title-block SVG (mask = outer disc minus\n offset inner disc → crescent). Local mask id\n (`s2a-canvas-corner-mask`) prevents collision with the\n other inline crescents. opacity 0.35 (slightly more\n subtle than the bottom watermark's 0.4 since the\n canvas top-left has more contrast headroom). */}\n {/* Round 327 / Loop: canvas brand crescent joins the always-\n mount-opacity-gate family (R181/R182/R183/R213×2/R214/R215/\n R221/R222/R223). Pre-R327 the crescent conditionally\n mounted on `flowLinks.length === 0` — first flow arriving\n SNAP-removed it, last flow leaving SNAP-added it. The\n recent-signal panel at the same (16,16) corner has the\n same snap problem on its conditional-mount path; this\n round closes the crescent's snap-on-mount (the panel's\n own crossfade is a larger surface, deferred).\n\n Always-mounted with `opacity={flowLinks.length === 0 ?\n 0.35 : 0}` + 300ms ease-out transition: when the panel\n hides, the crescent fades in over 300ms; when the panel\n shows, the crescent fades out. Same opacity ramp time\n the R175 panel-fade-in uses for cascade rhythm. data-\n topo-brand-canvas-mark-visible exposes the gate for\n tests. */}\n {/* Round 526 / Loop — focal-recede family 4th anchor.\n Symmetric polish to R525 (watermark recede). The\n brand crescent at canvas top-left is the second\n decorative brand element on the canvas; pre-R526 it\n stayed at flat opacity 0.35 (when visible) regardless\n of canvas attention. R526 multiplies its visible\n opacity by 0.7 when ANY non-hub canvas surface is\n hovered, matching R525's deeper-recede semantic for\n decorative brand elements (vs hub focal cluster's\n 0.85× recede at R507/R508).\n Composes cleanly with existing flowLinks gate:\n normal, flowLinks=0: opacity = 0.35 * 1.0 = 0.350\n recede, flowLinks=0: opacity = 0.35 * 0.7 = 0.245\n invisible, flowLinks>0: opacity = 0.00 * any = 0.000\n Multiplicative chain means recede only matters when\n crescent is visible (quiet canvas, flowLinks=0) —\n exactly when canvas attention elsewhere should\n dim the decorative register. 300ms transition\n already covers both axes (the existing visibility\n opacity ramp + the new recede multiplier easing).\n Focal-recede family extension (4 anchors): R507 hub\n digit / R508 hub-highlight / R525 watermark / R526\n crescent (this round). Canvas brand surfaces (R525\n watermark + R526 crescent) now BOTH carry focal-\n recede at the same 0.7 multiplier, fading as a\n decorative pair when the canvas's focal attention\n shifts elsewhere.\n data-topo-brand-canvas-mark-recede attr exposes the\n gate state for tests. */}\n {/* Round 705 / Loop — canvas crescent moon wrapper joins R704\n nested-envelope respiration pattern, closing the canvas-\n brand-pair envelope symmetry. Pattern mirror:\n R704 watermark wrapper envelope 15 s (sibling)\n R705 crescent wrapper envelope 13 s (this round)\n Two cadences coprime (13 and 15) — never beat together\n when both visible. Coprime selection is the canvas-brand-\n pair version of the \"panel-pair both at 8 s parity\" choice\n (R700/R701): there the two pair members share cadence\n because they're typographic twins; here the two are\n visually distinct (text vs glyph) so coprime gives them\n independent voices.\n\n Multiplicative chain (when visible + not recede):\n outer (CSS animation, 13 s): opacity 0.30 ↔ 0.35\n inner (SVG animate, 7 s): fill-opacity 0.8 ↔ 1.0\n effective range: 0.24 ↔ 0.35\n Pre-R705 outer was static 0.35 → 0.28 ↔ 0.35 range.\n Post-R705 the outer ALSO breathes → slightly wider 0.24 ↔\n 0.35 range. Nested respiration on the crescent now matches\n the R704 watermark nested respiration.\n\n Alpha range deliberately HUGS the existing 0.35 baseline\n (0.30 ↔ 0.35, ~14% range) — to NOT clobber the wrapper's\n composed opacity math. Animation overrides inline opacity\n when running, so the alpha tracks the existing visible\n baseline. When the visibility or recede gates flip\n (data-attr \"false\" or \"true\"), CSS animation: none\n releases control back to the inline opacity attr (0 when\n invisible, 0.245 when recede). Same gate strategy as R704.\n\n Rolodex (12 anchors, 10-tier ladder):\n 3 s row hot · 4 s hub idle · 5 s brand logo\n 6 s watermark text · 6 s kicker · 7 s crescent inner\n 8 s recent title · 8 s legend title\n 9 s zoom-level readout · 10 s H2\n 13 s crescent wrapper envelope ← this round\n 15 s watermark wrapper envelope\n\n data-topo-brand-canvas-mark-envelope-breath attr surfaces\n the cadence for tests. */}\n <g\n opacity={(flowLinks.length === 0 ? 0.35 : 0) * (\n (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) && !hoveredHub ? 0.7 : 1\n )}\n className=\"anet-topo-brand-canvas-mark-envelope-breath\"\n data-topo-brand-canvas-mark\n data-topo-brand-canvas-mark-visible={flowLinks.length === 0 ? 'true' : 'false'}\n data-topo-brand-canvas-mark-recede={\n (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||\n hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'\n }\n data-topo-brand-canvas-mark-breath={reducedMotion ? 'false' : 'true'}\n data-topo-brand-canvas-mark-envelope-breath=\"13s\"\n style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out, fill 200ms ease-out' }}\n >\n {/* Round 731 / Loop — SVG <title> a11y child for the canvas-\n corner crescent brand mark. Visible only when no recent\n panel renders (flowLinks.length === 0); screen readers\n can navigate to it via accessibility tree at all times\n since the <title> child stays in the DOM regardless of\n opacity gate. Sibling treatment to R730 watermark a11y. */}<title>sleep2agi · canvas-corner brand mark</title>\n <defs>\n <mask id=\"s2a-canvas-corner-mask\">\n <rect x=\"0\" y=\"0\" width=\"28\" height=\"28\" fill=\"black\" />\n <circle cx=\"14\" cy=\"14\" r=\"12\" fill=\"white\" />\n <circle cx=\"17.5\" cy=\"13\" r=\"10\" fill=\"black\" />\n </mask>\n </defs>\n {/* Round 528 / Loop — 呼吸感 family 4th anchor. Symmetric\n polish to R519 watermark ambient breath. The brand\n crescent at canvas top-left is the second decorative\n canvas brand surface; pre-R528 it stayed at the static\n composed opacity (wrapper 0.35 × no inner anim = flat).\n Post-R528 the inner <rect>'s fill-opacity breathes\n 0.8 ↔ 1.0 on a 7s cycle, composing multiplicatively\n with the wrapper's recede gate:\n normal visible: 0.35 × (0.8-1.0) = 0.280-0.350\n recede visible: 0.245 × (0.8-1.0) = 0.196-0.245\n invisible: 0 × any = 0\n 7s cadence intentionally OUT OF PHASE with R519\n watermark's 6s — the two ambient anchors never beat\n together visibly when both visible. R497 hub idle\n breath (4s) is the loudest; R498 recent-row hot pulse\n (~3s) is the most-active; R519 watermark (6s) +\n R528 crescent (7s) are the quietest ambient pair.\n 呼吸感 family extension (4 anchors):\n R497 hub idle digit 4s active-idle register\n R498 recent-row hot pulse 3s active-fresh register\n R519 watermark ambient 6s ambient (always-on)\n R528 crescent ambient 7s ambient (quiet-only) ← this round\n SMIL <animate> on fill-opacity (not parent opacity) so\n the wrapper's React-controlled gate compositions stay\n intact. Gated on !reducedMotion at JSX level —\n reducedMotion users see the inner rect at default\n fill-opacity=1.0 (no SMIL mounted, wrapper's static\n composed opacity wins). data-topo-brand-canvas-mark-\n breath attr exposes the gate state. */}\n <rect\n x=\"16\" y=\"16\" width=\"28\" height=\"28\"\n fill={pal.legendText}\n mask=\"url(#s2a-canvas-corner-mask)\"\n >\n {!reducedMotion && (\n <animate attributeName=\"fill-opacity\" values=\"0.8;1;0.8\" dur=\"7s\" repeatCount=\"indefinite\" />\n )}\n </rect>\n </g>\n </svg>\n\n {/* Round 30 / Loop: minimap. Big fleets in fullscreen mode at high\n zoom let users lose their position — the minimap shows the\n whole topology miniaturised plus a viewport rectangle so the\n user always knows where they are. Click anywhere to recenter\n the canvas there. Only mounted when the view is non-default\n (zoomed or panned) since at 1× centered the minimap and the\n canvas show the same thing. HTML overlay so it stays fixed\n while the SVG transforms. */}\n {(() => {\n const isDefaultView = Math.abs(view.zoom - 1) < 0.01 && Math.abs(view.x) < 1 && Math.abs(view.y) < 1;\n if (isDefaultView || (onlineNodes.length + offlineNodes.length) === 0) return null;\n const MW = 120, MH = 82;\n const sx = MW / VIEWBOX_W, sy = MH / VIEWBOX_H;\n const rectX = (-view.x / view.zoom) * sx;\n const rectY = (-view.y / view.zoom) * sy;\n const rectW = (VIEWBOX_W / view.zoom) * sx;\n const rectH = (VIEWBOX_H / view.zoom) * sy;\n return (\n <div\n /* Round 332 / Loop: minimap container rounded-md → rounded-lg\n (6 → 8 px) — continues the R330-R331 corner-radius cascade\n onto the minimap overlay card. The minimap is a smaller\n surface than the inner SVG panels (120×82 vs 230×88), so\n it sits one tier inward in the size hierarchy: panels at\n rx=10 (R331), minimap at rounded-lg=8 (R332), inner\n detail card at rx=8 (codex 8f981a9). Same 2 px gradient\n step the rest of the cascade uses. Geometry-safe — the\n minimap is an HTML overlay positioned `bottom: 56` +\n `right-4`, no impact on SVG layout or topo-overlap-test. */\n className=\"absolute right-4 rounded-lg border shadow-lg shadow-black/30 overflow-hidden anet-fade-in anet-topo-chip-focus\"\n /* Round 254 / Loop: minimap container theme transitions —\n background-color, border-color, color (used for SVG\n currentColor inside) all ease at 200ms alongside the\n R254 wrapper + R247 panel treatments. */\n /* R696 — minimap container joins the multi-layer halo\n family on hoveredMinimap. Pre-R696 the container had a\n static Tailwind shadow-lg shadow-black/30 box-shadow\n (vertical elevation) but no radial paint signal on\n hover. R696 adds 2-layer drop-shadow at pal.legendAccent\n tint (2+4 stride, alpha 80/40) — radial halo echoes the\n cyan-themed border outward in soft glow when the user\n hovers the minimap (the same gesture that drives the\n R655 viewport-rect halo inside).\n Compact-chrome tier stride (2+4) matches R667/R668\n chrome-button siblings — minimap (120×82) sits at the\n compact-chrome tier alongside reset/fullscreen buttons.\n transition list extends with 'filter 200ms ease-out'.\n 52nd anchor in multi-layer halo family — first minimap-\n container anchor (the inner viewport-rect R655 was\n already in the family). data-topo-minimap-halo-layers\n attr exposes the gate. */\n style={{\n bottom: 56,\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n cursor: 'crosshair',\n color: pal.legendAccent,\n filter: hoveredMinimap\n ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40)`\n : undefined,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out, color 200ms ease-out, filter 200ms ease-out',\n }}\n // R157: minimap a11y completion. Pre-R157 the element had\n // role=\"img\" + aria-label but no tabIndex / onKeyDown — it\n // was clickable for mouse users (recenter to where you\n // clicked) but tab-unreachable. role=\"img\" was also wrong\n // for an interactive surface; role=\"button\" matches the\n // canonical pattern R116 / R139 / R140 / R151 / R152 use.\n // Keyboard activation can't compute a click position, so\n // Enter / Space falls back to resetView() — same gesture\n // as the dedicated reset button (R104). Click semantics\n // unchanged; only added a clarifying tail to the aria-\n // label + title. anet-topo-chip-focus picks up R155's\n // cyan outline via color: pal.legendAccent inline so the\n // currentColor inherits cleanly on the rounded card.\n role=\"button\"\n tabIndex={0}\n aria-label=\"Topology minimap — click to recenter, Enter to reset view\"\n title=\"Minimap · click to recenter · Enter to reset view\"\n onClick={(e) => {\n const r = e.currentTarget.getBoundingClientRect();\n const fx = (e.clientX - r.left) / r.width;\n const fy = (e.clientY - r.top) / r.height;\n setView(prev => ({\n ...prev,\n x: VIEWBOX_W / 2 - fx * VIEWBOX_W * prev.zoom,\n y: VIEWBOX_H / 2 - fy * VIEWBOX_H * prev.zoom,\n }));\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n resetView();\n }\n }}\n // R346: viewport rect hover affordance driven by parent.\n onMouseEnter={() => setHoveredMinimap(true)}\n onMouseLeave={() => setHoveredMinimap(false)}\n onFocus={() => setHoveredMinimap(true)}\n onBlur={() => setHoveredMinimap(false)}\n data-topo-minimap\n data-topo-minimap-hovered={hoveredMinimap ? 'true' : 'false'}\n data-topo-minimap-container-halo-layers={hoveredMinimap ? '2' : '0'}\n >\n <svg width={MW} height={MH} viewBox={`0 0 ${MW} ${MH}`} style={{ display: 'block' }}>\n {/* Round 198 / Loop: minimap dots gain smooth status\n transitions. Pre-R198 a session flipping working→idle\n or going offline made the minimap dot snap in a single\n frame (opacity 0.9→0.5, r 1.7→1.2, fill swap). The\n canvas itself eases all of these via R167 status-ring\n transitions, R10 freshness, R3 fade-in — but the\n minimap mirror was still snap-cut. Adding opacity /\n fill / r to the CSS transition list lets a status\n change ripple smoothly through both views at the\n same rhythm. 200ms matches the R167 nodeStrokeWidth\n interpolation on the main canvas so the two surfaces\n visually flip in sync. r-as-property is well supported\n Chrome ≥ 95 / Safari ≥ 16 / FF ≥ 70 (same support\n matrix R197 just leveraged on the legend swatch).\n data-topo-minimap-dot exposes each dot for the test;\n data-topo-minimap-dot-online encodes the binary status\n used by the visible attributes. */}\n {[...onlineNodes, ...offlineNodes].map(s => {\n const p = nodePositions[s.alias];\n if (!p) return null;\n const sseN = (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n const isOn = s.status !== 'offline' || !!sseN;\n const st = nodeStatus(s, isOn, isLight);\n return (\n /* Round 372 / Loop: minimap offline-dot opacity\n 0.5 → 0.6. Sibling stale-state legibility lift\n to R358 freshness ramp floor 0.25 → 0.30 + R317\n subordinate-text-lift family. Pre-R372 R198\n drew offline dots at α=0.5 (44 % below online\n 0.9). The minimap is a small overlay against\n the canvas backdrop — at α=0.5 offline dots\n sat at the legibility floor when the minimap\n mounted (only on non-default view). R372 lifts\n offline 0.5 → 0.6 for +20 % relative presence;\n online stays at 0.9 so the offline/online\n contrast ratio is now 0.6/0.9 ≈ 0.67 (vs prior\n 0.5/0.9 ≈ 0.56) — still a clear two-tier\n distinction. R198 opacity + fill + r transition\n list preserved so status flips still ease\n smoothly. data-topo-minimap-dot-opacity attr\n exposes the resolved value for tests. */\n <circle\n key={s.alias}\n cx={p.x * sx} cy={p.y * sy}\n /* Round 384 / Loop: minimap online dot radius 1.7\n → 1.9. Sibling visual-weight bump (10th anchor)\n to R383 recent-row pip 1.8 → 2.0. R198 designed\n the dots at 1.7 (online) / 1.2 (offline) — at\n the minimap's 120 × 82 scale these read clearly\n but the online ↔ offline contrast was modest\n (1.7/1.2 = 1.42×). R384 bumps online to 1.9 so\n the tier delta widens to 1.58× (1.9/1.2). Pair\n completes minimap-dot legibility polish:\n R358 (era R372) offline opacity 0.5 → 0.6\n R384 online radius 1.7 → 1.9 (this round)\n R198 transition list (opacity + fill + r 200ms)\n preserved so status flips still ease smoothly.\n data-topo-minimap-dot-radius attr exposes the\n resolved value for tests. */\n /* Round 392 / Loop: minimap online dot opacity\n 0.9 → 0.95. Theme-consistency / canvas-presence\n polish family (7th anchor) — mirrors R386's\n hub-highlight idle 0.9 → 0.95 lift on the\n minimap surface: the online-dot's idle alpha\n gap (0.10 against full presence) halves to\n 0.05, so the live-fleet anchors on the minimap\n read more confidently. Offline dot stays at\n R372 0.6 — the binary online/offline contrast\n ratio shifts from 0.6/0.9 ≈ 0.67 to 0.6/0.95\n ≈ 0.63, preserved as a clear two-tier\n distinction. R198 opacity + fill + r transition\n list + R384 r=1.9 + R372 offline 0.6 all\n preserved. data-topo-minimap-dot-opacity attr\n bumps to '0.95' for tests. */\n /* Round 421 / Loop: online dot opacity 0.95 → 1.0\n on minimap container hover. Sibling to R346\n viewport rect strokeWidth/opacity hover tween.\n When the user hovers the minimap container,\n the live-fleet anchors brighten from R392\n baseline (0.95) to full opacity in concert\n with the R346 viewport rect lift. Offline\n stays at R372 0.6 — hover state focuses\n attention on the ACTIVE anchors, not the\n stale ones. data-topo-minimap-dot-opacity\n attr (R392) reflects the resolved hover-\n state value for tests. */\n /* Round 486 / Loop — 3rd anchor in the\n inspection-overrides-encoding pattern. Sibling\n to R484 (recent-row timestamp) + R485 (edge\n particle). When the operator hovers a node\n alias on the main canvas, the matching\n minimap dot lifts to opacity=1.0 regardless\n of the binary online/offline encoding —\n cross-reference cue between canvas focal\n and the minimap wayfinding overlay.\n Pre-R486 an offline node's minimap dot stayed\n at 0.6 even when the operator was inspecting\n it via canvas hover; R486 makes the\n inspection signal jump the minimap dot to\n full presence so the spatial reference is\n unambiguous.\n Encoding survives: data-topo-minimap-dot-\n online preserves the online/offline binary,\n data-topo-minimap-dot-opacity-rest preserves\n the would-be opacity. Only the LIVE painted\n opacity flips on inspection.\n inspection-overrides-encoding family — 3\n anchors now:\n R484 recent-row timestamp\n R485 edge particle\n R486 minimap dot ← this round\n data-topo-minimap-dot-lifted attr exposes\n the override gate. */\n /* Round 628 / Loop — extend the R486 inspection\n lift to chatAlias. 13th anchor in chat-target-\n gated brightness family. When the operator\n opens a chat with a node, its minimap dot\n lifts to opacity=1.0 same way hover does, so\n the chat-target's spatial position on the\n minimap is unambiguous while typing in the\n popover (which obscures the main canvas).\n Single-line gate union: hoveredAlias OR\n chatAlias. Encoding still survives via\n data-topo-minimap-dot-online +\n data-topo-minimap-dot-opacity-rest.\n chat-target-gated family anchors so far:\n R615 chat-target ring filter\n R616 alias-text filter\n R617 sub-text filter\n R618 label card stroke + filter\n R619 avatar (image + fallback)\n R620 runtime badge\n R621 ring hover\n R622 spoke hover\n R623 halo hover\n R624 incident edge\n R625 recent-row tint\n R626 group cluster\n R628 minimap dot ← this round (13th) */\n r={isOn ? 1.9 : 1.2}\n fill={st.primary}\n opacity={(hoveredAlias === s.alias || chatAlias === s.alias) ? 1 : (isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6)}\n data-topo-minimap-dot={s.alias}\n data-topo-minimap-dot-online={isOn ? 'true' : 'false'}\n data-topo-minimap-dot-opacity={(hoveredAlias === s.alias || chatAlias === s.alias) ? 1 : (isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6)}\n data-topo-minimap-dot-opacity-rest={isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6}\n data-topo-minimap-dot-lifted={(hoveredAlias === s.alias || chatAlias === s.alias) ? 'true' : 'false'}\n data-topo-minimap-dot-lifted-chat={chatAlias === s.alias ? 'true' : 'false'}\n data-topo-minimap-dot-radius={isOn ? 1.9 : 1.2}\n style={{\n transition: 'opacity 200ms ease-out, fill 200ms ease-out, r 200ms ease-out',\n } as React.CSSProperties}\n />\n );\n })}\n {/* viewport rectangle.\n Round 199 / Loop: rect dimensions transition smoothly\n during R169 smoothView arming (discrete zoom button\n clicks + keyboard +/− + reset/fit). Pre-R199 the main\n canvas crossfaded over R168's 280ms opacity blend\n while the minimap viewport rect snap-cut to its new\n x/y/w/h in one frame — exactly the same rhythm\n mismatch R198 just closed for the minimap dots, now\n fixed for the rectangle that frames them.\n\n Gated to smoothView=true so continuous wheel-zoom /\n drag-pan stay snappy (a CSS transition during drag\n would cause the rect to chase the cursor with a\n 280ms lag). Discrete zooms arm smoothView for 350ms,\n long enough to cover the 280ms x/y/w/h transition.\n\n Setting x/y/width/height as CSS PROPERTIES (style.x\n etc.) — same approach R197 used for legend swatch\n r. Modern Chrome/Safari/FF interpolate these.\n\n data-topo-minimap-viewport / -smooth expose state for\n tests. */}\n {/* Round 287 / Loop: minimap viewport rect strokeWidth\n 1 → 1.5. The rect frames the user's current view\n within the full topology — it IS the wayfinding\n indicator. At 1px stroke against a 120×82 mini-\n canvas it was readable but reserved; 1.5px gives\n the boundary clearer presence without crowding the\n miniaturised dots (still r 1.2-1.7) inside.\n Same micro-polish family as R283 monogram stroke\n 1 → 1.5 — small visual-weight bump on a high-\n information element to lift it above ambient\n chrome. opacity 0.9 stays — strokeWidth alone\n does the lifting. */}\n {/* Round 379 / Loop: minimap viewport rect picks up\n strokeLinejoin='round'. Pre-R379 the rect's 4\n corners painted with default 'miter' joins —\n sharp 90° corners with a small miter overshoot\n (≈ strokeWidth × 1.4 = 2.1 px at sw=1.5). R379\n rounds the joins so corners arc smoothly through\n a quarter-circle of radius ≈ strokeWidth/2. At\n sw=1.5 that's a 0.75-px radius — subtle but\n matches the same stroke-softening vocabulary R288\n chrome icons (zoom/reset/fullscreen) and R378\n flow-rail already speak. Geometry-safe: stroke-\n linejoin only affects the corner overshoot, the\n rect's bbox is unchanged. R287 strokeWidth=1.5 +\n R346 hover-state strokeWidth/opacity bump + R199\n smoothView x/y/w/h transition all preserved.\n data-topo-minimap-viewport-linejoin attr exposes\n the value for tests. */}\n {/* Round 393 / Loop: minimap viewport rect rx 0 → 2.\n Pre-R393 the cyan-stroked viewport rect (the frame\n showing what's currently visible on the canvas)\n drew with sharp corners inside the R332 rounded\n minimap container (rx=8). A small frame with sharp\n corners sitting inside a rounded container reads\n as visually loud — the 90° corners catch the eye\n against the soft container edge. R393 adds rx=2\n so the viewport corners get a subtle radius that\n matches the family's softening idiom on a sub-\n element scale. The R379 strokeLinejoin='round'\n already softens stroke joins; R393 adds a complete\n geometric soften via rx.\n Corner-radius cascade (7 anchors now):\n R330 canvas rx 12\n R331 panels rx 10\n R332 minimap container rx 8\n R375 Layout-toggle rx 8\n R376 nodeSize/zoom rx 8\n R390 hover-detail rx 10\n R393 minimap viewport rx 2 (this round, sub-element)\n The 2-px radius is intentionally small — the\n viewport rect is typically only 30-50px wide,\n where rx=2 reads as \"rounded enough to not snap\"\n without feeling pillowy. data-topo-minimap-\n viewport-rx attr exposes the resolved value\n for tests. R346 hover-state tweens (strokeWidth\n + opacity) preserved verbatim. */}\n <rect\n x={Math.max(0, rectX)} y={Math.max(0, rectY)}\n width={Math.max(0, Math.min(MW - Math.max(0, rectX), rectW))}\n height={Math.max(0, Math.min(MH - Math.max(0, rectY), rectH))}\n rx=\"2\"\n fill=\"none\" stroke={pal.legendAccent}\n // R346: strokeWidth + opacity tween on container hover.\n strokeWidth={hoveredMinimap ? '1.75' : '1.5'}\n strokeLinejoin=\"round\"\n /* Round 450 / Loop · milestone: minimap viewport rest\n opacity 0.9 → 0.95. Closes half the alpha gap on\n the wayfinding indicator while preserving the\n R346 hover delta to 1.0. Pre-R450 the rest viewport\n sat at 0.9 (10 pct alpha gap) — adequate but\n under-confident for the user's primary \"you are\n here\" indicator on the minimap. R450 lifts to 0.95\n so the rest read is more present without erasing\n the hover lift cue (the +0.05 rest-to-hover delta\n is small but pairs with R346 sw 1.5→1.75 to keep\n hover clearly distinguishable). Sibling to R449\n legend-count active opacity 0.95→1.0 — same\n \"close the active-presence alpha gap\" idiom now\n applied to the REST tier of the wayfinding rect\n (the minimap viewport stays at canvas-presence\n register even when un-hovered since it's the\n spatial referent). Theme-consistency / canvas-\n presence family (8th anchor on the active-\n presence lift sub-arc).\n R287 strokeWidth=1.5 + R379 strokeLinejoin='round'\n + R346 hover-state tweens + R393 rx=2 + R199\n smoothView x/y/w/h transition all preserved. */\n opacity={hoveredMinimap ? '1' : '0.95'}\n data-topo-minimap-viewport\n data-topo-minimap-viewport-rx=\"2\"\n data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}\n data-topo-minimap-viewport-hover={hoveredMinimap ? 'true' : 'false'}\n data-topo-minimap-viewport-linejoin=\"round\"\n /* Round 481 / Loop — 6th anchor in the drop-shadow\n visual-polish family. New gate type: ZOOM STATE.\n When current canvas zoom > 1.5x (50% above the\n default 1.0 baseline), the minimap viewport rect\n gains a soft cyan halo signaling \"you're zoomed\n in beyond default\". The minimap viewport already\n shrinks as you zoom in (rectW = VIEWBOX_W /\n view.zoom * sx, so at zoom=2 it halves) — the\n glow tells you the wayfinding marker is now\n scaled-down rather than at canvas-default size.\n Drop-shadow family — 6 gate types covered:\n R476 hub digit hover-gated\n R477 legend pin-ring pin-gated\n R478 freshness pip freshness-gated\n R479 group label pin-gated\n R480 edge badge hot-lane-gated\n R481 minimap zoom-gated ← this round\n 6 distinct semantic gates (user interaction\n transient/sticky × 2, data freshness, data\n volume, canvas zoom state). Each anchor uses\n hue family appropriate to its semantic context.\n Hue: pal.legendAccent at 0x80 alpha — matches\n the existing R107 tint family and R478/R479\n cyan-tone choices. 2-px blur reads as subtle\n (the minimap viewport is small, ~120×82 px).\n Filter is paint-only — bbox unchanged. transition\n list extends to include 'filter 200ms ease-out'\n so the glow eases when zoom crosses 1.5x.\n R540: extends the drop-shadow to also fire on\n hoveredMinimap with HOVER PRECEDENCE over zoom-\n state. Pre-R540 the viewport drop-shadow was\n zoom-only (single gate); R540 adds an\n interactional gate at lighter blur intensity.\n Hover wins when both true — interactional signal\n (user is inspecting) trumps informational signal\n (you're zoomed). Sibling to R534 edge-badge\n hover-precedence + R538 group-label hover-tier\n extensions.\n 2-tier alpha ladder:\n hover (interactional) legendAccent 99 (~60%)\n zoom > 1.5 (info) legendAccent 80 (~50%)\n rest none\n data-topo-minimap-viewport-glow attr upgraded\n binary ('true'/'false') → 3-value ('hover' |\n 'zoom' | 'false') so tests can distinguish\n gate cause. */\n data-topo-minimap-viewport-glow={hoveredMinimap ? 'hover' : view.zoom > 1.5 ? 'zoom' : 'false'}\n data-topo-minimap-viewport-brightness={(hoveredMinimap || view.zoom > 1.5) ? '1.15' : '1'}\n data-topo-minimap-viewport-halo-layers={(hoveredMinimap || view.zoom > 1.5) ? '2' : '0'}\n style={{\n /* R627 — minimap viewport rect stacks brightness(1.15)\n onto the existing hover drop-shadow + zoom>1.5\n drop-shadow filters. Banked R582/R583 stacked-\n filter pattern at the minimap chrome scope.\n Drop-shadow paints the cyan/teal halo; brightness\n lifts the underlying stroke +15% — both effects\n fire together on hover (legendAccent 99 alpha)\n or zoom>1.5 (legendAccent 80 alpha) and ease at\n the same 200ms cadence per the existing\n transition list.\n\n Extends the minimap-viewport hover signature to\n include the brightness axis alongside the\n existing R332 stroke-width/opacity lifts +\n drop-shadow glow. The cyan viewport indicator\n now reads ~15% brighter on hover, distinguishing\n it from sibling minimap chrome at quick glance. */\n /* Round 655 / Loop — minimap viewport rect filter gains\n a SECOND outer drop-shadow on both hover (alpha\n 0x99 → 0x4c) and zoom>1.5 (alpha 0x80 → 0x40)\n branches. 14th anchor in multi-layer halo family\n (1st minimap-tier anchor). 2+4 stride matches\n R643/R644 small-element scope; same R642-R654\n 0.5x alpha falloff vocabulary applied to the\n minimap chrome.\n The viewport rect at 120×82 minimap scale is a\n small ~24×16 indicator — the wide outer halo\n gives the cyan zoom indicator an ambient glow\n so the canvas's current viewport reads as\n \"lit up\" within the minimap envelope, matching\n the visual grammar the rest of the canvas uses\n for active indicators. */\n filter: hoveredMinimap\n ? `drop-shadow(0 0 2px ${pal.legendAccent}99) drop-shadow(0 0 4px ${pal.legendAccent}4c) brightness(1.15)`\n : view.zoom > 1.5\n ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)`\n : undefined,\n transition: smoothView\n ? 'x 280ms ease-out, y 280ms ease-out, width 280ms ease-out, height 280ms ease-out, stroke-width 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out'\n : 'stroke-width 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',\n } as React.CSSProperties}\n />\n </svg>\n </div>\n );\n })()}\n {/* Round 103 (issue #81): zoom / pan / fullscreen controls — HTML\n overlay so they stay fixed while the SVG content transforms.\n Round 104: Vincent 实测 — the reset action used to be hidden\n behind the \"%\" label (looked like an indicator, not a button).\n Split into a plain % readout + an explicit reset button with\n its own icon + tooltip. */}\n {/* Round 261 / Loop: chrome strip bottom-3 right-3 (12 CSS px) →\n bottom-4 right-4 (16 CSS px) to align HTML overlay padding\n with the SVG corner panels at (16, 16) panel-translate. Pre-\n R261 the SVG panels (at 16 SVG units from canvas edges,\n ≈ 15 CSS px after render-scale ~0.94) and the HTML chrome\n (at 12 CSS px) sat at visually different distances from\n the canvas edges — small but real ~3 CSS px optical\n asymmetry between SVG-layer and HTML-layer overlay padding.\n 16 CSS px ≈ 17 SVG units, unifying the visual padding\n vocabulary across both layers. Sibling change at the\n minimap container (line ~6444, `absolute right-3` →\n `right-4`) keeps the bottom-right corner HTML overlays\n aligned at the same canvas-edge inset. */}\n {/* Round 326 / Loop: chrome strip outer wrapper gap 1.5 → 2\n (6px → 8px between control groups). Pre-R326 the four\n chrome groups (nodeSize segmented S/M/L, zoom +/100%/−,\n reset, fullscreen) sat 6px apart — close enough that on a\n busy canvas with bright cyan accents they read as one\n uniform strip rather than four distinct affordances. Bump\n to 8px gives each group its own visual breath without\n disturbing the bottom-4 right-4 corner-inset alignment.\n Sibling treatment to R298/R299 title-block gap polish on\n the top side of the canvas — both ends of the canvas\n chrome now breathe at the same 8px rhythm. Geometry-safe\n for the overlap-test (chrome is HTML overlay on top of\n the SVG, not part of the viewBox 1000x680 surface; ring\n r=325 / grid gx0 layout untouched). */}\n {/* Round 743 / Loop — chrome strip joins entrance family as 3rd\n one-shot-mount member, completing the entrance TRIO. Three-\n stage cascading \"settle in\" wave:\n R740 canvas 0 → 600 ms scale 0.99→1, opacity 0.8→1\n R742 title-block 200 → 700 ms translateY -4→0, opacity 0.7→1\n R743 chrome strip 400 → 900 ms translateX 8→0, opacity 0.7→1\n Each stage starts 200 ms after the prior — the canvas\n arrives, the heading writes in, then the chrome strip slides\n in from the right edge. translateX (not translateY) because\n the chrome strip lives at the right edge — sliding in from\n its own edge reads as \"docking into place\".\n Chrome strip wrapper is plain HTML with no existing\n animation, so a simple single-animation class suffices (no\n compound needed, unlike R742's title-block). Gated by\n reducedMotion at JSX level. See globals.css R743 block. */}\n <div className={`absolute bottom-4 right-4 flex items-center gap-2 text-xs select-none${reducedMotion ? '' : ' anet-topo-chrome-strip-entrance'}`} data-topo-chrome data-topo-chrome-entrance={reducedMotion ? 'false' : 'true'}>\n {/* #113: node size — S / M / L segmented control (Vincent 4727).\n R154: stable data-* hooks for tests + focus-visible ring so\n keyboard navigation lands somewhere visible against the\n dark canvas (browser default outline often vanishes on\n cyber theme). */}\n {/* Round 264 / Loop: nodeSize wrapper gains theme-toggle\n transition. Pre-R264 the wrapper's bg (pal.legendBox.fill)\n + borderColor (pal.containerBorder) were inline theme-\n conditional, but neither inline transition nor a\n transition-colors className → wrapper snapped on cyber↔\n light flip while the inner S/M/L buttons eased via their\n own transition-colors. Same R254 holdover pattern that\n R263 just closed at the canvas wrapper scope, now at the\n chrome strip's nodeSize sub-wrapper scope. */}\n {/* Round 376 / Loop: nodeSize wrapper rounded-md → rounded-lg.\n Sibling polish to R375 Layout-toggle wrapper. Three\n chrome-strip segmented controls now all share rounded-lg\n at the wrapper tier:\n R375 Layout-toggle wrapper rounded-lg 8 px\n R376 nodeSize wrapper rounded-lg 8 px (this round)\n R376 zoom wrapper rounded-lg 8 px (this round)\n Individual atomic chrome buttons (reset, fullscreen) keep\n rounded-md (6 px) as their own atomic-button tier — the\n chrome strip's typography now expresses a clear two-tier\n hierarchy: 'segmented control container' (rounded-lg)\n vs 'standalone button' (rounded-md). Pure paint change,\n no layout shift. */}\n {/* Round 708 / Loop — chrome nodeSize wrapper joins the\n respiratory family at 19 s, mirror to R707 Layout wrapper.\n Closes 2 of the 3 chrome wrappers (Layout 17s / nodeSize\n 19s); zoom wrapper still single-voiced. Coprime ladder\n forms across the chrome wrapper trio:\n R707 Layout wrapper 17 s\n R708 nodeSize wrapper 19 s ← this round\n future zoom wrapper 23 s ?\n All primes coprime with all other rolodex cadences and\n with each other.\n\n Pattern parallel to canvas-brand-pair (R704/R705) coprime\n envelope-per-member: chrome strip control wrappers each\n get their own prime cadence. Unlike canvas-brand-pair\n (which had ONE inner element per envelope), chrome\n wrappers have MULTIPLE inner buttons (Ring/Grid for\n Layout, S/M/L for nodeSize) — wrappers signal \"control\n group is alive\"; inner buttons signal their own R674/R675\n hover-halo when interacted.\n\n Same alpha (0.94 ↔ 1, ~6%) + same :has(button:hover)\n gate as R707 — consistent control-tier rationale across\n the chrome wrapper trio. Coprime cadence selection gives\n the chrome strip its own distinct rhythm-of-control. */}\n <div\n className=\"anet-topo-chrome-fleet-group-trailer-breath flex items-center rounded-lg border overflow-hidden\"\n data-topo-chrome-nodesize-radius=\"rounded-lg\"\n style={{\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out',\n }}\n role=\"group\"\n aria-label=\"Node size\"\n data-topo-chrome-fleet-group-trailer\n data-topo-chrome-fleet-group-trailer-breath=\"19s\"\n /* R697 sibling — wrapper-level has-hover halo (see globals.css). */\n data-topo-chrome-wrapper-halo-family=\"nodesize\"\n >\n {([['S', 0.7], ['M', 0.84], ['L', 1]] as const).map(([lbl, v], idx) => {\n const popKey = `size-${lbl}` as 'size-S' | 'size-M' | 'size-L';\n return (\n <button\n key={lbl}\n onClick={() => { popChrome(popKey); pickNodeScale(v); }}\n onMouseEnter={() => setHoveredNodeSize(lbl)}\n onMouseLeave={() => setHoveredNodeSize((prev) => prev === lbl ? null : prev)}\n aria-pressed={nodeScale === v}\n data-topo-chrome-nodesize={lbl}\n data-topo-chrome-nodesize-active={nodeScale === v ? 'true' : 'false'}\n data-topo-chrome-nodesize-popping={chromePopping === popKey ? 'true' : 'false'}\n /* R675 — nodeSize S/M/L join multi-layer halo family.\n Sibling pattern to R674 Layout ring/grid — same value-\n typed gate (hoveredNodeSize: 'S'|'M'|'L'|null) with\n leave-guard preserving sibling hover across the 3-\n button divider. Inline filter overrides the Tailwind\n hover:brightness-[1.15] (R598) with 2-layer drop-\n shadow at pal.legendAccent tint, 2+4 stride. */\n data-topo-chrome-nodesize-halo-layers={hoveredNodeSize === lbl ? '2' : '0'}\n title={`Node size: ${lbl === 'S' ? 'small' : lbl === 'M' ? 'medium' : 'large'}`}\n // Round 179 / Loop: nodeSize S/M/L active-button hover\n // variant closes the inconsistency with R163 layout\n // toggle and R178 fullscreen. Pre-R179 the active\n // (selected) S/M/L button had bg-cyan-500/15 + text-\n // cyan-300 but NO hover response — the chip looked\n // 'locked', not 'still interactive'. R163 and R178\n // both add hover:bg-cyan-500/20 on the active variant\n // so the active chip stays responsive to mouse. R179\n // closes the trio so all three chrome active-cyan\n // surfaces ship the same gesture.\n // Round 196 / Loop: nodeSize buttons pick up press-state\n // (active:) — selected variant deepens to cyan-500/25,\n // unselected to white/10. Same tier pattern as R196 layout\n // toggle + zoom/reset/fullscreen below.\n // Round 250 / Loop: nodeSize buttons close the chrome-pop\n // family — every clickable chrome button now fires the\n // R186 .anet-chrome-pop scale-pulse on release. R171\n // canvas crossfade (nodeSizeSwitching) keeps masking the\n // node radius change at the global scope; R250 chrome-pop\n // adds the LOCAL button-level confirmation. The two\n // happen simultaneously without conflict — local scale\n // pulse on the button, global canvas dim around it.\n // Milestone round: the entire chrome strip (zoom -/+,\n // ring/grid, fullscreen, S/M/L) now speaks one\n // consistent click vocabulary.\n /* Round 270 / Loop: nodeSize INACTIVE buttons align with\n the Layout toggle's R163 hover-preview pattern. Pre-\n R270 inactive S/M/L used `hover:bg-white/5\n active:bg-white/10` (neutral white tint) while the\n Layout toggle's inactive Ring/Grid uses `hover:bg-\n cyan-500/5 active:bg-cyan-500/15` (faint cyan ghost\n that previews what the active state will look like —\n the active variant is bg-cyan-500/15). Two different\n hover vocabularies for visually-analogous toggle\n controls. R270 unifies inactive toggle hover to\n cyan so all TOGGLE chrome buttons (Layout / nodeSize\n / fullscreen) preview their active state on hover.\n Pure actions (zoom -/+, reset) stay white — they\n aren't toggles, have no active state to preview. */\n // Round 493 / Loop — extends R492 chrome-strip press-feedback\n // family to nodeSize S/M/L buttons. Adds active:scale-95\n // alongside the existing color-deepen (R196) + chrome-pop\n // (R249). transition-transform + duration-200 + ease-out\n // + transform-gpu added since this className previously had\n // transition-colors only — without the transform transition,\n // active:scale-95 would hard-cut. transform-gpu promotes the\n // layer so scale doesn't trigger paint thrash.\n /* Round 521 / Loop — extends R270's hover-preview pattern\n (inactive toggle hover previews the active state's\n visual register) to the TYPOGRAPHY axis at the chrome\n nodeSize S/M/L surface. Pre-R521 the inactive variant\n had `hover:bg-cyan-500/5` (R270 bg preview) but no\n typography preview — active variant uses `font-medium`\n (fw 500), inactive variant sat at default fw 400 even\n on hover.\n R521 adds `hover:font-medium` + `transition-[font-\n weight]` to the inactive variant so hovering an\n inactive S/M/L letter thickens the glyph 400 → 500,\n previewing the typography of the active state the\n click would commit to. Sibling to R421 chrome zoom-\n level fontWeight hover delta (rest 500 → hover 600)\n and R520 footer fontWeight hover (500 → 600) — same\n idiom: thicken-on-hover for chrome surfaces with a\n pre-commit gesture.\n `font-medium` (500) matches the ACTIVE variant's\n fw exactly — the inactive hover landing weight equals\n the active locked weight, so clicking commits to a\n typography state the eye already saw 'on the way in'.\n Hover-fw family extension (5 anchors now):\n R416 chip-row count digit rest 500 → hover 700/600\n R420 chrome zoom-level rest 500 → hover 600\n R425 hub-center digit rest 700 → hover 800\n R520 +N more flows footer rest 500 → hover 600\n R521 chrome nodeSize S/M/L inactive rest 400 → hover 500 ← this round\n Active variant `font-medium` unchanged so the rest-vs-\n active typography distinction stays intact when the\n user IS clicked-in (active stays at fw 500, inactive\n rest at fw 400, inactive hover preview at fw 500).\n data-topo-chrome-nodesize-hover-preview-fw=\"500\" attr\n exposes the polish for tests. */\n /* R598 — nodeSize S/M/L segmented buttons gain hover:\n brightness-[1.15] (39+40+41st anchors, 8+9+10th HTML).\n Final segmented chrome control to close brightness\n coverage. After R598 all three segmented controls\n (zoom + layout + nodeSize) have full hover-brightness\n parity.\n Tailwind v4 arbitrary `[transition-property:color,\n background-color,transform,font-weight,filter]`\n replaces the chain of `transition-colors transition-\n transform transition-[font-weight]` so the filter\n property joins the existing 200ms cadence at the\n same beat. Segmented-unity rule (R400) preserved —\n brightness is pure paint, no geometric break.\n data-topo-chrome-nodesize-brightness-hover='1.15'\n attr documents the hover value for tests. */\n className={`px-2 py-1 [transition-property:color,background-color,transform,font-weight,filter] duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:brightness-[1.15] ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}\n data-topo-chrome-nodesize-hover-preview-fw={nodeScale === v ? null : '500'}\n data-topo-chrome-nodesize-brightness-hover=\"1.15\"\n style={{\n color: nodeScale === v ? undefined : pal.legendText,\n borderColor: pal.containerBorder,\n filter: hoveredNodeSize === lbl ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n }}\n >\n {lbl}\n </button>\n );\n })}\n </div>\n {/* Round 255 / Loop: semantic gap between the fleet-control group\n (node size S/M/L) and the view-control group (zoom / reset /\n fullscreen). Pre-R255 the four groups sat at uniform gap-1.5\n (6px); the spatial signal read as \"4 separate things\" instead\n of \"1 fleet control + 3 view controls\". Doubling the gap before\n the first view-control (ml-1.5 = 6px stacks on top of the\n parent's gap-1.5 = 6px, total 12px) communicates the semantic\n boundary through proximity alone — classic \"law of proximity\"\n layout polish, no extra chrome, no new visual elements.\n data-topo-chrome-view-group-leader marks the boundary surface\n for the test probe; data-topo-chrome-fleet-group-trailer marks\n the nodeSize wrapper's right edge for the gap measurement. */}\n {/* R376 sibling — zoom wrapper rounded-md → rounded-lg.\n Closes the chrome-strip segmented-control corner radius\n cascade (Layout R375 + nodeSize R376 + zoom R376). */}\n {/* Round 709 / Loop — chrome zoom wrapper joins the\n respiratory family at 23 s, closing the chrome control\n trio with three adjacent-prime coprime cadences:\n R707 Layout wrapper 17 s\n R708 nodeSize wrapper 19 s\n R709 zoom wrapper 23 s ← this round\n All three primes coprime with each other and with every\n other rolodex cadence (3/4/5/6/7/8/9/10/11/13/15) — no\n phase locks across the entire 16-anchor family.\n\n 23s is the slowest cadence yet in the rolodex, intentional\n for zoom: among the 3 chrome control wrappers, zoom is the\n MOST functional (its inner buttons drive viewport math\n that compounds across panning/scaling state) — slower\n cadence reads as \"calm anchor for the viewport-control\n cluster\". The cadence-by-role choice mirrors the H2 anchor\n (10s) being slowest in the title-block trio.\n\n Same alpha (0.94 ↔ 1, ~6%) + same :has(button:hover) gate\n as R707/R708 — consistent control-tier rationale across\n the chrome wrapper trio. Together the 17/19/23 prime\n triple gives the chrome strip its own \"control orchestra\"\n independent from data tier 9s (R703) and from all other\n respiratory regions. */}\n <div\n className=\"anet-topo-chrome-zoom-wrapper-breath ml-1.5 flex items-center rounded-lg border overflow-hidden\"\n data-topo-chrome-zoom-wrapper-radius=\"rounded-lg\"\n style={{\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out',\n }}\n data-topo-chrome-view-group-leader\n data-topo-chrome-zoom-wrapper\n data-topo-chrome-zoom-wrapper-breath=\"23s\"\n /* R697 sibling — wrapper-level has-hover halo (see globals.css). */\n data-topo-chrome-wrapper-halo-family=\"zoom\"\n >\n <button\n onClick={() => { popChrome('zoom-out'); zoomByDiscrete(1 / 1.2); }}\n onMouseEnter={() => setHoveredZoomOut(true)}\n onMouseLeave={() => setHoveredZoomOut(false)}\n data-topo-chrome-zoom-out\n data-topo-chrome-zoom-out-popping={chromePopping === 'zoom-out' ? 'true' : 'false'}\n data-topo-chrome-zoom-out-brightness-hover=\"1.15\"\n /* R673 — chrome zoom-out picks up multi-layer halo on\n hover, completing the chrome zoom-strip trio (zoom-out\n + zoom-level pill R668 + zoom-in below) at FULL halo\n parity. Pre-R673 the button had only Tailwind hover:\n brightness-[1.15] (R596) — a single-axis paint lift.\n Post-R673 inline filter on hoveredZoomOut state takes\n precedence over the Tailwind hover, applying 2-layer\n drop-shadow at pal.legendAccent tint with 2+4 stride\n (matches R667/R668 chrome-control siblings).\n 32nd anchor in multi-layer halo family. */\n data-topo-chrome-zoom-out-halo-layers={hoveredZoomOut ? '2' : '0'}\n // R196: press-state deepens bg one tier above hover (white/5\n // → white/10) so mouse-down has a tactile dim before the\n // R186 icon pop fires on release.\n // R352: `group` lets the inner svg respond via group-hover.\n // R493 — zoom +/− buttons join the chrome-strip active:scale-95\n // press-feedback family (R492 + nodeSize above). transition-\n // transform + duration-200 + ease-out + transform-gpu added\n // since the className had only transition-colors.\n /* R596 — zoom-out segmented button gains hover:brightness-[1.15]\n (35th anchor in per-element brightness family, 4th HTML).\n Sibling to zoom-in below — paired-anchor round closing the\n entire zoom trio at brightness parity (R593 zoom-level\n readout + R596 zoom-out + zoom-in).\n\n Segmented-control constraint preserved: brightness is pure\n paint (no geometry shift) so it doesn't break the R400\n segmented-unity rule (which only excludes geometric hover-\n lift like translateY). Each segment can brighten\n independently while the strip stays planted as one unit.\n\n Tailwind v4 arbitrary `[transition-property:...]` replaces\n `transition-colors transition-transform` so the filter\n property joins the existing 200ms cadence — bg / color /\n transform / filter all ease in unison.\n\n data-topo-chrome-zoom-out-brightness-hover='1.15' attr\n documents the hover value for tests. */\n className=\"group px-2 py-1 hover:bg-white/5 hover:brightness-[1.15] active:bg-white/10 [transition-property:color,background-color,transform,filter] duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset\"\n style={{\n color: pal.legendText,\n filter: hoveredZoomOut ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n }}\n aria-label=\"Zoom out\"\n title=\"Zoom out (−)\"\n >\n {/* R186: icon pop on click. CSS animation runs once;\n React removes the class after 240ms so a quick\n re-click can replay. */}\n {/* Round 352 / Loop: zoom-out icon picks up group-hover:\n scale-110 — sibling to R350 reset hover-rotate. Pre-\n R352 hovering the zoom button only changed the bg\n (white/5); the icon inside stayed perfectly still.\n R352 lifts the icon 10% on hover for a tactile \"this\n button does something\" cue. The R186 anet-chrome-pop\n keyframe (220ms scale 1→1.06→1) still owns transform\n during click via CSS-animation precedence over\n transition-transform; after the pop ends + className\n is removed, the group-hover scale-110 picks up\n smoothly. `transform-gpu` hint promotes the svg to\n its own compositor layer for crisper edges during\n the scale tween. Sibling change on zoom-in icon\n below. */}\n {/* Round 454 / Loop: extend R453 chrome reset icon hover\n sw lift to zoom +/− icons via Tailwind arbitrary class\n group-hover:[stroke-width:2.8]. Chrome icon hover sw\n lift family now 5 anchors:\n R208 runtime badge outer ring 1.5 → 2\n R443 runtime badge inner icon 2.4 → 2.8\n R453 chrome reset icon 2.5 → 2.8\n R454 chrome zoom-out icon 2.5 → 2.8 ← this round\n R454 chrome zoom-in icon 2.5 → 2.8 ← this round\n Tailwind v4 arbitrary-value group-hover variant\n resolves [stroke-width:2.8] as a CSS property which\n overrides the static strokeWidth='2.5' attribute on\n hover. transition-[stroke-width] appended to the\n existing transition-transform list so the sw tween\n eases under the same 200ms cadence as R352 group-\n hover:scale-110. R186 anet-chrome-pop keyframe still\n owns transform during click via CSS-animation\n precedence over transition-transform. Sibling change\n on zoom-in icon below. */}\n <svg\n width=\"12\" height=\"12\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\"\n aria-hidden\n className={`transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:[stroke-width:2.8] transform-gpu${chromePopping === 'zoom-out' ? ' anet-chrome-pop' : ''}`}\n data-topo-chrome-zoom-out-icon\n ><path d=\"M5 12h14\" /></svg>\n </button>\n {/* Round 192 / Loop: zoom-level readout span participates in the\n R186 click-feel pop alongside the +/− icons. Pre-R192 a click\n on + or − triggered:\n · icon pop (R186, ~220ms scale 1→1.06→1)\n · canvas crossfade (R169, ~280ms opacity blend)\n · readout text snap (instant — 100% → 120%)\n The readout was the only surface that didn't acknowledge the\n gesture. Reusing the existing .anet-chrome-pop CSS keyframe\n (no new keyframes) lets the \"%\" number gently bounce in\n sync with the icon — same 0.22s ease-out, transform-origin\n center. transform-box: fill-box on the keyframe is\n SVG-specific and harmlessly ignored on this HTML span. The\n base layout classes (px / border / tabular-nums / minWidth)\n stay intact; only when chromePopping is 'zoom-in' or\n 'zoom-out' do we splice in the animation class. Same\n React-clears-after-240ms cleanup R186 already runs, so the\n class can replay on a repeat click. */}\n {/* Round 312 / Loop: chrome strip zoom readout '{N}%'\n picks up `font-medium` (500). Extends the R309-R311\n 'data digit weighs more than label' rule to the\n chrome strip's one data display — the zoom\n percentage. Every other chrome strip text is a\n control (S/M/L buttons, zoom +/-, reset, fullscreen,\n Ring/Grid labels); the percent readout is the only\n live DATA. font-medium (not 600 like the SVG panel\n counts) is a tier below because the readout sits in\n HTML chrome context (lighter visual baseline) where\n 500 reads as 'noticeably data-prominent' without\n competing with the SVG panel counts. tabular-nums\n + minWidth 46 stay (R225 family), the existing R264\n color/border transitions stay, the R186 chrome-pop\n class still toggles on zoom-click. */}\n {/* Round 703 / Loop — chrome zoom-level readout gains at-rest\n breathing fade. Sits between the 8 s panel titles and the\n 10 s H2 in the respiratory rolodex — the chrome strip's\n only data display now has a \"this readout is alive even\n when zoom is steady\" signal at rest. 9 s reads as a slow,\n informational rhythm — slower than panel titles (8 s, more\n editorial) but faster than the H2 (10 s, anchor). Tighter\n alpha range (0.85 ↔ 1, ~15%) than the kicker (0.78 ↔ 1)\n — readout carries tabular numeric data so it shouldn't\n drift too far from full opacity.\n\n Hover gate via CSS `[data-topo-chrome-zoom-level-hover=\n \"true\"]` rule — when hovered, animation: none, and the\n existing 4-axis hover lift (R347 ls + R420 fw + R517\n color + R593 brightness + R668 halo) takes precedence\n without competing alpha breath underneath.\n\n Cadence rolodex (10 anchors now, 7-tier ladder):\n 3 s row hot · 4 s hub idle · 5 s brand logo\n 6 s watermark · 6 s kicker · 7 s crescent\n 8 s recent title · 8 s legend title\n 9 s zoom-level readout ← this round\n 10 s H2\n\n Pure opacity → bbox stable → topo-overlap-test untouched.\n prefers-reduced-motion: reduce neutralizes inside the CSS\n class. data-topo-chrome-zoom-level-breath attr surfaces\n the cadence for tests. */}\n <span\n className={`anet-topo-chrome-zoom-level-breath px-2 py-1 tabular-nums font-medium border-x text-center${\n chromePopping === 'zoom-in' || chromePopping === 'zoom-out'\n ? ' anet-chrome-pop' : ''\n }`}\n data-topo-chrome-zoom-level\n data-topo-chrome-zoom-level-breath=\"9s\"\n data-topo-chrome-zoom-level-popping={\n chromePopping === 'zoom-in' || chromePopping === 'zoom-out'\n ? 'true' : 'false'\n }\n data-topo-chrome-zoom-level-hover={hoveredZoomLevel ? 'true' : 'false'}\n /* Round 517 / Loop — extends the chrome zoom-level readout\n from 2-axis (R347 letter-spacing + R420 fontWeight) to\n 3-axis hover signature by adding a color brighten to\n pal.legendHeadline. Pre-R517 the readout's color stayed\n at pal.legendText on hover; the digits got tighter\n kerning (0→0.5px) and heavier weight (500→600) but\n stayed the same legendText gray tone. R517 lifts color\n to legendHeadline on hover so the readout brightens\n into the headline tier at the same beat — matching the\n R55/R197/R239 hover-deepen-own-hue idiom that legend-\n row label + count carry at panel scope. Chrome strip's\n only data display now has full 3-axis hover signature\n (letter-spacing + fontWeight + color), parity with the\n chip-row chips' own hover-brighten pattern.\n Implementation: inline color uses the same hoveredZoom-\n Level state as R347/R420 — no new state. Transition\n already includes 'color 200ms ease-out' (R264) so the\n brighten eases under the same cadence as the kerning +\n weight tweens — one motion-coherent 3-axis lift.\n data-topo-chrome-zoom-level-color attr exposes the\n resolved color string for tests. */\n data-topo-chrome-zoom-level-color={hoveredZoomLevel ? 'headline' : 'text'}\n /* R593 — chrome zoom-level readout gains filter\n brightness(1.15) on hover. 32nd anchor in per-element\n brightness family. FIRST HTML-element brightness\n anchor outside the SVG canvas (the readout is an\n HTML <span>, not SVG).\n\n Closes the chrome zoom-level readout's hover signature\n at 4 AXES:\n R347 letter-spacing 0 → 0.5px\n R420 fontWeight 500 → 600\n R517 color legendText → legendHeadline\n R593 brightness 1 → 1.15 ← this round\n\n The chrome strip's only data display now has full\n 4-axis hover signature — color brightens to headline\n tier, glyph thickens, kerning spreads, AND brightness\n lifts another +15% on top of the color swap.\n\n Triple-paint multiplicative interaction:\n color (legendHeadline) × brightness(1.15) — the\n headline tier reads dramatically brighter than a\n plain color swap.\n\n Transition list extends with 'filter 200ms ease-out'\n matching the existing 200ms cadence across all 4 axes\n — motion-coherent state-flip.\n\n data-topo-chrome-zoom-level-brightness attr exposes\n the gate for tests. */\n data-topo-chrome-zoom-level-brightness={hoveredZoomLevel ? '1.15' : '1'}\n /* R668 — chrome zoom-level readout joins the multi-layer\n halo family. 27th anchor in the family, 2nd chrome-\n control anchor (sibling to R667 reset/fullscreen). Pre-\n R668 the readout had brightness(1.15) only on hover —\n a single-axis paint lift. R668 extends to the same\n 2-layer 0.5×-falloff vocabulary the chrome buttons use\n so the percent readout glows with the same near + far\n cyan halo as its siblings — chrome strip now has\n unified halo language across zoom + reset + fullscreen.\n Tint: pal.legendAccent (matches R667). Stride: 2+4\n (matches R667 chrome-control stride). The chrome strip\n is now the first FULLY halo-extended scope —\n every interactive element halos coherently.\n data-topo-chrome-zoom-level-halo-layers attr surfaces\n the gate for tests. */\n data-topo-chrome-zoom-level-halo-layers={hoveredZoomLevel ? '2' : '0'}\n onMouseEnter={() => setHoveredZoomLevel(true)}\n onMouseLeave={() => setHoveredZoomLevel(false)}\n style={{\n color: hoveredZoomLevel ? pal.legendHeadline : pal.legendText,\n borderColor: pal.containerBorder,\n minWidth: 46,\n display: 'inline-block',\n filter: hoveredZoomLevel ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n // R347: letter-spacing hover tween — extends R344/R345\n // hover-letter-spacing family into the chrome strip.\n letterSpacing: hoveredZoomLevel ? '0.5px' : '0',\n // Round 420 / Loop: zoom-level readout gains a SECOND\n // hover axis — fontWeight 500 → 600 on hover. Sibling\n // to R347 (same element, hover letter-spacing tween).\n // The chrome strip's only data display now has a two-\n // axis hover signature (letter-spacing + fontWeight),\n // matching the R416 chip-row chip digit hover-bold\n // pattern at the chrome scope. Pre-R420 hovering only\n // spread the digits 0 → 0.5px; the weight stayed at\n // R332's 'font-medium' (500) baseline. Post-R420\n // hover lifts BOTH letter-spacing AND weight so the\n // percent reads with the same data-tier emphasis\n // intensification the chip-row chips do on hover.\n // Inline fontWeight overrides the className's\n // 'font-medium' since they target the same property.\n // 200ms transition list extends to font-weight for\n // smooth easing. data-topo-chrome-zoom-level-hover\n // attr surfaces the hover state for tests.\n fontWeight: hoveredZoomLevel ? 600 : 500,\n /* Round 264 / Loop: zoom level readout gains theme-toggle\n transition. The span has theme-driven color (pal.\n legendText) + border-x (pal.containerBorder via the\n inline borderColor) but className lacks transition-\n colors — the readout's text + side dividers snapped\n on theme flip while siblings eased. Sibling treatment\n to the nodeSize + zoom wrapper transitions added this\n round. */\n transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',\n }}\n title=\"Current zoom level\"\n >\n {Math.round(view.zoom * 100)}%\n </span>\n <button\n onClick={() => { popChrome('zoom-in'); zoomByDiscrete(1.2); }}\n onMouseEnter={() => setHoveredZoomIn(true)}\n onMouseLeave={() => setHoveredZoomIn(false)}\n data-topo-chrome-zoom-in\n data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}\n data-topo-chrome-zoom-in-brightness-hover=\"1.15\"\n /* R673 sibling — zoom-in mirrors zoom-out above. Together\n with R668 zoom-level pill, the chrome zoom-strip is\n FULLY halo-extended. */\n data-topo-chrome-zoom-in-halo-layers={hoveredZoomIn ? '2' : '0'}\n // R196: press-state (mirror of zoom-out above).\n // R352: `group` lets the inner svg respond via group-hover.\n // R493 — zoom +/− buttons join the chrome-strip active:scale-95\n // press-feedback family (R492 + nodeSize above). transition-\n // transform + duration-200 + ease-out + transform-gpu added\n // since the className had only transition-colors.\n /* R596 sibling — zoom-in mirrors zoom-out above. 36th anchor\n in per-element brightness family, 5th HTML. */\n className=\"group px-2 py-1 hover:bg-white/5 hover:brightness-[1.15] active:bg-white/10 [transition-property:color,background-color,transform,filter] duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset\"\n style={{\n color: pal.legendText,\n filter: hoveredZoomIn ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n }}\n aria-label=\"Zoom in\"\n title=\"Zoom in (+)\"\n >\n {/* R186: icon pop on click. Same one-shot CSS animation\n as zoom-out; React removes the class after 240ms. */}\n {/* R352 sibling — zoom-in icon picks up the same\n group-hover:scale-110 family. Mirror change at\n the zoom-out icon above. */}\n {/* R454 sibling — zoom-in icon picks up the same\n group-hover:[stroke-width:2.8] family lift. */}\n <svg\n width=\"12\" height=\"12\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\"\n aria-hidden\n className={`transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:[stroke-width:2.8] transform-gpu${chromePopping === 'zoom-in' ? ' anet-chrome-pop' : ''}`}\n data-topo-chrome-zoom-in-icon\n ><path d=\"M12 5v14M5 12h14\" /></svg>\n </button>\n </div>\n <button\n onClick={() => { armResetSpin(); resetView(); }}\n data-topo-chrome-reset\n data-topo-chrome-reset-spinning={resetSpinning ? 'true' : 'false'}\n data-topo-chrome-reset-hover={hoveredReset ? 'true' : 'false'}\n // R350: hover state drives the icon transform below.\n onMouseEnter={() => setHoveredReset(true)}\n onMouseLeave={() => setHoveredReset(false)}\n onFocus={() => setHoveredReset(true)}\n onBlur={() => setHoveredReset(false)}\n // R196: press-state deepens before R184 reset-spin fires on\n // release — mouse-down dim then 450ms spin = full handshake.\n /* Round 400 / Loop · milestone: chrome reset + fullscreen\n buttons gain hover:-translate-y-px lift — closes the\n hover-lift gesture vocabulary across every standalone\n interactive HTML element in TopoGraph. Segmented\n controls (zoom -/+, nodeSize S/M/L, Layout Ring/Grid)\n intentionally stay planted: lifting one segment of a\n unified strip would tear the visual unity of the\n segmented control. Only the standalone chrome buttons\n (reset, fullscreen) get the lift.\n Gesture vocabulary post-R400 (now complete across HTML):\n chip-row chips (3×) -1 px R398, R399\n filter pin pills (4×) -1 px R397\n recent-signal row -1 px R143\n legend row -1 px R144\n reset button -1 px R400 (this round)\n fullscreen button -1 px R400 (this round)\n Every standalone interactive HTML surface in TopoGraph\n now lifts on hover. data-topo-chrome-reset-hover-lift\n attr surfaces the lift for tests. */\n // R493 — reset button joins the chrome-strip active:scale-95\n // press-feedback family. The button already has transition-\n // transform + transform-gpu (R350 reset spin + R400 hover lift),\n // so just appending active:scale-95 plugs straight in. Compound\n // active state during press = hover-lift (-1px) + scale-95\n // composes as translateY(-1px) scale(0.95) — lift-and-compress\n // for tactile click feel.\n className=\"anet-topo-chrome-reset-breath p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px active:scale-95 transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60\"\n data-topo-chrome-reset-hover-lift=\"true\"\n /* Round 718 / Loop — reset button joins the respiratory\n family at 21 s as the 5th anchor on the chrome-strip\n pattern. Extends \"tiered-with-trio\" → \"tiered-with-\n quartet\"; first ATOMIC chrome button to breathe (the\n other 4 chrome anchors are either segmented-control\n wrappers or a data readout). Hover gate piggybacks on\n the existing data-topo-chrome-reset-hover attr — same\n leaf-element gate pattern as R703 zoom-level readout.\n See globals.css R718 block for cadence rationale. */\n data-topo-chrome-reset-breath=\"21s\"\n /* R594 — chrome reset button gains filter brightness(1.15)\n on hoveredReset. 33rd anchor in per-element brightness\n family, 2nd HTML-element anchor (R593 zoom-level was\n the first).\n\n Closes reset button hover signature at 5 axes:\n R400 button hover-lift translateY(-1px)\n R350 icon hover-rotate -8°\n R453 icon stroke-width 2.5 → 2.8\n R514 icon scale 1.0 → 1.10\n R594 button brightness 1 → 1.15 ← this round\n\n The reset button now has the densest hover signature\n among the 2 standalone chrome buttons (reset + fullscreen\n from R400). When user hovers, the entire button lifts\n (-1px), brightens (+15%), AND its icon rotates (-8°),\n thickens (+0.3 sw), and scales up (+10%). Inside +\n outside motion-coherent.\n\n Triple-paint multiplicative interaction at this surface:\n bg (hover:bg-white/5) × border (pal.containerBorder)\n × color (pal.legendText) ALL get brightness(1.15)\n multiplied in — the button's chrome stack uniformly\n brightens, reading as \"this control is awake under\n your cursor\".\n\n Inline transition shorthand replaces the className-\n based `transition-colors transition-transform` (R557\n banked: when adding new transition-driven hover axes\n to an element, extend the INLINE list — inline\n overrides className). All 5 transition properties (bg\n / color / border / transform / filter) now ride one\n 200ms ease-out beat. */\n data-topo-chrome-reset-brightness={hoveredReset ? '1.15' : '1'}\n data-topo-chrome-reset-halo-layers={hoveredReset ? '2' : '0'}\n style={{\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n color: pal.legendText,\n /* R667 — chrome reset button gains 2-layer drop-shadow halo\n on hover. Pre-R667 had only brightness(1.15) — no halo.\n R667 introduces stroke-tinted halo at 2+4 stride, alpha\n 0x80 → 0x40 (50% falloff), pal.legendAccent tint (cyan/\n teal). First chrome-control anchor in multi-layer halo\n family. */\n filter: hoveredReset ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n transition: 'color 200ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, transform 200ms ease-out, filter 200ms ease-out',\n }}\n aria-label=\"Reset view\"\n title=\"Reset zoom + pan (0, or double-click the canvas)\"\n >\n {/* R184: the refresh-arrow icon does one counter-clockwise\n rotation on click. CSS animation runs once; React removes\n the className after 460ms (just past the 450ms duration)\n so a subsequent click can replay. */}\n {/* Round 288 / Loop: reset icon strokeWidth 2 → 2.5 unifies\n the chrome icon weight family. Pre-R288 zoom-in / zoom-\n out icons rendered at strokeWidth 2.5 while reset +\n fullscreen icons sat thinner at strokeWidth 2 — five\n chrome icons in a single horizontal strip with two\n weights is exactly the inconsistency R268 closed for\n border colors. Same unification idiom now applied to\n icon strokes: zoom (2.5) + reset (2.5) + fullscreen\n (2.5) all share one weight. View-box (24×24) and\n display size (13×13) unchanged, so geometry stays\n pixel-stable — only the stroke deepens. */}\n {/* Round 453 / Loop: chrome reset icon strokeWidth hover\n lift — 2.5 → 2.8 on hoveredReset && !resetSpinning.\n Sibling to R443 runtime badge inner-icon sw lift\n (2.4→2.8) — both chrome icons now thicken on hover\n for tactile feedback. Pre-R453 reset hover was a\n rotate-only cue (R350); R453 adds a stroke-weight\n axis so the affordance reads with both motion (R350\n rotate -8°) AND geometry (R453 sw +0.3). Gated on\n !resetSpinning so the R184 spin keyframe owns paint\n during its 450ms run. 200ms stroke-width transition\n appended to the style list matches R350 transform\n cadence. data-topo-chrome-reset-icon-stroke-width\n attr exposes the resolved value for tests. */}\n <svg\n width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\"\n strokeWidth={hoveredReset && !resetSpinning ? '2.8' : '2.5'}\n strokeLinecap=\"round\" strokeLinejoin=\"round\"\n aria-hidden\n className={resetSpinning ? 'anet-reset-spin' : undefined}\n data-topo-chrome-reset-icon\n data-topo-chrome-reset-icon-stroke-width={hoveredReset && !resetSpinning ? '2.8' : '2.5'}\n // R350: hover-rotate preview of the R184 click-spin.\n // Gated on !resetSpinning so the anet-reset-spin keyframe\n // owns transform during its 450ms run. transformOrigin\n // 'center' so rotation pivots around the icon's centre\n // (default would be top-left and the icon would arc).\n /* Round 514 / Loop — extends R352/R353 chrome icon hover-\n scale family to the reset button. Pre-R514 the reset\n icon had hover-rotate (-8°, R350) + hover-sw (2.5→2.8,\n R453) but no hover-scale, while zoom-out (R352), zoom-\n in (R352), and fullscreen (R353) icons all carried\n `group-hover:scale-110`. R514 brings the reset icon\n into the same 3-axis hover signature (rotate + sw +\n scale) as the rest of the chrome strip.\n Implementation: inline transform composes rotate +\n scale into one string. `transform: rotate(-8deg)\n scale(1.1)` on hover; `rotate(0) scale(1)` at rest.\n transformOrigin 'center' applies to both — rotation\n pivots around centre AND scale grows from centre.\n The Tailwind `group-hover:scale-110` approach can't\n work here because inline `style.transform` overrides\n className-based transforms; compose the multi-axis\n transform inline instead.\n Chrome icon hover gesture parity (post-R514):\n zoom-out scale-110 + sw-lift (R352/R454)\n zoom-in scale-110 + sw-lift (R352/R454)\n fullscreen scale-110 + sw-lift (R353/R455)\n reset scale-1.1 + sw-lift + rotate -8°\n (R514 + R453 + R350)\n reset gets the EXTRA rotate axis because R350's spin\n preview semantic is reset-specific — the rotation\n hints at the click-spin (R184) the button will fire. */\n style={{\n transform: hoveredReset && !resetSpinning ? 'rotate(-8deg) scale(1.1)' : 'rotate(0deg) scale(1)',\n transformOrigin: 'center',\n transition: 'transform 200ms ease-out, stroke-width 200ms ease-out',\n }}\n data-topo-chrome-reset-icon-hover={hoveredReset && !resetSpinning ? 'true' : 'false'}\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n </button>\n {/* Round 178 / Loop: fullscreen chrome button picks up the\n active-state visual indicator R163 introduced for the\n Ring/Grid layout toggle. Pre-R178 the button changed\n icon when isFullscreen flipped but its background +\n foreground stayed unchanged — operators in fullscreen\n didn't get a strong 'you're in fullscreen' cue. Adding\n the bg-cyan-500/15 + text-cyan-300 active variant\n mirrors R163's pattern; hover variants tier 1\n brighter (cyan-500/20) when active so the chip\n continues to respond to mouse. Inline style now omits\n background + color when active so the Tailwind cyan\n classes win specificity. */}\n <button\n onClick={() => { popChrome('fullscreen'); toggleFullscreen(); }}\n onMouseEnter={() => setHoveredFullscreen(true)}\n onMouseLeave={() => setHoveredFullscreen(false)}\n onFocus={() => setHoveredFullscreen(true)}\n onBlur={() => setHoveredFullscreen(false)}\n data-topo-chrome-fullscreen\n data-topo-chrome-fullscreen-active={isFullscreen ? 'true' : 'false'}\n data-topo-chrome-fullscreen-popping={chromePopping === 'fullscreen' ? 'true' : 'false'}\n data-topo-chrome-fullscreen-hover={hoveredFullscreen ? 'true' : 'false'}\n data-topo-chrome-fullscreen-brightness={hoveredFullscreen ? '1.15' : '1'}\n data-topo-chrome-fullscreen-halo-layers={hoveredFullscreen ? '2' : '0'}\n // R196: fullscreen also picks up press-state — active variant\n // deepens cyan-500/20 → cyan-500/25 on press; non-active\n // deepens white/5 → white/10.\n // R249: chrome-pop on click — same one-vocabulary click signal\n // as layout toggle and zoom buttons.\n /* Round 270 / Loop: fullscreen INACTIVE picks up the cyan\n hover-preview pattern from the Layout toggle. The\n fullscreen button is a TOGGLE (enter/exit fullscreen) so\n its inactive state benefits from the same \"hover previews\n active state\" idiom R163 designed. Sibling treatment to\n the nodeSize buttons at line ~6711. */\n // R353: `group` lets the inner svg respond via group-hover —\n // sibling to R352 zoom buttons. Closes the chrome-strip per-\n // icon hover-affordance arc (zoom-out / zoom-in / reset /\n // fullscreen now all carry an icon-level hover gesture in\n // addition to the bg hover).\n // R400: hover translateY(-1px) lift — see reset button above for family doc.\n // R493 — fullscreen joins active:scale-95 press family (same as\n // reset above: lift-and-compress compound transform on press).\n className={`anet-topo-chrome-fullscreen-breath group p-1.5 rounded-md border hover:-translate-y-px active:scale-95 transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${\n isFullscreen\n ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25'\n : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'\n }${chromePopping === 'fullscreen' ? ' anet-chrome-pop' : ''}`}\n data-topo-chrome-fullscreen-hover-lift=\"true\"\n /* Round 719 / Loop — fullscreen button joins the respiratory\n family at 25 s. Closes the atomic-control DUO (reset 21 s +\n fullscreen 25 s) — both standalone chrome buttons now\n breathe at parity 7% alpha range with coprime cadences.\n 6th anchor on chrome-strip pattern, shape advances to\n \"tiered-with-quintet\". Hover-gate via existing\n data-topo-chrome-fullscreen-hover attr — sibling leaf-\n element gate pattern to R703 zoom-level and R718 reset.\n See globals.css R719 block for cadence rationale. */\n data-topo-chrome-fullscreen-breath=\"25s\"\n /* R595 — chrome fullscreen button gains filter brightness(1.15)\n on hoveredFullscreen. 34th anchor in per-element brightness\n family, 3rd HTML-element anchor (R593 zoom-level + R594\n reset). Sibling to R594 — closes the standalone chrome\n button pair (reset + fullscreen) at full brightness parity.\n\n Per the R400 family doc, reset + fullscreen are the only\n two standalone (non-segmented) chrome buttons. Both now\n share the same 5-axis hover signature pattern:\n button hover-lift translateY(-1px) R400\n icon hover-scale 1.0 → 1.10 R353 (full) / R514 (reset)\n icon stroke-width 2.5 → 2.8 R455 (full) / R453 (reset)\n icon hover-rotate R576 (full +3°) / R350 (reset -8°)\n button brightness 1 → 1.15 R595 (full) / R594 (reset)\n\n Inline transition shorthand replaces the className-based\n `transition-colors transition-transform` (R557 banked:\n inline overrides className for transition-driven hover\n axes). 5 transition properties (bg / color / border /\n transform / filter) ride one 200ms ease-out beat.\n\n Active-variant interaction: when isFullscreen=true the\n className applies cyan bg + cyan text. brightness(1.15)\n on top makes the active+hovered state read at maximum\n vivid cyan — the user knows they're poised to EXIT\n fullscreen with extra visual confirmation.\n\n data-topo-chrome-fullscreen-brightness attr exposes\n the gate for tests. */\n style={{\n borderColor: pal.containerBorder,\n ...(isFullscreen\n ? {}\n : { background: pal.legendBox.fill, color: pal.legendText }),\n /* R667 sibling — fullscreen chrome button gains 2-layer\n drop-shadow halo on hover, same R667 reset pattern. */\n filter: hoveredFullscreen ? `drop-shadow(0 0 2px ${pal.legendAccent}80) drop-shadow(0 0 4px ${pal.legendAccent}40) brightness(1.15)` : undefined,\n transition: 'color 200ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, transform 200ms ease-out, filter 200ms ease-out',\n }}\n aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}\n >\n {/* R288 / Loop: fullscreen enter + exit icons strokeWidth\n 2 → 2.5 — same chrome-icon weight unification described\n at the reset icon above. data-topo-chrome-fullscreen-\n icon attribute exposes BOTH variants (entered / exited)\n for the round's stroke-width regression probe. */}\n {/* Round 353 / Loop: fullscreen icon (both enter + exit\n variants) picks up the R352 family group-hover:scale-110.\n Pre-R353 hovering the button only changed the bg; the\n icon stayed still. R353 lifts the icon 10 % on hover —\n same gesture vocabulary as the zoom buttons. transform-\n gpu hint promotes the svg to its own compositor layer\n for crisper edges during the scale tween. Closes the\n chrome-strip per-icon hover-affordance arc. */}\n {/* R455 — fullscreen ENTER + EXIT icons pick up the same\n group-hover:[stroke-width:2.8] family lift as the\n zoom +/− icons (R454) and chrome reset icon (R453).\n Chrome icon hover sw lift family now 6 anchors —\n R208/R443 runtime badge + R453/R454-zoom-out/zoom-in\n + R455 fullscreen (this round). transition-[transform,\n stroke-width] expands existing transition-transform\n so the sw lift eases under R352 scale-110 cadence. */}\n {/* Round 576 / Loop — fullscreen icon picks up hover-rotate-3.\n Joins R350 reset / R547 pill × / R549 brand logo at 4th\n anchor in hover-rotate idiom. Corner arrows rotate 3° on\n hover — subtle \"preparing to transform\" gesture (enter\n or exit fullscreen). Tailwind v4 emits individual rotate\n property (banked R547) — sits alongside group-hover:\n scale-110 in independent rotate axis. */}\n {isFullscreen ? (\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className=\"transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:rotate-3 group-hover:[stroke-width:2.8] transform-gpu\" data-topo-chrome-fullscreen-icon=\"exit\">\n <path d=\"M8 3v4a1 1 0 0 1-1 1H3M21 8h-4a1 1 0 0 1-1-1V3M3 16h4a1 1 0 0 1 1 1v4M16 21v-4a1 1 0 0 1 1-1h4\" />\n </svg>\n ) : (\n // R576 sibling — enter variant also picks up rotate-3.\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className=\"transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:rotate-3 group-hover:[stroke-width:2.8] transform-gpu\" data-topo-chrome-fullscreen-icon=\"enter\">\n <path d=\"M3 8V5a2 2 0 0 1 2-2h3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M21 16v3a2 2 0 0 1-2 2h-3\" />\n </svg>\n )}\n </button>\n </div>\n\n {/* Issue #100/#106: draggable, resizable singleton chat popover.\n position:fixed so it floats above the page (overflow-hidden here\n doesn't clip fixed children). Rendered *inside* the container so\n that when the graph goes fullscreen (#81) the popover joins the\n fullscreen subtree and stays visible — a sibling render would be\n outside the fullscreened element and disappear. */}\n {chatAlias && (\n <ChatPopover alias={chatAlias} onClose={() => setChatAlias(null)} />\n )}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { AliasAvatar } from './AliasAvatar';\nimport { TaskChatPanel } from './TaskChatPanel';\nimport { useSessions } from '../lib/hooks';\nimport { relativeAgo } from '../lib/time';\n\ninterface ChatPopoverProps {\n /** Node alias to chat with. Changing it switches the conversation. */\n alias: string;\n onClose: () => void;\n}\n\nconst MAX_W = 380;\nconst MAX_H = 520;\nconst MIN_W = 300;\nconst MIN_H = 320;\nconst MARGIN = 16;\nconst MOBILE_BP = 640;\n\n/**\n * Issue #100: a floating, draggable chat window opened by clicking a node\n * avatar in the topology graph. Singleton — the parent (TopoGraph) keeps a\n * single `chatAlias`, so clicking another node just swaps `alias` here and\n * the conversation switches in place.\n *\n * Issue #106: a bottom-right handle resizes the window (drag to change w/h,\n * clamped to MIN_W/MIN_H .. the viewport). Coexists with the header move-drag\n * and the close button — each interaction lives on a distinct element and\n * stops its own pointerdown from reaching the others.\n *\n * One consistent design across viewports: a floating draggable card sized to\n * fit (`min(380, vw-32) × min(520, vh-96)`). On a phone it ends up near\n * full-width, so it's docked low on open — the graph above stays visible, and\n * to chat with a different node you drag it down / tap an exposed avatar.\n *\n * The chat body reuses TaskChatPanel's `inline` mode — send / SSE-receive /\n * history are already solved there; this component only adds the floating\n * shell + drag/resize behaviour.\n */\n/** Round 37 / Loop: surface node metadata (cwd + last-seen) inside the\n * ChatPopover header. The SVG <title> tooltip (Rounds 33-34) only shows\n * on hover-over-node, which is lost once the popover is open and\n * potentially dragged away. Lifting cwd and last-seen into the popover\n * header keeps the context where the user actually needs it.\n *\n * Round 38: relativeAgo factored to app/lib/time.ts so this file shares\n * the same TZ-safe parser as TopoGraph (was a duplicated mirror until\n * this round). */\n\nexport function ChatPopover({ alias, onClose }: ChatPopoverProps) {\n // Position + size are resolved on mount (SSR-safe defaults here).\n const [pos, setPos] = useState({ x: 0, y: 0 });\n const [size, setSize] = useState({ w: MAX_W, h: MAX_H });\n const dragRef = useRef<{ active: boolean; startX: number; startY: number; baseX: number; baseY: number }>({\n active: false, startX: 0, startY: 0, baseX: 0, baseY: 0,\n });\n const resizeRef = useRef<{ active: boolean; startX: number; startY: number; baseW: number; baseH: number }>({\n active: false, startX: 0, startY: 0, baseW: 0, baseH: 0,\n });\n\n // Round 37 / Loop: pull the target session out of the SWR cache that\n // TopoGraph already populates — same URL, so no extra network fetch.\n const { sessions } = useSessions();\n const session = useMemo(() => sessions.find(s => s.alias === alias), [sessions, alias]);\n const isOnline = !!session && session.status !== 'offline';\n const lastSeenLine = !isOnline ? relativeAgo(session?.last_seen_at) : null;\n\n const clamp = useCallback((x: number, y: number, w: number, h: number) => {\n const maxX = Math.max(MARGIN, window.innerWidth - w - MARGIN);\n const maxY = Math.max(MARGIN, window.innerHeight - h - MARGIN);\n return {\n x: Math.min(Math.max(MARGIN, x), maxX),\n y: Math.min(Math.max(MARGIN, y), maxY),\n };\n }, []);\n\n useEffect(() => {\n const place = () => {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const w = Math.min(MAX_W, vw - MARGIN * 2);\n const h = Math.min(MAX_H, vh - MARGIN * 6);\n setSize({ w, h });\n const mobile = vw < MOBILE_BP;\n // Desktop: top-right of the graph. Mobile: docked low so the graph\n // above stays visible — switch nodes by tapping an exposed avatar.\n const x = mobile ? MARGIN : vw - w - 24;\n const y = mobile ? vh - h - MARGIN : 96;\n setPos(clamp(x, y, w, h));\n };\n place();\n // Keep the popover on-screen if the window is resized.\n window.addEventListener('resize', place);\n return () => window.removeEventListener('resize', place);\n }, [clamp]);\n\n // Esc closes — matches the rest of the dashboard's overlay convention.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [onClose]);\n\n const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n dragRef.current = { active: true, startX: e.clientX, startY: e.clientY, baseX: pos.x, baseY: pos.y };\n };\n const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const d = dragRef.current;\n if (!d.active) return;\n setPos(clamp(d.baseX + (e.clientX - d.startX), d.baseY + (e.clientY - d.startY), size.w, size.h));\n };\n const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragRef.current.active) return;\n dragRef.current.active = false;\n try { (e.currentTarget as Element).releasePointerCapture?.(e.pointerId); } catch {}\n };\n\n // Resize from the bottom-right handle. stopPropagation keeps the header\n // move-drag out of it; the popover is top-left anchored so growing it can\n // only push the bottom/right edges, which we clamp to the viewport.\n const onResizeDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n e.stopPropagation();\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n resizeRef.current = { active: true, startX: e.clientX, startY: e.clientY, baseW: size.w, baseH: size.h };\n };\n const onResizeMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const r = resizeRef.current;\n if (!r.active) return;\n e.stopPropagation();\n const maxW = Math.max(MIN_W, window.innerWidth - pos.x - MARGIN);\n const maxH = Math.max(MIN_H, window.innerHeight - pos.y - MARGIN);\n setSize({\n w: Math.min(maxW, Math.max(MIN_W, r.baseW + (e.clientX - r.startX))),\n h: Math.min(maxH, Math.max(MIN_H, r.baseH + (e.clientY - r.startY))),\n });\n };\n const onResizeUp = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!resizeRef.current.active) return;\n resizeRef.current.active = false;\n e.stopPropagation();\n try { (e.currentTarget as Element).releasePointerCapture?.(e.pointerId); } catch {}\n };\n\n return (\n <div\n className=\"fixed z-50 flex flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg)] shadow-2xl shadow-black/60 anet-fade-in\"\n style={{ left: pos.x, top: pos.y, width: size.w, height: size.h }}\n role=\"dialog\"\n aria-label={`Chat with ${alias}`}\n >\n {/* Drag handle — the whole header bar moves the window. */}\n <div\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerUp}\n className=\"flex items-center justify-between px-3 py-2.5 border-b border-[var(--border)] bg-[var(--bg-secondary)] rounded-t-xl cursor-grab active:cursor-grabbing select-none touch-none\"\n >\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <AliasAvatar alias={alias} size={28} />\n <div className=\"min-w-0\">\n <div className=\"text-sm font-semibold text-[var(--fg)] truncate\">{alias}</div>\n {/* Round 37: cwd / last-seen lines surface the same metadata\n the SVG <title> tooltip carries (rounds 33-34), but inside\n the popover where it stays accessible after dragging the\n window away from the node. Fall back to the drag-hint\n when no metadata is reported. */}\n {session?.project_dir ? (\n <div className=\"text-[10px] text-[var(--fg-muted)] truncate font-mono\" title={session.project_dir} data-popover-cwd>\n cwd: {session.project_dir}\n </div>\n ) : null}\n {lastSeenLine ? (\n <div className=\"text-[10px] text-[var(--fg-muted)] truncate\" data-popover-lastseen>\n last seen: {lastSeenLine}\n </div>\n ) : null}\n {!session?.project_dir && !lastSeenLine ? (\n <div className=\"text-[10px] text-[var(--fg-muted)]\">Drag to move · Esc to close</div>\n ) : null}\n </div>\n </div>\n <button\n onClick={onClose}\n // Without this the header's drag handler captures the pointer and\n // Chromium retargets the click to the header — the button never fires.\n onPointerDown={(e) => e.stopPropagation()}\n aria-label=\"Close chat\"\n className=\"inline-flex min-h-[44px] min-w-[44px] items-center justify-center text-[var(--fg-muted)] hover:text-[var(--fg)] rounded-lg hover:bg-[var(--bg-elevated)] shrink-0\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Chat body — TaskChatPanel inline mode owns send / SSE / history. */}\n <div className=\"flex-1 min-h-0\">\n <TaskChatPanel alias={alias} onClose={onClose} inline />\n </div>\n\n {/* Issue #106: bottom-right resize handle. */}\n <div\n onPointerDown={onResizeDown}\n onPointerMove={onResizeMove}\n onPointerUp={onResizeUp}\n onPointerCancel={onResizeUp}\n aria-label=\"Resize chat\"\n className=\"absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize touch-none z-10\"\n style={{ touchAction: 'none' }}\n >\n <svg className=\"absolute bottom-0.5 right-0.5 w-3 h-3 text-[var(--fg-dim)]\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M11 5L5 11M11 9L9 11\" />\n </svg>\n </div>\n </div>\n );\n}\n","/** Issue #96: node visual identity — model vendor + runtime.\n *\n * Single source for two orthogonal node dimensions:\n * - vendor : which model house powers the node (avatar body)\n * - runtime: which execution shell it runs in (corner badge)\n *\n * Both are driven by the server `model` / `runtime` fields added in\n * commhub-server 0.8.1-preview.0 (#96 Phase 1, commit 08482ef). Old nodes\n * report `model: null` / `runtime: null` and fall back gracefully — never a\n * broken image or blank avatar.\n *\n * Adding a vendor = one entry in VENDOR_RULES. Dropping a real logo file into\n * public/vendors/ + setting `logo` lights it up with zero render-code change;\n * until then a vendor-tinted monogram stands in (not a fake official logo).\n */\n\nexport interface VendorIdentity {\n id: string;\n label: string;\n /** Monogram fallback colours, used when `logo` is null. */\n mono: { bg: string; ring: string; text: string };\n /** 1-char monogram shown when there's no logo asset. */\n initial: string;\n /** Packaged logo asset path, or null → render the monogram instead. */\n logo: string | null;\n}\n\nconst UNKNOWN_VENDOR: VendorIdentity = {\n id: 'unknown',\n label: 'Unknown vendor',\n mono: { bg: 'hsl(220 10% 26%)', ring: 'hsl(220 10% 46%)', text: 'hsl(220 14% 82%)' },\n initial: '?',\n logo: null,\n};\n\n// Vendor identities, named so both the model-prefix rules and the\n// runtime fallback map below can reference the same object.\nconst INTERN_VENDOR: VendorIdentity = {\n id: 'intern',\n label: '书生 · 上海 AI 实验室',\n mono: { bg: 'hsl(28 38% 24%)', ring: 'hsl(32 45% 52%)', text: 'hsl(34 60% 82%)' },\n initial: '书',\n // #79 shipped this asset — reuse it as the 书生 vendor logo.\n logo: '/intern_avatar.png',\n};\nconst MINIMAX_VENDOR: VendorIdentity = {\n id: 'minimax',\n label: 'MiniMax',\n mono: { bg: 'hsl(18 50% 26%)', ring: 'hsl(18 65% 52%)', text: 'hsl(20 80% 82%)' },\n initial: 'M',\n // Official MiniMax mark (simple-icons set, CC0) on a warm-red tinted\n // badge. Vincent-authorized 2026-05-21 to use real vendor marks.\n logo: '/vendors/minimax.svg',\n};\nconst ANTHROPIC_VENDOR: VendorIdentity = {\n id: 'anthropic',\n label: 'Anthropic',\n mono: { bg: 'hsl(16 32% 26%)', ring: 'hsl(16 48% 54%)', text: 'hsl(18 60% 84%)' },\n initial: 'A',\n // Official Anthropic mark (simple-icons set, CC0) on a warm-orange\n // tinted badge. Vincent-authorized 2026-05-21 to use real vendor marks.\n logo: '/vendors/claude.svg',\n};\nconst OPENAI_VENDOR: VendorIdentity = {\n id: 'openai',\n label: 'OpenAI',\n mono: { bg: 'hsl(165 26% 22%)', ring: 'hsl(165 40% 44%)', text: 'hsl(165 45% 80%)' },\n initial: 'O',\n // Official OpenAI mark (simple-icons set, CC0) on a teal tinted\n // badge. Vincent-authorized 2026-05-21 to use real vendor marks.\n logo: '/vendors/openai.svg',\n};\nconst GROK_VENDOR: VendorIdentity = {\n id: 'grok',\n label: 'Grok Build',\n mono: { bg: 'hsl(150 14% 16%)', ring: 'hsl(150 48% 42%)', text: 'hsl(150 54% 82%)' },\n initial: 'G',\n // Local Grok Build mark for network graph avatars; keeps grok-build-acp\n // nodes out of the generic unknown-vendor fallback.\n logo: '/vendors/grok.svg',\n};\n\n// Ordered prefix rules — first match wins. `test` runs against a lowercased\n// model id. Keep the most specific prefixes first.\nconst VENDOR_RULES: Array<{ test: (m: string) => boolean; vendor: VendorIdentity }> = [\n { test: (m) => m.startsWith('intern'), vendor: INTERN_VENDOR },\n { test: (m) => m.startsWith('minimax'), vendor: MINIMAX_VENDOR },\n { test: (m) => m.startsWith('claude'), vendor: ANTHROPIC_VENDOR },\n { test: (m) => m.startsWith('grok') || m.startsWith('xai'), vendor: GROK_VENDOR },\n {\n test: (m) => m.startsWith('gpt') || m.startsWith('codex') || m.startsWith('o1') || m.startsWith('o3') || m.startsWith('o4'),\n vendor: OPENAI_VENDOR,\n },\n];\n\n/* Runtime → vendor fallback. Used only when the `model` field can't\n * resolve a vendor (null or unmatched).\n *\n * Why this exists: live hub /api/status probe (2026-05-21) showed 62%\n * of sessions report `model: null` — including ALL claude-code-cli\n * nodes — so the avatar fell back to the dark \"unknown vendor\"\n * monogram for most of the fleet. The `runtime` field, by contrast,\n * is reliably populated. A claude-code-cli / claude-agent-sdk node is\n * an Anthropic shell; codex-sdk is OpenAI's. Deriving the vendor from\n * runtime when the model is silent lights up the vendor logo for the\n * whole fleet instead of leaving it grey. The model still wins when\n * present — runtime is strictly the fallback. */\nconst RUNTIME_VENDOR: Record<string, VendorIdentity> = {\n 'claude-code-cli': ANTHROPIC_VENDOR,\n 'claude-agent-sdk': ANTHROPIC_VENDOR,\n 'codex-sdk': OPENAI_VENDOR,\n 'grok-build-acp': GROK_VENDOR,\n 'grok-build': GROK_VENDOR,\n grok: GROK_VENDOR,\n};\n\n/** Resolve a node's vendor identity. Model id wins; when the model is\n * null or unmatched, fall back to the execution runtime. Both silent\n * → UNKNOWN. */\nexport function vendorForModel(\n model: string | null | undefined,\n runtime?: string | null | undefined,\n): VendorIdentity {\n if (model) {\n const m = model.toLowerCase();\n for (const rule of VENDOR_RULES) {\n if (rule.test(m)) return rule.vendor;\n }\n }\n const rt = runtime?.toLowerCase();\n if (rt && RUNTIME_VENDOR[rt]) return RUNTIME_VENDOR[rt];\n return UNKNOWN_VENDOR;\n}\n\nexport type Runtime = 'claude-code-cli' | 'codex-sdk' | 'claude-agent-sdk' | 'grok-build-acp' | 'http-api';\n\nexport interface RuntimeIdentity {\n label: string;\n /** SVG path(s) drawn inside a 0..24 viewBox for the corner badge. */\n iconPath: string;\n /** Badge accent colour — kept clear of working/idle/offline status hues. */\n color: string;\n}\n\nconst RUNTIME_MAP: Record<Runtime, RuntimeIdentity> = {\n // terminal / CLI\n 'claude-code-cli': {\n label: 'Claude Code CLI',\n iconPath: 'M4 5h16v14H4z M7 9l3 3-3 3 M13 15h4',\n color: '#a78bfa',\n },\n // code-block / SDK\n 'codex-sdk': {\n label: 'Codex SDK',\n iconPath: 'M9 7l-5 5 5 5 M15 7l5 5-5 5',\n color: '#38bdf8',\n },\n // SDK, distinct hue from codex\n 'claude-agent-sdk': {\n label: 'Claude Agent SDK',\n iconPath: 'M4 7h16v10H4z M8 11h8 M8 14h5',\n color: '#34d399',\n },\n // Grok Build ACP bridge\n 'grok-build-acp': {\n label: 'Grok Build ACP',\n iconPath: 'M12 3l2.1 6.2H21l-5.5 4 2.1 6.8L12 15.9 6.4 20l2.1-6.8L3 9.2h6.9L12 3z',\n color: '#22c55e',\n },\n // cloud / API — non-resident process\n 'http-api': {\n label: 'HTTP API',\n iconPath: 'M7 18a4 4 0 010-8 5 5 0 019.6-1.3A3.5 3.5 0 0118 18z',\n color: '#fbbf24',\n },\n};\n\n/** Resolve a server runtime string to badge identity. Unknown → null. */\nexport function runtimeIdentity(runtime: string | null | undefined): RuntimeIdentity | null {\n if (!runtime) return null;\n return RUNTIME_MAP[runtime.toLowerCase() as Runtime] ?? null;\n}\n\n/** Compact one-line identity for hover / detail surfaces:\n * \"Anthropic · claude-opus-4 · Claude Code CLI\". Pieces with no data drop. */\nexport function identityLine(model: string | null | undefined, runtime: string | null | undefined): string {\n const v = vendorForModel(model, runtime);\n const r = runtimeIdentity(runtime);\n const parts: string[] = [];\n if (v.id !== 'unknown') parts.push(v.label);\n if (model) parts.push(model);\n if (r) parts.push(r.label);\n return parts.join(' · ');\n}\n","'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { Session } from './types';\nimport { timeAgo } from './utils';\nimport { AliasAvatar } from './AliasAvatar';\nimport { NodeSettingsPanel } from './NodeSettingsPanel';\n\ninterface AgentCardProps {\n session: Session;\n hasSse: boolean;\n sseCount: number;\n onChat?: (alias: string) => void;\n}\n\nconst STATUS_CONFIG: Record<string, { bg: string; text: string; dot: string; glow: string }> = {\n working: { bg: 'bg-green-900/30 border-green-800/30', text: 'text-green-300', dot: 'bg-green-500', glow: 'shadow-green-500/10' },\n idle: { bg: 'bg-cyan-900/30 border-cyan-800/30', text: 'text-cyan-300', dot: 'bg-cyan-400', glow: 'shadow-cyan-500/5' },\n blocked: { bg: 'bg-yellow-900/30 border-yellow-800/30', text: 'text-yellow-300', dot: 'bg-yellow-500', glow: '' },\n error: { bg: 'bg-red-900/30 border-red-800/30', text: 'text-red-300', dot: 'bg-red-500', glow: '' },\n};\n\nconst DEFAULT_STATUS = { bg: 'bg-gray-800/50 border-gray-700/30', text: 'text-gray-500', dot: 'bg-gray-500', glow: '' };\n\nexport function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardProps) {\n const cfg = hasSse ? (STATUS_CONFIG[s.status] || DEFAULT_STATUS) : DEFAULT_STATUS;\n // #260: per-node settings panel (UI-only stub). Local open state — the\n // panel itself is presentational and issues no backend calls.\n const [settingsOpen, setSettingsOpen] = useState(false);\n\n return (\n <>\n {settingsOpen && <NodeSettingsPanel session={s} onClose={() => setSettingsOpen(false)} />}\n <Link\n href={`/node?alias=${encodeURIComponent(s.alias)}`}\n prefetch={false}\n onClick={e => {\n // #217 M2 (Vincent tg 646 \"为什么还要留一个 chat 按钮,直接点击\n // 不就行了\"): tapping a reachable agent's card opens chat\n // directly — the Chat button is gone. Offline/unreachable cards\n // keep navigating to /node detail, and detail for reachable\n // agents stays available via the Agents tab.\n if (onChat && hasSse) {\n e.preventDefault();\n onChat(s.alias);\n }\n }}\n className={`anet-agent-card group relative block rounded-xl border p-3 sm:p-4 transition-all duration-300 cursor-pointer hover:-translate-y-0.5 ${\n hasSse\n ? `bg-[#161618] border-[#26262b] hover:border-cyan-500/30 hover:shadow-lg ${cfg.glow}`\n : 'bg-[#111113] border-[#1c1c1f] opacity-40'\n }`}\n >\n {/* Header: avatar + name + status. Avatar carries the alias→hue map\n shared with Messages/Nodes/Tasks/Overview; the live status dot\n stays as a small pulse-capable indicator. R5 of #190 mobile\n polish: trim mb-3 → mb-2 sm:mb-3 so the card's vertical rhythm\n tightens on narrow viewports. */}\n <div className=\"flex items-center justify-between mb-2 sm:mb-3\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <AliasAvatar alias={s.alias} size={22} />\n <span className=\"font-semibold text-white truncate text-sm\" title={s.alias}>{s.alias}</span>\n <span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${cfg.dot} ${hasSse && s.status === 'working' ? 'animate-pulse' : ''}`} />\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <span className={`text-[11px] px-2 py-0.5 rounded-md border ${cfg.bg} ${cfg.text}`}>\n {hasSse ? s.status : 'offline'}\n </span>\n {/* #260: per-node ⋮ menu → settings panel. The card is a <Link>,\n so stop the click from navigating / opening chat. Opens a\n UI-only stub panel (channels / model / mode / restart) — no\n backend wired yet. 44px tap target for mobile. */}\n <button\n type=\"button\"\n aria-label={`${s.alias} 节点设置`}\n onClick={e => {\n e.preventDefault();\n e.stopPropagation();\n setSettingsOpen(true);\n }}\n className=\"inline-flex h-7 w-7 items-center justify-center -mr-1 rounded-md text-gray-500 hover:text-white hover:bg-white/5 transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden>\n <circle cx=\"12\" cy=\"5\" r=\"1.6\" />\n <circle cx=\"12\" cy=\"12\" r=\"1.6\" />\n <circle cx=\"12\" cy=\"19\" r=\"1.6\" />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Agent type badge — hidden below sm because the runtime\n (`claude-code` / `codex`) repeats across nearly every card and\n chews ~28px per card × 99 sessions on Overview mobile. The\n agent type stays one tap away on /node detail. */}\n <div className=\"hidden sm:flex items-center gap-2 mb-3\">\n <span className=\"text-xs text-gray-600 bg-[#0e0e10] px-2 py-0.5 rounded border border-[#1c1c1f]\">\n {s.agent || 'unknown'}\n </span>\n {hasSse && (\n <span className=\"text-[10px] text-green-500\">SSE:{sseCount}</span>\n )}\n </div>\n\n {/* Task. Mobile: line-clamp-1 (one-liner) instead of two, and a\n single-line padding (px-2 py-1) so the task strip is ~28px\n rather than ~56px. The full task is still in the title\n tooltip and on /node detail. */}\n {/* #217 M3: the \"No active task\" italic placeholder is gone — the\n idle status chip in the header already says it, and the line\n cost ~30px on every idle card across a 150-node fleet. The\n task strip renders only when there is a task. */}\n {s.task && (\n <div className=\"text-xs text-gray-400 bg-[#0e0e10] rounded-lg px-2 sm:px-3 py-1 sm:py-2 border border-[#1c1c1f] line-clamp-1 sm:line-clamp-2\" title={s.task}>\n {s.task}\n </div>\n )}\n\n {/* Progress bar — hidden below sm so an empty 0% bar doesn't\n occupy 20px on every idle card. Visible from sm up where space\n is no longer the constraint. */}\n {s.progress > 0 && (\n <div className=\"hidden sm:block mt-3\">\n <div className=\"flex justify-between text-[10px] mb-1\">\n <span className=\"text-gray-600\">Progress</span>\n <span className={cfg.text}>{s.progress}%</span>\n </div>\n <div className=\"w-full bg-gray-800 rounded-full h-1.5 overflow-hidden\">\n <div\n className={`h-1.5 rounded-full transition-all duration-700 ${s.status === 'working' ? 'bg-green-500' : 'bg-cyan-500'}`}\n style={{ width: `${Math.min(s.progress, 100)}%` }}\n />\n </div>\n </div>\n )}\n\n {/* Footer: time + chat + hover chevron affordance (round 44).\n The card is a <Link> so it's clickable everywhere, but with no\n visible cue users may not realise. Chevron appears on hover and\n slides right ~2px for a \"drill in\" hint.\n #209 R40: two small mobile tweaks —\n (1) the hover chevron is `hidden sm:inline-block` because\n touch devices never trigger the :hover state, and the\n 12 px wide reserved slot was just an empty void on\n mobile — but flex with opacity-0 was still spending\n 12 + 8 (gap) = 20 px on the right side of every card.\n (2) mt-3 → mt-2 sm:mt-3 mirrors the same density pattern\n R28 / R39 brought to the rest of the page. */}\n <div className=\"mt-2 sm:mt-3 flex justify-between items-center text-[10px] text-gray-600\">\n {/* #217 D5 (Vincent: 乱七八糟的元素都可以删掉): the raw server\n hostname (cloud instance IDs like iZrj93…) is noise on a\n phone card — desktop keeps it, /node detail always has it. */}\n <span className=\"hidden sm:inline truncate\" title={s.server || ''}>{s.server || '--'}</span>\n <div className=\"flex items-center gap-2 ml-auto\">\n <span>{timeAgo(s.updated_at)}</span>\n <svg\n aria-hidden\n className=\"hidden sm:inline-block w-3 h-3 text-gray-700 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200\"\n fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n >\n <path d=\"M9 18l6-6-6-6\" />\n </svg>\n </div>\n </div>\n </Link>\n </>\n );\n}\n","'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { Session } from './types';\nimport { AliasAvatar } from './AliasAvatar';\n\n/**\n * Per-node settings panel (issue #260 → v0.11 flagship #262, Vincent tg\n * 2026-06-24, \"must actually work\").\n *\n * STATUS (F1/F2/F3, 通信龙 review round 2):\n * - **Wired (real)**: B Runtime·模型 model select + C 运行模式 flags\n * (permissionMode / dangerouslySkipPermissions / teammateMode / maxTurns /\n * budget / timeout). Controlled form state, loaded on mount from\n * `GET /api/anet/node-config` and persisted via `POST`. The six flags +\n * model are the editable contract 通信龙 specified.\n * - **F3 apply lifecycle**: after Save the panel runs a take-effect state\n * machine — saving → applying (node restarting) → applied | rejected |\n * timeout — polling `GET ?apply_id` for real backends and showing a colored\n * status strip (\"toast\") for the outcome.\n * - **Still P2 stub (disabled)**: A 接入 Channel (display-only) and D 运维\n * ops buttons (no-op), each with its own local \"暂未接后端\" note.\n *\n * ⚠️ The hub-side tool is not deployed yet (工程马 WIP): the route mock-falls\n * back with `mock: true` + `status: pending`, so the lifecycle is *simulated*\n * on local timers and the UI says so (\"模拟 · 后端未接入\") rather than claiming a\n * node actually restarted. Runtime + imageCapable stay read-only (auto-derived).\n * Field names mirror anet's real config.json.\n */\n\n// B. Runtime → selectable model presets (from anet VENDORS, bin/cli.ts).\n// Runtime itself is read-only this round; only the model is editable.\nconst RUNTIME_MODELS: Record<string, string[]> = {\n 'claude-agent-sdk': ['deepseek-v4-pro', 'MiniMax-M3', 'MiniMax-M2.7', 'claude-sonnet-4-6', 'claude-opus-4-x', 'intern', 'mimo'],\n 'claude-code-cli': ['(由 CLI 管理)'],\n 'codex-sdk': ['gpt-5.5'],\n 'grok-build-acp': ['grok-build'],\n};\n\n// C. config.flags contract (per 通信龙). permissionMode is a select; two\n// booleans; three numeric bounds. These six are the entire editable flag set.\nconst PERMISSION_MODES = ['auto', 'default', 'bypassPermissions'];\n\n// A. Channel display (P2 — read-only this round). `roadmap` = greyed \"即将支持\".\nconst CHANNELS: { key: string; label: string; roadmap?: boolean; fields: { label: string; value?: string }[] }[] = [\n {\n key: 'telegram', label: 'Telegram',\n fields: [\n { label: 'Bot Token', value: '••••••••••••' },\n { label: '允许的 chat id (allowFrom)', value: '—' },\n { label: '状态目录', value: '~/.anet/channels/telegram' },\n ],\n },\n {\n key: 'feishu', label: '飞书 Feishu',\n fields: [\n { label: 'App ID', value: 'cli_••••••' },\n { label: 'App Secret', value: '••••••••••••' },\n { label: '允许的 open_id (allowFrom)', value: '—' },\n { label: '允许的群 chat_id (allowChats)', value: '—' },\n { label: '群触发策略', value: 'mention' },\n ],\n },\n {\n key: 'commhub', label: 'CommHub',\n fields: [\n { label: 'Hub 地址', value: '<hub-address>' },\n { label: '节点 token(自动)', value: '••••••••' },\n ],\n },\n { key: 'wechat', label: 'WeChat', roadmap: true, fields: [] },\n];\n\n// D. Node ops — still P2 no-op stubs this round. `danger` = destructive styling.\n\n// Editable flag form shape. Numeric fields are kept as strings for controlled\n// inputs and coerced (or dropped when blank) at save time.\ntype FlagsForm = {\n permissionMode: string;\n dangerouslySkipPermissions: boolean;\n teammateMode: boolean;\n maxTurns: string;\n budget: string;\n timeout: string;\n};\n\nconst DEFAULT_FLAGS: FlagsForm = {\n permissionMode: 'default',\n dangerouslySkipPermissions: false,\n teammateMode: false,\n maxTurns: '',\n budget: '',\n timeout: '',\n};\n\n// F3 apply lifecycle. A save flows:\n// idle → saving (POST in flight) → applying (config dispatched, node\n// restarting / taking effect) → applied | rejected | timeout.\n// `error` = the save POST itself failed. The `mock` flag flavours the terminal\n// copy when the hub tool isn't deployed (the lifecycle is then simulated on\n// local timers rather than polled).\ntype ApplyPhase = 'idle' | 'saving' | 'applying' | 'applied' | 'rejected' | 'timeout' | 'error';\n\n// Real-backend apply-status poll cadence + ceiling. The mock path uses its own\n// short timers instead of polling (the route would only ever return pending).\nconst APPLY_POLL_MS = 1500;\nconst APPLY_TIMEOUT_MS = 30000;\n// Simulated mock progression delay (dispatched → applied).\nconst MOCK_APPLY_MS = 1400;\n// Auto-dismiss the success strip after this long.\nconst SUCCESS_DISMISS_MS = 4000;\n// Per-request ceiling so a hung hub proxy can't freeze the lifecycle — every\n// POST/poll fetch is aborted past this (通信牛 review #11 blocker 2).\nconst FETCH_TIMEOUT_MS = 8000;\n\nfunction isAbort(e: unknown): boolean {\n return e instanceof DOMException && e.name === 'AbortError';\n}\n\n/** fetch + json with a hard AbortController timeout. Rejects (AbortError) if it\n * doesn't resolve in `timeoutMs` — callers treat that as transient/abort. */\nasync function fetchJson(url: string, init: RequestInit, timeoutMs: number): Promise<{ res: Response; data: Record<string, unknown> }> {\n const ctrl = new AbortController();\n const to = setTimeout(() => ctrl.abort(), timeoutMs);\n try {\n const res = await fetch(url, { ...init, signal: ctrl.signal });\n const data = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n return { res, data };\n } finally {\n clearTimeout(to);\n }\n}\n\nfunction channelSet(channels: Session['channels']): Set<string> {\n if (!channels) return new Set();\n const list = Array.isArray(channels) ? channels : String(channels).split(/[,\\s]+/);\n return new Set(list.map(c => c.trim().toLowerCase()).filter(Boolean));\n}\n\n/** Coerce a snapshot flag value into the string form fields expect. */\nfunction numStr(v: unknown): string {\n return v === undefined || v === null || v === '' ? '' : String(v);\n}\n\n/** Section heading. */\nfunction SectionTitle({ children }: { children: React.ReactNode }) {\n return <h3 className=\"text-[11px] font-medium uppercase tracking-wide text-gray-500 mb-2\">{children}</h3>;\n}\n\n/** Read-only label/value row. */\nfunction InfoRow({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex items-center justify-between gap-3 px-3 py-1.5 text-[12px]\">\n <span className=\"text-gray-500 shrink-0\">{label}</span>\n <span className=\"text-gray-300 truncate font-mono text-[11px]\" title={value}>{value}</span>\n </div>\n );\n}\n\n/** Disabled text field (P2 channel display). */\nfunction StubField({ label, value }: { label: string; value?: string }) {\n return (\n <label className=\"block\">\n <span className=\"text-[11px] text-gray-500\">{label}</span>\n <input\n type=\"text\" disabled readOnly value={value ?? ''} placeholder=\"未配置\"\n className=\"mt-0.5 w-full rounded-md border border-[#26262b] bg-[#0e0e10] px-2.5 py-1.5 text-[12px] text-gray-300 cursor-not-allowed disabled:opacity-100\"\n />\n </label>\n );\n}\n\n/** Live controlled toggle. */\nfunction Toggle({ label, checked, onChange, disabled }: { label: string; checked: boolean; onChange: (v: boolean) => void; disabled?: boolean }) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n className={`flex w-full items-center justify-between rounded-lg border border-[#26262b] bg-[#161618] px-3 py-2 text-sm text-gray-300 transition-colors ${disabled ? 'cursor-not-allowed opacity-60' : 'hover:bg-[#1c1c1f]'}`}\n >\n <span className=\"text-left pr-3\">{label}</span>\n <span className={`relative inline-block h-5 w-9 shrink-0 rounded-full transition-colors ${checked ? 'bg-cyan-600' : 'bg-[#2a2a30]'}`}>\n <span className={`absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all ${checked ? 'left-[18px]' : 'left-0.5'}`} />\n </span>\n </button>\n );\n}\n\n/** Live controlled select. */\nfunction Select({ label, value, options, onChange }: { label: string; value: string; options: string[]; onChange: (v: string) => void }) {\n return (\n <label className=\"block\">\n <span className=\"text-[11px] text-gray-500\">{label}</span>\n <select\n value={value}\n onChange={e => onChange(e.target.value)}\n className=\"mt-0.5 w-full appearance-none rounded-md border border-[#26262b] bg-[#161618] px-2.5 py-1.5 text-[12px] text-gray-200 hover:border-[#3a3a42] focus:border-cyan-600 focus:outline-none\"\n >\n {options.map(o => <option key={o} value={o}>{o}</option>)}\n </select>\n </label>\n );\n}\n\n/** Live controlled numeric field (kept as string; coerced at save). */\nfunction NumberField({ label, value, onChange, placeholder }: { label: string; value: string; onChange: (v: string) => void; placeholder?: string }) {\n return (\n <label className=\"block\">\n <span className=\"text-[11px] text-gray-500\">{label}</span>\n <input\n type=\"number\"\n inputMode=\"numeric\"\n value={value}\n placeholder={placeholder ?? '默认'}\n onChange={e => onChange(e.target.value)}\n className=\"mt-0.5 w-full rounded-md border border-[#26262b] bg-[#0e0e10] px-2.5 py-1.5 text-[12px] text-gray-200 focus:border-cyan-600 focus:outline-none\"\n />\n </label>\n );\n}\n\nexport function NodeSettingsPanel({ session: s, onClose }: { session: Session; onClose: () => void }) {\n const active = channelSet(s.channels);\n const runtime = s.runtime || '—';\n const initialModel = s.model || s.agent || '';\n // imageCapable: read-only, auto from model (MiniMax-M3 / Claude = yes).\n const imageCapable = /minimax-m3|claude/i.test(initialModel);\n // Model options for this runtime; always include the current value so a\n // server-set model outside the preset list stays selectable.\n const presetModels = RUNTIME_MODELS[runtime] || [];\n const modelOptions = Array.from(new Set([initialModel, ...presetModels].filter(Boolean)));\n\n const nodeKey = s.node_id || s.alias;\n\n // ---- Editable form state (B model + C flags) ----\n const [model, setModel] = useState(initialModel);\n const [flags, setFlags] = useState<FlagsForm>(DEFAULT_FLAGS);\n const [dirty, setDirty] = useState(false);\n const [loading, setLoading] = useState(true);\n // ---- F3 apply lifecycle ----\n const [phase, setPhase] = useState<ApplyPhase>('idle');\n const [phaseMsg, setPhaseMsg] = useState('');\n const [phaseMock, setPhaseMock] = useState(false);\n // ---- M1 node lifecycle actions (restart wired to RFC-024 restart_node;\n // rename/stop/delete gated on backend → shown as 即将支持). Self-contained,\n // independent of the config apply lifecycle above.\n const [lcBusy, setLcBusy] = useState(false);\n const [lcResult, setLcResult] = useState<{ tone: 'info' | 'ok' | 'warn' | 'err'; text: string } | null>(null);\n // Outstanding lifecycle timers (mock progression, real poll, auto-dismiss);\n // cleared on unmount and at the start of each new save so a stale timer can't\n // overwrite a fresh result.\n const timersRef = useRef<ReturnType<typeof setTimeout>[]>([]);\n const clearTimers = useCallback(() => {\n timersRef.current.forEach(clearTimeout);\n timersRef.current = [];\n }, []);\n const after = useCallback((ms: number, fn: () => void) => {\n timersRef.current.push(setTimeout(fn, ms));\n }, []);\n // Avoid setState after unmount (panel closes mid-request).\n const mounted = useRef(true);\n useEffect(() => () => { mounted.current = false; clearTimers(); }, [clearTimers]);\n\n // Monotonic run id, bumped on every save. Each async lifecycle callback\n // captures the run id it belongs to and bails if a newer save superseded it —\n // this guards the callback that has ALREADY passed `await fetch` and so can't\n // be cancelled by clearTimers() (通信牛 review #11 blocker 1: stale poll\n // overwriting a fresh save).\n const applySeqRef = useRef(0);\n // \"First terminal wins\" within a run: once applied/rejected/timeout is set, a\n // late result can't overwrite it — this makes the 30s ceiling truly hard\n // (通信牛 review #11 blocker 2).\n const settledRef = useRef(false);\n const isCurrent = useCallback((runId: number) => mounted.current && runId === applySeqRef.current, []);\n const settle = useCallback((runId: number, apply: () => void) => {\n if (!isCurrent(runId) || settledRef.current) return;\n settledRef.current = true;\n apply();\n }, [isCurrent]);\n\n const setFlag = useCallback(<K extends keyof FlagsForm>(key: K, v: FlagsForm[K]) => {\n setFlags(f => ({ ...f, [key]: v }));\n setDirty(true);\n }, []);\n\n // Load current config on mount. Falls back to session-derived defaults when\n // the hub tool isn't deployed (route returns mock with empty flags).\n useEffect(() => {\n let cancelled = false;\n (async () => {\n try {\n const res = await fetch(`/api/anet/node-config?node_id=${encodeURIComponent(nodeKey)}`, { cache: 'no-store' });\n const data = await res.json().catch(() => ({}));\n if (cancelled || !mounted.current) return;\n if (data?.model) setModel(String(data.model));\n const f = data?.flags && typeof data.flags === 'object' ? data.flags : {};\n setFlags({\n permissionMode: typeof f.permissionMode === 'string' && PERMISSION_MODES.includes(f.permissionMode) ? f.permissionMode : DEFAULT_FLAGS.permissionMode,\n dangerouslySkipPermissions: !!f.dangerouslySkipPermissions,\n teammateMode: !!f.teammateMode,\n maxTurns: numStr(f.maxTurns),\n budget: numStr(f.budget),\n timeout: numStr(f.timeout),\n });\n } catch {\n // keep defaults — Save still works (mock path) so the UI is reviewable\n } finally {\n if (!cancelled && mounted.current) setLoading(false);\n }\n })();\n return () => { cancelled = true; };\n }, [nodeKey]);\n\n // Terminal \"applied\" state + auto-dismiss of the success strip.\n const finishApplied = useCallback((mock: boolean, runId: number) => {\n settle(runId, () => {\n setPhase('applied');\n setPhaseMock(mock);\n setPhaseMsg(mock ? '已应用(模拟 · 后端未接入,未真正下发)' : '配置已生效');\n after(SUCCESS_DISMISS_MS, () => {\n if (isCurrent(runId)) setPhase(p => (p === 'applied' ? 'idle' : p));\n });\n });\n }, [after, isCurrent, settle]);\n\n const markTimeout = useCallback((runId: number) => {\n settle(runId, () => {\n setPhase('timeout');\n setPhaseMsg('应用超时未确认,请稍后在节点详情核对');\n });\n }, [settle]);\n\n // Poll the real apply-status endpoint until terminal or timeout. `runId` ties\n // every callback to the save that started it; a newer save (or a settled\n // result) makes it bail before touching state.\n const pollApply = useCallback((applyId: string, startedAt: number, runId: number) => {\n after(APPLY_POLL_MS, async () => {\n if (!isCurrent(runId) || settledRef.current) return;\n let status: string | undefined;\n let detail: string | undefined;\n try {\n const { data } = await fetchJson(`/api/anet/node-config?apply_id=${encodeURIComponent(applyId)}`, { cache: 'no-store' }, FETCH_TIMEOUT_MS);\n status = data?.status as string | undefined;\n detail = data?.detail as string | undefined;\n } catch { /* transient / aborted — fall through to retry or timeout */ }\n // Re-check AFTER the await: a newer save may have superseded this poll.\n if (!isCurrent(runId) || settledRef.current) return;\n if (status === 'applied') { finishApplied(false, runId); return; }\n if (status === 'rejected') {\n settle(runId, () => {\n setPhase('rejected');\n setPhaseMsg(detail ? `应用被拒绝:${detail}` : '应用被拒绝');\n });\n return;\n }\n if (Date.now() - startedAt >= APPLY_TIMEOUT_MS) { markTimeout(runId); return; }\n pollApply(applyId, startedAt, runId); // still pending → keep polling\n });\n }, [after, finishApplied, isCurrent, markTimeout, settle]);\n\n async function handleSave() {\n clearTimers(); // cancel any pending timers from a prior run\n const runId = ++applySeqRef.current; // supersede any in-flight (post-await) callback\n settledRef.current = false;\n setPhase('saving');\n setPhaseMsg('');\n setPhaseMock(false);\n // Coerce numeric strings → numbers; drop blanks so unset fields aren't\n // forced to 0 on the backend.\n const numOrUndef = (v: string) => (v.trim() === '' ? undefined : Number(v));\n const payload = {\n node_id: nodeKey,\n model,\n flags: {\n permissionMode: flags.permissionMode,\n dangerouslySkipPermissions: flags.dangerouslySkipPermissions,\n teammateMode: flags.teammateMode,\n maxTurns: numOrUndef(flags.maxTurns),\n budget: numOrUndef(flags.budget),\n timeout: numOrUndef(flags.timeout),\n },\n };\n let data: { ok?: boolean; error?: string; mock?: boolean; applied?: boolean; status?: string; applyId?: string } = {};\n try {\n const r = await fetchJson('/api/anet/node-config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }, FETCH_TIMEOUT_MS);\n if (!isCurrent(runId)) return; // a newer save started while this POST was in flight\n data = r.data as typeof data;\n if (!r.res.ok || data?.ok === false) {\n settledRef.current = true;\n setPhase('error');\n setPhaseMsg(data?.error ? `保存失败:${data.error}` : '保存失败');\n return;\n }\n } catch (e) {\n if (!isCurrent(runId)) return;\n settledRef.current = true;\n setPhase('error');\n setPhaseMsg(isAbort(e) ? '保存超时,请重试' : e instanceof Error ? `保存失败:${e.message}` : '保存失败');\n return;\n }\n\n // Save accepted — now drive the take-effect lifecycle.\n setDirty(false);\n const mock = !!(data.mock || data.applied === false);\n if (data.status === 'applied') { finishApplied(mock, runId); return; }\n if (data.status === 'rejected') { settle(runId, () => { setPhase('rejected'); setPhaseMsg('应用被拒绝'); }); return; }\n\n // Optimistic \"applying\" — node is restarting / taking the new config.\n setPhase('applying');\n setPhaseMock(mock);\n if (mock) {\n // Backend not live: simulate the apply so the lifecycle UI is reviewable.\n after(MOCK_APPLY_MS, () => finishApplied(true, runId));\n } else if (data.applyId) {\n // Hard 30s ceiling that fires regardless of whether a poll is in flight\n // or the hub proxy is hanging — `settle` makes the first terminal win, so\n // a late poll result can't reopen it (通信牛 review #11 blocker 2).\n after(APPLY_TIMEOUT_MS, () => markTimeout(runId));\n pollApply(data.applyId, Date.now(), runId);\n } else {\n // Real success with no applyId to poll → treat as applied.\n finishApplied(false, runId);\n }\n }\n\n async function handleRestart() {\n if (lcBusy) return;\n if (typeof window !== 'undefined' && !window.confirm(`重启节点「${s.alias}」?当前会话会中断,节点将以现有配置重新启动。`)) return;\n setLcBusy(true);\n setLcResult({ tone: 'info', text: '重启指令下发中…' });\n try {\n const r = await fetch('/api/anet/node-lifecycle', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n // Pass the node's own network so multi-network users (whose default\n // network may differ from this node's) restart the right node.\n body: JSON.stringify({ node_id: nodeKey, action: 'restart', ...(s.network_id ? { network_id: s.network_id } : {}) }),\n });\n const data = await r.json().catch(() => ({}));\n if (!mounted.current) return;\n if (r.ok && data?.ok) {\n setLcResult({ tone: 'ok', text: '重启已下发,节点正在重启生效中…' });\n } else if (data?.unconfirmed) {\n setLcResult({ tone: 'warn', text: `后端工具未上线:${data?.error || 'restart_node 不可用'}` });\n } else {\n setLcResult({ tone: 'err', text: data?.error ? `重启失败:${data.error}` : '重启失败' });\n }\n } catch (e) {\n if (mounted.current) setLcResult({ tone: 'err', text: `重启失败:${e instanceof Error ? e.message : '网络错误'}` });\n } finally {\n if (mounted.current) setLcBusy(false);\n }\n }\n\n return (\n <>\n <div className=\"fixed inset-0 bg-black/40 z-40\" onClick={onClose} aria-hidden />\n\n <div\n role=\"dialog\"\n aria-label={`${s.alias} 节点设置`}\n className=\"fixed top-0 right-0 h-[100dvh] w-full sm:w-[440px] bg-[#0f0f11] border-l border-[#26262b] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in\"\n >\n {/* Header */}\n <div className=\"flex items-center gap-2.5 px-4 py-3 border-b border-[#26262b]\">\n <AliasAvatar alias={s.alias} size={26} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-semibold text-white truncate\" title={s.alias}>{s.alias}</div>\n <div className=\"text-[11px] text-gray-500\">节点设置</div>\n </div>\n <button onClick={onClose} aria-label=\"关闭设置\" className=\"inline-flex h-9 w-9 items-center justify-center text-gray-500 hover:text-white rounded-md hover:bg-white/5\">\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"flex-1 overflow-y-auto px-4 py-4 space-y-5\">\n {/* E. 基础信息(只读) */}\n <section>\n <SectionTitle>基础信息</SectionTitle>\n <div className=\"rounded-lg border border-[#26262b] bg-[#161618] divide-y divide-[#1c1c1f]\">\n <InfoRow label=\"别名\" value={s.alias} />\n <InfoRow label=\"node_id\" value={s.node_id || '—'} />\n <InfoRow label=\"所属 network\" value={s.network_id || '—'} />\n <InfoRow label=\"当前 session\" value={s.session_id || '—'} />\n <InfoRow label=\"在线状态\" value={s.status || '—'} />\n </div>\n </section>\n\n {/* B. Runtime + 模型 (model editable) */}\n <section>\n <SectionTitle>Runtime · 模型</SectionTitle>\n <div className=\"space-y-2.5\">\n <div>\n <span className=\"text-[11px] text-gray-500\">Runtime(只读)</span>\n <div className=\"mt-0.5 rounded-md border border-[#26262b] bg-[#0e0e10] px-2.5 py-1.5 text-[12px] text-gray-400 font-mono\">{runtime}</div>\n </div>\n {modelOptions.length > 1 ? (\n <Select label=\"模型 / Preset\" value={model} options={modelOptions} onChange={v => { setModel(v); setDirty(true); }} />\n ) : (\n <label className=\"block\">\n <span className=\"text-[11px] text-gray-500\">模型 / Preset</span>\n <input\n type=\"text\" value={model} onChange={e => { setModel(e.target.value); setDirty(true); }}\n placeholder=\"未配置\"\n className=\"mt-0.5 w-full rounded-md border border-[#26262b] bg-[#0e0e10] px-2.5 py-1.5 text-[12px] text-gray-200 focus:border-cyan-600 focus:outline-none\"\n />\n </label>\n )}\n <div className=\"flex items-center gap-2 text-[11px] text-gray-500\">\n <span>图片能力 (imageCapable)</span>\n <span className={`px-1.5 py-0.5 rounded ${imageCapable ? 'bg-green-900/30 text-green-300' : 'bg-[#2a2a30] text-gray-400'}`}>\n {imageCapable ? '支持' : '不支持'} · 只读\n </span>\n </div>\n </div>\n </section>\n\n {/* C. 运行模式 / flags (editable) */}\n <section>\n <SectionTitle>运行模式 · flags</SectionTitle>\n <div className=\"space-y-2\">\n <Select\n label=\"权限模式 permissionMode\"\n value={flags.permissionMode}\n options={PERMISSION_MODES}\n onChange={v => setFlag('permissionMode', v)}\n />\n <Toggle\n label=\"跳过权限确认 dangerouslySkipPermissions\"\n checked={flags.dangerouslySkipPermissions}\n onChange={v => setFlag('dangerouslySkipPermissions', v)}\n />\n <Toggle\n label=\"团队模式 teammateMode\"\n checked={flags.teammateMode}\n onChange={v => setFlag('teammateMode', v)}\n />\n <div className=\"grid grid-cols-3 gap-2 pt-0.5\">\n <NumberField label=\"maxTurns\" value={flags.maxTurns} onChange={v => setFlag('maxTurns', v)} />\n <NumberField label=\"budget\" value={flags.budget} onChange={v => setFlag('budget', v)} />\n <NumberField label=\"timeout(s)\" value={flags.timeout} onChange={v => setFlag('timeout', v)} />\n </div>\n </div>\n </section>\n\n {/* Save bar + F3 apply lifecycle (optimistic → restarting → ack) */}\n <section>\n {(() => {\n const busy = phase === 'saving' || phase === 'applying';\n const btnDisabled = loading || busy || !dirty;\n const btnLabel = phase === 'saving' ? '保存中…'\n : phase === 'applying' ? '应用中…'\n : loading ? '加载中…'\n : dirty ? '保存模型 / flags'\n : '无改动';\n return (\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={btnDisabled}\n className={`flex w-full items-center justify-center gap-2 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${\n btnDisabled ? 'bg-[#1c1c1f] text-gray-600 cursor-not-allowed' : 'bg-cyan-600 text-white hover:bg-cyan-500'\n }`}\n >\n {busy && (\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden>\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-90\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" />\n </svg>\n )}\n {btnLabel}\n </button>\n );\n })()}\n\n {/* Lifecycle status strip — the \"toast\" for the apply outcome. */}\n {phase !== 'idle' && (() => {\n const map: Record<Exclude<ApplyPhase, 'idle'>, { tone: string; icon: string; text: string }> = {\n saving: { tone: 'border-[#26262b] bg-[#161618] text-gray-300', icon: '·', text: '正在提交配置…' },\n applying: { tone: 'border-cyan-800/50 bg-cyan-900/15 text-cyan-300', icon: '↻', text: '已下发,正在应用(节点重启生效中)…' },\n applied: { tone: 'border-green-800/50 bg-green-900/15 text-green-300', icon: '✓', text: phaseMsg || '配置已生效' },\n rejected: { tone: 'border-red-800/50 bg-red-900/15 text-red-300', icon: '✗', text: phaseMsg || '应用被拒绝' },\n timeout: { tone: 'border-amber-700/50 bg-amber-900/15 text-amber-300', icon: '⚠', text: phaseMsg || '应用超时未确认' },\n error: { tone: 'border-red-800/50 bg-red-900/15 text-red-300', icon: '✗', text: phaseMsg || '保存失败' },\n };\n const v = map[phase];\n return (\n <div role=\"status\" aria-live=\"polite\" className={`mt-2 flex items-start gap-2 rounded-lg border px-3 py-2 text-[12px] ${v.tone}`}>\n <span className={`shrink-0 leading-5 ${phase === 'applying' ? 'inline-block animate-spin' : ''}`} aria-hidden>{v.icon}</span>\n <span>{v.text}</span>\n </div>\n );\n })()}\n {phaseMock && (phase === 'applying' || phase === 'applied') && (\n <p className=\"mt-1 text-[10px] text-gray-600\">后端工具未上线,应用流程为模拟演示(不会真正下发到节点)</p>\n )}\n </section>\n\n {/* A. 接入 Channel (P2 — display only) */}\n <section>\n <SectionTitle>接入 Channel</SectionTitle>\n <div className=\"space-y-2\">\n {CHANNELS.map(ch => (\n <div key={ch.key} className={`rounded-lg border border-[#26262b] bg-[#161618] ${ch.roadmap ? 'opacity-50' : ''}`}>\n <label className=\"flex items-center justify-between px-3 py-2 text-sm text-gray-300 cursor-not-allowed\">\n <span className=\"flex items-center gap-2\">\n {ch.label}\n {ch.roadmap && <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-[#2a2a30] text-gray-400\">即将支持</span>}\n </span>\n <input type=\"checkbox\" checked={!ch.roadmap && active.has(ch.key)} disabled readOnly className=\"h-4 w-4 accent-cyan-500 cursor-not-allowed\" />\n </label>\n {!ch.roadmap && active.has(ch.key) && ch.fields.length > 0 && (\n <div className=\"px-3 pb-3 pt-1 space-y-2 border-t border-[#1c1c1f]\">\n {ch.fields.map(f => <StubField key={f.label} label={f.label} value={f.value} />)}\n </div>\n )}\n </div>\n ))}\n </div>\n <p className=\"mt-2 text-[11px] text-gray-600\">Channel 绑定为下一阶段(P2)· 当前仅展示,暂未接后端</p>\n </section>\n\n {/* D. 节点操作 (M1 lifecycle — restart wired to RFC-024 restart_node;\n rename/stop/delete gated on backend, shown as 即将支持). */}\n <section>\n <SectionTitle>节点操作</SectionTitle>\n <button\n type=\"button\"\n onClick={handleRestart}\n disabled={lcBusy}\n className={`flex w-full items-center justify-center gap-2 rounded-lg border px-3 py-2.5 text-[13px] font-medium transition-colors ${\n lcBusy\n ? 'border-[#26262b] bg-[#1c1c1f] text-gray-600 cursor-not-allowed'\n : 'border-amber-700/50 bg-amber-900/15 text-amber-300 hover:bg-amber-900/25'\n }`}\n >\n {lcBusy && (\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden>\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-90\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" />\n </svg>\n )}\n 重启节点\n </button>\n {lcResult && (() => {\n const tone = lcResult.tone === 'ok' ? 'border-green-800/50 bg-green-900/15 text-green-300'\n : lcResult.tone === 'warn' ? 'border-amber-700/50 bg-amber-900/15 text-amber-300'\n : lcResult.tone === 'err' ? 'border-red-800/50 bg-red-900/15 text-red-300'\n : 'border-[#26262b] bg-[#161618] text-gray-300';\n return (\n <div role=\"status\" aria-live=\"polite\" className={`mt-2 rounded-lg border px-3 py-2 text-[12px] ${tone}`}>\n {lcResult.text}\n </div>\n );\n })()}\n {/* Gated actions — backend not yet available (RFC-024 rename / RFC-027 stop+delete). */}\n <div className=\"mt-2 grid grid-cols-3 gap-2\">\n {[{ label: '重命名' }, { label: '停止' }, { label: '删除', danger: true }].map(op => (\n <div\n key={op.label}\n aria-disabled=\"true\"\n title=\"后端待上线\"\n className={`flex flex-col items-center justify-center gap-1 rounded-lg border px-2 py-2 text-[12px] cursor-not-allowed ${\n op.danger ? 'border-red-900/30 bg-red-900/[0.06] text-red-300/55' : 'border-[#26262b] bg-[#161618] text-gray-500'\n }`}\n >\n {op.label}\n <span className=\"text-[9px] px-1 py-0.5 rounded bg-[#2a2a30] text-gray-400\">即将支持</span>\n </div>\n ))}\n </div>\n <p className=\"mt-2 text-[11px] text-gray-600\">重启已接入后端;重命名 / 停止 / 删除待后端(RFC-024 改名 · RFC-027 停止/删除)上线。</p>\n </section>\n </div>\n </div>\n\n <style jsx global>{`\n @keyframes node-settings-slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n }\n .animate-slide-in { animation: node-settings-slide-in 0.2s ease-out; }\n `}</style>\n </>\n );\n}\n","'use client';\n\n/**\n * Loading skeleton — mirrors the actual Overview layout structure so the\n * page doesn't appear to \"shift\" once data arrives. Uses the same\n * `anet-skeleton-pulse` rhythm as the brand pulse (1.6s opacity drift,\n * no scale, no blur, no glow) so the loading animation feels native to\n * the rest of the dashboard rather than a generic spinner.\n *\n * Bars are theme-aware: anet-skeleton-bar (existing CSS shim resolves this to\n * var(--bg-elevated) on light/mint), so light theme shows soft grey blocks\n * on white cards and dark theme shows lighter blocks on navy cards.\n */\nexport function LoadingSkeleton() {\n return (\n <div className=\"min-h-screen bg-[#0b0b0d] text-gray-100 p-4 sm:p-6\">\n {/* KPI top strip — 4 cards matching StatsBar.\n #209 R38: mb-8 → mb-4 sm:mb-8 to track the live StatsBar\n wrapper after R29 mobile-tighten. Without this the skeleton\n and the loaded page jump by 16 px on mobile when data arrives. */}\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4 sm:mb-8 anet-skeleton-pulse\">\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"rounded-xl border border-[#26262b] bg-[#161618] px-4 py-3\">\n <Bar w=\"2.5rem\" h=\"1.75rem\" />\n <Bar w=\"3.5rem\" h=\"0.75rem\" className=\"mt-2\" />\n <Bar w=\"5rem\" h=\"0.625rem\" className=\"mt-1\" />\n </div>\n ))}\n </div>\n\n {/* Dispatch + UserBar row.\n #209 R38: mb-3 → mb-4 — live page uses mb-4 here, skeleton\n was 4 px tighter and triggered a small jump on load. */}\n <div className=\"flex items-center gap-3 mb-4 anet-skeleton-pulse\">\n <Bar w=\"6rem\" h=\"2.5rem\" rounded=\"0.75rem\" />\n <div className=\"flex-1 rounded-lg border border-[#26262b] bg-[#161618] px-4 py-2.5 flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full anet-skeleton-bar\" />\n <div className=\"flex-1\">\n <Bar w=\"6rem\" h=\"0.875rem\" />\n <Bar w=\"10rem\" h=\"0.625rem\" className=\"mt-1\" />\n </div>\n </div>\n </div>\n\n {/* Config bar.\n #209 R38: mb-6 → mb-4 sm:mb-6 to track the R28 mobile tighten. */}\n <div className=\"mb-4 sm:mb-6 rounded-lg border border-[#26262b] bg-[#161618] px-4 py-3 anet-skeleton-pulse\">\n <Bar w=\"14rem\" h=\"0.875rem\" />\n </div>\n\n {/* Stat strip 3 cards */}\n <div className=\"grid grid-cols-3 gap-2 sm:gap-3 mb-3 anet-skeleton-pulse\">\n {[1, 2, 3].map(i => (\n <div key={i} className=\"rounded-xl border border-[#26262b] bg-[#161618] px-3 py-3\">\n <Bar w=\"2rem\" h=\"1.25rem\" />\n <Bar w=\"2.5rem\" h=\"0.75rem\" className=\"mt-1\" />\n <Bar w=\"3.5rem\" h=\"0.625rem\" className=\"mt-px\" />\n </div>\n ))}\n </div>\n\n {/* Nav rail 3 cards.\n #209 R38: mb-6 → mb-4 sm:mb-6 to track the R28 mobile tighten. */}\n <div className=\"grid grid-cols-3 gap-2 sm:gap-3 mb-4 sm:mb-6 anet-skeleton-pulse\">\n {[1, 2, 3].map(i => (\n <div key={i} className=\"rounded-xl border border-[#26262b] bg-[#161618] px-3 py-2.5 flex items-center justify-center gap-2\">\n <div className=\"w-4 h-4 rounded anet-skeleton-bar\" />\n <Bar w=\"4rem\" h=\"0.75rem\" />\n </div>\n ))}\n </div>\n\n {/* #209 R38: Broadcast bar skeleton dropped — the live page\n removed BroadcastBar in r70's \"demote zero-data noise\" pass\n (it lives behind /admin now) so the skeleton was a phantom\n row that did not exist in the loaded page. Caused a 56 px\n downward shift on data arrival. */}\n\n {/* Agent card grid.\n #209 R38: breakpoints synced with the live AgentCard grid\n (R48 set lg:grid-cols-3 — skeleton was still on lg:grid-cols-2\n which made cards rearrange under hydration on 1024-1279 px). */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4 anet-skeleton-pulse\">\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"rounded-xl border border-[#26262b] bg-[#161618] p-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <div className=\"w-2 h-2 rounded-full anet-skeleton-bar\" />\n <Bar w=\"6rem\" h=\"0.875rem\" />\n </div>\n <div className=\"space-y-2\">\n <Bar w=\"100%\" h=\"0.625rem\" />\n <Bar w=\"75%\" h=\"0.625rem\" />\n <Bar w=\"55%\" h=\"0.625rem\" />\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/** Single shimmer bar — uses `anet-skeleton-bar` so theme-specific bar\n * color (dark navy on dark themes, mid-grey on light) overrides the\n * default. */\nfunction Bar({ w, h, rounded, className }: { w: string; h: string; rounded?: string; className?: string }) {\n return (\n <div\n className={`anet-skeleton-bar ${className || ''}`}\n style={{ width: w, height: h, borderRadius: rounded || '0.375rem' }}\n />\n );\n}\n\n// EmptyState export removed in 0.4.5 — replaced by EmptyState.tsx with\n// per-variant glyphs and a NodesEmptyState wrapper for the Overview\n// hint-aware behavior. See app/components/EmptyState.tsx.\n","'use client';\n\nimport { useState } from 'react';\nimport { TaskChatPanel } from './TaskChatPanel';\n\ninterface CommandCenterProps {\n /** Currently open chat tabs */\n tabs: string[];\n activeTab: string;\n onOpenTab: (alias: string) => void;\n onCloseTab: (alias: string) => void;\n onSetActive: (alias: string) => void;\n onClose: () => void;\n}\n\n/**\n * Multi-tab chat panel for commanding multiple agents simultaneously.\n * Wraps TaskChatPanel with a tab bar for switching between agents.\n */\nexport function CommandCenter({ tabs, activeTab, onOpenTab, onCloseTab, onSetActive, onClose }: CommandCenterProps) {\n if (tabs.length === 0) return null;\n\n return (\n <>\n {/* Backdrop */}\n <div className=\"fixed inset-0 bg-black/30 z-40 lg:hidden\" onClick={onClose} />\n\n {/* Panel */}\n <div className=\"fixed top-0 right-0 h-[100dvh] w-full lg:w-[500px] bg-[#0b0b0d] border-l border-[#26262b] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in\">\n {/* Tab bar */}\n <div className=\"flex items-center border-b border-[#26262b] bg-[#111113] overflow-x-auto\">\n <div className=\"flex-1 flex min-w-0\">\n {tabs.map(alias => (\n <button\n key={alias}\n onClick={() => onSetActive(alias)}\n className={`flex items-center gap-1.5 px-3 py-2.5 text-xs border-b-2 transition-colors shrink-0 ${\n activeTab === alias\n ? 'border-cyan-400 text-cyan-300 bg-cyan-500/5'\n : 'border-transparent text-gray-500 hover:text-gray-300'\n }`}\n >\n <div className={`w-2 h-2 rounded-full ${activeTab === alias ? 'bg-cyan-400' : 'bg-gray-600'}`} />\n <span className=\"max-w-[80px] truncate\">{alias}</span>\n {/* R21 of #190 mobile a11y: nested close had no aria-label\n (screen-readers spoke just \"button\") and an ~8 px\n tap target (p-0.5 + text \"×\"). Both fixed; nested\n <button> inside <button> remains invalid HTML but\n is browser-tolerated and refactoring to role=\"tab\"\n is out of scope for this round. */}\n <button\n onClick={e => { e.stopPropagation(); onCloseTab(alias); }}\n aria-label={`Close ${alias} chat tab`}\n className=\"ml-1 inline-flex h-7 w-7 items-center justify-center text-gray-600 hover:text-gray-300 rounded-md hover:bg-white/5\"\n >\n ×\n </button>\n </button>\n ))}\n </div>\n {/* R21 mobile a11y: outer command-center close was SVG-only\n and screen-reader silent; also bumped to 44 x 44 hit zone\n from 32 x ~36. */}\n <button onClick={onClose} aria-label=\"Close command center\" className=\"inline-flex min-h-[44px] min-w-[44px] items-center justify-center text-gray-500 hover:text-white shrink-0 border-l border-[#26262b]\">\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Active chat - render all but only show active (preserves state) */}\n <div className=\"flex-1 relative overflow-hidden\">\n {tabs.map(alias => (\n <div key={alias} className={`absolute inset-0 ${activeTab === alias ? 'block' : 'hidden'}`}>\n <InlineChat alias={alias} />\n </div>\n ))}\n </div>\n </div>\n\n <style jsx global>{`\n @keyframes slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n }\n .animate-slide-in {\n animation: slide-in 0.2s ease-out;\n }\n `}</style>\n </>\n );\n}\n\n/** Inline chat without the panel chrome (used inside CommandCenter tabs) */\nfunction InlineChat({ alias }: { alias: string }) {\n // Reuse TaskChatPanel's logic but render without the outer frame\n return <TaskChatPanel alias={alias} onClose={() => {}} inline />;\n}\n\n/**\n * Hook to manage multi-tab command center state.\n */\nexport function useCommandCenter() {\n const [tabs, setTabs] = useState<string[]>([]);\n const [activeTab, setActiveTab] = useState('');\n\n const openTab = (alias: string) => {\n setTabs(prev => prev.includes(alias) ? prev : [...prev, alias]);\n setActiveTab(alias);\n };\n\n const closeTab = (alias: string) => {\n setTabs(prev => {\n const next = prev.filter(t => t !== alias);\n if (activeTab === alias) setActiveTab(next[next.length - 1] || '');\n return next;\n });\n };\n\n const closeAll = () => {\n setTabs([]);\n setActiveTab('');\n };\n\n return { tabs, activeTab, openTab, closeTab, closeAll, setActiveTab };\n}\n"],"names":["React","_interopDefaultLegacy","e","React__default","_defineProperties","target","props","i","length","descriptor","enumerable","configurable","writable","Object","defineProperty","key","_createClass","Constructor","protoProps","staticProps","prototype","isProd","process","env","isString","o","toString","call","StyleSheet","param","ref","_name","name","_optimizeForSpeed","optimizeForSpeed","invariant$1","_deletedRulePlaceholder","_serverSheet","undefined","_tags","_injected","_rulesCount","node","document","querySelector","_nonce","_proto","setOptimizeForSpeed","bool","flush","inject","isOptimizeForSpeed","_this","cssRules","insertRule","rule","index","cssText","push","deleteRule","getSheetForTag","tag","sheet","styleSheets","ownerNode","getSheet","insertionPoint","replaceRule","trim","error","console","warn","makeStyleTag","cssString","relativeToTag","createElement","setAttribute","type","appendChild","createTextNode","head","getElementsByTagName","insertBefore","get","condition","message","Error","hash","str","_$hash","charCodeAt","stringHash","sanitize","replace","cache","computeId","baseId","propsToString","String","computeSelector","id","css","selectoPlaceholderRegexp","idcss","mapRulesToStyle","options","map","args","nonce","dangerouslySetInnerHTML","__html","StyleSheetRegistry","_styleSheet","styleSheet","_sheet","_fromServer","_indices","_instancesCounts","add","Array","isArray","children","getIdAndRules","styleId","rules","indices","filter","remove","invariant","tagFromServer","parentNode","removeChild","forEach","update","nextProps","fromServer","keys","concat","join","Boolean","styles","dynamic","selectFromServer","elements","slice","querySelectorAll","reduce","acc","element","StyleSheetContext","createContext","displayName","createStyleRegistry","StyleRegistry","configuredRegistry","registry","rootRegistry","useContext","useState","Provider","value","useStyleRegistry","useInsertionEffect","useLayoutEffect","defaultRegistry","JSXStyle","info","tagInfo","exports","style","module","StatsBar","online","working","total","onlinePercent","Math","round","fleetEmpty","className","StatCard","label","sub","color"],"mappings":"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","ignoreList":[0,1]}
|