@sleep2agi/agent-network-dashboard 0.4.6-preview.33 → 0.4.6-preview.331

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 (496) 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/0ggj5ummw2a84.js +7 -0
  173. package/.next/static/chunks/0glmrdj9112tc.js +7 -0
  174. package/.next/static/chunks/0mpa-fgyi2k78.js +1 -0
  175. package/.next/static/chunks/0nlhul7k4hwil.js +1 -0
  176. package/.next/static/chunks/0rxrez_cjn.eh.js +1 -0
  177. package/.next/static/chunks/0x8x.29hkju5a.js +1 -0
  178. package/.next/static/chunks/174re5ar~kxne.js +1 -0
  179. package/.next/static/chunks/1851sbdemt00t.js +4 -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 +111 -1
  185. package/app/components/TopoGraph.tsx +6197 -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/screenshots/r293-p0/v0.10.0-hero3-rfc.md +230 -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 +94 -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-chiprow-data-weight-test.mjs +94 -0
  225. package/scripts/topo-chrome-a11y-test.mjs +107 -0
  226. package/scripts/topo-chrome-focus-ring-unify-test.mjs +100 -0
  227. package/scripts/topo-chrome-icon-stroke-test.mjs +95 -0
  228. package/scripts/topo-chrome-inactive-color-test.mjs +90 -0
  229. package/scripts/topo-chrome-pop-extension-test.mjs +98 -0
  230. package/scripts/topo-chrome-pop-nodesize-test.mjs +88 -0
  231. package/scripts/topo-chrome-press-state-test.mjs +135 -0
  232. package/scripts/topo-chrome-strip-semantic-gap-test.mjs +99 -0
  233. package/scripts/topo-chrome-strip-theme-ease-test.mjs +99 -0
  234. package/scripts/topo-chrome-zoom-level-pop-test.mjs +122 -0
  235. package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
  236. package/scripts/topo-chrome-zoom-readout-weight-test.mjs +81 -0
  237. package/scripts/topo-click-ripple-ease-test.mjs +94 -0
  238. package/scripts/topo-cmdk-edge-clear-test.mjs +109 -0
  239. package/scripts/topo-cmdk-layout-view-test.mjs +108 -0
  240. package/scripts/topo-cmdk-pin-test.mjs +123 -0
  241. package/scripts/topo-cmdk-vendor-clear-test.mjs +133 -0
  242. package/scripts/topo-cmdk-vendor-pin-test.mjs +103 -0
  243. package/scripts/topo-corner-radius-unify-test.mjs +103 -0
  244. package/scripts/topo-cwd-tooltip-test.mjs +64 -0
  245. package/scripts/topo-dblclick-reset-test.mjs +74 -0
  246. package/scripts/topo-dense-alias-fill-transition-test.mjs +133 -0
  247. package/scripts/topo-discrete-zoom-smooth-test.mjs +125 -0
  248. package/scripts/topo-edge-badge-click-pin-test.mjs +94 -0
  249. package/scripts/topo-edge-badge-crossfade-test.mjs +138 -0
  250. package/scripts/topo-edge-badge-hot-test.mjs +115 -0
  251. package/scripts/topo-edge-badge-hover-test.mjs +85 -0
  252. package/scripts/topo-edge-badge-lift-test.mjs +150 -0
  253. package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
  254. package/scripts/topo-edge-badge-stroke-transition-test.mjs +118 -0
  255. package/scripts/topo-edge-badge-text-pin-test.mjs +121 -0
  256. package/scripts/topo-edge-badge-theme-ease-test.mjs +89 -0
  257. package/scripts/topo-edge-count-badge-test.mjs +86 -0
  258. package/scripts/topo-edge-endpoint-highlight-test.mjs +111 -0
  259. package/scripts/topo-edge-fadein-test.mjs +106 -0
  260. package/scripts/topo-edge-focus-test.mjs +113 -0
  261. package/scripts/topo-edge-hover-test.mjs +93 -0
  262. package/scripts/topo-edge-pill-hot-test.mjs +129 -0
  263. package/scripts/topo-edge-pill-test.mjs +105 -0
  264. package/scripts/topo-edge-ping-pulse-retired-test.mjs +119 -0
  265. package/scripts/topo-edge-stroke-ease-test.mjs +100 -0
  266. package/scripts/topo-edge-stroke-transition-test.mjs +132 -0
  267. package/scripts/topo-edge-tooltip-test.mjs +70 -0
  268. package/scripts/topo-empty-hint-tracking-test.mjs +84 -0
  269. package/scripts/topo-empty-state-breath-test.mjs +150 -0
  270. package/scripts/topo-empty-state-crossfade-test.mjs +130 -0
  271. package/scripts/topo-empty-state-recenter-test.mjs +107 -0
  272. package/scripts/topo-empty-subhint-tracking-test.mjs +87 -0
  273. package/scripts/topo-endpoint-ring-fade-test.mjs +132 -0
  274. package/scripts/topo-endpoint-ring-test.mjs +93 -0
  275. package/scripts/topo-endpoint-ring-thicken-test.mjs +112 -0
  276. package/scripts/topo-esc-clears-pin-test.mjs +98 -0
  277. package/scripts/topo-filter-pill-aliases-test.mjs +121 -0
  278. package/scripts/topo-filter-pill-body-click-test.mjs +112 -0
  279. package/scripts/topo-filter-pill-count-test.mjs +110 -0
  280. package/scripts/topo-filter-pill-mobile-test.mjs +90 -0
  281. package/scripts/topo-fit-key-test.mjs +106 -0
  282. package/scripts/topo-freshness-chip-test.mjs +74 -0
  283. package/scripts/topo-freshness-chip-transition-test.mjs +80 -0
  284. package/scripts/topo-freshness-chip-weight-test.mjs +90 -0
  285. package/scripts/topo-freshness-conditional-render-test.mjs +92 -0
  286. package/scripts/topo-fullscreen-active-test.mjs +115 -0
  287. package/scripts/topo-group-box-pinned-state-test.mjs +95 -0
  288. package/scripts/topo-group-count-test.mjs +6 -1
  289. package/scripts/topo-group-label-a11y-test.mjs +113 -0
  290. package/scripts/topo-group-label-count-inherit-test.mjs +99 -0
  291. package/scripts/topo-group-label-hover-test.mjs +119 -0
  292. package/scripts/topo-group-label-pin-spacing-test.mjs +136 -0
  293. package/scripts/topo-group-label-tint-test.mjs +99 -0
  294. package/scripts/topo-group-label-title-test.mjs +83 -0
  295. package/scripts/topo-group-pin-test.mjs +119 -0
  296. package/scripts/topo-group-pip-fadein-test.mjs +100 -0
  297. package/scripts/topo-group-pip-tabular-test.mjs +95 -0
  298. package/scripts/topo-group-status-pips-test.mjs +103 -0
  299. package/scripts/topo-groupbox-fadein-test.mjs +95 -0
  300. package/scripts/topo-groupbox-hover-lift-test.mjs +127 -0
  301. package/scripts/topo-groupbox-hubcore-ease-test.mjs +106 -0
  302. package/scripts/topo-groupbox-live-test.mjs +100 -0
  303. package/scripts/topo-groupbox-march-busyness-test.mjs +155 -0
  304. package/scripts/topo-groupbox-test.mjs +4 -2
  305. package/scripts/topo-help-overlay-r109-test.mjs +67 -0
  306. package/scripts/topo-help-overlay-r118-test.mjs +69 -0
  307. package/scripts/topo-help-overlay-r123-test.mjs +69 -0
  308. package/scripts/topo-help-overlay-r134-test.mjs +74 -0
  309. package/scripts/topo-help-overlay-r137-test.mjs +81 -0
  310. package/scripts/topo-help-overlay-r141-test.mjs +86 -0
  311. package/scripts/topo-help-overlay-r153-test.mjs +85 -0
  312. package/scripts/topo-help-overlay-r59-test.mjs +67 -0
  313. package/scripts/topo-help-overlay-r65-test.mjs +64 -0
  314. package/scripts/topo-help-overlay-r70-test.mjs +63 -0
  315. package/scripts/topo-help-overlay-r78-test.mjs +59 -0
  316. package/scripts/topo-help-overlay-r81-test.mjs +58 -0
  317. package/scripts/topo-help-overlay-r87-test.mjs +64 -0
  318. package/scripts/topo-help-overlay-r91-test.mjs +65 -0
  319. package/scripts/topo-hot-tail-crossfade-test.mjs +117 -0
  320. package/scripts/topo-html-overlay-padding-unify-test.mjs +120 -0
  321. package/scripts/topo-hub-a11y-test.mjs +112 -0
  322. package/scripts/topo-hub-busyness-test.mjs +100 -0
  323. package/scripts/topo-hub-center-crossfade-test.mjs +124 -0
  324. package/scripts/topo-hub-click-fit-test.mjs +137 -0
  325. package/scripts/topo-hub-digit-scale-test.mjs +129 -0
  326. package/scripts/topo-hub-fadein-test.mjs +88 -0
  327. package/scripts/topo-hub-hover-lift-test.mjs +101 -0
  328. package/scripts/topo-hub-hover-ring-test.mjs +82 -0
  329. package/scripts/topo-hub-pip-theme-ease-test.mjs +116 -0
  330. package/scripts/topo-hub-spoke-ease-test.mjs +120 -0
  331. package/scripts/topo-hub-title-test.mjs +93 -0
  332. package/scripts/topo-hub-working-count-test.mjs +99 -0
  333. package/scripts/topo-ingroup-edge-test.mjs +103 -0
  334. package/scripts/topo-inner-radar-ring-retired-test.mjs +89 -0
  335. package/scripts/topo-kicker-color-test.mjs +93 -0
  336. package/scripts/topo-kicker-tracking-test.mjs +101 -0
  337. package/scripts/topo-kicker-weight-test.mjs +98 -0
  338. package/scripts/topo-label-card-theme-ease-test.mjs +85 -0
  339. package/scripts/topo-label-lift-test.mjs +119 -0
  340. package/scripts/topo-lastseen-tooltip-test.mjs +59 -0
  341. package/scripts/topo-layout-hover-text-preview-test.mjs +101 -0
  342. package/scripts/topo-layout-key-test.mjs +91 -0
  343. package/scripts/topo-layout-switch-crossfade-test.mjs +126 -0
  344. package/scripts/topo-layout-toggle-border-unify-test.mjs +104 -0
  345. package/scripts/topo-layout-toggle-test.mjs +131 -0
  346. package/scripts/topo-legend-compress-test.mjs +107 -0
  347. package/scripts/topo-legend-count-empty-test.mjs +126 -0
  348. package/scripts/topo-legend-count-tabular-parity-test.mjs +90 -0
  349. package/scripts/topo-legend-count-tabular-test.mjs +87 -0
  350. package/scripts/topo-legend-count-test.mjs +88 -0
  351. package/scripts/topo-legend-count-tier-fill-test.mjs +121 -0
  352. package/scripts/topo-legend-count-weight-test.mjs +86 -0
  353. package/scripts/topo-legend-header-symmetric-inset-test.mjs +112 -0
  354. package/scripts/topo-legend-hitbox-swatch-center-test.mjs +93 -0
  355. package/scripts/topo-legend-hover-test.mjs +113 -0
  356. package/scripts/topo-legend-offline-delimiter-test.mjs +84 -0
  357. package/scripts/topo-legend-offline-label-simplify-test.mjs +86 -0
  358. package/scripts/topo-legend-panel-count-weight-test.mjs +89 -0
  359. package/scripts/topo-legend-panel-header-test.mjs +83 -0
  360. package/scripts/topo-legend-pin-ring-fade-test.mjs +112 -0
  361. package/scripts/topo-legend-pin-test.mjs +128 -0
  362. package/scripts/topo-legend-row-hover-lift-test.mjs +109 -0
  363. package/scripts/topo-legend-row-labels-simplify-test.mjs +80 -0
  364. package/scripts/topo-legend-row-pin-spacing-test.mjs +124 -0
  365. package/scripts/topo-legend-row-tint-test.mjs +95 -0
  366. package/scripts/topo-legend-row-title-test.mjs +102 -0
  367. package/scripts/topo-legend-swatch-grow-test.mjs +116 -0
  368. package/scripts/topo-legend-swatch-radius-test.mjs +78 -0
  369. package/scripts/topo-minimap-a11y-test.mjs +101 -0
  370. package/scripts/topo-minimap-dot-transition-test.mjs +113 -0
  371. package/scripts/topo-minimap-rect-stroke-test.mjs +94 -0
  372. package/scripts/topo-minimap-test.mjs +68 -0
  373. package/scripts/topo-minimap-viewport-smooth-test.mjs +124 -0
  374. package/scripts/topo-mobile-chips-test.mjs +103 -0
  375. package/scripts/topo-monogram-font-test.mjs +112 -0
  376. package/scripts/topo-monogram-stroke-test.mjs +101 -0
  377. package/scripts/topo-node-a11y-test.mjs +107 -0
  378. package/scripts/topo-node-alias-chat-pin-test.mjs +95 -0
  379. package/scripts/topo-node-hover-lift-test.mjs +85 -0
  380. package/scripts/topo-node-label-card-elevation-test.mjs +157 -0
  381. package/scripts/topo-node-label-card-stroke-test.mjs +135 -0
  382. package/scripts/topo-node-stroke-transition-test.mjs +103 -0
  383. package/scripts/topo-node-text-fill-transition-test.mjs +119 -0
  384. package/scripts/topo-node-title-peers-test.mjs +107 -0
  385. package/scripts/topo-node-title-test.mjs +101 -0
  386. package/scripts/topo-nodesize-active-hover-test.mjs +109 -0
  387. package/scripts/topo-nodesize-crossfade-test.mjs +141 -0
  388. package/scripts/topo-online-chip-nav-test.mjs +143 -0
  389. package/scripts/topo-orbit-particles-retired-test.mjs +91 -0
  390. package/scripts/topo-overlap-test.mjs +32 -5
  391. package/scripts/topo-panel-elevation-test.mjs +76 -0
  392. package/scripts/topo-panel-fadein-test.mjs +102 -0
  393. package/scripts/topo-panel-hot-tail-fade-test.mjs +132 -0
  394. package/scripts/topo-panel-hover-elevation-test.mjs +118 -0
  395. package/scripts/topo-panel-theme-ease-test.mjs +85 -0
  396. package/scripts/topo-panel-title-fill-ease-test.mjs +91 -0
  397. package/scripts/topo-panel-title-tracking-test.mjs +78 -0
  398. package/scripts/topo-particle-stagger-test.mjs +82 -0
  399. package/scripts/topo-pin-intersection-color-ease-test.mjs +111 -0
  400. package/scripts/topo-pin-intersection-empty-test.mjs +129 -0
  401. package/scripts/topo-pin-intersection-tabular-test.mjs +96 -0
  402. package/scripts/topo-pin-intersection-test.mjs +121 -0
  403. package/scripts/topo-pin-intersection-warning-crossfade-test.mjs +107 -0
  404. package/scripts/topo-pin-mirror-transition-test.mjs +101 -0
  405. package/scripts/topo-pin-persistence-test.mjs +105 -0
  406. package/scripts/topo-popover-meta-test.mjs +78 -0
  407. package/scripts/topo-prepublish-guard-test.mjs +95 -0
  408. package/scripts/topo-pressure-bar-test.mjs +69 -0
  409. package/scripts/topo-pressure-hover-test.mjs +123 -0
  410. package/scripts/topo-pressure-pin-test.mjs +119 -0
  411. package/scripts/topo-pressure-seg-self-hover-test.mjs +126 -0
  412. package/scripts/topo-pressure-seg-tooltip-test.mjs +98 -0
  413. package/scripts/topo-pressure-seg-transition-test.mjs +107 -0
  414. package/scripts/topo-pulse-dot-crossfade-test.mjs +122 -0
  415. package/scripts/topo-pulse-dot-retired-test.mjs +86 -0
  416. package/scripts/topo-recent-panel-count-freshness-test.mjs +109 -0
  417. package/scripts/topo-recent-panel-count-test.mjs +72 -0
  418. package/scripts/topo-recent-panel-count-weight-test.mjs +91 -0
  419. package/scripts/topo-recent-panel-footer-breath-test.mjs +142 -0
  420. package/scripts/topo-recent-panel-hot-header-test.mjs +132 -0
  421. package/scripts/topo-recent-panel-more-crossfade-test.mjs +124 -0
  422. package/scripts/topo-recent-panel-more-cyan-hover-test.mjs +151 -0
  423. package/scripts/topo-recent-panel-more-nav-test.mjs +127 -0
  424. package/scripts/topo-recent-panel-more-test.mjs +106 -0
  425. package/scripts/topo-recent-panel-readability-bump-test.mjs +151 -0
  426. package/scripts/topo-recent-row-count-fill-test.mjs +129 -0
  427. package/scripts/topo-recent-row-fadein-test.mjs +107 -0
  428. package/scripts/topo-recent-row-freshness-test.mjs +102 -0
  429. package/scripts/topo-recent-row-hot-test.mjs +110 -0
  430. package/scripts/topo-recent-row-hover-lift-test.mjs +128 -0
  431. package/scripts/topo-recent-row-hover-test.mjs +122 -0
  432. package/scripts/topo-recent-row-pin-spacing-test.mjs +137 -0
  433. package/scripts/topo-recent-row-pin-test.mjs +126 -0
  434. package/scripts/topo-recent-row-tint-test.mjs +91 -0
  435. package/scripts/topo-recent-row-title-test.mjs +112 -0
  436. package/scripts/topo-recent-row-ts-alpha-test.mjs +100 -0
  437. package/scripts/topo-recent-row-ts-test.mjs +95 -0
  438. package/scripts/topo-recent-row-typography-test.mjs +119 -0
  439. package/scripts/topo-recent-signal-empty-hint-test.mjs +73 -0
  440. package/scripts/topo-recent-signal-empty-test.mjs +63 -0
  441. package/scripts/topo-reduced-motion-test.mjs +77 -0
  442. package/scripts/topo-reset-spin-test.mjs +120 -0
  443. package/scripts/topo-runtime-badge-grow-test.mjs +114 -0
  444. package/scripts/topo-spokes-retired-test.mjs +93 -0
  445. package/scripts/topo-starfield-density-test.mjs +89 -0
  446. package/scripts/topo-status-chip-hover-test.mjs +101 -0
  447. package/scripts/topo-status-chip-tooltip-test.mjs +95 -0
  448. package/scripts/topo-statusbar-chips-self-hover-test.mjs +177 -0
  449. package/scripts/topo-svg-focus-test.mjs +112 -0
  450. package/scripts/topo-sweep-busyness-test.mjs +94 -0
  451. package/scripts/topo-tabular-nums-sweep-test.mjs +93 -0
  452. package/scripts/topo-tier-guide-dashes-test.mjs +86 -0
  453. package/scripts/topo-tier-ring-fadein-test.mjs +107 -0
  454. package/scripts/topo-tier-ring-occupancy-test.mjs +87 -0
  455. package/scripts/topo-tier-ring-test.mjs +69 -0
  456. package/scripts/topo-tier-ring-tint-test.mjs +110 -0
  457. package/scripts/topo-tier-stagger-test.mjs +89 -0
  458. package/scripts/topo-title-block-tight-leading-test.mjs +95 -0
  459. package/scripts/topo-title-tracking-test.mjs +98 -0
  460. package/scripts/topo-titleblock-gap-test.mjs +86 -0
  461. package/scripts/topo-titleblock-margin-test.mjs +83 -0
  462. package/scripts/topo-toggle-inactive-cyan-hover-test.mjs +110 -0
  463. package/scripts/topo-top-rail-theme-ease-test.mjs +89 -0
  464. package/scripts/topo-tz-ghost-test.mjs +69 -0
  465. package/scripts/topo-vendor-chip-hover-test.mjs +111 -0
  466. package/scripts/topo-vendor-chip-self-hover-test.mjs +131 -0
  467. package/scripts/topo-vendor-chip-tabular-test.mjs +92 -0
  468. package/scripts/topo-vendor-chip-threshold-test.mjs +78 -0
  469. package/scripts/topo-vendor-chip-weight-test.mjs +87 -0
  470. package/scripts/topo-vendor-letter-tooltip-test.mjs +92 -0
  471. package/scripts/topo-vendor-pill-test.mjs +125 -0
  472. package/scripts/topo-vendor-pin-test.mjs +125 -0
  473. package/scripts/topo-view-smooth-test.mjs +131 -0
  474. package/scripts/topo-watermark-tracking-test.mjs +99 -0
  475. package/scripts/topo-working-chip-pin-test.mjs +154 -0
  476. package/scripts/topo-working-halo-breath-retired-test.mjs +99 -0
  477. package/scripts/topo-wrapper-flow-arrow-theme-test.mjs +97 -0
  478. package/scripts/topo-wrapper-shadow-theme-ease-test.mjs +89 -0
  479. package/.next/static/chunks/0-7iowzu4.798.js +0 -1
  480. package/.next/static/chunks/02t5jr4shh.hy.js +0 -1
  481. package/.next/static/chunks/09nd.t_hd.y6t.css +0 -2
  482. package/.next/static/chunks/0_8snedeata9k.js +0 -1
  483. package/.next/static/chunks/0_qfg5s5uzs7..js +0 -7
  484. package/.next/static/chunks/0elalb9n_.~di.js +0 -1
  485. package/.next/static/chunks/0j_q__o8p6g83.js +0 -1
  486. package/.next/static/chunks/0jl6_w50zh4l5.js +0 -1
  487. package/.next/static/chunks/0kwf..oq7.27x.js +0 -7
  488. package/.next/static/chunks/0r4q73.-1bkyl.js +0 -1
  489. package/.next/static/chunks/0r_c8eccg3z2o.js +0 -1
  490. package/.next/static/chunks/0sjqjenh7~k-j.js +0 -1
  491. package/.next/static/chunks/0y8v4grez.a76.js +0 -1
  492. package/.next/static/chunks/10oi7l-tycgl9.js +0 -1
  493. package/.next/static/chunks/11uk1izuv3~0t.js +0 -1
  494. /package/.next/static/{Q3_3F7glo-M3CFrXA8POE → 3PMYuA9TBRow9F7QeNZc7}/_buildManifest.js +0 -0
  495. /package/.next/static/{Q3_3F7glo-M3CFrXA8POE → 3PMYuA9TBRow9F7QeNZc7}/_clientMiddlewareManifest.js +0 -0
  496. /package/.next/static/{Q3_3F7glo-M3CFrXA8POE → 3PMYuA9TBRow9F7QeNZc7}/_ssgManifest.js +0 -0
