@sleep2agi/agent-network-dashboard 0.4.6 → 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 (493) 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 +107 -0
  185. package/app/components/HelpOverlay.tsx +109 -1
  186. package/app/components/ServersDrawer.tsx +181 -5
  187. package/app/components/TopoGraph.tsx +6409 -286
  188. package/app/globals.css +110 -5
  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-filter-pill-test.mjs +101 -0
  209. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  210. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  211. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  212. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  213. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  214. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  215. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  216. package/scripts/topo-brand-logo-transition-test.mjs +94 -0
  217. package/scripts/topo-brand-watermark-test.mjs +97 -0
  218. package/scripts/topo-canvas-aria-test.mjs +108 -0
  219. package/scripts/topo-canvas-brand-fade-test.mjs +87 -0
  220. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  221. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  222. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  223. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  224. package/scripts/topo-chip-focus-test.mjs +95 -0
  225. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  226. package/scripts/topo-chip-row-gap-test.mjs +90 -0
  227. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  228. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  229. package/scripts/topo-chip-unit-demotion-test.mjs +93 -0
  230. package/scripts/topo-chiprow-data-weight-test.mjs +94 -0
  231. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  232. package/scripts/topo-chrome-active-weight-test.mjs +104 -0
  233. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  234. package/scripts/topo-chrome-gap-test.mjs +73 -0
  235. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  236. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  237. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  238. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  239. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  240. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  241. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  242. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  243. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  244. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  245. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  246. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  247. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  248. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  249. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  250. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  251. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  252. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  253. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  254. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  255. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  256. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  257. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  258. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  259. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  260. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  261. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  262. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  263. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  264. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  265. package/scripts/topo-edge-fadein-test.mjs +106 -0
  266. package/scripts/topo-edge-focus-test.mjs +113 -0
  267. package/scripts/topo-edge-hover-test.mjs +13 -5
  268. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  269. package/scripts/topo-edge-pill-test.mjs +105 -0
  270. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  271. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  272. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  273. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  274. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  275. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  276. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  277. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  278. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  279. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  280. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  281. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  282. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  283. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  284. package/scripts/topo-filter-pill-count-tabular-test.mjs +135 -0
  285. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  286. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  287. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  288. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  289. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  290. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  291. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  292. package/scripts/topo-group-count-test.mjs +6 -1
  293. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  294. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  295. package/scripts/topo-group-label-hover-test.mjs +119 -0
  296. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  297. package/scripts/topo-group-label-tint-test.mjs +99 -0
  298. package/scripts/topo-group-label-title-test.mjs +83 -0
  299. package/scripts/topo-group-pin-test.mjs +119 -0
  300. package/scripts/topo-group-pip-fadein-test.mjs +104 -0
  301. package/scripts/topo-group-pip-singletier-drop-test.mjs +131 -0
  302. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  303. package/scripts/topo-group-status-pips-test.mjs +103 -0
  304. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  305. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  306. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  307. package/scripts/topo-groupbox-live-test.mjs +100 -0
  308. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  309. package/scripts/topo-groupbox-test.mjs +4 -2
  310. package/scripts/topo-header-gap-test.mjs +94 -0
  311. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  312. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  313. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  314. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  315. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  316. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  317. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  318. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  319. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  320. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  321. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  322. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  323. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  324. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  325. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  326. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  327. package/scripts/topo-hub-a11y-test.mjs +112 -0
  328. package/scripts/topo-hub-busyness-test.mjs +100 -0
  329. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  330. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  331. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  332. package/scripts/topo-hub-fadein-test.mjs +88 -0
  333. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  334. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  335. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  336. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  337. package/scripts/topo-hub-title-test.mjs +7 -2
  338. package/scripts/topo-hub-working-count-test.mjs +99 -0
  339. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  340. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  341. package/scripts/topo-kicker-color-test.mjs +93 -0
  342. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  343. package/scripts/topo-kicker-weight-test.mjs +98 -0
  344. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  345. package/scripts/topo-label-lift-test.mjs +5 -1
  346. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  347. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  348. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  349. package/scripts/topo-layout-toggle-test.mjs +131 -0
  350. package/scripts/topo-layout-trailer-mr-test.mjs +82 -0
  351. package/scripts/topo-legend-compress-test.mjs +107 -0
  352. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  353. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  354. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  355. package/scripts/topo-legend-count-test.mjs +88 -0
  356. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  357. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  358. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  359. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  360. package/scripts/topo-legend-hover-test.mjs +113 -0
  361. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  362. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  363. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  364. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  365. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  366. package/scripts/topo-legend-pin-test.mjs +128 -0
  367. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  368. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  369. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  370. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  371. package/scripts/topo-legend-row-title-test.mjs +102 -0
  372. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  373. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  374. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  375. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  376. package/scripts/topo-minimap-radius-test.mjs +88 -0
  377. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  378. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  379. package/scripts/topo-monogram-font-test.mjs +112 -0
  380. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  381. package/scripts/topo-more-flows-letterspacing-test.mjs +111 -0
  382. package/scripts/topo-node-a11y-test.mjs +107 -0
  383. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  384. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  385. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  386. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  387. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  388. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  389. package/scripts/topo-node-title-peers-test.mjs +107 -0
  390. package/scripts/topo-node-title-test.mjs +101 -0
  391. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  392. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  393. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  394. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  395. package/scripts/topo-panel-count-unit-test.mjs +112 -0
  396. package/scripts/topo-panel-elevation-test.mjs +76 -0
  397. package/scripts/topo-panel-fadein-test.mjs +102 -0
  398. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  399. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  400. package/scripts/topo-panel-rx-test.mjs +90 -0
  401. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  402. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  403. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  404. package/scripts/topo-particle-stagger-test.mjs +82 -0
  405. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  406. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  407. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  408. package/scripts/topo-pin-intersection-test.mjs +121 -0
  409. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  410. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  411. package/scripts/topo-pin-persistence-test.mjs +105 -0
  412. package/scripts/topo-prefix-opacity-test.mjs +107 -0
  413. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  414. package/scripts/topo-pressure-hover-test.mjs +123 -0
  415. package/scripts/topo-pressure-pin-test.mjs +119 -0
  416. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  417. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  418. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  419. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  420. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  421. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  422. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  423. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  424. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  425. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  426. package/scripts/topo-recent-panel-hot-tabular-test.mjs +113 -0
  427. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  428. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  429. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  430. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  431. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  432. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  433. package/scripts/topo-recent-row-count-weight-test.mjs +112 -0
  434. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  435. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  436. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  437. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  438. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  439. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  440. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  441. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  442. package/scripts/topo-recent-row-title-test.mjs +112 -0
  443. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  444. package/scripts/topo-recent-row-ts-tabular-test.mjs +108 -0
  445. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  446. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  447. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  448. package/scripts/topo-reset-spin-test.mjs +120 -0
  449. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  450. package/scripts/topo-spokes-retired-test.mjs +93 -0
  451. package/scripts/topo-starfield-density-test.mjs +89 -0
  452. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  453. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  454. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  455. package/scripts/topo-svg-focus-test.mjs +112 -0
  456. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  457. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  458. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  459. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  460. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  461. package/scripts/topo-tier-ring-test.mjs +69 -0
  462. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  463. package/scripts/topo-tier-stagger-test.mjs +89 -0
  464. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  465. package/scripts/topo-title-tracking-test.mjs +98 -0
  466. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  467. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  468. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  469. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  470. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  471. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  472. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  473. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  474. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  475. package/scripts/topo-vendor-count-suffix-test.mjs +95 -0
  476. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  477. package/scripts/topo-vendor-pill-test.mjs +125 -0
  478. package/scripts/topo-vendor-pin-test.mjs +125 -0
  479. package/scripts/topo-view-smooth-test.mjs +131 -0
  480. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  481. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  482. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  483. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  484. package/scripts/topo-wrapper-radius-test.mjs +73 -0
  485. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  486. package/.next/static/chunks/088ouqy7tekty.js +0 -2
  487. package/.next/static/chunks/0hyl7mdapsz38.js +0 -1
  488. package/.next/static/chunks/0mqwn-l7wtcdo.js +0 -7
  489. package/.next/static/chunks/127kw7~wsnyu7.css +0 -2
  490. package/.next/static/chunks/16yfxp9ak2gdm.js +0 -1
  491. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_buildManifest.js +0 -0
  492. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_clientMiddlewareManifest.js +0 -0
  493. /package/.next/static/{23MxP0RBVA4BaOJ_HMYmM → 424zX3i80AJ_wHF_otKuP}/_ssgManifest.js +0 -0
