@sleep2agi/agent-network-dashboard 0.4.6-preview.99 → 0.5.0

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 (448) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/diagnostics/route-bundle-stats.json +31 -31
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/routes-manifest.json +16 -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/server/[hostname]/agents/route/app-paths-manifest.json +3 -0
  34. package/.next/server/app/api/hub/server/[hostname]/agents/route/build-manifest.json +9 -0
  35. package/.next/server/app/api/hub/server/[hostname]/agents/route/server-reference-manifest.json +4 -0
  36. package/.next/server/app/api/hub/server/[hostname]/agents/route.js +7 -0
  37. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.map +5 -0
  38. package/.next/server/app/api/hub/server/[hostname]/agents/route.js.nft.json +1 -0
  39. package/.next/server/app/api/hub/server/[hostname]/agents/route_client-reference-manifest.js +3 -0
  40. package/.next/server/app/api/hub/server/[hostname]/health/route/app-paths-manifest.json +3 -0
  41. package/.next/server/app/api/hub/server/[hostname]/health/route/build-manifest.json +9 -0
  42. package/.next/server/app/api/hub/server/[hostname]/health/route/server-reference-manifest.json +4 -0
  43. package/.next/server/app/api/hub/server/[hostname]/health/route.js +7 -0
  44. package/.next/server/app/api/hub/server/[hostname]/health/route.js.map +5 -0
  45. package/.next/server/app/api/hub/server/[hostname]/health/route.js.nft.json +1 -0
  46. package/.next/server/app/api/hub/server/[hostname]/health/route_client-reference-manifest.js +3 -0
  47. package/.next/server/app/index.html +2 -2
  48. package/.next/server/app/index.rsc +14 -14
  49. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  50. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  55. package/.next/server/app/login.html +2 -2
  56. package/.next/server/app/login.rsc +14 -14
  57. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  58. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  59. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  60. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  61. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  62. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  63. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app/logs.html +2 -2
  65. package/.next/server/app/logs.rsc +14 -14
  66. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  67. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  68. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  69. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  70. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  71. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  72. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  73. package/.next/server/app/messages.html +2 -2
  74. package/.next/server/app/messages.rsc +14 -14
  75. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  76. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  77. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  78. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  80. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  81. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/node.html +2 -2
  83. package/.next/server/app/node.rsc +14 -14
  84. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  85. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  86. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  87. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  88. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  89. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  90. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  91. package/.next/server/app/nodes.html +2 -2
  92. package/.next/server/app/nodes.rsc +14 -14
  93. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  94. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  95. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  96. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  97. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  98. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  99. package/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  101. package/.next/server/app/server-logs.html +2 -2
  102. package/.next/server/app/server-logs.rsc +14 -14
  103. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  104. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  105. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  106. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  107. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  108. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  109. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  110. package/.next/server/app/settings/networks.html +2 -2
  111. package/.next/server/app/settings/networks.rsc +14 -14
  112. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  113. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  114. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  115. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  116. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  117. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  118. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  119. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  120. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  121. package/.next/server/app/settings/tokens.html +2 -2
  122. package/.next/server/app/settings/tokens.rsc +14 -14
  123. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  124. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  125. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  126. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  127. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  128. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  129. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  130. package/.next/server/app/settings.html +2 -2
  131. package/.next/server/app/settings.rsc +14 -14
  132. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  133. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  134. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  135. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  136. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  137. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  138. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  139. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  140. package/.next/server/app/tasks.html +2 -2
  141. package/.next/server/app/tasks.rsc +14 -14
  142. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  143. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  144. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  145. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  146. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  147. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  148. package/.next/server/app-paths-manifest.json +2 -0
  149. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js +3 -0
  150. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_agents_route_actions_0vr92s4.js.map +1 -0
  151. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js +3 -0
  152. package/.next/server/chunks/0dk5_next-internal_server_app_api_hub_server_[hostname]_health_route_actions_0vr-bv5.js.map +1 -0
  153. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js +3 -0
  154. package/.next/server/chunks/[root-of-the-server]__05nyivw._.js.map +1 -0
  155. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js +3 -0
  156. package/.next/server/chunks/[root-of-the-server]__0floe3h._.js.map +1 -0
  157. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js +1 -1
  158. package/.next/server/chunks/[root-of-the-server]__0tx8s8i._.js.map +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -2
  160. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  161. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  162. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  163. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  164. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  165. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  166. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  167. package/.next/server/middleware-build-manifest.js +3 -3
  168. package/.next/server/pages/404.html +2 -2
  169. package/.next/server/pages/500.html +1 -1
  170. package/.next/server/server-reference-manifest.js +1 -1
  171. package/.next/server/server-reference-manifest.json +1 -1
  172. package/.next/static/chunks/0aauz~36q5n2a.css +2 -0
  173. package/.next/static/chunks/0f~0kgy2pycyc.js +1 -0
  174. package/.next/static/chunks/0l_~q07bhpkcx.js +7 -0
  175. package/.next/static/chunks/1175-dp18a1e_.js +1 -0
  176. package/.next/static/chunks/1552j0415oeal.js +4 -0
  177. package/.next/trace +2 -2
  178. package/.next/trace-build +1 -1
  179. package/.next/types/routes.d.ts +3 -1
  180. package/.next/types/validator.ts +18 -0
  181. package/app/api/hub/server/[hostname]/agents/route.ts +62 -0
  182. package/app/api/hub/server/[hostname]/health/route.ts +58 -0
  183. package/app/api/hub/servers/route.ts +88 -1
  184. package/app/components/CommandPalette.tsx +41 -1
  185. package/app/components/HelpOverlay.tsx +55 -0
  186. package/app/components/ServersDrawer.tsx +181 -5
  187. package/app/components/TopoGraph.tsx +5514 -336
  188. package/app/globals.css +93 -6
  189. package/app/lib/vendorIdentity.ts +14 -3
  190. package/docs/rfc/v0.10.0-hero3-dashboard-upgrade.md +230 -0
  191. package/package.json +2 -2
  192. package/public/sleep2agi-logo.svg +11 -0
  193. package/public/vendors/claude.svg +11 -0
  194. package/public/vendors/minimax.svg +11 -0
  195. package/public/vendors/openai.svg +12 -0
  196. package/screenshots/r293-p0/after-full.png +0 -0
  197. package/screenshots/r293-p0/after-topo.png +0 -0
  198. package/screenshots/r293-p0/baseline-full.png +0 -0
  199. package/screenshots/r293-p0/baseline-topo.png +0 -0
  200. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  201. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  202. package/screenshots/r293-p0/servers-drawer-v0.10.0-full.png +0 -0
  203. package/screenshots/r293-p0/servers-drawer-v0.10.0.png +0 -0
  204. package/screenshots/r293-p0/wave1-after-full.png +0 -0
  205. package/screenshots/r293-p0/wave1-after-topo.png +0 -0
  206. package/scripts/p0-servers-drawer-screenshot.mjs +99 -0
  207. package/scripts/p0-topo-screenshot.mjs +82 -0
  208. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  209. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  210. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  211. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  212. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  213. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  214. package/scripts/topo-brand-logo-transition-test.mjs +94 -0
  215. package/scripts/topo-brand-watermark-test.mjs +97 -0
  216. package/scripts/topo-canvas-aria-test.mjs +108 -0
  217. package/scripts/topo-canvas-brand-fade-test.mjs +87 -0
  218. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  219. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  220. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  221. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  222. package/scripts/topo-chip-focus-test.mjs +95 -0
  223. package/scripts/topo-chip-row-gap-test.mjs +90 -0
  224. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  225. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  226. package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
  227. package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
  228. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  229. package/scripts/topo-chrome-active-weight-test.mjs +104 -0
  230. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  231. package/scripts/topo-chrome-gap-test.mjs +73 -0
  232. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  233. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  234. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  235. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  236. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  237. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  238. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  239. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  240. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  241. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  242. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  243. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  244. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  245. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  246. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  247. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  248. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  249. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  250. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  251. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  252. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  253. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  254. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  255. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  256. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  257. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  258. package/scripts/topo-edge-fadein-test.mjs +106 -0
  259. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  260. package/scripts/topo-edge-pill-test.mjs +105 -0
  261. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  262. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  263. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  264. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  265. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  266. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  267. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  268. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  269. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  270. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  271. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  272. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  273. package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
  274. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  275. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  276. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  277. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  278. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  279. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  280. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  281. package/scripts/topo-group-label-tint-test.mjs +99 -0
  282. package/scripts/topo-group-label-title-test.mjs +83 -0
  283. package/scripts/topo-group-pip-fadein-test.mjs +104 -0
  284. package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
  285. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  286. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  287. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  288. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  289. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  290. package/scripts/topo-header-gap-test.mjs +94 -0
  291. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  292. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  293. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  294. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  295. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  296. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  297. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  298. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  299. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  300. package/scripts/topo-hub-a11y-test.mjs +112 -0
  301. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  302. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  303. package/scripts/topo-hub-fadein-test.mjs +88 -0
  304. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  305. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  306. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  307. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  308. package/scripts/topo-hub-working-count-test.mjs +99 -0
  309. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  310. package/scripts/topo-kicker-color-test.mjs +93 -0
  311. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  312. package/scripts/topo-kicker-weight-test.mjs +98 -0
  313. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  314. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  315. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  316. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  317. package/scripts/topo-layout-toggle-test.mjs +131 -0
  318. package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
  319. package/scripts/topo-legend-compress-test.mjs +107 -0
  320. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  321. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  322. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  323. package/scripts/topo-legend-count-test.mjs +88 -0
  324. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  325. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  326. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  327. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  328. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  329. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  330. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  331. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  332. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  333. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  334. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  335. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  336. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  337. package/scripts/topo-legend-row-title-test.mjs +102 -0
  338. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  339. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  340. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  341. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  342. package/scripts/topo-minimap-radius-test.mjs +88 -0
  343. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  344. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  345. package/scripts/topo-monogram-font-test.mjs +112 -0
  346. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  347. package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
  348. package/scripts/topo-node-a11y-test.mjs +107 -0
  349. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  350. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  351. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  352. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  353. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  354. package/scripts/topo-node-title-peers-test.mjs +107 -0
  355. package/scripts/topo-node-title-test.mjs +101 -0
  356. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  357. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  358. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  359. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  360. package/scripts/topo-panel-count-unit-test.mjs +112 -0
  361. package/scripts/topo-panel-fadein-test.mjs +102 -0
  362. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  363. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  364. package/scripts/topo-panel-rx-test.mjs +90 -0
  365. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  366. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  367. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  368. package/scripts/topo-particle-stagger-test.mjs +82 -0
  369. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  370. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  371. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  372. package/scripts/topo-pin-intersection-test.mjs +121 -0
  373. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  374. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  375. package/scripts/topo-prefix-opacity-test.mjs +107 -0
  376. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  377. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  378. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  379. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  380. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  381. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  382. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  383. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  384. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  385. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  386. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  387. package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
  388. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  389. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  390. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  391. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  392. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  393. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  394. package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
  395. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  396. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  397. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  398. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  399. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  400. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  401. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  402. package/scripts/topo-recent-row-title-test.mjs +112 -0
  403. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  404. package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
  405. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  406. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  407. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  408. package/scripts/topo-reset-spin-test.mjs +120 -0
  409. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  410. package/scripts/topo-spokes-retired-test.mjs +93 -0
  411. package/scripts/topo-starfield-density-test.mjs +89 -0
  412. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  413. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  414. package/scripts/topo-svg-focus-test.mjs +112 -0
  415. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  416. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  417. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  418. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  419. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  420. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  421. package/scripts/topo-title-tracking-test.mjs +98 -0
  422. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  423. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  424. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  425. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  426. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  427. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  428. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  429. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  430. package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
  431. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  432. package/scripts/topo-view-smooth-test.mjs +131 -0
  433. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  434. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  435. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  436. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  437. package/scripts/topo-wrapper-radius-test.mjs +73 -0
  438. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  439. package/.next/static/chunks/03w4rgx_vt8ec.js +0 -1
  440. package/.next/static/chunks/072so8rfx2tt-.css +0 -2
  441. package/.next/static/chunks/0o.k-.0kl4bz5.js +0 -2
  442. package/.next/static/chunks/121d8yvfl8l_..js +0 -7
  443. package/.next/static/chunks/148o1e4ijgwp1.js +0 -1
  444. package/scripts/topo-arrival-ping-test.mjs +0 -87
  445. package/scripts/topo-dispatch-pulse-test.mjs +0 -83
  446. /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
  447. /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
  448. /package/.next/static/{Dhky2WTh8mvJhw56taHVs → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
@@ -0,0 +1,112 @@
1
+ /* Round 181 verification: legend row pinned-state ring (R61) is
2
+ * now always-mounted with opacity-gated visibility + 150ms
3
+ * transition — smooth fade in/out on pin / unpin.
4
+ *
5
+ * Pre-R181 the ring mounted/unmounted conditionally, snapping
6
+ * on every state change. Same vocabulary as R165/R180 smooth-
7
+ * pin-mirror family, applied at the legend-row scope.
8
+ *
9
+ * Test:
10
+ * 1. Idle: ring exists in DOM but opacity=0
11
+ * 2. Click 'working' legend row → ring for working = opacity=1
12
+ * 3. Other rings (idle / offline) stay opacity=0
13
+ * 4. Re-click 'working' → unpin → opacity returns to 0
14
+ * 5. All rings carry 'opacity 150ms ease-out' transition
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const browser = await chromium.launch({ headless: true });
21
+
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
26
+ });
27
+
28
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
+ const stale = new Date(Date.now() - 10 * 60 * 1000).toISOString();
30
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias, status, last) => ({
35
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
36
+ network_id: nid, project_dir: null,
37
+ created_at: last, updated_at: last, last_seen_at: last,
38
+ });
39
+ // Mix of statuses so all three legend rows render
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('w1', 'working', fresh), mk('w2', 'working', fresh),
42
+ mk('i1', 'idle', fresh),
43
+ mk('o1', 'offline', stale),
44
+ ] } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
52
+ await page.waitForSelector('[data-legend-pin-ring]', { timeout: 10000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const probe = () => page.evaluate(() => {
56
+ const grab = (k) => {
57
+ const r = document.querySelector(`[data-legend-pin-ring="${k}"]`);
58
+ return r ? {
59
+ opacity: parseFloat(r.getAttribute('opacity') || ''),
60
+ pinnedAttr: r.getAttribute('data-legend-pin-ring-pinned'),
61
+ transition: r.style.transition || getComputedStyle(r).transition,
62
+ } : null;
63
+ };
64
+ return {
65
+ working: grab('working'),
66
+ idle: grab('idle'),
67
+ offline: grab('offline'),
68
+ };
69
+ });
70
+
71
+ const idle = await probe();
72
+
73
+ // Pin 'working' status by clicking the legend row
74
+ await page.locator('[data-legend-status="working"]').click();
75
+ await page.waitForTimeout(250);
76
+ const afterPin = await probe();
77
+
78
+ // Re-click to unpin
79
+ await page.locator('[data-legend-status="working"]').click();
80
+ await page.waitForTimeout(250);
81
+ const afterUnpin = await probe();
82
+
83
+ await browser.close();
84
+
85
+ const hasTransition = (s) =>
86
+ (s?.transition || '').includes('opacity 150ms') ||
87
+ /opacity\s+0\.15s|opacity\s+150ms/.test(s?.transition || '');
88
+
89
+ const results = {
90
+ // Idle (no pin)
91
+ three_rings_present: idle.working !== null && idle.idle !== null && idle.offline !== null,
92
+ idle_working_opacity_0: idle.working?.opacity === 0,
93
+ idle_idle_opacity_0: idle.idle?.opacity === 0,
94
+ idle_offline_opacity_0: idle.offline?.opacity === 0,
95
+ all_have_transition: hasTransition(idle.working) && hasTransition(idle.idle) && hasTransition(idle.offline),
96
+
97
+ // After pin working
98
+ pin_working_opacity_1: afterPin.working?.opacity === 1,
99
+ pin_working_pinned_attr: afterPin.working?.pinnedAttr === 'true',
100
+ pin_idle_stays_0: afterPin.idle?.opacity === 0,
101
+ pin_offline_stays_0: afterPin.offline?.opacity === 0,
102
+
103
+ // After unpin
104
+ unpin_working_opacity_0: afterUnpin.working?.opacity === 0,
105
+ unpin_working_pinned_attr: afterUnpin.working?.pinnedAttr === 'false',
106
+ };
107
+ const ok = Object.values(results).every(Boolean);
108
+ console.log(`${ok ? '✅' : '❌'} legend pin-ring fade:`, JSON.stringify(results),
109
+ `\n idle =`, idle,
110
+ `\n pinned =`, afterPin,
111
+ `\n unpinned =`, afterUnpin);
112
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,109 @@
1
+ /* Round 144 verification: legend panel rows lift 1px on hover or
2
+ * pin. Symmetric extension of R143 (recent-signal rows) to the
3
+ * sibling overlay panel.
4
+ *
5
+ * Now both side panels share matching row-level affordances:
6
+ * recent-signal row (R143): hover/pin → translateY(-1px)
7
+ * legend row (R144): hover/pin → translateY(-1px)
8
+ *
9
+ * Each row composes inside its panel's hover-shadow (R135) — two
10
+ * layers of feedback simultaneously when hovering a row.
11
+ *
12
+ * Path:
13
+ * 1. idle: working/idle/offline rows all flat
14
+ * 2. hover working row → that row lifts; others stay flat
15
+ * 3. leave hover → restored
16
+ * 4. click to pin idle row → idle lifted+pinned; working flat
17
+ * 5. Esc clears pin → idle flat again
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const browser = await chromium.launch({ headless: true });
24
+
25
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript(() => {
28
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
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, status) => ({
36
+ alias, status, 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
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('alpha', 'working'), mk('beta', 'idle'), mk('gamma', 'working'),
42
+ ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
46
+
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
50
+ await page.waitForSelector('[data-legend-status="working"]', { timeout: 10000 });
51
+ await page.waitForTimeout(400);
52
+
53
+ const inspectAll = () => page.evaluate(() => {
54
+ const rows = [...document.querySelectorAll('[data-legend-status]')];
55
+ return rows.map(row => ({
56
+ key: row.getAttribute('data-legend-status'),
57
+ lifted: row.getAttribute('data-legend-row-lifted'),
58
+ pressed: row.getAttribute('aria-pressed'),
59
+ transform: row.getAttribute('style')?.match(/translateY\([^)]+\)/)?.[0],
60
+ }));
61
+ });
62
+
63
+ const s1 = await inspectAll();
64
+
65
+ await page.locator('[data-legend-status="working"]').hover();
66
+ await page.waitForTimeout(250);
67
+ const s2 = await inspectAll();
68
+ const s2_working = s2.find(r => r.key === 'working');
69
+ const s2_idle = s2.find(r => r.key === 'idle');
70
+
71
+ await page.mouse.move(20, 20);
72
+ await page.waitForTimeout(250);
73
+ const s3 = await inspectAll();
74
+ const s3_working = s3.find(r => r.key === 'working');
75
+
76
+ await page.locator('[data-legend-status="idle"]').click();
77
+ await page.waitForTimeout(250);
78
+ await page.mouse.move(20, 20);
79
+ await page.waitForTimeout(250);
80
+ const s4 = await inspectAll();
81
+ const s4_idle = s4.find(r => r.key === 'idle');
82
+ const s4_working = s4.find(r => r.key === 'working');
83
+
84
+ await page.keyboard.press('Escape');
85
+ await page.waitForTimeout(250);
86
+ const s5 = await inspectAll();
87
+ const s5_idle = s5.find(r => r.key === 'idle');
88
+
89
+ await browser.close();
90
+
91
+ const liftStr = 'translateY(-1px)';
92
+ const results = {
93
+ s1_threeRows: s1.length === 3,
94
+ s1_allFlat: s1.every(r => r.lifted === 'false' && !r.transform),
95
+
96
+ s2_workingLifted: s2_working?.lifted === 'true' && s2_working?.transform === liftStr,
97
+ s2_idleStillFlat: s2_idle?.lifted === 'false' && !s2_idle?.transform,
98
+
99
+ s3_workingRestored: s3_working?.lifted === 'false' && !s3_working?.transform,
100
+
101
+ s4_idlePinned: s4_idle?.pressed === 'true' && s4_idle?.lifted === 'true' && s4_idle?.transform === liftStr,
102
+ s4_workingNotLifted: s4_working?.lifted === 'false' && !s4_working?.transform,
103
+
104
+ s5_idleCleared: s5_idle?.lifted === 'false' && !s5_idle?.transform && s5_idle?.pressed === 'false',
105
+ };
106
+ const ok = Object.values(results).every(Boolean);
107
+ console.log(`${ok ? '✅' : '❌'} legend-row hover lift:`, JSON.stringify(results),
108
+ `\n s1=`, s1, `\n s2=`, s2, `\n s3=`, s3, `\n s4=`, s4, `\n s5=`, s5);
109
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,80 @@
1
+ /* Round 308 verification: continue R307 legend label 减法.
2
+ * 'working node' → 'working' (drops 'node' qualifier).
3
+ * 'online idle' → 'idle' (drops 'online' qualifier).
4
+ * Plus R307 'offline' (already simplified).
5
+ *
6
+ * After R307+R308 all three legend labels are just status words:
7
+ * working / idle / offline — 3-state list at 7 / 4 / 7 chars.
8
+ *
9
+ * Contract:
10
+ * - [data-legend-row-label='working'] text === 'working'.
11
+ * - [data-legend-row-label='idle'] text === 'idle'.
12
+ * - [data-legend-row-label='offline'] text === 'offline' (R307).
13
+ * - No labels contain ' node' or 'online ' substrings.
14
+ * - R306 focus-ring-1 + R304 sub-hint ls=0.15 + R294 pulse absent intact.
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
26
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias, model, status) => ({
32
+ alias, status, model, runtime: 'claude-code-cli',
33
+ 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
+ mk('alpha', 'claude-opus-4', 'working'),
38
+ mk('beta', 'gpt-4o', 'idle'),
39
+ mk('gamma', 'claude-sonnet-4', 'offline'),
40
+ ] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForSelector('[data-legend-row-label="offline"]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const sel = (s) => document.querySelector(s);
52
+ const txt = (el) => (el?.textContent || '').trim();
53
+ const layoutRing = sel('[data-topo-chrome-layout="ring"]');
54
+ return {
55
+ workingText: txt(sel('[data-legend-row-label="working"]')),
56
+ idleText: txt(sel('[data-legend-row-label="idle"]')),
57
+ offlineText: txt(sel('[data-legend-row-label="offline"]')),
58
+ layoutRingCls: layoutRing?.className ?? '',
59
+ subhintLs: sel('[data-recent-signal-empty-hint]')?.getAttribute('letter-spacing') ?? null,
60
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
61
+ };
62
+ });
63
+ await browser.close();
64
+
65
+ const results = {
66
+ working_simplified: probe.workingText === 'working',
67
+ idle_simplified: probe.idleText === 'idle',
68
+ offline_kept_r307: probe.offlineText === 'offline',
69
+ no_node_qualifier: !/ node\b/i.test(probe.workingText),
70
+ no_online_qualifier: !/online /i.test(probe.idleText),
71
+ r306_focus_ring_1_kept: probe.layoutRingCls.includes('focus-visible:ring-1'),
72
+ r304_subhint_ls_0_15: probe.subhintLs === '0.15',
73
+ r294_pulse_absent: probe.pulseCount === 0,
74
+ };
75
+ const ok = Object.values(results).every(Boolean);
76
+ console.log(`${ok ? '✅' : '❌'} legend rows simplified:`, JSON.stringify(results),
77
+ '\n working:', JSON.stringify(probe.workingText),
78
+ '\n idle:', JSON.stringify(probe.idleText),
79
+ '\n offline:', JSON.stringify(probe.offlineText));
80
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,124 @@
1
+ /* Round 219 verification: legend row text gets letter-spacing pin
2
+ * signature 0px → 0.5px, mirroring R218 group label treatment.
3
+ * Pre-R219 hover and pin shared fill colour and were typographically
4
+ * identical at letter form.
5
+ *
6
+ * Test (cyber dark, 4 working sessions):
7
+ * 1. Three legend rows (working / idle / offline) render with
8
+ * data-legend-row-label.
9
+ * 2. Idle: data-legend-row-label-pinned='false' on all rows,
10
+ * computed letter-spacing ~ 0 (CSS resolves '0px' → 'normal').
11
+ * 3. Inline transition includes 'letter-spacing 150ms'.
12
+ * 4. Click the working legend row → that row becomes pinned.
13
+ * 5. Working row: pinned='true', letter-spacing ~ 0.5px.
14
+ * 6. Other rows (idle / offline) stay unpinned + 0 spacing.
15
+ * 7. Click pinned row again → unpin → back to 0 spacing.
16
+ *
17
+ * Same 'normal' vs '0px' CSS-equivalence parser as R218 (CSS
18
+ * resolves letter-spacing: 0px to keyword 'normal' in computed style).
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
24
+ const browser = await chromium.launch({ headless: true });
25
+
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+
32
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias) => ({
38
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ await route.fulfill({ response: r, json: { ...b, sessions: [
43
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
44
+ ] } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
52
+ await page.waitForSelector('[data-legend-row-label]', { timeout: 10000, state: 'attached' });
53
+ await page.waitForTimeout(300);
54
+
55
+ const probe = async () => page.evaluate(() => {
56
+ return [...document.querySelectorAll('[data-legend-row-label]')].map(el => {
57
+ const cs = getComputedStyle(el);
58
+ return {
59
+ key: el.getAttribute('data-legend-row-label'),
60
+ pinned: el.getAttribute('data-legend-row-label-pinned'),
61
+ spacing: cs.letterSpacing,
62
+ transition: el.style.transition,
63
+ };
64
+ });
65
+ });
66
+
67
+ const idle = await probe();
68
+
69
+ // Click the working row to pin it. Use the row wrapper <g
70
+ // data-legend-status="working"> to ensure the click hits the hit
71
+ // area (the text itself has pointerEvents:'none' via the parent <g>).
72
+ await page.click('[data-legend-status="working"]');
73
+ await page.waitForTimeout(200);
74
+ const pinned = await probe();
75
+
76
+ await page.click('[data-legend-status="working"]');
77
+ await page.waitForTimeout(200);
78
+ const unpinned = await probe();
79
+
80
+ await browser.close();
81
+
82
+ // CSS resolves letter-spacing: 0px to 'normal' in computed style.
83
+ // Accept both forms as "zero spacing".
84
+ const near = (s, want, tol = 0.15) => {
85
+ if (want === 0 && (s === 'normal' || s === '0px' || s === '0' || !s)) return true;
86
+ const got = parseFloat(s || '0');
87
+ return Math.abs(got - want) < tol;
88
+ };
89
+ const hasLetterSpacingT = (s) => /letter-spacing\s+(?:150ms|0\.15s)/.test(s || '');
90
+
91
+ const idleWorking = idle.find(p => p.key === 'working');
92
+ const idleIdle = idle.find(p => p.key === 'idle');
93
+ const idleOffline = idle.find(p => p.key === 'offline');
94
+ const pinWorking = pinned.find(p => p.key === 'working');
95
+ const pinIdle = pinned.find(p => p.key === 'idle');
96
+ const unpinWorking = unpinned.find(p => p.key === 'working');
97
+
98
+ const results = {
99
+ three_rows_present: idle.length === 3,
100
+ has_working: !!idleWorking,
101
+ has_idle: !!idleIdle,
102
+ has_offline: !!idleOffline,
103
+
104
+ // Idle: all unpinned, spacing 0, transition present
105
+ all_idle_unpinned: idle.every(p => p.pinned === 'false'),
106
+ all_idle_spacing_zero: idle.every(p => near(p.spacing, 0)),
107
+ all_have_transition: idle.every(p => hasLetterSpacingT(p.transition)),
108
+
109
+ // After pin click: working pinned, idle unchanged
110
+ working_pinned_attr: pinWorking?.pinned === 'true',
111
+ working_spacing_half: near(pinWorking?.spacing, 0.5),
112
+ idle_stays_unpinned: pinIdle?.pinned === 'false',
113
+ idle_spacing_unchanged: near(pinIdle?.spacing, 0),
114
+
115
+ // After unpin click: working back to unpinned + 0 spacing
116
+ unpin_attr_false: unpinWorking?.pinned === 'false',
117
+ unpin_spacing_zero: near(unpinWorking?.spacing, 0),
118
+ };
119
+ const ok = Object.values(results).every(Boolean);
120
+ console.log(`${ok ? '✅' : '❌'} legend-row pin spacing:`, JSON.stringify(results),
121
+ `\n idle:`, idle,
122
+ `\n pinned:`, pinned,
123
+ `\n unpinned:`, unpinned);
124
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,95 @@
1
+ /* Round 105 verification: legend rows in the SVG panel pick up a
2
+ * row-background tint on hover and pin, mirroring R104's treatment
3
+ * on the recent-signal panel. The tint uses the ROW'S OWN swatch
4
+ * colour (working=green, idle=teal, offline=slate) so the user's
5
+ * eye associates the highlight with its swatch instead of a generic
6
+ * accent.
7
+ *
8
+ * Verifies idle / hover / pinned states via the new data-legend-
9
+ * row-tinted attribute + actual fill/opacity. Pin is stronger than
10
+ * hover; idle is transparent.
11
+ */
12
+ import { chromium } from 'playwright';
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
16
+ const browser = await chromium.launch({ headless: true });
17
+
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
+ sessionStorage.setItem('anet_v3_auth', '1');
24
+ sessionStorage.removeItem('anet-topo-pinned-status');
25
+ } catch {}
26
+ });
27
+
28
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const mk = (alias, status) => ({
34
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [
39
+ mk('wkr1', 'working'),
40
+ mk('idl1', 'idle'),
41
+ mk('off1', 'offline'),
42
+ ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
45
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
48
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
49
+ await page.waitForTimeout(400);
50
+
51
+ const readRow = (key) => page.evaluate((k) => {
52
+ const g = document.querySelector(`[data-legend-status="${k}"]`);
53
+ if (!g) return null;
54
+ const rect = g.querySelector('rect');
55
+ return {
56
+ tinted: rect?.getAttribute('data-legend-row-tinted') || '',
57
+ fill: rect?.getAttribute('fill') || '',
58
+ opacity: rect?.getAttribute('opacity') || '',
59
+ };
60
+ }, key);
61
+
62
+ const before = await Promise.all(['working', 'idle', 'offline'].map(readRow));
63
+
64
+ // Hover working.
65
+ await page.locator('[data-legend-status="working"]').hover();
66
+ await page.waitForTimeout(250);
67
+ const onHoverWorking = await Promise.all(['working', 'idle', 'offline'].map(readRow));
68
+
69
+ // Move away — clears hover.
70
+ await page.mouse.move(10, 10);
71
+ await page.waitForTimeout(250);
72
+
73
+ // Pin idle.
74
+ await page.locator('[data-legend-status="idle"]').click();
75
+ await page.waitForTimeout(250);
76
+ const onPinIdle = await Promise.all(['working', 'idle', 'offline'].map(readRow));
77
+
78
+ await browser.close();
79
+
80
+ const opacity = (s) => parseFloat(s) || 0;
81
+ const results = {
82
+ before_allTransparent: before.every(r => r?.tinted === 'none' && r?.fill === 'transparent'),
83
+ hoverWorking_workingTinted: onHoverWorking[0]?.tinted === 'hover' && onHoverWorking[0]?.fill !== 'transparent',
84
+ hoverWorking_othersUnchanged: onHoverWorking.slice(1).every(r => r?.tinted === 'none'),
85
+ hoverWorking_opacityLow: opacity(onHoverWorking[0]?.opacity || '') > 0 && opacity(onHoverWorking[0]?.opacity || '') < 0.2,
86
+ pinIdle_idlePinned: onPinIdle[1]?.tinted === 'pinned' && onPinIdle[1]?.fill !== 'transparent',
87
+ pinIdle_pinStrongerThanHover: opacity(onPinIdle[1]?.opacity || '') > opacity(onHoverWorking[0]?.opacity || ''),
88
+ pinIdle_otherRowsClean: onPinIdle.filter((_, i) => i !== 1).every(r => r?.tinted === 'none'),
89
+ };
90
+ const ok = Object.values(results).every(Boolean);
91
+ console.log(`${ok ? '✅' : '❌'} legend-row tint:`, JSON.stringify(results),
92
+ `\n before=`, before,
93
+ `\n onHoverWorking=`, onHoverWorking,
94
+ `\n onPinIdle=`, onPinIdle);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,102 @@
1
+ /* Round 149 verification: legend rows gain a <title> tooltip
2
+ * symmetric with R148's recent-signal row tooltip. Closes the
3
+ * R97 idiom on the panel side — anywhere the UI shows "N" should
4
+ * hover-explain WHICH N.
5
+ *
6
+ * Both side panels now have matching row-tooltip affordances:
7
+ * recent-signal row (R148): full from/to/timestamp/content + pin hint
8
+ * legend row (R149): status label + matched-aliases + pin hint
9
+ *
10
+ * Fleet: 3 working / 2 idle / 1 offline so all three bucket rows
11
+ * show non-zero. Verify each row's title contains:
12
+ * - status label + count
13
+ * - alias list of matching nodes
14
+ * - "click to pin" hint when not pinned
15
+ *
16
+ * Then click idle to pin → title flips to "click to release pin".
17
+ */
18
+ import { chromium } from 'playwright';
19
+ import { readFileSync } from 'node:fs';
20
+
21
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
22
+ const browser = await chromium.launch({ headless: true });
23
+
24
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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, status) => ({
36
+ alias, status, 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
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('worker-a', 'working'), mk('worker-b', 'working'), mk('worker-c', 'working'),
42
+ mk('idle-a', 'idle'), mk('idle-b', 'idle'),
43
+ mk('off-a', 'offline'),
44
+ ] } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ // 3 working + 2 idle + 1 offline = 6 sessions; offline nodes still
52
+ // render as g[data-node] (line ~2754 [...onlineNodes, ...offlineNodes]
53
+ // .map). Initial count check was off-by-one.
54
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 6, { timeout: 30000 });
55
+ await page.waitForSelector('[data-legend-status="working"]', { timeout: 10000 });
56
+ await page.waitForTimeout(400);
57
+
58
+ const readTitle = (key) => page.evaluate((k) => {
59
+ const row = document.querySelector(`[data-legend-status="${k}"]`);
60
+ return row?.querySelector('title')?.textContent || '';
61
+ }, key);
62
+
63
+ const wTitle = await readTitle('working');
64
+ const iTitle = await readTitle('idle');
65
+ const oTitle = await readTitle('offline');
66
+
67
+ // Pin idle → its title flips
68
+ await page.locator('[data-legend-status="idle"]').click();
69
+ await page.waitForTimeout(200);
70
+ const iPinnedTitle = await readTitle('idle');
71
+
72
+ await browser.close();
73
+
74
+ const results = {
75
+ // Working row: 3 working aliases
76
+ w_hasLabel: wTitle.includes('working node · 3'),
77
+ w_hasWorkerA: wTitle.includes('worker-a'),
78
+ w_hasWorkerB: wTitle.includes('worker-b'),
79
+ w_hasWorkerC: wTitle.includes('worker-c'),
80
+ w_pinHint: wTitle.includes('click to pin'),
81
+
82
+ // Idle row: 2 idle aliases
83
+ i_hasLabel: iTitle.includes('online idle · 2'),
84
+ i_hasIdleA: iTitle.includes('idle-a'),
85
+ i_hasIdleB: iTitle.includes('idle-b'),
86
+ i_pinHint: iTitle.includes('click to pin'),
87
+
88
+ // Offline row: 1 offline alias
89
+ o_hasLabel: oTitle.includes('offline / no SSE · 1'),
90
+ o_hasOffA: oTitle.includes('off-a'),
91
+
92
+ // After pin: title hint flipped
93
+ iPinned_releaseHint: iPinnedTitle.includes('click to release pin'),
94
+ iPinned_noPinHint: !iPinnedTitle.includes('click to pin · hover to preview'),
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} legend-row title:`, JSON.stringify(results),
98
+ `\n workingTitle:\n${wTitle}`,
99
+ `\n idleTitle:\n${iTitle}`,
100
+ `\n offlineTitle:\n${oTitle}`,
101
+ `\n idlePinnedTitle:\n${iPinnedTitle}`);
102
+ process.exit(ok ? 0 : 1);