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

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 (822) 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/02t5jr4shh.hy.js +1 -0
  506. package/.next/static/chunks/039-b.v_9-_rm.js +1 -0
  507. package/.next/static/chunks/05oi25hh7b81r.js +31 -0
  508. package/.next/static/chunks/07~8sx3_5uiox.js +1 -0
  509. package/.next/static/chunks/09nd.t_hd.y6t.css +2 -0
  510. package/.next/static/chunks/{0pqt~8bl3ukh4.js → 0_pxg.4pebw__.js} +2 -2
  511. package/.next/static/chunks/0_qfg5s5uzs7..js +7 -0
  512. package/.next/static/chunks/0b8wnt4put-5n.js +2 -0
  513. package/.next/static/chunks/0elalb9n_.~di.js +1 -0
  514. package/.next/static/chunks/{01xlw8hd842-c.js → 0eov~nkwaf-qa.js} +1 -1
  515. package/.next/static/chunks/0j_q__o8p6g83.js +1 -0
  516. package/.next/static/chunks/0jl6_w50zh4l5.js +1 -0
  517. package/.next/static/chunks/{0w1zwt8kq_7nb.js → 0ku0fjqlm9mca.js} +1 -1
  518. package/.next/static/chunks/0nguf50i8enay.js +1 -0
  519. package/.next/static/chunks/{0-hlcuoemhe7w.js → 0pl7l_.2im.j0.js} +2 -2
  520. package/.next/static/chunks/0r4q73.-1bkyl.js +1 -0
  521. package/.next/static/chunks/0r_c8eccg3z2o.js +1 -0
  522. package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
  523. package/.next/static/chunks/0u3b93reo9xci.js +1 -0
  524. package/.next/static/chunks/0v5rym8-nhetp.js +1 -0
  525. package/.next/static/chunks/10oi7l-tycgl9.js +1 -0
  526. package/.next/static/chunks/11.s_mjm-snyc.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 +726 -161
  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-zoom-keys-test.mjs +89 -0
  561. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_06e.r3r._.js +0 -13
  562. package/.next/build/chunks/node_modules_13sb.px._.js.map +0 -47
  563. package/.next/server/chunks/[root-of-the-server]__0-mykaf._.js +0 -3
  564. package/.next/server/chunks/[root-of-the-server]__0-mykaf._.js.map +0 -1
  565. package/.next/server/chunks/[root-of-the-server]__0.d_kqy._.js +0 -3
  566. package/.next/server/chunks/[root-of-the-server]__0.d_kqy._.js.map +0 -1
  567. package/.next/server/chunks/[root-of-the-server]__02pocyg._.js +0 -3
  568. package/.next/server/chunks/[root-of-the-server]__02pocyg._.js.map +0 -1
  569. package/.next/server/chunks/[root-of-the-server]__03ueci3._.js +0 -3
  570. package/.next/server/chunks/[root-of-the-server]__04aud7x._.js +0 -3
  571. package/.next/server/chunks/[root-of-the-server]__04aud7x._.js.map +0 -1
  572. package/.next/server/chunks/[root-of-the-server]__05fnt3n._.js +0 -3
  573. package/.next/server/chunks/[root-of-the-server]__05fnt3n._.js.map +0 -1
  574. package/.next/server/chunks/[root-of-the-server]__077tsd.._.js +0 -20
  575. package/.next/server/chunks/[root-of-the-server]__077tsd.._.js.map +0 -1
  576. package/.next/server/chunks/[root-of-the-server]__08wy1jr._.js +0 -3
  577. package/.next/server/chunks/[root-of-the-server]__08wy1jr._.js.map +0 -1
  578. package/.next/server/chunks/[root-of-the-server]__0adhu1m._.js +0 -3
  579. package/.next/server/chunks/[root-of-the-server]__0adhu1m._.js.map +0 -1
  580. package/.next/server/chunks/[root-of-the-server]__0aqv02v._.js +0 -3
  581. package/.next/server/chunks/[root-of-the-server]__0aqv02v._.js.map +0 -1
  582. package/.next/server/chunks/[root-of-the-server]__0cawhfq._.js +0 -3
  583. package/.next/server/chunks/[root-of-the-server]__0cawhfq._.js.map +0 -1
  584. package/.next/server/chunks/[root-of-the-server]__0d_-_k.._.js +0 -3
  585. package/.next/server/chunks/[root-of-the-server]__0d_-_k.._.js.map +0 -1
  586. package/.next/server/chunks/[root-of-the-server]__0f6bxfl._.js +0 -3
  587. package/.next/server/chunks/[root-of-the-server]__0f6bxfl._.js.map +0 -1
  588. package/.next/server/chunks/[root-of-the-server]__0h15edq._.js +0 -3
  589. package/.next/server/chunks/[root-of-the-server]__0h15edq._.js.map +0 -1
  590. package/.next/server/chunks/[root-of-the-server]__0k.vegl._.js +0 -3
  591. package/.next/server/chunks/[root-of-the-server]__0k.vegl._.js.map +0 -1
  592. package/.next/server/chunks/[root-of-the-server]__0kltbce._.js +0 -3
  593. package/.next/server/chunks/[root-of-the-server]__0kltbce._.js.map +0 -1
  594. package/.next/server/chunks/[root-of-the-server]__0mn04fw._.js +0 -3
  595. package/.next/server/chunks/[root-of-the-server]__0mn04fw._.js.map +0 -1
  596. package/.next/server/chunks/[root-of-the-server]__0o29v8c._.js +0 -3
  597. package/.next/server/chunks/[root-of-the-server]__0o4qfn9._.js +0 -3
  598. package/.next/server/chunks/[root-of-the-server]__0o7ya7r._.js +0 -3
  599. package/.next/server/chunks/[root-of-the-server]__0o7ya7r._.js.map +0 -1
  600. package/.next/server/chunks/[root-of-the-server]__0p3fo6l._.js +0 -6
  601. package/.next/server/chunks/[root-of-the-server]__0p3fo6l._.js.map +0 -1
  602. package/.next/server/chunks/[root-of-the-server]__0p6tutt._.js +0 -3
  603. package/.next/server/chunks/[root-of-the-server]__0pn3wsx._.js +0 -3
  604. package/.next/server/chunks/[root-of-the-server]__0pn3wsx._.js.map +0 -1
  605. package/.next/server/chunks/[root-of-the-server]__0rg9ifx._.js +0 -3
  606. package/.next/server/chunks/[root-of-the-server]__0x.-194._.js +0 -3
  607. package/.next/server/chunks/[root-of-the-server]__0x.-194._.js.map +0 -1
  608. package/.next/server/chunks/[root-of-the-server]__0x.a_yh._.js +0 -3
  609. package/.next/server/chunks/[root-of-the-server]__0x.a_yh._.js.map +0 -1
  610. package/.next/server/chunks/[root-of-the-server]__0~h2uqi._.js +0 -3
  611. package/.next/server/chunks/[root-of-the-server]__139~d6a._.js +0 -3
  612. package/.next/server/chunks/[root-of-the-server]__139~d6a._.js.map +0 -1
  613. package/.next/server/chunks/_next-internal_server_app_api_anet_config_route_actions_0xzy73j.js +0 -3
  614. package/.next/server/chunks/_next-internal_server_app_api_auth_login_route_actions_0zukc38.js +0 -3
  615. package/.next/server/chunks/_next-internal_server_app_api_auth_logout_route_actions_0fm~3ij.js +0 -3
  616. package/.next/server/chunks/_next-internal_server_app_api_auth_v3_route_actions_0ndzs5l.js +0 -3
  617. package/.next/server/chunks/_next-internal_server_app_api_hub_audit-log_route_actions_11k8fqt.js +0 -3
  618. package/.next/server/chunks/_next-internal_server_app_api_hub_auth_route_actions_0gs7ttk.js +0 -3
  619. package/.next/server/chunks/_next-internal_server_app_api_hub_broadcast_route_actions_0ads-kd.js +0 -3
  620. package/.next/server/chunks/_next-internal_server_app_api_hub_events_route_actions_0opktby.js +0 -3
  621. package/.next/server/chunks/_next-internal_server_app_api_hub_health_route_actions_0tzzt~x.js +0 -3
  622. package/.next/server/chunks/_next-internal_server_app_api_hub_inbox_route_actions_0xvslrg.js +0 -3
  623. package/.next/server/chunks/_next-internal_server_app_api_hub_license_route_actions_0i3tlr3.js +0 -3
  624. package/.next/server/chunks/_next-internal_server_app_api_hub_messages_route_actions_08-gi-s.js +0 -3
  625. package/.next/server/chunks/_next-internal_server_app_api_hub_networks_route_actions_02vejq2.js +0 -3
  626. package/.next/server/chunks/_next-internal_server_app_api_hub_nodes_route_actions_0nx8ryn.js +0 -3
  627. package/.next/server/chunks/_next-internal_server_app_api_hub_register_route_actions_086wi_3.js +0 -3
  628. package/.next/server/chunks/_next-internal_server_app_api_hub_send_route_actions_0-ga_ug.js +0 -3
  629. package/.next/server/chunks/_next-internal_server_app_api_hub_server-logs_route_actions_00euexv.js +0 -3
  630. package/.next/server/chunks/_next-internal_server_app_api_hub_session_route_actions_041wa62.js +0 -3
  631. package/.next/server/chunks/_next-internal_server_app_api_hub_stats_route_actions_1242~od.js +0 -3
  632. package/.next/server/chunks/_next-internal_server_app_api_hub_status_route_actions_0aml09a.js +0 -3
  633. package/.next/server/chunks/_next-internal_server_app_api_hub_task-events_route_actions_02v1tmm.js +0 -3
  634. package/.next/server/chunks/_next-internal_server_app_api_hub_tasks_route_actions_0nl77v4.js +0 -3
  635. package/.next/server/chunks/_next-internal_server_app_api_hub_tmux_route_actions_0u~aaza.js +0 -3
  636. package/.next/server/chunks/_next-internal_server_app_api_hub_tokens_route_actions_09hzf_1.js +0 -3
  637. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_095lj93.js +0 -3
  638. package/.next/server/chunks/node_modules_next_11synfn._.js +0 -10
  639. package/.next/server/chunks/node_modules_next_11synfn._.js.map +0 -1
  640. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0yubv2d.js +0 -3
  641. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0yubv2d.js.map +0 -1
  642. package/.next/server/chunks/ssr/[root-of-the-server]__014uja_._.js +0 -3
  643. package/.next/server/chunks/ssr/[root-of-the-server]__014uja_._.js.map +0 -1
  644. package/.next/server/chunks/ssr/[root-of-the-server]__02hi65f._.js +0 -3
  645. package/.next/server/chunks/ssr/[root-of-the-server]__02hi65f._.js.map +0 -1
  646. package/.next/server/chunks/ssr/[root-of-the-server]__02xqa5z._.js +0 -3
  647. package/.next/server/chunks/ssr/[root-of-the-server]__02xqa5z._.js.map +0 -1
  648. package/.next/server/chunks/ssr/[root-of-the-server]__04ph1de._.js +0 -3
  649. package/.next/server/chunks/ssr/[root-of-the-server]__04ph1de._.js.map +0 -1
  650. package/.next/server/chunks/ssr/[root-of-the-server]__05wc5-z._.js +0 -3
  651. package/.next/server/chunks/ssr/[root-of-the-server]__05wc5-z._.js.map +0 -1
  652. package/.next/server/chunks/ssr/[root-of-the-server]__06-2p1a._.js +0 -33
  653. package/.next/server/chunks/ssr/[root-of-the-server]__06-2p1a._.js.map +0 -1
  654. package/.next/server/chunks/ssr/[root-of-the-server]__08qr2ji._.js.map +0 -1
  655. package/.next/server/chunks/ssr/[root-of-the-server]__09l8xaa._.js +0 -3
  656. package/.next/server/chunks/ssr/[root-of-the-server]__09l8xaa._.js.map +0 -1
  657. package/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js +0 -19
  658. package/.next/server/chunks/ssr/[root-of-the-server]__09z7o2x._.js.map +0 -1
  659. package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.js +0 -3
  660. package/.next/server/chunks/ssr/[root-of-the-server]__0a0ffmv._.js.map +0 -1
  661. package/.next/server/chunks/ssr/[root-of-the-server]__0b7uu-_._.js +0 -3
  662. package/.next/server/chunks/ssr/[root-of-the-server]__0b7uu-_._.js.map +0 -1
  663. package/.next/server/chunks/ssr/[root-of-the-server]__0bl_wqk._.js +0 -3
  664. package/.next/server/chunks/ssr/[root-of-the-server]__0bl_wqk._.js.map +0 -1
  665. package/.next/server/chunks/ssr/[root-of-the-server]__0ft0rpu._.js +0 -3
  666. package/.next/server/chunks/ssr/[root-of-the-server]__0ft0rpu._.js.map +0 -1
  667. package/.next/server/chunks/ssr/[root-of-the-server]__0i~g-2t._.js +0 -3
  668. package/.next/server/chunks/ssr/[root-of-the-server]__0i~g-2t._.js.map +0 -1
  669. package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js +0 -3
  670. package/.next/server/chunks/ssr/[root-of-the-server]__0jl4ukn._.js.map +0 -1
  671. package/.next/server/chunks/ssr/[root-of-the-server]__0mook08._.js +0 -3
  672. package/.next/server/chunks/ssr/[root-of-the-server]__0mook08._.js.map +0 -1
  673. package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js +0 -3
  674. package/.next/server/chunks/ssr/[root-of-the-server]__0n46s~k._.js.map +0 -1
  675. package/.next/server/chunks/ssr/[root-of-the-server]__0qchqb~._.js +0 -3
  676. package/.next/server/chunks/ssr/[root-of-the-server]__0qchqb~._.js.map +0 -1
  677. package/.next/server/chunks/ssr/[root-of-the-server]__0rrm~-q._.js +0 -3
  678. package/.next/server/chunks/ssr/[root-of-the-server]__0rrm~-q._.js.map +0 -1
  679. package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js +0 -3
  680. package/.next/server/chunks/ssr/[root-of-the-server]__0w2ay2h._.js.map +0 -1
  681. package/.next/server/chunks/ssr/[root-of-the-server]__0xjish~._.js +0 -3
  682. package/.next/server/chunks/ssr/[root-of-the-server]__0xjish~._.js.map +0 -1
  683. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js +0 -3
  684. package/.next/server/chunks/ssr/[root-of-the-server]__0xm3h9x._.js.map +0 -1
  685. package/.next/server/chunks/ssr/[root-of-the-server]__0ywihy_._.js +0 -8
  686. package/.next/server/chunks/ssr/[root-of-the-server]__0ywihy_._.js.map +0 -1
  687. package/.next/server/chunks/ssr/[root-of-the-server]__12ad1n.._.js +0 -3
  688. package/.next/server/chunks/ssr/[root-of-the-server]__12ad1n.._.js.map +0 -1
  689. package/.next/server/chunks/ssr/_0nne7mz._.js +0 -3
  690. package/.next/server/chunks/ssr/_0nne7mz._.js.map +0 -1
  691. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
  692. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
  693. package/.next/server/chunks/ssr/_next-internal_server_app_admin_page_actions_0ycnu8f.js +0 -3
  694. package/.next/server/chunks/ssr/_next-internal_server_app_login_page_actions_02kefem.js +0 -3
  695. package/.next/server/chunks/ssr/_next-internal_server_app_logs_page_actions_0rvsl~d.js +0 -3
  696. package/.next/server/chunks/ssr/_next-internal_server_app_messages_page_actions_0gsqep_.js +0 -3
  697. package/.next/server/chunks/ssr/_next-internal_server_app_node_page_actions_0wd8cgl.js +0 -3
  698. package/.next/server/chunks/ssr/_next-internal_server_app_nodes_page_actions_0-.rv67.js +0 -3
  699. package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
  700. package/.next/server/chunks/ssr/_next-internal_server_app_server-logs_page_actions_0g1gb5c.js +0 -3
  701. package/.next/server/chunks/ssr/_next-internal_server_app_settings_networks_page_actions_09uw.sw.js +0 -3
  702. package/.next/server/chunks/ssr/_next-internal_server_app_settings_page_actions_0mr68ai.js +0 -3
  703. package/.next/server/chunks/ssr/_next-internal_server_app_settings_tokens_page_actions_0yi54x8.js +0 -3
  704. package/.next/server/chunks/ssr/_next-internal_server_app_tasks_[id]_page_actions_0k_hm9g.js +0 -3
  705. package/.next/server/chunks/ssr/_next-internal_server_app_tasks_page_actions_0_2o_cr.js +0 -3
  706. package/.next/server/chunks/ssr/app_0.zbtn5._.js.map +0 -1
  707. package/.next/server/chunks/ssr/app_01y-~mp._.js +0 -3
  708. package/.next/server/chunks/ssr/app_01y-~mp._.js.map +0 -1
  709. package/.next/server/chunks/ssr/app_04n78pr._.js.map +0 -1
  710. package/.next/server/chunks/ssr/app_0_agk0r._.js.map +0 -1
  711. package/.next/server/chunks/ssr/app_0aenxef._.js +0 -3
  712. package/.next/server/chunks/ssr/app_0aenxef._.js.map +0 -1
  713. package/.next/server/chunks/ssr/app_0aum_g5._.js +0 -3
  714. package/.next/server/chunks/ssr/app_0aum_g5._.js.map +0 -1
  715. package/.next/server/chunks/ssr/app_0emvkn2._.js +0 -3
  716. package/.next/server/chunks/ssr/app_0emvkn2._.js.map +0 -1
  717. package/.next/server/chunks/ssr/app_0esuljp._.js +0 -3
  718. package/.next/server/chunks/ssr/app_0esuljp._.js.map +0 -1
  719. package/.next/server/chunks/ssr/app_0izi28j._.js.map +0 -1
  720. package/.next/server/chunks/ssr/app_components_08ys5y3._.js.map +0 -1
  721. package/.next/server/chunks/ssr/app_server-logs_page_tsx_0_kpp1p._.js.map +0 -1
  722. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js +0 -3
  723. package/.next/server/chunks/ssr/app_tasks_page_tsx_0z-d6mp._.js.map +0 -1
  724. package/.next/server/chunks/ssr/node_modules_09w7yel._.js.map +0 -1
  725. package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js +0 -3
  726. package/.next/server/chunks/ssr/node_modules_0ck2~9g._.js.map +0 -1
  727. package/.next/server/chunks/ssr/node_modules_0r_.s40._.js.map +0 -1
  728. package/.next/server/chunks/ssr/node_modules_0vtdjw7._.js +0 -3
  729. package/.next/server/chunks/ssr/node_modules_0vtdjw7._.js.map +0 -1
  730. package/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js +0 -6
  731. package/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js.map +0 -1
  732. package/.next/server/chunks/ssr/node_modules_next_dist_0e1izl_._.js +0 -6
  733. package/.next/server/chunks/ssr/node_modules_next_dist_0e1izl_._.js.map +0 -1
  734. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js +0 -3
  735. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0inhx6q._.js.map +0 -1
  736. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js +0 -3
  737. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0ghu-f7.js.map +0 -1
  738. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js +0 -3
  739. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js.map +0 -1
  740. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js +0 -3
  741. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js.map +0 -1
  742. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_002l7yi.js.map +0 -1
  743. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_02suzhc.js.map +0 -1
  744. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_07uu-xq.js.map +0 -1
  745. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0d2yq5t.js.map +0 -1
  746. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dm~kj8.js.map +0 -1
  747. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0foejvu.js.map +0 -1
  748. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0l-fh0i.js.map +0 -1
  749. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0nsg22r.js.map +0 -1
  750. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0o9vt1n.js.map +0 -1
  751. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0qm1-a1.js.map +0 -1
  752. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0uccf9z.js.map +0 -1
  753. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0uv6jyc.js.map +0 -1
  754. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10~~o-h.js +0 -4
  755. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10~~o-h.js.map +0 -1
  756. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13leyhk.js +0 -4
  757. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13leyhk.js.map +0 -1
  758. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13s07li.js.map +0 -1
  759. package/.next/static/chunks/0218jmcfde~9s.js +0 -1
  760. package/.next/static/chunks/0257pdz1-imal.js +0 -2
  761. package/.next/static/chunks/02_zpo7taex9o.js +0 -7
  762. package/.next/static/chunks/06n3d~fbbtagk.js +0 -1
  763. package/.next/static/chunks/07eba1lz2u3p4.js +0 -1
  764. package/.next/static/chunks/09n9xs.6dvvo2.css +0 -2
  765. package/.next/static/chunks/0av2~0.96~d~g.js +0 -1
  766. package/.next/static/chunks/0c1bp~g7.8ru..js +0 -1
  767. package/.next/static/chunks/0e-.zgw81gela.js +0 -31
  768. package/.next/static/chunks/0g9aof0ob_fcj.js +0 -1
  769. package/.next/static/chunks/0s8jo6jypzkx5.js +0 -1
  770. package/.next/static/chunks/0u359llqm1_01.js +0 -1
  771. package/.next/static/chunks/0ubqqhhpsaez4.js +0 -1
  772. package/.next/static/chunks/0z1xslnw3jx1s.js +0 -1
  773. package/.next/static/chunks/0~qw9kxksr1v2.js +0 -1
  774. package/.next/static/chunks/12gprw3l0kvvf.js +0 -1
  775. package/.next/static/chunks/12yuo353giomx.js +0 -1
  776. package/.next/static/chunks/13j9vr8_l9ket.js +0 -1
  777. package/.next/static/chunks/14bsg3_-taiw4.js +0 -1
  778. /package/.next/build/chunks/{[root-of-the-server]__0ubbtyl._.js.map → [root-of-the-server]__0tgc-8p._.js.map} +0 -0
  779. /package/.next/build/chunks/{[turbopack-node]_transforms_postcss_ts_06e.r3r._.js.map → [turbopack-node]_transforms_postcss_ts_0r4x5ys._.js.map} +0 -0
  780. /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
  781. /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
  782. /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
  783. /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
  784. /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
  785. /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
  786. /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
  787. /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
  788. /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
  789. /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
  790. /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
  791. /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
  792. /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
  793. /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
  794. /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
  795. /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
  796. /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
  797. /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
  798. /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
  799. /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
  800. /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
  801. /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
  802. /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
  803. /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
  804. /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
  805. /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
  806. /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
  807. /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
  808. /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
  809. /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
  810. /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
  811. /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
  812. /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
  813. /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
  814. /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
  815. /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
  816. /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
  817. /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
  818. /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
  819. /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
  820. /package/.next/static/{kXeBUTKSdNooA8p2BTTfp → SjGVu8dTaZVySLlXjmaar}/_buildManifest.js +0 -0
  821. /package/.next/static/{kXeBUTKSdNooA8p2BTTfp → SjGVu8dTaZVySLlXjmaar}/_clientMiddlewareManifest.js +0 -0
  822. /package/.next/static/{kXeBUTKSdNooA8p2BTTfp → SjGVu8dTaZVySLlXjmaar}/_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
 
