@sleep2agi/agent-network-dashboard 0.4.6-preview.11 → 0.4.6-preview.111

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 (317) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +69 -55
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +2 -2
  17. package/.next/server/app/_not-found.rsc +12 -12
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  20. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  24. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/admin.html +2 -2
  26. package/.next/server/app/admin.rsc +14 -14
  27. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  28. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  30. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  32. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  33. package/.next/server/app/api/hub/servers/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/servers/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/servers/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/servers/route.js +7 -0
  37. package/.next/server/app/api/hub/servers/route.js.map +5 -0
  38. package/.next/server/app/api/hub/servers/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/servers/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/index.html +2 -2
  41. package/.next/server/app/index.rsc +14 -14
  42. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  43. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  45. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  46. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  47. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/login.html +2 -2
  49. package/.next/server/app/login.rsc +14 -14
  50. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  56. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/logs.html +2 -2
  58. package/.next/server/app/logs.rsc +14 -14
  59. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  65. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/messages.html +2 -2
  67. package/.next/server/app/messages.rsc +14 -14
  68. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  74. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/node.html +2 -2
  76. package/.next/server/app/node.rsc +14 -14
  77. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  83. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/nodes.html +2 -2
  85. package/.next/server/app/nodes.rsc +14 -14
  86. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  89. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  91. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/server-logs.html +2 -2
  95. package/.next/server/app/server-logs.rsc +14 -14
  96. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/networks.html +2 -2
  104. package/.next/server/app/settings/networks.rsc +14 -14
  105. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  106. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  107. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  108. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  109. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  110. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  111. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  112. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  113. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  114. package/.next/server/app/settings/tokens.html +2 -2
  115. package/.next/server/app/settings/tokens.rsc +14 -14
  116. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  122. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  123. package/.next/server/app/settings.html +2 -2
  124. package/.next/server/app/settings.rsc +14 -14
  125. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  131. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  132. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  133. package/.next/server/app/tasks.html +2 -2
  134. package/.next/server/app/tasks.rsc +14 -14
  135. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  136. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  137. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  138. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  139. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  140. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  141. package/.next/server/app-paths-manifest.json +1 -0
  142. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js +3 -0
  143. package/.next/server/chunks/0ykm__next-internal_server_app_api_hub_servers_route_actions_08-m-cs.js.map +1 -0
  144. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +3 -0
  145. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -0
  146. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  147. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  148. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  149. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  150. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  151. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  160. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  167. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  168. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  169. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  170. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  171. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  172. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  173. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  174. package/.next/server/middleware-build-manifest.js +3 -3
  175. package/.next/server/pages/404.html +2 -2
  176. package/.next/server/pages/500.html +1 -1
  177. package/.next/server/server-reference-manifest.js +1 -1
  178. package/.next/server/server-reference-manifest.json +1 -1
  179. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  180. package/.next/static/chunks/0052a9q2fvljq.js +1 -0
  181. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  182. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  183. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  184. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  185. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  186. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  187. package/.next/static/chunks/0ofu4mc55-oar.js +4 -0
  188. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  189. package/.next/static/chunks/0tvn2l1pc.h65.js +1 -0
  190. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  191. package/.next/static/chunks/115k17sotk9jg.css +2 -0
  192. package/.next/static/chunks/121d8yvfl8l_..js +7 -0
  193. package/.next/static/chunks/14ppi2e6ljtms.js +1 -0
  194. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  195. package/.next/trace +2 -2
  196. package/.next/trace-build +1 -1
  197. package/.next/types/routes.d.ts +2 -1
  198. package/.next/types/validator.ts +9 -0
  199. package/app/api/hub/servers/route.ts +39 -0
  200. package/app/components/AppShell.tsx +2 -0
  201. package/app/components/ChatPopover.tsx +38 -2
  202. package/app/components/CommandPalette.tsx +67 -0
  203. package/app/components/HelpOverlay.tsx +71 -0
  204. package/app/components/ServersDrawer.tsx +206 -0
  205. package/app/components/TopoGraph.tsx +2032 -157
  206. package/app/components/utils.ts +8 -7
  207. package/app/globals.css +19 -1
  208. package/app/lib/time.ts +36 -0
  209. package/package.json +1 -1
  210. package/scripts/help-overlay-topo-test.mjs +44 -0
  211. package/scripts/servers-drawer-live-test.mjs +96 -0
  212. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  213. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  214. package/scripts/topo-active-links-chip-test.mjs +73 -0
  215. package/scripts/topo-arrival-ping-test.mjs +87 -0
  216. package/scripts/topo-arrow-tier-test.mjs +61 -0
  217. package/scripts/topo-auto-fit-test.mjs +73 -0
  218. package/scripts/topo-chat-focus-ring-test.mjs +77 -0
  219. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  220. package/scripts/topo-click-ripple-test.mjs +66 -0
  221. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  222. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  223. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  224. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  225. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  226. package/scripts/topo-dispatch-pulse-test.mjs +83 -0
  227. package/scripts/topo-edge-count-badge-test.mjs +84 -0
  228. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  229. package/scripts/topo-edge-focus-test.mjs +113 -0
  230. package/scripts/topo-edge-hover-test.mjs +93 -0
  231. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  232. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  233. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  234. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  235. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  236. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  237. package/scripts/topo-fit-key-test.mjs +106 -0
  238. package/scripts/topo-freshness-chip-test.mjs +74 -0
  239. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  240. package/scripts/topo-group-count-test.mjs +68 -0
  241. package/scripts/topo-group-hover-test.mjs +82 -0
  242. package/scripts/topo-group-label-hover-test.mjs +119 -0
  243. package/scripts/topo-group-label-title-test.mjs +83 -0
  244. package/scripts/topo-group-pin-test.mjs +119 -0
  245. package/scripts/topo-group-status-pips-test.mjs +103 -0
  246. package/scripts/topo-groupbox-live-test.mjs +100 -0
  247. package/scripts/topo-groupbox-test.mjs +4 -2
  248. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  249. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  250. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  251. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  252. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  253. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  254. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  255. package/scripts/topo-hub-busyness-test.mjs +100 -0
  256. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  257. package/scripts/topo-hub-title-test.mjs +93 -0
  258. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  259. package/scripts/topo-label-lift-test.mjs +119 -0
  260. package/scripts/topo-label-scale-test.mjs +78 -0
  261. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  262. package/scripts/topo-layout-key-test.mjs +91 -0
  263. package/scripts/topo-legend-count-test.mjs +88 -0
  264. package/scripts/topo-legend-hover-test.mjs +113 -0
  265. package/scripts/topo-legend-pin-test.mjs +128 -0
  266. package/scripts/topo-minimap-test.mjs +68 -0
  267. package/scripts/topo-mobile-chips-test.mjs +103 -0
  268. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  269. package/scripts/topo-node-title-test.mjs +101 -0
  270. package/scripts/topo-offline-dim-test.mjs +70 -0
  271. package/scripts/topo-overlap-test.mjs +32 -5
  272. package/scripts/topo-pan-cursor-test.mjs +62 -0
  273. package/scripts/topo-panel-elevation-test.mjs +76 -0
  274. package/scripts/topo-particle-stagger-test.mjs +82 -0
  275. package/scripts/topo-pin-persistence-test.mjs +105 -0
  276. package/scripts/topo-popover-meta-test.mjs +78 -0
  277. package/scripts/topo-pressure-bar-test.mjs +69 -0
  278. package/scripts/topo-pressure-hover-test.mjs +123 -0
  279. package/scripts/topo-pressure-pin-test.mjs +119 -0
  280. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  281. package/scripts/topo-pulse-rate-test.mjs +71 -0
  282. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  283. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  284. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  285. package/scripts/topo-recent-row-ts-test.mjs +92 -0
  286. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  287. package/scripts/topo-reduced-motion-test.mjs +77 -0
  288. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  289. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  290. package/scripts/topo-tier-ring-test.mjs +69 -0
  291. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  292. package/scripts/topo-tier-stagger-test.mjs +89 -0
  293. package/scripts/topo-tz-ghost-test.mjs +69 -0
  294. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  295. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  296. package/scripts/topo-vendor-pill-test.mjs +125 -0
  297. package/scripts/topo-vendor-pin-test.mjs +125 -0
  298. package/scripts/topo-wheel-zoom-test.mjs +79 -0
  299. package/scripts/topo-zoom-keys-test.mjs +89 -0
  300. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  301. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  302. package/.next/static/chunks/03cra9~ibhxtq.js +0 -1
  303. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  304. package/.next/static/chunks/0cj5qr_a8dzky.js +0 -7
  305. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  306. package/.next/static/chunks/0g-urk~xe4p41.js +0 -1
  307. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  308. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  309. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  310. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  311. package/.next/static/chunks/0r~2i_zubnm0u.js +0 -1
  312. package/.next/static/chunks/0~5-jv77qynyc.css +0 -2
  313. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  314. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  315. /package/.next/static/{-dh0O1dlTLFw1pzDolqQ3 → 93NJDvLYgCZAXqweNe_Ye}/_buildManifest.js +0 -0
  316. /package/.next/static/{-dh0O1dlTLFw1pzDolqQ3 → 93NJDvLYgCZAXqweNe_Ye}/_clientMiddlewareManifest.js +0 -0
  317. /package/.next/static/{-dh0O1dlTLFw1pzDolqQ3 → 93NJDvLYgCZAXqweNe_Ye}/_ssgManifest.js +0 -0
