@sleep2agi/agent-network-dashboard 0.4.6-preview.32 → 0.4.6-preview.321

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 (487) 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/07lsi~s-c0wvh.css +2 -0
  171. package/.next/static/chunks/08kf-.bgs10ze.js +1 -0
  172. package/.next/static/chunks/08s10wueoqydl.js +1 -0
  173. package/.next/static/chunks/0_l-j6k-ic81v.js +4 -0
  174. package/.next/static/chunks/0ggj5ummw2a84.js +7 -0
  175. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  176. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  177. package/.next/static/chunks/0wlu_nyra0im0.js +1 -0
  178. package/.next/static/chunks/0x8x.29hkju5a.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 +6091 -262
  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-logo-transition-test.mjs +93 -0
  214. package/scripts/topo-brand-watermark-test.mjs +97 -0
  215. package/scripts/topo-canvas-aria-test.mjs +108 -0
  216. package/scripts/topo-chat-target-breath-test.mjs +84 -0
  217. package/scripts/topo-chat-target-ring-ease-test.mjs +82 -0
  218. package/scripts/topo-chat-target-ring-fade-test.mjs +124 -0
  219. package/scripts/topo-chip-empty-recede-test.mjs +116 -0
  220. package/scripts/topo-chip-focus-test.mjs +95 -0
  221. package/scripts/topo-chip-pin-mirror-test.mjs +113 -0
  222. package/scripts/topo-chip-row-semantic-gap-test.mjs +97 -0
  223. package/scripts/topo-chip-row-tabular-test.mjs +111 -0
  224. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  225. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  226. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  227. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  228. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  229. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  230. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  231. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  232. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  233. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  234. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  235. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  236. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  237. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  238. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  239. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  240. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  241. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  242. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  243. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  244. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  245. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  246. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  247. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  248. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  249. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  250. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  251. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  252. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  253. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  254. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  255. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  256. package/scripts/topo-edge-fadein-test.mjs +106 -0
  257. package/scripts/topo-edge-focus-test.mjs +113 -0
  258. package/scripts/topo-edge-hover-test.mjs +93 -0
  259. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  260. package/scripts/topo-edge-pill-test.mjs +105 -0
  261. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  262. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  263. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  264. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  265. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  266. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  267. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  268. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  269. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  270. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  271. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  272. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  273. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  274. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  275. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  276. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  277. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  278. package/scripts/topo-fit-key-test.mjs +106 -0
  279. package/scripts/topo-freshness-chip-test.mjs +74 -0
  280. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  281. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  282. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  283. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  284. package/scripts/topo-group-count-test.mjs +6 -1
  285. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  286. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  287. package/scripts/topo-group-label-hover-test.mjs +119 -0
  288. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  289. package/scripts/topo-group-label-tint-test.mjs +99 -0
  290. package/scripts/topo-group-label-title-test.mjs +83 -0
  291. package/scripts/topo-group-pin-test.mjs +119 -0
  292. package/scripts/topo-group-pip-fadein-test.mjs +100 -0
  293. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  294. package/scripts/topo-group-status-pips-test.mjs +103 -0
  295. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  296. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  297. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  298. package/scripts/topo-groupbox-live-test.mjs +100 -0
  299. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  300. package/scripts/topo-groupbox-test.mjs +4 -2
  301. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  302. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  303. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  304. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  305. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  306. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  307. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  308. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  309. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  310. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  311. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  312. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  313. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  314. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  315. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  316. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  317. package/scripts/topo-hub-a11y-test.mjs +112 -0
  318. package/scripts/topo-hub-busyness-test.mjs +100 -0
  319. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  320. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  321. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  322. package/scripts/topo-hub-fadein-test.mjs +88 -0
  323. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  324. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  325. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  326. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  327. package/scripts/topo-hub-title-test.mjs +93 -0
  328. package/scripts/topo-hub-working-count-test.mjs +99 -0
  329. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  330. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  331. package/scripts/topo-kicker-color-test.mjs +93 -0
  332. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  333. package/scripts/topo-kicker-weight-test.mjs +98 -0
  334. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  335. package/scripts/topo-label-lift-test.mjs +119 -0
  336. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  337. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  338. package/scripts/topo-layout-key-test.mjs +91 -0
  339. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  340. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  341. package/scripts/topo-layout-toggle-test.mjs +131 -0
  342. package/scripts/topo-legend-compress-test.mjs +107 -0
  343. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  344. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  345. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  346. package/scripts/topo-legend-count-test.mjs +88 -0
  347. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  348. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  349. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  350. package/scripts/topo-legend-hover-test.mjs +113 -0
  351. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  352. package/scripts/topo-legend-offline-label-simplify-test.mjs +85 -0
  353. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  354. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  355. package/scripts/topo-legend-pin-test.mjs +128 -0
  356. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  357. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  358. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  359. package/scripts/topo-legend-row-title-test.mjs +102 -0
  360. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  361. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  362. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  363. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  364. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  365. package/scripts/topo-minimap-test.mjs +68 -0
  366. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  367. package/scripts/topo-mobile-chips-test.mjs +103 -0
  368. package/scripts/topo-monogram-font-test.mjs +112 -0
  369. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  370. package/scripts/topo-node-a11y-test.mjs +107 -0
  371. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  372. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  373. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  374. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  375. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  376. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  377. package/scripts/topo-node-title-peers-test.mjs +107 -0
  378. package/scripts/topo-node-title-test.mjs +101 -0
  379. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  380. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  381. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  382. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  383. package/scripts/topo-overlap-test.mjs +32 -5
  384. package/scripts/topo-panel-elevation-test.mjs +76 -0
  385. package/scripts/topo-panel-fadein-test.mjs +102 -0
  386. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  387. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  388. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  389. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  390. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  391. package/scripts/topo-particle-stagger-test.mjs +82 -0
  392. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  393. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  394. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  395. package/scripts/topo-pin-intersection-test.mjs +121 -0
  396. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  397. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  398. package/scripts/topo-pin-persistence-test.mjs +105 -0
  399. package/scripts/topo-popover-meta-test.mjs +78 -0
  400. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  401. package/scripts/topo-pressure-bar-test.mjs +69 -0
  402. package/scripts/topo-pressure-hover-test.mjs +123 -0
  403. package/scripts/topo-pressure-pin-test.mjs +119 -0
  404. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  405. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  406. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  407. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  408. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  409. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  410. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  411. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  412. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  413. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  414. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  415. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  416. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  417. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  418. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  419. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  420. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  421. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  422. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  423. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  424. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  425. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  426. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  427. package/scripts/topo-recent-row-title-test.mjs +112 -0
  428. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  429. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  430. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  431. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  432. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  433. package/scripts/topo-reduced-motion-test.mjs +77 -0
  434. package/scripts/topo-reset-spin-test.mjs +120 -0
  435. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  436. package/scripts/topo-spokes-retired-test.mjs +93 -0
  437. package/scripts/topo-starfield-density-test.mjs +89 -0
  438. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  439. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  440. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  441. package/scripts/topo-svg-focus-test.mjs +112 -0
  442. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  443. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  444. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  445. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  446. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  447. package/scripts/topo-tier-ring-test.mjs +69 -0
  448. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  449. package/scripts/topo-tier-stagger-test.mjs +89 -0
  450. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  451. package/scripts/topo-title-tracking-test.mjs +98 -0
  452. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  453. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  454. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  455. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  456. package/scripts/topo-tz-ghost-test.mjs +69 -0
  457. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  458. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  459. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  460. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  461. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  462. package/scripts/topo-vendor-pill-test.mjs +125 -0
  463. package/scripts/topo-vendor-pin-test.mjs +125 -0
  464. package/scripts/topo-view-smooth-test.mjs +131 -0
  465. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  466. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  467. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  468. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  469. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  470. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  471. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  472. package/.next/static/chunks/08b1xx89c0ts9.js +0 -1
  473. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  474. package/.next/static/chunks/0_8snedeata9k.js +0 -1
  475. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  476. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  477. package/.next/static/chunks/0h_~v6f9o~_in.js +0 -1
  478. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  479. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  480. package/.next/static/chunks/0pl7l_.2im.j0.js +0 -7
  481. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  482. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  483. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  484. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  485. /package/.next/static/{QkCLPQNNPLHlzKVvmAInn → 95X2ICTd2SB5ohx7R2QN2}/_buildManifest.js +0 -0
  486. /package/.next/static/{QkCLPQNNPLHlzKVvmAInn → 95X2ICTd2SB5ohx7R2QN2}/_clientMiddlewareManifest.js +0 -0
  487. /package/.next/static/{QkCLPQNNPLHlzKVvmAInn → 95X2ICTd2SB5ohx7R2QN2}/_ssgManifest.js +0 -0