@@ -546,6 +757,12 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
546
757
  return () => svg.removeEventListener('wheel', onWheel);
547
758
  }, []);
548
759
 
760
+ // Round 21 / Loop: pan-aware cursor. Static `grab` gave no tactile cue
761
+ // that the canvas was actually being dragged — "grabbing" while
762
+ // dragRef.current.active = true tells the hand it's moving the
763
+ // viewport. Mirroring dragRef.active into state is the only way to
764
+ // re-render the SVG style; the ref itself doesn't trigger renders.
765
+ const [isPanning, setIsPanning] = useState(false);
549
766
  const onPointerDown = (e: React.PointerEvent<SVGSVGElement>) => {
550
767
  if (e.button !== 0) return;
551
768
  (e.currentTarget as Element).setPointerCapture?.(e.pointerId);
@@ -556,6 +773,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
556
773
  baseX: viewRef.current.x,
557
774
  baseY: viewRef.current.y,
558
775
  };
776
+ setIsPanning(true);
559
777
  };
560
778
  const onPointerMove = (e: React.PointerEvent<SVGSVGElement>) => {
561
779
  const d = dragRef.current;
@@ -570,6 +788,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
570
788
  const onPointerUp = (e: React.PointerEvent<SVGSVGElement>) => {
571
789
  if (!dragRef.current.active) return;
572
790
  dragRef.current.active = false;
791
+ setIsPanning(false);
573
792
  try {
574
793
  (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);
575
794
  } catch {}
@@ -587,6 +806,30 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
587
806
  };
588
807
  const resetView = () => setView({ zoom: 1, x: 0, y: 0 });
589
808
 
809
+ // Round 22 / Loop: keyboard zoom — +/= zoom in, - zoom out, 0 reset.
810
+ // Listen on window so the user doesn't need to focus the SVG first,
811
+ // but only act when no text input has focus (otherwise typing "-" in
812
+ // a chat would zoom the topology — surprising and bad). Modifier keys
813
+ // pass through so Cmd/Ctrl combos still hit their owners. The button
814
+ // titles below document these shortcuts so users discover them.
815
+ useEffect(() => {
816
+ const onKey = (e: KeyboardEvent) => {
817
+ if (e.ctrlKey || e.metaKey || e.altKey) return;
818
+ const ae = document.activeElement as HTMLElement | null;
819
+ if (ae) {
820
+ const tag = ae.tagName;
821
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || ae.isContentEditable) return;
822
+ }
823
+ if (e.key === '+' || e.key === '=') { zoomBy(1.2); e.preventDefault(); }
824
+ else if (e.key === '-' || e.key === '_') { zoomBy(1 / 1.2); e.preventDefault(); }
825
+ else if (e.key === '0') { resetView(); e.preventDefault(); }
826
+ };
827
+ window.addEventListener('keydown', onKey);
828
+ return () => window.removeEventListener('keydown', onKey);
829
+ // zoomBy / resetView are stable wrt setView callback; safe to omit deps
830
+ // eslint-disable-next-line react-hooks/exhaustive-deps
831
+ }, []);
832
+
590
833
  const toggleFullscreen = () => {
591
834
  const el = containerRef.current;
592
835
  if (!el) return;
@@ -632,6 +875,19 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
632
875
  <span className="px-2.5 py-1 rounded-md bg-cyan-500/10 text-cyan-300 border border-cyan-500/20">
633
876
  {onlineNodes.length} online
634
877
  </span>
878
+ {vendorDist.length > 1 && (
879
+ <span
880
+ 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"
881
+ title="Fleet by model vendor"
882
+ >
883
+ {vendorDist.map(v => (
884
+ <span key={v.initial} className="inline-flex items-baseline gap-0.5">
885
+ <span style={{ color: v.color }}>{v.initial}</span>
886
+ <span className="text-gray-500">:{v.count}</span>
887
+ </span>
888
+ ))}
889
+ </span>
890
+ )}
635
891
  <span className="px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20">
636
892
  {flowLinks.length} active links
637
893
  </span>
@@ -654,7 +910,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
654
910
  onPointerMove={onPointerMove}
655
911
  onPointerUp={onPointerUp}
656
912
  onPointerLeave={onPointerUp}
657
- style={{ cursor: 'grab', touchAction: 'none' }}
913
+ style={{ cursor: isPanning ? 'grabbing' : 'grab', touchAction: 'none' }}
658
914
  >
659
915
  <defs>
660
916
  <linearGradient id="topo-panel" x1="0" x2="1" y1="0" y2="1">
@@ -676,9 +932,37 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
676
932
  </feMerge>
677
933
  </filter>
678
934
  )}
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>
935
+ {/* Round 16 / Loop: 3-tier flow-link arrow markers.
936
+ The single marker had `markerUnits` defaulting to
937
+ `strokeWidth`, so heavy edges (stroke=7) rendered 35-user-
938
+ unit arrowheads — visually dominant, the head outweighed
939
+ the line. Switching to `userSpaceOnUse` decouples arrow
940
+ size from stroke; binning by count gives a clearer
941
+ hierarchy than continuous linear scaling:
942
+ s (count 1-2) → 12 user units
943
+ m (count 3-4) → 16 user units (alias for `topo-arrow`)
944
+ l (count 5+) → 22 user units
945
+ `topo-arrow` stays bound to the medium tier so the legend
946
+ swatch (line ~1500) renders without change. */}
947
+ {[
948
+ { id: 'topo-arrow-s', size: 12 },
949
+ { id: 'topo-arrow', size: 16 },
950
+ { id: 'topo-arrow-l', size: 22 },
951
+ ].map(m => (
952
+ <marker
953
+ key={m.id}
954
+ id={m.id}
955
+ viewBox="0 0 10 10"
956
+ refX="8"
957
+ refY="5"
958
+ markerWidth={m.size}
959
+ markerHeight={m.size}
960
+ markerUnits="userSpaceOnUse"
961
+ orient="auto-start-reverse"
962
+ >
963
+ <path d="M 0 0 L 10 5 L 0 10 z" fill={pal.arrowFill} />
964
+ </marker>
965
+ ))}
682
966
  {/* Round 45: radial radar sweep gradient — bright at center
683
967
  (where it meets the hub) fading to leading edge. */}
