@sleep2agi/agent-network-dashboard 0.4.6-preview.3 → 0.4.6-preview.31

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.
Files changed (823) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build/chunks/{node_modules_13sb.px._.js → 00jm_02mlcew._.js} +167 -167
  4. package/.next/build/chunks/00jm_02mlcew._.js.map +47 -0
  5. package/.next/build/chunks/{[root-of-the-server]__0ubbtyl._.js → [root-of-the-server]__0tgc-8p._.js} +3 -3
  6. package/.next/build/chunks/{[root-of-the-server]__0d-m0h0._.js → [root-of-the-server]__13z64y.._.js} +7 -7
  7. package/.next/build/chunks/{[root-of-the-server]__0d-m0h0._.js.map → [root-of-the-server]__13z64y.._.js.map} +2 -2
  8. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_0r4x5ys._.js +13 -0
  9. package/.next/build/chunks/[turbopack]_runtime.js +1 -1
  10. package/.next/build/postcss.js +4 -4
  11. package/.next/build-manifest.json +8 -8
  12. package/.next/diagnostics/route-bundle-stats.json +157 -143
  13. package/.next/fallback-build-manifest.json +3 -3
  14. package/.next/next-minimal-server.js.nft.json +1 -1
  15. package/.next/next-server.js.nft.json +1 -1
  16. package/.next/prerender-manifest.json +3 -3
  17. package/.next/required-server-files.js +4 -4
  18. package/.next/required-server-files.json +4 -4
  19. package/.next/routes-manifest.json +6 -0
  20. package/.next/server/app/_global-error/page/build-manifest.json +5 -5
  21. package/.next/server/app/_global-error/page.js +9 -9
  22. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  23. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/_global-error.html +1 -1
  25. package/.next/server/app/_global-error.rsc +8 -8
  26. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  27. package/.next/server/app/_global-error.segments/_full.segment.rsc +8 -8
  28. package/.next/server/app/_global-error.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  30. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/.next/server/app/_not-found/page/build-manifest.json +5 -5
  32. package/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
  33. package/.next/server/app/_not-found/page.js +12 -12
  34. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  35. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/_not-found.html +2 -2
  37. package/.next/server/app/_not-found.rsc +12 -12
  38. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  39. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  40. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  41. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  42. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  43. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/admin/page/build-manifest.json +5 -5
  45. package/.next/server/app/admin/page/next-font-manifest.json +1 -1
  46. package/.next/server/app/admin/page.js +13 -13
  47. package/.next/server/app/admin/page.js.nft.json +1 -1
  48. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  49. package/.next/server/app/admin.html +2 -2
  50. package/.next/server/app/admin.rsc +14 -14
  51. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  52. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  53. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  54. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  55. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  56. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  57. package/.next/server/app/api/anet/config/route.js +6 -6
  58. package/.next/server/app/api/anet/config/route.js.nft.json +1 -1
  59. package/.next/server/app/api/auth/login/route.js +7 -7
  60. package/.next/server/app/api/auth/login/route.js.nft.json +1 -1
  61. package/.next/server/app/api/auth/logout/route.js +7 -7
  62. package/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  63. package/.next/server/app/api/auth/v3/route.js +7 -7
  64. package/.next/server/app/api/auth/v3/route.js.nft.json +1 -1
  65. package/.next/server/app/api/hub/audit-log/route.js +6 -6
  66. package/.next/server/app/api/hub/audit-log/route.js.nft.json +1 -1
  67. package/.next/server/app/api/hub/auth/route.js +7 -7
  68. package/.next/server/app/api/hub/auth/route.js.nft.json +1 -1
  69. package/.next/server/app/api/hub/broadcast/route.js +6 -6
  70. package/.next/server/app/api/hub/broadcast/route.js.nft.json +1 -1
  71. package/.next/server/app/api/hub/events/route.js +6 -6
  72. package/.next/server/app/api/hub/events/route.js.nft.json +1 -1
  73. package/.next/server/app/api/hub/health/route.js +6 -6
  74. package/.next/server/app/api/hub/health/route.js.nft.json +1 -1
  75. package/.next/server/app/api/hub/inbox/route.js +6 -6
  76. package/.next/server/app/api/hub/inbox/route.js.nft.json +1 -1
  77. package/.next/server/app/api/hub/license/route.js +6 -6
  78. package/.next/server/app/api/hub/license/route.js.nft.json +1 -1
  79. package/.next/server/app/api/hub/messages/route.js +6 -6
  80. package/.next/server/app/api/hub/messages/route.js.nft.json +1 -1
  81. package/.next/server/app/api/hub/networks/route.js +6 -6
  82. package/.next/server/app/api/hub/networks/route.js.nft.json +1 -1
  83. package/.next/server/app/api/hub/nodes/route.js +6 -6
  84. package/.next/server/app/api/hub/nodes/route.js.nft.json +1 -1
  85. package/.next/server/app/api/hub/register/route.js +6 -6
  86. package/.next/server/app/api/hub/register/route.js.nft.json +1 -1
  87. package/.next/server/app/api/hub/send/route.js +6 -6
  88. package/.next/server/app/api/hub/send/route.js.nft.json +1 -1
  89. package/.next/server/app/api/hub/server-logs/route.js +6 -6
  90. package/.next/server/app/api/hub/server-logs/route.js.nft.json +1 -1
  91. package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
  92. package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
  93. package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
  94. package/.next/server/app/api/hub/servers/route.js +7 -0
  95. package/.next/server/app/api/hub/servers/route.js.map +5 -0
  96. package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
  97. package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
  98. package/.next/server/app/api/hub/session/route.js +6 -6
  99. package/.next/server/app/api/hub/session/route.js.nft.json +1 -1
  100. package/.next/server/app/api/hub/stats/route.js +6 -6
  101. package/.next/server/app/api/hub/stats/route.js.nft.json +1 -1
  102. package/.next/server/app/api/hub/status/route.js +6 -6
  103. package/.next/server/app/api/hub/status/route.js.nft.json +1 -1
  104. package/.next/server/app/api/hub/task-events/route.js +6 -6
  105. package/.next/server/app/api/hub/task-events/route.js.nft.json +1 -1
  106. package/.next/server/app/api/hub/tasks/route.js +6 -6
  107. package/.next/server/app/api/hub/tasks/route.js.nft.json +1 -1
  108. package/.next/server/app/api/hub/tmux/route.js +6 -6
  109. package/.next/server/app/api/hub/tmux/route.js.nft.json +1 -1
  110. package/.next/server/app/api/hub/tokens/route.js +6 -6
  111. package/.next/server/app/api/hub/tokens/route.js.nft.json +1 -1
  112. package/.next/server/app/favicon.ico/route.js +6 -6
  113. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  114. package/.next/server/app/index.html +2 -2
  115. package/.next/server/app/index.rsc +14 -14
  116. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  117. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  118. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  119. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  120. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  121. package/.next/server/app/login/page/build-manifest.json +5 -5
  122. package/.next/server/app/login/page/next-font-manifest.json +1 -1
  123. package/.next/server/app/login/page.js +13 -13
  124. package/.next/server/app/login/page.js.nft.json +1 -1
  125. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  126. package/.next/server/app/login.html +2 -2
  127. package/.next/server/app/login.rsc +14 -14
  128. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  129. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  130. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  131. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  132. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  133. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  134. package/.next/server/app/logs/page/build-manifest.json +5 -5
  135. package/.next/server/app/logs/page/next-font-manifest.json +1 -1
  136. package/.next/server/app/logs/page.js +13 -13
  137. package/.next/server/app/logs/page.js.nft.json +1 -1
  138. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/logs.html +2 -2
  140. package/.next/server/app/logs.rsc +14 -14
  141. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  142. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  143. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  144. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  145. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  146. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  147. package/.next/server/app/messages/page/build-manifest.json +5 -5
  148. package/.next/server/app/messages/page/next-font-manifest.json +1 -1
  149. package/.next/server/app/messages/page.js +13 -13
  150. package/.next/server/app/messages/page.js.nft.json +1 -1
  151. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  152. package/.next/server/app/messages.html +2 -2
  153. package/.next/server/app/messages.rsc +14 -14
  154. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  155. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  156. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  157. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  158. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  159. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  160. package/.next/server/app/node/page/build-manifest.json +5 -5
  161. package/.next/server/app/node/page/next-font-manifest.json +1 -1
  162. package/.next/server/app/node/page.js +13 -13
  163. package/.next/server/app/node/page.js.nft.json +1 -1
  164. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  165. package/.next/server/app/node.html +2 -2
  166. package/.next/server/app/node.rsc +14 -14
  167. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  168. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  169. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  170. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  171. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  172. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  173. package/.next/server/app/nodes/page/build-manifest.json +5 -5
  174. package/.next/server/app/nodes/page/next-font-manifest.json +1 -1
  175. package/.next/server/app/nodes/page.js +13 -13
  176. package/.next/server/app/nodes/page.js.nft.json +1 -1
  177. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  178. package/.next/server/app/nodes.html +2 -2
  179. package/.next/server/app/nodes.rsc +14 -14
  180. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  181. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  182. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  183. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  184. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  185. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  186. package/.next/server/app/page/build-manifest.json +5 -5
  187. package/.next/server/app/page/next-font-manifest.json +1 -1
  188. package/.next/server/app/page.js +13 -13
  189. package/.next/server/app/page.js.nft.json +1 -1
  190. package/.next/server/app/page_client-reference-manifest.js +1 -1
  191. package/.next/server/app/server-logs/page/build-manifest.json +5 -5
  192. package/.next/server/app/server-logs/page/next-font-manifest.json +1 -1
  193. package/.next/server/app/server-logs/page.js +13 -13
  194. package/.next/server/app/server-logs/page.js.nft.json +1 -1
  195. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  196. package/.next/server/app/server-logs.html +2 -2
  197. package/.next/server/app/server-logs.rsc +14 -14
  198. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  199. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  200. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  201. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  202. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  203. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  204. package/.next/server/app/settings/networks/page/build-manifest.json +5 -5
  205. package/.next/server/app/settings/networks/page/next-font-manifest.json +1 -1
  206. package/.next/server/app/settings/networks/page.js +13 -13
  207. package/.next/server/app/settings/networks/page.js.nft.json +1 -1
  208. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  209. package/.next/server/app/settings/networks.html +2 -2
  210. package/.next/server/app/settings/networks.rsc +14 -14
  211. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  212. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  213. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  214. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  215. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  216. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  217. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  218. package/.next/server/app/settings/page/build-manifest.json +5 -5
  219. package/.next/server/app/settings/page/next-font-manifest.json +1 -1
  220. package/.next/server/app/settings/page.js +13 -13
  221. package/.next/server/app/settings/page.js.nft.json +1 -1
  222. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  223. package/.next/server/app/settings/tokens/page/build-manifest.json +5 -5
  224. package/.next/server/app/settings/tokens/page/next-font-manifest.json +1 -1
  225. package/.next/server/app/settings/tokens/page.js +13 -13
  226. package/.next/server/app/settings/tokens/page.js.nft.json +1 -1
  227. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  228. package/.next/server/app/settings/tokens.html +2 -2
  229. package/.next/server/app/settings/tokens.rsc +14 -14
  230. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  231. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  232. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  233. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  234. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  235. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  236. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  237. package/.next/server/app/settings.html +2 -2
  238. package/.next/server/app/settings.rsc +14 -14
  239. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  240. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  241. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  242. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  243. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  244. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  245. package/.next/server/app/tasks/[id]/page/build-manifest.json +5 -5
  246. package/.next/server/app/tasks/[id]/page/next-font-manifest.json +1 -1
  247. package/.next/server/app/tasks/[id]/page.js +13 -13
  248. package/.next/server/app/tasks/[id]/page.js.nft.json +1 -1
  249. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  250. package/.next/server/app/tasks/page/build-manifest.json +5 -5
  251. package/.next/server/app/tasks/page/next-font-manifest.json +1 -1
  252. package/.next/server/app/tasks/page.js +13 -13
  253. package/.next/server/app/tasks/page.js.nft.json +1 -1
  254. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  255. package/.next/server/app/tasks.html +2 -2
  256. package/.next/server/app/tasks.rsc +14 -14
  257. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  258. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  259. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  260. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  261. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  262. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  263. package/.next/server/app-paths-manifest.json +1 -0
  264. package/.next/server/chunks/00jm_next_04f3gac._.js +10 -0
  265. package/.next/server/chunks/00jm_next_04f3gac._.js.map +1 -0
  266. package/.next/server/chunks/00jm_next_dist_esm_build_templates_app-route_02f430o.js +3 -0
  267. package/.next/server/chunks/00jm_next_dist_esm_build_templates_app-route_02f430o.js.map +1 -0
  268. package/.next/server/chunks/0ci9_nt-network-dashboard__next-internal_server_app_api_auth_v3_route_actions_0f5nbzr.js +3 -0
  269. package/.next/server/chunks/0ci9_nt-network-dashboard__next-internal_server_app_favicon_ico_route_actions_0t0eu9r.js +3 -0
  270. package/.next/server/chunks/0ykm__next-internal_server_app_api_anet_config_route_actions_0a~v55_.js +3 -0
  271. package/.next/server/chunks/0ykm__next-internal_server_app_api_auth_login_route_actions_0xnxfs4.js +3 -0
  272. package/.next/server/chunks/0ykm__next-internal_server_app_api_auth_logout_route_actions_0p8p0rn.js +3 -0
  273. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_audit-log_route_actions_06uquwi.js +3 -0
  274. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_auth_route_actions_0f_y1ey.js +3 -0
  275. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_broadcast_route_actions_0ao3j43.js +3 -0
  276. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_events_route_actions_0_5ndld.js +3 -0
  277. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_health_route_actions_0mpzl74.js +3 -0
  278. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_inbox_route_actions_0stoyto.js +3 -0
  279. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js +3 -0
  280. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_messages_route_actions_059rw1o.js +3 -0
  281. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_networks_route_actions_03sehpn.js +3 -0
  282. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_nodes_route_actions_11fbp28.js +3 -0
  283. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_register_route_actions_0l.d7~n.js +3 -0
  284. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_send_route_actions_0.p82vy.js +3 -0
  285. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_server-logs_route_actions_05~c5lb.js +3 -0
  286. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
  287. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_session_route_actions_0b9ef2i.js +3 -0
  288. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_stats_route_actions_03u65u0.js +3 -0
  289. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_status_route_actions_0_71ear.js +3 -0
  290. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_task-events_route_actions_07md_ee.js +3 -0
  291. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_tasks_route_actions_0er3uvk.js +3 -0
  292. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_tmux_route_actions_0o_1-8u.js +3 -0
  293. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_tokens_route_actions_09doluz.js +3 -0
  294. package/.next/server/chunks/[root-of-the-server]__0-2le8-._.js +3 -0
  295. package/.next/server/chunks/[root-of-the-server]__0-2le8-._.js.map +1 -0
  296. package/.next/server/chunks/[root-of-the-server]__0-g~15e._.js +3 -0
  297. package/.next/server/chunks/[root-of-the-server]__0-g~15e._.js.map +1 -0
  298. package/.next/server/chunks/[root-of-the-server]__00194.t._.js +3 -0
  299. package/.next/server/chunks/{[root-of-the-server]__03ueci3._.js.map → [root-of-the-server]__00194.t._.js.map} +1 -1
  300. package/.next/server/chunks/[root-of-the-server]__02p2n3n._.js +3 -0
  301. package/.next/server/chunks/[root-of-the-server]__02p2n3n._.js.map +1 -0
  302. package/.next/server/chunks/[root-of-the-server]__02y-i-p._.js +6 -0
  303. package/.next/server/chunks/[root-of-the-server]__02y-i-p._.js.map +1 -0
  304. package/.next/server/chunks/[root-of-the-server]__043tnx2._.js +3 -0
  305. package/.next/server/chunks/{[root-of-the-server]__0o4qfn9._.js.map → [root-of-the-server]__043tnx2._.js.map} +1 -1
  306. package/.next/server/chunks/[root-of-the-server]__04z62zk._.js +3 -0
  307. package/.next/server/chunks/[root-of-the-server]__04z62zk._.js.map +1 -0
  308. package/.next/server/chunks/[root-of-the-server]__06p6kkp._.js +3 -0
  309. package/.next/server/chunks/[root-of-the-server]__06p6kkp._.js.map +1 -0
  310. package/.next/server/chunks/[root-of-the-server]__07ztet2._.js +3 -0
  311. package/.next/server/chunks/[root-of-the-server]__07ztet2._.js.map +1 -0
  312. package/.next/server/chunks/[root-of-the-server]__08uyvdq._.js +20 -0
  313. package/.next/server/chunks/[root-of-the-server]__08uyvdq._.js.map +1 -0
  314. package/.next/server/chunks/[root-of-the-server]__0b7v51y._.js +3 -0
  315. package/.next/server/chunks/[root-of-the-server]__0b7v51y._.js.map +1 -0
  316. package/.next/server/chunks/[root-of-the-server]__0e3ucl1._.js +3 -0
  317. package/.next/server/chunks/{[root-of-the-server]__0rg9ifx._.js.map → [root-of-the-server]__0e3ucl1._.js.map} +1 -1
  318. package/.next/server/chunks/[root-of-the-server]__0e6yail._.js +3 -0
  319. package/.next/server/chunks/[root-of-the-server]__0e6yail._.js.map +1 -0
  320. package/.next/server/chunks/[root-of-the-server]__0gx2_6a._.js +3 -0
  321. package/.next/server/chunks/[root-of-the-server]__0gx2_6a._.js.map +1 -0
  322. package/.next/server/chunks/[root-of-the-server]__0hcnyp.._.js +3 -0
  323. package/.next/server/chunks/{[root-of-the-server]__0o29v8c._.js.map → [root-of-the-server]__0hcnyp.._.js.map} +1 -1
  324. package/.next/server/chunks/[root-of-the-server]__0hg3_8r._.js +3 -0
  325. package/.next/server/chunks/[root-of-the-server]__0hg3_8r._.js.map +1 -0
  326. package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js +3 -0
  327. package/.next/server/chunks/[root-of-the-server]__0mxw4vb._.js.map +1 -0
  328. package/.next/server/chunks/[root-of-the-server]__0mzopco._.js +3 -0
  329. package/.next/server/chunks/[root-of-the-server]__0mzopco._.js.map +1 -0
  330. package/.next/server/chunks/[root-of-the-server]__0o45a4q._.js +3 -0
  331. package/.next/server/chunks/{[root-of-the-server]__0p6tutt._.js.map → [root-of-the-server]__0o45a4q._.js.map} +1 -1
  332. package/.next/server/chunks/[root-of-the-server]__0q-teo6._.js +3 -0
  333. package/.next/server/chunks/[root-of-the-server]__0q-teo6._.js.map +1 -0
  334. package/.next/server/chunks/[root-of-the-server]__0qcxxzf._.js +3 -0
  335. package/.next/server/chunks/[root-of-the-server]__0qcxxzf._.js.map +1 -0
  336. package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js +3 -0
  337. package/.next/server/chunks/[root-of-the-server]__0rovr5-._.js.map +1 -0
  338. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
  339. package/.next/server/chunks/{[root-of-the-server]__0~h2uqi._.js.map → [root-of-the-server]__0tx8s8i._.js.map} +1 -1
  340. package/.next/server/chunks/[root-of-the-server]__0xtp-et._.js +3 -0
  341. package/.next/server/chunks/[root-of-the-server]__0xtp-et._.js.map +1 -0
  342. package/.next/server/chunks/[root-of-the-server]__10pvabe._.js +3 -0
  343. package/.next/server/chunks/[root-of-the-server]__10pvabe._.js.map +1 -0
  344. package/.next/server/chunks/[root-of-the-server]__10t11zq._.js +3 -0
  345. package/.next/server/chunks/[root-of-the-server]__10t11zq._.js.map +1 -0
  346. package/.next/server/chunks/[root-of-the-server]__12r~omy._.js +3 -0
  347. package/.next/server/chunks/[root-of-the-server]__12r~omy._.js.map +1 -0
  348. package/.next/server/chunks/[root-of-the-server]__1389~cs._.js +3 -0
  349. package/.next/server/chunks/[root-of-the-server]__1389~cs._.js.map +1 -0
  350. package/.next/server/chunks/[root-of-the-server]__13n8t..._.js +3 -0
  351. package/.next/server/chunks/[root-of-the-server]__13n8t..._.js.map +1 -0
  352. package/.next/server/chunks/[turbopack]_runtime.js +1 -1
  353. package/.next/server/chunks/ssr/{node_modules_0r_.s40._.js → 00jm_03rn9vn._.js} +2 -2
  354. package/.next/server/chunks/ssr/00jm_03rn9vn._.js.map +1 -0
  355. package/.next/server/chunks/ssr/{node_modules_09w7yel._.js → 00jm_059ns68._.js} +3 -3
  356. package/.next/server/chunks/ssr/00jm_059ns68._.js.map +1 -0
  357. package/.next/server/chunks/ssr/00jm_09ae-q0._.js +3 -0
  358. package/.next/server/chunks/ssr/00jm_09ae-q0._.js.map +1 -0
  359. package/.next/server/chunks/ssr/00jm_0j7__z.._.js +3 -0
  360. package/.next/server/chunks/ssr/00jm_0j7__z.._.js.map +1 -0
  361. package/.next/server/chunks/ssr/00jm_next_01-0urn._.js +6 -0
  362. package/.next/server/chunks/ssr/00jm_next_01-0urn._.js.map +1 -0
  363. package/.next/server/chunks/ssr/00jm_next_dist_05rlh5d._.js +6 -0
  364. package/.next/server/chunks/ssr/00jm_next_dist_05rlh5d._.js.map +1 -0
  365. package/.next/server/chunks/ssr/00jm_next_dist_client_components_0gkudcq._.js +3 -0
  366. package/.next/server/chunks/ssr/00jm_next_dist_client_components_0gkudcq._.js.map +1 -0
  367. package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_forbidden_0ecus3e.js +3 -0
  368. package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_forbidden_0ecus3e.js.map +1 -0
  369. package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_global-error_07n8pg2.js +3 -0
  370. package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_global-error_07n8pg2.js.map +1 -0
  371. package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_unauthorized_0r0uq4j.js +3 -0
  372. package/.next/server/chunks/ssr/00jm_next_dist_client_components_builtin_unauthorized_0r0uq4j.js.map +1 -0
  373. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_002l7yi.js → 00jm_next_dist_esm_build_templates_app-page_0..ddwm.js} +3 -3
  374. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0..ddwm.js.map +1 -0
  375. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0qm1-a1.js → 00jm_next_dist_esm_build_templates_app-page_02bxi6j.js} +3 -3
  376. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_02bxi6j.js.map +1 -0
  377. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_02suzhc.js → 00jm_next_dist_esm_build_templates_app-page_05oassd.js} +3 -3
  378. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05oassd.js.map +1 -0
  379. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0dm~kj8.js → 00jm_next_dist_esm_build_templates_app-page_05vxtby.js} +3 -3
  380. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05vxtby.js.map +1 -0
  381. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0uccf9z.js → 00jm_next_dist_esm_build_templates_app-page_05~eml4.js} +3 -3
  382. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_05~eml4.js.map +1 -0
  383. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0l-fh0i.js → 00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js} +3 -3
  384. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_-g_q2.js.map +1 -0
  385. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0uv6jyc.js → 00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js} +3 -3
  386. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0_ec0xu.js.map +1 -0
  387. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js +4 -0
  388. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0czb6wz.js.map +1 -0
  389. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_13s07li.js → 00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js} +3 -3
  390. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0e-cn.p.js.map +1 -0
  391. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0o9vt1n.js → 00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js} +3 -3
  392. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0k8_sr8.js.map +1 -0
  393. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0nsg22r.js → 00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js} +3 -3
  394. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0lt_3bw.js.map +1 -0
  395. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0d2yq5t.js → 00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js} +3 -3
  396. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0uyh3yt.js.map +1 -0
  397. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0foejvu.js → 00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js} +3 -3
  398. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_0v-a2ps.js.map +1 -0
  399. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js +4 -0
  400. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_12.4r9~.js.map +1 -0
  401. package/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_07uu-xq.js → 00jm_next_dist_esm_build_templates_app-page_13mqkay.js} +3 -3
  402. package/.next/server/chunks/ssr/00jm_next_dist_esm_build_templates_app-page_13mqkay.js.map +1 -0
  403. package/.next/server/chunks/ssr/0dwh_ent-network-dashboard__next-internal_server_app_server-logs_page_actions_0hkk815.js +3 -0
  404. package/.next/server/chunks/ssr/0yas_gent-network-dashboard__next-internal_server_app__not-found_page_actions_0u.f-rz.js +3 -0
  405. package/.next/server/chunks/ssr/0yas_gent-network-dashboard__next-internal_server_app_tasks_[id]_page_actions_0r3~lho.js +3 -0
  406. package/.next/server/chunks/ssr/0ykm__next-internal_server_app__global-error_page_actions_100fxls.js +3 -0
  407. package/.next/server/chunks/ssr/0ykm__next-internal_server_app_settings_networks_page_actions_0jgy57m.js +3 -0
  408. package/.next/server/chunks/ssr/0ykm__next-internal_server_app_settings_tokens_page_actions_0l5h.k~.js +3 -0
  409. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js +3 -0
  410. package/.next/server/chunks/ssr/[root-of-the-server]__030vg4n._.js.map +1 -0
  411. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +3 -0
  412. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -0
  413. package/.next/server/chunks/ssr/[root-of-the-server]__063~dul._.js +3 -0
  414. package/.next/server/chunks/ssr/[root-of-the-server]__063~dul._.js.map +1 -0
  415. package/.next/server/chunks/ssr/[root-of-the-server]__07io.d8._.js +3 -0
  416. package/.next/server/chunks/ssr/[root-of-the-server]__07io.d8._.js.map +1 -0
  417. package/.next/server/chunks/ssr/[root-of-the-server]__08d6lp4._.js +3 -0
  418. package/.next/server/chunks/ssr/[root-of-the-server]__08d6lp4._.js.map +1 -0
  419. package/.next/server/chunks/ssr/[root-of-the-server]__09f1wn6._.js +3 -0
  420. package/.next/server/chunks/ssr/[root-of-the-server]__09f1wn6._.js.map +1 -0
  421. package/.next/server/chunks/ssr/[root-of-the-server]__0c4xtkg._.js +3 -0
  422. package/.next/server/chunks/ssr/[root-of-the-server]__0c4xtkg._.js.map +1 -0
  423. package/.next/server/chunks/ssr/[root-of-the-server]__0erfzjn._.js +3 -0
  424. package/.next/server/chunks/ssr/[root-of-the-server]__0erfzjn._.js.map +1 -0
  425. package/.next/server/chunks/ssr/[root-of-the-server]__0ezjwje._.js +3 -0
  426. package/.next/server/chunks/ssr/[root-of-the-server]__0ezjwje._.js.map +1 -0
  427. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +3 -0
  428. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -0
  429. package/.next/server/chunks/ssr/{[root-of-the-server]__08qr2ji._.js → [root-of-the-server]__0gc.4uk._.js} +3 -3
  430. package/.next/server/chunks/ssr/[root-of-the-server]__0gc.4uk._.js.map +1 -0
  431. package/.next/server/chunks/ssr/[root-of-the-server]__0h_p~-4._.js +19 -0
  432. package/.next/server/chunks/ssr/[root-of-the-server]__0h_p~-4._.js.map +1 -0
  433. package/.next/server/chunks/ssr/[root-of-the-server]__0jdr1e5._.js +3 -0
  434. package/.next/server/chunks/ssr/[root-of-the-server]__0jdr1e5._.js.map +1 -0
  435. package/.next/server/chunks/ssr/[root-of-the-server]__0lw16mu._.js +3 -0
  436. package/.next/server/chunks/ssr/[root-of-the-server]__0lw16mu._.js.map +1 -0
  437. package/.next/server/chunks/ssr/[root-of-the-server]__0mjly~-._.js +33 -0
  438. package/.next/server/chunks/ssr/[root-of-the-server]__0mjly~-._.js.map +1 -0
  439. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +3 -0
  440. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -0
  441. package/.next/server/chunks/ssr/[root-of-the-server]__0p-95r_._.js +3 -0
  442. package/.next/server/chunks/ssr/[root-of-the-server]__0p-95r_._.js.map +1 -0
  443. package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js +8 -0
  444. package/.next/server/chunks/ssr/[root-of-the-server]__0u5aqkk._.js.map +1 -0
  445. package/.next/server/chunks/ssr/[root-of-the-server]__0ue5.fy._.js +3 -0
  446. package/.next/server/chunks/ssr/[root-of-the-server]__0ue5.fy._.js.map +1 -0
  447. package/.next/server/chunks/ssr/[root-of-the-server]__0uggxvx._.js +3 -0
  448. package/.next/server/chunks/ssr/[root-of-the-server]__0uggxvx._.js.map +1 -0
  449. package/.next/server/chunks/ssr/[root-of-the-server]__0zjk5ro._.js +3 -0
  450. package/.next/server/chunks/ssr/[root-of-the-server]__0zjk5ro._.js.map +1 -0
  451. package/.next/server/chunks/ssr/[root-of-the-server]__0zz3v7a._.js +3 -0
  452. package/.next/server/chunks/ssr/[root-of-the-server]__0zz3v7a._.js.map +1 -0
  453. package/.next/server/chunks/ssr/[root-of-the-server]__0~y12ef._.js +3 -0
  454. package/.next/server/chunks/ssr/[root-of-the-server]__0~y12ef._.js.map +1 -0
  455. package/.next/server/chunks/ssr/[root-of-the-server]__129sc_9._.js +3 -0
  456. package/.next/server/chunks/ssr/[root-of-the-server]__129sc_9._.js.map +1 -0
  457. package/.next/server/chunks/ssr/[turbopack]_runtime.js +1 -1
  458. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -0
  459. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -0
  460. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_admin_page_actions_0i~.x~j.js +3 -0
  461. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_login_page_actions_0frwl15.js +3 -0
  462. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_logs_page_actions_0xa2ox_.js +3 -0
  463. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_messages_page_actions_10j3tg4.js +3 -0
  464. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_node_page_actions_0sl4ntd.js +3 -0
  465. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_nodes_page_actions_0cpgkfs.js +3 -0
  466. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_page_actions_08nuo36.js +3 -0
  467. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_settings_page_actions_0iv.lmk.js +3 -0
  468. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_tasks_page_actions_08c7qlx.js +3 -0
  469. package/.next/server/chunks/ssr/agent-network-dashboard__next-internal_server_app_tasks_page_actions_08c7qlx.js.map +1 -0
  470. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +3 -0
  471. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -0
  472. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +3 -0
  473. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -0
  474. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +3 -0
  475. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -0
  476. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +3 -0
  477. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -0
  478. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +3 -0
  479. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -0
  480. package/.next/server/chunks/ssr/{app_0izi28j._.js → agent-network-dashboard_app_0xgney8._.js} +2 -2
  481. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -0
  482. package/.next/server/chunks/ssr/{app_0_agk0r._.js → agent-network-dashboard_app_10hjgv4._.js} +2 -2
  483. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -0
  484. package/.next/server/chunks/ssr/{app_0.zbtn5._.js → agent-network-dashboard_app_1153xeb._.js} +2 -2
  485. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -0
  486. package/.next/server/chunks/ssr/{app_04n78pr._.js → agent-network-dashboard_app_12l4oto._.js} +2 -2
  487. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -0
  488. package/.next/server/chunks/ssr/{app_components_08ys5y3._.js → agent-network-dashboard_app_components_0mvyi-4._.js} +3 -3
  489. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -0
  490. package/.next/server/chunks/ssr/{app_server-logs_page_tsx_0_kpp1p._.js → agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js} +2 -2
  491. package/.next/server/chunks/ssr/agent-network-dashboard_app_server-logs_page_tsx_0dg.l_8._.js.map +1 -0
  492. package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js +3 -0
  493. package/.next/server/chunks/ssr/agent-network-dashboard_app_tasks_page_tsx_0mwxy4z._.js.map +1 -0
  494. package/.next/server/middleware-build-manifest.js +8 -8
  495. package/.next/server/middleware.js +3 -3
  496. package/.next/server/middleware.js.nft.json +1 -1
  497. package/.next/server/next-font-manifest.js +1 -1
  498. package/.next/server/next-font-manifest.json +14 -14
  499. package/.next/server/pages/404.html +2 -2
  500. package/.next/server/pages/500.html +1 -1
  501. package/.next/server/server-reference-manifest.js +1 -1
  502. package/.next/server/server-reference-manifest.json +1 -1
  503. package/.next/static/chunks/0-7iowzu4.798.js +1 -0
  504. package/.next/static/chunks/{0gtbwcilfu6b7.js → 0.66f3.rtcybb.js} +1 -1
  505. package/.next/static/chunks/01u5zo8b7r_r3.js +1 -0
  506. package/.next/static/chunks/02t5jr4shh.hy.js +1 -0
  507. package/.next/static/chunks/039-b.v_9-_rm.js +1 -0
  508. package/.next/static/chunks/05oi25hh7b81r.js +31 -0
  509. package/.next/static/chunks/07~8sx3_5uiox.js +1 -0
  510. package/.next/static/chunks/09nd.t_hd.y6t.css +2 -0
  511. package/.next/static/chunks/{0pqt~8bl3ukh4.js → 0_pxg.4pebw__.js} +2 -2
  512. package/.next/static/chunks/0_qfg5s5uzs7..js +7 -0
  513. package/.next/static/chunks/0b8wnt4put-5n.js +2 -0
  514. package/.next/static/chunks/0elalb9n_.~di.js +1 -0
  515. package/.next/static/chunks/{01xlw8hd842-c.js → 0eov~nkwaf-qa.js} +1 -1
  516. package/.next/static/chunks/0fqit.b4j_6rz.js +1 -0
  517. package/.next/static/chunks/0j_q__o8p6g83.js +1 -0
  518. package/.next/static/chunks/0jl6_w50zh4l5.js +1 -0
  519. package/.next/static/chunks/{0w1zwt8kq_7nb.js → 0ku0fjqlm9mca.js} +1 -1
  520. package/.next/static/chunks/{0-hlcuoemhe7w.js → 0pl7l_.2im.j0.js} +2 -2
  521. package/.next/static/chunks/0qcfcgdthhos6.js +1 -0
  522. package/.next/static/chunks/0r4q73.-1bkyl.js +1 -0
  523. package/.next/static/chunks/0r_c8eccg3z2o.js +1 -0
  524. package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
  525. package/.next/static/chunks/0u3b93reo9xci.js +1 -0
  526. package/.next/static/chunks/10oi7l-tycgl9.js +1 -0
  527. package/.next/static/chunks/11uk1izuv3~0t.js +1 -0
  528. package/.next/static/chunks/{turbopack-01f1xs~cq_~si.js → turbopack-0.7dvsf9rllt0.js} +1 -1
  529. package/.next/trace +2 -2
  530. package/.next/trace-build +1 -1
  531. package/.next/types/routes.d.ts +2 -1
  532. package/.next/types/validator.ts +9 -0
  533. package/app/api/hub/servers/route.ts +39 -0
  534. package/app/components/AppShell.tsx +2 -0
  535. package/app/components/ChatPopover.tsx +6 -6
  536. package/app/components/ServersDrawer.tsx +206 -0
  537. package/app/components/TaskChatPanel.tsx +39 -39
  538. package/app/components/TopoGraph.tsx +739 -163
  539. package/app/components/types.ts +7 -0
  540. package/app/lib/vendorIdentity.ts +145 -0
  541. package/app/page.tsx +18 -1
  542. package/package.json +1 -1
  543. package/scripts/chat-popover-test.mjs +2 -0
  544. package/scripts/chat-theme-test.mjs +98 -0
  545. package/scripts/servers-drawer-live-test.mjs +96 -0
  546. package/scripts/topo-arrow-tier-test.mjs +61 -0
  547. package/scripts/topo-chat-focus-ring-test.mjs +77 -0
  548. package/scripts/topo-click-ripple-test.mjs +66 -0
  549. package/scripts/topo-group-count-test.mjs +63 -0
  550. package/scripts/topo-group-hover-test.mjs +82 -0
  551. package/scripts/topo-groupbox-test.mjs +118 -0
  552. package/scripts/topo-label-scale-test.mjs +78 -0
  553. package/scripts/topo-layout-test.mjs +1 -0
  554. package/scripts/topo-offline-dim-test.mjs +70 -0
  555. package/scripts/topo-overlap-test.mjs +137 -0
  556. package/scripts/topo-p0round-test.mjs +166 -0
  557. package/scripts/topo-pan-cursor-test.mjs +62 -0
  558. package/scripts/topo-rename-test.mjs +107 -0
  559. package/scripts/topo-vendor-test.mjs +97 -0
  560. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  561. package/scripts/topo-zoom-keys-test.mjs +89 -0
  562. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_06e.r3r._.js +0 -13
  563. package/.next/build/chunks/node_modules_13sb.px._.js.map +0 -47
  564. package/.next/server/chunks/[root-of-the-server]__0-mykaf._.js +0 -3
  565. package/.next/server/chunks/[root-of-the-server]__0-mykaf._.js.map +0 -1
  566. package/.next/server/chunks/[root-of-the-server]__0.d_kqy._.js +0 -3
  567. package/.next/server/chunks/[root-of-the-server]__0.d_kqy._.js.map +0 -1
  568. package/.next/server/chunks/[root-of-the-server]__02pocyg._.js +0 -3
  569. package/.next/server/chunks/[root-of-the-server]__02pocyg._.js.map +0 -1
  570. package/.next/server/chunks/[root-of-the-server]__03ueci3._.js +0 -3
  571. package/.next/server/chunks/[root-of-the-server]__04aud7x._.js +0 -3
  572. package/.next/server/chunks/[root-of-the-server]__04aud7x._.js.map +0 -1
  573. package/.next/server/chunks/[root-of-the-server]__05fnt3n._.js +0 -3
  574. package/.next/server/chunks/[root-of-the-server]__05fnt3n._.js.map +0 -1
  575. package/.next/server/chunks/[root-of-the-server]__077tsd.._.js +0 -20
  576. package/.next/server/chunks/[root-of-the-server]__077tsd.._.js.map +0 -1
  577. package/.next/server/chunks/[root-of-the-server]__08wy1jr._.js +0 -3
  578. package/.next/server/chunks/[root-of-the-server]__08wy1jr._.js.map +0 -1
  579. package/.next/server/chunks/[root-of-the-server]__0adhu1m._.js +0 -3
  580. package/.next/server/chunks/[root-of-the-server]__0adhu1m._.js.map +0 -1
  581. package/.next/server/chunks/[root-of-the-server]__0aqv02v._.js +0 -3
  582. package/.next/server/chunks/[root-of-the-server]__0aqv02v._.js.map +0 -1
  583. package/.next/server/chunks/[root-of-the-server]__0cawhfq._.js +0 -3
  584. package/.next/server/chunks/[root-of-the-server]__0cawhfq._.js.map +0 -1
  585. package/.next/server/chunks/[root-of-the-server]__0d_-_k.._.js +0 -3
  586. package/.next/server/chunks/[root-of-the-server]__0d_-_k.._.js.map +0 -1
  587. package/.next/server/chunks/[root-of-the-server]__0f6bxfl._.js +0 -3
  588. package/.next/server/chunks/[root-of-the-server]__0f6bxfl._.js.map +0 -1
  589. package/.next/server/chunks/[root-of-the-server]__0h15edq._.js +0 -3
  590. package/.next/server/chunks/[root-of-the-server]__0h15edq._.js.map +0 -1
  591. package/.next/server/chunks/[root-of-the-server]__0k.vegl._.js +0 -3
  592. package/.next/server/chunks/[root-of-the-server]__0k.vegl._.js.map +0 -1
  593. package/.next/server/chunks/[root-of-the-server]__0kltbce._.js +0 -3
  594. package/.next/server/chunks/[root-of-the-server]__0kltbce._.js.map +0 -1
  595. package/.next/server/chunks/[root-of-the-server]__0mn04fw._.js +0 -3
  596. package/.next/server/chunks/[root-of-the-server]__0mn04fw._.js.map +0 -1
  597. package/.next/server/chunks/[root-of-the-server]__0o29v8c._.js +0 -3
  598. package/.next/server/chunks/[root-of-the-server]__0o4qfn9._.js +0 -3
  599. package/.next/server/chunks/[root-of-the-server]__0o7ya7r._.js +0 -3
  600. package/.next/server/chunks/[root-of-the-server]__0o7ya7r._.js.map +0 -1
  601. package/.next/server/chunks/[root-of-the-server]__0p3fo6l._.js +0 -6
  602. package/.next/server/chunks/[root-of-the-server]__0p3fo6l._.js.map +0 -1
  603. package/.next/server/chunks/[root-of-the-server]__0p6tutt._.js +0 -3
  604. package/.next/server/chunks/[root-of-the-server]__0pn3wsx._.js +0 -3
  605. package/.next/server/chunks/[root-of-the-server]__0pn3wsx._.js.map +0 -1
  606. package/.next/server/chunks/[root-of-the-server]__0rg9ifx._.js +0 -3
  607. package/.next/server/chunks/[root-of-the-server]__0x.-194._.js +0 -3
  608. package/.next/server/chunks/[root-of-the-server]__0x.-194._.js.map +0 -1
  609. package/.next/server/chunks/[root-of-the-server]__0x.a_yh._.js +0 -3
  610. package/.next/server/chunks/[root-of-the-server]__0x.a_yh._.js.map +0 -1
  611. package/.next/server/chunks/[root-of-the-server]__0~h2uqi._.js +0 -3
  612. package/.next/server/chunks/[root-of-the-server]__139~d6a._.js +0 -3
  613. package/.next/server/chunks/[root-of-the-server]__139~d6a._.js.map +0 -1
  614. package/.next/server/chunks/_next-internal_server_app_api_anet_config_route_actions_0xzy73j.js +0 -3
  615. package/.next/server/chunks/_next-internal_server_app_api_auth_login_route_actions_0zukc38.js +0 -3
  616. package/.next/server/chunks/_next-internal_server_app_api_auth_logout_route_actions_0fm~3ij.js +0 -3
  617. package/.next/server/chunks/_next-internal_server_app_api_auth_v3_route_actions_0ndzs5l.js +0 -3
  618. package/.next/server/chunks/_next-internal_server_app_api_hub_audit-log_route_actions_11k8fqt.js +0 -3
  619. package/.next/server/chunks/_next-internal_server_app_api_hub_auth_route_actions_0gs7ttk.js +0 -3
  620. package/.next/server/chunks/_next-internal_server_app_api_hub_broadcast_route_actions_0ads-kd.js +0 -3
  621. package/.next/server/chunks/_next-internal_server_app_api_hub_events_route_actions_0opktby.js +0 -3
  622. package/.next/server/chunks/_next-internal_server_app_api_hub_health_route_actions_0tzzt~x.js +0 -3
  623. package/.next/server/chunks/_next-internal_server_app_api_hub_inbox_route_actions_0xvslrg.js +0 -3
  624. package/.next/server/chunks/_next-internal_server_app_api_hub_license_route_actions_0i3tlr3.js +0 -3
  625. package/.next/server/chunks/_next-internal_server_app_api_hub_messages_route_actions_08-gi-s.js +0 -3
  626. package/.next/server/chunks/_next-internal_server_app_api_hub_networks_route_actions_02vejq2.js +0 -3
  627. package/.next/server/chunks/_next-internal_server_app_api_hub_nodes_route_actions_0nx8ryn.js +0 -3
  628. package/.next/server/chunks/_next-internal_server_app_api_hub_register_route_actions_086wi_3.js +0 -3
  629. package/.next/server/chunks/_next-internal_server_app_api_hub_send_route_actions_0-ga_ug.js +0 -3
  630. package/.next/server/chunks/_next-internal_server_app_api_hub_server-logs_route_actions_00euexv.js +0 -3
  631. package/.next/server/chunks/_next-internal_server_app_api_hub_session_route_actions_041wa62.js +0 -3
  632. package/.next/server/chunks/_next-internal_server_app_api_hub_stats_route_actions_1242~od.js +0 -3
  633. package/.next/server/chunks/_next-internal_server_app_api_hub_status_route_actions_0aml09a.js +0 -3
  634. package/.next/server/chunks/_next-internal_server_app_api_hub_task-events_route_actions_02v1tmm.js +0 -3
  635. package/.next/server/chunks/_next-internal_server_app_api_hub_tasks_route_actions_0nl77v4.js +0 -3
  636. package/.next/server/chunks/_next-internal_server_app_api_hub_tmux_route_actions_0u~aaza.js +0 -3
  637. package/.next/server/chunks/_next-internal_server_app_api_hub_tokens_route_actions_09hzf_1.js +0 -3
  638. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_095lj93.js +0 -3
  639. package/.next/server/chunks/node_modules_next_11synfn._.js +0 -10
  640. package/.next/server/chunks/node_modules_next_11synfn._.js.map +0 -1
  641. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0yubv2d.js +0 -3
  642. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0yubv2d.js.map +0 -1
  643. package/.next/server/chunks/ssr/[root-of-the-server]__014uja_._.js +0 -3
  644. package/.next/server/chunks/ssr/[root-of-the-server]__014uja_._.js.map +0 -1
  645. package/.next/server/chunks/ssr/[root-of-the-server]__02hi65f._.js +0 -3
  646. package/.next/server/chunks/ssr/[root-of-the-server]__02hi65f._.js.map +0 -1
  647. package/.next/server/chunks/ssr/[root-of-the-server]__02xqa5z._.js +0 -3
  648. package/.next/server/chunks/ssr/[root-of-the-server]__02xqa5z._.js.map +0 -1
  649. package/.next/server/chunks/ssr/[root-of-the-server]__04ph1de._.js +0 -3
  650. package/.next/server/chunks/ssr/[root-of-the-server]__04ph1de._.js.map +0 -1
  651. package/.next/server/chunks/ssr/[root-of-the-server]__05wc5-z._.js +0 -3
  652. package/.next/server/chunks/ssr/[root-of-the-server]__05wc5-z._.js.map +0 -1
  653. package/.next/server/chunks/ssr/[root-of-the-server]__06-2p1a._.js +0 -33
  654. package/.next/server/chunks/ssr/[root-of-the-server]__06-2p1a._.js.map +0 -1
  655. package/.next/server/chunks/ssr/[root-of-the-server]__08qr2ji._.js.map +0 -1
  656. package/.next/server/chunks/ssr/[root-of-the-server]__09l8xaa._.js +0 -3
  657. package/.next/server/chunks/ssr/[root-of-the-server]__09l8xaa._.js.map +0 -1
  658. package/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js +0 -19
  659. package/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js.map +0 -1
  660. package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.js +0 -3
  661. package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.js.map +0 -1
  662. package/.next/server/chunks/ssr/[root-of-the-server]__0b7uu-_._.js +0 -3
  663. package/.next/server/chunks/ssr/[root-of-the-server]__0b7uu-_._.js.map +0 -1
  664. package/.next/server/chunks/ssr/[root-of-the-server]__0bl_wqk._.js +0 -3
  665. package/.next/server/chunks/ssr/[root-of-the-server]__0bl_wqk._.js.map +0 -1
  666. package/.next/server/chunks/ssr/[root-of-the-server]__0ft0rpu._.js +0 -3
  667. package/.next/server/chunks/ssr/[root-of-the-server]__0ft0rpu._.js.map +0 -1
  668. package/.next/server/chunks/ssr/[root-of-the-server]__0i~g-2t._.js +0 -3
  669. package/.next/server/chunks/ssr/[root-of-the-server]__0i~g-2t._.js.map +0 -1
  670. package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js +0 -3
  671. package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js.map +0 -1
  672. package/.next/server/chunks/ssr/[root-of-the-server]__0mook08._.js +0 -3
  673. package/.next/server/chunks/ssr/[root-of-the-server]__0mook08._.js.map +0 -1
  674. package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js +0 -3
  675. package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js.map +0 -1
  676. package/.next/server/chunks/ssr/[root-of-the-server]__0qchqb~._.js +0 -3
  677. package/.next/server/chunks/ssr/[root-of-the-server]__0qchqb~._.js.map +0 -1
  678. package/.next/server/chunks/ssr/[root-of-the-server]__0rrm~-q._.js +0 -3
  679. package/.next/server/chunks/ssr/[root-of-the-server]__0rrm~-q._.js.map +0 -1
  680. package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js +0 -3
  681. package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js.map +0 -1
  682. package/.next/server/chunks/ssr/[root-of-the-server]__0xjish~._.js +0 -3
  683. package/.next/server/chunks/ssr/[root-of-the-server]__0xjish~._.js.map +0 -1
  684. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +0 -3
  685. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +0 -1
  686. package/.next/server/chunks/ssr/[root-of-the-server]__0ywihy_._.js +0 -8
  687. package/.next/server/chunks/ssr/[root-of-the-server]__0ywihy_._.js.map +0 -1
  688. package/.next/server/chunks/ssr/[root-of-the-server]__12ad1n.._.js +0 -3
  689. package/.next/server/chunks/ssr/[root-of-the-server]__12ad1n.._.js.map +0 -1
  690. package/.next/server/chunks/ssr/_0nne7mz._.js +0 -3
  691. package/.next/server/chunks/ssr/_0nne7mz._.js.map +0 -1
  692. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
  693. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
  694. package/.next/server/chunks/ssr/_next-internal_server_app_admin_page_actions_0ycnu8f.js +0 -3
  695. package/.next/server/chunks/ssr/_next-internal_server_app_login_page_actions_02kefem.js +0 -3
  696. package/.next/server/chunks/ssr/_next-internal_server_app_logs_page_actions_0rvsl~d.js +0 -3
  697. package/.next/server/chunks/ssr/_next-internal_server_app_messages_page_actions_0gsqep_.js +0 -3
  698. package/.next/server/chunks/ssr/_next-internal_server_app_node_page_actions_0wd8cgl.js +0 -3
  699. package/.next/server/chunks/ssr/_next-internal_server_app_nodes_page_actions_0-.rv67.js +0 -3
  700. package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
  701. package/.next/server/chunks/ssr/_next-internal_server_app_server-logs_page_actions_0g1gb5c.js +0 -3
  702. package/.next/server/chunks/ssr/_next-internal_server_app_settings_networks_page_actions_09uw.sw.js +0 -3
  703. package/.next/server/chunks/ssr/_next-internal_server_app_settings_page_actions_0mr68ai.js +0 -3
  704. package/.next/server/chunks/ssr/_next-internal_server_app_settings_tokens_page_actions_0yi54x8.js +0 -3
  705. package/.next/server/chunks/ssr/_next-internal_server_app_tasks_[id]_page_actions_0k_hm9g.js +0 -3
  706. package/.next/server/chunks/ssr/_next-internal_server_app_tasks_page_actions_0_2o_cr.js +0 -3
  707. package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +0 -1
  708. package/.next/server/chunks/ssr/app_01y-~mp._.js +0 -3
  709. package/.next/server/chunks/ssr/app_01y-~mp._.js.map +0 -1
  710. package/.next/server/chunks/ssr/app_04n78pr._.js.map +0 -1
  711. package/.next/server/chunks/ssr/app_0_agk0r._.js.map +0 -1
  712. package/.next/server/chunks/ssr/app_0aenxef._.js +0 -3
  713. package/.next/server/chunks/ssr/app_0aenxef._.js.map +0 -1
  714. package/.next/server/chunks/ssr/app_0aum_g5._.js +0 -3
  715. package/.next/server/chunks/ssr/app_0aum_g5._.js.map +0 -1
  716. package/.next/server/chunks/ssr/app_0emvkn2._.js +0 -3
  717. package/.next/server/chunks/ssr/app_0emvkn2._.js.map +0 -1
  718. package/.next/server/chunks/ssr/app_0esuljp._.js +0 -3
  719. package/.next/server/chunks/ssr/app_0esuljp._.js.map +0 -1
  720. package/.next/server/chunks/ssr/app_0izi28j._.js.map +0 -1
  721. package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +0 -1
  722. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +0 -1
  723. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +0 -3
  724. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +0 -1
  725. package/.next/server/chunks/ssr/node_modules_09w7yel._.js.map +0 -1
  726. package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js +0 -3
  727. package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js.map +0 -1
  728. package/.next/server/chunks/ssr/node_modules_0r_.s40._.js.map +0 -1
  729. package/.next/server/chunks/ssr/node_modules_0vtdjw7._.js +0 -3
  730. package/.next/server/chunks/ssr/node_modules_0vtdjw7._.js.map +0 -1
  731. package/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js +0 -6
  732. package/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js.map +0 -1
  733. package/.next/server/chunks/ssr/node_modules_next_dist_0e1izl_._.js +0 -6
  734. package/.next/server/chunks/ssr/node_modules_next_dist_0e1izl_._.js.map +0 -1
  735. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js +0 -3
  736. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js.map +0 -1
  737. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js +0 -3
  738. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js.map +0 -1
  739. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js +0 -3
  740. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js.map +0 -1
  741. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js +0 -3
  742. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js.map +0 -1
  743. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_002l7yi.js.map +0 -1
  744. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_02suzhc.js.map +0 -1
  745. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07uu-xq.js.map +0 -1
  746. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0d2yq5t.js.map +0 -1
  747. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dm~kj8.js.map +0 -1
  748. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0foejvu.js.map +0 -1
  749. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0l-fh0i.js.map +0 -1
  750. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0nsg22r.js.map +0 -1
  751. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0o9vt1n.js.map +0 -1
  752. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0qm1-a1.js.map +0 -1
  753. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0uccf9z.js.map +0 -1
  754. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0uv6jyc.js.map +0 -1
  755. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10~~o-h.js +0 -4
  756. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10~~o-h.js.map +0 -1
  757. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13leyhk.js +0 -4
  758. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13leyhk.js.map +0 -1
  759. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13s07li.js.map +0 -1
  760. package/.next/static/chunks/0218jmcfde~9s.js +0 -1
  761. package/.next/static/chunks/0257pdz1-imal.js +0 -2
  762. package/.next/static/chunks/02_zpo7taex9o.js +0 -7
  763. package/.next/static/chunks/06n3d~fbbtagk.js +0 -1
  764. package/.next/static/chunks/07eba1lz2u3p4.js +0 -1
  765. package/.next/static/chunks/09n9xs.6dvvo2.css +0 -2
  766. package/.next/static/chunks/0av2~0.96~d~g.js +0 -1
  767. package/.next/static/chunks/0c1bp~g7.8ru..js +0 -1
  768. package/.next/static/chunks/0e-.zgw81gela.js +0 -31
  769. package/.next/static/chunks/0g9aof0ob_fcj.js +0 -1
  770. package/.next/static/chunks/0s8jo6jypzkx5.js +0 -1
  771. package/.next/static/chunks/0u359llqm1_01.js +0 -1
  772. package/.next/static/chunks/0ubqqhhpsaez4.js +0 -1
  773. package/.next/static/chunks/0z1xslnw3jx1s.js +0 -1
  774. package/.next/static/chunks/0~qw9kxksr1v2.js +0 -1
  775. package/.next/static/chunks/12gprw3l0kvvf.js +0 -1
  776. package/.next/static/chunks/12yuo353giomx.js +0 -1
  777. package/.next/static/chunks/13j9vr8_l9ket.js +0 -1
  778. package/.next/static/chunks/14bsg3_-taiw4.js +0 -1
  779. /package/.next/build/chunks/{[root-of-the-server]__0ubbtyl._.js.map → [root-of-the-server]__0tgc-8p._.js.map} +0 -0
  780. /package/.next/build/chunks/{[turbopack-node]_transforms_postcss_ts_06e.r3r._.js.map → [turbopack-node]_transforms_postcss_ts_0r4x5ys._.js.map} +0 -0
  781. /package/.next/server/chunks/{_next-internal_server_app_api_anet_config_route_actions_0xzy73j.js.map → 0ci9_nt-network-dashboard__next-internal_server_app_api_auth_v3_route_actions_0f5nbzr.js.map} +0 -0
  782. /package/.next/server/chunks/{_next-internal_server_app_api_auth_login_route_actions_0zukc38.js.map → 0ci9_nt-network-dashboard__next-internal_server_app_favicon_ico_route_actions_0t0eu9r.js.map} +0 -0
  783. /package/.next/server/chunks/{_next-internal_server_app_api_auth_logout_route_actions_0fm~3ij.js.map → 0ykm__next-internal_server_app_api_anet_config_route_actions_0a~v55_.js.map} +0 -0
  784. /package/.next/server/chunks/{_next-internal_server_app_api_auth_v3_route_actions_0ndzs5l.js.map → 0ykm__next-internal_server_app_api_auth_login_route_actions_0xnxfs4.js.map} +0 -0
  785. /package/.next/server/chunks/{_next-internal_server_app_api_hub_audit-log_route_actions_11k8fqt.js.map → 0ykm__next-internal_server_app_api_auth_logout_route_actions_0p8p0rn.js.map} +0 -0
  786. /package/.next/server/chunks/{_next-internal_server_app_api_hub_auth_route_actions_0gs7ttk.js.map → 0ykm__next-internal_server_app_api_hub_audit-log_route_actions_06uquwi.js.map} +0 -0
  787. /package/.next/server/chunks/{_next-internal_server_app_api_hub_broadcast_route_actions_0ads-kd.js.map → 0ykm__next-internal_server_app_api_hub_auth_route_actions_0f_y1ey.js.map} +0 -0
  788. /package/.next/server/chunks/{_next-internal_server_app_api_hub_events_route_actions_0opktby.js.map → 0ykm__next-internal_server_app_api_hub_broadcast_route_actions_0ao3j43.js.map} +0 -0
  789. /package/.next/server/chunks/{_next-internal_server_app_api_hub_health_route_actions_0tzzt~x.js.map → 0ykm__next-internal_server_app_api_hub_events_route_actions_0_5ndld.js.map} +0 -0
  790. /package/.next/server/chunks/{_next-internal_server_app_api_hub_inbox_route_actions_0xvslrg.js.map → 0ykm__next-internal_server_app_api_hub_health_route_actions_0mpzl74.js.map} +0 -0
  791. /package/.next/server/chunks/{_next-internal_server_app_api_hub_license_route_actions_0i3tlr3.js.map → 0ykm__next-internal_server_app_api_hub_inbox_route_actions_0stoyto.js.map} +0 -0
  792. /package/.next/server/chunks/{_next-internal_server_app_api_hub_messages_route_actions_08-gi-s.js.map → 0ykm__next-internal_server_app_api_hub_license_route_actions_0a4.fuh.js.map} +0 -0
  793. /package/.next/server/chunks/{_next-internal_server_app_api_hub_networks_route_actions_02vejq2.js.map → 0ykm__next-internal_server_app_api_hub_messages_route_actions_059rw1o.js.map} +0 -0
  794. /package/.next/server/chunks/{_next-internal_server_app_api_hub_nodes_route_actions_0nx8ryn.js.map → 0ykm__next-internal_server_app_api_hub_networks_route_actions_03sehpn.js.map} +0 -0
  795. /package/.next/server/chunks/{_next-internal_server_app_api_hub_register_route_actions_086wi_3.js.map → 0ykm__next-internal_server_app_api_hub_nodes_route_actions_11fbp28.js.map} +0 -0
  796. /package/.next/server/chunks/{_next-internal_server_app_api_hub_send_route_actions_0-ga_ug.js.map → 0ykm__next-internal_server_app_api_hub_register_route_actions_0l.d7~n.js.map} +0 -0
  797. /package/.next/server/chunks/{_next-internal_server_app_api_hub_server-logs_route_actions_00euexv.js.map → 0ykm__next-internal_server_app_api_hub_send_route_actions_0.p82vy.js.map} +0 -0
  798. /package/.next/server/chunks/{_next-internal_server_app_api_hub_session_route_actions_041wa62.js.map → 0ykm__next-internal_server_app_api_hub_server-logs_route_actions_05~c5lb.js.map} +0 -0
  799. /package/.next/server/chunks/{_next-internal_server_app_api_hub_stats_route_actions_1242~od.js.map → 0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map} +0 -0
  800. /package/.next/server/chunks/{_next-internal_server_app_api_hub_status_route_actions_0aml09a.js.map → 0ykm__next-internal_server_app_api_hub_session_route_actions_0b9ef2i.js.map} +0 -0
  801. /package/.next/server/chunks/{_next-internal_server_app_api_hub_task-events_route_actions_02v1tmm.js.map → 0ykm__next-internal_server_app_api_hub_stats_route_actions_03u65u0.js.map} +0 -0
  802. /package/.next/server/chunks/{_next-internal_server_app_api_hub_tasks_route_actions_0nl77v4.js.map → 0ykm__next-internal_server_app_api_hub_status_route_actions_0_71ear.js.map} +0 -0
  803. /package/.next/server/chunks/{_next-internal_server_app_api_hub_tmux_route_actions_0u~aaza.js.map → 0ykm__next-internal_server_app_api_hub_task-events_route_actions_07md_ee.js.map} +0 -0
  804. /package/.next/server/chunks/{_next-internal_server_app_api_hub_tokens_route_actions_09hzf_1.js.map → 0ykm__next-internal_server_app_api_hub_tasks_route_actions_0er3uvk.js.map} +0 -0
  805. /package/.next/server/chunks/{_next-internal_server_app_favicon_ico_route_actions_095lj93.js.map → 0ykm__next-internal_server_app_api_hub_tmux_route_actions_0o_1-8u.js.map} +0 -0
  806. /package/.next/server/chunks/{ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js.map → 0ykm__next-internal_server_app_api_hub_tokens_route_actions_09doluz.js.map} +0 -0
  807. /package/.next/server/chunks/ssr/{_next-internal_server_app__not-found_page_actions_0eq97pa.js.map → 0dwh_ent-network-dashboard__next-internal_server_app_server-logs_page_actions_0hkk815.js.map} +0 -0
  808. /package/.next/server/chunks/ssr/{_next-internal_server_app_admin_page_actions_0ycnu8f.js.map → 0yas_gent-network-dashboard__next-internal_server_app__not-found_page_actions_0u.f-rz.js.map} +0 -0
  809. /package/.next/server/chunks/ssr/{_next-internal_server_app_login_page_actions_02kefem.js.map → 0yas_gent-network-dashboard__next-internal_server_app_tasks_[id]_page_actions_0r3~lho.js.map} +0 -0
  810. /package/.next/server/chunks/ssr/{_next-internal_server_app_logs_page_actions_0rvsl~d.js.map → 0ykm__next-internal_server_app__global-error_page_actions_100fxls.js.map} +0 -0
  811. /package/.next/server/chunks/ssr/{_next-internal_server_app_messages_page_actions_0gsqep_.js.map → 0ykm__next-internal_server_app_settings_networks_page_actions_0jgy57m.js.map} +0 -0
  812. /package/.next/server/chunks/ssr/{_next-internal_server_app_node_page_actions_0wd8cgl.js.map → 0ykm__next-internal_server_app_settings_tokens_page_actions_0l5h.k~.js.map} +0 -0
  813. /package/.next/server/chunks/ssr/{_next-internal_server_app_nodes_page_actions_0-.rv67.js.map → agent-network-dashboard__next-internal_server_app_admin_page_actions_0i~.x~j.js.map} +0 -0
  814. /package/.next/server/chunks/ssr/{_next-internal_server_app_page_actions_09-gtaw.js.map → agent-network-dashboard__next-internal_server_app_login_page_actions_0frwl15.js.map} +0 -0
  815. /package/.next/server/chunks/ssr/{_next-internal_server_app_server-logs_page_actions_0g1gb5c.js.map → agent-network-dashboard__next-internal_server_app_logs_page_actions_0xa2ox_.js.map} +0 -0
  816. /package/.next/server/chunks/ssr/{_next-internal_server_app_settings_networks_page_actions_09uw.sw.js.map → agent-network-dashboard__next-internal_server_app_messages_page_actions_10j3tg4.js.map} +0 -0
  817. /package/.next/server/chunks/ssr/{_next-internal_server_app_settings_page_actions_0mr68ai.js.map → agent-network-dashboard__next-internal_server_app_node_page_actions_0sl4ntd.js.map} +0 -0
  818. /package/.next/server/chunks/ssr/{_next-internal_server_app_settings_tokens_page_actions_0yi54x8.js.map → agent-network-dashboard__next-internal_server_app_nodes_page_actions_0cpgkfs.js.map} +0 -0
  819. /package/.next/server/chunks/ssr/{_next-internal_server_app_tasks_[id]_page_actions_0k_hm9g.js.map → agent-network-dashboard__next-internal_server_app_page_actions_08nuo36.js.map} +0 -0
  820. /package/.next/server/chunks/ssr/{_next-internal_server_app_tasks_page_actions_0_2o_cr.js.map → agent-network-dashboard__next-internal_server_app_settings_page_actions_0iv.lmk.js.map} +0 -0
  821. /package/.next/static/{kXeBUTKSdNooA8p2BTTfp → mgs4E0Fu2JlZsA5DB0XUu}/_buildManifest.js +0 -0
  822. /package/.next/static/{kXeBUTKSdNooA8p2BTTfp → mgs4E0Fu2JlZsA5DB0XUu}/_clientMiddlewareManifest.js +0 -0
  823. /package/.next/static/{kXeBUTKSdNooA8p2BTTfp → mgs4E0Fu2JlZsA5DB0XUu}/_ssgManifest.js +0 -0
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Session } from './types';
5
5
  import { aliasAvatarColors, aliasInitial } from './AliasAvatar';