@@ -0,0 +1,101 @@
1
+ /* Round 285 verification: section kicker letter-spacing widens
2
+ * tracking-wider (0.05em) → tracking-widest (0.1em).
3
+ *
4
+ * Tailwind v4 utility class compiles to:
5
+ * tracking-wider → letter-spacing: 0.05em
6
+ * tracking-widest → letter-spacing: 0.1em
7
+ *
8
+ * At text-xs (12px) the 0.05em→0.1em jump adds ~0.6px between
9
+ * letters, which on an uppercase 'NETWORK TOPOLOGY' label
10
+ * telegraphs "eyebrow label" cleanly vs body-text density.
11
+ *
12
+ * Contract:
13
+ * - [data-topo-section-kicker] computed letter-spacing ~ 0.1em
14
+ * (12 * 0.1 = 1.2px ± rounding).
15
+ * - Pre-R285 was 0.05em → ~0.6px.
16
+ * - Element still text-transform: uppercase, font-size: 12px.
17
+ * - data-topo-section-title sibling unchanged (regression).
18
+ * - R283 monogram stroke + R282 watermark intact (regression).
19
+ */
20
+ import { chromium } from 'playwright';
21
+ import { readFileSync } from 'node:fs';
22
+
23
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
24
+ const browser = await chromium.launch({ headless: true });
25
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript(() => {
28
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
29
+ });
30
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias, model) => ({
36
+ alias, status: 'working', model, runtime: 'claude-code-cli',
37
+ network_id: nid, project_dir: null,
38
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
39
+ });
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('alpha', 'claude-opus-4'),
42
+ mk('beta', 'gpt-4o'),
43
+ ] } });
44
+ });
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
46
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
47
+
48
+ const page = await ctx.newPage();
49
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
50
+ await page.waitForSelector('[data-topo-section-kicker]', { timeout: 15000 });
51
+ await page.waitForSelector('[data-topo-brand-watermark]', { timeout: 10000 });
52
+ await page.waitForTimeout(300);
53
+
54
+ const probe = await page.evaluate(() => {
55
+ const kicker = document.querySelector('[data-topo-section-kicker]');
56
+ const title = document.querySelector('[data-topo-section-title]');
57
+ const kCs = kicker ? getComputedStyle(kicker) : null;
58
+ const tCs = title ? getComputedStyle(title) : null;
59
+ // Probe alpha avatar circle stroke for R283 regression.
60
+ const alphaNode = document.querySelector('g[data-node="alpha"]');
61
+ const alphaCircles = alphaNode ? [...alphaNode.querySelectorAll('circle')] : [];
62
+ const alphaAvatarCircle = alphaCircles
63
+ .map(c => ({ r: +c.getAttribute('r') || 0, sw: c.getAttribute('stroke-width'), isBadge: c.hasAttribute('data-runtime-badge') }))
64
+ .filter(c => c.r > 0 && c.r < 18 && !c.isBadge)
65
+ .sort((a, b) => b.r - a.r)[0];
66
+ const watermark = document.querySelector('[data-topo-brand-watermark]');
67
+ return {
68
+ kickerLetterSpacing: kCs?.letterSpacing ?? null,
69
+ kickerFontSize: kCs?.fontSize ?? null,
70
+ kickerTextTransform: kCs?.textTransform ?? null,
71
+ kickerText: kicker?.textContent ?? null,
72
+ titleText: title?.textContent ?? null,
73
+ titleLeading: tCs?.lineHeight ?? null,
74
+ alphaAvatarStroke: alphaAvatarCircle?.sw ?? null,
75
+ watermarkPresent: watermark !== null,
76
+ };
77
+ });
78
+ await browser.close();
79
+
80
+ // 12px * 0.1em = 1.2px (post-R285). Pre-R285 0.05em = 0.6px.
81
+ // Allow tolerance window 1.0–1.4 to be safe across rounding.
82
+ const ls = parseFloat(probe.kickerLetterSpacing) || 0;
83
+ const fs = parseFloat(probe.kickerFontSize) || 12;
84
+ const lsPerEm = fs ? ls / fs : 0;
85
+
86
+ const results = {
87
+ kicker_present: probe.kickerText === 'Network Topology',
88
+ kicker_uppercase: probe.kickerTextTransform === 'uppercase',
89
+ kicker_widest_ls: ls >= 1.0 && ls <= 1.5, // 0.1em at 12px ≈ 1.2px
90
+ kicker_ls_per_em_widest: lsPerEm >= 0.08 && lsPerEm <= 0.12,
91
+ title_still_present: probe.titleText === 'Command mesh',
92
+ r283_monogram_stroke_kept: probe.alphaAvatarStroke === '1.5',
93
+ r282_watermark_present: probe.watermarkPresent,
94
+ };
95
+ const ok = Object.values(results).every(Boolean);
96
+ console.log(`${ok ? '✅' : '❌'} kicker tracking:`, JSON.stringify(results),
97
+ '\n kicker text:', JSON.stringify(probe.kickerText),
98
+ '\n letter-spacing:', probe.kickerLetterSpacing, `(=${lsPerEm.toFixed(3)}em)`,
99
+ '\n font-size:', probe.kickerFontSize,
100
+ '\n title text:', JSON.stringify(probe.titleText));
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,98 @@
1
+ /* Round 300 (milestone) verification: kicker font-weight bumps from
2
+ * default (400/normal) → font-medium (500). Uppercase eyebrow at
3
+ * text-xs with R285 tracking-widest reads slightly under-authored
4
+ * at 400; 500 lifts the label to the conventional SaaS-eyebrow
5
+ * weight (Stripe / Vercel / Linear).
6
+ *
7
+ * Hierarchy preserved: h2 (600 font-semibold) > kicker (500 font-
8
+ * medium) > body weight families.
9
+ *
10
+ * Contract:
11
+ * - [data-topo-section-kicker] computed font-weight === 500.
12
+ * - R296 kicker color (~gray-500) preserved.
13
+ * - R285 tracking-widest (~1.2px @ 12px) preserved.
14
+ * - R299 title-block mb=16px + R298 left flex gap=10px preserved.
15
+ * - R297 brand-logo transition + R294 pulse absent intact.
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
21
+ const browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
23
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
24
+ await ctx.addInitScript(() => {
25
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
26
+ });
27
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
28
+ await ctx.route('**/api/hub/status*', async (route) => {
29
+ const r = await route.fetch();
30
+ const b = await r.json();
31
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
32
+ const mk = (alias, model) => ({
33
+ alias, status: 'working', model, runtime: 'claude-code-cli',
34
+ network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
36
+ });
37
+ await route.fulfill({ response: r, json: { ...b, sessions: [
38
+ mk('alpha', 'claude-opus-4'),
39
+ mk('beta', 'gpt-4o'),
40
+ ] } });
41
+ });
42
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+
45
+ const page = await ctx.newPage();
46
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
47
+ await page.waitForSelector('[data-topo-section-kicker]', { timeout: 15000 });
48
+ await page.waitForTimeout(300);
49
+
50
+ const probe = await page.evaluate(() => {
51
+ const kicker = document.querySelector('[data-topo-section-kicker]');
52
+ const title = document.querySelector('[data-topo-section-title]');
53
+ const logo = document.querySelector('[data-topo-brand-logo]');
54
+ const kCs = kicker ? getComputedStyle(kicker) : null;
55
+ const tCs = title ? getComputedStyle(title) : null;
56
+ const flexParent = logo?.parentElement;
57
+ const flexCs = flexParent ? getComputedStyle(flexParent) : null;
58
+ const titleBlockContainer = flexParent?.parentElement;
59
+ const tbCs = titleBlockContainer ? getComputedStyle(titleBlockContainer) : null;
60
+ const swatch = document.querySelector('[data-legend-swatch="working"]');
61
+ return {
62
+ kickerFontWeight: kCs?.fontWeight ?? null,
63
+ kickerColor: kCs?.color ?? null,
64
+ kickerLetterSpacing: kCs?.letterSpacing ?? null,
65
+ titleFontWeight: tCs?.fontWeight ?? null,
66
+ titleBlockMarginBottom: tbCs?.marginBottom ?? null,
67
+ leftFlexGap: flexCs?.columnGap ?? null,
68
+ brandLogoTransition: logo ? getComputedStyle(logo).transition : null,
69
+ swatchR: swatch?.getAttribute('r') ?? null,
70
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
71
+ };
72
+ });
73
+ await browser.close();
74
+
75
+ const kFw = parseInt(probe.kickerFontWeight) || 0;
76
+ const tFw = parseInt(probe.titleFontWeight) || 0;
77
+ const ls = parseFloat(probe.kickerLetterSpacing) || 0;
78
+ const mb = parseFloat(probe.titleBlockMarginBottom) || 0;
79
+ const gap = parseFloat(probe.leftFlexGap) || 0;
80
+
81
+ const results = {
82
+ kicker_weight_500: kFw === 500,
83
+ title_weight_higher: tFw > kFw,
84
+ r285_tracking_widest: ls >= 1.0 && ls <= 1.5,
85
+ r296_kicker_color_not_gray600: !/rgba?\(\s*75\s*,\s*85\s*,\s*99/.test(probe.kickerColor || ''),
86
+ r299_mb_16: mb >= 15.5 && mb <= 16.5,
87
+ r298_gap_10: gap >= 9.5 && gap <= 10.5,
88
+ r297_transition_kept: /color/.test(probe.brandLogoTransition || ''),
89
+ r295_swatch_r_6: probe.swatchR === '6',
90
+ r294_pulse_absent: probe.pulseCount === 0,
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} kicker weight (R300 milestone):`, JSON.stringify(results),
94
+ '\n kicker font-weight:', probe.kickerFontWeight,
95
+ '\n title font-weight:', probe.titleFontWeight,
96
+ '\n letter-spacing:', probe.kickerLetterSpacing,
97
+ '\n title-block mb (R299):', probe.titleBlockMarginBottom);
98
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,85 @@
1
+ /* Round 246 verification: node label card chrome rect picks up
2
+ * fill + opacity transitions to coordinate with the existing
3
+ * filter (R142) + stroke (R217) easing on the same element.
4
+ *
5
+ * Pre-R246 the rect transition list was 'filter 220ms ease-out,
6
+ * stroke 220ms ease-out'. fill (pal.labelBox.fill: cyber dark ↔
7
+ * light white) and opacity (cyber 0.94, light 1) snapped on theme
8
+ * toggle. Surrounding elements already ease theme:
9
+ * R211 alias/sub text fill (same card)
10
+ * R242 chat-target ring filter + stroke
11
+ * R244 hub + halo opacity
12
+ * R245 edge stroke
13
+ *
14
+ * R246 closes the label-card chrome — adds fill + opacity to the
15
+ * existing transition list so the whole card (background + text)
16
+ * eases as one unit through theme switches and any future
17
+ * fill/opacity state changes.
18
+ *
19
+ * Test scope per visible label card:
20
+ * - element present at [data-node-label-card]
21
+ * - style.transition contains filter, stroke, fill, AND opacity
22
+ * each at 220ms (or 0.22s browser-normalised)
23
+ */
24
+ import { chromium } from 'playwright';
25
+ import { readFileSync } from 'node:fs';
26
+
27
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
28
+ const browser = await chromium.launch({ headless: true });
29
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
30
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
31
+ await ctx.addInitScript(() => {
32
+ try {
33
+ localStorage.setItem('anet-theme', 'cyber');
34
+ sessionStorage.setItem('anet_v3_auth', '1');
35
+ localStorage.setItem('anet-topo-layout', 'ring');
36
+ } catch {}
37
+ });
38
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
39
+ await ctx.route('**/api/hub/status*', async (route) => {
40
+ const r = await route.fetch();
41
+ const b = await r.json();
42
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
43
+ const mk = (alias) => ({
44
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
45
+ network_id: nid, project_dir: null,
46
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
47
+ });
48
+ await route.fulfill({ response: r, json: { ...b, sessions: [
49
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
50
+ ] } });
51
+ });
52
+ await ctx.route('**/api/hub/messages*', (r2) => r2.fulfill({ json: { messages: [] } }));
53
+ await ctx.route('**/api/hub/tasks*', (r2) => r2.fulfill({ json: { tasks: [] } }));
54
+
55
+ const page = await ctx.newPage();
56
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
57
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
58
+ await page.waitForSelector('[data-node-label-card]', { timeout: 10000, state: 'attached' });
59
+ await page.waitForTimeout(300);
60
+
61
+ const probe = await page.evaluate(() => {
62
+ const cards = Array.from(document.querySelectorAll('[data-node-label-card]'));
63
+ return cards.map((r) => ({
64
+ alias: r.getAttribute('data-node-label-card'),
65
+ elevation: r.getAttribute('data-node-label-card-elevation'),
66
+ transition: r.style.transition,
67
+ }));
68
+ });
69
+ await browser.close();
70
+
71
+ const hasProp = (s, prop) => new RegExp(`${prop}\\s+(?:220ms|0\\.22s)`).test(s || '');
72
+
73
+ const results = {
74
+ four_cards: probe.length === 4,
75
+ all_have_alias: probe.every(p => typeof p.alias === 'string'),
76
+ all_idle_elevation: probe.every(p => p.elevation === 'idle'),
77
+ all_have_filter_220: probe.every(p => hasProp(p.transition, 'filter')),
78
+ all_have_stroke_220: probe.every(p => hasProp(p.transition, 'stroke')),
79
+ all_have_fill_220: probe.every(p => hasProp(p.transition, 'fill')),
80
+ all_have_opacity_220: probe.every(p => hasProp(p.transition, 'opacity')),
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} label card theme ease:`, JSON.stringify(results),
84
+ '\n cards:', probe.map(p => ({ a: p.alias, e: p.elevation, t: (p.transition || '').slice(0, 100) })));
85
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,119 @@
1
+ /* Round 26 verification: hovering a node lifts its label group up by
2
+ * 1.5 px (CSS transform stacked on the SVG positioning translate). Both
3
+ * the full-card path (sparse fleet) and the dense plain-text path
4
+ * (large fleet) should respond. */
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
+ async function probe(fleetSize) {
12
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
13
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
14
+ await ctx.addInitScript(() => {
15
+ try {
16
+ localStorage.setItem('anet-theme', 'cyber');
17
+ localStorage.setItem('anet-topo-layout', 'grid');
18
+ localStorage.setItem('anet-topo-nodescale', '1');
19
+ sessionStorage.setItem('anet_v3_auth', '1');
20
+ } catch {}
21
+ });
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+ await ctx.route('**/api/hub/status*', async (route) => {
24
+ const r = await route.fetch();
25
+ const b = await r.json();
26
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
27
+ const sessions = Array.from({ length: fleetSize }, (_, i) => ({
28
+ alias: `lab${i + 1}`, status: 'idle', network_id: nid, project_dir: null,
29
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
30
+ }));
31
+ await route.fulfill({ response: r, json: { ...b, sessions } });
32
+ });
33
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
34
+ const page = await ctx.newPage();
35
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
36
+ await page.waitForFunction((n) => {
37
+ const svg = document.querySelector('svg[viewBox="0 0 1000 680"]');
38
+ return !!svg && svg.querySelectorAll('g[data-node]').length === n;
39
+ }, fleetSize, { timeout: 30000 }).catch(() => {});
40
+ await page.waitForTimeout(500);
41
+
42
+ // Re-query the label element fresh each sample — React re-renders on
43
+ // hover (hoveredAlias state change) so any element handle captured
44
+ // before the hover would be detached/stale by the time we read its
45
+ // post-hover position.
46
+ const readLabelTop = () => page.evaluate(() => {
47
+ const node = document.querySelector('g[data-node="lab1"]');
48
+ if (!node) return null;
49
+ const full = [...node.querySelectorAll('g')].find(g => /group-hover:-translate-y/.test(g.getAttribute('class') || ''));
50
+ const el = full || [...node.querySelectorAll('text')].find(t => /group-hover:-translate-y/.test(t.getAttribute('class') || ''));
51
+ return el ? el.getBoundingClientRect().top : null;
52
+ });
53
+
54
+ const beforeY = await readLabelTop();
55
+ await page.locator('g[data-node="lab1"]').hover();
56
+ await page.waitForTimeout(300);
57
+ const afterY = await readLabelTop();
58
+
59
+ await ctx.close();
60
+ return { beforeY, afterY };
61
+ }
62
+
63
+ const sparse = await probe(4);
64
+ // Note: in dense mode the same alias's hover triggers `showFullLabel`,
65
+ // which swaps the plain-text element for the full-card <g>. That path
66
+ // switch makes a same-element before/after comparison meaningless.
67
+ // The plain-text element still carries the same hover-lift class, so
68
+ // the lift wiring is verified at the DOM level via the second probe.
69
+ const denseClassPresent = await (async () => {
70
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
71
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
72
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
73
+ await ctx.addInitScript(() => {
74
+ localStorage.setItem('anet-topo-layout', 'grid');
75
+ localStorage.setItem('anet-topo-nodescale', '1');
76
+ sessionStorage.setItem('anet_v3_auth', '1');
77
+ });
78
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
79
+ await ctx.route('**/api/hub/status*', async (route) => {
80
+ const r = await route.fetch();
81
+ const b = await r.json();
82
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
83
+ const sessions = Array.from({ length: 28 }, (_, i) => ({
84
+ alias: `lab${i + 1}`, status: 'idle', network_id: nid, project_dir: null,
85
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
86
+ }));
87
+ await route.fulfill({ response: r, json: { ...b, sessions } });
88
+ });
89
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
90
+ const page = await ctx.newPage();
91
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
92
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 28, { timeout: 30000 }).catch(() => {});
93
+ await page.waitForTimeout(500);
94
+ const has = await page.evaluate(() => {
95
+ const node = document.querySelector('g[data-node="lab2"]'); // not the one we'd hover
96
+ if (!node) return false;
97
+ const t = [...node.querySelectorAll('text')].find(x => /group-hover:-translate-y/.test(x.getAttribute('class') || ''));
98
+ return !!t;
99
+ });
100
+ await ctx.close();
101
+ return has;
102
+ })();
103
+
104
+ await browser.close();
105
+
106
+ // R26 contributes label-only -1.5px lift; R51 (preview.58) adds -2px on the
107
+ // parent <g data-node> on hover, so the label's total measured movement
108
+ // when the node is hovered is ~3.5px. Widen the upper bound to 4 so the
109
+ // two lifts compose without flagging this as a regression.
110
+ const lifted = (m) => m.beforeY != null && m.afterY != null && m.beforeY - m.afterY >= 1 && m.beforeY - m.afterY <= 4;
111
+
112
+ const results = {
113
+ sparseLabelLifts: lifted(sparse),
114
+ densePlainTextHasLiftClass: denseClassPresent,
115
+ };
116
+ const ok = Object.values(results).every(Boolean);
117
+ console.log(`${ok ? '✅' : '❌'} label hover lift:`, JSON.stringify(results),
118
+ `sparse=Δ${(sparse.beforeY - sparse.afterY)?.toFixed(2)}px`);
119
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,59 @@
1
+ /* Round 34 verification: offline node tooltip carries "last seen: <relative>";
2
+ * online nodes skip the line. SQL-style timestamp parsing is exercised in
3
+ * production (CommHub format) but the existing isGhost filter has a
4
+ * separate TZ-sensitivity issue that ghosts SQL-without-Z entries on
5
+ * non-UTC browsers — out of Round 34 scope. The helper supports both
6
+ * formats; the SQL path is covered by manual inspection. */
7
+ import { chromium } from 'playwright';
8
+ import { readFileSync } from 'node:fs';
9
+
10
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
11
+ const browser = await chromium.launch({ headless: true });
12
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
13
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
14
+ await ctx.addInitScript(() => {
15
+ try {
16
+ localStorage.setItem('anet-theme', 'cyber');
17
+ localStorage.setItem('anet-topo-layout', 'grid');
18
+ sessionStorage.setItem('anet_v3_auth', '1');
19
+ } catch {}
20
+ });
21
+ // One online, two offline at different ages (both ISO).
22
+ const now = Date.now();
23
+ const isoMin = (m) => new Date(now - m * 60 * 1000).toISOString();
24
+ await ctx.route('**/api/hub/status*', async (route) => {
25
+ const r = await route.fetch();
26
+ const b = await r.json();
27
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
28
+ const fresh = isoMin(0.1);
29
+ const sessions = [
30
+ { alias: 'live', status: 'idle', network_id: nid, project_dir: null,
31
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh },
32
+ { alias: 'off6m', status: 'offline', network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: isoMin(6) },
34
+ { alias: 'off45m', status: 'offline', network_id: nid, project_dir: null,
35
+ created_at: fresh, updated_at: fresh, last_seen_at: isoMin(45) },
36
+ ];
37
+ await route.fulfill({ response: r, json: { ...b, sessions } });
38
+ });
39
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
40
+ const page = await ctx.newPage();
41
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
42
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
43
+ await page.waitForTimeout(400);
44
+
45
+ const titleOf = alias => page.$eval(`g[data-node="${alias}"] title`, el => el.textContent);
46
+ const live = await titleOf('live');
47
+ const off6m = await titleOf('off6m');
48
+ const off45m = await titleOf('off45m');
49
+
50
+ await browser.close();
51
+ const results = {
52
+ liveSkipsLastSeen: !/last seen:/.test(live),
53
+ off6mShows6mAgo: /last seen: [56]m ago/.test(off6m), // 6m exactly, ±1 minute slack
54
+ off45mShows45mAgo: /last seen: 4[45]m ago/.test(off45m), // 45m exactly, ±1 minute slack
55
+ };
56
+ const ok = Object.values(results).every(Boolean);
57
+ console.log(`${ok ? '✅' : '❌'} last-seen tooltip:`, JSON.stringify(results),
58
+ `\n live=${JSON.stringify(live)}\n off6m=${JSON.stringify(off6m)}\n off45m=${JSON.stringify(off45m)}`);
59
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,101 @@
1
+ /* Round 273 verification: Layout toggle inactive buttons hover-text
2
+ * now previews active text color (cyan-300), completing R163's
3
+ * "hover previews active state" pattern.
4
+ *
5
+ * Pre-R273 the inactive Ring/Grid buttons used:
6
+ * text-gray-500 hover:text-gray-300 hover:bg-cyan-500/5 active:bg-cyan-500/15
7
+ *
8
+ * The bg part previewed the active cyan-500/15 ghost via cyan-500/5,
9
+ * but the text just brightened gray-500→gray-300. Half-preview:
10
+ * cyan ghost bg + gray text. Active state was cyan-500/15 bg + cyan-300
11
+ * text — so hovering inactive didn't visually preview the active text
12
+ * color.
13
+ *
14
+ * Post-R273:
15
+ * text-gray-500 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15
16
+ *
17
+ * Hover state now previews BOTH the active bg (cyan ghost) AND text
18
+ * (cyan-300). Bg /5 vs /15 still distinguishes hover-preview from
19
+ * active-real.
20
+ *
21
+ * Test scope:
22
+ * 1. Ring inactive button (default state has Ring active; toggle to
23
+ * Grid first so Ring becomes inactive) classlist contains
24
+ * 'hover:text-cyan-300' and does NOT contain 'hover:text-gray-300'.
25
+ * 2. Grid inactive button (default state) classlist contains
26
+ * 'hover:text-cyan-300'.
27
+ * 3. R163 invariant: hover:bg-cyan-500/5 still present on inactive.
28
+ * 4. R272 freshness chip text starts with 'live · ' (regression).
29
+ */
30
+ import { chromium } from 'playwright';
31
+ import { readFileSync } from 'node:fs';
32
+
33
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
34
+ const browser = await chromium.launch({ headless: true });
35
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
36
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
37
+ await ctx.addInitScript(() => {
38
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
39
+ });
40
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
41
+ await ctx.route('**/api/hub/status*', async (route) => {
42
+ const r = await route.fetch();
43
+ const b = await r.json();
44
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
45
+ const mk = (alias) => ({
46
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
47
+ network_id: nid, project_dir: null,
48
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
49
+ });
50
+ await route.fulfill({ response: r, json: { ...b, sessions: [
51
+ mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
52
+ ] } });
53
+ });
54
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
55
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
56
+
57
+ const page = await ctx.newPage();
58
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
59
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
60
+ await page.waitForSelector('[data-topo-chrome-layout="ring"]', { timeout: 10000 });
61
+ await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 10000 });
62
+ await page.waitForSelector('[data-freshness-chip]', { timeout: 10000 });
63
+ await page.waitForTimeout(300);
64
+
65
+ const probe = await page.evaluate(() => {
66
+ // Default localStorage = ring is active, grid is inactive.
67
+ const ring = document.querySelector('[data-topo-chrome-layout="ring"]');
68
+ const grid = document.querySelector('[data-topo-chrome-layout="grid"]');
69
+ const freshness = document.querySelector('[data-freshness-chip]');
70
+ return {
71
+ ringClasses: ring ? ring.className.toString() : null,
72
+ ringActive: ring ? ring.getAttribute('data-topo-chrome-layout-active') : null,
73
+ gridClasses: grid ? grid.className.toString() : null,
74
+ gridActive: grid ? grid.getAttribute('data-topo-chrome-layout-active') : null,
75
+ freshnessText: freshness ? freshness.textContent.trim() : null,
76
+ };
77
+ });
78
+ await browser.close();
79
+
80
+ const has = (s, cls) => (s || '').includes(cls);
81
+
82
+ // Ring is default-active (true). Grid is default-inactive (false).
83
+ // Grid (inactive) should have hover:text-cyan-300.
84
+ // Ring (active) uses 'bg-cyan-500/15 text-cyan-300 hover:bg-cyan-500/20 active:bg-cyan-500/25'
85
+ // — no hover:text-cyan-300 (it's already cyan-300 at rest) — UNCHANGED.
86
+
87
+ const results = {
88
+ ring_is_active: probe.ringActive === 'true',
89
+ grid_is_inactive: probe.gridActive === 'false',
90
+ grid_inactive_has_hover_cyan_text: has(probe.gridClasses, 'hover:text-cyan-300'),
91
+ grid_inactive_no_hover_gray_text: !has(probe.gridClasses, 'hover:text-gray-300'),
92
+ grid_inactive_keeps_cyan_bg_hover: has(probe.gridClasses, 'hover:bg-cyan-500/5'),
93
+ ring_active_has_cyan_text_base: has(probe.ringClasses, 'text-cyan-300'),
94
+ r272_freshness_starts_with_live: probe.freshnessText != null && probe.freshnessText.startsWith('live · '),
95
+ };
96
+ const ok = Object.values(results).every(Boolean);
97
+ console.log(`${ok ? '✅' : '❌'} layout hover text preview:`, JSON.stringify(results),
98
+ '\n ring classes:', probe.ringClasses,
99
+ '\n grid classes:', probe.gridClasses,
100
+ '\n freshness:', probe.freshnessText);
101
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,91 @@
1
+ /* Round 32 verification: `l` (and `L`) keyboard shortcut toggles
2
+ * ring ↔ grid; titles + Help overlay surface the hint; input-focus
3
+ * suppression still blocks it. */
4
+ import { chromium } from 'playwright';
5
+ import { readFileSync } from 'node:fs';
6
+
7
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
8
+ const browser = await chromium.launch({ headless: true });
9
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 1600 } });
10
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
11
+ await ctx.addInitScript(() => {
12
+ try {
13
+ localStorage.setItem('anet-theme', 'cyber');
14
+ localStorage.setItem('anet-topo-layout', 'grid'); // start on grid
15
+ sessionStorage.setItem('anet_v3_auth', '1');
16
+ } catch {}
17
+ });
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+ await ctx.route('**/api/hub/status*', async (route) => {
20
+ const r = await route.fetch();
21
+ const b = await r.json();
22
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
23
+ const sessions = ['a', 'b', 'c'].map(a => ({
24
+ alias: a, status: 'idle', network_id: nid, project_dir: null,
25
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
26
+ }));
27
+ await route.fulfill({ response: r, json: { ...b, sessions } });
28
+ });
29
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
30
+ const page = await ctx.newPage();
31
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
32
+ await page.waitForSelector('button[aria-label="Topology layout"], button[title*="Ring layout"]', { timeout: 30000 });
33
+ await page.waitForTimeout(400);
34
+
35
+ const layoutPressed = () => page.evaluate(() => {
36
+ const ring = document.querySelector('button[title^="Ring layout"]');
37
+ const grid = document.querySelector('button[title^="Grid layout"]');
38
+ return { ring: ring?.getAttribute('aria-pressed'), grid: grid?.getAttribute('aria-pressed') };
39
+ });
40
+
41
+ const initial = await layoutPressed();
42
+
43
+ // Press `l` → should toggle to ring
44
+ await page.keyboard.press('l');
45
+ await page.waitForTimeout(150);
46
+ const afterL = await layoutPressed();
47
+
48
+ // Press `L` (shift+l) → toggle back to grid
49
+ await page.keyboard.press('Shift+L');
50
+ await page.waitForTimeout(150);
51
+ const afterShiftL = await layoutPressed();
52
+
53
+ // Titles carry the hint
54
+ const titles = await page.evaluate(() => ({
55
+ ring: document.querySelector('button[title^="Ring layout"]')?.getAttribute('title') || '',
56
+ grid: document.querySelector('button[title^="Grid layout"]')?.getAttribute('title') || '',
57
+ }));
58
+
59
+ // Help overlay lists the shortcut
60
+ await page.keyboard.press('?');
61
+ await page.waitForTimeout(200);
62
+ const helpHasLayout = await page.evaluate(() => {
63
+ const heading = [...document.querySelectorAll('[role="dialog"] div')].find(el => el.textContent === 'Topology canvas');
64
+ if (!heading) return false;
65
+ return /Toggle layout/i.test(heading.parentElement?.textContent || '');
66
+ });
67
+
68
+ // Input-focus suppression — Cmd+K input, press l, no toggle
69
+ await page.keyboard.press('Escape');
70
+ await page.keyboard.press('Meta+k');
71
+ await page.waitForTimeout(200);
72
+ const palette = page.locator('input[placeholder*="Search"], input[type="search"], [role="dialog"] input').first();
73
+ await palette.focus().catch(() => {});
74
+ const beforeFocusToggle = await layoutPressed();
75
+ await page.keyboard.press('l');
76
+ await page.waitForTimeout(150);
77
+ const afterFocusToggle = await layoutPressed();
78
+ await page.keyboard.press('Escape');
79
+
80
+ await browser.close();
81
+ const results = {
82
+ startsGrid: initial.grid === 'true' && initial.ring === 'false',
83
+ lTogglesToRing: afterL.ring === 'true' && afterL.grid === 'false',
84
+ shiftLTogglesBack: afterShiftL.grid === 'true' && afterShiftL.ring === 'false',
85
+ titlesPresent: /l to toggle/i.test(titles.ring) && /l to toggle/i.test(titles.grid),
86
+ helpListsLayout: helpHasLayout,
87
+ inputFocusSuppressed: beforeFocusToggle.grid === afterFocusToggle.grid && beforeFocusToggle.ring === afterFocusToggle.ring,
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} layout key:`, JSON.stringify(results), `titles=`, titles);
91
+ process.exit(ok ? 0 : 1);