684
968
  <radialGradient id="topo-sweep" cx="0%" cy="50%" r="100%">
@@ -806,16 +1090,89 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
806
1090
  );
807
1091
  })}
808
1092
 
1093
+ {/* #111: prefix-group boundary boxes (Vincent 4722). Grid layout
1094
+ only — groupBoxes is empty in ring mode. Rendered behind the
1095
+ flow links + nodes; pointer-events off so they never intercept
1096
+ a node click. Restrained dashed container + group-name label. */}
1097
+ {groupBoxes.map(box => {
1098
+ const isHovered = hoveredGroup === box.key;
1099
+ // Round 18 / Loop: group-box hover linkage. The Round 8 fade
1100
+ // already dropped OUT-of-focus groups to 0.28, but the IN-focus
1101
+ // group sat at its baseline appearance — no positive emphasis.
1102
+ // Hovering now upgrades the box to an "accent" treatment:
1103
+ // solid stroke (not dashed), thicker, accent-coloured; brighter
1104
+ // text and slightly stronger fill. Label and box read as one
1105
+ // selected unit. Geometry unchanged → overlap test untouched.
1106
+ return (
1107
+ <g
1108
+ key={`grp-${box.key}`}
1109
+ data-group={box.key}
1110
+ className="transition-opacity"
1111
+ style={{ pointerEvents: 'none', opacity: !hoveredGroup || isHovered ? 1 : 0.28 }}
1112
+ >
1113
+ <rect
1114
+ x={box.x}
1115
+ y={box.y}
1116
+ width={box.w}
1117
+ height={box.h}
1118
+ rx="14"
1119
+ fill={isLight ? '#0f172a' : '#a5b4fc'}
1120
+ fillOpacity={isHovered ? (isLight ? 0.05 : 0.09) : (isLight ? 0.025 : 0.045)}
1121
+ stroke={isHovered ? pal.legendAccent : pal.ringStroke}
1122
+ strokeWidth={isHovered ? 2 : 1.5}
1123
+ strokeDasharray={isHovered ? 'none' : '6 6'}
1124
+ style={{ transition: 'stroke 200ms ease-out, stroke-width 200ms ease-out, fill-opacity 200ms ease-out' }}
1125
+ />
1126
+ <text
1127
+ x={box.x + 12}
1128
+ y={box.y + 14}
1129
+ fill={isHovered ? pal.legendHeadline : pal.legendText}
1130
+ fontSize="13"
1131
+ fontFamily="monospace"
1132
+ fontWeight="700"
1133
+ style={{ transition: 'fill 200ms ease-out' }}
1134
+ >
1135
+ {box.key}
1136
+ {/* Round 19 / Loop: member-count chip. Inline tspan stays
1137
+ in the single <text> bbox the overlap test reads, so
1138
+ the node↔label guard still catches if the chip ever
1139
+ pushes the label far enough right to clip a node.
1140
+ Smaller + lighter weight reads as metadata, not name. */}
1141
+ <tspan dx="6" fill={pal.legendText} fontSize="11" fontWeight="400">· {box.count}</tspan>
1142
+ </text>
1143
+ </g>
1144
+ );
1145
+ })}
1146
+
809
1147
  {/* directed message flows */}
