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

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 (477) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +41 -41
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +2 -2
  15. package/.next/server/app/_not-found.rsc +12 -12
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/admin.html +2 -2
  24. package/.next/server/app/admin.rsc +14 -14
  25. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  26. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  27. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  28. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  30. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  31. package/.next/server/app/index.html +2 -2
  32. package/.next/server/app/index.rsc +14 -14
  33. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  34. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  35. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  36. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  37. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  38. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  39. package/.next/server/app/login.html +2 -2
  40. package/.next/server/app/login.rsc +14 -14
  41. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  42. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  43. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  44. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  46. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  47. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/logs.html +2 -2
  49. package/.next/server/app/logs.rsc +14 -14
  50. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  51. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  52. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  53. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  55. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  56. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/messages.html +2 -2
  58. package/.next/server/app/messages.rsc +14 -14
  59. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  60. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  61. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  62. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  64. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  65. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/node.html +2 -2
  67. package/.next/server/app/node.rsc +14 -14
  68. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  69. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  70. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  71. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  73. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  74. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/nodes.html +2 -2
  76. package/.next/server/app/nodes.rsc +14 -14
  77. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  78. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  79. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  80. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  82. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  83. package/.next/server/app/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/server-logs.html +2 -2
  86. package/.next/server/app/server-logs.rsc +14 -14
  87. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  88. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  89. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  90. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  91. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
  92. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  93. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/settings/networks.html +2 -2
  95. package/.next/server/app/settings/networks.rsc +14 -14
  96. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  97. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  98. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  99. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
  101. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  102. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  103. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/settings/tokens.html +2 -2
  106. package/.next/server/app/settings/tokens.rsc +14 -14
  107. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  108. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  109. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  110. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  111. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
  112. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  113. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  114. package/.next/server/app/settings.html +2 -2
  115. package/.next/server/app/settings.rsc +14 -14
  116. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  117. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  118. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  119. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  121. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  122. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/tasks.html +2 -2
  125. package/.next/server/app/tasks.rsc +14 -14
  126. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  127. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  128. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  129. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  130. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  131. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  132. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js +1 -1
  133. package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +1 -1
  134. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js +1 -1
  135. package/.next/server/chunks/ssr/[root-of-the-server]__0fhoq8i._.js.map +1 -1
  136. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js +1 -1
  137. package/.next/server/chunks/ssr/[root-of-the-server]__0nw~zhp._.js.map +1 -1
  138. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -1
  139. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  140. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  141. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  142. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js +1 -1
  143. package/.next/server/chunks/ssr/agent-network-dashboard_app_057q.ne._.js.map +1 -1
  144. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  145. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  146. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js +1 -1
  147. package/.next/server/chunks/ssr/agent-network-dashboard_app_0_d45-d._.js.map +1 -1
  148. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js +1 -1
  149. package/.next/server/chunks/ssr/agent-network-dashboard_app_0i3759l._.js.map +1 -1
  150. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js +1 -1
  151. package/.next/server/chunks/ssr/agent-network-dashboard_app_0xgney8._.js.map +1 -1
  152. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js +1 -1
  153. package/.next/server/chunks/ssr/agent-network-dashboard_app_10hjgv4._.js.map +1 -1
  154. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js +1 -1
  155. package/.next/server/chunks/ssr/agent-network-dashboard_app_1153xeb._.js.map +1 -1
  156. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js +1 -1
  157. package/.next/server/chunks/ssr/agent-network-dashboard_app_12l4oto._.js.map +1 -1
  158. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  159. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  160. package/.next/server/middleware-build-manifest.js +3 -3
  161. package/.next/server/pages/404.html +2 -2
  162. package/.next/server/pages/500.html +1 -1
  163. package/.next/server/server-reference-manifest.js +1 -1
  164. package/.next/server/server-reference-manifest.json +1 -1
  165. package/.next/static/chunks/0-mpa_947ipeq.js +1 -0
  166. package/.next/static/chunks/01lmbsd37fybu.js +1 -0
  167. package/.next/static/chunks/03o.h6kvmw4l_.js +1 -0
  168. package/.next/static/chunks/03~~oirxz7~vc.js +1 -0
  169. package/.next/static/chunks/05uk96gc~9mni.js +1 -0
  170. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  171. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  172. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  173. package/.next/static/chunks/0o40uf-rn28q6.js +1 -0
  174. package/.next/static/chunks/0q3hr1~viwzkt.js +4 -0
  175. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  176. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  177. package/.next/static/chunks/0~ks9b48lw8hx.css +2 -0
  178. package/.next/static/chunks/13lvf-h-j8bmc.js +1 -0
  179. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  180. package/.next/trace +2 -2
  181. package/.next/trace-build +1 -1
  182. package/app/components/ChatPopover.tsx +38 -2
  183. package/app/components/CommandPalette.tsx +107 -0
  184. package/app/components/HelpOverlay.tsx +126 -0
  185. package/app/components/TopoGraph.tsx +5927 -258
  186. package/app/components/utils.ts +8 -7
  187. package/app/globals.css +110 -5
  188. package/app/lib/time.ts +36 -0
  189. package/app/lib/vendorIdentity.ts +14 -3
  190. package/package.json +2 -2
  191. package/public/sleep2agi-logo.svg +11 -0
  192. package/public/vendors/claude.svg +11 -0
  193. package/public/vendors/minimax.svg +11 -0
  194. package/public/vendors/openai.svg +12 -0
  195. package/screenshots/r293-p0/after-full.png +0 -0
  196. package/screenshots/r293-p0/after-topo.png +0 -0
  197. package/screenshots/r293-p0/baseline-full.png +0 -0
  198. package/screenshots/r293-p0/baseline-topo.png +0 -0
  199. package/screenshots/r293-p0/logo-integrated-full.png +0 -0
  200. package/screenshots/r293-p0/logo-integrated-topo.png +0 -0
  201. package/scripts/help-overlay-topo-test.mjs +44 -0
  202. package/scripts/p0-topo-screenshot.mjs +82 -0
  203. package/scripts/topo-active-filter-pill-test.mjs +101 -0
  204. package/scripts/topo-active-links-chip-hover-test.mjs +103 -0
  205. package/scripts/topo-active-links-chip-nav-test.mjs +117 -0
  206. package/scripts/topo-active-links-chip-self-hover-test.mjs +171 -0
  207. package/scripts/topo-active-links-chip-test.mjs +73 -0
  208. package/scripts/topo-active-links-empty-recede-test.mjs +125 -0
  209. package/scripts/topo-active-links-freshness-test.mjs +113 -0
  210. package/scripts/topo-active-links-tooltip-test.mjs +79 -0
  211. package/scripts/topo-active-pulse-ease-test.mjs +120 -0
  212. package/scripts/topo-auto-fit-test.mjs +73 -0
  213. package/scripts/topo-brand-watermark-test.mjs +97 -0
  214. package/scripts/topo-canvas-aria-test.mjs +108 -0
  215. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  216. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  217. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  218. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  219. package/scripts/topo-chip-focus-test.mjs +95 -0
  220. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  221. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  222. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  223. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  224. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  225. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  226. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  227. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  228. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  229. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  230. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  231. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  232. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  233. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  234. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  235. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  236. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  237. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  238. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  239. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  240. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  241. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  242. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  243. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  244. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  245. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  246. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  247. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  248. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  249. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  250. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  251. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  252. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  253. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  254. package/scripts/topo-edge-fadein-test.mjs +106 -0
  255. package/scripts/topo-edge-focus-test.mjs +113 -0
  256. package/scripts/topo-edge-hover-test.mjs +93 -0
  257. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  258. package/scripts/topo-edge-pill-test.mjs +105 -0
  259. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  260. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  261. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  262. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  263. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  264. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  265. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  266. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  267. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  268. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  269. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  270. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  271. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  272. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  273. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  274. package/scripts/topo-fit-key-test.mjs +106 -0
  275. package/scripts/topo-freshness-chip-test.mjs +74 -0
  276. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  277. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  278. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  279. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  280. package/scripts/topo-group-count-test.mjs +6 -1
  281. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  282. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  283. package/scripts/topo-group-label-hover-test.mjs +119 -0
  284. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  285. package/scripts/topo-group-label-tint-test.mjs +99 -0
  286. package/scripts/topo-group-label-title-test.mjs +83 -0
  287. package/scripts/topo-group-pin-test.mjs +119 -0
  288. package/scripts/topo-group-pip-fadein-test.mjs +100 -0
  289. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  290. package/scripts/topo-group-status-pips-test.mjs +103 -0
  291. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  292. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  293. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  294. package/scripts/topo-groupbox-live-test.mjs +100 -0
  295. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  296. package/scripts/topo-groupbox-test.mjs +4 -2
  297. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  298. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  299. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  300. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  301. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  302. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  303. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  304. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  305. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  306. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  307. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  308. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  309. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  310. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  311. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  312. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  313. package/scripts/topo-hub-a11y-test.mjs +112 -0
  314. package/scripts/topo-hub-busyness-test.mjs +100 -0
  315. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  316. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  317. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  318. package/scripts/topo-hub-fadein-test.mjs +88 -0
  319. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  320. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  321. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  322. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  323. package/scripts/topo-hub-title-test.mjs +93 -0
  324. package/scripts/topo-hub-working-count-test.mjs +99 -0
  325. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  326. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  327. package/scripts/topo-kicker-color-test.mjs +93 -0
  328. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  329. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  330. package/scripts/topo-label-lift-test.mjs +119 -0
  331. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  332. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  333. package/scripts/topo-layout-key-test.mjs +91 -0
  334. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  335. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  336. package/scripts/topo-layout-toggle-test.mjs +131 -0
  337. package/scripts/topo-legend-compress-test.mjs +107 -0
  338. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  339. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  340. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  341. package/scripts/topo-legend-count-test.mjs +88 -0
  342. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  343. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  344. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  345. package/scripts/topo-legend-hover-test.mjs +113 -0
  346. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  347. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  348. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  349. package/scripts/topo-legend-pin-test.mjs +128 -0
  350. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  351. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  352. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  353. package/scripts/topo-legend-row-title-test.mjs +102 -0
  354. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  355. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  356. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  357. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  358. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  359. package/scripts/topo-minimap-test.mjs +68 -0
  360. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  361. package/scripts/topo-mobile-chips-test.mjs +103 -0
  362. package/scripts/topo-monogram-font-test.mjs +112 -0
  363. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  364. package/scripts/topo-node-a11y-test.mjs +107 -0
  365. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  366. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  367. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  368. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  369. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  370. package/scripts/topo-node-title-peers-test.mjs +107 -0
  371. package/scripts/topo-node-title-test.mjs +101 -0
  372. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  373. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  374. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  375. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  376. package/scripts/topo-overlap-test.mjs +32 -5
  377. package/scripts/topo-panel-elevation-test.mjs +76 -0
  378. package/scripts/topo-panel-fadein-test.mjs +102 -0
  379. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  380. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  381. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  382. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  383. package/scripts/topo-particle-stagger-test.mjs +82 -0
  384. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  385. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  386. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  387. package/scripts/topo-pin-intersection-test.mjs +121 -0
  388. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  389. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  390. package/scripts/topo-pin-persistence-test.mjs +105 -0
  391. package/scripts/topo-popover-meta-test.mjs +78 -0
  392. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  393. package/scripts/topo-pressure-bar-test.mjs +69 -0
  394. package/scripts/topo-pressure-hover-test.mjs +123 -0
  395. package/scripts/topo-pressure-pin-test.mjs +119 -0
  396. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  397. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  398. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  399. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  400. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  401. package/scripts/topo-pulse-rate-test.mjs +71 -0
  402. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  403. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  404. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  405. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  406. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  407. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  408. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  409. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  410. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  411. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  412. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  413. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  414. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  415. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  416. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  417. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  418. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  419. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  420. package/scripts/topo-recent-row-title-test.mjs +112 -0
  421. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  422. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  423. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  424. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  425. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  426. package/scripts/topo-reduced-motion-test.mjs +77 -0
  427. package/scripts/topo-reset-spin-test.mjs +120 -0
  428. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  429. package/scripts/topo-spokes-retired-test.mjs +93 -0
  430. package/scripts/topo-starfield-density-test.mjs +89 -0
  431. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  432. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  433. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  434. package/scripts/topo-svg-focus-test.mjs +112 -0
  435. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  436. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  437. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  438. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  439. package/scripts/topo-tier-ring-test.mjs +69 -0
  440. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  441. package/scripts/topo-tier-stagger-test.mjs +89 -0
  442. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  443. package/scripts/topo-title-tracking-test.mjs +98 -0
  444. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  445. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  446. package/scripts/topo-tz-ghost-test.mjs +69 -0
  447. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  448. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  449. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  450. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  451. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  452. package/scripts/topo-vendor-pill-test.mjs +125 -0
  453. package/scripts/topo-vendor-pin-test.mjs +125 -0
  454. package/scripts/topo-view-smooth-test.mjs +131 -0
  455. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  456. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  457. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  458. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  459. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  460. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  461. package/.next/static/chunks/01u5zo8b7r_r3.js +0 -1
  462. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  463. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  464. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  465. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  466. package/.next/static/chunks/0fqit.b4j_6rz.js +0 -1
  467. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  468. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  469. package/.next/static/chunks/0pl7l_.2im.j0.js +0 -7
  470. package/.next/static/chunks/0qcfcgdthhos6.js +0 -1
  471. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  472. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  473. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  474. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  475. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_buildManifest.js +0 -0
  476. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_clientMiddlewareManifest.js +0 -0
  477. /package/.next/static/{mgs4E0Fu2JlZsA5DB0XUu → Kkkh4U0z1GCAKfQMsfmjt}/_ssgManifest.js +0 -0