@@ -0,0 +1,95 @@
1
+ /* Round 155 verification: chip-row interactives gain
2
+ * `.anet-topo-chip-focus` for keyboard focus-visible outline,
3
+ * matching R154's chrome-button treatment.
4
+ *
5
+ * Why `outline` not `box-shadow`: the R88 vendor letter / R139
6
+ * working chip / R140 online chip already use inline boxShadow
7
+ * for the pin-mirror inset. Adding focus-visible:ring (which
8
+ * Tailwind paints via box-shadow) would conflict — inline style
9
+ * wins specificity. `outline: 2px solid currentColor` is on a
10
+ * separate CSS property, no conflict; `currentColor` inherits
11
+ * the chip's own accent (green / cyan / per-vendor / etc.).
12
+ *
13
+ * Test: every chip-row interactive surface carries the class.
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+
21
+ const ctx = await browser.newContext({ viewport: { width: 1400, 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, status, model) => ({
32
+ alias, status, model, runtime: 'cli-claude-code',
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', 'working', 'claude-opus-4'),
38
+ mk('beta', 'idle', 'gpt-4o'),
39
+ ] } });
40
+ });
41
+
42
+ const now = Date.now();
43
+ // 1 message to populate flowLinks → active-links chip is interactive
44
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
45
+ { id: 'm0', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
46
+ network_id: 'default', created_at: new Date(now - 10000).toISOString() },
47
+ ] } }));
48
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ // Pin status + vendor to surface filter pills + intersection chip
56
+ await page.evaluate(() => {
57
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'working' } }));
58
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'vendor', value: 'A' } }));
59
+ });
60
+ await page.waitForTimeout(300);
61
+
62
+ const inspect = () => page.evaluate(() => {
63
+ const has = (sel) => {
64
+ const el = document.querySelector(sel);
65
+ return el && (el.className || '').includes('anet-topo-chip-focus');
66
+ };
67
+ return {
68
+ pressureSeg_working: has('[data-pressure-seg="working"]'),
69
+ workingChip: has('[data-working-chip]'),
70
+ onlineChip: has('[data-online-chip]'),
71
+ vendorLetter_A: has('[data-vendor-letter="A"]'),
72
+ activeLinks: has('[data-active-links-chip]'),
73
+ filterPill_status: has('[data-active-filter="status"]'),
74
+ filterPill_vendor: has('[data-active-filter="vendor"]'),
75
+ intersectionChip: has('[data-pin-intersection]'),
76
+ };
77
+ });
78
+
79
+ const result = await inspect();
80
+ await browser.close();
81
+
82
+ const results = {
83
+ pressureSeg_focusClass: result.pressureSeg_working === true,
84
+ workingChip_focusClass: result.workingChip === true,
85
+ onlineChip_focusClass: result.onlineChip === true,
86
+ vendorLetter_focusClass: result.vendorLetter_A === true,
87
+ activeLinks_focusClass: result.activeLinks === true,
88
+ filterPillStatus_focusClass: result.filterPill_status === true,
89
+ filterPillVendor_focusClass: result.filterPill_vendor === true,
90
+ intersectionChip_focusClass: result.intersectionChip === true,
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} chip focus class:`, JSON.stringify(results),
94
+ `\n raw=`, result);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,113 @@
1
+ /* Round 82 verification: chip-row "N working" + "N online" chips mirror
2
+ * the pin state already shown by the R60 pressure-bar segments. When
3
+ * pinnedStatus === 'working', the working chip lights up via inset
4
+ * boxShadow + data-pin-mirror="true"; idle pin lights the online chip.
5
+ * Closes the visual inconsistency where pressure-bar + legend showed
6
+ * the pin but the sibling count chips stayed flat.
7
+ *
8
+ * Drive the pin via Cmd+K palette (R69 action) to verify the chip
9
+ * reflects a pin set from a non-chip surface — exercises the full
10
+ * coupling, not just a chip clicking itself.
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: 1200 } });
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
+ sessionStorage.removeItem('anet-topo-pinned-group');
26
+ } catch {}
27
+ });
28
+
29
+ const fresh = new Date(Date.now() - 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 sessions = [
35
+ { alias: 'alpha', status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
36
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
37
+ { alias: 'beta', status: 'idle', model: 'gpt-5', runtime: 'codex-cli',
38
+ network_id: nid, project_dir: null, created_at: fresh, updated_at: fresh, last_seen_at: fresh },
39
+ ];
40
+ await route.fulfill({ response: r, json: { ...b, sessions } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
47
+ await page.waitForTimeout(400);
48
+
49
+ const snap = () => page.evaluate(() => {
50
+ const w = document.querySelector('[data-working-chip]');
51
+ const o = document.querySelector('[data-online-chip]');
52
+ return {
53
+ working: {
54
+ mirror: w?.getAttribute('data-pin-mirror'),
55
+ shadow: w ? (w.getAttribute('style') || '').includes('inset') : false,
56
+ title: w?.getAttribute('title') || '',
57
+ },
58
+ online: {
59
+ mirror: o?.getAttribute('data-pin-mirror'),
60
+ shadow: o ? (o.getAttribute('style') || '').includes('inset') : false,
61
+ title: o?.getAttribute('title') || '',
62
+ },
63
+ };
64
+ });
65
+
66
+ const before = await snap();
67
+
68
+ // Dispatch the R69 CustomEvent directly — same coupling the palette
69
+ // uses, deterministic without fuzzy-match ambiguity. The R69 listener
70
+ // in TopoGraph reads `detail.kind === 'status'` + `detail.value`.
71
+ const pin = async (value) => {
72
+ await page.evaluate((v) => {
73
+ try { sessionStorage.setItem('anet-topo-pinned-status', v); } catch {}
74
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: v } }));
75
+ }, value);
76
+ await page.waitForTimeout(200);
77
+ };
78
+ const clear = async () => {
79
+ await page.keyboard.press('Escape');
80
+ await page.waitForTimeout(250);
81
+ };
82
+
83
+ await pin('working');
84
+ const onWorking = await snap();
85
+ await clear();
86
+ const afterWorking = await snap();
87
+
88
+ await pin('idle');
89
+ const onIdle = await snap();
90
+ await clear();
91
+ const afterIdle = await snap();
92
+
93
+ await browser.close();
94
+
95
+ const results = {
96
+ before_workingFlat: before.working.mirror === 'false' && !before.working.shadow,
97
+ before_onlineFlat: before.online.mirror === 'false' && !before.online.shadow,
98
+ workingPin_workingLit: onWorking.working.mirror === 'true' && onWorking.working.shadow,
99
+ workingPin_onlineFlat: onWorking.online.mirror === 'false' && !onWorking.online.shadow,
100
+ workingPin_titleHint: /Pinned/i.test(onWorking.working.title),
101
+ esc_workingFlat: afterWorking.working.mirror === 'false' && !afterWorking.working.shadow,
102
+ esc_onlineUntouched: afterWorking.online.mirror === 'false' && !afterWorking.online.shadow,
103
+ idlePin_onlineLit: onIdle.online.mirror === 'true' && onIdle.online.shadow,
104
+ idlePin_workingFlat: onIdle.working.mirror === 'false' && !onIdle.working.shadow,
105
+ idlePin_titleHint: /Pinned/i.test(onIdle.online.title),
106
+ esc_onlineFlat: afterIdle.online.mirror === 'false' && !afterIdle.online.shadow,
107
+ };
108
+ const ok = Object.values(results).every(Boolean);
109
+ console.log(`${ok ? '✅' : '❌'} chip pin-mirror:`, JSON.stringify(results),
110
+ `\n before=`, before,
111
+ `\n onWorking=`, onWorking,
112
+ `\n onIdle=`, onIdle);
113
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,90 @@
1
+ /* Round 328 verification: chip-row strip wrapper gap-2 → gap-2.5
2
+ * (8px → 10px between chips).
3
+ *
4
+ * The chip-row strip stacks status chips (working/online/active-
5
+ * links), vendor letters, pressure-bar segments, filter pin pills,
6
+ * pin-intersection chip, and the Layout toggle. Each chip has its
7
+ * own `px-2.5` (10px) horizontal padding. Pre-R328 the gap (8px)
8
+ * was tighter than chip-padding, so neighbors visually touched.
9
+ * R328 makes inter-chip = chip-padding rhythm.
10
+ *
11
+ * Sibling family:
12
+ * R298 title-block gap-2.5
13
+ * R328 chip-row gap-2.5 (this round)
14
+ * R326 chrome gap-2
15
+ *
16
+ * Contract:
17
+ * - The chip-row wrapper carries className 'gap-2.5' (not 'gap-2').
18
+ * - Computed column-gap === 10px.
19
+ * - R326 regression: [data-topo-chrome] still column-gap 8px.
20
+ * - R317/R318/R294 chrome + pulse regressions intact.
21
+ *
22
+ * The chip-row wrapper has no test-id; we find it as the sibling
23
+ * `<div>` of the title-block (containing the Layout toggle button
24
+ * via `[data-topo-chrome-layout-trailer]` which is its child).
25
+ */
26
+ import { chromium } from 'playwright';
27
+ import { readFileSync } from 'node:fs';
28
+
29
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
30
+ const browser = await chromium.launch({ headless: true });
31
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
32
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
33
+ await ctx.addInitScript(() => {
34
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
35
+ });
36
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
37
+ await ctx.route('**/api/hub/status*', async (route) => {
38
+ const r = await route.fetch();
39
+ const b = await r.json();
40
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
41
+ const mk = (alias) => ({
42
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
43
+ network_id: nid, project_dir: null,
44
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
45
+ });
46
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('alpha'), mk('beta') ] } });
47
+ });
48
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
49
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
50
+
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
53
+ await page.waitForSelector('[data-topo-chrome-layout-trailer]', { timeout: 15000 });
54
+ await page.waitForTimeout(300);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ const layoutWrapper = document.querySelector('[data-topo-chrome-layout-trailer]');
58
+ // Chip-row strip is the parent div with className containing 'flex-wrap'
59
+ // — walk up from the Layout toggle wrapper.
60
+ let chipRow = layoutWrapper?.parentElement || null;
61
+ while (chipRow && !chipRow.className.includes('flex-wrap')) {
62
+ chipRow = chipRow.parentElement;
63
+ }
64
+ const chrome = document.querySelector('[data-topo-chrome]');
65
+ return {
66
+ chipRowClass: chipRow?.className ?? '',
67
+ chipRowColumnGap: chipRow ? getComputedStyle(chipRow).columnGap : null,
68
+ chromeColumnGap: chrome ? getComputedStyle(chrome).columnGap : null,
69
+ layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
70
+ layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
71
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
72
+ };
73
+ });
74
+ await browser.close();
75
+
76
+ const results = {
77
+ chip_row_has_gap_2_5: probe.chipRowClass.includes('gap-2.5') && !/(^|\s)gap-2(\s|$)/.test(probe.chipRowClass),
78
+ chip_row_gap_10px: probe.chipRowColumnGap === '10px',
79
+ // R326 regression: chrome strip stays at gap-2 (8px).
80
+ r326_chrome_gap_8px: probe.chromeColumnGap === '8px',
81
+ // R317 / R318 chrome regression.
82
+ r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
83
+ r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
84
+ r294_pulse_absent: probe.pulseCount === 0,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} chip-row gap-2.5:`, JSON.stringify(results),
88
+ '\n chip-row column-gap:', probe.chipRowColumnGap,
89
+ '\n chrome column-gap:', probe.chromeColumnGap);
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,97 @@
1
+ /* Round 260 verification: chip-row semantic gap — Layout toggle (control)
2
+ * separated from display chips by an extra 4px (mr-1 stacks on parent
3
+ * gap-2 → effective 12px).
4
+ *
5
+ * Pre-R260 the header chip-row rendered all 8 children at uniform
6
+ * gap-2 (8px):
7
+ *
8
+ * [Ring|Grid] [N working] [N online] [pressure ▮▮] [A:3 ?:1] [N active links] [filter…] [live · Ns]
9
+ * 8 8 8 8 8 8 8
10
+ *
11
+ * Spatial signal said "8 separate things" — but semantically only the
12
+ * Layout toggle is a CONTROL (changes layout mode); everything that
13
+ * follows is READ-ONLY display chrome. R260 widens the gap before the
14
+ * first display chip via mr-1 on the Layout toggle wrapper:
15
+ *
16
+ * [Ring|Grid] 12px [N working] 8 [N online] 8 [pressure ▮▮] …
17
+ * ↑
18
+ * control → display boundary
19
+ *
20
+ * Classic law-of-proximity polish — same pattern R255 applied to the
21
+ * bottom-right chrome strip (fleet vs view).
22
+ *
23
+ * Test scope:
24
+ * 1. Layout toggle present + tagged with data-topo-chrome-layout-trailer.
25
+ * 2. Gap from Layout toggle's right edge to working chip's left edge
26
+ * is ≥ 11px (target 12px, allow 1px sub-pixel slop).
27
+ * 3. Adjacent display chips' gap stays ~8px (working → online ∈ [6,10]).
28
+ * 4. Vertical center alignment preserved between Layout toggle and
29
+ * the working chip (Δ ≤ 1px).
30
+ */
31
+ import { chromium } from 'playwright';
32
+ import { readFileSync } from 'node:fs';
33
+
34
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
35
+ const browser = await chromium.launch({ headless: true });
36
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
37
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
38
+ await ctx.addInitScript(() => {
39
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
40
+ });
41
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
42
+ await ctx.route('**/api/hub/status*', async (route) => {
43
+ const r = await route.fetch();
44
+ const b = await r.json();
45
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
46
+ const mk = (alias) => ({
47
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
48
+ network_id: nid, project_dir: null,
49
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
50
+ });
51
+ await route.fulfill({ response: r, json: { ...b, sessions: [
52
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
53
+ ] } });
54
+ });
55
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
56
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
57
+
58
+ const page = await ctx.newPage();
59
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
60
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
61
+ await page.waitForSelector('[data-topo-chrome-layout-trailer]', { timeout: 10000 });
62
+ await page.waitForSelector('[data-working-chip]', { timeout: 10000 });
63
+ await page.waitForSelector('[data-online-chip]', { timeout: 10000 });
64
+ await page.waitForTimeout(300);
65
+
66
+ const probe = await page.evaluate(() => {
67
+ const trailer = document.querySelector('[data-topo-chrome-layout-trailer]');
68
+ const working = document.querySelector('[data-working-chip]');
69
+ const online = document.querySelector('[data-online-chip]');
70
+ const r = (el) => el ? el.getBoundingClientRect() : null;
71
+ return {
72
+ trailer: r(trailer),
73
+ working: r(working),
74
+ online: r(online),
75
+ };
76
+ });
77
+ await browser.close();
78
+
79
+ const gapToggleToWorking = (probe.trailer && probe.working) ? probe.working.left - probe.trailer.right : null;
80
+ const gapWorkingToOnline = (probe.working && probe.online) ? probe.online.left - probe.working.right : null;
81
+ const yCenterDelta = (probe.trailer && probe.working)
82
+ ? Math.abs((probe.trailer.top + probe.trailer.height / 2) - (probe.working.top + probe.working.height / 2))
83
+ : null;
84
+
85
+ const results = {
86
+ trailer_present: probe.trailer !== null,
87
+ working_present: probe.working !== null,
88
+ online_present: probe.online !== null,
89
+ toggle_to_working_gap_widened: gapToggleToWorking != null && gapToggleToWorking >= 11,
90
+ working_to_online_gap_unchanged: gapWorkingToOnline != null && gapWorkingToOnline >= 6 && gapWorkingToOnline <= 10,
91
+ y_center_aligned: yCenterDelta != null && yCenterDelta <= 1,
92
+ };
93
+ const ok = Object.values(results).every(Boolean);
94
+ console.log(`${ok ? '✅' : '❌'} chip-row semantic gap:`, JSON.stringify(results),
95
+ '\n gaps: toggle→working =', gapToggleToWorking, 'working→online =', gapWorkingToOnline,
96
+ '\n yCenterΔ:', yCenterDelta);
97
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,111 @@
1
+ /* Round 232 verification: HTML chip row (working / online / active-
2
+ * links) gains the Tailwind `tabular-nums` utility class so the digit
3
+ * width stays stable across counter rollovers (9→10, 99→100).
4
+ *
5
+ * 7th surface in the info-density tabular-nums sweep — first on the
6
+ * HTML side (previous 6 were SVG <text>/<tspan>):
7
+ * R224 edge midpoint badge digit
8
+ * R225 hub center digit
9
+ * R225 recent panel header tspan
10
+ * R225 recent row count tspan
11
+ * R229 group-label · count chip
12
+ * R230 R58 status pip strip × 3 tiers
13
+ * R232 chip row × 3 chips (this round)
14
+ *
15
+ * Scenario: 4 working agents + 1 flow alpha→beta count=5. Drives
16
+ * workingCount=4, onlineNodes.length=4, flowLinks.length=1 → all
17
+ * three chips render with non-empty digits.
18
+ *
19
+ * Verifies for each chip:
20
+ * - element present at its data attr selector
21
+ * - getComputedStyle().fontVariantNumeric includes 'tabular-nums'
22
+ * - textContent starts with the expected digit (sanity)
23
+ */
24
+ import { chromium } from 'playwright';
25
+ import { readFileSync } from 'node:fs';
26
+
27
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
28
+ const browser = await chromium.launch({ headless: true });
29
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
30
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
31
+ await ctx.addInitScript(() => {
32
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
33
+ });
34
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
35
+ await ctx.route('**/api/hub/status*', async (route) => {
36
+ const r = await route.fetch();
37
+ const b = await r.json();
38
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
39
+ const mk = (alias) => ({
40
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
41
+ network_id: nid, project_dir: null,
42
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
+ });
44
+ await route.fulfill({ response: r, json: { ...b, sessions: [
45
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
46
+ ] } });
47
+ });
48
+ const now = Date.now();
49
+ const msgs = [];
50
+ for (let i = 0; i < 5; i++) {
51
+ msgs.push({
52
+ id: `m${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
53
+ network_id: 'default', created_at: new Date(now - (1000 + i * 50)).toISOString(),
54
+ });
55
+ }
56
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: msgs } }));
57
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
58
+
59
+ const page = await ctx.newPage();
60
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
61
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
62
+ await page.waitForSelector('[data-working-chip]', { timeout: 10000, state: 'attached' });
63
+ await page.waitForSelector('[data-online-chip]', { timeout: 10000, state: 'attached' });
64
+ await page.waitForSelector('[data-active-links-chip]', { timeout: 10000, state: 'attached' });
65
+ await page.waitForTimeout(300);
66
+
67
+ const out = await page.evaluate(() => {
68
+ const grab = (sel, expectedPrefix) => {
69
+ const el = document.querySelector(sel);
70
+ if (!el) return null;
71
+ return {
72
+ present: true,
73
+ fontVarNumeric: getComputedStyle(el).fontVariantNumeric,
74
+ hasUtility: el.className.split(/\s+/).includes('tabular-nums'),
75
+ textStartsWith: (el.textContent || '').trim().startsWith(expectedPrefix),
76
+ text: (el.textContent || '').trim().slice(0, 30),
77
+ };
78
+ };
79
+ return {
80
+ working: grab('[data-working-chip]', '4 working'),
81
+ online: grab('[data-online-chip]', '4 online'),
82
+ activeLinks: grab('[data-active-links-chip]', '1 active'),
83
+ };
84
+ });
85
+ await browser.close();
86
+
87
+ const hasTab = (s) => /tabular-nums/.test(s || '');
88
+ const present = (o) => o && o.present;
89
+
90
+ const results = {
91
+ working_present: present(out.working),
92
+ working_class_utility: out.working?.hasUtility === true,
93
+ working_fvn_tabular: hasTab(out.working?.fontVarNumeric),
94
+ working_text_ok: out.working?.textStartsWith === true,
95
+
96
+ online_present: present(out.online),
97
+ online_class_utility: out.online?.hasUtility === true,
98
+ online_fvn_tabular: hasTab(out.online?.fontVarNumeric),
99
+ online_text_ok: out.online?.textStartsWith === true,
100
+
101
+ active_present: present(out.activeLinks),
102
+ active_class_utility: out.activeLinks?.hasUtility === true,
103
+ active_fvn_tabular: hasTab(out.activeLinks?.fontVarNumeric),
104
+ active_text_ok: out.activeLinks?.textStartsWith === true,
105
+ };
106
+ const ok = Object.values(results).every(Boolean);
107
+ console.log(`${ok ? '✅' : '❌'} chip row tabular-nums:`, JSON.stringify(results),
108
+ '\n working: ', out.working,
109
+ '\n online: ', out.online,
110
+ '\n activeLinks:', out.activeLinks);
111
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 337 verification: working chip + online chip (HTML chip-row)
2
+ * split digit/unit with the unit at opacity-70.
3
+ *
4
+ * Extends the R333/R335/R336 chip-internal-hierarchy arc from SVG
5
+ * surfaces (panel headers + vendor count) and pin-chip prefix
6
+ * surfaces (filter pills) into the HTML chip-row chips themselves.
7
+ * Recurring pattern this loop arc: small label spans demote, value
8
+ * stays prominent.
9
+ *
10
+ * Contract:
11
+ * - [data-working-chip-unit] className contains 'opacity-70',
12
+ * textContent contains 'working', computed opacity 0.7.
13
+ * - [data-online-chip-unit] same for 'online'.
14
+ * - Parent chips' textContent retains the full "{N} working" /
15
+ * "{N} online" string so any legacy text-based tests pass.
16
+ * - R336 panel count units + R335 prefix opacity-70 regressions.
17
+ * - R317/R318/R294 chrome + pulse regressions intact.
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
+ const ctx = await browser.newContext({ viewport: { width: 1500, 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
+ const fresh = new Date(Date.now() - 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) => ({
35
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
36
+ network_id: nid, project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
38
+ });
39
+ await route.fulfill({ response: r, json: { ...b, sessions: [
40
+ mk('alpha'), mk('beta'), mk('gamma'),
41
+ ] } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
44
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
45
+
46
+ const page = await ctx.newPage();
47
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
48
+ await page.waitForSelector('[data-working-chip-unit]', { timeout: 15000 });
49
+ await page.waitForTimeout(300);
50
+
51
+ const probe = await page.evaluate(() => {
52
+ const workUnit = document.querySelector('[data-working-chip-unit]');
53
+ const onlineUnit = document.querySelector('[data-online-chip-unit]');
54
+ const workChip = document.querySelector('[data-working-chip]');
55
+ const onlineChip = document.querySelector('[data-online-chip]');
56
+ return {
57
+ workUnitClass: workUnit?.className ?? '',
58
+ workUnitText: workUnit?.textContent ?? null,
59
+ workUnitOpacity: workUnit ? getComputedStyle(workUnit).opacity : null,
60
+ onlineUnitClass: onlineUnit?.className ?? '',
61
+ onlineUnitText: onlineUnit?.textContent ?? null,
62
+ onlineUnitOpacity: onlineUnit ? getComputedStyle(onlineUnit).opacity : null,
63
+ workChipText: workChip?.textContent ?? null,
64
+ onlineChipText: onlineChip?.textContent ?? null,
65
+ layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
66
+ layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
67
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
68
+ };
69
+ });
70
+ await browser.close();
71
+
72
+ const opacityClose = (val) => val !== null && Math.abs(parseFloat(val) - 0.7) < 0.01;
73
+
74
+ const results = {
75
+ work_unit_has_opacity70: /opacity-70/.test(probe.workUnitClass),
76
+ work_unit_text_working: (probe.workUnitText || '').includes('working'),
77
+ work_unit_computed_0_7: opacityClose(probe.workUnitOpacity),
78
+ online_unit_has_opacity70: /opacity-70/.test(probe.onlineUnitClass),
79
+ online_unit_text_online: (probe.onlineUnitText || '').includes('online'),
80
+ online_unit_computed_0_7: opacityClose(probe.onlineUnitOpacity),
81
+ // Parent textContent retention.
82
+ work_chip_contains_working: (probe.workChipText || '').includes('working'),
83
+ online_chip_contains_online: (probe.onlineChipText || '').includes('online'),
84
+ // R317 / R318 chrome regression.
85
+ r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
86
+ r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
87
+ r294_pulse_absent: probe.pulseCount === 0,
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} chip unit demotion:`, JSON.stringify(results),
91
+ '\n work unit:', JSON.stringify(probe.workUnitText), 'opacity:', probe.workUnitOpacity,
92
+ '\n online unit:', JSON.stringify(probe.onlineUnitText), 'opacity:', probe.onlineUnitOpacity);
93
+ process.exit(ok ? 0 : 1);