810
1148
  {flowLinks.map((link, index) => {
811
1149
  const from = nodePositions[link.from];
812
1150
  const to = nodePositions[link.to];
813
1151
  if (!from || !to) return null;
814
1152
 
815
- const lift = index % 2 === 0 ? 36 : -36;
1153
+ // Round 7 / Loop: lift now scales with distance so short links
1154
+ // aren't over-bent (long links keep the ~36px hump), and the
1155
+ // particle period shortens with link.count so busier edges
1156
+ // visibly pulse faster — instant info density on top of the
1157
+ // existing stroke-width-by-count chip.
1158
+ const dist = Math.hypot(to.x - from.x, to.y - from.y);
1159
+ const lift = (index % 2 === 0 ? 1 : -1) * Math.min(36, dist * 0.18);
816
1160
  const path = curvePath(from, to, lift);
817
1161
  const width = Math.min(2 + link.count, 7);
818
- const duration = 2.2 + (index % 5) * 0.32;
1162
+ const duration = Math.max(0.9, 2.6 / Math.sqrt(link.count));
1163
+ // Round 10 / Loop: freshness fade. An edge that fired ≤30s ago
1164
+ // stays at full intensity; over 5 minutes it decays to ~35%.
1165
+ // Surfaces "what's happening now" vs background chatter without
1166
+ // hiding old flow entirely (some context still useful). `now`
1167
+ // captured at useMemo-recompute time (every 5s message refresh)
1168
+ // — accuracy is within the poll interval, plenty.
1169
+ const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;
1170
+ const fresh = Math.max(0.35, 1 - ageMs / (5 * 60 * 1000));
1171
+ // Round 16 arrow-tier binning — keep `topo-arrow` as the
1172
+ // medium tier id so the legend swatch picks it up unchanged.
1173
+ const arrowId = link.count <= 2 ? 'topo-arrow-s'
1174
+ : link.count <= 4 ? 'topo-arrow'
1175
+ : 'topo-arrow-l';
819
1176
 
820
1177
  return (
821
1178
  <g key={link.key}>
@@ -824,9 +1181,10 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
824
1181
  fill="none"
825
1182
  stroke={pal.flowEdge}
826
1183
  strokeWidth={width}
827
- opacity={isLight ? 0.22 : 0.28}
1184
+ opacity={(isLight ? 0.22 : 0.28) * fresh}
828
1185
  filter={isLight ? undefined : 'url(#topo-glow)'}
829
- markerEnd="url(#topo-arrow)"
1186
+ markerEnd={`url(#${arrowId})`}
1187
+ className="transition-opacity duration-500"
830
1188
  />
831
1189
  <path
832
1190
  id={`flow-path-${index}`}
@@ -835,37 +1193,37 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
835
1193
  stroke={pal.flowPath}
836
1194
  strokeWidth="1"
837
1195
  strokeDasharray="2 12"
838
- opacity={isLight ? 0.4 : 0.75}
1196
+ opacity={(isLight ? 0.4 : 0.75) * fresh}
1197
+ className="transition-opacity duration-500"
839
1198
  />
840
- <circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'}>
1199
+ <circle r="4" fill={pal.flowParticle} filter={isLight ? undefined : 'url(#topo-glow)'} opacity={fresh}>
841
1200
  <animateMotion dur={`${duration}s`} repeatCount="indefinite" path={path} />
842
1201
  </circle>
843
1202
  </g>
844
1203
  );
845
1204
  })}