6
6
  import { ChatPopover } from './ChatPopover';
7
+ import { vendorForModel, runtimeIdentity, identityLine } from '../lib/vendorIdentity';
7
8
 
8
9
  interface MessageFlow {
9
10
  from_alias: string;
@@ -15,6 +16,9 @@ interface MessageFlow {
15
16
  interface TopoGraphProps {
16
17
  sessions: Session[];
17
18
  sseSessions: Record<string, number>;
19
+ // #84: a node.renamed event from the Overview's SSE listener. When the
20
+ // currently open chat popover targets `from`, it follows the rename to `to`.
21
+ renameSignal?: { from: string; to: string; ts: number } | null;
18
22
  }
19
23
 
20
24
  interface Point {
@@ -28,6 +32,9 @@ interface FlowLink {
28
32
  to: string;
29
33
  count: number;
30
34
  content: string;
35
+ /** ISO timestamp of the most recent message on this edge — drives
36
+ * the Round 10 freshness fade so dormant links recede visually. */
37
+ last_at: string;
31
38
  }
32
39
 
33
40
  const cx = 500;
@@ -81,36 +88,52 @@ function truncate(value: string, max: number) {
81
88
  return value.length > max ? `${value.slice(0, max - 1)}...` : value;
82
89
  }
83
90
 
91
+ /** Round 12 / Loop: status trio audit.
92
+ *
93
+ * Each (status × theme) cell returns a {primary, halo, text} trio. The trio
94
+ * invariant — keep it when adding states or tweaking shades:
95
+ *
96
+ * light: primary = <hue>-600 halo = <hue>-100 * text = <hue>-800/900
97
+ * dark: primary = <hue>-400/500 halo = <hue>-900 text = <hue>-100
98
+ *
99
+ * * offline.halo light deviates intentionally to slate-200 (#e2e8f0):
100
+ * slate-100 (#f1f5f9) is too close to the panel bg (#f8fafc) and the
101
+ * halo would vanish. The other three rows keep the 100-shade.
102
+ *
103
+ * Audit caught one cross-family drift before this round: online-other halo
104
+ * light was #dbeafe (blue-100) while its primary (#0284c7 sky-600) and text
105
+ * (#0c4a6e sky-900) were sky-family — now sky-100 (#e0f2fe). Tiny visual
106
+ * difference; large hygiene win — every trio is now mono-hue. */
84
107
  function nodeStatus(session: Session, isOnline: boolean, isLight: boolean) {
85
108
  if (!isOnline) {
86
109
  return {
87
110
  label: 'offline',
88
- primary: isLight ? '#94a3b8' : '#6b7280',
89
- halo: isLight ? '#e2e8f0' : '#111827',
90
- text: isLight ? '#475569' : '#9ca3af',
111
+ primary: isLight ? '#94a3b8' : '#6b7280', // slate-400 / gray-500
112
+ halo: isLight ? '#e2e8f0' : '#111827', // slate-200* / gray-900
113
+ text: isLight ? '#475569' : '#9ca3af', // slate-600 / gray-400
91
114
  };
92
115
  }
93
116
  if (session.status === 'working') {
94
117
  return {
95
118
  label: 'working',
96
- primary: isLight ? '#059669' : '#22c55e',
97
- halo: isLight ? '#d1fae5' : '#14532d',
98
- text: isLight ? '#065f46' : '#dcfce7',
119
+ primary: isLight ? '#059669' : '#22c55e', // emerald-600 / green-500
120
+ halo: isLight ? '#d1fae5' : '#14532d', // emerald-100 / green-900
121
+ text: isLight ? '#065f46' : '#dcfce7', // emerald-800 / green-100
99
122
  };
100
123
  }
101
124
  if (session.status === 'idle') {
102
125
  return {
103
126
  label: 'idle',
104
- primary: isLight ? '#0d9488' : '#2dd4bf',
105
- halo: isLight ? '#ccfbf1' : '#134e4a',
106
- text: isLight ? '#115e59' : '#ccfbf1',
127
+ primary: isLight ? '#0d9488' : '#2dd4bf', // teal-600 / teal-400
128
+ halo: isLight ? '#ccfbf1' : '#134e4a', // teal-100 / teal-900
129
+ text: isLight ? '#115e59' : '#ccfbf1', // teal-800 / teal-100
107
130
  };
108
131
  }
109
132
  return {
110
133
  label: session.status || 'online',
111
- primary: isLight ? '#0284c7' : '#38bdf8',
112
- halo: isLight ? '#dbeafe' : '#0c4a6e',
113
- text: isLight ? '#0c4a6e' : '#e0f2fe',
134
+ primary: isLight ? '#0284c7' : '#38bdf8', // sky-600 / sky-400
135
+ halo: isLight ? '#e0f2fe' : '#0c4a6e', // sky-100 / sky-900 (was blue-100 — drift fixed Round 12)
136
+ text: isLight ? '#0c4a6e' : '#e0f2fe', // sky-900 / sky-100
114
137
  };
115
138
  }
116
139
 
@@ -236,22 +259,59 @@ function commonPrefix(a: string, b: string): string {
236
259
  return a.slice(0, i);
237
260
  }
238
261
 
239
- function computeGroupKeys(aliases: string[]): Record<string, string> {
240
- const sorted = [...aliases].sort((a, b) => a.localeCompare(b));
262
+ /** #83 + #111: group nodes that share a ≥2-char alias prefix OR a project_dir
263
+ * ("either criterion same group", Vincent 4724). Union-find over the
264
+ * sessions; the component label prefers the shared project_dir's basename,
265
+ * else the common alias prefix. Returns alias → groupKey. */
266
+ function computeGroups(sessions: { alias: string; project_dir?: string | null }[]): Record<string, string> {
267
+ const n = sessions.length;
268
+ const parent = sessions.map((_, i) => i);
269
+ const find = (i: number): number => {
270
+ while (parent[i] !== i) { parent[i] = parent[parent[i]]; i = parent[i]; }
271
+ return i;
272
+ };
273
+ const union = (a: number, b: number) => {
274
+ const ra = find(a), rb = find(b);
275
+ if (ra !== rb) parent[ra] = rb;
276
+ };
277
+
278
+ // union by shared project_dir
279
+ const byDir: Record<string, number[]> = {};
280
+ sessions.forEach((s, i) => {
281
+ const d = s.project_dir?.trim();
282
+ if (d) (byDir[d] ||= []).push(i);
283
+ });
284
+ for (const idxs of Object.values(byDir)) {
285
+ for (let k = 1; k < idxs.length; k++) union(idxs[0], idxs[k]);
286
+ }
287
+
288
+ // union by shared ≥2-char alias prefix — sort, link adjacent pairs
289
+ const order = sessions.map((_, i) => i).sort((a, b) => sessions[a].alias.localeCompare(sessions[b].alias));
290
+ for (let k = 0; k + 1 < order.length; k++) {
291
+ if (commonPrefix(sessions[order[k]].alias, sessions[order[k + 1]].alias).length >= 2) {
292
+ union(order[k], order[k + 1]);
293
+ }
294
+ }
295
+
296
+ // label each connected component
297
+ const comps: Record<number, number[]> = {};
298
+ for (let i = 0; i < n; i++) (comps[find(i)] ||= []).push(i);
241
299
  const keys: Record<string, string> = {};
242
- let i = 0;
243
- while (i < sorted.length) {
244
- let j = i;
245
- let prefix = sorted[i];
246
- while (j + 1 < sorted.length) {
247
- const lcp = commonPrefix(prefix, sorted[j + 1]);
248
- if (lcp.length < 2) break;
249
- prefix = lcp;
250
- j++;
300
+ for (const members of Object.values(comps)) {
301
+ let label: string;
302
+ if (members.length === 1) {
303
+ label = sessions[members[0]].alias;
304
+ } else {
305
+ const dirs = new Set(members.map(i => sessions[i].project_dir?.trim()).filter(Boolean) as string[]);
306
+ if (dirs.size === 1) {
307
+ const d = [...dirs][0];
308
+ label = d.split('/').filter(Boolean).pop() || d;
309
+ } else {
310
+ label = members.map(i => sessions[i].alias).reduce((a, b) => commonPrefix(a, b));
311
+ if (label.length < 2) label = sessions[members[0]].alias;
312
+ }
251
313
  }
252
- const key = j > i ? prefix : sorted[i];
253
- for (let k = i; k <= j; k++) keys[sorted[k]] = key;
254
- i = j + 1;
314
+ for (const i of members) keys[sessions[i].alias] = label;
255
315
  }
256
316
  return keys;
257
317
  }
@@ -271,19 +331,27 @@ function buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point
271
331
  const key = `${message.from_alias}->${message.to_alias}`;
272
332
  const current = links.get(key);
273
333
 
334
+ // Keep the most-recent timestamp per pair so the render can fade
335
+ // dormant edges (Round 10 freshness fade).
336
+ const incoming = message.created_at || '';
337
+ const last_at = !current
338
+ ? incoming
339
+ : (incoming > current.last_at ? incoming : current.last_at);
340
+
274
341
  links.set(key, {
275
342
  key,
276
343
  from: message.from_alias,
277
344
  to: message.to_alias,
278
345
  count: (current?.count || 0) + 1,
279
346
  content: current?.content || message.content,
347
+ last_at,
280
348
  });
281
349
  });
282
350
 
283
351
  return [...links.values()].slice(0, 18);
284
352
  }
285
353
 
286
- export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
354
+ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProps) {
287
355
  const theme = useTheme();
288
356
  const isLight = theme === 'light';
289
357
  const pal = isLight ? LIGHT_PALETTE : DARK_PALETTE;
@@ -306,6 +374,20 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
306
374
  try { localStorage.setItem('anet-topo-layout', next); } catch {}
307
375
  return next;
308
376
  });
377
+ // Issue #113: node size scale (Vincent 4727 — nodes too big / crowded at
378
+ // ~22 nodes). S/M/L → 0.7/0.84/1.0; default M (one notch down from the old
379
+ // fixed size). Persisted like the layout toggle.
380
+ const [nodeScale, setNodeScale] = useState(0.84);
381
+ useEffect(() => {
382
+ try {
383
+ const saved = parseFloat(localStorage.getItem('anet-topo-nodescale') || '');
384
+ if (saved === 0.7 || saved === 0.84 || saved === 1) setNodeScale(saved);
385
+ } catch {}
386
+ }, []);
387
+ const pickNodeScale = (v: number) => {
388
+ setNodeScale(v);
389
+ try { localStorage.setItem('anet-topo-nodescale', String(v)); } catch {}
390
+ };
309
391
 
310
392
  useEffect(() => {
311
393
  const fetchMessages = async () => {
@@ -332,6 +414,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
332
414
  flowLinks,
333
415
  activeAliases,
334
416
  groupKeys,
417
+ groupBoxes,
335
418
  } = useMemo(() => {
336
419
  const sseCount = (s: { alias: string; network_id?: string }) =>
337
420
  (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];
@@ -341,36 +424,123 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
341
424
  // index, so contiguous-in-array becomes contiguous-in-ring, i.e.
342
425
  // each team visually clusters. localeCompare keeps CJK ordering sane.
343
426
  const byAlias = (a: Session, b: Session) => a.alias.localeCompare(b.alias);
427
+ // #112 umbrella: ghost age-out — an offline node not seen for over a day
428
+ // is almost certainly a deleted node the server `/api/status` still
429
+ // returns (#74 root cause is server-side; this is the dashboard-side
430
+ // fallback so stale ghosts stop cluttering the topology). A missing
431
+ // last_seen_at is kept (conservative — could be a legitimately new node).
432
+ const GHOST_MS = 24 * 60 * 60 * 1000;
433
+ const now = Date.now();
434
+ const isGhost = (s: Session) => {
435
+ if (s.status !== 'offline' || sseCount(s) || !s.last_seen_at) return false;
436
+ const t = Date.parse(s.last_seen_at);
437
+ return !Number.isNaN(t) && now - t > GHOST_MS;
438
+ };
344
439
  const online = sessions.filter(s => s.status !== 'offline' || sseCount(s)).sort(byAlias);
345
- const offline = sessions.filter(s => s.status === 'offline' && !sseCount(s)).sort(byAlias);
440
+ const offline = sessions.filter(s => s.status === 'offline' && !sseCount(s) && !isGhost(s)).sort(byAlias);
346
441
  const positions: Record<string, Point> = {};
347
442
 
348
443
  if (layout === 'grid') {
349
- // Issue #87: N×M grid layout. All nodes share one sorted array (online
350
- // first, then offline) so same-prefix aliases land in adjacent cells
351
- // (#83 prefix-grouping synergy). cols = ⌈√N⌉; the last partial row is
352
- // centred. Grid spans an inset box so node labels don't clip the edge.
444
+ // Issue #87 + #111: group-banded grid. Nodes are sorted by alias so
445
+ // same-prefix aliases are adjacent; each multi-member prefix group then
446
+ // gets its OWN row(s) starting at column 0, while singletons pack into
447
+ // shared rows. Group-banded placement keeps every group's bounding box
448
+ // (#111) a clean rectangle — no box ever overlaps another group's.
353
449
  const all = [...online, ...offline];
354
- const n = all.length;
355
- const cols = Math.max(1, Math.ceil(Math.sqrt(n)));
356
- const rows = Math.max(1, Math.ceil(n / cols));
357
- const gx0 = 150, gx1 = 850, gy0 = 120, gy1 = 560;
450
+ const groupKeys = computeGroups(all);
451
+ const cols = Math.max(1, Math.ceil(Math.sqrt(all.length)));
452
+ // #112: gy0 starts below the (now compact) recent-signal / legend
453
+ // overlay panels their max bottom edge is y≈112 so grid row 0 is
454
+ // never occluded; gy1 extends near the canvas bottom for breathing room.
455
+ const gx0 = 150, gx1 = 850, gy0 = 126, gy1 = 652;
358
456
  const cellW = (gx1 - gx0) / cols;
359
- const cellH = (gy1 - gy0) / rows;
360
- all.forEach((s, i) => {
361
- const row = Math.floor(i / cols);
362
- const col = i % cols;
363
- const rowCount = row === rows - 1 ? n - row * cols : cols;
364
- const rowInset = ((cols - rowCount) * cellW) / 2;
365
- positions[s.alias] = {
366
- x: gx0 + rowInset + (col + 0.5) * cellW,
367
- y: gy0 + (row + 0.5) * cellH,
368
- };
369
- });
457
+ // largest node radius at the current size scale — drives the group
458
+ // label band + the row-height floor so nothing ever overlaps.
459
+ const nodeR = Math.round(26 * nodeScale);
460
+
461
+ // ordered runs of consecutive same-group-key nodes (≥2 = real group)
462
+ const runs: { key: string; members: Session[] }[] = [];
463
+ for (const s of all) {
464
+ const gk = groupKeys[s.alias];
465
+ const last = runs[runs.length - 1];
466
+ if (last && last.key === gk) last.members.push(s);
467
+ else runs.push({ key: gk, members: [s] });
468
+ }
469
+
470
+ // Pass 1 — assign each run to a band: a multi-member group owns its
471
+ // rows (left-aligned, so its bounding box is a tidy rect); contiguous
472
+ // singletons pack into shared rows (centred). Collect total row count.
473
+ type Band = { members: Session[]; startRow: number; centred: boolean; isGroup: boolean };
474
+ const bands: Band[] = [];
475
+ let row = 0;
476
+ let i = 0;
477
+ while (i < runs.length) {
478
+ if (runs[i].members.length >= 2) {
479
+ bands.push({ members: runs[i].members, startRow: row, centred: false, isGroup: true });
480
+ row += Math.ceil(runs[i].members.length / cols);
481
+ i++;
482
+ } else {
483
+ const singles: Session[] = [];
484
+ while (i < runs.length && runs[i].members.length < 2) {
485
+ singles.push(runs[i].members[0]);
486
+ i++;
487
+ }
488
+ bands.push({ members: singles, startRow: row, centred: true, isGroup: false });
489
+ row += Math.ceil(singles.length / cols);
490
+ }
491
+ }
492
+ const totalRows = Math.max(1, row);
493
+ // #112: the group label sits in a band ABOVE the topmost node, so the
494
+ // band must clear the node radius — GROUP_TOP is node-relative, never
495
+ // cellH-derived (cellH-derived was the label↔node overlap bug Vincent
496
+ // hit). cellH then floors at GROUP_TOP + 30 so the label band + bottom
497
+ // padding always fit and stacked group boxes never touch; past the
498
+ // floor the grid overflows and zoom/pan handles it.
499
+ const GROUP_TOP = nodeR + 20;
500
+ // Row-height floor = the tightest of: a node + its label below it
501
+ // (2·nodeR + ~22), and the label band + min box padding (GROUP_TOP + 12).
502
+ // Below this, rows would overlap; past it the grid overflows and
503
+ // zoom/pan covers the rest.
504
+ const cellH = Math.max(2 * nodeR + 22, GROUP_TOP + 12, Math.min(100, (gy1 - gy0) / totalRows));
505
+
506
+ // Pass 2 — place each band's members.
507
+ for (const band of bands) {
508
+ band.members.forEach((s, idx) => {
509
+ const rowInBand = Math.floor(idx / cols);
510
+ const c = idx % cols;
511
+ const inRow = Math.min(cols, band.members.length - rowInBand * cols);
512
+ const inset = band.centred ? ((cols - inRow) * cellW) / 2 : 0;
513
+ positions[s.alias] = {
514
+ x: gx0 + inset + (c + 0.5) * cellW,
515
+ y: gy0 + (band.startRow + rowInBand + 0.5) * cellH,
516
+ };
517
+ });
518
+ }
519
+
370
520
  const links = buildFlowLinks(messages, positions);
371
521
  const active = new Set<string>();
372
522
  links.forEach(link => { active.add(link.from); active.add(link.to); });
373
- const groupKeys = computeGroupKeys([...online, ...offline].map(s => s.alias));
523
+ // #111: one bounding box per multi-member group (Vincent 4722). Each
524
+ // group owns its rows; GROUP_PAD fills the row space left below the
525
+ // nodes after the label band, so stacked group boxes always have a
526
+ // gap between them (GROUP_TOP is defined above, with cellH).
527
+ const GROUP_PAD = Math.max(8, Math.min(26, cellH - GROUP_TOP - 8)); // side/bottom
528
+ const groupBoxes = bands
529
+ .filter(b => b.isGroup)
530
+ .map(band => {
531
+ const pts = band.members.map(s => positions[s.alias]).filter(Boolean);
532
+ const xs = pts.map(p => p.x);
533
+ const ys = pts.map(p => p.y);
534
+ const minX = Math.min(...xs), minY = Math.min(...ys);
535
+ return {
536
+ key: band.members.length ? groupKeys[band.members[0].alias] : '',
537
+ count: band.members.length,
538
+ x: minX - GROUP_PAD,
539
+ y: minY - GROUP_TOP,
540
+ w: Math.max(...xs) - minX + GROUP_PAD * 2,
541
+ h: Math.max(...ys) - minY + GROUP_TOP + GROUP_PAD,
542
+ };
543
+ });
374
544
  return {
375
545
  onlineNodes: online,
376
546
  offlineNodes: offline,
@@ -378,6 +548,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
378
548
  flowLinks: links,
379
549
  activeAliases: active,
380
550
  groupKeys,
551
+ groupBoxes,
381
552
  };
382
553
  }
383
554
 
@@ -448,7 +619,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
448
619
  });
449
620
 
450
621
  // Round 106 (issue #83): group key per alias → shared hue per team.
451
- const groupKeys = computeGroupKeys([...online, ...offline].map(s => s.alias));
622
+ const groupKeys = computeGroups([...online, ...offline]);
452
623
 
453
624
  return {
454
625
  onlineNodes: online,
@@ -457,15 +628,36 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
457
628
  flowLinks: links,
458
629
  activeAliases: active,
459
630
  groupKeys,
631
+ // #111: group boxes are a grid-layout feature only — radially scattered
632
+ // ring nodes can't be cleanly boxed. Ring keeps the #83 prefix hue.
633
+ groupBoxes: [] as { key: string; count: number; x: number; y: number; w: number; h: number }[],
460
634
  };
461
- }, [messages, sessions, sseSessions, layout]);
635
+ }, [messages, sessions, sseSessions, layout, nodeScale]);
462
636
 