@@ -0,0 +1,95 @@
1
+ /* Round 267 verification: title block adopts leading-tight on both
2
+ * kicker and h2 for tighter editorial-style rhythm.
3
+ *
4
+ * Pre-R267 the title block used Tailwind compound defaults:
5
+ * kicker (text-xs): font 12px / line 16px (ratio ~1.33)
6
+ * h2 (text-lg): font 18px / line 28px (ratio ~1.56)
7
+ * Total title block height: ~44 px
8
+ *
9
+ * R267 adds leading-tight (1.25) to both:
10
+ * kicker: 12px font in 15px line
11
+ * h2: 18px font in 22.5px line
12
+ * Total: ~37.5 px (~15% more compact)
13
+ *
14
+ * Test scope:
15
+ * 1. Kicker present with data-topo-section-kicker attr.
16
+ * 2. h2 present with data-topo-section-title attr.
17
+ * 3. Kicker computed line-height < 16px (was 16px; should be 15px).
18
+ * 4. h2 computed line-height < 28px (was 28px; should be 22.5px).
19
+ * 5. Total title block bounding height ≤ 45 px (was ~44; should be ~37).
20
+ * Allow generous slop since browser font metrics + sub-pixel
21
+ * rounding vary.
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
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
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [
44
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
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.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
53
+ await page.waitForSelector('[data-topo-section-kicker]', { timeout: 10000 });
54
+ await page.waitForSelector('[data-topo-section-title]', { timeout: 10000 });
55
+ await page.waitForTimeout(300);
56
+
57
+ const probe = await page.evaluate(() => {
58
+ const kicker = document.querySelector('[data-topo-section-kicker]');
59
+ const title = document.querySelector('[data-topo-section-title]');
60
+ // The two stack inside a parent <div>; measure parent's bounding box
61
+ // for "title block height".
62
+ const parent = kicker?.parentElement ?? null;
63
+ const parseLH = (el) => {
64
+ const lh = window.getComputedStyle(el).lineHeight;
65
+ // Chrome returns either '15px' or 'normal' or a multiplier like '1.25'.
66
+ // For px values, parseFloat works; for 'normal', return font-size.
67
+ if (lh === 'normal') {
68
+ return parseFloat(window.getComputedStyle(el).fontSize);
69
+ }
70
+ return parseFloat(lh);
71
+ };
72
+ return {
73
+ kickerHasClass: kicker?.classList.contains('leading-tight') ?? false,
74
+ titleHasClass: title?.classList.contains('leading-tight') ?? false,
75
+ kickerLH: kicker ? parseLH(kicker) : null,
76
+ titleLH: title ? parseLH(title) : null,
77
+ parentHeight: parent ? parent.getBoundingClientRect().height : null,
78
+ };
79
+ });
80
+ await browser.close();
81
+
82
+ const results = {
83
+ kicker_has_leading_tight_class: probe.kickerHasClass,
84
+ title_has_leading_tight_class: probe.titleHasClass,
85
+ kicker_line_height_under_16: probe.kickerLH != null && probe.kickerLH < 16,
86
+ title_line_height_under_28: probe.titleLH != null && probe.titleLH < 28,
87
+ // Pre-R267 title block was ~44px tall. Post-R267 target ~37.5px.
88
+ // Allow slop for browser metrics: <= 45 confirms compression happened.
89
+ title_block_height_under_45: probe.parentHeight != null && probe.parentHeight <= 45,
90
+ };
91
+ const ok = Object.values(results).every(Boolean);
92
+ console.log(`${ok ? '✅' : '❌'} title block tight leading:`, JSON.stringify(results),
93
+ '\n kicker line-height:', probe.kickerLH, '/ title line-height:', probe.titleLH,
94
+ '\n title block height:', probe.parentHeight);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,98 @@
1
+ /* Round 286 verification: h2 title 'Command mesh' adopts tracking-
2
+ * tight (-0.025em) to pair with R285 kicker tracking-widest (0.1em).
3
+ * Wide eyebrow + tight headline is the editorial-pairing convention.
4
+ *
5
+ * At text-lg = 18px, tracking-tight resolves to:
6
+ * 18 * -0.025 = -0.45px letter-spacing
7
+ *
8
+ * Contract:
9
+ * - [data-topo-section-title] computed letter-spacing is negative
10
+ * and ~ -0.025em (= ~ -0.45px at 18px).
11
+ * - Text content still 'Command mesh'.
12
+ * - font-weight still 600 (font-semibold).
13
+ * - Kicker still tracking-widest (R285 regression).
14
+ * - R283 monogram stroke / R282 watermark intact.
15
+ */
16
+ import { chromium } from 'playwright';
17
+ import { readFileSync } from 'node:fs';
18
+
19
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
26
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias, model) => ({
32
+ alias, status: 'working', model, runtime: 'claude-code-cli',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ mk('alpha', 'claude-opus-4'),
38
+ mk('beta', 'gpt-4o'),
39
+ ] } });
40
+ });
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
42
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
43
+
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
46
+ await page.waitForSelector('[data-topo-section-title]', { timeout: 15000 });
47
+ await page.waitForSelector('[data-topo-brand-watermark]', { timeout: 10000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const title = document.querySelector('[data-topo-section-title]');
52
+ const kicker = document.querySelector('[data-topo-section-kicker]');
53
+ const tCs = title ? getComputedStyle(title) : null;
54
+ const kCs = kicker ? getComputedStyle(kicker) : null;
55
+ const alphaNode = document.querySelector('g[data-node="alpha"]');
56
+ const alphaCircles = alphaNode ? [...alphaNode.querySelectorAll('circle')] : [];
57
+ const alphaAvatarCircle = alphaCircles
58
+ .map(c => ({ r: +c.getAttribute('r') || 0, sw: c.getAttribute('stroke-width'), isBadge: c.hasAttribute('data-runtime-badge') }))
59
+ .filter(c => c.r > 0 && c.r < 18 && !c.isBadge)
60
+ .sort((a, b) => b.r - a.r)[0];
61
+ const watermark = document.querySelector('[data-topo-brand-watermark]');
62
+ return {
63
+ titleLetterSpacing: tCs?.letterSpacing ?? null,
64
+ titleFontSize: tCs?.fontSize ?? null,
65
+ titleFontWeight: tCs?.fontWeight ?? null,
66
+ titleText: title?.textContent ?? null,
67
+ kickerLetterSpacing: kCs?.letterSpacing ?? null,
68
+ kickerFontSize: kCs?.fontSize ?? null,
69
+ alphaAvatarStroke: alphaAvatarCircle?.sw ?? null,
70
+ watermarkPresent: watermark !== null,
71
+ };
72
+ });
73
+ await browser.close();
74
+
75
+ const tLs = parseFloat(probe.titleLetterSpacing) || 0;
76
+ const tFs = parseFloat(probe.titleFontSize) || 18;
77
+ const tLsPerEm = tFs ? tLs / tFs : 0;
78
+
79
+ const kLs = parseFloat(probe.kickerLetterSpacing) || 0;
80
+ const kFs = parseFloat(probe.kickerFontSize) || 12;
81
+ const kLsPerEm = kFs ? kLs / kFs : 0;
82
+
83
+ const results = {
84
+ title_present: probe.titleText === 'Command mesh',
85
+ title_tight_negative_ls: tLs < 0,
86
+ title_tight_about_n025em: tLsPerEm <= -0.02 && tLsPerEm >= -0.03,
87
+ title_font_weight_600: String(probe.titleFontWeight) === '600',
88
+ kicker_still_widest_r285: kLsPerEm >= 0.08 && kLsPerEm <= 0.12,
89
+ r283_monogram_stroke_kept: probe.alphaAvatarStroke === '1.5',
90
+ r282_watermark_present: probe.watermarkPresent,
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} title tracking:`, JSON.stringify(results),
94
+ '\n title text:', JSON.stringify(probe.titleText),
95
+ '\n title letter-spacing:', probe.titleLetterSpacing, `(=${tLsPerEm.toFixed(4)}em)`,
96
+ '\n title font-size:', probe.titleFontSize, ' weight:', probe.titleFontWeight,
97
+ '\n kicker letter-spacing (R285):', probe.kickerLetterSpacing, `(=${kLsPerEm.toFixed(3)}em)`);
98
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 298 verification: title-block flex gap shrinks from gap-3
2
+ * (12px) → gap-2.5 (10px). Tightens the brand-logo + kicker/h2 unit
3
+ * to read as a single editorial "logo + title" badge rather than
4
+ * two loosely-spaced elements.
5
+ *
6
+ * Contract:
7
+ * - The parent of [data-topo-brand-logo] has computed column-gap
8
+ * of 10px (gap-2.5 in Tailwind).
9
+ * - Pre-R298 was 12px (gap-3).
10
+ * - Brand-logo + kicker + h2 all still render in the title block.
11
+ * - R297 brand-logo transition + R296 kicker color + R295 swatch
12
+ * r=6 + R294 pulse absent intact.
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const browser = await chromium.launch({ headless: true });
19
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
23
+ });
24
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias, model) => ({
30
+ alias, status: 'working', model, runtime: 'claude-code-cli',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ await route.fulfill({ response: r, json: { ...b, sessions: [
35
+ mk('alpha', 'claude-opus-4'),
36
+ mk('beta', 'gpt-4o'),
37
+ ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
41
+
42
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
44
+ await page.waitForSelector('[data-topo-brand-logo]', { timeout: 15000 });
45
+ await page.waitForTimeout(300);
46
+
47
+ const probe = await page.evaluate(() => {
48
+ const logo = document.querySelector('[data-topo-brand-logo]');
49
+ const flexParent = logo?.parentElement;
50
+ const cs = flexParent ? getComputedStyle(flexParent) : null;
51
+ const kicker = document.querySelector('[data-topo-section-kicker]');
52
+ const title = document.querySelector('[data-topo-section-title]');
53
+ const swatch = document.querySelector('[data-legend-swatch="working"]');
54
+ const pulses = document.querySelectorAll('[data-pulse-wrapper]');
55
+ return {
56
+ parentColumnGap: cs?.columnGap ?? null,
57
+ parentGap: cs?.gap ?? null,
58
+ parentDisplay: cs?.display ?? null,
59
+ brandLogoPresent: logo !== null,
60
+ kickerPresent: kicker !== null,
61
+ titlePresent: title !== null,
62
+ swatchR: swatch?.getAttribute('r') ?? null,
63
+ pulseCount: pulses.length,
64
+ brandLogoTransition: logo ? getComputedStyle(logo).transition : null,
65
+ };
66
+ });
67
+ await browser.close();
68
+
69
+ const gapPx = parseFloat(probe.parentColumnGap) || parseFloat(probe.parentGap) || 0;
70
+
71
+ const results = {
72
+ flex_parent_is_flex: probe.parentDisplay === 'flex',
73
+ gap_is_10px: gapPx >= 9.5 && gapPx <= 10.5,
74
+ brand_logo_present: probe.brandLogoPresent,
75
+ kicker_present: probe.kickerPresent,
76
+ title_present: probe.titlePresent,
77
+ r297_transition_kept: /color/.test(probe.brandLogoTransition || ''),
78
+ r295_swatch_r_6: probe.swatchR === '6',
79
+ r294_pulse_absent: probe.pulseCount === 0,
80
+ };
81
+ const ok = Object.values(results).every(Boolean);
82
+ console.log(`${ok ? '✅' : '❌'} title-block gap:`, JSON.stringify(results),
83
+ '\n computed column-gap:', probe.parentColumnGap,
84
+ '\n computed gap:', probe.parentGap,
85
+ '\n brand-logo transition (R297):', probe.brandLogoTransition);
86
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,83 @@
1
+ /* Round 299 verification: title block container bottom margin
2
+ * mb-3 (12px) → mb-4 (16px). After R298 tightened title-block
3
+ * INTERNAL gap (12→10px), the OUTER margin to the topology canvas
4
+ * widens so the title block reads as a deliberate compact badge
5
+ * with breathing room before the canvas frame begins.
6
+ *
7
+ * Contract:
8
+ * - The container wrapping [data-topo-section-kicker] +
9
+ * [data-topo-section-title] has computed margin-bottom 16px.
10
+ * - R298 internal gap === 10px still in place (regression).
11
+ * - R297 brand-logo transition + R296 kicker color + R295 swatch
12
+ * r=6 + R294 pulse absent intact.
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const browser = await chromium.launch({ headless: true });
19
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
23
+ });
24
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
25
+ await ctx.route('**/api/hub/status*', async (route) => {
26
+ const r = await route.fetch();
27
+ const b = await r.json();
28
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
29
+ const mk = (alias, model) => ({
30
+ alias, status: 'working', model, runtime: 'claude-code-cli',
31
+ network_id: nid, project_dir: null,
32
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
33
+ });
34
+ await route.fulfill({ response: r, json: { ...b, sessions: [
35
+ mk('alpha', 'claude-opus-4'),
36
+ mk('beta', 'gpt-4o'),
37
+ ] } });
38
+ });
39
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
40
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
41
+
42
+ const page = await ctx.newPage();
43
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
44
+ await page.waitForSelector('[data-topo-brand-logo]', { timeout: 15000 });
45
+ await page.waitForTimeout(300);
46
+
47
+ const probe = await page.evaluate(() => {
48
+ const logo = document.querySelector('[data-topo-brand-logo]');
49
+ // Title-block flex container is logo.parentElement. The OUTER margin
50
+ // belongs to logo.parentElement.parentElement (the title-block wrapper
51
+ // that holds left flex + right chip-row).
52
+ const titleBlockContainer = logo?.parentElement?.parentElement;
53
+ const tbCs = titleBlockContainer ? getComputedStyle(titleBlockContainer) : null;
54
+ const flexParent = logo?.parentElement;
55
+ const flexCs = flexParent ? getComputedStyle(flexParent) : null;
56
+ const swatch = document.querySelector('[data-legend-swatch="working"]');
57
+ return {
58
+ titleBlockMarginBottom: tbCs?.marginBottom ?? null,
59
+ titleBlockClass: titleBlockContainer?.className ?? null,
60
+ leftFlexGap: flexCs?.columnGap ?? null,
61
+ brandLogoTransition: logo ? getComputedStyle(logo).transition : null,
62
+ swatchR: swatch?.getAttribute('r') ?? null,
63
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
64
+ };
65
+ });
66
+ await browser.close();
67
+
68
+ const mb = parseFloat(probe.titleBlockMarginBottom) || 0;
69
+ const gap = parseFloat(probe.leftFlexGap) || 0;
70
+
71
+ const results = {
72
+ margin_bottom_16px: mb >= 15.5 && mb <= 16.5,
73
+ margin_not_12px: !(mb >= 11.5 && mb <= 12.5),
74
+ r298_left_gap_10px_kept: gap >= 9.5 && gap <= 10.5,
75
+ r297_transition_kept: /color/.test(probe.brandLogoTransition || ''),
76
+ r295_swatch_r_6: probe.swatchR === '6',
77
+ r294_pulse_absent: probe.pulseCount === 0,
78
+ };
79
+ const ok = Object.values(results).every(Boolean);
80
+ console.log(`${ok ? '✅' : '❌'} title-block margin:`, JSON.stringify(results),
81
+ '\n margin-bottom:', probe.titleBlockMarginBottom,
82
+ '\n left flex gap (R298):', probe.leftFlexGap);
83
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,110 @@
1
+ /* Round 270 verification: chrome strip TOGGLE buttons align inactive
2
+ * hover-bg to the Layout toggle's R163 cyan-preview pattern.
3
+ *
4
+ * Pre-R270:
5
+ * Layout Ring/Grid inactive: 'hover:bg-cyan-500/5 active:bg-cyan-500/15' ← R163 cyan preview
6
+ * nodeSize S/M/L inactive: 'hover:bg-white/5 active:bg-white/10' ← neutral white
7
+ * fullscreen inactive: 'hover:bg-white/5 active:bg-white/10' ← neutral white
8
+ * zoom -/+ + reset: 'hover:bg-white/5 active:bg-white/10' ← actions, no state
9
+ *
10
+ * The TOGGLES (Layout, nodeSize, fullscreen) split into two hover vocabularies:
11
+ * Layout uses cyan-preview, the others use neutral white. R270 unifies the
12
+ * other toggles to the cyan-preview pattern so all chrome TOGGLES preview
13
+ * their active state on hover. Pure ACTIONS (zoom, reset) stay white.
14
+ *
15
+ * Test scope:
16
+ * 1. nodeSize INACTIVE button(s) have 'hover:bg-cyan-500/5' class. With
17
+ * nodeScale=1 (L active), S and M are inactive — probe one of them.
18
+ * 2. nodeSize ACTIVE button still has 'hover:bg-cyan-500/20' (unchanged).
19
+ * 3. Fullscreen INACTIVE has 'hover:bg-cyan-500/5' (we're not in fullscreen).
20
+ * 4. Pure ACTIONS (zoom-out, reset) STILL use 'hover:bg-white/5' (regression).
21
+ * 5. R268 layout toggle border still uses pal.containerBorder (regression).
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
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
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias) => ({
39
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
40
+ network_id: nid, project_dir: null,
41
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
42
+ });
43
+ await route.fulfill({ response: r, json: { ...b, sessions: [
44
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
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.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
53
+ await page.waitForSelector('[data-topo-chrome-nodesize="S"]', { timeout: 10000 });
54
+ await page.waitForSelector('[data-topo-chrome-nodesize="L"]', { timeout: 10000 });
55
+ await page.waitForSelector('[data-topo-chrome-fullscreen]', { timeout: 10000 });
56
+ await page.waitForSelector('[data-topo-chrome-zoom-out]', { timeout: 10000 });
57
+ await page.waitForSelector('[data-topo-chrome-reset]', { timeout: 10000 });
58
+ await page.waitForSelector('[data-topo-chrome-layout-trailer]',{ timeout: 10000 });
59
+ await page.waitForTimeout(300);
60
+
61
+ const probe = await page.evaluate(() => {
62
+ // With default state, nodeScale=1 → L active, S+M inactive.
63
+ const s = document.querySelector('[data-topo-chrome-nodesize="S"]');
64
+ const m = document.querySelector('[data-topo-chrome-nodesize="M"]');
65
+ const l = document.querySelector('[data-topo-chrome-nodesize="L"]');
66
+ const fullscreen = document.querySelector('[data-topo-chrome-fullscreen]');
67
+ const zoomOut = document.querySelector('[data-topo-chrome-zoom-out]');
68
+ const reset = document.querySelector('[data-topo-chrome-reset]');
69
+ const layoutWrap = document.querySelector('[data-topo-chrome-layout-trailer]');
70
+ return {
71
+ sClasses: s ? s.className.toString() : null,
72
+ mClasses: m ? m.className.toString() : null,
73
+ lClasses: l ? l.className.toString() : null,
74
+ fullscreenClasses: fullscreen ? fullscreen.className.toString() : null,
75
+ zoomOutClasses: zoomOut ? zoomOut.className.toString() : null,
76
+ resetClasses: reset ? reset.className.toString() : null,
77
+ layoutBorderColor: layoutWrap ? window.getComputedStyle(layoutWrap).borderTopColor : null,
78
+ };
79
+ });
80
+ await browser.close();
81
+
82
+ const has = (s, cls) => (s || '').includes(cls);
83
+
84
+ // nodeScale defaults to 0.84 (M is active). S and L are inactive.
85
+ const results = {
86
+ // Inactive nodeSize buttons (S + L when M is default-active) → cyan hover
87
+ s_inactive_has_cyan_hover: has(probe.sClasses, 'hover:bg-cyan-500/5'),
88
+ l_inactive_has_cyan_hover: has(probe.lClasses, 'hover:bg-cyan-500/5'),
89
+ // Active nodeSize (M @ 0.84 default) → unchanged cyan-500/20 hover (active variant)
90
+ m_active_has_cyan20_hover: has(probe.mClasses, 'hover:bg-cyan-500/20'),
91
+ // No more white/5 on inactive nodeSize
92
+ s_inactive_no_white_hover: !has(probe.sClasses, 'hover:bg-white/5'),
93
+ // Fullscreen INACTIVE (not in fullscreen) picks up cyan hover
94
+ fullscreen_inactive_has_cyan: has(probe.fullscreenClasses, 'hover:bg-cyan-500/5'),
95
+ fullscreen_no_white_hover: !has(probe.fullscreenClasses, 'hover:bg-white/5'),
96
+ // Pure ACTIONS stay white
97
+ zoom_out_keeps_white_hover: has(probe.zoomOutClasses, 'hover:bg-white/5'),
98
+ reset_keeps_white_hover: has(probe.resetClasses, 'hover:bg-white/5'),
99
+ // R268 regression: layout border still uses pal.containerBorder
100
+ r268_layout_border_unified: probe.layoutBorderColor === 'rgb(42, 42, 74)',
101
+ };
102
+ const ok = Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} toggle inactive cyan hover:`, JSON.stringify(results),
104
+ '\n S classes:', probe.sClasses,
105
+ '\n L (active):', probe.lClasses,
106
+ '\n fullscreen (inactive):', probe.fullscreenClasses,
107
+ '\n zoom-out:', probe.zoomOutClasses,
108
+ '\n reset:', probe.resetClasses,
109
+ '\n layout border:', probe.layoutBorderColor);
110
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,89 @@
1
+ /* Round 265 verification: canvas top-rail gradient gains theme-toggle
2
+ * transition — closes the last canvas-envelope theme-snap.
3
+ *
4
+ * Pre-R265 the top-rail was:
5
+ * <div className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${pal.topRailGradient}`} />
6
+ *
7
+ * pal.topRailGradient is theme-conditional:
8
+ * cyber: 'from-transparent via-cyan-400/70 to-transparent'
9
+ * light: 'from-transparent via-emerald-500/40 to-transparent'
10
+ *
11
+ * On theme toggle the className swap changes the gradient color but no
12
+ * inline transition existed — so the 1px-tall bright line at the
13
+ * canvas top edge SNAPPED while the wrapper bg (R254), border (R254),
14
+ * and shadow (R263) all eased. Small visible holdover.
15
+ *
16
+ * R265 adds `transition: background-image 200ms ease-out`. CSS
17
+ * gradient interpolation works on browsers ≥ Chrome 89 / Safari 14.1 /
18
+ * FF 96 when both gradients share matching stop structure — both
19
+ * gradients here are `from-transparent via-X to-transparent`
20
+ * (identical 3-stop layout).
21
+ *
22
+ * Test scope:
23
+ * 1. Top-rail element present at `[data-topo-top-rail]`.
24
+ * 2. Inline transition contains `background-image` at 200ms (or 0.2s).
25
+ * 3. Computed backgroundImage is a non-empty linear-gradient (not
26
+ * 'none' / empty — confirms the className gradient applied).
27
+ * 4. R263 wrapper box-shadow transition still in place (regression).
28
+ */
29
+ import { chromium } from 'playwright';
30
+ import { readFileSync } from 'node:fs';
31
+
32
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
33
+ const browser = await chromium.launch({ headless: true });
34
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
35
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
36
+ await ctx.addInitScript(() => {
37
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
38
+ });
39
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
40
+ await ctx.route('**/api/hub/status*', async (route) => {
41
+ const r = await route.fetch();
42
+ const b = await r.json();
43
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
44
+ const mk = (alias) => ({
45
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
46
+ network_id: nid, project_dir: null,
47
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
48
+ });
49
+ await route.fulfill({ response: r, json: { ...b, sessions: [
50
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
51
+ ] } });
52
+ });
53
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
54
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
55
+
56
+ const page = await ctx.newPage();
57
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
58
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
59
+ await page.waitForSelector('[data-topo-top-rail]', { timeout: 10000 });
60
+ await page.waitForSelector('[data-topo-wrapper]', { timeout: 10000 });
61
+ await page.waitForTimeout(300);
62
+
63
+ const probe = await page.evaluate(() => {
64
+ const rail = document.querySelector('[data-topo-top-rail]');
65
+ const wrapper = document.querySelector('[data-topo-wrapper]');
66
+ return {
67
+ railTransition: rail ? rail.style.transition : null,
68
+ railBackgroundImage: rail ? window.getComputedStyle(rail).backgroundImage : null,
69
+ wrapperTransition: wrapper ? wrapper.style.transition : null,
70
+ };
71
+ });
72
+ await browser.close();
73
+
74
+ const has = (s, prop) => new RegExp(`${prop}\\s+(?:200ms|0\\.2s)`).test(s || '');
75
+
76
+ const results = {
77
+ rail_present: probe.railTransition !== null,
78
+ rail_transition_has_bg_image_200: has(probe.railTransition, 'background-image'),
79
+ rail_has_non_empty_gradient: probe.railBackgroundImage != null
80
+ && probe.railBackgroundImage !== 'none'
81
+ && probe.railBackgroundImage.includes('linear-gradient'),
82
+ r263_wrapper_has_box_shadow_200: has(probe.wrapperTransition, 'box-shadow'),
83
+ };
84
+ const ok = Object.values(results).every(Boolean);
85
+ console.log(`${ok ? '✅' : '❌'} top-rail theme ease:`, JSON.stringify(results),
86
+ '\n rail transition: ', probe.railTransition,
87
+ '\n rail backgroundImage: ', probe.railBackgroundImage,
88
+ '\n wrapper transition: ', probe.wrapperTransition);
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,69 @@
1
+ /* Round 35 verification: SQL-style timestamps (CommHub format) parse as
2
+ * UTC regardless of the browser's local timezone. Reproduces the latent
3
+ * bug from Round 34 — a 30-min-old offline node would have been ghosted
4
+ * on a UTC+8 browser without parseHubTime. */
5
+ import { chromium } from 'playwright';
6
+ import { readFileSync } from 'node:fs';
7
+
8
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
9
+ const browser = await chromium.launch({ headless: true });
10
+
11
+ // SQL-style timestamp = ISO with the T replaced by space and Z stripped.
12
+ // This is exactly what the CommHub returns (see /api/status probe).
13
+ const sqlMin = (m) => {
14
+ const d = new Date(Date.now() - m * 60 * 1000);
15
+ return d.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
16
+ };
17
+
18
+ async function probe(timezoneId) {
19
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 }, timezoneId });
20
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
21
+ await ctx.addInitScript(() => {
22
+ try {
23
+ localStorage.setItem('anet-theme', 'cyber');
24
+ localStorage.setItem('anet-topo-layout', 'grid');
25
+ sessionStorage.setItem('anet_v3_auth', '1');
26
+ } catch {}
27
+ });
28
+ const fresh = sqlMin(0.1);
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const sessions = [
34
+ { alias: 'live', status: 'idle', network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
36
+ { alias: 'off30', status: 'offline', network_id: nid, project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: sqlMin(30) }, // 30 min ago, SQL format
38
+ { alias: 'ghost', status: 'offline', network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: sqlMin(75) }, // 75 min ago — should ghost
40
+ ];
41
+ await route.fulfill({ response: r, json: { ...b, sessions } });
42
+ });
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.waitForSelector('svg[viewBox="0 0 1000 680"]', { timeout: 30000 });
47
+ await page.waitForTimeout(600);
48
+ const rendered = await page.$$eval('g[data-node]', els => els.map(e => e.getAttribute('data-node')));
49
+ await ctx.close();
50
+ return rendered;
51
+ }
52
+
53
+ const utc = await probe('UTC');
54
+ const shanghai = await probe('Asia/Shanghai'); // UTC+8 — the failing case before Round 35
55
+ const losAngeles = await probe('America/Los_Angeles'); // UTC-7/8 — the opposite skew
56
+ await browser.close();
57
+
58
+ const expectsLiveAndOff30 = list =>
59
+ list.includes('live') && list.includes('off30') && !list.includes('ghost');
60
+
61
+ const results = {
62
+ utcRendersLiveAndOff30: expectsLiveAndOff30(utc),
63
+ shanghaiRendersLiveAndOff30: expectsLiveAndOff30(shanghai),
64
+ losAngelesRendersLiveAndOff30: expectsLiveAndOff30(losAngeles),
65
+ };
66
+ const ok = Object.values(results).every(Boolean);
67
+ console.log(`${ok ? '✅' : '❌'} TZ-safe ghost:`, JSON.stringify(results),
68
+ `\n UTC=${JSON.stringify(utc)}\n Asia/Shanghai=${JSON.stringify(shanghai)}\n America/Los_Angeles=${JSON.stringify(losAngeles)}`);
69
+ process.exit(ok ? 0 : 1);