846
1205
 
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. */}
1206
+ {/* center hub — round 39 sized + round 13 restraint.
1207
+ The hub is the control-plane anchor. r39 gave it two outward
1208
+ pulses (r 10→38, 2.4s, double-phase) which read as "loudest
1209
+ node in the room" wrong semantics: anchors don't emit,
1210
+ they hold. r13 swaps the kinetic pulse for a slow opacity
1211
+ breath on the static halo (4s, ±15% from base), so the hub
1212
+ stays alive without throwing kinetic energy outward. The
1213
+ dual-circle "lit lamp" core is unchanged. */}
853
1214
  {layout === 'ring' && (<g>
854
- {/* static grounding halo — sits underneath, low opacity */}
1215
+ {/* grounding halo — now breathes in opacity, no expansion */}
855
1216
  <circle
856
1217
  cx={cx} cy={cy} r="18"
857
1218
  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" />
1219
+ opacity={isLight ? 0.42 : 0.12}
1220
+ >
1221
+ <animate
1222
+ attributeName="opacity"
1223
+ values={isLight ? '0.32;0.52;0.32' : '0.08;0.16;0.08'}
1224
+ dur="4s"
1225
+ repeatCount="indefinite"
1226
+ />
869
1227
  </circle>
870
1228
  {/* core — 20px diameter, larger inner highlight reads as a "lit lamp" */}
871
1229
  <circle cx={cx} cy={cy} r="10" fill={isLight ? '#059669' : '#10b981'} />
@@ -873,7 +1231,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
873
1231
  </g>)}