463
637
  const workingCount = onlineNodes.filter(s => s.status === 'working').length;
638
+ // Round 6 / Loop: vendor distribution for the header chip — at a glance
639
+ // "what's in the fleet" (A:5 M:2 O:8 书:12 …) without opening a node.
640
+ // Sorted by count desc; "unknown" vendors collapse into a "?" bucket.
641
+ const vendorDist = useMemo(() => {
642
+ const tally = new Map<string, { initial: string; count: number; color: string }>();
643
+ for (const s of [...onlineNodes, ...offlineNodes]) {
644
+ const v = vendorForModel(s.model);
645
+ const key = v.id === 'unknown' ? '?' : v.initial;
646
+ const cur = tally.get(key);
647
+ if (cur) cur.count++;
648
+ else tally.set(key, { initial: key, count: 1, color: v.mono.text });
649
+ }
650
+ return [...tally.values()].sort((a, b) => b.count - a.count);
651
+ }, [onlineNodes, offlineNodes]);
464
652
  // Round 109 (Vincent 4582 P0): hover-gated labels above this node count
465
653
  // so dense fleets show clean avatars instead of a wall of overlapping
466
654
  // label cards. 16 ≈ where the triple-tier rings start to crowd.
467
655
  const denseLayout = onlineNodes.length + offlineNodes.length > 16;