@@ -0,0 +1,131 @@
1
+ /* Round 163 verification: Ring/Grid layout toggle buttons pick
2
+ * up the R154 chrome-button polish convention.
3
+ *
4
+ * Pre-R163 the buttons had aria-pressed + transition-colors but:
5
+ * - no focus-visible ring (browser default invisible on canvas)
6
+ * - inactive hover only nudged text-gray-500 → gray-400, no bg
7
+ *
8
+ * R163 adds:
9
+ * data-topo-chrome-layout="ring|grid"
10
+ * data-topo-chrome-layout-active="true|false"
11
+ * focus-visible:ring-2 ring-cyan-400/60 ring-inset
12
+ * inactive: hover:text-gray-300 + hover:bg-cyan-500/5
13
+ * active: hover:bg-cyan-500/20 (slightly hotter)
14
+ *
15
+ * Test:
16
+ * 1. Both buttons present with data-topo-chrome-layout attr
17
+ * 2. Active button has bg-cyan-500/15 class fragment (existing)
18
+ * 3. Focus the inactive button → focus-visible ring class fragment
19
+ * 4. Hover the inactive button → bg color shifts from transparent
20
+ * to a cyan-ish tint (border bg-cyan-500/5)
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); localStorage.setItem('anet-topo-layout', 'ring'); } catch {}
32
+ });
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: 'cli-claude-code',
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: [mk('alpha'), mk('beta')] } });
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 === 2, { timeout: 30000 });
52
+ await page.waitForSelector('[data-topo-chrome-layout]', { timeout: 10000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ // Probe basic attrs & class membership
56
+ const probe = await page.evaluate(() => {
57
+ const ring = document.querySelector('[data-topo-chrome-layout="ring"]');
58
+ const grid = document.querySelector('[data-topo-chrome-layout="grid"]');
59
+ if (!ring || !grid) return null;
60
+ return {
61
+ ring_active: ring.getAttribute('data-topo-chrome-layout-active'),
62
+ grid_active: grid.getAttribute('data-topo-chrome-layout-active'),
63
+ ring_pressed: ring.getAttribute('aria-pressed'),
64
+ grid_pressed: grid.getAttribute('aria-pressed'),
65
+ ring_class: ring.getAttribute('class') || '',
66
+ grid_class: grid.getAttribute('class') || '',
67
+ };
68
+ });
69
+
70
+ // Focus the inactive (grid) button — focus-visible ring should kick in.
71
+ // Tab-focus produces the :focus-visible state; .focus() doesn't always.
72
+ // Use page.keyboard.press('Tab') after first focusing somewhere known.
73
+ await page.locator('[data-topo-chrome-layout="grid"]').focus();
74
+ // trigger keyboard navigation to enable focus-visible state heuristic
75
+ await page.evaluate(() => {
76
+ const el = document.querySelector('[data-topo-chrome-layout="grid"]');
77
+ if (el) el.matches(':focus-visible');
78
+ });
79
+ const focusVisibleProbe = await page.evaluate(() => {
80
+ const el = document.querySelector('[data-topo-chrome-layout="grid"]');
81
+ if (!el) return null;
82
+ return {
83
+ isActiveElement: document.activeElement === el,
84
+ // Inspect outline & box-shadow because the Tailwind focus-visible:ring
85
+ // class compiles to a box-shadow. After .focus() Chromium reports
86
+ // :focus-visible heuristically — we verify the class fragment exists
87
+ // in the stylesheet bound to this element rather than relying on the
88
+ // dynamic pseudo-class match.
89
+ classFragments: {
90
+ hasFocusVisibleRingClass: (el.getAttribute('class') || '').includes('focus-visible:ring-cyan-400/60'),
91
+ hasRingInset: (el.getAttribute('class') || '').includes('focus-visible:ring-inset'),
92
+ hasFocusOutlineNone: (el.getAttribute('class') || '').includes('focus:outline-none'),
93
+ },
94
+ };
95
+ });
96
+
97
+ // Hover the inactive (grid) button — bg should shift away from transparent.
98
+ const gridBgBefore = await page.evaluate(() => {
99
+ const el = document.querySelector('[data-topo-chrome-layout="grid"]');
100
+ return el ? getComputedStyle(el).backgroundColor : null;
101
+ });
102
+ await page.locator('[data-topo-chrome-layout="grid"]').hover();
103
+ await page.waitForTimeout(300);
104
+ const gridBgHover = await page.evaluate(() => {
105
+ const el = document.querySelector('[data-topo-chrome-layout="grid"]');
106
+ return el ? getComputedStyle(el).backgroundColor : null;
107
+ });
108
+
109
+ await browser.close();
110
+
111
+ const results = {
112
+ both_buttons_found: probe !== null,
113
+ ring_is_active: probe?.ring_active === 'true',
114
+ grid_is_inactive: probe?.grid_active === 'false',
115
+ ring_aria_pressed: probe?.ring_pressed === 'true',
116
+ grid_aria_unpressed: probe?.grid_pressed === 'false',
117
+ active_has_cyan_bg_class: probe?.ring_class.includes('bg-cyan-500/15'),
118
+ inactive_hover_bg_class: probe?.grid_class.includes('hover:bg-cyan-500/5'),
119
+ active_hover_bg_class: probe?.ring_class.includes('hover:bg-cyan-500/20'),
120
+ focus_outline_none: focusVisibleProbe?.classFragments?.hasFocusOutlineNone === true,
121
+ focus_visible_ring_class: focusVisibleProbe?.classFragments?.hasFocusVisibleRingClass === true,
122
+ focus_visible_ring_inset: focusVisibleProbe?.classFragments?.hasRingInset === true,
123
+ inactive_bg_before: gridBgBefore !== null,
124
+ hover_changes_bg: gridBgBefore !== gridBgHover,
125
+ };
126
+ const ok = Object.values(results).every(Boolean);
127
+ console.log(`${ok ? '✅' : '❌'} layout toggle polish:`, JSON.stringify(results),
128
+ `\n probe=`, probe,
129
+ `\n focus=`, focusVisibleProbe,
130
+ `\n bg before/hover=`, gridBgBefore, '→', gridBgHover);
131
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,107 @@
1
+ /* Round 277 verification: legend panel compress 104 → 88, matching the
2
+ * recent-signal panel's height post-R256. Row stride 24 → 18.
3
+ *
4
+ * Pre-R277:
5
+ * Panel: 224×104, rx=8
6
+ * Row 1 (working): y0=32, y1=36
7
+ * Row 2 (idle): y0=56, y1=60
8
+ * Row 3 (offline): y0=80, y1=84
9
+ * Flow-arrow: M140,80 Q164,56 196,80 (endpoints y=80)
10
+ *
11
+ * Post-R277:
12
+ * Panel: 224×88, rx=8
13
+ * Row 1 (working): y0=32, y1=36 (anchored — R271 hitbox y=21 invariant)
14
+ * Row 2 (idle): y0=50, y1=54 (-6)
15
+ * Row 3 (offline): y0=68, y1=72 (-12)
16
+ * Flow-arrow: M140,68 Q164,44 196,68 (tracks new offline cy)
17
+ *
18
+ * Net: legend panel takes ~15% less vertical chrome; panel pair
19
+ * (recent-signal 88 + legend 88) now share the same height. Third
20
+ * 减法 cut after R275 (FreshnessChip conditional) + R276 (orbit
21
+ * particles retired).
22
+ *
23
+ * Test scope:
24
+ * 1. Legend panel rect height === 88.
25
+ * 2. Recent-signal panel rect height === 88 (regression — R256 invariant).
26
+ * 3. Working row hitbox y === 21 (R271 invariant: row.y0=32 unchanged).
27
+ * 4. Flow-arrow path starts with "M140,68" (tracks new offline cy=68).
28
+ * 5. R275 freshness chip absent at fresh (regression).
29
+ * 6. R276 orbit particles absent (regression).
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-panel="legend"]', { timeout: 10000 });
62
+ await page.waitForSelector('[data-topo-panel="recent"]', { timeout: 10000 });
63
+ await page.waitForSelector('[data-legend-status="working"] rect', { timeout: 10000 });
64
+ await page.waitForSelector('[data-legend-flow-arrow]', { timeout: 10000 });
65
+ await page.waitForTimeout(300);
66
+
67
+ // Hover working row to materialize the tinted hitbox rect for the
68
+ // y=21 invariant check (R271). Without hover the rect carries the
69
+ // same y attr but tint fills are transparent.
70
+ await page.locator('[data-legend-status="working"]').hover();
71
+ await page.waitForTimeout(150);
72
+
73
+ const probe = await page.evaluate(() => {
74
+ const legendG = document.querySelector('[data-topo-panel="legend"]');
75
+ const recentG = document.querySelector('[data-topo-panel="recent"]');
76
+ const workingRow = document.querySelector('[data-legend-status="working"]');
77
+ const workingHitbox = workingRow ? workingRow.querySelector('rect') : null;
78
+ const flowArrow = document.querySelector('[data-legend-flow-arrow]');
79
+ const freshnessChip = document.querySelector('[data-freshness-chip]');
80
+ const orbits = document.querySelectorAll('[data-topo-orbit-bucket]');
81
+ return {
82
+ legendHeight: legendG?.querySelector('rect')?.getAttribute('height') ?? null,
83
+ recentHeight: recentG?.querySelector('rect')?.getAttribute('height') ?? null,
84
+ workingHitboxY: workingHitbox ? +workingHitbox.getAttribute('y') : null,
85
+ flowArrowD: flowArrow ? flowArrow.getAttribute('d') : null,
86
+ freshnessPresent: freshnessChip !== null,
87
+ orbitCount: orbits.length,
88
+ };
89
+ });
90
+ await browser.close();
91
+
92
+ const results = {
93
+ legend_height_88: probe.legendHeight === '88',
94
+ recent_height_88_regression: probe.recentHeight === '88',
95
+ r271_working_hitbox_y_21: probe.workingHitboxY === 21,
96
+ flow_arrow_starts_at_y68: probe.flowArrowD === 'M140,68 Q164,44 196,68',
97
+ r275_freshness_absent: probe.freshnessPresent === false,
98
+ r276_orbit_absent: probe.orbitCount === 0,
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} legend compress:`, JSON.stringify(results),
102
+ '\n legend height:', probe.legendHeight,
103
+ '\n recent height:', probe.recentHeight,
104
+ '\n working hitbox y:', probe.workingHitboxY,
105
+ '\n flow arrow d:', probe.flowArrowD,
106
+ '\n orbit count:', probe.orbitCount);
107
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,126 @@
1
+ /* Round 204 verification: legend count text recedes when its status
2
+ * tier is empty (count = 0). Pre-R204 "0" at opacity 0.65 read
3
+ * visually identical to "12" at opacity 0.65; R204 drops empty rows
4
+ * to 0.30 (dark) / 0.28 (light) so the eye gets a "no nodes in this
5
+ * tier" signal without parsing the digit.
6
+ *
7
+ * Test (cyber dark, all 4 sessions = working → idle=0, offline=0):
8
+ * 1. Three legend count texts present with data-legend-count for
9
+ * working / idle / offline.
10
+ * 2. Working row: count='4', data-legend-count-empty='false',
11
+ * opacity = 0.65 (idle, not hovered).
12
+ * 3. Idle row: count='0', data-legend-count-empty='true',
13
+ * opacity ≈ 0.30 (dim recede).
14
+ * 4. Offline row: count='0', data-legend-count-empty='true',
15
+ * opacity ≈ 0.30.
16
+ * 5. Hover working row → its count bumps to opacity ~0.95
17
+ * (existing R95 hover behaviour unbroken).
18
+ * 6. Hover idle row → empty-tier opacity DOES NOT bump (empty
19
+ * gate wins over hover). Recedes regardless.
20
+ * 7. Transition: opacity 150ms ease-out still on all three.
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
32
+ });
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
+ // All 4 working — idle / offline tiers empty
40
+ const mk = (alias) => ({
41
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
42
+ network_id: nid, project_dir: null,
43
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
44
+ });
45
+ await route.fulfill({ response: r, json: { ...b, sessions: [
46
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
47
+ ] } });
48
+ });
49
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
50
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
51
+
52
+ const page = await ctx.newPage();
53
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
54
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
55
+ await page.waitForSelector('[data-legend-count="working"]', { timeout: 10000 });
56
+ await page.waitForTimeout(300);
57
+
58
+ const probe = async () => page.evaluate(() => {
59
+ const pick = (key) => {
60
+ const el = document.querySelector(`[data-legend-count="${key}"]`);
61
+ if (!el) return null;
62
+ return {
63
+ key,
64
+ text: el.textContent,
65
+ empty: el.getAttribute('data-legend-count-empty'),
66
+ opacity: parseFloat(el.getAttribute('opacity') || ''),
67
+ transition: el.style.transition,
68
+ };
69
+ };
70
+ return { working: pick('working'), idle: pick('idle'), offline: pick('offline') };
71
+ });
72
+
73
+ const idle = await probe();
74
+
75
+ // Hover the working row — verify R95 hover bump still fires (count > 0)
76
+ await page.hover('[data-legend-status="working"]');
77
+ await page.waitForTimeout(200);
78
+ const hovWorking = await probe();
79
+
80
+ // Move off, then hover the empty (idle=0) row — verify empty gate wins
81
+ await page.mouse.move(0, 0);
82
+ await page.waitForTimeout(200);
83
+ await page.hover('[data-legend-status="idle"]');
84
+ await page.waitForTimeout(200);
85
+ const hovIdle = await probe();
86
+
87
+ await page.mouse.move(0, 0);
88
+ await page.waitForTimeout(200);
89
+
90
+ await browser.close();
91
+
92
+ const opacityNear = (got, want, tol = 0.04) => Math.abs(got - want) < tol;
93
+
94
+ const results = {
95
+ working_present: !!idle?.working,
96
+ idle_present: !!idle?.idle,
97
+ offline_present: !!idle?.offline,
98
+
99
+ working_text_4: idle?.working?.text === '4',
100
+ working_not_empty: idle?.working?.empty === 'false',
101
+ working_opacity_0p65: opacityNear(idle?.working?.opacity ?? 0, 0.65),
102
+
103
+ idle_text_0: idle?.idle?.text === '0',
104
+ idle_empty_true: idle?.idle?.empty === 'true',
105
+ idle_opacity_dim: opacityNear(idle?.idle?.opacity ?? 0, 0.30),
106
+
107
+ offline_text_0: idle?.offline?.text === '0',
108
+ offline_empty_true: idle?.offline?.empty === 'true',
109
+ offline_opacity_dim: opacityNear(idle?.offline?.opacity ?? 0, 0.30),
110
+
111
+ // R95 hover still bumps when row populated
112
+ working_hover_opacity_high: opacityNear(hovWorking?.working?.opacity ?? 0, 0.95),
113
+
114
+ // Empty-gate wins: hovering an empty row does NOT bump its count opacity
115
+ idle_hover_stays_dim: opacityNear(hovIdle?.idle?.opacity ?? 0, 0.30),
116
+
117
+ // Transition still in place across all 3
118
+ all_have_transition: ['working', 'idle', 'offline'].every(k =>
119
+ /opacity\s+(?:150ms|0\.15s)/.test(idle?.[k]?.transition || '')),
120
+ };
121
+ const ok = Object.values(results).every(Boolean);
122
+ console.log(`${ok ? '✅' : '❌'} legend count empty:`, JSON.stringify(results),
123
+ `\n idle:`, idle,
124
+ `\n hovWorking:`, hovWorking,
125
+ `\n hovIdle:`, hovIdle);
126
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,90 @@
1
+ /* Round 292 verification: legend panel header count picks up explicit
2
+ * fontVariantNumeric='tabular-nums' parity with the recent-signal
3
+ * panel header count (R232). Both panel headers now declare tabular-
4
+ * nums explicitly in inline style.
5
+ *
6
+ * Pre-R292 the legend count inherited tabular digit width via
7
+ * monospace but lacked the explicit directive its sibling
8
+ * recent-signal count carries. Post-R292 the panel-header pair
9
+ * shares the same explicit declaration.
10
+ *
11
+ * Note distinct from R274 (topo-legend-count-tabular-test.mjs)
12
+ * which probes the per-ROW counts (working/idle/offline). R292
13
+ * is the panel-level HEADER count ("N nodes" right-anchored).
14
+ *
15
+ * Contract:
16
+ * - [data-legend-panel-count] computed font-variant-numeric
17
+ * includes 'tabular-nums'.
18
+ * - Text content still '{N} node[s]'.
19
+ * - [data-recent-panel-count] also tabular (sibling parity check).
20
+ * - R291 starfield 14 dots + R290 three radar rings intact.
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
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, model) => ({
38
+ alias, status: 'working', model, 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', 'claude-opus-4'),
44
+ mk('beta', 'gpt-4o'),
45
+ ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.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.waitForSelector('[data-legend-panel-count]', { timeout: 15000 });
53
+ await page.waitForTimeout(300);
54
+
55
+ const probe = await page.evaluate(() => {
56
+ const legendCount = document.querySelector('[data-legend-panel-count]');
57
+ const recentCount = document.querySelector('[data-recent-panel-count]');
58
+ const legendFvn = legendCount ? getComputedStyle(legendCount).fontVariantNumeric : null;
59
+ const recentFvn = recentCount ? getComputedStyle(recentCount).fontVariantNumeric : null;
60
+ const dots = document.querySelectorAll('[data-topo-starfield-dot]');
61
+ const rings = [...document.querySelectorAll('[data-topo-radar-ring]')]
62
+ .map(r => r.getAttribute('data-topo-radar-ring')).sort((a, b) => +a - +b);
63
+ return {
64
+ legendText: legendCount?.textContent ?? null,
65
+ legendFvn,
66
+ legendFontFamily: legendCount ? getComputedStyle(legendCount).fontFamily : null,
67
+ recentFvn,
68
+ starfieldCount: dots.length,
69
+ radarRings: rings,
70
+ };
71
+ });
72
+ await browser.close();
73
+
74
+ const hasTab = (s) => /tabular-nums/.test(s || '');
75
+
76
+ const results = {
77
+ legend_count_tabular: hasTab(probe.legendFvn),
78
+ legend_count_text_kept: /^\d+ node/.test(probe.legendText || ''),
79
+ legend_count_monospace: /mono/i.test(probe.legendFontFamily || ''),
80
+ recent_count_tabular_sib: hasTab(probe.recentFvn),
81
+ r291_starfield_14: probe.starfieldCount === 14,
82
+ r290_three_rings: JSON.stringify(probe.radarRings) === JSON.stringify(['170', '250', '330']),
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} legend count tabular parity:`, JSON.stringify(results),
86
+ '\n legend text:', JSON.stringify(probe.legendText),
87
+ '\n legend font-variant-numeric:', probe.legendFvn,
88
+ '\n legend font-family:', probe.legendFontFamily,
89
+ '\n recent (sibling) font-variant-numeric:', probe.recentFvn);
90
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,87 @@
1
+ /* Round 274 verification: legend per-row count text gains tabular-nums.
2
+ *
3
+ * Pre-R274 the legend per-row count at x=215 used fontFamily="monospace"
4
+ * (typically tabular by nature) but NO explicit fontVariantNumeric:
5
+ * 'tabular-nums'. The recent-signal panel header flow-count (R225)
6
+ * AND the group-label pip strip (R230) both explicitly set tabular-
7
+ * nums for digit-width stability across 9→10 / 99→100 thresholds.
8
+ *
9
+ * R274 closes the consistency gap: legend per-row counts now also
10
+ * carry fontVariantNumeric: 'tabular-nums'. Belt-and-suspenders for
11
+ * monospace fonts where some implementations have subtle digit-pair
12
+ * variance.
13
+ *
14
+ * Test scope:
15
+ * 1. Working row count text has computed fontVariantNumeric ===
16
+ * 'tabular-nums'.
17
+ * 2. Idle row count text same.
18
+ * 3. Offline row count text same.
19
+ * 4. R273 Layout toggle Grid inactive has hover:text-cyan-300
20
+ * (regression).
21
+ */
22
+ import { chromium } from 'playwright';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
26
+ const browser = await chromium.launch({ headless: true });
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
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, status = 'working') => ({
38
+ alias, status, 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
+ // Mix of statuses so all 3 legend row counts have non-zero values
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [
44
+ mk('alpha', 'working'), mk('beta', 'idle'),
45
+ mk('gamma', 'idle'), mk('delta', 'idle'),
46
+ ] } });
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.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
54
+ await page.waitForSelector('[data-legend-count="working"]', { timeout: 10000 });
55
+ await page.waitForSelector('[data-legend-count="idle"]', { timeout: 10000 });
56
+ await page.waitForSelector('[data-legend-count="offline"]', { timeout: 10000 });
57
+ await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 10000 });
58
+ await page.waitForTimeout(300);
59
+
60
+ const probe = await page.evaluate(() => {
61
+ const working = document.querySelector('[data-legend-count="working"]');
62
+ const idle = document.querySelector('[data-legend-count="idle"]');
63
+ const offline = document.querySelector('[data-legend-count="offline"]');
64
+ const grid = document.querySelector('[data-topo-chrome-layout="grid"]');
65
+ return {
66
+ workingFVN: working ? window.getComputedStyle(working).fontVariantNumeric : null,
67
+ idleFVN: idle ? window.getComputedStyle(idle).fontVariantNumeric : null,
68
+ offlineFVN: offline ? window.getComputedStyle(offline).fontVariantNumeric : null,
69
+ gridClasses: grid ? grid.className.toString() : null,
70
+ };
71
+ });
72
+ await browser.close();
73
+
74
+ const isTabular = (v) => v != null && v.includes('tabular-nums');
75
+
76
+ const results = {
77
+ working_count_tabular: isTabular(probe.workingFVN),
78
+ idle_count_tabular: isTabular(probe.idleFVN),
79
+ offline_count_tabular: isTabular(probe.offlineFVN),
80
+ r273_grid_has_cyan_hover_text: (probe.gridClasses || '').includes('hover:text-cyan-300'),
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} legend count tabular-nums:`, JSON.stringify(results),
84
+ '\n working FVN:', probe.workingFVN,
85
+ '\n idle FVN: ', probe.idleFVN,
86
+ '\n offline FVN:', probe.offlineFVN);
87
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,88 @@
1
+ /* Round 95 verification: legend rows show live counts. Fleet = 3
2
+ * working + 2 idle + 1 offline. Each row's data-legend-count
3
+ * attribute matches the expected number. Pinning a row brightens
4
+ * its count alongside the label.
5
+ *
6
+ * The count is right-aligned at x=215 (after the flow-arrow swatch
7
+ * which spans x=140-196 on the offline row), pointerEvents:none so
8
+ * the row's R55 hover affordance still hits.
9
+ */
10
+ import { chromium } from 'playwright';
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
14
+ const browser = await chromium.launch({ headless: true });
15
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
16
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
17
+ await ctx.addInitScript(() => {
18
+ try {
19
+ localStorage.setItem('anet-theme', 'cyber');
20
+ localStorage.setItem('anet-topo-layout', 'ring');
21
+ sessionStorage.setItem('anet_v3_auth', '1');
22
+ sessionStorage.removeItem('anet-topo-pinned-status');
23
+ } catch {}
24
+ });
25
+
26
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+ const stale10 = new Date(Date.now() - 10 * 60 * 1000).toISOString();
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias, status, ts) => ({
33
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
34
+ network_id: nid, project_dir: null,
35
+ created_at: ts, updated_at: ts, last_seen_at: ts,
36
+ });
37
+ const sessions = [
38
+ mk('wkr1', 'working', fresh),
39
+ mk('wkr2', 'working', fresh),
40
+ mk('wkr3', 'working', fresh),
41
+ mk('idl1', 'idle', fresh),
42
+ mk('idl2', 'idle', fresh),
43
+ mk('off1', 'offline', stale10),
44
+ ];
45
+ await route.fulfill({ response: r, json: { ...b, sessions } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
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 === 6, { timeout: 30000 });
52
+ await page.waitForTimeout(400);
53
+
54
+ const counts = () => page.evaluate(() => ({
55
+ working: document.querySelector('[data-legend-count="working"]')?.textContent,
56
+ idle: document.querySelector('[data-legend-count="idle"]')?.textContent,
57
+ offline: document.querySelector('[data-legend-count="offline"]')?.textContent,
58
+ }));
59
+ const opacities = () => page.evaluate(() => ({
60
+ working: +(document.querySelector('[data-legend-count="working"]')?.getAttribute('opacity') || '0'),
61
+ idle: +(document.querySelector('[data-legend-count="idle"]')?.getAttribute('opacity') || '0'),
62
+ offline: +(document.querySelector('[data-legend-count="offline"]')?.getAttribute('opacity') || '0'),
63
+ }));
64
+
65
+ const c = await counts();
66
+ const opsRest = await opacities();
67
+
68
+ // Pin the working row — its count should brighten.
69
+ await page.locator('[data-legend-status="working"]').click();
70
+ await page.waitForTimeout(250);
71
+ const opsPinned = await opacities();
72
+
73
+ await browser.close();
74
+
75
+ const results = {
76
+ workingCount_3: c.working === '3',
77
+ idleCount_2: c.idle === '2',
78
+ offlineCount_1: c.offline === '1',
79
+ resting_dim: opsRest.working < 0.7 && opsRest.idle < 0.7 && opsRest.offline < 0.7,
80
+ pinnedWorking_bright: opsPinned.working >= 0.9,
81
+ pinnedWorking_othersUnchanged: opsPinned.idle < 0.7 && opsPinned.offline < 0.7,
82
+ };
83
+ const ok = Object.values(results).every(Boolean);
84
+ console.log(`${ok ? '✅' : '❌'} legend counts:`, JSON.stringify(results),
85
+ `\n counts=`, c,
86
+ `\n opsRest=`, opsRest,
87
+ `\n opsPinned=`, opsPinned);
88
+ process.exit(ok ? 0 : 1);