874
1232
 
875
1233
  {/* agent nodes */}
876
- {[...onlineNodes, ...offlineNodes].map(session => {
1234
+ {[...onlineNodes, ...offlineNodes].map((session, nodeIdx) => {
877
1235
  const pos = nodePositions[session.alias];
878
1236
  if (!pos) return null;
879
1237
 
@@ -881,7 +1239,9 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
881
1239
  const isOnline = session.status !== 'offline' || !!sseCountFor;
882
1240
  const status = nodeStatus(session, isOnline, isLight);
883
1241
  const isActive = activeAliases.has(session.alias);
884
- const radius = isOnline ? 26 : 18;
1242
+ // #113: node size scales with the S/M/L control; halos, labels,
1243
+ // badge and avatar all derive from `radius` so they follow.
1244
+ const radius = Math.round((isOnline ? 26 : 18) * nodeScale);
885
1245
  // Round 109/110 (Vincent 4582 + 4583 P0): at high node counts
886
1246
  // the 100px opaque label cards overlapped each other and
887
1247
  // covered neighbouring avatars. But hiding text entirely went
@@ -891,12 +1251,48 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
891
1251
  // occlude an avatar), and the full card appears only for the
892
1252
  // hovered node or once zoomed in past 1.4×.
893
1253
  const showFullLabel = !denseLayout || hoveredAlias === session.alias || view.zoom >= 1.4;
1254
+ // Round 8: when a node in another group is hovered, fade this
1255
+ // one. Same-group nodes (incl. singletons matching the hovered
1256
+ // alias) stay full. Pure visual focus, geometry unchanged.
1257
+ const inFocus = !hoveredGroup || (groupKeys[session.alias] ?? session.alias) === hoveredGroup;
894
1258
 
895
1259
  return (
896
1260
  <g
897
1261
  key={session.alias}
898
- className="transition-opacity"
899
- style={{ cursor: 'pointer' }}
1262
+ data-node={session.alias}
1263
+ // Round 3 / Loop: `anet-fade-in` runs once when the <g>
1264
+ // mounts — a new session entering the fleet (or the topology
1265
+ // first rendering) eases in instead of popping. Re-renders of
1266
+ // an existing node don't re-trigger (React preserves the <g>
1267
+ // via the alias key), so status changes don't flicker. The
1268
+ // global prefers-reduced-motion sweep already neutralises it.
1269
+ className="group transition-opacity anet-fade-in"
1270
+ style={{
1271
+ cursor: 'pointer',
1272
+ // Round 17 / Loop: offline nodes drop to 0.6 at rest so
1273
+ // online nodes pop without losing the offline-as-ghost
1274
+ // information. The dashed stroke + smaller radius already
1275
+ // say "offline"; dimming the whole group strengthens the
1276
+ // online-vs-offline hierarchy at first glance. Exempt the
1277
+ // chat-focused node — if the user explicitly opened a
1278
+ // popover targeting an offline alias, that node stays
1279
+ // full-brightness so the focus ring + popover read as one
1280
+ // selected thing rather than a dimmed selection. Group-
1281
+ // hover fade (Round 8) still wins when inFocus is false.
1282
+ opacity: !inFocus
1283
+ ? 0.32
1284
+ : chatAlias === session.alias
1285
+ ? 1
1286
+ : isOnline ? 1 : 0.6,
1287
+ // Round 9 / Loop: stagger the anet-fade-in so the topology
1288
+ // reveals as a wave on first paint instead of one big pop.
1289
+ // Cap at 24 indices (≈600ms tail) so 50-node fleets still
1290
+ // finish revealing within a beat. CSS animation-delay only
1291
+ // applies during the keyframe — re-renders without a new
1292
+ // mount (same alias key) don't replay, so status changes
1293
+ // never trigger the stagger again.
1294
+ animationDelay: `${Math.min(nodeIdx, 24) * 25}ms`,
1295
+ }}
900
1296
  // Stop the pointerdown from reaching the SVG pan handler: the
901
1297
  // SVG calls setPointerCapture, and a captured pointer makes
902
1298
  // Chromium fire the follow-up `click` on the SVG instead of
@@ -904,17 +1300,86 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
904
1300
  // Side effect (intended): you pan from empty canvas, not by
905
1301
  // grabbing a node.
906
1302
  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)}
1303
+ // Track hover globally (not just under denseLayout) so the
1304
+ // Round 8 group-focus fade works at any fleet size.
1305
+ onPointerEnter={() => setHoveredAlias(session.alias)}
1306
+ onPointerLeave={() => setHoveredAlias(prev => (prev === session.alias ? null : prev))}
1307
+ onClick={() => {
1308
+ setChatAlias(session.alias);
1309
+ // Round 14 ripple — capture position, radius and status
1310
+ // colour at click time so the one-shot circle is self-
1311
+ // contained (no re-render-time recomputation needed).
1312
+ setClickRipple({
1313
+ ts: Date.now(),
1314
+ x: pos.x, y: pos.y, r0: radius,
1315
+ color: status.primary,
1316
+ });
1317
+ setTimeout(() => setClickRipple(prev =>
1318
+ prev && Date.now() - prev.ts >= 590 ? null : prev), 600);
1319
+ }}
910
1320
  >
1321
+ {/* Issue #96: native hover tooltip — "Vendor · model · Runtime".
1322
+ Falls back to just the alias when the node reports no
1323
+ model/runtime. */}
1324
+ <title>{identityLine(session.model, session.runtime) || session.alias}</title>
1325
+ {/* Round 2 / Loop: hover ring — a thin outer stroke that fades
1326
+ in when the cursor enters the node, signalling clickability
1327
+ (real-user feedback for the chat-popover open). Pure CSS via
1328
+ Tailwind group-hover, so it costs nothing per frame and
1329
+ respects prefers-reduced-motion via the global media query. */}
1330
+ <circle
1331
+ cx={pos.x}
1332
+ cy={pos.y}
1333
+ r={radius + 12}
1334
+ fill="none"
1335
+ stroke={status.primary}
1336
+ // strokeWidth must NOT be 1.5 (offline status ring) or 3
1337
+ // (online status ring) — the overlap test selects by those
1338
+ // exact widths and would mis-count this invisible hover
1339
+ // ring as a node footprint.
1340
+ strokeWidth="2"
1341
+ className="opacity-0 group-hover:opacity-70 transition-opacity duration-150"
1342
+ style={{ pointerEvents: 'none' }}
1343
+ />
1344
+ {/* Round 11 / Loop: chat-focus ring — when the ChatPopover is
1345
+ open targeting this node, anchor a persistent ring around
1346
+ it so the floating popover visibly links back to its source
1347
+ node. Static (not pulsing) so it reads as "selected state"
1348
+ rather than "this node is active". strokeWidth=2.5 stays
1349
+ clear of the overlap-test selectors (1.5 / 3). Sits just
1350
+ outside the halo radius+8 so it never overlaps a neighbour
1351
+ (halos already pack flush in dense grids). */}
1352
+ {chatAlias === session.alias && (
1353
+ <circle
1354
+ cx={pos.x}
1355
+ cy={pos.y}
1356
+ r={radius + 14}
1357
+ fill="none"
1358
+ stroke={status.primary}
1359
+ strokeWidth="2.5"
1360
+ opacity={isLight ? 0.85 : 0.95}
1361
+ filter={!isLight ? 'url(#topo-glow)' : undefined}
1362
+ className="transition-opacity duration-200"
1363
+ style={{ pointerEvents: 'none' }}
1364
+ />
1365
+ )}
911
1366
  {isActive && (
912
1367
  <circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>
913
1368
  <animate attributeName="r" values={`${radius + 8};${radius + 22};${radius + 8}`} dur="2.4s" repeatCount="indefinite" />
914
1369
  <animate attributeName="opacity" values={isLight ? '0.12;0.02;0.12' : '0.18;0.04;0.18'} dur="2.4s" repeatCount="indefinite" />
915
1370
  </circle>
916
1371
  )}
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)} />
1372
+ {/* Round 4 / Loop: transition-[fill,stroke,opacity] smooths
1373
+ status colour changes so idle↔working↔offline doesn't snap
1374
+ — task replies / node-rename / SSE updates ease in. */}
1375
+ <circle
1376
+ cx={pos.x}
1377
+ cy={pos.y}
1378
+ r={radius + 8}
1379
+ fill={status.halo}
1380
+ opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.4 : 0.25)}
1381
+ className="transition-[fill,opacity] duration-300 ease-out"
1382
+ />
918
1383
  <circle
919
1384
  cx={pos.x}
920
1385
  cy={pos.y}
@@ -924,34 +1389,28 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
924
1389
  strokeWidth={isOnline ? 3 : 1.5}
925
1390
  strokeDasharray={isOnline ? 'none' : '5 5'}
926
1391
  filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}