468
656
  const [hoveredAlias, setHoveredAlias] = useState<string | null>(null);
657
+ // Round 8 / Loop: which group is currently focused. Nodes outside this
658
+ // group + other group boxes fade so the eye locks onto the team you're
659
+ // pointing at. Singletons use their own alias as the group key.
660
+ const hoveredGroup = hoveredAlias ? (groupKeys[hoveredAlias] ?? hoveredAlias) : null;
469
661
 
470
662
  // --- Round 103 (issue #81): fullscreen + zoom + pan interaction layer ---
471
663
  // DIY native (no d3 / svg-pan-zoom): wrap the topology content in a single
@@ -485,6 +677,25 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
485
677
  // Issue #100: singleton chat popover. One alias at a time — clicking
486
678
  // another node swaps the target and the conversation switches in place.
487
679
  const [chatAlias, setChatAlias] = useState<string | null>(null);
680
+ // Round 14 / Loop: one-shot click ripple — fades outward from the clicked
681
+ // node so the click registers physically before the popover snaps in.
682
+ // Pairs with the Round 11 chat-focus ring: ripple expands, ring locks on.
683
+ // Keyed by ts so re-clicking the same node remounts the <circle> and the
684
+ // SMIL <animate> replays. Cleared 600ms after click (longer than the
685
+ // 500ms animation so a final frame at opacity 0 is still in the tree).
686
+ const [clickRipple, setClickRipple] = useState<{
687
+ ts: number; x: number; y: number; r0: number; color: string;
688
+ } | null>(null);
689
+ // #84: when a node is renamed while its chat popover is open, follow the
690
+ // rename so the conversation keeps targeting a live alias.
691
+ useEffect(() => {
692
+ if (renameSignal && renameSignal.from === chatAlias) {
693
+ setChatAlias(renameSignal.to);
694
+ }
695
+ // chatAlias intentionally omitted — only react to a new rename signal,
696
+ // not to the user opening/closing the popover.
697
+ // eslint-disable-next-line react-hooks/exhaustive-deps
698
+ }, [renameSignal]);
488
699
 