@@ -0,0 +1,90 @@
1
+ /* Round 67 verification: filter pills shed the "filter:" prefix on
2
+ * narrow viewports and gain an anet-fade-in entrance.
3
+ * - At <sm (375 px) the prefix span has `display:none` via the
4
+ * `hidden sm:inline` class — visible pill text = "working×".
5
+ * - At sm+ (900 px) the prefix is visible — "filter: working ×".
6
+ * - The pill `<span>` carries `anet-fade-in` class so it eases in
7
+ * on mount (entrance polish).
8
+ */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+
15
+ async function probe(width) {
16
+ const ctx = await browser.newContext({ viewport: { width, height: 1500 } });
17
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
18
+ await ctx.addInitScript(() => {
19
+ try {
20
+ localStorage.setItem('anet-theme', 'cyber');
21
+ sessionStorage.setItem('anet_v3_auth', '1');
22
+ // Pre-seed the working pin via R66 persistence so the pill exists
23
+ // on first paint — keeps the test independent of click wiring.
24
+ sessionStorage.setItem('anet-topo-pinned-status', 'working');
25
+ } catch {}
26
+ });
27
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const sessions = [
33
+ { alias: 'wkr', status: 'working', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ { alias: 'idl', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
35
+ ];
36
+ await route.fulfill({ response: r, json: { ...b, sessions } });
37
+ });
38
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
39
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ // Mobile viewport auto-hides the topology section; click "Show Topology"
43
+ // if the button surfaces. Otherwise the pill still renders in the chip
44
+ // row (chip row is independent of canvas visibility).
45
+ await page.waitForTimeout(1200);
46
+ const showBtn = page.locator('button', { hasText: /Show Topology/i });
47
+ if (await showBtn.count() > 0) await showBtn.first().click();
48
+ await page.waitForSelector('[data-active-filter="status"]', { timeout: 15000 });
49
+
50
+ const info = await page.evaluate(() => {
51
+ const pill = document.querySelector('[data-active-filter="status"]');
52
+ if (!pill) return null;
53
+ const prefix = pill.querySelector('[data-filter-prefix]');
54
+ // innerText respects display:none (vs textContent which includes
55
+ // all DOM text); that's the visible-to-the-user version.
56
+ return {
57
+ pillText: (pill.innerText || '').trim(),
58
+ pillFull: (pill.textContent || '').trim(),
59
+ pillClasses: pill.getAttribute('class') || '',
60
+ hasFadeIn: /\banet-fade-in\b/.test(pill.getAttribute('class') || ''),
61
+ prefixVisible: prefix
62
+ ? (() => {
63
+ const r = prefix.getBoundingClientRect();
64
+ return r.width > 0 && r.height > 0;
65
+ })()
66
+ : false,
67
+ prefixDisplay: prefix ? getComputedStyle(prefix).display : null,
68
+ };
69
+ });
70
+ await ctx.close();
71
+ return info;
72
+ }
73
+
74
+ const mobile = await probe(375);
75
+ const desktop = await probe(900);
76
+ await browser.close();
77
+
78
+ const results = {
79
+ mobile_prefixHidden: mobile && mobile.prefixVisible === false && mobile.prefixDisplay === 'none',
80
+ mobile_textNoPrefix: mobile && /^working/.test(mobile.pillText.trim()),
81
+ mobile_hasFadeIn: mobile && mobile.hasFadeIn === true,
82
+ desktop_prefixVisible: desktop && desktop.prefixVisible === true,
83
+ desktop_textWithPrefix: desktop && /^filter:\s*working/.test(desktop.pillText.trim()),
84
+ desktop_hasFadeIn: desktop && desktop.hasFadeIn === true,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} filter pill mobile:`, JSON.stringify(results),
88
+ `\n mobile=`, mobile,
89
+ `\n desktop=`, desktop);
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,106 @@
1
+ /* Round 29 verification: `f` keyboard shortcut triggers fit-to-content.
2
+ * - on a fleet that overflows: f sets zoom < 100%
3
+ * - on a fleet that fits at 100%: f is a recenter (zoom stays/returns 100%)
4
+ * - input-focus suppression: typing `f` in Cmd+K palette must not fit
5
+ * - help overlay lists `f` */
6
+ import { chromium } from 'playwright';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
10
+ const browser = await chromium.launch({ headless: true });
11
+
12
+ async function load({ fleetSize, persisted }) {
13
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
14
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
15
+ await ctx.addInitScript(([pers]) => {
16
+ try {
17
+ localStorage.setItem('anet-theme', 'cyber');
18
+ localStorage.setItem('anet-topo-layout', 'grid');
19
+ if (pers) localStorage.setItem('anet-topo-view', JSON.stringify(pers));
20
+ else localStorage.removeItem('anet-topo-view');
21
+ sessionStorage.setItem('anet_v3_auth', '1');
22
+ } catch {}
23
+ }, [persisted]);
24
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const groups = ['A站', 'B站', 'C站', 'D站', 'E站', 'F站', 'G站'];
30
+ const aliases = Array.from({ length: fleetSize }, (_, i) =>
31
+ `${groups[Math.floor(i / 5) % groups.length]}n${i + 1}`);
32
+ const sessions = aliases.map(a => ({
33
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ }));
36
+ await route.fulfill({ response: r, json: { ...b, sessions } });
37
+ });
38
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
39
+ const page = await ctx.newPage();
40
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
41
+ await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, fleetSize, { timeout: 30000 }).catch(() => {});
42
+ await page.waitForTimeout(500);
43
+ return { ctx, page };
44
+ }
45
+
46
+ const readZoom = (page) => page.evaluate(() => {
47
+ const span = document.querySelector('button[aria-label="Zoom in"]').parentElement.querySelector('span[title*="zoom level"]');
48
+ return +span.textContent.replace('%', '');
49
+ });
50
+
51
+ // 1. Big fleet, persisted view at 100% → autoFit doesn't run (persisted respected);
52
+ // user presses `f` and gets fitted now.
53
+ const big = await load({ fleetSize: 35, persisted: { zoom: 1, x: 0, y: 0 } });
54
+ const bigBefore = await readZoom(big.page);
55
+ await big.page.keyboard.press('f');
56
+ await big.page.waitForTimeout(150);
57
+ const bigAfter = await readZoom(big.page);
58
+ await big.ctx.close();
59
+
60
+ // 2. Small fleet (no overflow), user manually zoomed in via `+` then presses `f` to recenter.
61
+ const small = await load({ fleetSize: 6, persisted: { zoom: 1, x: 0, y: 0 } });
62
+ await small.page.keyboard.press('=');
63
+ await small.page.keyboard.press('=');
64
+ await small.page.waitForTimeout(120);
65
+ const smallZoomedIn = await readZoom(small.page);
66
+ await small.page.keyboard.press('f');
67
+ await small.page.waitForTimeout(150);
68
+ const smallAfterFit = await readZoom(small.page);
69
+ await small.ctx.close();
70
+
71
+ // 3. Input-focus suppression: open Cmd+K, type `f`, expect no fit.
72
+ const focus = await load({ fleetSize: 35, persisted: { zoom: 1, x: 0, y: 0 } });
73
+ const focusBefore = await readZoom(focus.page);
74
+ await focus.page.keyboard.press('Meta+k');
75
+ await focus.page.waitForTimeout(200);
76
+ const paletteInput = focus.page.locator('input[placeholder*="Search"], input[type="search"], [role="dialog"] input').first();
77
+ await paletteInput.focus().catch(() => {});
78
+ await focus.page.keyboard.press('f');
79
+ await focus.page.waitForTimeout(150);
80
+ const focusAfter = await readZoom(focus.page);
81
+ await focus.page.keyboard.press('Escape');
82
+ await focus.ctx.close();
83
+
84
+ // 4. Help overlay lists `f`.
85
+ const help = await load({ fleetSize: 4 });
86
+ await help.page.keyboard.press('?');
87
+ await help.page.waitForTimeout(200);
88
+ const helpHasFit = await help.page.evaluate(() => {
89
+ const heading = [...document.querySelectorAll('[role="dialog"] div')].find(el => el.textContent === 'Topology canvas');
90
+ if (!heading) return false;
91
+ const txt = heading.parentElement?.textContent || '';
92
+ return /Fit topology/i.test(txt);
93
+ });
94
+ await help.ctx.close();
95
+
96
+ await browser.close();
97
+ const results = {
98
+ bigFleetFKeyFits: bigBefore === 100 && bigAfter < 100 && bigAfter >= 50,
99
+ smallFleetFKeyRecenters: smallZoomedIn > 100 && smallAfterFit === 100,
100
+ inputFocusSuppressed: focusAfter === focusBefore,
101
+ helpOverlayListsFit: helpHasFit,
102
+ };
103
+ const ok = Object.values(results).every(Boolean);
104
+ console.log(`${ok ? '✅' : '❌'} fit key:`, JSON.stringify(results),
105
+ `bigBefore=${bigBefore} bigAfter=${bigAfter} smallZoomedIn=${smallZoomedIn} smallAfterFit=${smallAfterFit} focusBefore=${focusBefore} focusAfter=${focusAfter}`);
106
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,74 @@
1
+ /* Round 46 verification: live · Ns freshness chip.
2
+ * - renders in the topology header with initial "live · 0s" or "live · 1s"
3
+ * - ticks up over time (>= 2s after a 2.5s wait)
4
+ * - resets near 0 when SWR delivers a new sessions payload (we trigger
5
+ * this by mutating the mocked /api/hub/status response — SWR's 5s
6
+ * refresh re-fetches and the chip should re-zero)
7
+ * - turns amber when stale (>10s since last sync; verified by blocking
8
+ * the second poll and waiting >12s) */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 } });
15
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
16
+ await ctx.addInitScript(() => {
17
+ try {
18
+ localStorage.setItem('anet-theme', 'cyber');
19
+ localStorage.setItem('anet-topo-layout', 'grid');
20
+ sessionStorage.setItem('anet_v3_auth', '1');
21
+ } catch {}
22
+ });
23
+
24
+ let pollCount = 0;
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ pollCount++;
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const sessions = [
32
+ { alias: `a${pollCount}`, status: 'idle', network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ { alias: 'b', status: 'idle', network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
36
+ ];
37
+ await route.fulfill({ response: r, json: { ...b, sessions } });
38
+ });
39
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForSelector('[data-freshness-chip]', { timeout: 30000 });
43
+
44
+ const readChip = () => page.evaluate(() => {
45
+ const el = document.querySelector('[data-freshness-chip]');
46
+ if (!el) return null;
47
+ return {
48
+ text: (el.textContent || '').trim(),
49
+ isAmber: /amber|warning/i.test(el.className) || el.className.includes('amber'),
50
+ };
51
+ });
52
+
53
+ const initial = await readChip();
54
+ await page.waitForTimeout(2500);
55
+ const afterTick = await readChip();
56
+
57
+ // SWR refreshes every 5s — wait past one cycle and confirm the chip re-zeros.
58
+ // Initial chip might be 1s, after 2.5s should be 3s. After a ~5s sync,
59
+ // should drop to 0-1s.
60
+ await page.waitForTimeout(3500);
61
+ const afterSync = await readChip();
62
+
63
+ await browser.close();
64
+ const seconds = (s) => +(s.text.match(/(\d+)s/)?.[1] ?? -1);
65
+ const results = {
66
+ initialNearZero: initial !== null && seconds(initial) <= 2,
67
+ tickedUpAfter25s: afterTick !== null && seconds(afterTick) >= 2 && seconds(afterTick) <= 4,
68
+ reZeroedAfterSync: afterSync !== null && seconds(afterSync) <= 2,
69
+ pollsHappened: pollCount >= 2,
70
+ };
71
+ const ok = Object.values(results).every(Boolean);
72
+ console.log(`${ok ? '✅' : '❌'} freshness chip:`, JSON.stringify(results),
73
+ `\n initial=${JSON.stringify(initial)}\n afterTick=${JSON.stringify(afterTick)}\n afterSync=${JSON.stringify(afterSync)} polls=${pollCount}`);
74
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,95 @@
1
+ /* Round 68 verification: group-box rect has a 3-tier visual ladder:
2
+ * idle → strokeWidth 1.5, dashed, ringStroke colour
3
+ * hovered → strokeWidth 2, solid, legendAccent colour
4
+ * pinned → strokeWidth 3, solid, legendAccent colour (stronger)
5
+ *
6
+ * Plus `data-group-box-pinned` reflects the pinned state so external
7
+ * tooling can spot a pinned box without inspecting computed CSS.
8
+ */
9
+ import { chromium } from 'playwright';
10
+ import { readFileSync } from 'node:fs';
11
+
12
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
13
+ const browser = await chromium.launch({ headless: true });
14
+
15
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1500 } });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript(() => {
18
+ try {
19
+ localStorage.setItem('anet-theme', 'cyber');
20
+ localStorage.setItem('anet-topo-layout', 'grid');
21
+ sessionStorage.setItem('anet_v3_auth', '1');
22
+ } catch {}
23
+ });
24
+
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const sessions = [
31
+ { alias: 'alpha1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
32
+ { alias: 'alpha2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
33
+ { alias: 'alpha3', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
34
+ { alias: 'beta1', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
35
+ { alias: 'beta2', status: 'idle', network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
36
+ ];
37
+ await route.fulfill({ response: r, json: { ...b, sessions } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
41
+ const page = await ctx.newPage();
42
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
43
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 5, { timeout: 30000 });
44
+ await page.waitForTimeout(600);
45
+
46
+ const readAlphaBox = () => page.evaluate(() => {
47
+ // Find the alpha group <g data-group="alpha"> and read its <rect>.
48
+ const g = document.querySelector('g[data-group="alpha"]');
49
+ if (!g) return null;
50
+ const rect = g.querySelector('rect');
51
+ if (!rect) return null;
52
+ return {
53
+ strokeWidth: rect.getAttribute('stroke-width'),
54
+ stroke: rect.getAttribute('stroke'),
55
+ dashArray: rect.getAttribute('stroke-dasharray'),
56
+ fillOpacity: rect.getAttribute('fill-opacity'),
57
+ pinnedAttr: rect.getAttribute('data-group-box-pinned'),
58
+ };
59
+ });
60
+
61
+ const idle = await readAlphaBox();
62
+
63
+ // Hover a member of alpha (alpha1) → group becomes hovered.
64
+ await page.locator('g[data-node="alpha1"]').hover();
65
+ await page.waitForTimeout(300);
66
+ const hovered = await readAlphaBox();
67
+ await page.mouse.move(10, 10);
68
+ await page.waitForTimeout(300);
69
+
70
+ // Pin alpha via group label click.
71
+ await page.locator('g[data-group-label-hit="alpha"]').first().click({ force: true });
72
+ await page.mouse.move(10, 10);
73
+ await page.waitForTimeout(300);
74
+ const pinned = await readAlphaBox();
75
+
76
+ await browser.close();
77
+
78
+ const results = {
79
+ idle_stroke15: idle && idle.strokeWidth === '1.5',
80
+ idle_dashed: idle && (idle.dashArray || '').startsWith('6'),
81
+ idle_pinnedFalse: idle && idle.pinnedAttr === 'false',
82
+ hovered_stroke2: hovered && hovered.strokeWidth === '2',
83
+ hovered_solid: hovered && (hovered.dashArray === 'none' || hovered.dashArray === null),
84
+ hovered_pinnedFalse: hovered && hovered.pinnedAttr === 'false',
85
+ pinned_stroke3: pinned && pinned.strokeWidth === '3',
86
+ pinned_solid: pinned && (pinned.dashArray === 'none' || pinned.dashArray === null),
87
+ pinned_pinnedTrue: pinned && pinned.pinnedAttr === 'true',
88
+ pinned_strongerFill: pinned && hovered && parseFloat(pinned.fillOpacity) > parseFloat(hovered.fillOpacity),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} group-box pinned state:`, JSON.stringify(results),
92
+ `\n idle=`, idle,
93
+ `\n hovered=`, hovered,
94
+ `\n pinned=`, pinned);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,68 @@
1
+ /* Round 19 verification: each group-box label carries a member-count
2
+ * tspan ("· N"), reflecting the actual member count of the group. */
3
+ import { chromium } from 'playwright';
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
7
+ const browser = await chromium.launch({ headless: true });
8
+
9
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
10
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
11
+ await ctx.addInitScript(() => {
12
+ try {
13
+ localStorage.setItem('anet-theme', 'cyber');
14
+ localStorage.removeItem('anet-brand');
15
+ localStorage.removeItem('anet-topo-view');
16
+ localStorage.setItem('anet-topo-layout', 'grid');
17
+ sessionStorage.setItem('anet_v3_auth', '1');
18
+ } catch {}
19
+ });
20
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
21
+ await ctx.route('**/api/hub/status*', async (route) => {
22
+ const r = await route.fetch();
23
+ const b = await r.json();
24
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
25
+ // A站 with 4 members, B站 with 2 members → 2 group boxes (counts 4 and 2).
26
+ const aliases = ['A站红', 'A站蓝', 'A站绿', 'A站黄', 'B站乙', 'B站丙'];
27
+ const sessions = aliases.map(a => ({
28
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
29
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
30
+ }));
31
+ await route.fulfill({ response: r, json: { ...b, sessions } });
32
+ });
33
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
34
+ const page = await ctx.newPage();
35
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
36
+ await page.waitForFunction(() => {
37
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
38
+ return !!svg && svg.querySelectorAll('g[data-group]').length === 2;
39
+ }, { timeout: 30000 }).catch(() => {});
40
+ await page.waitForTimeout(400);
41
+
42
+ const labels = await page.$$eval('svg[viewBox="0 0 1000 680"] g[data-group]', els => els.map(g => {
43
+ const text = g.querySelector('text');
44
+ const tspan = text.querySelector('tspan');
45
+ return {
46
+ key: g.getAttribute('data-group'),
47
+ full: text.textContent.replace(/\s+/g, ' ').trim(),
48
+ chipFontSize: tspan ? +tspan.getAttribute('font-size') : null,
49
+ chipFontWeight: tspan ? tspan.getAttribute('font-weight') : null,
50
+ };
51
+ }));
52
+
53
+ await browser.close();
54
+ const a = labels.find(l => l.key === 'A站');
55
+ const b = labels.find(l => l.key === 'B站');
56
+ // R58 appended status-mix pips inside the same <text>, and SVG textContent
57
+ // concatenates tspans without preserving the visual `dx` gaps — so what
58
+ // renders as "A站 · 4 4w" surfaces as the string "A站· 44w". The R19
59
+ // count chip is still the second tspan; verify by matching "Group·" then
60
+ // the count, allowing any trailing pip text.
61
+ const results = {
62
+ bothGroupsHaveChip: /^A站·\s*4/.test(a?.full || '') && /^B站·\s*2/.test(b?.full || ''),
63
+ chipSmallerFont: a?.chipFontSize === 11,
64
+ chipLighterWeight: a?.chipFontWeight === '400',
65
+ };
66
+ const ok = Object.values(results).every(Boolean);
67
+ console.log(`${ok ? '✅' : '❌'} group count chip:`, JSON.stringify(results), 'labels=', labels);
68
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,82 @@
1
+ /* Round 18 verification: hovering a node in a multi-member group upgrades
2
+ * the group-box to accent treatment (solid stroke, thicker, legendAccent
3
+ * colour) and brightens the label; other groups dim to 0.28; resting
4
+ * appearance unchanged. */
5
+ import { chromium } from 'playwright';
6
+ import { readFileSync } from 'node:fs';
7
+
8
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
9
+ const browser = await chromium.launch({ headless: true });
10
+
11
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
12
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
13
+ await ctx.addInitScript(() => {
14
+ try {
15
+ localStorage.setItem('anet-theme', 'cyber');
16
+ localStorage.removeItem('anet-brand');
17
+ localStorage.removeItem('anet-topo-view');
18
+ localStorage.setItem('anet-topo-layout', 'grid');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ // Two prefix groups (A站 / B站), each ≥2 members so groupBoxes renders.
28
+ const aliases = ['A站红', 'A站蓝', 'A站绿', 'B站乙', 'B站丙', 'B站丁'];
29
+ const sessions = aliases.map(a => ({
30
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
31
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
32
+ }));
33
+ await route.fulfill({ response: r, json: { ...b, sessions } });
34
+ });
35
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
36
+ const page = await ctx.newPage();
37
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
38
+ await page.waitForFunction(() => {
39
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
40
+ return !!svg && svg.querySelectorAll('g[data-group]').length === 2;
41
+ }, { timeout: 30000 }).catch(() => {});
42
+ await page.waitForTimeout(400);
43
+
44
+ const readGroup = key => page.evaluate(k => {
45
+ const g = document.querySelector(`g[data-group="${k}"]`);
46
+ if (!g) return null;
47
+ const rect = g.querySelector('rect');
48
+ const text = g.querySelector('text');
49
+ return {
50
+ opacity: parseFloat(getComputedStyle(g).opacity || '1'),
51
+ stroke: rect.getAttribute('stroke'),
52
+ strokeWidth: +rect.getAttribute('stroke-width'),
53
+ strokeDasharray: rect.getAttribute('stroke-dasharray'),
54
+ fillOpacity: rect.getAttribute('fill-opacity'),
55
+ textFill: text.getAttribute('fill'),
56
+ };
57
+ }, key);
58
+
59
+ // Resting state (no hover)
60
+ const restA = await readGroup('A站');
61
+ const restB = await readGroup('B站');
62
+
63
+ // Hover a node in group A站
64
+ await page.locator('g[data-node="A站红"]').hover();
65
+ await page.waitForTimeout(350); // allow the 200ms transition to settle
66
+
67
+ const hoverA = await readGroup('A站');
68
+ const hoverB = await readGroup('B站');
69
+
70
+ await browser.close();
71
+ const results = {
72
+ restADashedThin: restA?.strokeDasharray === '6 6' && restA?.strokeWidth === 1.5,
73
+ restBDashedThin: restB?.strokeDasharray === '6 6' && restB?.strokeWidth === 1.5,
74
+ hoveredASolidThick: hoverA?.strokeDasharray === 'none' && hoverA?.strokeWidth === 2,
75
+ hoveredAAccentStroke: hoverA?.stroke === '#67e8f9', // dark legendAccent
76
+ hoveredABrighterText: hoverA?.textFill === '#e5e7eb', // dark legendHeadline
77
+ otherBDimmedOpacity: Math.abs((hoverB?.opacity ?? 1) - 0.28) < 0.02,
78
+ otherBKeepsDashed: hoverB?.strokeDasharray === '6 6',
79
+ };
80
+ const ok = Object.values(results).every(Boolean);
81
+ console.log(`${ok ? '✅' : '❌'} group hover linkage:`, JSON.stringify(results));
82
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,119 @@
1
+ /* Round 86 verification: hovering a group label previews the same
2
+ * dim mechanism as hovering a member node. R63 wired click-to-pin
3
+ * but forgot the hover preview — same R83 hover/click consistency
4
+ * gap. Now closed for group labels too.
5
+ *
6
+ * Fleet: 3 alpha + 2 beta in grid layout. Hover alpha label →
7
+ * alpha nodes stay bright, beta nodes dim. Hover beta label →
8
+ * inverse. Click alpha label → pin (R63 existing). Hover beta
9
+ * while alpha pinned → live transient preview of beta, alpha dims.
10
+ * Leave beta label → alpha pin restores (activeGroup =
11
+ * hoveredGroup ?? pinnedGroup).
12
+ */
13
+ import { chromium } from 'playwright';
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'grid');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
25
+ sessionStorage.removeItem('anet-topo-pinned-status');
26
+ sessionStorage.removeItem('anet-topo-pinned-group');
27
+ } catch {}
28
+ });
29
+
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias) => ({
36
+ alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
37
+ network_id: nid, project_dir: null,
38
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ const sessions = [mk('alpha1'), mk('alpha2'), mk('alpha3'), mk('beta1'), mk('beta2')];
41
+ await route.fulfill({ response: r, json: { ...b, sessions } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForFunction(() => document.querySelectorAll('[data-group-label-hit]').length >= 2, { timeout: 30000 });
48
+ await page.waitForTimeout(400);
49
+
50
+ const ops = () => page.evaluate(() => {
51
+ const o = {};
52
+ for (const a of ['alpha1', 'alpha2', 'alpha3', 'beta1', 'beta2']) {
53
+ const g = document.querySelector(`g[data-node="${a}"]`);
54
+ o[a] = g ? +(g.style.opacity || '1') : null;
55
+ }
56
+ return o;
57
+ });
58
+
59
+ const before = await ops();
60
+
61
+ // Hover the alpha label — alphas should stay bright, betas dim.
62
+ await page.locator('[data-group-label-hit="alpha"]').hover();
63
+ await page.waitForTimeout(250);
64
+ const onHoverAlpha = await ops();
65
+
66
+ // Leave (move mouse far away) — baseline restores.
67
+ await page.mouse.move(10, 10);
68
+ await page.waitForTimeout(250);
69
+ const afterLeaveAlpha = await ops();
70
+
71
+ // Hover beta label — inverse.
72
+ await page.locator('[data-group-label-hit="beta"]').hover();
73
+ await page.waitForTimeout(250);
74
+ const onHoverBeta = await ops();
75
+
76
+ await page.mouse.move(10, 10);
77
+ await page.waitForTimeout(250);
78
+
79
+ // Click alpha label to pin → alpha pinned (R63 behaviour preserved).
80
+ await page.locator('[data-group-label-hit="alpha"]').click();
81
+ await page.mouse.move(10, 10);
82
+ await page.waitForTimeout(250);
83
+ const afterPinAlpha = await ops();
84
+
85
+ // Hover beta while alpha is pinned — transient preview wins.
86
+ await page.locator('[data-group-label-hit="beta"]').hover();
87
+ await page.waitForTimeout(250);
88
+ const onHoverBetaWhilePin = await ops();
89
+
90
+ // Leave beta — alpha pin restores.
91
+ await page.mouse.move(10, 10);
92
+ await page.waitForTimeout(250);
93
+ const afterLeaveBetaWhilePin = await ops();
94
+
95
+ await browser.close();
96
+
97
+ const bright = (v) => v != null && v >= 0.55;
98
+ const dim = (v) => v != null && v < 0.4;
99
+ const results = {
100
+ before_baseline: bright(before.alpha1) && bright(before.beta1),
101
+ hoverAlpha_alphasBright: bright(onHoverAlpha.alpha1) && bright(onHoverAlpha.alpha2) && bright(onHoverAlpha.alpha3),
102
+ hoverAlpha_betasDim: dim(onHoverAlpha.beta1) && dim(onHoverAlpha.beta2),
103
+ leaveAlpha_restores: bright(afterLeaveAlpha.alpha1) && bright(afterLeaveAlpha.beta1),
104
+ hoverBeta_betasBright: bright(onHoverBeta.beta1) && bright(onHoverBeta.beta2),
105
+ hoverBeta_alphasDim: dim(onHoverBeta.alpha1) && dim(onHoverBeta.alpha2),
106
+ pinAlpha_keepsAlpha: bright(afterPinAlpha.alpha1) && bright(afterPinAlpha.alpha2),
107
+ pinAlpha_dimsBeta: dim(afterPinAlpha.beta1) && dim(afterPinAlpha.beta2),
108
+ hoverBetaPin_transient: bright(onHoverBetaWhilePin.beta1) && dim(onHoverBetaWhilePin.alpha1),
109
+ leaveBetaPin_alphaRestored: bright(afterLeaveBetaWhilePin.alpha1) && dim(afterLeaveBetaWhilePin.beta1),
110
+ };
111
+ const ok = Object.values(results).every(Boolean);
112
+ console.log(`${ok ? '✅' : '❌'} group-label hover:`, JSON.stringify(results),
113
+ `\n before=`, before,
114
+ `\n onHoverAlpha=`, onHoverAlpha,
115
+ `\n onHoverBeta=`, onHoverBeta,
116
+ `\n afterPinAlpha=`, afterPinAlpha,
117
+ `\n onHoverBetaPin=`, onHoverBetaWhilePin,
118
+ `\n leaveBetaPin=`, afterLeaveBetaWhilePin);
119
+ process.exit(ok ? 0 : 1);