1392
+ className="transition-[fill,stroke] duration-300 ease-out"
927
1393
  />
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. */}
1394
+ {/* Issue #96: node "avatar" is now driven by the model
1395
+ vendor. Decision order:
1396
+ 1. ?brand=intern flag, or an intern-aliased node with
1397
+ no model field 书生 coin (preserves #79).
1398
+ 2. vendor has a packaged logo asset that logo image
1399
+ (intern-s1-* models land here via vendorForModel).
1400
+ 3. known vendor, logo asset not shipped yet → a
1401
+ vendor-tinted monogram (spec-mandated fallback).
1402
+ 4. unknown vendor / null model → the prefix-group
1403
+ hue-hashed initial (#83/#99 behaviour, unchanged). */}
935
1404
  {(() => {
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;
1405
+ const ar = Math.round((isOnline ? 14 : 10) * nodeScale);
1406
+ const size = radius * 2;
1407
+ const vendor = vendorForModel(session.model);
1408
+ const internByAlias = /书生|书小生|intern/i.test(session.alias);
1409
+
1410
+ if (isIntern || internByAlias || vendor.logo) {
952
1411
  return (
953
1412
  <image
954
- href="/intern_avatar.png"
1413
+ href={vendor.logo ?? '/intern_avatar.png'}
955
1414
  x={pos.x - size / 2}
956
1415
  y={pos.y - size / 2}
957
1416
  width={size}
@@ -960,12 +1419,25 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
960
1419
  />
961
1420
  );
962
1421
  }
1422
+ if (vendor.id !== 'unknown') {
1423
+ // Known model house, logo asset not in public/vendors/
1424
+ // yet — vendor-tinted monogram stands in.
1425
+ return (
1426
+ <>
1427
+ <circle cx={pos.x} cy={pos.y} r={ar} fill={vendor.mono.bg} stroke={vendor.mono.ring} strokeWidth="1" />
1428
+ <text
1429
+ x={pos.x} y={pos.y} dy="0.34em" textAnchor="middle"
1430
+ fill={vendor.mono.text} fontSize={ar}
1431
+ fontFamily="monospace" fontWeight="700"
1432
+ >
1433
+ {vendor.initial}
1434
+ </text>
1435
+ </>
1436
+ );
1437
+ }
963
1438
  // 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.
1439
+ // not the full alias — every 通信* node shares one color.
967
1440
  const c = aliasAvatarColors(groupKeys[session.alias] || session.alias);
968
- const fs = isOnline ? 14 : 10;
969
1441
  return (
970
1442
  <>
971
1443
  <circle cx={pos.x} cy={pos.y} r={ar} fill={c.bg} stroke={c.ring} strokeWidth="1" />
@@ -975,7 +1447,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
975
1447
  dy="0.34em"
976
1448
  textAnchor="middle"
977
1449
  fill={c.text}
978
- fontSize={fs}
1450
+ fontSize={ar}
979
1451
  fontFamily="monospace"
980
1452
  fontWeight="700"
981
1453
  >
@@ -984,12 +1456,32 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
984
1456
  </>
985
1457
  );
986
1458
  })()}
1459
+ {/* Issue #96: runtime badge — small corner glyph marking the
1460
+ execution shell (CLI / SDK / HTTP API). Sits bottom-right
1461
+ of the avatar; colours kept off the working/idle/offline
1462
+ status hues. Absent when the node reports no runtime. */}
1463
+ {(() => {
1464
+ const rt = runtimeIdentity(session.runtime);
1465
+ if (!rt) return null;
1466
+ const br = isOnline ? 7 : 5.5;
1467
+ const bx = pos.x + radius * 0.72;
1468
+ const by = pos.y + radius * 0.72;
1469
+ const icon = br * 2 * 0.62;
1470
+ return (
1471
+ <g style={{ pointerEvents: 'none' }}>
1472
+ <circle cx={bx} cy={by} r={br} fill={pal.containerBg} stroke={rt.color} strokeWidth="1.5" />
1473
+ <g transform={`translate(${bx - icon / 2} ${by - icon / 2}) scale(${icon / 24})`}>
1474
+ <path d={rt.iconPath} fill="none" stroke={rt.color} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
1475
+ </g>
1476
+ </g>
1477
+ );
1478
+ })()}
987
1479
  {session.status === 'working' && (
988
1480
  // working pulse — small dot just inside the status ring,
989
1481
  // above the avatar text. Was a 18px horizontal bar at
990
1482
  // y+11 which now collides with the avatar; the dot reads
991
1483
  // 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}>
1484
+ <circle cx={pos.x} cy={pos.y - (radius - 6)} r="2.5" fill={pal.flowParticle}>
993
1485
  <animate attributeName="opacity" values="1;0.25;1" dur="1.1s" repeatCount="indefinite" />
994
1486
  </circle>
995
1487
  )}
@@ -998,61 +1490,112 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
998
1490
  Round 109/110 (Vincent P0): full opaque card below the
999
1491
  density threshold / on hover / when zoomed; otherwise a