489
700
  useEffect(() => { viewRef.current = view; }, [view]);
490
701
 
@@ -524,6 +735,14 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
524
735
  const svg = svgRef.current;
525
736
  if (!svg) return;
526
737
  const onWheel = (e: WheelEvent) => {
738
+ // Round 23 / Loop: only zoom on Ctrl/Meta+wheel when inline; plain
739
+ // wheel over the canvas keeps scrolling the page (the topo sits at
740
+ // the top of /; trapping page scroll mid-page is the classic
741
+ // "scroll-jail" anti-pattern). In fullscreen the canvas owns the
742
+ // viewport so any wheel zooms — no page scroll to preserve. Pinch-
743
+ // zoom on a trackpad surfaces as ctrlKey=true natively, which is
744
+ // also what we want (zoom the canvas, not the browser).
745
+ if (!isFullscreen && !e.ctrlKey && !e.metaKey) return;
527
746
  e.preventDefault();
528
747
  const rect = svg.getBoundingClientRect();
529
748
  const mx = ((e.clientX - rect.left) / rect.width) * VIEWBOX_W;
@@ -544,8 +763,17 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
544
763
  };
545
764
  svg.addEventListener('wheel', onWheel, { passive: false });
546
765
  return () => svg.removeEventListener('wheel', onWheel);
547
- }, []);
548
-
766
+ // Re-attach when the fullscreen flag toggles so the handler's
767
+ // closure picks up the new value (capture-by-closure means we'd
768
+ // otherwise read the stale boolean forever).
769
+ }, [isFullscreen]);
770
+
771
+ // Round 21 / Loop: pan-aware cursor. Static `grab` gave no tactile cue
772
+ // that the canvas was actually being dragged — "grabbing" while
773
+ // dragRef.current.active = true tells the hand it's moving the
774
+ // viewport. Mirroring dragRef.active into state is the only way to
775
+ // re-render the SVG style; the ref itself doesn't trigger renders.
776
+ const [isPanning, setIsPanning] = useState(false);
549
777
  const onPointerDown = (e: React.PointerEvent<SVGSVGElement>) => {
550
778
  if (e.button !== 0) return;
551
779
  (e.currentTarget as Element).setPointerCapture?.(e.pointerId);
@@ -556,6 +784,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
556
784
  baseX: viewRef.current.x,
557
785
  baseY: viewRef.current.y,
558
786
  };
787
+ setIsPanning(true);
559
788
  };
560
789
  const onPointerMove = (e: React.PointerEvent<SVGSVGElement>) => {
561
790
  const d = dragRef.current;
@@ -570,6 +799,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
570
799
  const onPointerUp = (e: React.PointerEvent<SVGSVGElement>) => {
571
800
  if (!dragRef.current.active) return;
572
801
  dragRef.current.active = false;
802
+ setIsPanning(false);
573
803
  try {
574
804
  (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);
575
805
  } catch {}
@@ -587,6 +817,30 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
587
817
  };
588
818
  const resetView = () => setView({ zoom: 1, x: 0, y: 0 });
589
819
 
820
+ // Round 22 / Loop: keyboard zoom — +/= zoom in, - zoom out, 0 reset.
821
+ // Listen on window so the user doesn't need to focus the SVG first,
822
+ // but only act when no text input has focus (otherwise typing "-" in
823
+ // a chat would zoom the topology — surprising and bad). Modifier keys
824
+ // pass through so Cmd/Ctrl combos still hit their owners. The button
825
+ // titles below document these shortcuts so users discover them.
826
+ useEffect(() => {
827
+ const onKey = (e: KeyboardEvent) => {
828
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
829
+ const ae = document.activeElement as HTMLElement | null;
830
+ if (ae) {
831
+ const tag = ae.tagName;
832
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || ae.isContentEditable) return;
833
+ }
834
+ if (e.key === '+' || e.key === '=') { zoomBy(1.2); e.preventDefault(); }
835
+ else if (e.key === '-' || e.key === '_') { zoomBy(1 / 1.2); e.preventDefault(); }
836
+ else if (e.key === '0') { resetView(); e.preventDefault(); }
837
+ };
838
+ window.addEventListener('keydown', onKey);
839
+ return () => window.removeEventListener('keydown', onKey);
840
+ // zoomBy / resetView are stable wrt setView callback; safe to omit deps
841
+ // eslint-disable-next-line react-hooks/exhaustive-deps
842
+ }, []);
843
+
590
844
  const toggleFullscreen = () => {
591
845
  const el = containerRef.current;
592
846
  if (!el) return;
@@ -632,6 +886,19 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
632
886
  <span className="px-2.5 py-1 rounded-md bg-cyan-500/10 text-cyan-300 border border-cyan-500/20">
633
887
  {onlineNodes.length} online
634
888
  </span>
889
+ {vendorDist.length > 1 && (
890
+ <span
891
+ className="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"
892
+ title="Fleet by model vendor"
893
+ >
894
+ {vendorDist.map(v => (
895
+ <span key={v.initial} className="inline-flex items-baseline gap-0.5">
896
+ <span style={{ color: v.color }}>{v.initial}</span>
897
+ <span className="text-gray-500">:{v.count}</span>
898
+ </span>
899
+ ))}
900
+ </span>
901
+ )}
635
902
  <span className="px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20">
636
903
  {flowLinks.length} active links
637
904
  </span>
@@ -654,7 +921,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
654
921
  onPointerMove={onPointerMove}
655
922
  onPointerUp={onPointerUp}
656
923
  onPointerLeave={onPointerUp}
657
- style={{ cursor: 'grab', touchAction: 'none' }}
924
+ style={{ cursor: isPanning ? 'grabbing' : 'grab', touchAction: 'none' }}
658
925
  >
659
926
  <defs>
660
927
  <linearGradient id="topo-panel" x1="0" x2="1" y1="0" y2="1">
@@ -676,9 +943,37 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
676
943
  </feMerge>
677
944
  </filter>
678
945
  )}
679
- <marker id="topo-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
680
- <path d="M 0 0 L 10 5 L 0 10 z" fill={pal.arrowFill} />
681
- </marker>
946
+ {/* Round 16 / Loop: 3-tier flow-link arrow markers.
947
+ The single marker had `markerUnits` defaulting to
948
+ `strokeWidth`, so heavy edges (stroke=7) rendered 35-user-
949
+ unit arrowheads — visually dominant, the head outweighed
950
+ the line. Switching to `userSpaceOnUse` decouples arrow
951
+ size from stroke; binning by count gives a clearer
952
+ hierarchy than continuous linear scaling:
953
+ s (count 1-2) → 12 user units
954
+ m (count 3-4) → 16 user units (alias for `topo-arrow`)
955
+ l (count 5+) → 22 user units
956
+ `topo-arrow` stays bound to the medium tier so the legend
957
+ swatch (line ~1500) renders without change. */}
958
+ {[
959
+ { id: 'topo-arrow-s', size: 12 },
960
+ { id: 'topo-arrow', size: 16 },
961
+ { id: 'topo-arrow-l', size: 22 },
962
+ ].map(m => (
963
+ <marker
964
+ key={m.id}
965
+ id={m.id}
966
+ viewBox="0 0 10 10"
967
+ refX="8"
968
+ refY="5"
969
+ markerWidth={m.size}
970
+ markerHeight={m.size}
971
+ markerUnits="userSpaceOnUse"
972
+ orient="auto-start-reverse"
973
+ >
974
+ <path d="M 0 0 L 10 5 L 0 10 z" fill={pal.arrowFill} />
975
+ </marker>
976
+ ))}
682
977
  {/* Round 45: radial radar sweep gradient — bright at center
683
978
  (where it meets the hub) fading to leading edge. */}
684
979
  <radialGradient id="topo-sweep" cx="0%" cy="50%" r="100%">
@@ -806,16 +1101,89 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
806
1101
  );
807
1102
  })}
808
1103
 
1104
+ {/* #111: prefix-group boundary boxes (Vincent 4722). Grid layout
1105
+ only — groupBoxes is empty in ring mode. Rendered behind the
1106
+ flow links + nodes; pointer-events off so they never intercept
1107
+ a node click. Restrained dashed container + group-name label. */}
1108
+ {groupBoxes.map(box => {
1109
+ const isHovered = hoveredGroup === box.key;
1110
+ // Round 18 / Loop: group-box hover linkage. The Round 8 fade
1111
+ // already dropped OUT-of-focus groups to 0.28, but the IN-focus
1112
+ // group sat at its baseline appearance — no positive emphasis.
1113
+ // Hovering now upgrades the box to an "accent" treatment:
1114
+ // solid stroke (not dashed), thicker, accent-coloured; brighter
1115
+ // text and slightly stronger fill. Label and box read as one
1116
+ // selected unit. Geometry unchanged → overlap test untouched.
1117
+ return (
1118
+ <g
1119
+ key={`grp-${box.key}`}
1120
+ data-group={box.key}
1121
+ className="transition-opacity"
1122
+ style={{ pointerEvents: 'none', opacity: !hoveredGroup || isHovered ? 1 : 0.28 }}
1123
+ >
1124
+ <rect
1125
+ x={box.x}
1126
+ y={box.y}
1127
+ width={box.w}
1128
+ height={box.h}
1129
+ rx="14"
1130
+ fill={isLight ? '#0f172a' : '#a5b4fc'}
1131
+ fillOpacity={isHovered ? (isLight ? 0.05 : 0.09) : (isLight ? 0.025 : 0.045)}
1132
+ stroke={isHovered ? pal.legendAccent : pal.ringStroke}
1133
+ strokeWidth={isHovered ? 2 : 1.5}
1134
+ strokeDasharray={isHovered ? 'none' : '6 6'}
1135
+ style={{ transition: 'stroke 200ms ease-out, stroke-width 200ms ease-out, fill-opacity 200ms ease-out' }}
1136
+ />
1137
+ <text
1138
+ x={box.x + 12}
1139
+ y={box.y + 14}
1140
+ fill={isHovered ? pal.legendHeadline : pal.legendText}
1141
+ fontSize="13"
1142
+ fontFamily="monospace"
1143
+ fontWeight="700"
1144
+ style={{ transition: 'fill 200ms ease-out' }}
1145
+ >
1146
+ {box.key}
1147
+ {/* Round 19 / Loop: member-count chip. Inline tspan stays
1148
+ in the single <text> bbox the overlap test reads, so
1149
+ the node↔label guard still catches if the chip ever
1150
+ pushes the label far enough right to clip a node.
1151
+ Smaller + lighter weight reads as metadata, not name. */}
1152
+ <tspan dx="6" fill={pal.legendText} fontSize="11" fontWeight="400">· {box.count}</tspan>
1153
+ </text>
1154
+ </g>
1155
+ );
1156
+ })}
1157
+
809
1158
  {/* directed message flows */}