1000
1492
  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}` : ''}
1493
+ labelled without an opaque box covering its neighbours.
1494
+ Round 15 / Loop: when nodeScale=S the node shrinks 30%
1495
+ but the label card was staying full-size, so the label
1496
+ visually outweighed the small node. Tighten the card
1497
+ frame, alias / sub fontSize, drop-offset and truncate
1498
+ length specifically for S; M and L keep their existing
1499
+ sizes (M ≈ L for labels by design — the S user is the
1500
+ one who explicitly asked for a denser view). */}
1501
+ {(() => {
1502
+ const isSmall = nodeScale < 0.8;
1503
+ const cardW = isSmall ? 88 : 100;
1504
+ const cardH = isSmall ? 36 : 42;
1505
+ const cardTopY = isSmall ? -12 : -14;
1506
+ const aliasFs = isSmall ? 11 : 12;
1507
+ const subFs = isSmall ? 8 : 9;
1508
+ const subY = isSmall ? 15 : 17;
1509
+ const dropY = isSmall ? 18 : 22;
1510
+ const fullMax = isSmall ? 11 : 12;
1511
+ const denseFs = isSmall ? 9 : 10;
1512
+ const denseDrop = isSmall ? 12 : 14;
1513
+ return showFullLabel ? (
1514
+ <g transform={`translate(${pos.x}, ${pos.y + radius + dropY})`} style={{ pointerEvents: 'none' }}>
1515
+ <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} />
1516
+ <text x="0" y="1" textAnchor="middle" fill={status.text} fontSize={aliasFs} fontFamily="monospace" fontWeight="700">
1517
+ {truncate(session.alias, fullMax)}
1518
+ </text>
1519
+ <text x="0" y={subY} textAnchor="middle" fill={status.primary} fontSize={subFs} fontFamily="monospace">
1520
+ {status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}
1521
+ </text>
1522
+ </g>
1523
+ ) : (
1524
+ <text
1525
+ x={pos.x}
1526
+ y={pos.y + radius + denseDrop}
1527
+ textAnchor="middle"
1528
+ fill={status.text}
1529
+ fontSize={denseFs}
1530
+ fontFamily="monospace"
1531
+ fontWeight="700"
1532
+ opacity={0.9}
1533
+ style={{ pointerEvents: 'none', paintOrder: 'stroke' }}
1534
+ stroke={pal.containerBg}
1535
+ strokeWidth="3"
1536
+ >
1537
+ {truncate(session.alias, isSmall ? 9 : 10)}
1010
1538
  </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
- )}
1539
+ );
1540
+ })()}
1029
1541
  </g>
1030
1542
  );
1031
1543
  })}
1032
1544
 
1545
+ {/* Round 14 / Loop: click ripple — one-shot expanding ring from
1546
+ the clicked node, ~500ms, status-coloured. Sits inside the
1547
+ zoom/pan <g> so it scales / pans with the topology. Keyed by
1548
+ ts so a re-click on any node (same or different) remounts the
1549
+ <circle> and the SMIL <animate> elements replay from t=0.
1550
+ strokeWidth=2 doesn't match the overlap-test selectors. */}
1551
+ {clickRipple && (
1552
+ <circle
1553
+ key={clickRipple.ts}
1554
+ cx={clickRipple.x}
1555
+ cy={clickRipple.y}
1556
+ r={clickRipple.r0 + 4}
1557
+ fill="none"
1558
+ stroke={clickRipple.color}
1559
+ strokeWidth="2"
1560
+ opacity="0"
1561
+ style={{ pointerEvents: 'none' }}
1562
+ >
1563
+ <animate attributeName="r" values={`${clickRipple.r0 + 4};${clickRipple.r0 + 30}`} dur="0.5s" fill="freeze" />
1564
+ <animate attributeName="opacity" values="0.7;0" dur="0.5s" fill="freeze" />
1565
+ </circle>
1566
+ )}
1567
+
1568
+ </g>
1569
+
1570
+ {/* #112: overlay panels (recent-signal + legend) render OUTSIDE the
1571
+ zoom/pan <g> so they stay fixed while the topology pans/zooms.
1572
+ They're sized + tucked into the top corners so every corner of
1573
+ each panel is >325px from the canvas centre — i.e. fully outside
1574
+ the outermost (offline) ring. No node on any ring can reach the
1575
+ corner triangles, so the panels never overlap a node, in ring
1576
+ OR grid layout (Vincent 4727 zero-overlap criterion). */}
1033
1577
  {/* 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)}
1578
+ <g transform="translate(16, 16)">
1579
+ <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} />
1580
+ <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight="700">recent signal</text>
1581
+ <text x="150" y="21" fill={pal.legendAccent} fontSize="10" fontFamily="monospace">{messages.length} msgs</text>
1582
+ {flowLinks.slice(0, 3).map((link, index) => (
1583
+ <text key={link.key} x="13" y={38 + index * 16} fill={pal.legendText} fontSize="9" fontFamily="monospace">
1584
+ {truncate(link.from, 6)} {'->'} {truncate(link.to, 6)} / {link.count} / {truncate(link.content, 12)}
1041
1585
  </text>
1042
1586
  ))}
1043
1587
  </g>
1044
1588
 
1045
1589
  {/* 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>
1590
+ <g transform="translate(760, 16)">
1591
+ <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} />
1592
+ <circle cx="16" cy="24" r="5.5" fill={isLight ? '#059669' : '#22c55e'} />
1593
+ <text x="30" y="28" fill={pal.legendText} fontSize="11" fontFamily="monospace">working node</text>
1594
+ <circle cx="16" cy="48" r="5.5" fill={isLight ? '#0d9488' : '#2dd4bf'} />
1595
+ <text x="30" y="52" fill={pal.legendText} fontSize="11" fontFamily="monospace">online idle</text>
1596
+ <circle cx="16" cy="72" r="5.5" fill={isLight ? '#94a3b8' : '#6b7280'} />
1597
+ <text x="30" y="76" fill={pal.legendText} fontSize="11" fontFamily="monospace">offline / no SSE</text>
1598
+ <path d="M140,72 Q164,48 196,72" fill="none" stroke={pal.flowEdge} strokeWidth="3" markerEnd="url(#topo-arrow)" />
1056
1599
  </g>
1057
1600
  </svg>
1058
1601
 
@@ -1063,6 +1606,26 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1063
1606
  Split into a plain % readout + an explicit reset button with
1064
1607
  its own icon + tooltip. */}
1065
1608
  <div className="absolute bottom-3 right-3 flex items-center gap-1.5 text-xs select-none">
1609
+ {/* #113: node size — S / M / L segmented control (Vincent 4727). */}
1610
+ <div
1611
+ className="flex items-center rounded-md border overflow-hidden"
1612
+ style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder }}
1613
+ role="group"
1614
+ aria-label="Node size"
1615
+ >
1616
+ {([['S', 0.7], ['M', 0.84], ['L', 1]] as const).map(([lbl, v], idx) => (
1617
+ <button
1618
+ key={lbl}
1619
+ onClick={() => pickNodeScale(v)}
1620
+ aria-pressed={nodeScale === v}
1621
+ title={`Node size: ${lbl === 'S' ? 'small' : lbl === 'M' ? 'medium' : 'large'}`}
1622
+ className={`px-2 py-1 transition-colors ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300' : 'hover:bg-white/5'}`}
1623
+ style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}
1624
+ >
1625
+ {lbl}
1626
+ </button>
1627
+ ))}
1628
+ </div>
1066
1629
  <div
1067
1630
  className="flex items-center rounded-md border overflow-hidden"
1068
1631
  style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder }}
@@ -1072,6 +1635,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1072
1635
  className="px-2 py-1 hover:bg-white/5 transition-colors"
1073
1636
  style={{ color: pal.legendText }}
1074
1637
  aria-label="Zoom out"
1638
+ title="Zoom out (−)"
1075
1639
  >
1076
1640
  <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
1641
  </button>
@@ -1087,6 +1651,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1087
1651
  className="px-2 py-1 hover:bg-white/5 transition-colors"
1088
1652
  style={{ color: pal.legendText }}
1089
1653
  aria-label="Zoom in"
1654
+ title="Zoom in (+)"
1090
1655
  >
1091
1656
  <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
1657
  </button>
@@ -1096,7 +1661,7 @@ export function TopoGraph({ sessions, sseSessions }: TopoGraphProps) {
1096
1661
  className="p-1.5 rounded-md border hover:bg-white/5 transition-colors"
1097
1662
  style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
1098
1663
  aria-label="Reset view"
1099
- title="Reset zoom + pan (or double-click the canvas)"
1664
+ title="Reset zoom + pan (0, or double-click the canvas)"
1100
1665
  >
1101
1666
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
1102
1667
  <path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8" />