810
1159
  {flowLinks.map((link, index) => {
811
1160
  const from = nodePositions[link.from];
812
1161
  const to = nodePositions[link.to];
813
1162
  if (!from || !to) return null;
814
1163
 
815
- const lift = index % 2 === 0 ? 36 : -36;
1164
+ // Round 7 / Loop: lift now scales with distance so short links
1165
+ // aren't over-bent (long links keep the ~36px hump), and the
1166
+ // particle period shortens with link.count so busier edges
1167
+ // visibly pulse faster — instant info density on top of the
1168
+ // existing stroke-width-by-count chip.
1169
+ const dist = Math.hypot(to.x - from.x, to.y - from.y);
1170
+ const lift = (index % 2 === 0 ? 1 : -1) * Math.min(36, dist * 0.18);
816
1171
  const path = curvePath(from, to, lift);
817
1172
  const width = Math.min(2 + link.count, 7);
818
- const duration = 2.2 + (index % 5) * 0.32;
1173
+ const duration = Math.max(0.9, 2.6 / Math.sqrt(link.count));
1174
+ // Round 10 / Loop: freshness fade. An edge that fired ≤30s ago
1175
+ // stays at full intensity; over 5 minutes it decays to ~35%.
1176
+ // Surfaces "what's happening now" vs background chatter without
1177
+ // hiding old flow entirely (some context still useful). `now`
1178
+ // captured at useMemo-recompute time (every 5s message refresh)
1179
+ // — accuracy is within the poll interval, plenty.
1180
+ const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;
1181
+ const fresh = Math.max(0.35, 1 - ageMs / (5 * 60 * 1000));
1182
+ // Round 16 arrow-tier binning — keep `topo-arrow` as the
1183
+ // medium tier id so the legend swatch picks it up unchanged.
1184
+ const arrowId = link.count <= 2 ? 'topo-arrow-s'
1185
+ : link.count <= 4 ? 'topo-arrow'
1186
+ : 'topo-arrow-l';
819
1187
 
820
1188
  return (
821
1189
  <g key={link.key}>
@@ -824,9 +1192,10 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
824
1192
  fill="none"
825
1193
  stroke={pal.flowEdge}
826
1194
  strokeWidth={width}
827
- opacity={isLight ? 0.22 : 0.28}
1195
+ opacity={(isLight ? 0.22 : 0.28) * fresh}
828
1196
  filter={isLight ? undefined : 'url(#topo-glow)'}
829
- markerEnd="url(#topo-arrow)"
1197
+ markerEnd={`url(#${arrowId})`}
1198
+ className="transition-opacity duration-500"
830
1199
  />
831
1200
  <path
832
1201
  id={`flow-path-${index}`}
@@ -835,37 +1204,37 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
835
1204
  stroke={pal.flowPath}
836
1205
  strokeWidth="1"
837
1206
  strokeDasharray="2 12"
838
- opacity={isLight ? 0.4 : 0.75}
1207
+ opacity={(isLight ? 0.4 : 0.75) * fresh}
1208
+ className="transition-opacity duration-500"
839
1209
  />
840
- <circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'}>
1210
+ <circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'} opacity={fresh}>
841
1211
  <animateMotion dur={`${duration}s`} repeatCount="indefinite" path={path} />
842
1212
  </circle>
843
1213
  </g>
844
1214
  );
845
1215
  })}
846
1216
 
847
- {/* center hub — round 39: enlarged from r=6 (12px) to r=10 (20px)
848
- core so the "control plane" reads as the network's anchor,
849
- not a stray particle. Static halo ring at r=18 grounds it
850
- visually; two outward pulses keep the "signal source" idea
851
- from r17. Light theme gets a paler core to avoid hot-spotting
852
- the bg. */}
1217
+ {/* center hub — round 39 sized + round 13 restraint.
1218
+ The hub is the control-plane anchor. r39 gave it two outward
1219
+ pulses (r 10→38, 2.4s, double-phase) which read as "loudest
1220
+ node in the room" wrong semantics: anchors don't emit,
1221
+ they hold. r13 swaps the kinetic pulse for a slow opacity
1222
+ breath on the static halo (4s, ±15% from base), so the hub
1223
+ stays alive without throwing kinetic energy outward. The
1224
+ dual-circle "lit lamp" core is unchanged. */}
853
1225
  {layout === 'ring' && (<g>
854
- {/* static grounding halo — sits underneath, low opacity */}
1226
+ {/* grounding halo — now breathes in opacity, no expansion */}
855
1227
  <circle
856
1228
  cx={cx} cy={cy} r="18"
857
1229
  fill={isLight ? '#d1fae5' : '#10b981'}
858
- opacity={isLight ? 0.35 : 0.10}
859
- />
860
- {/* outer pulse 1 */}
861
- <circle cx={cx} cy={cy} r="10" fill="none" stroke="#10b981" strokeWidth="1.5">
862
- <animate attributeName="r" values="10;38;10" dur="2.4s" repeatCount="indefinite" />
863
- <animate attributeName="opacity" values="0.55;0;0.55" dur="2.4s" repeatCount="indefinite" />
864
- </circle>
865
- {/* outer pulse 2 (delayed 1.2s) */}
866
- <circle cx={cx} cy={cy} r="10" fill="none" stroke="#10b981" strokeWidth="1.5" opacity="0">
867
- <animate attributeName="r" values="10;38;10" dur="2.4s" begin="1.2s" repeatCount="indefinite" />
868
- <animate attributeName="opacity" values="0.45;0;0.45" dur="2.4s" begin="1.2s" repeatCount="indefinite" />
1230
+ opacity={isLight ? 0.42 : 0.12}
1231
+ >
1232
+ <animate
1233
+ attributeName="opacity"
1234
+ values={isLight ? '0.32;0.52;0.32' : '0.08;0.16;0.08'}
1235
+ dur="4s"
1236
+ repeatCount="indefinite"
1237
+ />
869
1238
  </circle>
870
1239
  {/* core — 20px diameter, larger inner highlight reads as a "lit lamp" */}
871
1240
  <circle cx={cx} cy={cy} r="10" fill={isLight ? '#059669' : '#10b981'} />
@@ -873,7 +1242,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
873
1242
  </g>)}
874
1243
 
875
1244
  {/* agent nodes */}
876
- {[...onlineNodes, ...offlineNodes].map(session => {
1245
+ {[...onlineNodes, ...offlineNodes].map((session, nodeIdx) => {
877
1246
  const pos = nodePositions[session.alias];
878
1247
  if (!pos) return null;
879
1248
 
@@ -881,7 +1250,9 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
881
1250
  const isOnline = session.status !== 'offline' || !!sseCountFor;
882
1251
  const status = nodeStatus(session, isOnline, isLight);
883
1252
  const isActive = activeAliases.has(session.alias);
884
- const radius = isOnline ? 26 : 18;
1253
+ // #113: node size scales with the S/M/L control; halos, labels,
1254
+ // badge and avatar all derive from `radius` so they follow.
1255
+ const radius = Math.round((isOnline ? 26 : 18) * nodeScale);
885
1256
  // Round 109/110 (Vincent 4582 + 4583 P0): at high node counts
886
1257
  // the 100px opaque label cards overlapped each other and
887
1258
  // covered neighbouring avatars. But hiding text entirely went
@@ -891,12 +1262,48 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
891
1262
  // occlude an avatar), and the full card appears only for the
892
1263
  // hovered node or once zoomed in past 1.4×.
893
1264
  const showFullLabel = !denseLayout || hoveredAlias === session.alias || view.zoom >= 1.4;
1265
+ // Round 8: when a node in another group is hovered, fade this
1266
+ // one. Same-group nodes (incl. singletons matching the hovered
1267
+ // alias) stay full. Pure visual focus, geometry unchanged.
1268
+ const inFocus = !hoveredGroup || (groupKeys[session.alias] ?? session.alias) === hoveredGroup;
894
1269
 
895
1270
  return (
896
1271
  <g
897
1272
  key={session.alias}
898
- className="transition-opacity"
899
- style={{ cursor: 'pointer' }}
1273
+ data-node={session.alias}
1274
+ // Round 3 / Loop: `anet-fade-in` runs once when the <g>
1275
+ // mounts — a new session entering the fleet (or the topology
1276
+ // first rendering) eases in instead of popping. Re-renders of
1277
+ // an existing node don't re-trigger (React preserves the <g>
1278
+ // via the alias key), so status changes don't flicker. The
1279
+ // global prefers-reduced-motion sweep already neutralises it.
1280
+ className="group transition-opacity anet-fade-in"
1281
+ style={{
1282
+ cursor: 'pointer',
1283
+ // Round 17 / Loop: offline nodes drop to 0.6 at rest so
1284
+ // online nodes pop without losing the offline-as-ghost
1285
+ // information. The dashed stroke + smaller radius already
1286
+ // say "offline"; dimming the whole group strengthens the
1287
+ // online-vs-offline hierarchy at first glance. Exempt the
1288
+ // chat-focused node — if the user explicitly opened a
1289
+ // popover targeting an offline alias, that node stays
1290
+ // full-brightness so the focus ring + popover read as one
1291
+ // selected thing rather than a dimmed selection. Group-
1292
+ // hover fade (Round 8) still wins when inFocus is false.
1293
+ opacity: !inFocus
1294
+ ? 0.32
1295
+ : chatAlias === session.alias
1296
+ ? 1
1297
+ : isOnline ? 1 : 0.6,
1298
+ // Round 9 / Loop: stagger the anet-fade-in so the topology
1299
+ // reveals as a wave on first paint instead of one big pop.
1300
+ // Cap at 24 indices (≈600ms tail) so 50-node fleets still
1301
+ // finish revealing within a beat. CSS animation-delay only
1302
+ // applies during the keyframe — re-renders without a new
1303
+ // mount (same alias key) don't replay, so status changes
1304
+ // never trigger the stagger again.
1305
+ animationDelay: `${Math.min(nodeIdx, 24) * 25}ms`,
1306
+ }}
900
1307
  // Stop the pointerdown from reaching the SVG pan handler: the
901
1308
  // SVG calls setPointerCapture, and a captured pointer makes
902
1309
  // Chromium fire the follow-up `click` on the SVG instead of
@@ -904,17 +1311,86 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
904
1311
  // Side effect (intended): you pan from empty canvas, not by
905
1312
  // grabbing a node.
906
1313
  onPointerDown={(e) => e.stopPropagation()}
907
- onPointerEnter={() => denseLayout && setHoveredAlias(session.alias)}
908
- onPointerLeave={() => denseLayout && setHoveredAlias(prev => (prev === session.alias ? null : prev))}
909
- onClick={() => setChatAlias(session.alias)}
1314
+ // Track hover globally (not just under denseLayout) so the
1315
+ // Round 8 group-focus fade works at any fleet size.
1316
+ onPointerEnter={() => setHoveredAlias(session.alias)}
1317
+ onPointerLeave={() => setHoveredAlias(prev => (prev === session.alias ? null : prev))}
1318
+ onClick={() => {
1319
+ setChatAlias(session.alias);
1320
+ // Round 14 ripple — capture position, radius and status
1321
+ // colour at click time so the one-shot circle is self-
1322
+ // contained (no re-render-time recomputation needed).
1323
+ setClickRipple({
1324
+ ts: Date.now(),
1325
+ x: pos.x, y: pos.y, r0: radius,
1326
+ color: status.primary,
1327
+ });
1328
+ setTimeout(() => setClickRipple(prev =>
1329
+ prev && Date.now() - prev.ts >= 590 ? null : prev), 600);
1330
+ }}
910
1331
  >
1332
+ {/* Issue #96: native hover tooltip — "Vendor · model · Runtime".
1333
+ Falls back to just the alias when the node reports no
1334
+ model/runtime. */}
1335
+ <title>{identityLine(session.model, session.runtime) || session.alias}</title>
1336
+ {/* Round 2 / Loop: hover ring — a thin outer stroke that fades
1337
+ in when the cursor enters the node, signalling clickability
1338
+ (real-user feedback for the chat-popover open). Pure CSS via
1339
+ Tailwind group-hover, so it costs nothing per frame and
1340
+ respects prefers-reduced-motion via the global media query. */}
1341
+ <circle
1342
+ cx={pos.x}
1343
+ cy={pos.y}
1344
+ r={radius + 12}
1345
+ fill="none"
1346
+ stroke={status.primary}
1347
+ // strokeWidth must NOT be 1.5 (offline status ring) or 3
1348
+ // (online status ring) — the overlap test selects by those
1349
+ // exact widths and would mis-count this invisible hover
1350
+ // ring as a node footprint.
1351
+ strokeWidth="2"
1352
+ className="opacity-0 group-hover:opacity-70 transition-opacity duration-150"
1353
+ style={{ pointerEvents: 'none' }}
1354
+ />
1355
+ {/* Round 11 / Loop: chat-focus ring — when the ChatPopover is
1356
+ open targeting this node, anchor a persistent ring around
1357
+ it so the floating popover visibly links back to its source
1358
+ node. Static (not pulsing) so it reads as "selected state"
1359
+ rather than "this node is active". strokeWidth=2.5 stays
1360
+ clear of the overlap-test selectors (1.5 / 3). Sits just
1361
+ outside the halo radius+8 so it never overlaps a neighbour
1362
+ (halos already pack flush in dense grids). */}
1363
+ {chatAlias === session.alias && (
1364
+ <circle
1365
+ cx={pos.x}
1366
+ cy={pos.y}
1367
+ r={radius + 14}
1368
+ fill="none"
1369
+ stroke={status.primary}
1370
+ strokeWidth="2.5"
1371
+ opacity={isLight ? 0.85 : 0.95}
1372
+ filter={!isLight ? 'url(#topo-glow)' : undefined}
1373
+ className="transition-opacity duration-200"
1374
+ style={{ pointerEvents: 'none' }}
1375
+ />
1376
+ )}
911
1377
  {isActive && (
912
1378
  <circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>
913
1379
  <animate attributeName="r" values={`${radius + 8};${radius + 22};${radius + 8}`} dur="2.4s" repeatCount="indefinite" />
914
1380
  <animate attributeName="opacity" values={isLight ? '0.12;0.02;0.12' : '0.18;0.04;0.18'} dur="2.4s" repeatCount="indefinite" />
915
1381
  </circle>
916
1382
  )}
917
- <circle cx={pos.x} cy={pos.y} r={radius + 8} fill={status.halo} opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.4 : 0.25)} />
1383
+ {/* Round 4 / Loop: transition-[fill,stroke,opacity] smooths
1384
+ status colour changes so idle↔working↔offline doesn't snap
1385
+ — task replies / node-rename / SSE updates ease in. */}
1386
+ <circle
1387
+ cx={pos.x}
1388
+ cy={pos.y}
1389
+ r={radius + 8}
1390
+ fill={status.halo}
1391
+ opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.4 : 0.25)}
1392
+ className="transition-[fill,opacity] duration-300 ease-out"
1393
+ />
918
1394
  <circle
919
1395
  cx={pos.x}
920
1396
  cy={pos.y}
@@ -924,34 +1400,28 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
924
1400
  strokeWidth={isOnline ? 3 : 1.5}
925
1401
  strokeDasharray={isOnline ? 'none' : '5 5'}
926
1402
  filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}
1403
+ className="transition-[fill,stroke] duration-300 ease-out"
927
1404
  />
928
- {/* Round 99 (issue #79): node "avatar" hue-hashed
929
- initials circle inside the status ring. Round 100:
930
- when brand=intern, swap the initials for the 书小生
931
- mascot image (asset from 群星马 task 51dd0d1d). The
932
- image already has a transparent bg so it sits cleanly
933
- on the node's dark/light fill; the outer status ring
934
- still carries working/idle/etc. */}
1405
+ {/* Issue #96: node "avatar" is now driven by the model
1406
+ vendor. Decision order:
1407
+ 1. ?brand=intern flag, or an intern-aliased node with
1408
+ no model field 书生 coin (preserves #79).
1409
+ 2. vendor has a packaged logo asset that logo image
1410
+ (intern-s1-* models land here via vendorForModel).
1411
+ 3. known vendor, logo asset not shipped yet → a
1412
+ vendor-tinted monogram (spec-mandated fallback).
1413
+ 4. unknown vendor / null model → the prefix-group
1414
+ hue-hashed initial (#83/#99 behaviour, unchanged). */}
935
1415
  {(() => {
936
- const ar = isOnline ? 14 : 10;
937
- // Round 108 (issue #79 reopened): show the 书小生 avatar
938
- // coin for any agent whose alias marks it as an Intern /
939
- // 书生 runtime — not only under the global ?brand=intern
940
- // flag. Vincent 4565: a fleet of 书生N号 nodes should SHOW
941
- // 书生, not a generic "书" initial. ?brand=intern still
942
- // forces the coin on every node (full brand showcase).
943
- const isInternNode = isIntern || /书生|书小生|intern/i.test(session.alias);
944
- if (isInternNode) {
945
- // Round 102: self-contained "avatar coin" — 书小生 figure
946
- // keyed off its white background and composited onto a
947
- // cream circular backplate. The cream coin gives the
948
- // dark-haired figure consistent contrast on BOTH the
949
- // dark cyber theme and light theme. Render at the node
950
- // diameter so the coin fills inside the status ring.
951
- const size = radius * 2;
1416
+ const ar = Math.round((isOnline ? 14 : 10) * nodeScale);
1417
+ const size = radius * 2;
1418
+ const vendor = vendorForModel(session.model);
1419
+ const internByAlias = /书生|书小生|intern/i.test(session.alias);
1420
+
1421
+ if (isIntern || internByAlias || vendor.logo) {
952
1422
  return (
953
1423
  <image
954
- href="/intern_avatar.png"
1424
+ href={vendor.logo ?? '/intern_avatar.png'}
955
1425
  x={pos.x - size / 2}
956
1426
  y={pos.y - size / 2}
957
1427
  width={size}
@@ -960,12 +1430,25 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
960
1430
  />
961
1431
  );
962
1432
  }
1433
+ if (vendor.id !== 'unknown') {
1434
+ // Known model house, logo asset not in public/vendors/
1435
+ // yet — vendor-tinted monogram stands in.
1436
+ return (
1437
+ <>
1438
+ <circle cx={pos.x} cy={pos.y} r={ar} fill={vendor.mono.bg} stroke={vendor.mono.ring} strokeWidth="1" />
1439
+ <text
1440
+ x={pos.x} y={pos.y} dy="0.34em" textAnchor="middle"
1441
+ fill={vendor.mono.text} fontSize={ar}
1442
+ fontFamily="monospace" fontWeight="700"
1443
+ >
1444
+ {vendor.initial}
1445
+ </text>
1446
+ </>
1447
+ );
1448
+ }
963
1449
  // Round 106 (issue #83): hue keyed to the prefix group,
964
- // not the full alias — every 通信* node shares one color,
965
- // every 研究员* another, so teams cluster visually even
966
- // when the tier layout spreads them across rings.
1450
+ // not the full alias — every 通信* node shares one color.
967
1451
  const c = aliasAvatarColors(groupKeys[session.alias] || session.alias);
968
- const fs = isOnline ? 14 : 10;
969
1452
  return (
970
1453
  <>
971
1454
  <circle cx={pos.x} cy={pos.y} r={ar} fill={c.bg} stroke={c.ring} strokeWidth="1" />
@@ -975,7 +1458,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
975
1458
  dy="0.34em"
976
1459
  textAnchor="middle"
977
1460
  fill={c.text}
978
- fontSize={fs}
1461
+ fontSize={ar}
979
1462
  fontFamily="monospace"
980
1463
  fontWeight="700"
981
1464
  >
@@ -984,12 +1467,32 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
984
1467
  </>
985
1468
  );
986
1469
  })()}
1470
+ {/* Issue #96: runtime badge — small corner glyph marking the
1471
+ execution shell (CLI / SDK / HTTP API). Sits bottom-right
1472
+ of the avatar; colours kept off the working/idle/offline
1473
+ status hues. Absent when the node reports no runtime. */}
1474
+ {(() => {
1475
+ const rt = runtimeIdentity(session.runtime);
1476
+ if (!rt) return null;
1477
+ const br = isOnline ? 7 : 5.5;
1478
+ const bx = pos.x + radius * 0.72;
1479
+ const by = pos.y + radius * 0.72;
1480
+ const icon = br * 2 * 0.62;
1481
+ return (
1482
+ <g style={{ pointerEvents: 'none' }}>
1483
+ <circle cx={bx} cy={by} r={br} fill={pal.containerBg} stroke={rt.color} strokeWidth="1.5" />
1484
+ <g transform={`translate(${bx - icon / 2} ${by - icon / 2}) scale(${icon / 24})`}>
1485
+ <path d={rt.iconPath} fill="none" stroke={rt.color} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
1486
+ </g>
1487
+ </g>
1488
+ );
1489
+ })()}
987
1490
  {session.status === 'working' && (
988
1491
  // working pulse — small dot just inside the status ring,
989
1492
  // above the avatar text. Was a 18px horizontal bar at
990
1493
  // y+11 which now collides with the avatar; the dot reads
991
1494
  // as the same "active" cue without occluding the initial.
992
- <circle cx={pos.x} cy={pos.y - (isOnline ? 18 : 13)} r="2.5" fill={pal.flowParticle}>
1495
+ <circle cx={pos.x} cy={pos.y - (radius - 6)} r="2.5" fill={pal.flowParticle}>
993
1496
  <animate attributeName="opacity" values="1;0.25;1" dur="1.1s" repeatCount="indefinite" />
994
1497
  </circle>
995
1498
  )}
@@ -998,61 +1501,112 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
998
1501
  Round 109/110 (Vincent P0): full opaque card below the
999
1502
  density threshold / on hover / when zoomed; otherwise a
1000
1503
  lightweight plain-text alias that keeps every node
1001
- labelled without an opaque box covering its neighbours. */}
1002
- {showFullLabel ? (
1003
- <g transform={`translate(${pos.x}, ${pos.y + radius + 22})`} style={{ pointerEvents: 'none' }}>
1004
- <rect x="-50" y="-14" width="100" height="42" rx="6" fill={pal.labelBox.fill} stroke={pal.labelBox.stroke} opacity={isLight ? 1 : 0.94} />
1005
- <text x="0" y="1" textAnchor="middle" fill={status.text} fontSize="12" fontFamily="monospace" fontWeight="700">
1006
- {truncate(session.alias, 12)}
1007
- </text>
1008
- <text x="0" y="17" textAnchor="middle" fill={status.primary} fontSize="9" fontFamily="monospace">
1009
- {status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}
1504
+ labelled without an opaque box covering its neighbours.
1505
+ Round 15 / Loop: when nodeScale=S the node shrinks 30%
1506
+ but the label card was staying full-size, so the label
1507
+ visually outweighed the small node. Tighten the card
1508
+ frame, alias / sub fontSize, drop-offset and truncate
1509
+ length specifically for S; M and L keep their existing
1510
+ sizes (M ≈ L for labels by design — the S user is the
1511
+ one who explicitly asked for a denser view). */}
1512
+ {(() => {
1513
+ const isSmall = nodeScale < 0.8;
1514
+ const cardW = isSmall ? 88 : 100;
1515
+ const cardH = isSmall ? 36 : 42;
1516
+ const cardTopY = isSmall ? -12 : -14;
1517
+ const aliasFs = isSmall ? 11 : 12;
1518
+ const subFs = isSmall ? 8 : 9;
1519
+ const subY = isSmall ? 15 : 17;
1520
+ const dropY = isSmall ? 18 : 22;
1521
+ const fullMax = isSmall ? 11 : 12;
1522
+ const denseFs = isSmall ? 9 : 10;
1523
+ const denseDrop = isSmall ? 12 : 14;
1524
+ return showFullLabel ? (
1525
+ <g transform={`translate(${pos.x}, ${pos.y + radius + dropY})`} style={{ pointerEvents: 'none' }}>
1526
+ <rect x={-cardW / 2} y={cardTopY} width={cardW} height={cardH} rx="6" fill={pal.labelBox.fill} stroke={pal.labelBox.stroke} opacity={isLight ? 1 : 0.94} />
1527
+ <text x="0" y="1" textAnchor="middle" fill={status.text} fontSize={aliasFs} fontFamily="monospace" fontWeight="700">
1528
+ {truncate(session.alias, fullMax)}
1529
+ </text>
1530
+ <text x="0" y={subY} textAnchor="middle" fill={status.primary} fontSize={subFs} fontFamily="monospace">
1531
+ {status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}
1532
+ </text>
1533
+ </g>
1534
+ ) : (
1535
+ <text
1536
+ x={pos.x}
1537
+ y={pos.y + radius + denseDrop}
1538
+ textAnchor="middle"
1539
+ fill={status.text}
1540
+ fontSize={denseFs}
1541
+ fontFamily="monospace"
1542
+ fontWeight="700"
1543
+ opacity={0.9}
1544
+ style={{ pointerEvents: 'none', paintOrder: 'stroke' }}
1545
+ stroke={pal.containerBg}
1546
+ strokeWidth="3"
1547
+ >
1548
+ {truncate(session.alias, isSmall ? 9 : 10)}
1010
1549
  </text>
1011
- </g>
1012
- ) : (
1013
- <text
1014
- x={pos.x}
1015
- y={pos.y + radius + 14}
1016
- textAnchor="middle"
1017
- fill={status.text}
1018
- fontSize="10"
1019
- fontFamily="monospace"
1020
- fontWeight="700"
1021
- opacity={0.9}
1022
- style={{ pointerEvents: 'none', paintOrder: 'stroke' }}
1023
- stroke={pal.containerBg}
1024
- strokeWidth="3"
1025
- >
1026
- {truncate(session.alias, 10)}
1027
- </text>
1028
- )}
1550
+ );
1551
+ })()}
1029
1552
  </g>
1030
1553
  );
1031
1554
  })}
1032
1555
 
1556
+ {/* Round 14 / Loop: click ripple — one-shot expanding ring from
1557
+ the clicked node, ~500ms, status-coloured. Sits inside the
1558
+ zoom/pan <g> so it scales / pans with the topology. Keyed by
1559
+ ts so a re-click on any node (same or different) remounts the
1560
+ <circle> and the SMIL <animate> elements replay from t=0.
1561
+ strokeWidth=2 doesn't match the overlap-test selectors. */}
1562
+ {clickRipple && (
1563
+ <circle
1564
+ key={clickRipple.ts}
1565
+ cx={clickRipple.x}
1566
+ cy={clickRipple.y}
1567
+ r={clickRipple.r0 + 4}
1568
+ fill="none"
1569
+ stroke={clickRipple.color}
1570
+ strokeWidth="2"
1571
+ opacity="0"
1572
+ style={{ pointerEvents: 'none' }}
1573
+ >
1574
+ <animate attributeName="r" values={`${clickRipple.r0 + 4};${clickRipple.r0 + 30}`} dur="0.5s" fill="freeze" />
1575
+ <animate attributeName="opacity" values="0.7;0" dur="0.5s" fill="freeze" />
1576
+ </circle>
1577
+ )}
1578
+
1579
+ </g>
1580
+
1581
+ {/* #112: overlay panels (recent-signal + legend) render OUTSIDE the
1582
+ zoom/pan <g> so they stay fixed while the topology pans/zooms.
1583
+ They're sized + tucked into the top corners so every corner of
1584
+ each panel is >325px from the canvas centre — i.e. fully outside
1585
+ the outermost (offline) ring. No node on any ring can reach the
1586
+ corner triangles, so the panels never overlap a node, in ring
1587
+ OR grid layout (Vincent 4727 zero-overlap criterion). */}
1033
1588
  {/* latest flow labels */}
1034
- <g transform="translate(28, 34)">
1035
- <rect x="0" y="0" width="285" height={60 + Math.min(flowLinks.length, 4) * 20} rx="8" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 1 : 0.94} />
1036
- <text x="16" y="26" fill={pal.legendHeadline} fontSize="13" fontFamily="monospace" fontWeight="700">recent signal</text>
1037
- <text x="178" y="26" fill={pal.legendAccent} fontSize="11" fontFamily="monospace">{messages.length} messages</text>
1038
- {flowLinks.slice(0, 4).map((link, index) => (
1039
- <text key={link.key} x="16" y={54 + index * 20} fill={pal.legendText} fontSize="10" fontFamily="monospace">
1040
- {truncate(link.from, 8)} {'->'} {truncate(link.to, 8)} / {link.count} / {truncate(link.content, 18)}
1589
+ <g transform="translate(16, 16)">
1590
+ <rect x="0" y="0" width="230" height="84" rx="10" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 0.97 : 0.92} />
1591
+ <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight="700">recent signal</text>
1592
+ <text x="150" y="21" fill={pal.legendAccent} fontSize="10" fontFamily="monospace">{messages.length} msgs</text>
1593
+ {flowLinks.slice(0, 3).map((link, index) => (
1594
+ <text key={link.key} x="13" y={38 + index * 16} fill={pal.legendText} fontSize="9" fontFamily="monospace">
1595
+ {truncate(link.from, 6)} {'->'} {truncate(link.to, 6)} / {link.count} / {truncate(link.content, 12)}
1041
1596
  </text>
1042
1597
  ))}
1043
1598
  </g>
1044
1599
 
1045
1600
  {/* legend */}
1046
- <g transform="translate(720, 34)">
1047
- <rect x="0" y="0" width="250" height="112" rx="8" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 1 : 0.94} />
1048
- <circle cx="18" cy="26" r="6" fill={isLight ? '#059669' : '#22c55e'} />
1049
- <text x="34" y="30" fill={pal.legendText} fontSize="11" fontFamily="monospace">working node</text>
1050
- <circle cx="18" cy="52" r="6" fill={isLight ? '#0d9488' : '#2dd4bf'} />
1051
- <text x="34" y="56" fill={pal.legendText} fontSize="11" fontFamily="monospace">online idle</text>
1052
- <circle cx="18" cy="78" r="6" fill={isLight ? '#94a3b8' : '#6b7280'} />
1053
- <text x="34" y="82" fill={pal.legendText} fontSize="11" fontFamily="monospace">offline / no SSE</text>
1054
- <path d="M150,78 Q176,52 210,78" fill="none" stroke={pal.flowEdge} strokeWidth="3" markerEnd="url(#topo-arrow)" />
1055
- </g>
1601
+ <g transform="translate(760, 16)">
1602
+ <rect x="0" y="0" width="224" height="96" rx="10" fill={pal.legendBox.fill} stroke={pal.legendBox.stroke} opacity={isLight ? 0.97 : 0.92} />
1603
+ <circle cx="16" cy="24" r="5.5" fill={isLight ? '#059669' : '#22c55e'} />
1604
+ <text x="30" y="28" fill={pal.legendText} fontSize="11" fontFamily="monospace">working node</text>
1605
+ <circle cx="16" cy="48" r="5.5" fill={isLight ? '#0d9488' : '#2dd4bf'} />
1606
+ <text x="30" y="52" fill={pal.legendText} fontSize="11" fontFamily="monospace">online idle</text>
1607
+ <circle cx="16" cy="72" r="5.5" fill={isLight ? '#94a3b8' : '#6b7280'} />
1608
+ <text x="30" y="76" fill={pal.legendText} fontSize="11" fontFamily="monospace">offline / no SSE</text>
1609
+ <path d="M140,72 Q164,48 196,72" fill="none" stroke={pal.flowEdge} strokeWidth="3" markerEnd="url(#topo-arrow)" />
1056
1610
  </g>
1057
1611
  </svg>
1058
1612
 
@@ -1063,6 +1617,26 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1063
1617
  Split into a plain % readout + an explicit reset button with
1064
1618
  its own icon + tooltip. */}
1065
1619
  <div className="absolute bottom-3 right-3 flex items-center gap-1.5 text-xs select-none">
1620
+ {/* #113: node size — S / M / L segmented control (Vincent 4727). */}
1621
+ <div
1622
+ className="flex items-center rounded-md border overflow-hidden"
1623
+ style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder }}
1624
+ role="group"
1625
+ aria-label="Node size"
1626
+ >
1627
+ {([['S', 0.7], ['M', 0.84], ['L', 1]] as const).map(([lbl, v], idx) => (
1628
+ <button
1629
+ key={lbl}
1630
+ onClick={() => pickNodeScale(v)}
1631
+ aria-pressed={nodeScale === v}
1632
+ title={`Node size: ${lbl === 'S' ? 'small' : lbl === 'M' ? 'medium' : 'large'}`}
1633
+ className={`px-2 py-1 transition-colors ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300' : 'hover:bg-white/5'}`}
1634
+ style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}
1635
+ >
1636
+ {lbl}
1637
+ </button>
1638
+ ))}
1639
+ </div>
1066
1640
  <div
1067
1641
  className="flex items-center rounded-md border overflow-hidden"
1068
1642
  style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder }}
@@ -1072,6 +1646,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1072
1646
  className="px-2 py-1 hover:bg-white/5 transition-colors"
1073
1647
  style={{ color: pal.legendText }}
1074
1648
  aria-label="Zoom out"
1649
+ title="Zoom out (−)"
1075
1650
  >
1076
1651
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" aria-hidden><path d="M5 12h14" /></svg>
1077
1652
  </button>
@@ -1087,6 +1662,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1087
1662
  className="px-2 py-1 hover:bg-white/5 transition-colors"
1088
1663
  style={{ color: pal.legendText }}
1089
1664
  aria-label="Zoom in"
1665
+ title="Zoom in (+)"
1090
1666
  >
1091
1667
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" aria-hidden><path d="M12 5v14M5 12h14" /></svg>
1092
1668
  </button>
@@ -1096,7 +1672,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1096
1672
  className="p-1.5 rounded-md border hover:bg-white/5 transition-colors"
1097
1673
  style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
1098
1674
  aria-label="Reset view"
1099
- title="Reset zoom + pan (or double-click the canvas)"
1675
+ title="Reset zoom + pan (0, or double-click the canvas)"
1100
1676
  >
1101
1677
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
1102
1678
  <path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8" />